Back to Blog
critical SEVERITY7 min read

How URL Injection via Unvalidated User Input happens in Node.js and how to fix it

A critical URL injection vulnerability in the QQ info lookup feature allowed attackers to manipulate API request parameters by sending specially crafted messages. Without proper input validation, user-controlled data was directly embedded into external API URLs, potentially exposing sensitive authentication credentials (skey and pskey) to attacker-controlled servers.

O
By Orbis AppSec
Published August 5, 2026Reviewed August 5, 2026

Answer Summary

This is a URL injection vulnerability (CWE-20: Improper Input Validation) in Node.js that occurs when the qqinfo.js file constructs external API URLs using unvalidated user input. The `mid` variable, derived from user messages, was embedded directly into HTTP requests without checking if it contains only valid QQ numbers. The fix adds a regex validation `/^\d+$/.test(mid)` to ensure only numeric input is accepted, preventing attackers from injecting malicious URL parameters that could leak skey and pskey authentication credentials to third-party servers.

Vulnerability at a Glance

cweCWE-20 (Improper Input Validation)
fixAdded regex validation `/^\d+$/.test(mid)` to ensure only numeric QQ IDs are accepted
riskAuthentication credentials (skey, pskey) could be leaked to attacker-controlled servers
languageJavaScript (Node.js)
root causeUser input embedded in external API URL without validation
vulnerabilityURL Injection via Unvalidated User Input

Introduction

In a QQ bot application's apps/qqinfo.js file, we discovered a critical URL injection vulnerability at line 24 that could expose authentication credentials to attackers. The QQinfo class handles user queries for QQ account information, but a flaw in how it processes the mid variable—derived directly from user messages—created a serious security risk. The vulnerable code constructed external API URLs by directly concatenating user input without any validation, opening the door for attackers to manipulate request parameters and potentially redirect sensitive authentication tokens (skey and pskey) to malicious servers.

This matters because any application that builds URLs from user input without validation faces similar risks. Even seemingly harmless features like looking up user profiles can become attack vectors when input validation is missing.

The Vulnerability Explained

Let's examine the vulnerable code from apps/qqinfo.js:

let mid = atMsg?.qq || e.msg.replace(/#| |查询qq/g, "")
if (mid == "") {
    return e.reply("请输入qq号或者直接艾特再发送命令", true)
}
// ... later in the code ...
const url = `http://jiuli.xiaoapi.cn/i/qq/qq_level.php?qq=${mid}&return=json&uin=${uinRegex}&skey=${skey}&pskey=${p_skey}`
logger.info(url)
const DATA_JSON = await fetch(url).then(res => res.json())

The problem is on line 26 (now line 25 after the fix). The code extracts mid from user messages by removing certain characters (#, spaces, and the Chinese text "查询qq"), but only checks if the result is an empty string. It never validates whether mid actually contains a valid QQ number (which should be purely numeric).

Here's what makes this exploitable:

The Attack Scenario:

An attacker could send a specially crafted message like:

查询qq12345&pskey=attacker_token&redirect=http://evil.com?data=

After the replace() operation removes "查询qq", the mid variable becomes:

12345&pskey=attacker_token&redirect=http://evil.com?data=

This passes the empty string check (mid != ""), and gets embedded directly into the URL:

http://jiuli.xiaoapi.cn/i/qq/qq_level.php?qq=12345&pskey=attacker_token&redirect=http://evil.com?data=&return=json&uin=${uinRegex}&skey=${skey}&pskey=${p_skey}

The Real-World Impact:

  1. Credential Leakage: The attacker can inject additional parameters that might be logged by the third-party API server or cause the API to behave unexpectedly with sensitive credentials exposed in the URL.

  2. Parameter Pollution: By injecting &pskey=attacker_token, the attacker could override the legitimate pskey parameter, potentially causing authentication failures or logging the real credentials alongside attacker-controlled values.

  3. Open Redirect: If the API endpoint has any redirect functionality, the injected parameters could redirect the request through attacker-controlled servers, where they can capture the full URL including the legitimate skey and pskey values.

  4. Server-Side Request Forgery (SSRF): Depending on how the external API processes unexpected parameters, an attacker might be able to manipulate the server into making requests to internal services or attacker-controlled endpoints.

The vulnerability is particularly dangerous because the code logged the full URL (logger.info(url)), which would write the complete request including credentials to log files—potentially exposing them if logs are compromised or improperly secured.

The Fix

The fix is elegantly simple but highly effective. Here's what changed:

Before:

if (mid == "") {
    return e.reply("请输入qq号或者直接艾特再发送命令", true)
}

After:

if (mid == "" || !/^\d+$/.test(mid)) {
    return e.reply("请输入qq号或者直接艾特再发送命令", true)
}

What This Changes:

The fix adds a regex validation pattern !/^\d+$/.test(mid) that ensures mid contains only digits from start (^) to end ($). The exclamation mark (!) negates the test, so if the pattern doesn't match (meaning mid contains anything other than pure digits), the function returns early with an error message.

This prevents injection attacks because:

  1. Strict Format Enforcement: Only numeric QQ IDs are accepted. Any attempt to inject special characters like &, =, /, ?, or . will fail validation.

  2. Attack Surface Elimination: The attacker cannot inject URL parameters, path traversal sequences, or redirect URLs because none of these can be expressed using only digits.

  3. Defense in Depth: Even if an attacker finds a way to bypass the replace() operation, the regex acts as a second layer of validation.

The fix also removes the logger.info(url) statement at line 41, which eliminates the risk of credentials being written to log files. This is an important security improvement because logs are often stored in less secure locations or transmitted to log aggregation services.

Why This Specific Change Works:

QQ numbers are always numeric identifiers. By enforcing this format at the validation layer, the code aligns with the actual business logic requirement. There's no legitimate use case where a QQ ID would contain special characters, so this validation doesn't break any valid functionality—it only blocks malicious input.

Key Takeaways

  • The mid variable in qqinfo.js was vulnerable because it only checked for empty strings, not valid QQ number format — allowing attackers to inject URL parameters containing special characters like &, =, and ?.

  • Adding the regex validation /^\d+$/.test(mid) prevents all injection attacks by ensuring only pure numeric input reaches the URL construction logic, eliminating the attack surface entirely.

  • Logging complete URLs with authentication credentials (skey, pskey) creates a secondary vulnerability — the removal of logger.info(url) prevents credential exposure through log files.

  • URL injection can occur even when input undergoes some processing — the replace() operation removed certain characters but didn't validate the result's format, showing that transformation alone isn't sufficient for security.

  • Format validation should match business logic requirements — since QQ IDs are always numeric, enforcing this constraint provides both security and data integrity without breaking legitimate use cases.

How Orbis AppSec Detected This

Source: User-controlled message content accessed via e.msg in the QQinfo plugin command handler.

Sink: Template literal URL construction at line 39 where mid is embedded directly into the external API URL: http://jiuli.xiaoapi.cn/i/qq/qq_level.php?qq=${mid}&....

Missing control: No input validation to ensure mid contains only numeric characters before embedding it in the URL, allowing injection of arbitrary URL parameters and special characters.

CWE: CWE-20 (Improper Input Validation) leading to potential CWE-601 (URL Redirection to Untrusted Site) and credential leakage.

Fix: Added regex validation !/^\d+$/.test(mid) to reject any input containing non-digit characters, ensuring only valid QQ numbers proceed to URL construction.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This URL injection vulnerability in apps/qqinfo.js demonstrates how seemingly simple input handling can create serious security risks. By failing to validate that user input matched the expected format (numeric QQ IDs), the code allowed attackers to manipulate API requests and potentially expose authentication credentials. The fix—a single regex validation check—eliminates the entire attack surface by enforcing strict input format requirements.

The lesson for developers is clear: never trust user input, even after basic processing. Always validate that input conforms to expected formats before using it in security-sensitive operations like URL construction, database queries, or system commands. Combining format validation with proper encoding and secure coding practices creates defense-in-depth protection against injection attacks.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #48

Related Articles

critical

How URL Injection happens in Node.js template literals and how to fix it

A URL injection vulnerability in `lib/client.js` allowed user-controlled `repo`, `branch`, and `file` parameters to be interpolated directly into fetch URLs without encoding, enabling potential URL manipulation and request hijacking. The fix introduces per-segment percent-encoding via a new `encodePathSegments` helper, neutralizing special characters before they reach the URL construction layer. This closes an exploit primitive that automated attack tooling could chain with other weaknesses.

critical

{sample} Placeholder in shlex.split() Lets Filenames Inject Args

A protocol replay-check CLI built its subprocess argument list by calling `str.format()` on a user-supplied `--command` template and then handing the result to `shlex.split()`, so a sample filename containing spaces, quotes, or shell metacharacters could split into extra argv entries — or execute as shell code when the template wrapped the placeholder in `sh -c`. The fix wraps the interpolated path in `shlex.quote()` before formatting, so the path always survives `shlex.split()` as a single toke

critical

JWT Authentication Disabled Signature Validation in

A critical misconfiguration in JWT authentication explicitly disabled signature validation, allowing attackers to forge valid tokens with arbitrary claims and bypass authentication entirely. The fix re-enables signature validation on all incoming bearer tokens, restoring the security boundary of the authentication layer.

critical

Actual Budget addTransaction.sh SQL Injection via Shell Variable

A critical SQL injection vulnerability in Actual Budget's transaction automation script allowed attackers to manipulate database records through shell variables interpolated directly into SQL strings. The fix introduces proper escaping functions and numeric validation to prevent injection through unquoted fields.

critical

DataTables RowGroup startRender XSS via Unescaped Group Data

DataTables RowGroup's default `startRender` callback inserted group labels directly into the DOM using HTML-aware methods, enabling XSS when user data reached the `dataSrc` property. The fix applies `util.escapeHtml()` to neutralize malicious payloads before insertion.

critical

shell-quote 1.8.3: Line Terminator Command Injection (CVE-2026-9277)

CVE-2026-9277 is a critical command injection vulnerability in shell-quote versions before 1.9.0, where unescaped line terminators allow attackers to break out of quoted strings and execute arbitrary shell commands. The fix upgrades the dependency across multiple React Native CLI packages and related libraries through npm overrides.