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.

Prevention & Best Practices

To avoid URL injection vulnerabilities in your code:

1. Always Validate Input Format

Use allowlists (like regex patterns) to validate that input matches expected formats before using it in URLs:

// Good: Validate before use
if (!/^[a-zA-Z0-9_-]+$/.test(userInput)) {
    throw new Error("Invalid input format");
}

2. Use URL Construction Libraries

Instead of string concatenation, use URL construction methods that handle encoding:

// Better approach
const url = new URL('http://api.example.com/endpoint');
url.searchParams.append('qq', mid);
url.searchParams.append('skey', skey);

This automatically encodes special characters and prevents parameter injection.

3. Never Log Sensitive Data

Avoid logging complete URLs that contain authentication tokens, API keys, or other credentials. If logging is necessary for debugging:

// Redact sensitive parameters
const safeUrl = url.replace(/skey=[^&]+/, 'skey=REDACTED')
                   .replace(/pskey=[^&]+/, 'pskey=REDACTED');
logger.info(safeUrl);

4. Implement Input Sanitization Layers

Apply multiple layers of validation:
- Syntax validation: Check format (regex, type checking)
- Semantic validation: Verify the input makes sense for your use case
- Encoding: Properly encode before embedding in URLs

5. Use Security Linters

Tools like ESLint with security plugins can detect dangerous patterns:

npm install --save-dev eslint-plugin-security

Configure rules to flag unvalidated user input in URL construction.

6. Follow OWASP Guidelines

Refer to the OWASP Input Validation Cheat Sheet for comprehensive guidance on input validation strategies.

7. Consider Using Allowlist-Based Routing

For API integrations, maintain an allowlist of valid endpoints and parameters rather than constructing URLs dynamically from user 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.

References

Frequently Asked Questions

What is URL injection via unvalidated user input?

URL injection occurs when user-controlled data is embedded into URLs without validation, allowing attackers to manipulate request parameters, redirect requests to malicious servers, or inject additional query parameters that leak sensitive data.

How do you prevent URL injection in Node.js?

Validate and sanitize all user input before embedding it in URLs. Use allowlists (like regex patterns) to ensure input matches expected formats, encode special characters, and avoid directly concatenating user input into URL strings. Consider using URL parsing libraries that handle encoding automatically.

What CWE is URL injection via unvalidated user input?

This falls under CWE-20 (Improper Input Validation) and can lead to CWE-601 (URL Redirection to Untrusted Site) or CWE-918 (Server-Side Request Forgery) depending on the exploitation scenario.

Is URL encoding enough to prevent URL injection?

No. URL encoding only prevents syntax errors and basic injection, but doesn't validate that the input is semantically correct or safe. Attackers can still inject valid but malicious parameters. You need input validation (allowlists, format checks) combined with encoding for comprehensive protection.

Can static analysis detect URL injection vulnerabilities?

Yes. Static analysis tools can trace data flow from user input sources to URL construction sinks, identifying cases where unvalidated user data is embedded in URLs. Tools like Semgrep, CodeQL, and specialized security scanners can detect these patterns automatically.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #48

Related Articles

critical

How Insecure API Key Transmission Happens in JavaScript Browser Extensions and How to Fix It

A critical vulnerability in `utils/common.js` allowed API keys to be transmitted over unencrypted HTTP connections to remote servers, exposing them to network interception. The `buildModelApiRequest` function at line 490 constructed API requests without validating the transport protocol, enabling man-in-the-middle attacks. The fix enforces HTTPS for all remote API endpoints while preserving HTTP access for local development servers on loopback addresses.

critical

How Insecure HTTPS Requests and Missing Timeouts Happen in Python and How to Fix Them

A critical security hardening issue was discovered in `scripts/maimai/songs.py` where HTTP requests were made without SSL certificate verification and timeout values. This combination creates a Man-in-the-Middle (MITM) attack vector that could allow adversaries to intercept sensitive data or inject malicious content. The fix adds explicit SSL verification enforcement and request timeouts to all HTTP calls.

critical

How Arbitrary Code Execution via Command Injection Happens in Node.js shell-quote and How to Fix It

A critical command injection vulnerability (CVE-2026-9277) was discovered in the popular Node.js `shell-quote` package (versions prior to 1.8.4) where unescaped line terminators allowed attackers to inject and execute arbitrary shell commands. The fix upgrades `shell-quote` from version 1.8.1 to 1.8.4, which properly escapes line terminator characters (such as `\n`, `\r`, `\u2028`, and `\u2029`) before passing strings to the shell. This dependency was present in the project's `package-lock.json`

critical

How Security Bypass in Salesforce SOQL Queries Happens in Apex and How to Fix It

A critical security vulnerability in the ProductController.cls file allowed unauthorized users to bypass Salesforce's field-level and object-level security by executing unprotected SOQL queries. The fix adds a single `WITH USER_MODE` clause to enforce security checks, preventing guest users and unauthorized callers from accessing sensitive product data.

critical

How ReDoS Vulnerabilities Happen in Node.js Express Applications and How to Fix Them

A critical Regular Expression Denial of Service (ReDoS) vulnerability in the path-to-regexp package (CVE-2024-45296) was discovered in the lacartoons-addon project's dependency tree. The vulnerable versions used backtracking regular expressions that could cause catastrophic performance degradation when processing malicious route patterns. Upgrading to patched versions (0.1.10 for Express's internal router) eliminates this attack vector.