alibaba / alibaba/anyproxy

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

Open
#620 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
7.9k
Forks
1.2k
PR merge metrics
No merged PRs in 30d

Description

### 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:

```js
// 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`:

```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:

```js
// 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.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with lib/webInterface.js and lib/wsServer.js, then trace recorder.js's normalizeInfo() and the routes named in the report. Reproduce the HTTP and WebSocket disclosures using the provided curl examples. Done means captured records, bodies, certificates, and live WebSocket updates are no longer available to unauthorized cross-origin pages.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
backend, security
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.