Web Integration Patterns In the Era of HTML5 @johnwilander at OWASP BeNeLux 2012, Leuven, Belgium GeekMeet Stockholm, Sweden, 2013
May 10, 2015
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!