【java安全】Tomcat waf绕过(charset方式)
在一次hvv中应用存在表达式注入漏洞,但是payload被waf拦截,尝试了chunked无果,于是尝试寻找tomcat绕过waf的方法,最后发现该方式居然可以过雷池。
应用是以tomcat启动的,绕过waf最常用的就是编码,如何构造数据使得tomcat可以解析而waf无法解析从而放行?
tomcat基础
需要先了解tomcat是如何解析http内容的
tomcat的典型架构如下,一个Tomcat中只有一个Server,一个Server可以包含多个Service,一个Service只有一个Container,但是可以有多个Connectors
多个 Connector 和一个 Container 就形成了一个 Service,有了 Service 就可以对外提供服务
具体解析http请求的是在Connector中,Connector用于接受请求并将请求封装成Request和Response,然后交给Container进行处理,Container处理完之后在交给Connector返回给客户端。
Connector结构图
connector 拿到发来的请求,交给 processor 处理,一般来说我们的 processor 是 org.apache.coyote.http11.Http11Processor,用来处理 HTTP1.1 协议
其中比较关键的几个方法
org.apache.coyote.http11.Http11Processor#service 处理的入口
org.apache.coyote.http11.Http11Processor#prepareRequest 解析请求
解析完了之后把 request 传给 jsp,jsp 拿到的 request 是层层嵌套如下
org.apache.catalina.connector.RequestFacade
org.apache.catalina.connector.Request
org.apache.coyote.Request
最后一个 Request ,就是上面 Http11Processor 直接解析出的 request,它有一个 org.apache.tomcat.util.http.Parameters 类的成员
当 jsp 里面调用 request.getParameter 时,就会层层调用到这个 Parameters 类的 processParameters 方法
里面会按 & 和 = 分割,把 body 里面的参数值和参数名称解析出来
详细阅读代码,发现参数名和参数值都会被指定的 charset 解码,这里就给了我们用 charset 绕 WAF 的机会
private void processParameters(byte bytes[], int start, int len, Charset charset)
这个 charset 其实是来自这个类的成员,默认是 ISO_8859_1
找一下这个 charset 在哪里赋值了,很容易找到它其实来自org.apache.catalina.connector.Request#parseParameters
这个函数比较复杂,既处理 application/x-www-form-urlencoded 又处理 multipart/form-data 的编码问题
既解析 charset,又解析 chunk
我们只看我们关心的 ,这里调用 getCharset 方法,设置给了Parameters 类的 charset 成员,修改了默认 charset
跟进getCharset方法,里面层层调用getCharset,不断往下
找到核心代码:org.apache.coyote.Request#getCharsetFromContentType()
看一下这个方法怎么获取的,逻辑很简单,就是字符操作尝试获取charset=xxx中的xxx
这里其实就可以进行混淆比如
Content-Type: application/x-www-form-urlencoded;;AAAAAAAAcharset= " Utf16-lE " ;AAAAAAAAAAAA
混淆后也是可以正确获取值
后面会通过charsetname别名从缓存里获取charset全名
\org\apache\tomcat\util\buf\CharsetCache.class
比较复杂,但是知道名字或者别名就能获取到了,具体支持的 charset 可以看下面两个变量,支持非常多种charset
org.apache.tomcat.util.buf.CharsetCache#INITIAL_CHARSETS
org.apache.tomcat.util.buf.CharsetCache#LAZY_CHARSETS
测试
写一个简单的jsp测试一下:获取参数t并输出
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
String t = request.getParameter("t");
out.println(t);
%>
</body>
</html>
使用IBM 875编码
使用python发包,可以看到编码后的数据服务端成功解码
绕过雷池
测试一下是否能够绕过雷池,首先是没有编码的攻击请求,直接被拦截了
编码后的请求,成功绕过
不加混淆也是可以绕过的
备注
以上测试是在Tomcat 8.5.90, 在Tomcat9中org.apache.coyote.Request#getCharsetFromContentType()方法改用parseMediaType
解析ComtentType的各个部分,因此混淆方法会失效
核心是使用HttpParser.readToken
解析字符串(token流式处理