web integration patterns in the era of html5
DESCRIPTION
Presentation given at OWASP BeNeLux November 2012 and GeekMeet Stockholm January 2013. Covers secure and robust integration patterns for the web using cross origin resource sharing (CORS), sandboxed iframes, and the postMessage API.TRANSCRIPT
Web Integration Patterns In the Era of HTML5
@johnwilander atOWASP BeNeLux 2012, Leuven, BelgiumGeekMeet Stockholm, Sweden, 2013
@johnwilander
Part IThe Historyof Web Integration
@johnwilander
Remember when we had pages?
@johnwilander
… maybe a persistent menu system?
Menu
@johnwilander
Menu
… and 3rd party popus?
@johnwilander
You still see such sites.
@johnwilander
But product owners don’t want them
anymore.
@johnwilander
Menu
So we started loading things when we needed them, using Ajax and DOM manipulation with JavaScript.
@johnwilander
Menu
@johnwilander
Menu
The problem was that Ajax was only allowed to the same origin as the page. Ergo, no third party integration.
@johnwilander
But the same-origin policy didn’t apply to all
web elements.
@johnwilander
<form action="http://3rdparty.net"></form>
<img src="http://3rdparty.net">
<script src="http://3rdparty.net"></script>
@johnwilander
<form action="http://3rdparty.net"></form>
<img src="http://3rdparty.net">
<script src="http://3rdparty.net"></script>
The form tag actually had cross-domain intentions and is used for integration with e.g. payment providers such as PayPal. You post a purchase form from maindomain.com and enter the checkout process at paypal.com.
@johnwilander
<form action="http://3rdparty.net"></form>
<img src="http://3rdparty.net">
<script src="http://3rdparty.net"></script>The img tag has been used heavily for cross-origin web tracking such as Google Analytics and Omniture.
@johnwilander
Image srchttp://www.google-analytics.com/__utm.gif?utmwv=5.3.8&utms=1&utmn=1854976096&utmhn=appsandsecurity.blogspot.se&utmcs=UTF-8&utmsr=1920x1200&utmvp=1268x500&utmsc=24-bit&utmul=sv&utmje=1&utmfl=11.5%20r31&utmdt=Apps%20and%20Security&utmhid=2120874108&utmr=-&utmp=%2F&utmac=UA-6984098-5&utmcc=__utma%3D60396157.1187947367.1352854596.1353863861.1353882126.6%3B%2B__utmz%3D60396157.1352933082.2.2.utmcsr%3Dgoogle%7Cutmccn%3D(organic)%7Cutmcmd%3Dorganic%7Cutmctr%3D(not%2520provided)%3B&utmu=q~
Query string parametersutmwv:5.3.8utms:1utmn:1854976096utmhn:appsandsecurity.blogspot.seutmcs:UTF-8utmsr:1920x1200utmvp:1268x500utmsc:24-bitutmul:svutmje:1utmfl:11.5 r31utmdt:Apps and Securityutmhid:2120874108utmr:-utmp:/utmac:UA-6984098-5utmcc:__utma=60396157.1187947367.1352854596.1353863861.1353882126.6;+__utmz=60396157.1352933082.2.2.utmcsr=google|utmccn=(organic)|utmcmd=organic|utmctr=(not%20provided);utmu:q~
@johnwilander
<form action="http://3rdparty.net"></form>
<img src="http://3rdparty.net">
<script src="http://3rdparty.net"></script>
The script tag became the workhorse for Ajax-like cross-origin sharing by means of the popular jsonp hack.
@johnwilander
Jsonp =Json with Padding
(should be ”Json, Please?”)
@johnwilander
maindomain.com
3rdparty.net
<html>
<script> function receive(data) { var json = JSON.parse(data); ... };</script>
</html>
Jsonp
@johnwilander
maindomain.com
3rdparty.net
<html>
<script> function receive(data) { var json = JSON.parse(data); ... };</script>
</html>
Inserts a new script element toretrieve data cross-domain
Jsonp
@johnwilander
maindomain.com
3rdparty.net
<html>
<script> function receive(data) { var json = JSON.parse(data); ... };</script>
<script src=”3rdparty.net/getData?cb=receive”></script>
</html>
Inserts a new script element toretrieve data cross-domain.
Jsonp
@johnwilander
maindomain.com
3rdparty.net
<html>
<script> function receive(data) { var json = JSON.parse(data); ... };</script>
<script src=”3rdparty.net/getData?cb=receive”></script>
</html>
Jsonp
@johnwilander
maindomain.com
3rdparty.net
<html>
<script> function receive(data) { var json = JSON.parse(data); ... };</script>
<script src=”3rdparty.net/getData?cb=receive”></script>
</html>
The third party domain ”pads” theresponse data with a call to the given callback function.
Jsonp
@johnwilander
maindomain.com
3rdparty.net
<html>
<script> function receive(data) { var json = JSON.parse(data); ... };</script>
<script src=”3rdparty.net/getData?cb=receive”></script>
</html>
receive({ ”prop1”: ”data”, ”prop2”: ”data”, ”prop3”: ”data”, ”prop4”: ”data”});
Jsonp
@johnwilander
maindomain.com
3rdparty.net
<html>
<script> function receive(data) { var json = JSON.parse(data); ... };</script>
</html>
{ ”prop1”: ”data”, ”prop2”: ”data”, ”prop3”: ”data”, ”prop4”: ”data”}
receive({ ”prop1”: ”data”, ”prop2”: ”data”, ”prop3”: ”data”, ”prop4”: ”data”});
Jsonp
@johnwilander
maindomain.com
3rdparty.net
<html>
<script> function receive(data) { var json = JSON.parse(data); ... };</script>
</html>
{ ”prop1”: ”data”, ”prop2”: ”data”, ”prop3”: ”data”, ”prop4”: ”data”}
receive({ ”prop1”: ”data”, ”prop2”: ”data”, ”prop3”: ”data”, ”prop4”: ”data”});
Jsonp
Data is fetched asynchronously, cross-domain.
@johnwilander
http://johnwilander.blogspot.com/feeds/posts/default?callback=yourCallback&alt=json-in-script&start-index=1&max-results=5
Jsonp example:Blogspot Integration
@johnwilander
Jsonp example:Blogspot Integration
http://johnwilander.blogspot.com/feeds/posts/default?callback=yourCallback&alt=json-in-script&start-index=1&max-results=5
@johnwilander
Jsonp example:Blogspot Integration
http://johnwilander.blogspot.com/feeds/posts/default?callback=yourCallback&alt=json-in-script&start-index=1&max-results=5
@johnwilander
http://johnwilander.blogspot.com/feeds/posts/default?callback=yourCallback&alt=json-in-script&start-index=1&max-results=5
Jsonp example:Blogspot Integration
Just paste the above in your browser and run
@johnwilander
jQuery's ”crossDomain” Ajax is Jsonp
http://api.jquery.com/jQuery.ajax/
@johnwilander
Jsonp Is Inherently Dangerous
Benign server response: givenCallback({”prop1”: ”val1”});
Compromised server response: givenCallback({”prop1”: ”val1”}); evilCode(); moreEvilCode();
@johnwilander
Jsonp Is Inherently Dangerous
Benign server response: givenCallback({”prop1”: ”val1”});
Compromised server response: givenCallback({”prop1”: ”val1”}); evilCode(); moreEvilCode();
Note that a client doing jsonp calls has nochance to see/filter the reponse before it’s executed.The client has to blindly trust whatever code is returned.
@johnwilander
Jsonp and CSRF
Also, all relevant cookies for the Jsonp service are sent with the request opening up for non-blind CSRF. Non-blind as in the attacker getting the response in full.
@johnwilander
Demo jsonp
@johnwilander
Then there is the document.domain
trick.
@johnwilander
main.1-liner.org
other.1-liner.org
@johnwilander
main.1-liner.org
other.1-liner.org
@johnwilander
main.1-liner.org
other.1-liner.org
@johnwilander
main.1-liner.org
other.1-liner.org
Same-origin policyprohibits evendifferent subdomainsfrom interacting
@johnwilander
main.1-liner.org
document.domain = ”1-liner.org”;
other.1-liner.org
document.domain = ”1-liner.org”;
@johnwilander
main.1-liner.org
document.domain = ”1-liner.org”;
other.1-liner.org
document.domain = ”1-liner.org”;
@johnwilander
main.1-liner.org
document.domain = ”1-liner.org”;
main.1-liner.org
But …
@johnwilander
Demo document.domain
@johnwilander
In summary, web integration used to be
all-or-nothing.
@johnwilander
Part 2New Integration Technologies
@johnwilander
CORS var req = new XMLHttpRequest(); req.open(method, crossDomainUrl); req.send();
Sandboxed iframes <iframe src="http://3rdparty.net" sandbox="allow-scripts"></iframe>
postMessage API otherFrameOrWindow.postMessage( ’{"action": "purchase", "item": 34443}’, "http://3rdparty.net");
@johnwilander
Cross-Origin Resource Sharing, CORS
10+Partial supportin IE8 and IE9
15+
5.1+
22+
12+
3.2+
2.1+
http://caniuse.com/#search=cors
@johnwilander
CORS is basically cross-origin Ajax
@johnwilander
CORS
• The server has to authorize requests in response headers: Access-Control-Allow-Origin allowed.domain.com
• HTTP GET and POST are like normal Ajax
• Other HTTP methods or GET and POST with custom headers require a preflight
• The client has to explicitly send cookies: xhr.withCredentials = true;
@johnwilander
CORS-Unaware Server
Let's see what 2012 looks like if we are still running a …
@johnwilander
CORS-Unaware ServerClient Server
Ajax GET
Looks likea normal GET.Has a reliable Origin header.
If no authori-zation logic is present it just responds.
If no allowing CORS header client gets no response.
No cookies
@johnwilander
CORS-Unaware ServerClient Server
Ajax POST
Looks likea normal POST.Has a reliable Origin header.
If no authori-zation logic is present it just responds.
If no allowing CORS header client gets no response.
No cookies
@johnwilander
CORS-Unaware ServerClient Server
Looks like a normal request.Has a reliable Origin header.
If no authori-zation logic is present it just responds.
If no allowing CORS header client gets no response.
CookiesAjax + withCredentials = true;
@johnwilander
CORS-Unaware ServerClient Server
OPTIONS request with Access-Control-Request-Method: GET/POST
If no authori-zation logic is present it just responds.
If no allowing CORS header client never sends request.
Preflight requestAjax + setRequestHeader('X-Requested-With','XMLHttpRequest');
@johnwilander
CORS-Aware ServerClient Server
Looks like a normal request.Has a reliable Origin header.
Authorizes the calling origin and includesAccess-Control-Allow-Origin
If allowing CORS header client gets the response.
CookiesAjax + withCredentials = true;
@johnwilander
This means …
• Attackers can now do CSRF without img tags or form posting.
• Servers that don’t check origin headers have no clue it’s a cross-origin Ajax call.
• Custom headers including the Ajax header are effectively dead in the CORS case since developers want to avoid preflights.
@johnwilander
DEMO CORS
@johnwilander
Sandboxed iframes
10+Rumored to
not be there yet
17+
5.1+
22+
Nosupport
4.2+
2.2+
http://caniuse.com/#feat=iframe-sandbox
@johnwilander
main.1-liner.org
<iframe src=”main.1-liner.org” sandbox>
<script></script><form></form>
</iframe>
Sandboxed iframe
@johnwilander
main.1-liner.org
<iframe src=”main.1-liner.org” sandbox=”allow-same- origin”>
<script></script><form></form></iframe>
Sandboxed iframe
@johnwilander
main.1-liner.org
<iframe src=”main.1-liner.org” sandbox=”allow-same- origin allow-scripts”>
<script></script><form></form></iframe>
Sandboxed iframe
@johnwilander
main.1-liner.org
<iframe src=”main.1-liner.org” sandbox=”allow-same- origin allow-scripts allow-forms”><script></script><form></form></iframe>
Sandboxed iframe
@johnwilander
DEMO Sandboxed iframe
@johnwilander
Partialsupport inIE8 – IE10
15+
5.1+
22+
12+
3.2+
2.1+
http://caniuse.com/#search=postMessage
postMessage
@johnwilander
postMessage
• Allows string-based communication between frames and windows
• You need a handle to the target to be able to send a message
• The recipient whitelists origins from which it accepts messages
@johnwilander
postMessage// Sending messages requires a handle to the receiving endvar popup = window.open("http://other.1-liner.org", "_blank");popup.postMessage("Luke, I am your father.", "http://other.1-liner.org");
// Receiving messages requires an event listenerreceiveMessage = function(event) { if (event.origin !== "http://other.1-liner.org") { return; } console.log(event.data);}window.addEventListener("message", receiveMessage, false);
@johnwilander
postMessage// Sending messages requires a handle to the receiving endvar popup = window.open("http://other.1-liner.org", "_blank");popup.postMessage("Luke, I am your father.", "http://other.1-liner.org");
// Receiving messages requires an event listenerreceiveMessage = function(event) { if (event.origin !== "http://other.1-liner.org") { return; } console.log(event.data);}window.addEventListener("message", receiveMessage, false);
Handle to receiving
window or frame
Target origin makes sure the window
or frame hasn’t been redirected
@johnwilander
postMessage// Sending messages requires a handle to the receiving endvar popup = window.open("http://other.1-liner.org", "_blank");popup.postMessage("Luke, I am your father.", "http://other.1-liner.org");
// Receiving messages requires an event listenerreceiveMessage = function(event) { if (event.origin !== "http://other.1-liner.org") { return; } console.log(event.data);}window.addEventListener("message", receiveMessage, false);
Receiving event listener has to check the message
comes from a trusted origin
@johnwilander
DEMO postMessage
@johnwilander
Part 3How To Use the Technologies Securely
@johnwilander
First stop using …
• Jsonp
• document.location trick
• img tag trick
@johnwilander
Implement CORS Server-Side Now
Client ServerAlways check origin header.
Authorize the calling origin based on a whitelist.
If allowing CORS header client gets the response.
CookiesAjax + withCredentials = true;
@johnwilander
HTML
HTML
HTML
CSS
CSS
JavaScript
JavaScript
CSS
JavaScript
Bad Web Architecture
@johnwilander
HTML
CSS on file
JavaScript on file
Import
CSS
JavaScript
Good Web Architecture
@johnwilander
A good separation of content, code, and style
allows for CSP.
@johnwilander
But our legacy applications are a mess.
@johnwilander
HTML
CSS
HTMLCSS
JavaScript
JavaScript
JavaScript
HTML CSS
Placing legacy web and third party webin iframes allowsthe new code to runwith CSP.
iframe
@johnwilander
main.1-liner.org
HTML
CSS
HTMLCSS
JavaScript
JavaScript
JavaScript
HTML CSS
Then set the sandbox directive without allow-same-origin to leverage the same-origin policy for protection fromvulnerabilities in legacy or third party code.
Sandboxed iframe
main.1-liner.org… or …3rdparty.net
@johnwilander
main.1-liner.org
HTML
CSS
HTMLCSS
JavaScript
JavaScript
JavaScript
HTML CSS
A backwards compatible alternative is to move legacy code to anothersubdomain to leverage the same-origin policy for protection fromvulnerabilities in legacy code.
iframe
legacy.1-liner.org
@johnwilander
new.1-liner.org
HTML
CSS
HTMLCSS
JavaScript
JavaScript
JavaScript
HTML CSS
But beware. Legacy code typically doesn’t relocate gracefully. So you might have to keep its domain and get a new subdomain for your new code.
iframe
main.1-liner.org
@johnwilander
main.1-liner.org
HTML
CSS
HTMLCSS
JavaScript
JavaScript
JavaScript
HTML CSS
Use the postMessage API for communicating between your iframes and your main window.
Sandboxed iframe
main.1-liner.org… or …3rdparty.net
postMessage
@johnwilander
main.1-liner.org
CORS from/to 3rdparty.net
HTML
CSS
HTMLCSS
JavaScript
JavaScript
JavaScript
HTML CSS
Sandboxed iframe
main.1-liner.org… or …3rdparty.net
postMessage
Finally, use CORS when retrieving content from third parties and make sure to encode it right before injecting it to the DOM.
@johnwilander
Thanks!