osquery / osquery/osquery

Harden the redirects behavior in TLS transport

Open
#8,922 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

hardening networking
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

  1. 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.

  1. Prometheus table should be changed to use TLSTransport().getInternalOptions() and inherit the above-mentioned same-origin redirect enforcement.

  2. 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.

  3. (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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.