广西快乐双彩2017129期:Intercepting new Image().src requests

April 14th, 2017. Tagged: JavaScript

Maybe you're an attacker who sneaked in a little JavaScript to an unsuspecting site and would like to, well, sneak. Or maybe you want to know what exactly all these third-party analytics scripts are "calling home". Or maybe just for fun - wouldn't it be cool to intercept and log all requests made with new Image().src

new Image().src

It's a common pattern. A ping. Collect all the data then send it like:

new Image().src = '//example.org?time=' + Date.now() + '&...';

It's a strange API, that. As soon as you set a property (src), it does something. It sends a request. Weird. Oh well, it is what it is.

Interception

If it was any other normal method, like Array.prototype.map you can just overwrite it. But overwriting Image.prototype.src is a/ useless and b/ some browsers won't let you. Try Firefox:

> Image.prototype.src = "dude"
TypeError: 'set src' called on an object that does not implement interface HTMLImageElement.

Proxies!

JavaScript grows more powerful by the minute. What will they think of next? Proxies! That sounds like a lovely idea. Let's get to work!

First off, a copy of the original we're going to meddle with:

const NativeImage = Image;

Next, a "class" that will replace the original:

class FakeImage {
  constructor(w, h) {
    // magic!
  }
}

Finally, overwriting the original:

Image = FakeImage;

Now, how about that magic in the middle?

First, an instance of the original Image:

const nativeImage = new NativeImage(w, h);

Next, a handler that proxies calls to set and get methods and properties:

const handler = {
  set: function(obj, prop, value) {
    if (prop === 'src') {
      console.log('gotcha ' + value);
    }
    return nativeImage[prop] = value;
  },
  get: function(target, prop) {
    return target[prop];
  }
};

Finally, returning a Proxy instance that passes all there is to pass through the handler and onto the native Image instance.

return new Proxy(nativeImage, handler);

As you can see, all you need to do is check when src is being set and log it or do whatever with it. Interception complete!

Demo with all the code. Here it is in action in Firefox:

intercept

Hmm, someone might get suspicious

In the console:

> Image.name
"FakeImage"

Ouch.

Or even worse:

> Image.toString()

"function FakeImage(w, h) {
  const nativeImage = new NativeImage(w, h);
  const handler = {
  .....

... which should be more like native, all secret and such:

> NativeImage.toString()
"function Image() {
    [native code]
}"

Not good. An extra diligent developer might be checking for fakes before calling new Image(). (Who does that!? But still...)

Trying a naive approach won't cut it:

> Image.name = 'dude'
"dude"
> Image.name
"FakeImage"

Luckily, there's Object.defineProperty:

Object.defineProperty(FakeImage, 'name', {
  enumerable: false,
  configurable: false,
  writable: false,
  value: 'Image'
});

Testing:

> Image.name
"Image"

Tada!

Same with that toString() (and while at it, toSource() which is a Firefox invention):

Object.defineProperty(FakeImage, 'toString', {
  enumerable: true,
  configurable: false,
  writable: true,
  value: function() {
    return NativeImage.toString();
  }
});

if ('toSource' in NativeImage) { // FF extra
  Object.defineProperty(FakeImage, 'toSource', {
    enumerable: false,
    configurable: false,
    writable: true,
    value: function() {
      return NativeImage.toSource();
    }
  });
}

Testing now:

> Image.toString()
"function Image() {
    [native code]
}"
> Image.toSource()
"function Image() {
    [native code]
}"

Can you tell the fake? Don't think so.

Did you notice the NativeImage.toSource() call? Instead of hardcoding the [native code] mumbo-jumbo string, just ask the original. Especially given that browsers vary in the exact output.

Suspicious still...

What about toString() on instances? What about valueOf()?

> new Image().toString()
"[object Object]"
> new Image().valueOf()
Proxy { <target>: <img>, <handler>: Object }

Compare to the original:

> new NativeImage().valueOf()
<img>
> new NativeImage().toString()
"[object HTMLImageElement]"

Oh crap! No one must see them proxies and objects.

The fix is in the get method of the Proxy handler. Some properties are functions. Handle accordingly:

get: function(target, prop) {
  let result = target[prop];
  if (typeof result === 'function') {
    result = result.bind(target);
  }
  return result;
}

Boom! Like a charm!

fake

Fake it till you make it!

Recall the ole Object.prototype.toString.call call, y'all? People have used it since forever to tell, for example, real arrays from array-like things, such as arguments and NodeLists. (That was in the olden days before Array.isArray()).

Still very useful to tell, e.g. native JSON support vs a polyfill.

How does our little Image "polyfill" behave?

> Object.prototype.toString.call(Image)
"[object Function]"
> Object.prototype.toString.call(NativeImage)
"[object Function]"

Hm, alright. Next?

> Object.prototype.toString.call(new Image)
"[object Object]"
> Object.prototype.toString.call(new NativeImage)
"[object HTMLImageElement]"

Poop! We're caught in the act.

There's a fix. Wait for it. Symbol.toStringTag. Yup, that's right.

Back in the constructor, before you return...

const prox = new Proxy(nativeImage, handler);
try {
  prox[Symbol.toStringTag] = 'HTMLImageElement';
} catch(e){}
return prox;

What magic is this!

You're a wizard in a blizzard,
A mystical machine gun!

(Actually Chrome returns HTMLImageElement to begin with, so no fix is needed. And the fix is wrapped in try-catch, cause Chrome doesn't like it. Safari is more like Firefox returning "[object ProxyObject]" instead of "[object Object]" without the toStringTag fix.)

Cherry on top

No one checks the prototypes of the potentially useful things, but, hey, we're overachieving here.

Firefox and Safari agree:

> Object.prototype.toString.call(NativeImage.prototype)
"[object HTMLImageElementPrototype]"

Chrome, the oddball:

Object.prototype.toString.call(NativeImage.prototype)
"[object HTMLImageElement]"

But all agree that our Image is smelly:

> Object.prototype.toString.call(Image.prototype)
"[object Object]"

Fix:

FakeImage.prototype[Symbol.toStringTag] = NativeImage.prototype.toString();

Again, not hardcoding a string, but giving the browser-dependent different output, piggybacking on the native Image.

Yup!

Fake it till you make it. Result to play with.

Our fake is still recognizable in the browser console (like console.log(new Image())) but your victim (unsuspecting logging-reporting-ads-analytics script) is code. It doesn't look at the console. An Object.prototype.toString.call() is usually the extend of all checks for nativeness. If that.

Bye!

Tell your friends about this post: Facebook, Twitter, Google+

Sorry, comments disabled and hidden due to excessive spam.

Meanwhile, hit me up on twitter @stoyanstefanov


  • 运城市在长三角招商引资149.9亿元 2019-04-08
  • 济南五胞胎雪虎宝宝亮相 四雌一雄萌态十足 2019-04-08
  • 去产能迎年中考 煤炭、钢铁企业债务问题依然存在 2019-04-07
  • 最高检等四部门出台意见 指导依法办理恐怖活动和极端主义犯罪案件 2019-04-07
  • 广州市黄埔区人民法院公告专栏 2019-03-28
  • 这个“海之宁”是个死抱着相对论旧谬误不放,疯狂反对科学新真理的跳梁“小丑”,这个跳梁“小丑”根本就不懂得尊重客观事实及其规律,总是无视、脱离、歪曲客观... 2019-03-22
  • 如何理解孔子这句话?北大教授胡军动情论生死 2019-03-21
  • 海外舆论关注中国最新军备 称赞习近平主席强军号令 2019-03-19
  • 女性之声——全国妇联 2019-03-19
  • 美司法部科米在希拉里邮件门调查中存在过失 2019-03-17
  • “四新”彰显党的十九大思想灵魂和精髓要义 2018-08-17
  • 热身赛-U17女篮73-68立陶宛 杨舒宇19分刘禹彤13分 2018-08-16
  • 七月流火:一半是甜蜜 一半是仁慈 2018-08-15
  • “嫦娥”飞向月球背面,将会揭开哪些秘密? 2018-08-15
  • 一代枭雄身后事:“曹操墓”认定过程缘何一波三折? 2018-08-04
  • 765| 515| 152| 183| 827| 452| 302| 728| 735| 704|