代码审计-代码执行
代码执行漏洞
代码执行漏洞是指应用程序本身过滤不严,用户可以通过请求将代码注入到应用中执行。当应用在调用一些能将字符串转化成代码的函数(如php中的eval)时,没有考虑到用户是否能控制这个字符串,将造成代码注入漏洞。狭义的代码注入通常指将可执行代码注入到当前页面中,如php的eval函数,可以将字符串代表的代码作为php代码执行,当前用户能够控制这段字符串时,将产生代码注入漏洞
挖掘思路
- 用户能够控制函数输入
- 存在可执行代码的危险函数
常见危险函数
eval()和assert()、回调函数、动态函数执行、preg_replace函数
eval()和assert()
eval() 、assert()将输入的字符串参数作为PHP程序代码来执行
实例
eval()
<?php
if(isset($_GET['cmd'])){
$cmd = $_GET['cmd'];
eval($cmd);
}else{
echo "fail";
}
assert()
<?php
if(isset($_GET['cmd'])){
$cmd = $_GET['cmd'];
assert($cmd);
}else{
echo "fail";
}
回调函数
原型:mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )
$callback 是要调用的自定义函数名称
$parameter 是自定义函数的参数
常见的回调函数:
call_user_func()、call_user_func_array()、array_map()等
实例
<?php
error_reporting(E_ALL ^ E_NOTICE);
function callback()
{
$cmd = $_GET['cmd'];
eval($cmd);
}
call_user_func('callback',$cmd);
?>
<?php
error_reporting(E_ALL ^ E_NOTICE);
$a = 'phpinfo();';
call_user_func($_GET['a'],$a);
?>
动态函数执行
- 定义一个函数
- 将函数名(字符串)赋值给一个变量
- 使用变量名代替函数名动态调用函数
实例
GET:
<?php
// 接受GET请求a的参数作为一个函数,b是作为a函数里的参数
$_GET["a"]($_GET["b"]);
?>
POST:
<?php
// 接受GET请求a的参数作为一个函数,b是作为a函数里的参数
$_GET["a"]($_POST["b"]);
?>
正则表达式
正则表达式语法规则 普通字符作为原子
限定符 特殊符号作为原子
边界限制符 通用字符类型作为原子
后向引用 自定义原子表作为原子
1 $pattern = '/\d/'; //匹配任何一个数字
2 $pattern = '/\D/'; //匹配任何一个非数字
3 $pattern = '/\w/'; //匹配任何一个 数字、字母(大小写)、下划线
4 $pattern = '/\W/'; //匹配任何一个 非 数字、字母(大小写)、下划线
5 $pattern = '/\s/'; //匹配任何一个空白字符 空格 \n \r 回车 换行
6 $pattern = '/\S/'; //匹配任何一个非空白字符
实例
<?php
//1.普通字符作为原子
$pattern = '/abc/';
$str = 'abcdefghijklmnopqrstuvwxyzabc';
preg_match_all($pattern,$str,$res);
var_dump($res);
//2.特殊符号的字符作为原子
$pattern = '/\[php\]/';
$str = '[php]123456';
preg_match_all($pattern,$str,$res);
var_dump($res);
//3.通用字符作为原子
$pattern1 = '/\d/'; //0-9
$pattern2 = '/\D/'; //a-zA-Z
$str = '123411aassfafdas22';
preg_match_all($pattern1,$str,$res);
var_dump($res);
?>
<?php
//4.自定义原子
$pattern = '/[aj]sp/'; //匹配[aj]中任意一个字符作为原子的asp jsp
$str = 'aaaspjjjjjspspsp';
preg_match_all($pattern,$str,$res);
var_dump($res);
//5.限定符
$pattern1 = '/go*gle/'; //* 匹配前面出现的原子次数0次 1次 或多次 g go goo
$pattern2 = '/go+gle/'; //+ 匹配前面出现的原子次数1次 或多次 go goo
$pattern3 = '/go?gle/'; //? 匹配前面出现的原子次数0次 1次 g go
$str = 'go';
preg_match_all($pattern1,$str,$res);
var_dump($res);
//6.边界限定
$pattern1 = '/^abc/'; //匹配输入字符开始的位置 必须要以abc的形式开头
$pattern2 = '/abc$/'; //匹配输入字符结尾的位置 必须要以abc的形式结尾
$pattern3 = '/^abc$/'; //只匹配abc
$str ='abc';
preg_match_all($pattern3,$str,$res);
var_dump($res);
//7.反向引用
$pattern = '/\d{4}(-)\d{2}\\1\d{2}/';
$str = '2021-08-30';
preg_match_all($pattern,$str,$res);
var_dump($res);
?>
preg_replace函数
原型:
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
$pattern 正则匹配的内容$replacement 用于替换的字符串或字符串数组
$subject 要进行搜索和替换的字符串或字符串数组
$pattern 存在/e模式修正符修饰 允许代码执行
实例
第一个参数
<?php
echo $cmd = $_GET['cmd'];
$str = '<php>phpinfo()</php>';
preg_replace("/<php>(.*?)$cmd","\\1","$str");
?>
第二个参数
<?php
preg_replace("/php/e",$_GET['cmd'],'php');
?>
第三个参数
<?php
$str = $_GET['cmd'];
preg_replace("/\[php\](.*?)\[\/php\]/e","\\1",$str);
?>
修复方案
- 尽量不要执行外部的应用程序或命令 使用自定义函数或函数库来替代外部应用程序或命令的功能
- 使用escappeshellarg函数来处理命令的参数
- 使用safe_mode_exec_dir来指定可执行的文件路径
- 将执行函数的参数做白名单限制,在代码或配置文件中限制某些参数