路由分析
核心路由在WEB-INF/web.xml配置文件中,部分内容如下图
定义了Servlet组件,和过滤器。从web.xml配置来看,我们可以知道这个应用使用了Spring框架进行Web开发,配置了多个Servlet用于不同的功能,如表单处理、文件上传下载等,同时整合了Apache Shiro进行安全控制,另外还配置了字符编码和跨域资源共享的处理。
其中并没有Servlet的专门鉴权机制,只有一个Shiro相关的过滤器配置。
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
从上面的配置可以看出,shiroFilter
过滤器应用于所有URL(通过url-pattern
配置为/*
),这意味着所有进入应用的请求都将通过Shiro过滤器。因此所有Servlet也会经过。
通过分析我们可以知道org.springframework.web.filter.DelegatingFilterProxy
是Spring框架提供的一个类,它允许过滤器代理到一个Spring上下文中定义的bean。也就是当请求到达并且需要通过特定的过滤器处理时,DelegatingFilterProxy
会在Spring的上下文中查找相应名称的bean,并将请求处理工作委托给这个bean。
在这里DelegatingFilterProxy
被命名为shiroFilter
。当请求到达并且经过这个代理过滤器时,它会在Spring的上下文中查找一个也叫shiroFilter
的bean,然后将过滤任务委托给这个bean来处理。
shiroFilter的配置如上,定义了一个Spring bean,其ID为shiroFilter
,并且其类型为CustomShiroFilterFactoryBean
,这是一个自定义的过滤器工厂Bean(查看代码发现是自定义的拦截并处理包含潜在风险字符的请求的过滤器)。还配置了shiroFilter
的属性securityManager
,它引用了另一个名为securityManager
的Spring bean。在Shiro中,SecurityManager
是安全操作的核心,负责所有与安全相关的操作,包括认证、授权等。
同时我们可以发现在spring-context-shiro的XML配置定义了一个叫做shiroFilterChainDefinitions
的Spring bean,它是一个String
类型的bean,用于定义Apache Shiro框架用于URL模式匹配的权限和访问控制规则。
其中/servlet/fileupload/gpy* = anon
,表示这个特定的路径不需要认证。
漏洞分析
通过web.xml配置文件定位到相应代码。
分析代码,可以发现首先定义了一系列常量。这里private static final String uploadFolderName = "/pics/"
应该和后续的存储路径相关。extensionPermit
定义了允许上传的文件扩展名,但通过后续的分析发现并没有使用。
通过分析代码我们可以知道是使用了HttpServletRequest
对象来读取请求数据,并处理multipart/form-data格式的数据来提取文件和字段。
其中DataInputStream in = new DataInputStream(request.getInputStream());
直接从request
的输入流中读取数据,后续就是对输入流中读取的数据的提取操作。
其中if (s.indexOf("filename=") == -1) { ... } else { ... }
这个条件判断,用于检查当前读取的行String s
中是否包含了字符串"filename="。只有包含时,才认为当前行描述的是一个文件上传字段不然就是表单字段。
这里int pos22 = s.indexOf("filename=");
找到"文件名="字符串的位置,然后String fileFormName = s.substring(pos2, pos22 - 3);
截取从字段名开始到"filename="出现之前的字符串,得到表单中文件字段的名称。String s3 = s.substring(pos22 + "filename=".length() + 1);
截取"filename="之后的字符串,即完整的文件路径和名称。可以发现没有对文件名和后缀的过滤。
并且通过out.print("fileRealName=" + s5 + "");
将实际的文件名直接写入响应中。
后续就是读取文件数据并决定如何储存的操作,
byte[] b = subBytes(dataOrigin, byteIndexOf(dataOrigin, s, 0) + s.getBytes().length + 2, dataOrigin.length);
从原始的字节数组dataOrigin
(包含了整个HTTP请求体的数据)中提取出当前文件的数据。
String saveDirectoryPath = curProjectPath + "/uploads" + uploadFolderName + curDate + "/";
构造一个用于存上传文件的目录路径,包括上传的目录、一个文件夹名称和当前日期。其中curProjectPath
为当前web应用根目录。uploadFolderName
为先前定义的常量/pics/
注意此时定义了boolean isShare = false;
,在后续中会进行判断文件是否需要保存到本地服务器还是一个网络共享位置(使用SMB协议)。默认为false保存在本地。
在本地保存操作中File f = new File(saveDirectoryPath + File.separator + s5);
直接将上传的文件名拼接进入,DataOutputStream fileout = new DataOutputStream(new FileOutputStream(f));
然后打开一个到文件f
的数据输出流。fileout.write(b3, 0, b3.length - 1);
将上传内容写入文件。至此我们的上传文件流程大致结束,根据以上的分析文件上传路径为当前web应用根目录+/uploads+/pics/+服务器日期+上传文件名。同时在最后流程中out.print("date=" + curDate);直接将服务器日期写入响应中。
漏洞复现
直接将文件上传,再通过返回的日期,即可得到文件上传的路径