Harden the redirects behavior in TLS transport
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 23.6k
- Forks
- 2.6k
- Avg merge
- 6d 7h
- Merged PRs (30d)
- 14
Description
Problem
The osquery TLS transport follows HTTP redirects by default, including cross-origin redirects that downgrade HTTPS to HTTP. This is mitigated by the fact that a TLS handshake with the remote server is completed before any redirect response would be accepted (i.e., it's not a MitM risk scenario, just a misconfigured server/CDN/load balancer scenario).
In osquery/remote/transports/tls.cpp:112, follow_redirects is set to true. The HTTP client implementation in osquery does not restrict redirects to same-origin or same-protocol destinations. A redirect from an HTTPS endpoint to an HTTP endpoint will be followed, sending the request body in cleartext.
Besides the TLS fleet server, osquery also has curl, curl_certificate, Prometheus, and yara tables capable of making an HTTPS request.
The YARA rule fetch at yara.cpp:180 uses TLSTransport().getInternalOptions().
The curl table at curl.cpp:45 uses TLSTransport().getOptions().
Prometheus table currently creates its own Client::Options from scratch rather than using TLSTransport().getOptions(). This means it gets follow_redirects(true) but none of the TLS hardening — no cipher suite restriction, no protocol version flags (SSL_OP_NO_TLSv1 etc.), no certificate verification configuration, and no server certificate pinning. The scrape URLs come from the osquery config, which is fetched from the fleet server.
curl_certificate.cpp doesn't use the HTTP client at all. It opens a raw TCP socket (socket() at line 356), connects directly (connect() at line 377), wraps it in an OpenSSL SSL object (SSL_new / SSL_set_bio / SSL_connect at lines 389-410), and reads the peer certificate.
Mitigating factors
When --tls_server_certs flag points to a specific fleet server CA, redirects are less of a problem because osqueryd should only talk to servers with the trusted certs. When it's the default OSQUERY_CERTS_HOME "certs.pem", osqueryd can talk to any server with a valid cert from any public CA.
Specific hardening suggestions to consider
- Set follow_redirects(false) in the TLS transport options at tls.cpp:112:
options.follow_redirects(false).always_verify_peer(verify_peer_).timeout(16);
Perhaps allowing redirects to be re-enabled with a config flag if needed.
Alternatively, keep redirects working for legitimate same-origin cases (path changes, trailing-slash normalization) while blocking cross-origin, cross-scheme, and cross-port redirects. After parsing the redirect URL into a new URI, compare the request's scheme, host, and port. In the redirect-handling block at http_client.cpp:464-488:
std::string redir_url = Response(resp.release()).headers()["Location"];
if (!redir_url.size()) {
throw std::runtime_error("Location header missing in redirect response");
}
if (redir_url[0] != '/') {
// Absolute URI — enforce same-scheme and same-origin.
Uri redir_uri(redir_url);
if (req.protocol() && redir_uri.scheme() != *req.protocol()) {
throw std::runtime_error(
"Redirect blocked: scheme changed from " +
*req.protocol() + " to " + redir_uri.scheme());
}
if (req.remoteHost() && redir_uri.host() != *req.remoteHost()) {
throw std::runtime_error(
"Redirect blocked: host changed from " +
*req.remoteHost() + " to " + redir_uri.host());
}
auto original_port = req.remotePort()
? *req.remotePort()
: (req.protocol() && *req.protocol() == "https" ? "443" : "80");
auto redir_port = redir_uri.port() > 0
? std::to_string(redir_uri.port())
: (redir_uri.scheme() == "https" ? "443" : "80");
if (original_port != redir_port) {
throw std::runtime_error(
"Redirect blocked: port changed from " +
original_port + " to " + redir_port);
}
init_request = true;
}
req.uri(redir_url);
The relative-URI path (line 471, redir_url[0] == '/') is already safe — it preserves the original scheme, host, and port by construction. The enforcement only needs to apply to absolute URIs,
which is where the current code sets init_request = true and allows a completely different origin.
-
Prometheus table should be changed to use
TLSTransport().getInternalOptions()and inherit the above-mentioned same-origin redirect enforcement. -
curl table: because this exists specifically to let the query issuer fetch arbitrary URLs, restricting it to same-origin redirects would break legitimate use cases like following a URL shortener or a CDN redirect. If redirects are disabled for curl table, maybe return the 3xx status to the caller so they can see the redirect rather than silently following it. But HTTPS-to-HTTP downgrades should still be refused in any case.
-
(a) At yara.cpp:149-165, isRuleUrlAllowed() checks the sigurl against an allowlist by comparing scheme and host (line 155-156), then regex-matching the path (line 158-159). The port is never compared, but should be.
(b) If follow_redirects remains enabled for YARA, enforce same-scheme and same-origin (host:port) on absolute Location; otherwise fail. Strip request body and Authorization/cookie headers on any cross-origin hop.
(c) Re-validate the post-redirect URL against signature_urls before fetching.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing redirect handling in osquery/remote/transports/tls.cpp and the redirect block in http_client.cpp:464-488, then inspect yara.cpp, curl.cpp, prometheus, and curl_certificate.cpp as described. Determine the agreed policy for absolute redirects and each table's exceptions. Done means HTTPS downgrade and unsafe cross-origin behavior are addressed consistently across the affected request paths, with the YARA port and post-redirect checks covered.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- networking, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100