一、文件包含漏洞
严格来说,文件包含就是代码注入的一种。代码注入,其原理就是注入一段用户能控制的脚本或代码并让服务器端执行。代码注入的典型代表就是文件包含。文件包含可能会出现在JSP、PHP、ASP等语言中,常见函数如下:
PHP:include()、include_once()、require()、require_once()、fopen()、readfile()……
JSP/Servlet:ava.io.File()、java.io.FileReader()……
ASP:include file 、include virtual……
PHP当使用include()、include_once()、require()、require_once()这四个函数包含一个新文件时,该文件会被当做PHP代码执行,且不在意被包含的文件是什么类型。
想要成功利用文件包含漏洞,需要满足两个条件:
include()等函数通过动态变量的方式引入需要包含的文件
用户能够控制该动态变量
1.1 本地文件包含
可以温习一下dvwa的文件包含
DVWA文件包含漏洞
通常控制参数的值为../../etc/password,代表php将访问/etc/password文件
字符串截断也是文件包含中常用的技巧
%00截断:可以用0字节(\x00)作为字符串结束符
长度截断:目录字符串,在Windows下256字节、Linux下4096字节时会达到最大值,最大值长度之后的字符串会被丢弃
可构造:./././././././././././././abc
或
/abc
或
../1/abc/../1/abc/../1/abc
通过../../../这种方式返回上层目录,这种方式又被称为”目录遍历“
可以通过不同编码方式来绕过
但是当PHP配置了open_basedir时,会使得目录遍历失效。
open_basedir的作用是限制在某个特定目录下PHP能打开的文件,其作用与safe_mode是否开启无关。
主要注意的是,open_basedir的值是目录的前缀
如果open_basedir = /home/app/aaa
那么以下目录都是合理的
/home/app/aaa
/home/app/aaa123
/home/app/aaabbb
1.2 远程文件包含
如果PHP的配置选项allow_url_include=ON,则include和require函数可以加载远程文件
1.3 本地文件包含的利用技巧
包含用户上传的文件
如果文件内容包含PHP代码,则这些代码会被include()加载后执行
包含data://或php://input等伪协议
前提是allow_url_include=ON
可以参考之前的文章:php文件包含常用伪协议
包含Session文件
PHP默认生成的Session文件往往放在/tmp目录下
/tmp/sess_SESSIONID
包含日志文件,比如Web Server的access log
服务器一般会往Web Server的access log里记录客户端的请求信息,在error_log里记录出错请求。因此攻击者可以间接地将PHP代码写到日志文件中,在文件包含时,只需要包含日志文件即可。
包含/proc/self/environ文件
http://www.website.com/view.php?page=../../../../../proc/self/environ
通常在User-Agent里注入PHP代码最终完成攻击
包含上传的临时文件
包含其他应用创建的文件,比如数据库文件、缓存文件、应用日志等
二、变量覆盖漏洞
2.1 全局变量覆盖
PHP中使用变量不需要初始化,当register_globals=ON时,变量来源于各个不同的地方,很可能会导致安全问题。
类似的,通过¥GLOBALS获取的变量,也可能导致变量覆盖
2.2 extract()变量覆盖
extract() 函数从数组中将变量导入到当前的符号表。
extract(array,extract_type,prefix)
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
第二个参数 type 用于指定当某个变量已经存在,而数组中又有同名元素时,extract() 函数如何对待这样的冲突。最常见的两个值是”EXTR_OVERWRITE“和”EXTR_SKIP“。
当值为”EXTR_OVERWRITE“时,如果变量名冲突则覆盖已有变量,值为”EXTR_SKIP“则表示跳过不覆盖。默认是”EXTR_OVERWRITE“。
2.3 遍历初始化变量
常见的一些以遍历的方式释放变量的代码,可能会导致变量覆盖。
$chs = '';
if($_POST && $charset != 'utf-8'){
$chs = new Chinese('UTF-8',$charset";
foreach($_POST as $key => $value){
$$key = $chs->Convert ($value);
}
unset($chs);
若提交参数chs,则可能覆盖变量”$chs“的值。
2.4 import_request_variavles变量覆盖
bool import_request_variables ( string $types [, string $prefix ] )
import_request_variavles()将GET、POST、Cookie中的变量导入到全局,使用这个函数只需要简单地指定类型即可。第二个参数是为导入的变量添加的前缀,若没有指定,则将覆盖全局变量。
2.5 parse_str()变量覆盖
parse_str(string,array)
该函数用于解析URL的query string,但是当参数值能被用户控制时,可能导致变量覆盖
如果指定了第二个参数,则会将query string中的变量解析后存入该数组变量中。因此在使用该函数时,应该养成指定第二个参数的好习惯
针对覆盖变量的安全建议
1.确保register_globals = OFF
2.熟悉可能造成变量覆盖的函数和方法,检查用户能否控制变量来源
3.养成初始化变量的好习惯
三、代码执行漏洞
3.1 危险函数执行代码
危险函数,例如popen()、system()、passthru()、exec()等都可以执行系统命令
eval()函数也可以执行PHP代码
3.2 文件写入执行代码
可以参考vulhub漏洞复现的文章
CVE-2016-3088 ActiveMQ任意文件写入漏洞
3.3 其他执行代码方式
直接执行代码的函数
PHP:eval()、assert()、system()、exec()、shell_exec()、passthru()、escapeshellcmd()、pcntl_exec()等
文件包含函数
PHP:include()、include_once()、require()、require_once()
本地文件写入函数
file_put_contents()、fwrite()、fputs()等
preg_replace()代码执行
如果是/e模式,则允许代码执行(也可能通过%00截断注入/e)
可参考 Thinkphp 2.x 任意代码执行的漏洞复现: Thinkphp 2.x 任意代码执行
动态函数执行
调用函数直接导致代码执行,create_function()也具有此能力
Curly Syntax
PHP的Curly Syntax也能导致代码执行,它将执行{……}间的代码,并将结果替换回去
如:
<?php
$var = "I was innocent until ${'ls'} appeared here";
?>
ls命令将列出本地目录的文件并将结果返回
回调函数执行代码
很多函数都可以执行回调函数,当回调函数用户可控时,将导致代码执行
unserialize()导致代码执行
unserialize()就是反序列化函数,它能将序列化的数据重新映射为PHP变量。但是unserialize()在执行时如果定义了__destruct()函数或wakeup()函数,这两个函数将执行。
unserialize()代码执行有两个条件,一是 unserialize()的参数用户可控,这样可以构造出需要反序列化的数据结构;二是 存在__destruct()函数或wakeup()函数,这两个函数决定执行的代码。
四、定制安全的PHP环境
推荐php.ini中一些相关安全参数的配置
reguster_globals=OFF
open_basedir在设置目录时应该在最后加上”/",否则会被认为是前缀
allow_url_include=OFF
allow_uel_fopen=OFF
display_errors=OFF
log_errors=ON
magic_quotes_gpc=OFF
cgi.fix_pathinfo=0
session.cookie_httponly=1
session.cookie_secure=1
……