How URL Parameter Injection Happens in JavaScript APIs and How to Fix It
Introduction
The bilibili-api.js file handles search requests to the Bilibili video platform API, but a flaw in the getUserInfoByKeyword() function at line 215 created a critical security gap. The function was directly concatenating a user-supplied keyword parameter into the API URL without any URL encoding:
const response = await fetch(`${_BILIAPI.BILIBILI_API}/x/web-interface/wbi/search/type?search_type=bili_user&keyword=${keyword}`);
This seemingly innocent string concatenation meant that any special characters in the keyword would be interpreted as part of the URL structure itself, rather than as data. For developers working on similar API clients or search features, this is a critical reminder that user input should never be directly interpolated into URLs without encoding.
The Vulnerability Explained
The Problem: Direct String Concatenation
When you build a URL by directly concatenating user input, special characters in that input take on new meaning:
// VULNERABLE CODE (line 215 of bilibili-api.js)
const response = await fetch(`${_BILIAPI.BILIBILI_API}/x/web-interface/wbi/search/type?search_type=bili_user&keyword=${keyword}`);
The keyword variable comes from user input (via HTTP parameters, form submission, or API request). By concatenating it directly into the URL string, the code treats the string as literal URL syntax.
Attack Scenarios
Scenario 1: Parameter Injection
If an attacker supplies a keyword like test&limit=1, the resulting URL becomes:
https://api.bilibili.com/x/web-interface/wbi/search/type?search_type=bili_user&keyword=test&limit=1
Instead of searching for "test&limit=1", the API receives TWO parameters: keyword=test and limit=1. This could bypass rate limiting, pagination controls, or other filtering logic the API enforces.
Scenario 2: Null Byte Injection
A keyword like test%00admin (URL-encoded null byte) could inject a null terminator:
https://api.bilibili.com/x/web-interface/wbi/search/type?search_type=bili_user&keyword=test%00admin
Depending on how the backend parses URLs, this might truncate the keyword at the null byte, returning results for "test" instead of the intended search, or causing parsing errors.
Scenario 3: Fragment/Anchor Injection
A keyword containing # like test#anchor could inject a URL fragment:
https://api.bilibili.com/x/web-interface/wbi/search/type?search_type=bili_user&keyword=test#anchor
This could cause the API request to fail or be interpreted differently by caching layers, proxies, or the client itself.
Real-World Impact
For the Bilibili API client, an attacker could:
- Manipulate search results by injecting unexpected parameters
- Bypass pagination or filtering controls
- Cause API errors or unexpected behavior
- Potentially access unintended API endpoints or data
- Create denial-of-service conditions by injecting malicious parameters
The Fix
The pull request applied a single, critical change to line 215:
- const response = await fetch(`${_BILIAPI.BILIBILI_API}/x/web-interface/wbi/search/type?search_type=bili_user&keyword=${keyword}`);
+ const response = await fetch(`${_BILIAPI.BILIBILI_API}/x/web-interface/wbi/search/type?search_type=bili_user&keyword=${encodeURIComponent(keyword)}`);
What encodeURIComponent() Does
The built-in encodeURIComponent() function properly percent-encodes special characters according to RFC 3986:
| Character | Encoded As |
|---|---|
& |
%26 |
= |
%3D |
# |
%23 |
% |
%25 |
(space) |
%20 |
/ |
%2F |
: |
%3A |
? |
%3F |
Now, if an attacker supplies test&limit=1, it becomes test%26limit%3D1:
https://api.bilibili.com/x/web-interface/wbi/search/type?search_type=bili_user&keyword=test%26limit%3D1
The Bilibili API receives a single parameter with the literal value test&limit=1—the & is part of the data, not a parameter separator.
How This Prevents the Attack
- Parameter injection prevented: Special characters are encoded, so they can't create new parameters
- Null bytes handled: The
%00is just data, no longer a terminator - Fragments blocked:
#becomes%23, so it's treated as part of the keyword value - Encoding preserved: International characters and spaces are handled correctly
Prevention & Best Practices
To avoid URL parameter injection vulnerabilities in JavaScript API clients:
1. Always Use encodeURIComponent() for Query Parameters
// ✅ SECURE
const keyword = userInput;
const url = `https://api.example.com/search?q=${encodeURIComponent(keyword)}`;
// ✅ ALSO SECURE - Using URLSearchParams
const params = new URLSearchParams();
params.append('q', keyword);
const url = `https://api.example.com/search?${params.toString()}`;
2. Use the URL Constructor for Complex URLs
// ✅ SECURE - Most readable and maintainable
const url = new URL('https://api.example.com/search');
url.searchParams.set('keyword', userInput);
url.searchParams.set('type', 'user');
fetch(url.toString());
3. Never Trust Framework Defaults
Even modern frameworks may not automatically encode query parameters—explicitly verify:
// Check your framework's documentation
// In Next.js, for example:
// ✅ SECURE
fetch(new URL('/api/search', process.env.API_BASE).toString() + '?' +
new URLSearchParams({keyword: userInput}).toString());
4. Use Static Analysis to Catch This
- Semgrep rule: Set up rules to detect string concatenation in fetch/request calls
- ESLint plugins: Use eslint-plugin-security to flag direct template literals in URLs
- SAST tools: Integrate tools like CodeQL or Snyk into your CI/CD pipeline
5. Apply Defense in Depth
While encoding is essential, also:
- Validate input type (ensure keyword is a string, has reasonable length)
- Use Content Security Policy headers
- Monitor API request patterns for anomalies
- Rate-limit endpoints to mitigate injection attempts
Key Takeaways
-
String concatenation in URL query parameters is dangerous: The
getUserInfoByKeyword()function teaches us that even simple API client code can introduce security vulnerabilities when handling user input. -
encodeURIComponent()is non-negotiable for dynamic URLs in JavaScript: This single function call prevents parameter injection, null byte injection, and fragment attacks—but it must be applied consistently. -
URLSearchParams is the modern, safe alternative: Rather than manually encoding, using
new URL()with.searchParamseliminates the class of vulnerability entirely. -
This pattern appears throughout codebases: If you have one instance of direct URL concatenation without encoding, you likely have others—conduct a codebase-wide audit for
fetch(),axios(), or other HTTP calls with dynamic parameters. -
Static analysis tools can catch this at development time: Semgrep rules and ESLint security plugins can detect this pattern before code reaches production, preventing exploitation.
How Orbis AppSec Detected This
Source: The keyword parameter is sourced from user input (HTTP request parameters, form data, or API arguments passed to getUserInfoByKeyword()).
Sink: The dangerous sink is the template literal string interpolation directly into the fetch URL at line 215: fetch(`...&keyword=${keyword}`).
Missing Control: The code lacked URL encoding/normalization. No encodeURIComponent(), URLSearchParams, or URL constructor was used to sanitize the dynamic parameter.
CWE: CWE-95 (Improper Neutralization of Directives in Dynamically Evaluated Code) and CWE-20 (Improper Input Validation).
Fix: Wrap the keyword parameter with encodeURIComponent() to percent-encode all special characters before URL concatenation.
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
URL parameter injection is a subtle but critical vulnerability that developers often overlook when building API clients. The fix in bilibili-api.js demonstrates that a single line change—adding encodeURIComponent(keyword)—is all that's needed to eliminate the risk. However, the best defense is prevention through consistent practices: always use URLSearchParams, the URL constructor, or explicit encoding functions when building URLs with dynamic data. Audit your codebase for similar patterns, enable static analysis tools in your CI/CD pipeline, and remember that user input should never be trusted in URL construction. Secure coding practices like these are essential for building resilient API clients and preventing attackers from manipulating your application's interactions with third-party services.
References
- CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')
- CWE-20: Improper Input Validation
- OWASP: URL Encoding Cheat Sheet
- MDN: encodeURIComponent()
- MDN: URLSearchParams API
- Semgrep Rule: Insecure URL Concatenation
- GitHub PR: fix: the getuserinfobykeyword function directly inte... in...