Skip to content

Allowlist request options forwarded from the WebSocket handler#1393

Open
TristanInSec wants to merge 1 commit intocdapio:developfrom
TristanInSec:restrict-ws-request-options
Open

Allowlist request options forwarded from the WebSocket handler#1393
TristanInSec wants to merge 1 commit intocdapio:developfrom
TristanInSec:restrict-ws-request-options

Conversation

@TristanInSec
Copy link
Copy Markdown

Summary

The 'request' action inside Aggregator#onSocketData currently forwards the entire client-supplied resource object straight into the request HTTP library:

r.headers.Authorization = this.connection.authToken;
r.headers[this.cdapConfig['security.authentication.proxy.user.identity.header']] = this.connection.userid;
...
request(r, emitResponse.bind(this, r))

Because the server first injects its own Authorization header (and, in proxy mode, a user-identity header) into r.headers, every field that happens to sit on r becomes a client-controllable option on an outbound request that is already carrying server credentials. The request library recognises several options the WebSocket client has no business setting:

  • proxy, tunnel
  • auth
  • cert, key, ca, pfx
  • agent, agentOptions
  • baseUrl
  • strictSSL, rejectUnauthorized
  • pool, localAddress
  • har, aws, oauth, hawk, httpSignature

Changes

  • Add REQUEST_OPTION_ALLOWLIST and buildSafeRequestOptions(resource) in server/aggregator.js. The helper copies only a fixed set of fields — method, url/uri, headers, body/json/form/formData/multipart, qs/qsStringifyOptions/useQuerystring, encoding, gzip, timeout, followRedirect/followAllRedirects/maxRedirects — onto a fresh object, so unknown/dangerous fields are dropped before they reach request().
  • The 'request' case now calls request(buildSafeRequestOptions(r), ...). The original r continues to be bound into emitResponse, so fields consumed on the response side (id, requestOrigin, startTs, etc.) are unaffected.

Testing

Exercised buildSafeRequestOptions in isolation against an input that mixes legitimate fields with the full list of disallowed options. The resulting object contained only method, url, headers, and body; proxy, auth, cert, agent, strictSSL, baseUrl, har, aws, oauth, hawk, and the metadata fields (id, requestOrigin, startTs, templateid, pluginid) were all dropped.

The 'request' action in Aggregator#onSocketData currently passes the
entire client-supplied resource object directly to the request library:

    request(r, emitResponse.bind(this, r))

Before that call the server injects its own Authorization header and
proxy-user identity header into r.headers, so every field present on
the resource object becomes a client-controllable option on an
outbound request that is already carrying server credentials. The
request library recognises a number of fields that the WebSocket
client has no legitimate reason to set, notably proxy, tunnel, auth,
cert, key, ca, pfx, agent, agentOptions, baseUrl, strictSSL,
rejectUnauthorized, pool, localAddress, har, aws, oauth, hawk, and
httpSignature.

Introduce buildSafeRequestOptions(resource): it copies only a fixed
allowlist of fields (method, url/uri, headers, body/json/form/formData/
multipart, qs/qsStringifyOptions/useQuerystring, encoding, gzip,
timeout, followRedirect/followAllRedirects/maxRedirects) onto a fresh
object that is then handed to request(). The original resource object
continues to be bound into emitResponse so the response path (id,
requestOrigin, startTs, etc.) is unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants