看了赛博群的《从commons-fileupload源码看文件上传绕waf》,文末提到了dotnet也有这种问题,于是看了下dotnet的源码。
1public ActionResult Index()
2{
3 if (Request.Files.Count>0)
4 {
5 var file = Request.Files[0];
6 var filename = file.FileName;
7 var contenttype = file.ContentType;
8 var reader = new StreamReader(file.InputStream);
9 var content = reader.ReadToEnd();
10 var filepath = Request.MapPath("~/ ") + filename;
11 file.SaveAs(filepath);
12 var resp = $" filename:{filename}\n save file path:{filepath}\n file content:{content}\n file content type:{contenttype}";
13 return Content(resp);
14 }
15 else
16 {
17 return Content("no file");
18 }
19}
对于上传的文件处理类位于System.Web.HttpMultipartContentTemplateParser.Parse()
函数
1internal static MultipartContentElement[] Parse(HttpRawUploadedContent data, int length, byte[] boundary, Encoding encoding)
2{
3 HttpMultipartContentTemplateParser httpMultipartContentTemplateParser = new HttpMultipartContentTemplateParser(data, length, boundary, encoding);
4 httpMultipartContentTemplateParser.ParseIntoElementList();
5 return (MultipartContentElement[])httpMultipartContentTemplateParser._elements.ToArray(typeof(MultipartContentElement));
6}
和Request.Files的层级调用关系如图
在FillInFilesCollection()中,content-type必须以multipart/form-data开头
这里和common fileupload的处理不同,然后进入this.GetMultipartContent()
1来处理boundary 2来解析上传文件流 主要看1
1 private byte[] GetMultipartBoundary()
2 {
3 string text = HttpRequest.GetAttributeFromHeader(this.ContentType, "boundary");
4 if (text == null)
5 {
6 return null;
7 }
8 text = "--" + text;
9 return Encoding.ASCII.GetBytes(text.ToCharArray());
10 }
GetAttributeFromHeader是关键函数
分号逗号和等于号作为分隔符,并根据字符集忽略一些空白字符
所以content-type可以这么写
1Content-Type: multipart/form-data\u0085,;;,,,,,;;, boundary = aaa
接着看GetMultipartContent函数,解析完boundary和文件内容流之后,进入3 Parse函数也就是我们开篇提到的函数。
Parse函数就直接跟进了ParseIntoElementList函数
其中1 ParsePartHeaders是关键函数
能看到这个函数用来解析Content-Disposition
和Content-Type
,先以冒号分割拿到冒号后的部分
1Content-Disposition: form-data; name="file"; filename="1.txt"
2Content-Type: text/plain
即 form-data; name="file"; filename="1.txt"
再看ExtractValueFromContentDispositionHeader函数
1string text = " " + name + "=";
2int num = CultureInfo.InvariantCulture.CompareInfo.IndexOf(l, text, pos, CompareOptions.IgnoreCase);
3if (num < 0)
4{
5 text = ";" + name + "=";
6 num = CultureInfo.InvariantCulture.CompareInfo.IndexOf(l, text, pos, CompareOptions.IgnoreCase);
7 if (num < 0)
8 {
9 text = name + "=";
10 num = CultureInfo.InvariantCulture.CompareInfo.IndexOf(l, text, pos, CompareOptions.IgnoreCase);
11 }
12}
会自动加上分号和等号,所以可以随便构造
1Content-Disposition:\u0;;;;[email protected]#$%^&*(;asdas\u0085d;085filename=11.aspx
2Content-Disposition:filename=11.aspx
3Content-Disposition:aaaaaaaaaaafilename=11.aspx;aaaaaaaaa
两个对比一下就知道
1Content-Disposition: form-data; name="file";filename="11.aspx"
filename
和name
前随意填充字段filename
和name
后必须跟随等号,并且末尾有分号标识结束。在ExtractValueFromContentDispositionHeader函数中会对取的值进行Trim()处理,也能用\u0085来处理
content-type同上
最后贴一张构造的图
Request.Files[0]的name字段是忽略大小写的,Request.Files[0]和Request.Files[“file”]两种写法绕过时可能会出一些拿不到name的问题。
dotnet的特殊空白符如上文,但是位置一般只能放在两侧来用Trim去除。
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。