All posts

Technical10 min read

When IT Bugs Reach OT: Three Vulnerabilities in SCADA-LTS and SCADA-BR

Operational technology has spent much of its history enjoying a luxury that conventional IT systems lost decades ago:

Isolation.

Industrial control systems, SCADA platforms, engineering workstations and the devices they manage were traditionally separated from corporate networks, sometimes physically and sometimes by architecture. There were good operational reasons for this. A system controlling a production line, pumping station or electrical process does not particularly benefit from being able to talk to the finance department. However, as time and technology have advanced, the lines of separation between traditional OT environments and IT systems have blurred.

Remote monitoring, centralised management, predictive maintenance, historian integration and real-time reporting all create legitimate reasons for operational systems to communicate with conventional IT infrastructure. The business case is easy to understand. The security consequence is that software designed for an industrial environment increasingly finds itself exposed to traditional application security vulnerabilities.

During security research into Scada-LTS, an open-source SCADA platform, Aphotic Security identified two vulnerabilities affecting Scada-LTS 2.8.0, and a vulnerability affecting SCADA-BR. The first allowed an authenticated low-privileged user to perform SQL injection through the event search REST API. The second allowed the same class of user to bypass object-level permissions and retrieve historical telemetry belonging to arbitrary data points.

That combination is worth examining because it demonstrates a broader problem with IT/OT environments: an attacker does not necessarily need an exotic industrial protocol exploit to begin compromising an industrial system. Sometimes a web API will do just fine.

Familiar Vulnerabilities in Industrial Systems

Scada-LTS is a supervisory control and data acquisition platform used to monitor and manage industrial processes. Like many modern applications, it exposes a substantial amount of functionality through HTTP APIs.

From a security testing perspective, this creates an interesting overlap.

At one end sits an industrial application dealing with data points, alarms, event histories and process telemetry. At the other sits a fairly conventional Java web application using Spring Security, REST endpoints, SQL queries and session-based authentication. The vulnerabilities we identified existed in that second layer.

There was no requirement to reverse-engineer an industrial protocol, modify ladder logic or develop specialised ICS malware. These were application security problems occurring inside software responsible for an operational environment.

Vulnerability 1CVE-2026-84859Authenticated Blind SQL Injection

The first vulnerability was identified in:

http
POST /Scada-LTS/api/events/search

The endpoint accepted a JSON object containing parameters used to search and sort historical events. Among those parameters was a sortBy array.

Within EventDAO.findEvents, each member of this array was inserted directly into the SQL query used to populate the ORDER BY clause:

java
sorting.add(query.getSortBy()[i] + " " +    (query.getSortDesc()[i] ? "DESC " : "ASC "));sql.append("ORDER BY " + String.join(", ", sorting));sortBy concatenated into ORDER BYDAO.getInstance().getJdbcTemp().query(    sql.toString(),    params.toArray(),    ...);

Elsewhere in the query, attacker-controlled values were represented using bound parameters. The sorting expression was handled differently: the supplied value was directly concatenated into the SQL statement itself, textbook SQL injection. With the exception of a special case for the literal value status, there was no meaningful validation of the field before it reached the database.

The endpoint was protected by authentication, but the distinction between authenticated and trusted becomes rather important here. Scada-LTS permits low-privileged users with the ordinary ROLE_USER role to access /api/events/, meaning exploitation required no administrative privileges.

Asking the Database the Wrong Sort of Question

The first indication that the value reached the SQL parser could be demonstrated with two otherwise identical requests.

A legitimate column produced a normal response
{  "sortBy": ["e.id"]}
ResponseHTTP 200
An invalid column produced an SQL error
{  "sortBy": ["sqli_probe_zzz"]}
ResponseHTTP 500

That difference provided the foundation for a boolean oracle.

An injected expression could make the query remain valid when a condition evaluated to true, while deliberately causing a database error when it evaluated to false:

Returned
(select case    when (1=1)    then e.id    else (e.id+(select 1 union select 2))end)
ResponseHTTP 200
Changing the condition resulted in an HTTP 500 error
(select case    when (1=2)    then e.id    else (e.id+(select 1 union select 2))end)
ResponseHTTP 500

Replacing 1=1 with a condition concerning the first character of a database value and repeating the request allowed for arbitrary values to be recovered character by character. A time-based variant using SLEEP() provided a second oracle: a true condition delayed the response, while a false condition returned normally.

Our proof of concept used the vulnerability to extract the database version, current database name and entries from the Scada-LTS users table.

Why Database Read Access Matters Here

An SQL injection vulnerability in an industrial product deserves the same care in impact analysis as one in any other application. The presence of the letters SCADA does not automatically turn every software vulnerability into an industrial catastrophe. In this case, however, the surrounding application architecture made the database particularly valuable.

Scada-LTS stored user password hashes using unsalted SHA-1 encoded in Base64. This made recovered hashes practical candidates for offline cracking. The SQL injection therefore created a potential progression:

  1. 01Low-privileged Scada-LTS account
  2. 02Database extraction
  3. 03Password hash recovery
  4. 04Administrator credentials

That progression is consequential, because administrator access expands what can be done through the application itself. Scada-LTS has previously been affected by authenticated command execution through Event Handlers, tracked as CVE-2023-33472.

We deliberately stopped our proof of concept before privilege escalation or command execution. The vulnerability being disclosed here was the SQL injection, and the demonstrated impact was arbitrary database read access. The wider attack path nevertheless shows why individual weaknesses cannot always be considered in isolation. In an environment with numerous attack paths, the value of a singular vulnerability depends heavily on what data or access resides behind it.

On Disclosure and Submission

This vulnerability was submitted for a CVE number, however this was marked as a duplicate and merged with CVE-2026-84859. While the vulnerability data for CVE-2026-84859 was not publicly disclosed at the time of submission or discovery by Aphotic, it seems Tenable discovered this issue and submitted it before we did.

Vulnerability 2CVE-2026-115803Authorisation Bypass

The second vulnerability was subtler and, in some ways, more interesting. Scada-LTS exposes AmCharts endpoints for retrieving historical values associated with data points:

http
GET /Scada-LTS/api/amcharts/by-idGET /Scada-LTS/api/amcharts/by-xid

Data points could carry individual read permissions. An ordinary user may legitimately be permitted to view one process value while being prohibited from viewing another.

Spring Security attempted to enforce exactly that restriction:

xml
<intercept-url    pattern="/api/amcharts/by-id"    access="@guard.hasDataPointReadPermission(request,false,'ids')"Checks one ids value    method="GET" />

The problem appeared when the request contained the ids parameter more than once. The security guard obtained the parameter using:

java
String parameter = request.getParameter(key);Returns only the first value

HttpServletRequest.getParameter() returned only one value for a multi-valued parameter. The controller, meanwhile, interpreted the same request differently:

java
@GetMapping("/by-id")getValuesFromTimeRangeById(    @RequestParam Set<Integer> ids,Binds every value    ...)

The Set<Integer> contained all values supplied by the user.

The security layer and controller were therefore acting on different interpretations of the same request: the guard authorised only the first ID value, while the controller bound and processed the full set.

One Authorised Data Point, Several Unauthorised Ones

Consider an ordinary user who cannot read data point 1.

A direct request behaved correctly
GET /Scada-LTS/api/amcharts/by-id?ids=1&startTs=0&endTs=9999999999999
ResponseHTTP 403
Now place another ID before it
GET /Scada-LTS/api/amcharts/by-id?ids=999999&ids=1&startTs=0&endTs=9999999999999
ResponseHTTP 200

The response contained the historical values for data point 1.

The first value in this example, 999999, did not exist. This exposed a second defect: the permission check failed open when an identifier could not be resolved, treating the nonexistent object as permitted rather than denying access.

Consequently, an attacker did not even need to know the ID of a data point they were legitimately permitted to read. A nonexistent ID could be supplied first to satisfy the guard, while the controller subsequently processed and returned the remaining, unauthorised data points.

The same bypass could also be performed by placing a legitimately authorised data point first. The fail-open condition was therefore not required for exploitation, but made the bypass considerably easier and removed the requirement for the attacker to possess read access to any valid data point.

This is a classic object-level authorisation bypass (commonly referred to as an IDOR) caused here by parameter pollution and inconsistent parsing between the authorisation layer and the application logic.

From Data Exposure to Process Intelligence

The immediate impact of the second vulnerability was confidentiality loss.

An authenticated low-privileged user could retrieve historical values for data points that an administrator had explicitly prevented them from accessing.

In a normal business application, broken object-level authorisation might expose another user’s invoices, documents or account details.

Within a SCADA platform, the objects can represent something rather different.

A data point may correspond to a temperature, pressure, flow rate, motor state, tank level, electrical measurement or some other piece of process telemetry. Historical values can reveal how the underlying operation behaves: when machinery runs, what normal process ranges look like, when production starts and stops, how systems react to particular conditions and which assets appear operationally significant.

Vulnerability 3CVE-2026-94148Authentication Not Enforced

The third vulnerability discovered was in SCADA-BR versions up to 1.1, and involved the GET /ScadaBR/export_project.htm endpoint. This endpoint returns a ZIP archive containing a complete JSON dump of the system configuration, including every user account with its password hash, all data-source configurations (which embed device/protocol credentials), and all system settings.

Aphotic discovered that this endpoint required no authentication:

  1. The servlet authentication filter (NormalLoggedInFilter, alias IsLoggedIn) is mapped in web.xml to *.shtm only; export_project.htm uses the *.htm extension and is therefore never seen by the filter.
  2. On affected builds, the export routine EmportDwr.createExportJSON performs no in-code authorisation check.

While this vulnerability was not an interesting or nuanced authorisation bug like the previous one, it did expose all user password hashes which were unsalted SHA-1 hashes that were subsequently base64 encoded, making offline recovery via hash cracking simpler. Recovering the admin hash yields full administrative control of the SCADA instance (data sources, set-points, scripting, user management).

Three Bugs, One Larger Problem

The wider significance of these vulnerabilities lies in the environment around them. As IT and OT become more closely integrated, the security boundary around industrial systems changes with them. Historically, reaching a SCADA application might have required physical access to an engineering network or a foothold deep inside a heavily segregated environment.

Modern operational environments increasingly require connectivity. Vendors require remote access. Historians feed analytics platforms. Engineering systems exchange information with central infrastructure. Cloud services consume production data.

Every useful connection creates another possible path towards the operational environment.

An attacker who compromises a corporate account therefore has options, and attack paths which may impact more than IT assets. A vulnerability that requires authentication becomes considerably more interesting when enterprise compromise can provide that authentication.

The OT Attack Does Not Have to Start in OT

One of the more persistent misconceptions surrounding industrial security is the assumption that an attack on an OT environment begins with an OT-specific exploit. Real attack paths are rarely that accommodating.

Initial access may occur through phishing, stolen credentials, an internet exposed service, a vulnerable corporate application or a compromised third party. From there, the attacker moves through the organisation looking for systems that provide access to something more useful. Engineering workstations, jump servers, historian systems and SCADA interfaces are attractive precisely because they sit near the boundary between digital access and physical operations. This means traditional web application security can remain relevant even when the ultimate systems being protected are industrial in nature.

Security at the Point of Integration

There is a temptation in OT security to begin with specialised solutions: industrial intrusion detection, protocol-aware monitoring, sophisticated zero-trust architectures and purpose-built ICS security platforms. While those technologies have their place, the findings in Scada-LTS demonstrate why security fundamentals remain rather stubbornly relevant.

The guiding principle should be to reduce the number of paths between IT and OT, then tightly control the ones that remain. If a corporate system, user or remote-access service has no operational reason to reach SCADA infrastructure, it should have no route to it. Where connectivity is necessary, access should be limited to the specific systems, protocols and privileges required.

Application security testing also needs to extend into the systems sitting at these convergence points.

That testing should include the same fundamentals expected of conventional enterprise software:

  • authentication and session management;
  • object-level authorisation;
  • input validation;
  • SQL and command injection;
  • default and weak credentials;
  • privilege boundaries;
  • exposed administrative functionality; and
  • the security consequences of chaining several individually limited weaknesses together.

OT-specific testing can then build on those foundations through analysis of industrial protocols, engineering systems, PLCs, HMIs and process-specific attack paths.

Disclosure

Both vulnerabilities were identified by Lian Aldrich and Jacob Simmons of Aphotic Security while conducting independent security research into Scada-LTS 2.8.0, commit f2a519bf.

The issues were privately disclosed to the Scada-LTS vendor on 28 June 2026 at the security contact specified by the project, with further submissions made to VulDB in August. Our disclosure included a self-contained Docker laboratory and proof-of-concept tooling reproducing both vulnerabilities.

The SQL injection has been classified as CWE-89: Improper Neutralisation of Special Elements used in an SQL Command.

The AmCharts authorisation bypass maps primarily to CWE-639: Authorisation Bypass Through User-Controlled Key, with CWE-863: Incorrect Authorisation also applicable.

Written by

Lian Aldrich

Founder · LinkedIn

Jacob Simmons

Founder · LinkedIn

Aphotic

Ready to dive deeper?

Get an expert read on your security in 30 minutes, and a clear plan for what to improve next.