# KCSC CTF 2024
## Bài Ka Tuổi Trẻ (200 points)
- Đây là một bài của anh `null001` với một đoạn code python khá ngắn
```
from flask import Flask, request, redirect
from os import access, R_OK, stat
from os.path import isfile, join, normpath
import regex
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")
```
- Vị trí của flag là /flag.txt

- Chall chỉ có một chức năng duy nhất là get ``/`` với param là `file` sau đó nối chuỗi với thư mục ./static sẽ check xem file đó có tồn tại hay không và mở file này -> check tên file với regex với timeout tối đa là 2s và nếu tên không chứa `flag` thì sẽ hiển thị nội dung.
- Có thể thấy rõ là trang web dính lỗi path traversal.
- 
- Ban đầu có vẻ hầu hết mọi người đề thi theo hướng bypass regex để khai thác lỗi ở trên nhưng có vẻ không khả thi.
- Khi nhận được hint 1 thì ta thấy anh `null001` có đề cập đến `file descriptor` thì theo như mình tìm hiểu là trong linux nó sẽ có các tiến trình có các phần stdin stdout và stderror, khi thực hiện một tiến trình ví dụ như đọc file thì unix sẽ tạo một file trong ``/proc/{id}/fd/*`` thì có thể hiểu nó như một symlink hoặc là cache trỏ đến file mà mình mở ra đấy.
- Có thể thấy `Try except` mở file với điều kiện so sánh regex tối đa 2s nhưng việc mở file đã được thực hiện trước đó. Và khi mở file thì trong proc sẽ mở 1 tiến trình để trỏ tới cho đến khi with open file vẫn còn hoạt động.

- Mình sẽ để tên file dài nhất có thể để có thể đạt gần tới timeout 2s nhằm mục đích là mình sẽ race condition để đọc được file /flag.txt được trỏ đến ở trong `fd/` được tạo ra trong tiến trình này.

- Dùng intruder mình sẽ random `/proc/{id}/fd/{num}` từ 1 đến 20 hoặc có thể nhiều hơn nếu chưa được-> kết quả mình race được flag như dưới đây


Flag : `KCSC{D1eu_tuу3t_v01_n@m_o_n0i_ch1nh_ta_ch@ng_can_tim_d@u_xa}`
## Itest develop(500 points)
- Bài này mình hiểu rõ vấn đề và ngồi từ 10h đến hết giải để tìm configKey và browserKey trong logs của Safe example browser nhưng không có sau mới nhớ ra nó nằm trong default setup tool.
- Tiếp tục là một bài whitebox của anh `meulody` code trong 1 tiếng @@.

- Đề bài cho mình file Safe-Exame-Browser giống như đúc ở `kma` version `3.7.0.682` một file .seb để truy cập và một source code của server có chứa flag mà 1 middleware khá mạnh mẽ.
- Thì đầu tiên ta sẽ cài sau mở file .seb và cơ chế seb của trường là chạy với quyền administrator và tắt hết mấy chương trình khác + không chụp ảnh màn hình + không copy cho nên mình xin phép chụp ảnh với điện thoại, và ở đây thì đã cấu hình không tắt mấy chương trình khác rồi.
- Có thể thấy browserKey và configKey ở đây

- Khi vào trang server truy cập như sau:

- Chúng ta cùng view qua một chút source của server thì có phần quan trọng là middleware này sẽ lấy mã configKey và browserKey để cộng chuỗi với url rồi băm sha-256 để so sánh với 2 header mà người dùng dùng exame browser cung cấp nếu đúng thì có thể đi đế endpoint của ứng dụng nếu sai sẽ trả ra như ở ảnh trên.

- Thêm 2 endpoint quan trọng để có flag:

- Bây giờ chúng ta sẽ truy cập vào `/get-flag` sau đó 1 session được tạo và redirect đến `/flag/:uuid` vừa tạo và nhận flag.
- Vậy có vẻ mấu chốt của vấn đề đó là phải vượt qua được middleware.


- mở devtool console lên nhận được header là:
```
X-Safeexambrowser-Configkeyhash:
1ccc354e52405d81f0e214ad4a14648858e1f365a150d80698cace88c1e48af4
X-Safeexambrowser-Requesthash:
a7372538471a9010ad9d4d9fd5f7a46b0a0bf03d0f774730bdf20200e09f52ec
```
- Sau đó mở trang với 2 header ở trên thì đã load được trang web vì vượt qua middleware.

- Quan sát thấy CSP như sau: `default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests`
- Directive `frame-ancestors 'self'` chỉ cho phép trang web được nhúng (embedded) trong các khung (frames) từ cùng nguồn gốc. Điều này ngăn chặn các cuộc tấn công clickjacking.
- Anh Tuấn Anh gợi ý cho cách bypass CSP là dùng ``window.open()``

- Sau đó thì sẽ dùng ``location.href="http://itest.kcsc.ctf:10003/get-flag"`` truy cập để nhận được sessionID, và do chức năng redirect thì ta sẽ truy cập được đến endpoint `/flag/:uuid`


- Tiếp theo ta có thể gán sessionId đấy vào trong Cookie trong dev tool và trỏ ``location.href = "http://itest.kcsc.ctf:10003/flag/:uuid"`` không thì có thể dùng burp cũng tương tự nhau.
- Lấy header ở dưới này và dán vào request:

- Và dùng Cookie là sessionId được tạo cùng với 2 header truy cập đến /flag/uuid và nhận được flag.


- **Bài này mấu chốt là chúng ta cần phải tìm ra được cách bypass CSP**.
FLAG : `KCSC{-Ban-Da-Bi-Dinh-Chi-Thi-Mon-Nay-17c6c806-173f-45dd-b7bf-9f33f849df21}`
## Simple Flask(500 points)
- Đây là một bài python-flask white-box với chức năng unzip file mình đã tải lên server và lưu nó vào trong thư mục `uploads/` và hiển thị các đường link trỏ đến từng file trong file zip này.
- Quan sát source code dưới đây:
- Vị trí của flag là `env`
```
from flask import Flask, request, render_template, flash
import zipfile
import re
import os
from os import listdir
from os.path import isfile, join
app = Flask(__name__, static_folder='uploads')
app.secret_key = "test_keyyyyyyy"
def list_all_files(mypath: str):
output = []
for path, subdirs, files in os.walk(mypath):
for name in files:
output.append(os.path.join(path, name))
return output
def fileIsSafe(file_ext: str):
if not file_ext:
return False
if re.match(r'\.(py|ini|html|htm|env|bash|sh|so|preload)', file_ext):
return False
return True
@app.route('/')
def index():
mypath = "uploads"
uploaded_file = list_all_files(mypath)
return render_template('index.html', data = uploaded_file)
@app.route('/upload', methods=['POST'])
def upload():
if not request.files['file']:
return "No file provided"
else:
try:
client_file = request.files['file']
with zipfile.ZipFile(client_file, 'r') as zip_ref:
for name in zip_ref.namelist():
_, file_ext = os.path.splitext(name)
if fileIsSafe(file_ext):
if len(name.split("/")) != 1:
curr_path = "uploads"
for folder_name in name.split("/")[:-1]:
curr_path += f"/{folder_name}"
if not os.path.exists(curr_path):
os.mkdir(curr_path)
dest_path = os.path.normpath(f"uploads/{name}")
with open(dest_path, "wb") as f:
f.write(zip_ref.read(name))
except:
return "Something went wrong"
return "Success! Check the 'uploads/' folder"
@app.route('/healthz')
def healthz():
import subprocess
output = subprocess.check_output(["python", "-c", "print('OK')"])
return output
if __name__ == "__main__":
app.run(host="0.0.0.0", debug=False, port=5000)
```
- hàm fileIsSafe sẽ check xem nếu đuôi file nằm trong `py|ini|html|htm|env|bash|sh|so|preload` thì sẽ upload thất bại
- Route ``/healthz`` sẽ print('OK') với thư viện subprocess có thể để ý ở đây là subprocess được import lại mỗi khi truy cập tới enpoint.
- Bài này khá giống bài zip-slip ở trên HTB và hướng của bài này là khai thác zipslip để overwrite file.
### SOLUTION 1: UNINTENDED (ZipSlip to SSTI)
- Mình sẽ tận dụng zipslip để overwrite file index.html trong templates sau đó SSTI để đọc env.
- Tạo file index.HTML để bypass `fileIsSafe` với nội dung như dưới:

- Sau đó dùng `evilar.py` ở [**đây**](https://github.com/ptoomey3/evilarc/blob/master/evilarc.py) để tạo payload zipslip.

- Để ý một chút là nếu mà server đã render index.html lần đầu rồi thì lúc này sẽ lưu cache lại ta overwrite được nhưng mà lúc render thì server vẫn lấy index.html ở cache ban đầu cho nên không thể đọc được flag vì vậy ta cần phải upload file zip luôn lên endpoint `/upload` hoặc có thể dùng burp, ở đây mình dùng curl. Vì nếu truy cập vào `/` thì sẽ render và lưu cache kia trước nên sẽ fail.
```
┌──(l3mnt2010㉿ASUSEXPERTBOOK)-[~/tools/zip-slip-exploit-example]
└─$ curl -F file=@evil.zip http://localhost:5000/upload
Success! Check uploads/ folder
```
- Bây giờ thì chỉ cần truy cập vào `/` và nó sẽ hiển thị file `index.HTML` của mình đã overwrite.

- Nhưng mà không hiểu sao mình test trên server lại không được và chỉ ra flag với solution intended.
- Để ý có `/healthz` như này thì mình ý tưởng ghi đè file subprocess.py của python3.8 để return ra `os.popen('env').read()` như ở dưới.

- Nhưng mà dở ở chỗ là file này nó lại không overwrite được nên cũng fail với ý tưởng này.

- Tiếp nữa còn 1 ý tưởng nữa là overwrite file app.PY mình đã test thử để enpoint `/healthz` trả ra env nhưng mà server lại không reload lại được nên ý tưởng này cũng fail.
### SOLUTION2 : INTENDED (Zipslip overwrite .pth bypass blacklist)
- Mình nghĩ là sẽ overwrite được file khác không nằm trong black list kia nên mình đã tìm trong python3.8 chỉ thấy các file `.so` mà cũng fillter chắc cũng không ghi đè được với đuôi `.SO`.Stuck khá lâu thì đánh liều xin hint từ author là anh `nhienit2010` và anh bảo là overwrite file `.pth`



**CODE**:
` import os; var = 'SETUPTOOLS_USE_DISTUTILS'; enabled = os.environ.get(var, 'stdlib') == 'local'; enabled and __import__('_distutils_hack').add_shim();`
- GTP thì mình được giải thích khá đầy đủ như dưới đây

- Có thể hiểu mục đích của đoạn mã là để tùy chỉnh phiên bản của distutils nếu khác môi trường local thì sẽ sử dụng phiên bản mặc định còn trên local còn nếu không thì ngược lại. Cơ bản là nó sẽ load các package và file `.pth` sẽ được thiết lập môi trường trong 'sys.path' và sẽ thực thi đoạn mã .pth này khi chạy các lệnh python như ta thấy ở đây gói subprocess được gọi và trước lúc đó nó sẽ chạy file này trước(mình ga' nên không biết có chuẩn chưa)
- Bây giờ mình sẽ thêm một dòng `print(os.popen('env').read())` vào trong file như dưới đây mục đích là in ra env chứa flag khi truy cập đến endpoint `/healthz`.



FLAG : `KCSC{n0th1ng_1n_y0ur_eye5_62165631}`
Tài liệu tham khảo:
https://www.youtube.com/watch?v=-gP58pozNuM&t=7s
https://www.youtube.com/watch?v=FuiLk7uH9Jw&t=319s
https://khaidantri.net/file-descriptor-la-gi#google_vignette