alf.nu / @steike

Adobe Flash XSS traps

Some scary implementation bugs in ExternalInterface that easily lead to XSS (CVE-2014-0531?, CVE-2014-0532?, CVE-2014-0533?)

These bugs have been around for a long time, but were finally partially patched in the 14.0.0.25 update. Beware that the patch will not help unless you recompile your Flash files with a new target version.

If you're using click-to-play (good for you), you'll need to enable this: .

Spoilers

If you don't want to see the actual exploit strings, you can hide them.

Exceptions

If some JavaScript calls a function exported by the Flash file, and that function throws an exception, the exception is re-thrown in JavaScript land by evaluating throw "<string>" with no escaping whatsoever.

Flash land
  ExternalInterface.addCallback('throwException',
     function(s) {
       throw new Error(s)
     });
JavaScript land
var evilData = input();

SWF.throwException(evilData);

Flash file version 24 (choose 24 / 25)

Exceptions, part 2

Exceptions can also be thrown in the other direction. When running JavaScript code from Flash, the results are sent back to the plugin as XML; in particular the exception is serialized as <exception>...</exception>, again with no escaping. This would be vulnerable even if you've removed all " and \ characters.

Flash land
  ExternalInterface.addCallback('sendDataToJS', 
    function(s) { 
      ExternalInterface.call('jsFunction', s);
    });</pre>
JavaScript land
var evilData = input();

// evilData contains no " or \ now

window.jsFunction = function(s) {
  throw s;
};

SWF.sendDataToJS(evilData);

Flash file version 24 (choose 24 / 25)

Parameters to ExternalInterface.call()

This is the well-known backslash bug: parameter are JSON-encoded with a broken JSON-encoder that doesn't escape backslashes. Exploiting this is complicated by the fact that the string is evaluated like this:

try { 
  __flash__toXML(jsFunction("<data here>"));
} catch (e) { 
  "<exception>" + e + "</exception>"; 
}
Flash land
  ExternalInterface.addCallback('sendDataToJS', 
    function(s) { 
      ExternalInterface.call('jsFunction', s);
    });
JavaScript land
var evilData = input();

window.jsFunction = function(s) {
  alert('Got ' + s);
};

SWF.sendDataToJS(evilData);

Flash file version 24 (choose 24 / 25)

Return values from ExternalInterface.addCallback() callbacks

Same encoder as the last one, just in a place that is less likely to be caught by a reviewer that knows about ExternalInterface.call()...

Flash land
  ExternalInterface.addCallback('returnString',
    function(s) {
      return s;
    });
JavaScript land
var evilData = input();

alert('Got ' + SWF.returnString(evilData));

Flash file version 24 (choose 24 / 25)

Fix

The backslash bug has been known for years, so many code bases have workarounds like

return s.replace(/\\/g, '\\\\');

Now this code depends on the fact that the plugin doesn't escape, so if Adobe suddenly starts escaping things properly, this code would now double-escape — which might even introduce new vulnerabilities if there is nested data. As lcamtuf mentions, Adobe decided against patching this bug in 2011.

By 2014, they've apparently changed their mind, and with 14.0.0.25, a version-gated fix was released: Flash files compiled with a version number ≥ 25 get the correct marshalling behavior.

User with old Flash playerUser with new Flash player
Old fileXSSXSS
New fileXSSOK
Old file with workaroundOKOK
New file with workaroundOKGarbled

This is... hardly optimal. If you need to be compatible with both, the easiest way out is probably to assume the channel explodes on \ and apply some kind of transfer encoding that never uses it.

Complaints to @steike or @steike.