探寻 Java 文件上传流量层面 waf 绕过( 五 )


文章插图
 
看看具体逻辑
private String extractFilename(String contentDisposition, String key) {if (contentDisposition == null) {return null;} else {int startIndex = contentDisposition.indexOf(key);if (startIndex == -1) {return null;} else {//截取filename=后面的内容String filename = contentDisposition.substring(startIndex + key.length());int endIndex;//如果后面开头是“则截取”“之间的内容if (filename.startsWith(""")) {endIndex = filename.indexOf(""", 1);if (endIndex != -1) {return filename.substring(1, endIndex);}} else {//可以看到如果没有“”包裹其实也可以,这和当时陈师分享的其中一个trick是符合的endIndex = filename.indexOf(";");if (endIndex != -1) {return filename.substring(0, endIndex);}}return filename;}}}简单测试一波,与心中结果一致

探寻 Java 文件上传流量层面 waf 绕过

文章插图
 

探寻 Java 文件上传流量层面 waf 绕过

文章插图
【探寻 Java 文件上传流量层面 waf 绕过】 
同时由于indexof默认取第一位,因此我们还可以加一些干扰字符尝试突破waf逻辑
探寻 Java 文件上传流量层面 waf 绕过

文章插图
 
如果filename*开头但是spring4当中没有关于url解码的部分
探寻 Java 文件上传流量层面 waf 绕过

文章插图
 
没有这部分会出现什么呢?我们只能自己发包前解码,这样的话如果出现00字节就会报错,报错后
探寻 Java 文件上传流量层面 waf 绕过

文章插图
 
看起来是spring框架解析header的原因,但是这里报错信息也很有趣将项目地址的绝对路径抛出了,感觉不失为信息收集的一种方式
猜猜我在第几层说个前提这里只针对单文件上传的情况,虽然这里的代码逻辑一眼看出不能有上面那种存在双写的问题,但是这里又有个更有趣的现象
探寻 Java 文件上传流量层面 waf 绕过

文章插图
 
我们来看看这个 extractFilename 函数里面到底有啥骚操作吧,这里靠函数 indexOf 去定位key(filename=/filename*=)再做截取操作
private String extractFilename(String contentDisposition, String key) {if (contentDisposition == null) {return null;} else {int startIndex = contentDisposition.indexOf(key);if (startIndex == -1) {return null;} else {String filename = contentDisposition.substring(startIndex + key.length());int endIndex;if (filename.startsWith(""")) {endIndex = filename.indexOf(""", 1);if (endIndex != -1) {return filename.substring(1, endIndex);}} else {endIndex = filename.indexOf(";");if (endIndex != -1) {return filename.substring(0, endIndex);}}return filename;}}}这时候你的反应应该会和我一样,套中套之waf你猜猜我是谁
探寻 Java 文件上传流量层面 waf 绕过

文章插图
 
当然我们也可以不要双引号,让waf哭去吧
探寻 Java 文件上传流量层面 waf 绕过

文章插图
 
Spring5基础构造也是随便来个新的springboot2.6.4的,来看看spring5的,小版本间差异不测了,经过测试发现spring5和spring4之间也是有版本差异处理也有些不同,同样是在 parseRequest
private void parseRequest(HttpServletRequest request) {try {Collection<Part> parts = request.getParts();this.multipartParameterNames = new LinkedHashSet(parts.size());MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap(parts.size());Iterator var4 = parts.iterator();while(var4.hasNext()) {Part part = (Part)var4.next();String headerValue = https://www.isolves.com/it/cxkf/yy/JAVA/2022-07-13/part.getHeader("Content-Disposition");ContentDisposition disposition = ContentDisposition.parse(headerValue);String filename = disposition.getFilename();if (filename != null) {if (filename.startsWith("=?") && filename.endsWith("?=")) {filename = StandardMultipartHttpServletRequest.MimeDelegate.decode(filename);}files.add(part.getName(), new StandardMultipartHttpServletRequest.StandardMultipartFile(part, filename));} else {this.multipartParameterNames.add(part.getName());}}this.setMultipartFiles(files);} catch (Throwable var9) {this.handleParseFailure(var9);}}很明显可以看到这一行 filename.startsWith("=?") && filename.endsWith("?=") ,可以看出Spring对文件名也是支持QP编码
在上面能看到还调用了一个解析的方法
org.springframework.http.ContentDisposition#parse
,多半就是这里了,那么继续深入下


推荐阅读