Writeup KMA CTF 2024

pickleball

Bài này cho mình một trang web, có tính năng Signup và Signin đều không dùng được nên mình dirsearch và thấy được part 1 của flag trong file robots.txt :v

Vì đây chỉ là bài warmup nên mình đoán được là sẽ tìm các part tiếp theo trong các file public static của web.

🚩 Flag: KMACTF{p1Ckleb4ll_WitH-uU_piCklepal_5a6b89113abb}

malicip

Bài này cho mình một trang web có tính năng check và list ip. Tại endpoint /check-ip ta có đoạn code sau:

Ta có thể thấy mục đích của bài này cực kỳ đơn giản là bypass được hàm ip_address tại endpoint /check-ip từ đó khai thác SQL injection.

Mình dùng Pycharm để dựng local debug và nhảy vào source code của module ipaddress để xem cách nó check ip.

Sau một lúc debug thì mình phát hiện nếu IP address là IPv6 thì trước khi convert string sang ip để check valid thì ta sẽ đi qua method _split_scope_id

Tại hàm _split_scope_id sẽ split IPv6 thành 2 phần với delimiter là dấu %. Phần đầu tiên sẽ là địa chỉ IPv6 được module xử lý tiếp để check valid, còn phần sau dấu % được xem là scope id và bị bỏ qua không được check

Dựa vào hành vi này ta có thể bypass hàm ip_address và trigger SQL injection với payload sau:

Dump table name

Dump column name

Đọc flag

🚩Flag: KMACTF{actually__this_flag-is_not_so_malicious_but_the_ipv6_is}

Spring Up

Bài này cho mình một trang web chạy bằng spring boot với tính năng upload file và đọc file, mục tiêu là RCE. Nhìn sơ qua source code thì mình thấy có thể upload và ghi đè file vào bất kỳ đường dẫn nào nào (trừ các đường dẫn trong blacklist) và có thể đọc file từ bấy kỳ đường dẫn nào.

Upfile vào đường dẫn bất kỳ

Black list upload file

Read file từ đường dẫn bất kỳ

Vì blacklist là không thể bypass hoặc ít nhất với khả năng hiện tại của mình thì mình không thể bypass được nên mình tìm một đường dẫn khác mà có thể lợi dụng upload file để RCE.

Sau một hồi tìm kiếm thì mình đã tìm ra câu trả lời từ 2 bài blog sau:

Ý tưởng exploit là ta sẽ replace file charsets.jar trong thư mục lib của JDK. Sau khi thay thế thì gọi đến

GET / HTTP/1.1
Accept: text/html;charset=<custome_charset>

Khi Header Accept có giá trị charset thì Spring sẽ parse header lấy ra giá trị <custome_charset> và lookup

Vì ta đã thay thế lib charset bằng malicous charsets.jar. Nên ta chỉ cần gọi đến charset mà ta đã modify để RCE -> Win

Tuy nhiên còn 1 vấn đề cần quan tâm là ta chỉ có thể exploit khi spring chưa gọi đến lib charset, ngay khi spring đã gọi đến lib này rồi thì cho dù ta có replace thế nào đi nữa exploit cũng sẽ không hoạt động. Nên ta sẽ có 1 cơ hội duy nhất để RCE.

Ý tưởng là như vậy bây giờ thì cùng exploit thoi.

Đầu tiên để biết được đường dẫn JDK của chall mình chạy đoạn code java sau lên docker

System.getProperty("sun.boot.class.path")

Khi build và chạy mình có kết quả

/usr/lib/jvm/java-1.8-openjdk/jre/lib/resources.jar
/usr/lib/jvm/java-1.8-openjdk/jre/lib/rt.jar
/usr/lib/jvm/java-1.8-openjdk/jre/lib/sunrsasign.jar
/usr/lib/jvm/java-1.8-openjdk/jre/lib/jsse.jar
/usr/lib/jvm/java-1.8-openjdk/jre/lib/jce.jar
/usr/lib/jvm/java-1.8-openjdk/jre/lib/charsets.jar
/usr/lib/jvm/java-1.8-openjdk/jre/lib/jfr.jar
/usr/lib/jvm/java-1.8-openjdk/jre/classes

Ta sẽ tiến hành replace file /usr/lib/jvm/java-1.8-openjdk/jre/lib/charsets.jar bằng file charsets.jar độc hại trong link này. File này dùng để test exploit và khi trigger sẽ tạo file ở thư mục /tmp

Vì malicous charsets.jar overwrite hầu hết các charset bằng code RCE nên ta có thể gửi request trigger với bất kỳ charset hợp lệ nào, ví dụ như sau:

Nếu trigger thành công ta sẽ nhận response có status code 500, còn nếu failed ta sẽ nhận response có status code 406

RCE thành công

Bây giờ ta chỉ cần thay đổi code thực thi để đọc flag thôi. Tuy nhiên đây là bước khiến mình mất nhiều thời gian và bị lỗi nhiều nhất. May mắn khi build file jar bằng java trong môi trường docker thì thành công luôn.

Các bước build của mình như sau:

  • Git clone code gen payload về máy docker

  • Chỉnh sữa code RCE để đọc flag, mình chỉnh file IBM33722.java để cat flag vào thư mục /tmp/endy

  • Cd vào thư mục charsets và tiến hành build file jar với các câu lệnh sau

mkdir out
javac -d out src/sun/nio/cs/ext/ExtendedCharsets.java src/sun/nio/cs/ext/IBM33722.java
jar cfm charsets.jar ./src/META-INF/MANIFEST.MF -C out .

Khi build ra file class thì ta sẽ bị 7 warning, ta cứ mặc kệ tiếp tục build ra file jar thì payload vẫn hoạt động bình thường

Khi này mình có được file charsets.jar , việc cuối cùng là tải file về máy, upload vào challage và lấy flag thôi

Upload file

Trigger

Đọc flag

🚩Flag: KMACTF{ayoooo00oo0ooo0o0o00o0ooooo000oo0oo0o00000}

Not So Secure

Bài này cho ta một trang web như sau

Khi nhập tên và password thì ta được cấp 1 jwt và redirect đến dashboard

Tại đường dẫn robots.txt thì ta có được hint như sau

Since the algorithm as described in the header is ES256, the curve is P-256 (see: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm#Signature_generation_algorithm). 
Logging in with different usernames gives a signature with the same prefix, so there's likely a resuse of k-values. The last 32 bytes of the signature is a SHA256 hash is of the token header and payload.

Sau một vài giờ cố gắng để hiểu thuật toán trong vô vọng thì mình có được đoạn script để generate jwt theo ý muốn như sau bằng cách search gg ≽^•⩊•^≼:

import base64
import hashlib

"""
Disclaimer: I did not solve this challenge under the CTF (although I was close). This was solved after the CTF had finished with some help from the challenge author.
I am sharing this as a writeup to help others who like me were curious about the solution to this challenge, yet since so few actually solved it there has been
no solution made public until now.

Since the algorithm as described in the header is ES256, the curve is P-256 (see: https://ldapwiki.com/wiki/ES256). Logging in with different usernames gives a signature with the same prefix,
so there's likely a reuse of k-values. The last 32 bytes of the signature is a SHA256 hash of the token header and payload.

I recommend you to read https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm#Signature_generation_algorithm,
which details an attack on repeated k-values, and is implemented in this python script. Reading this will give you a better understanding
of this script.

Suprisingly, you didn't have to mess with curves of any kind to solve this challenge, just some basic modular artihmetic, web stuff, and perhaps some research
"""

# curve parameters of P-256 (see: https://ldapwiki.com/wiki/P-256)
n = 115792089210356248762697446949407573529996955224135760342422259061068512044369
L_n = len(bin(n)[2:])

def parse_signature(token):
    t = token.split(".")

    sig = base64.urlsafe_b64decode(t[2]+"===")
    m = t[0]+"."+t[1]

    e = hashlib.sha256(m.encode()).digest()

    z = int(bin(int(e.hex(),16))[2:L_n+2],2)

    r = int(sig[:-32].hex(),16) # start of the signature is r, which is defined as the x-component of the point k*G on the curve
    s = int(sig[-32:].hex(),16) # the last 32 bytes of the signature is the s value
    return r,s,z

# two tokes generated for the usernames "admim" and "admio" respectively
token1 = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImVuZHkxIiwicXVpcmsiOiJjaXZpbGlhbiJ9.B5Tj6WgyWib7Qz0g4wXSS2pQMuh_sKvANfK4pEjJq_I97k-29VvImQoR8zcxzhO_OWLGmsWaoSdm6V7AXLanbA"
token2 = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImVuZHkyIiwicXVpcmsiOiJjaXZpbGlhbiJ9.B5Tj6WgyWib7Qz0g4wXSS2pQMuh_sKvANfK4pEjJq_J_OBrqlPyi8IdiAHDHZXLlMsb9iHkXlWj7jnN1o_63zw"

# calculate the signature parameters from the tokens
r1, s1, z1 = parse_signature(token1)
r2, s2, z2 = parse_signature(token2)

assert r1==r2 # checks if the r-values match for the two signatures, if they do (spoiler alert: they do), then the same k-value is used and a signature can be forged

# recover the private key d from the two signatures using modular arithmetic (read the wikipedia article)
k = ((z1-z2)*pow((s1-s2)%n,-1,n))%n # k = (z-z')/(s-s') (mod n)
d = ((s1*k-z1)*pow(r1,-1,n))%n

# header and payload of the JWT
admin_token = b"eyJhbGciOiAiRVMyNTYiLCAidHlwIjogIkpXVCJ9." + base64.urlsafe_b64encode(b'{"username": "endy3","quirk":"HACKING"}')
admin_token = admin_token.replace(b"=",b"")

# calculate the signature using the recovered private key (again, read the wikipedia article)
e = hashlib.sha256(admin_token).digest() # e = HASH(m)
z = int(bin(int(e.hex(),16))[2:L_n+2],2) # z = e[:L_n] (in bits)
r = r1
s = (pow(k,-1,n)*(z+r*d))%n # s = (z+rd)/k (mod n)

# putting r and s together and encoding
signature = bytes.fromhex('0'+str(hex(r)[2:])) + bytes.fromhex(str(hex(s)[2:]))
admin_token += b"." + base64.urlsafe_b64encode(signature)

# print forged token
print(admin_token)
```

Cre: https://ctftime.org/writeup/26445

Mình chỉ cần thay 2 jwt làm mẫu và thêm padding vào giá trị r là code chạy ngon lành. Mình chỉ cần thay giá trị quirk thành HACKING là sẽ mở khóa được chức năng mới của web.

Tại đây thì mình có thể upload được file docx và trang web sẽ trả về response như sau khi mình upload thành công

Dựa vào response này mình xác định sẽ có 3 khả năng để exploit là

  • XSS

  • XXE

  • Zipslip/Symlink

Mình fuzz cả 3 trường hợp trên nhưng đều không ra kết quả khả quan. Sau một hồi bế tắc thì mình để ý thấy khi upload thành công web sẽ đếm số lượng words trong file docx, mà trong cấu trúc file docx ta có thể thay đổi giá trị của tag <Words> trong file docProps/app.xml -> có thể XXE và đọc file

Mình tạo 1 file docx và unzip để bắt đầu chỉnh sữa file docProps/app.xml

Sau đó mình nén lại thành file docx và upload

🚩 Flag: KMACTF{3cd54_n0nc3_r3u53_4774ck_4nd_xx3_up104d}

End.

Last updated