HexBrain

Node.js third-party modules · Report #541502

[https-proxy-agent] Socket returned without TLS upgrade on non-200 CONNECT response, allowing request data to be sent over unencrypted connection

Validstatus
mediumseverity
4upvotes
25 Sept 2019disclosed

How it was found

I would like to report a man-in-the-middle vulnerability in https-proxy-agent. It allows an attacker with access to the network firewall or targeted proxy server to obtain secrets (e.g. a HTTP basic auth header) from the client trying to send HTTPS traffic via HTTP proxy.

Module

module name: https-proxy-agent version: 2.2.1 npm page: https://www.npmjs.com/package/https-proxy-agent

Module Description

This module provides an http.Agent implementation that connects to a specified HTTP or HTTPS proxy server, and can be used with the built-in https module.

Module Stats

4314908 downloads in the last week

Vulnerability

Vulnerability Description

When targeting a HTTP proxy, https-proxy-agent opens a socket to the proxy, and sends the proxy server a CONNECT request. E.g.:

CONNECT www.google.com:443 HTTP/1.1
Host: www.google.com
Connection: close

If the proxy server responds with 200 and the client is targeting a secure endpoint, https-proxy-agent TLS-upgrades the socket before returning it for use. This is normal and expected.

However, if the proxy server (or firewall blocking the request) responds with something other than a 200, https-proxy-agent incorrectly returns the socket without any TLS upgrade. Companion library agent-base passes the socket off to Node HTTP internals, which will write the pending request data to the socket. E.g.:

GET / HTTP/1.1
Host: www.google.com
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
Connection: close

The request data, which may contain basic auth credentials or other secrets, is sent over an unencrypted connection. An attacker with access to tcpdump contents at the firewall or proxy server can steal these secrets and impersonate the client.

Steps To Reproduce:

Run a simple web server on port 80 that returns 403 in response to any request:

#!/bin/bash
while true; do
  echo -e "HTTP/1.1 403 FORBIDDEN\r\n$(date)\r\n\r\n<h1>hello world from $(hostname) on $(date)</h1>" |  nc -vl 80;
done

Send a a request to a remote server using the simple web server as a proxy:

var url = require('url');
var https = require('https');
var HttpsProxyAgent = require('https-proxy-agent');

var proxyOpts = url.parse('http://127.0.0.1:80');
var opts = url.parse('https://www.google.com');
var agent = new HttpsProxyAgent(proxyOpts);
opts.agent = agent;
opts.auth = 'username:password';
https.get(opts);

Logs observed on the simple web server:

CONNECT www.google.com:443 HTTP/1.1
Host: www.google.com
Connection: close

GET / HTTP/1.1
Host: www.google.com
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
Connection: close

Patch

Changes in a commit from 2013 to buffer and replay incoming proxy data in case of error should be reconsidered. Maybe the replaying can still be done when targeting an insecure endpoint, but it seems unsafe to return the socket without TLS upgrade in the secure endpoint case. It would be better to do something like:

if (200 == statusCode) {
  // Happy path
} else if (secureEndpoint) {
  cleanup();
  socket.destroy();
  fn(new Error(`Could not establish TLS connection. Status code: ${statusCode}`));
} else {
  // Replay on insecure endpoint
}

There may also be a way for the agent to replay the incoming proxy data on and then destroy the socket before returning it.

Supporting Material/References:

  • Ubuntu 16.04 / Linux 4.15.0-43
  • Node 10.15.3
  • npm 6.4.1
  • gnu-netcat 0.7.1

Wrap up

  • I contacted the maintainer to let them know: N
  • I opened an issue in the related repository: N

Impact

The vulnerability allows a determined attacker with access to the network firewall or targeted proxy server to see plaintext request data, which could expose auth credentials or other secrets.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original