Skip to content

Unauthenticated AnyProxy web interface exposes captured HTTP/HTTPS traffic (including cookies and auth headers) to any website via wildcard CORS and an Origin-unchecked WebSocket #620

Description

@geo-chen

Affected Version: 4.1.3

Summary

AnyProxy's local web interface (default http://127.0.0.1:8002, started automatically by anyproxy --web / anyproxy-ca workflows) has no authentication whatsoever, and its JSON API endpoints explicitly set Access-Control-Allow-Origin: *, unconditionally allowing any website to read the responses via fetch()/XMLHttpRequest. In addition, the companion WebSocket server performs no Origin validation at all, so any web page can open a WebSocket connection to it and receive a live broadcast stream of every request/response AnyProxy is currently recording, including headers such as Cookie and Authorization.

Because AnyProxy is a MITM debugging proxy, its whole purpose is to sit in the path of the traffic the user is inspecting (often traffic for other sites they are actively logged into while debugging). Any web page the user happens to have open in the same browser — completely unrelated to AnyProxy — can silently exfiltrate that captured traffic in real time, with zero user interaction with AnyProxy and no way for the user to notice.

Details

lib/webInterface.js sets a wildcard CORS header on the endpoints that expose the recorder's data:

// lib/webInterface.js
app.get('/latestLog', (req, res) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  recorder.getRecords(null, 10000, (err, docs) => {
    if (err) {
      res.end(err.toString());
    } else {
      res.json(docs);
    }
  });
});

/latestLog returns up to 10,000 full traffic records via recorder.getRecords, and each record includes the full request/response headers as captured by recorder.js:

// lib/recorder.js — normalizeInfo()
singleRecord.reqHeader = info.req.headers;   // includes Cookie, Authorization, etc.
...
singleRecord.resHeader = info.resHeader;

The same Access-Control-Allow-Origin: * pattern is repeated on /fetchBody, /fetchCrtFile, /api/getQrCode, and /api/getInitData (lib/webInterface.js), so response bodies (/fetchBody, which can carry account data, tokens, API responses) and the AnyProxy root CA certificate (/fetchCrtFile) are also readable cross-origin. None of these routes check the Host or Origin request header before responding, and there is no authentication middleware anywhere in getServer().

Separately, lib/wsServer.js creates a ws WebSocketServer on the same HTTP server with no Origin verification:

// lib/wsServer.js
const wss = new WebSocketServer({
  server: config.server,
  clientTracking: true,
});
...
recorder.on('update', (data) => {
  try {
    sendMultipleMessage(data);   // broadcast to ALL connected clients, no origin filter
  } catch (e) { ... }
});

sendMultipleMessage broadcasts every new/updated traffic record to every connected WebSocket client with wss.broadcast(...). Browsers do not apply the Same-Origin Policy to the WebSocket handshake itself, so a malicious page can open new WebSocket('ws://127.0.0.1:8002') from any origin and receive this live firehose without any CORS headers being involved at all — this channel is not mitigated even by fixing the Access-Control-Allow-Origin: * issue above.

Live validation

Pinned release: anyproxy@4.1.3 (npm), matching git commit b93f9481 ("release 4.1.3", the HEAD of the repository).

  1. Started AnyProxy locally with its default web UI:
    node bin/anyproxy -w 8002
    
  2. Sent a forged Origin header from a completely unrelated domain directly to the web API and confirmed the server reflects/allows it unconditionally:
    $ curl -sS -i -H "Origin: http://evil.example.com" http://127.0.0.1:8002/api/getInitData
    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: *
    ...
    {"status":"success","rootCAExists":false, ...}
    
  3. Proxied a request carrying secret cookie values through AnyProxy (simulating the victim debugging a real logged-in site):
    $ curl -x http://127.0.0.1:8001 -H "Cookie: session=SECRET_TOKEN_abc123; auth=topsecret" http://httpbin.org/get
    
  4. Confirmed the secret is retrievable cross-origin via the wildcarded HTTP endpoint:
    $ curl -sS -H "Origin: http://evil.example.com" http://127.0.0.1:8002/latestLog
    [{"url":"http://httpbin.org/get","reqHeader":{"Cookie":"session=SECRET_TOKEN_abc123; auth=topsecret", ...}, ...}]
    
  5. Confirmed the WebSocket channel independently discloses the same data with no Origin check, by connecting with a forged Origin: http://evil.example.com header and observing a live broadcast the moment a second proxied request was made:
    $ curl -x http://127.0.0.1:8001 -H "Cookie: session2=ANOTHER_SECRET_xyz" http://httpbin.org/headers
    
    WebSocket client output (Origin header forged to evil.example.com, connection accepted, no rejection):
    WS OPENED cross-origin (evil.example.com) with no rejection
    WS MESSAGE: {"type":"updateMultiple","content":[{"url":"http://httpbin.org/headers", ...,
      "reqHeader":{...,"Cookie":"session2=ANOTHER_SECRET_xyz"}, ...}]}
    

Both channels independently and fully disclosed the injected secret values to a simulated cross-origin attacker, with zero authentication and zero legitimate Origin required.

Proof of Concept

(available upon request)

Impact

Any website a user visits in the same browser while AnyProxy's local web UI is running can silently steal the full contents of every HTTP/HTTPS request and response AnyProxy has captured or is currently capturing — including session cookies, Authorization bearer tokens, API keys in headers or bodies, and any other sensitive data present in traffic the user is debugging through AnyProxy. Because AnyProxy's entire purpose is to intercept traffic for other applications the user is actively testing (often while logged in with real credentials), this converts routine use of a local debugging tool into a drive-by credential/session theft primitive triggerable by any page open in another tab, with no interaction with AnyProxy required.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions