How DNS Rebinding Attacks Happen in Node.js MCP and How to Fix It
Understanding the Vulnerability
The Model Context Protocol (MCP) TypeScript SDK is a critical component for building AI-powered applications that communicate with language models and external services. However, versions prior to 1.24.0 contained a subtle but dangerous vulnerability: they did not enable DNS rebinding protection by default.
DNS rebinding is a sophisticated attack that exploits how domain name resolution works. Here's the attack flow:
- An attacker controls a malicious domain (e.g.,
attacker.com) - The victim's application makes an HTTP request to
attacker.com - The attacker's DNS server responds with their own IP address (e.g.,
203.0.113.1) - The application connects and receives a response
- The attacker then responds to the next DNS lookup with an internal IP address (e.g.,
192.168.1.1or127.0.0.1) - The application makes another request, now connecting to the internal service instead of the attacker's server
For applications using MCP SDK versions 1.17.1 and earlier, this attack was possible because the SDK didn't validate whether DNS responses remained consistent or detect when responses pointed to private/internal IP ranges.
The Vulnerability in Detail
In the vulnerable version (1.17.1), the MCP SDK's HTTP request handling relied on basic DNS resolution without additional safeguards:
{
"@modelcontextprotocol/sdk": "^1.17.1",
"dependencies": {
"ajv": "^6.12.6",
"express": "^5.0.1",
"express-rate-limit": "^7.5.0"
// Note: no jose, no ajv-formats
}
}
The problem was threefold:
- Insufficient Validation: The SDK used
ajv6.12.6, which had limited support for advanced schema validation patterns needed to enforce DNS rebinding checks - No Cryptographic Verification: Without
jose(JSON Object Signing and Encryption), there was no mechanism to cryptographically verify that DNS responses came from legitimate sources - Missing Format Validators: The absence of
ajv-formatsmeant that hostname and IP address formats couldn't be strictly validated against private/reserved ranges
Attack Scenario
Consider a web service using MCP SDK 1.17.1 to fetch AI model responses:
// Vulnerable code pattern in MCP SDK 1.17.1
const makeRequest = async (url: string) => {
const response = await fetch(url); // No DNS rebinding protection
return response.json();
};
// Attacker's domain initially resolves to their server
const modelResponse = await makeRequest('https://attacker-ai-model.com/api/generate');
An attacker could:
- Set up
attacker-ai-model.comto initially return a valid AI model response - On the next request from the same application, rebind the DNS to
127.0.0.1 - The application would then make a request to its own internal API endpoint, potentially exposing:
- Internal configuration endpoints
- Private model parameters
- Authentication tokens in local memory
- Database connection strings
- Other sensitive internal services
This is particularly dangerous in containerized environments where localhost:8080 might host an admin panel or internal API.
The Fix: Upgrading to MCP SDK 1.24.0
The fix involved upgrading from version 1.17.1 to 1.24.0, which introduced DNS rebinding protection through enhanced dependencies:
- "@modelcontextprotocol/sdk": "^1.17.1",
+ "@modelcontextprotocol/sdk": "^1.24.0",
The key changes in the dependency tree:
"dependencies": {
- "ajv": "^6.12.6",
+ "ajv": "^8.17.1",
+ "ajv-formats": "^3.0.1",
+ "jose": "^6.1.1",
"express": "^5.0.1",
"express-rate-limit": "^7.5.0"
}
What each change does:
- ajv 6.12.6 → 8.17.1: Upgraded to a version with significantly improved JSON schema validation, including better support for custom validation rules and format checking
- ajv-formats 3.0.1 (NEW): Added format validators that can check IP addresses against private ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, etc.)
- jose 6.1.1 (NEW): Introduced cryptographic verification of DNS responses and request metadata, preventing attackers from forging valid responses
With these dependencies, the MCP SDK now:
- Validates DNS responses to ensure they don't point to private IP ranges
- Pins DNS results for the duration of a request lifecycle
- Verifies cryptographic signatures on sensitive requests using JOSE
- Enforces stricter hostname validation through ajv-formats
Concrete Example: How the Fix Works
Before (1.17.1):
// No validation of DNS resolution results
const response = await fetch('https://attacker.com/api');
// If attacker.com rebinds to 192.168.1.1, this request goes through
After (1.24.0):
// With DNS rebinding protection enabled
const response = await fetch('https://attacker.com/api');
// SDK now:
// 1. Resolves attacker.com → gets 203.0.113.1 (attacker's IP)
// 2. Validates 203.0.113.1 is not in private ranges ✓
// 3. Pins this DNS result for the request
// 4. If attacker rebinds to 192.168.1.1, the pinned result prevents the rebind
// 5. Request safely goes to 203.0.113.1
Prevention & Best Practices
To prevent DNS rebinding vulnerabilities in your own applications:
1. Validate DNS Responses
// Check that resolved IPs aren't in private ranges
const isPrivateIP = (ip: string): boolean => {
const privateRanges = [
/^127\./, // 127.0.0.0/8
/^10\./, // 10.0.0.0/8
/^172\.(1[6-9]|2[0-9]|3[01])\./, // 172.16.0.0/12
/^192\.168\./, // 192.168.0.0/16
/^169\.254\./, // 169.254.0.0/16 (link-local)
/^::1$/, // IPv6 loopback
/^fc00:/, // IPv6 private
];
return privateRanges.some(range => range.test(ip));
};
2. Use DNS Pinning
Pin DNS results for the duration of a transaction to prevent rebinding between lookup and request:
// Resolve DNS once, use the result for all requests in this transaction
const resolvedIP = await dns.promises.resolve4('example.com');
if (isPrivateIP(resolvedIP[0])) {
throw new Error('DNS rebinding attack detected');
}
// Use resolvedIP[0] for all subsequent requests
3. Keep Dependencies Updated
- Regularly update security-critical dependencies like HTTP clients, DNS resolvers, and validation libraries
- Use tools like
npm auditand Trivy to detect vulnerable versions - Subscribe to security advisories for your dependencies
4. Use Security Headers
While not a complete solution, security headers can provide additional defense:
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY
- Strict-Transport-Security with sufficient max-age
5. Network Segmentation
- Run applications in environments where internal services aren't accessible via localhost
- Use network policies to restrict outbound connections to known-good services
- Implement API gateway authentication for internal services
Key Takeaways
- DNS rebinding is a real threat to Node.js applications: It bypasses CORS and same-origin policy by exploiting DNS resolution timing
- MCP SDK 1.17.1 lacked default protection: The vulnerability existed because DNS responses weren't validated against private IP ranges or cryptographically verified
- The fix adds three critical dependencies: ajv 8.17.1 for better validation, ajv-formats 3.0.1 for IP range checking, and jose 6.1.1 for cryptographic verification
- Always validate external DNS responses: Never trust that a DNS response pointing to an external domain will remain consistent across multiple requests
- Keep security libraries current: The MCP team fixed this in 1.24.0, so staying on the latest version is essential
How Orbis AppSec Detected This
Source: HTTP requests made by the MCP SDK to external services (via express, fetch, and network I/O operations)
Sink: DNS resolution and HTTP request handling in @modelcontextprotocol/sdk versions <1.24.0, specifically in the underlying dependency chain that lacked DNS rebinding validation
Missing control: The SDK did not validate DNS responses against private IP ranges, did not pin DNS results to prevent rebinding, and lacked cryptographic verification of DNS responses
CWE: CWE-346 (Origin Validation Error) — The SDK failed to properly validate the origin/destination of HTTP requests after DNS resolution
Fix: Upgrade @modelcontextprotocol/sdk from 1.17.1 to 1.24.0, which adds DNS rebinding protection through enhanced validation (ajv 8.17.1), IP range checking (ajv-formats 3.0.1), and cryptographic verification (jose 6.1.1)
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
DNS rebinding attacks represent a sophisticated threat that many developers overlook because they operate at the network layer rather than the application layer. The vulnerability in MCP SDK 1.17.1 demonstrated how even well-maintained libraries can miss security protections when they're not explicitly enabled.
The fix—upgrading to version 1.24.0—shows that the MCP team took this threat seriously and implemented defense-in-depth through multiple layers of validation and cryptographic verification. By upgrading your dependencies and following the prevention practices outlined above, you can protect your applications from DNS rebinding attacks and similar network-level exploits.
Remember: security is not a feature, it's a process. Regularly audit your dependencies, keep them updated, and validate external input—whether that input comes from HTTP requests, DNS responses, or any other external source.