Merge pull request #20432 from github/copilot/fix-f50317f8-0a91-4bb4-a01b-353dcf0f6f3f

Rust: Implement new query for non-HTTPS URLs (CWE-319)
This commit is contained in:
Geoffrey White
2025-09-22 18:03:52 +01:00
committed by GitHub
15 changed files with 1921 additions and 0 deletions

View File

@@ -14,6 +14,7 @@ ql/rust/ql/src/queries/security/CWE-089/SqlInjection.ql
ql/rust/ql/src/queries/security/CWE-311/CleartextTransmission.ql
ql/rust/ql/src/queries/security/CWE-312/CleartextLogging.ql
ql/rust/ql/src/queries/security/CWE-312/CleartextStorageDatabase.ql
ql/rust/ql/src/queries/security/CWE-319/UseOfHttp.ql
ql/rust/ql/src/queries/security/CWE-327/BrokenCryptoAlgorithm.ql
ql/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql
ql/rust/ql/src/queries/security/CWE-770/UncontrolledAllocationSize.ql

View File

@@ -15,6 +15,7 @@ ql/rust/ql/src/queries/security/CWE-117/LogInjection.ql
ql/rust/ql/src/queries/security/CWE-311/CleartextTransmission.ql
ql/rust/ql/src/queries/security/CWE-312/CleartextLogging.ql
ql/rust/ql/src/queries/security/CWE-312/CleartextStorageDatabase.ql
ql/rust/ql/src/queries/security/CWE-319/UseOfHttp.ql
ql/rust/ql/src/queries/security/CWE-327/BrokenCryptoAlgorithm.ql
ql/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql
ql/rust/ql/src/queries/security/CWE-696/BadCtorInitialization.ql

View File

@@ -15,6 +15,7 @@ ql/rust/ql/src/queries/security/CWE-117/LogInjection.ql
ql/rust/ql/src/queries/security/CWE-311/CleartextTransmission.ql
ql/rust/ql/src/queries/security/CWE-312/CleartextLogging.ql
ql/rust/ql/src/queries/security/CWE-312/CleartextStorageDatabase.ql
ql/rust/ql/src/queries/security/CWE-319/UseOfHttp.ql
ql/rust/ql/src/queries/security/CWE-327/BrokenCryptoAlgorithm.ql
ql/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql
ql/rust/ql/src/queries/security/CWE-770/UncontrolledAllocationSize.ql

View File

@@ -0,0 +1,62 @@
/**
* Provides classes and predicates for reasoning about the use of
* non-HTTPS URLs in Rust code.
*/
import rust
private import codeql.rust.dataflow.DataFlow
private import codeql.rust.dataflow.FlowSink
private import codeql.rust.Concepts
/**
* Provides default sources, sinks and barriers for detecting use of
* non-HTTPS URLs, as well as extension points for adding your own.
*/
module UseOfHttp {
/**
* A data flow source for use of non-HTTPS URLs.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for use of non-HTTPS URLs.
*/
abstract class Sink extends QuerySink::Range {
override string getSinkType() { result = "UseOfHttp" }
}
/**
* A barrier for use of non-HTTPS URLs.
*/
abstract class Barrier extends DataFlow::Node { }
/**
* A string containing an HTTP URL.
*/
class HttpStringLiteral extends StringLiteralExpr {
HttpStringLiteral() {
exists(string s | this.getTextValue() = s |
// match HTTP URLs
s.regexpMatch("(?i)\"http://.*\"") and
// exclude private/local addresses:
// - IPv4: localhost / 127.0.0.1, 192.168.x.x, 10.x.x.x, 172.16.x.x -> 172.31.x.x
// - IPv6 (address inside []): ::1 (or 0:0:0:0:0:0:0:1), fc00::/7 (i.e. anything beginning `fcxx:` or `fdxx:`)
not s.regexpMatch("(?i)\"http://(localhost|127\\.0\\.0\\.1|192\\.168\\.[0-9]+\\.[0-9]+|10\\.[0-9]+\\.[0-9]+\\.[0-9]+|172\\.(1[6-9]|2[0-9]|3[01])\\.[0-9]+|\\[::1\\]|\\[0:0:0:0:0:0:0:1\\]|\\[f[cd][0-9a-f]{2}:.*\\]).*\"")
)
}
}
/**
* An HTTP string literal as a source.
*/
private class HttpStringLiteralAsSource extends Source {
HttpStringLiteralAsSource() { this.asExpr().getExpr() instanceof HttpStringLiteral }
}
/**
* A sink for use of HTTP URLs from model data.
*/
private class ModelsAsDataSink extends Sink {
ModelsAsDataSink() { sinkNode(this, "request-url") }
}
}

View File

@@ -0,0 +1,4 @@
---
category: newQuery
---
* Added a new query, `rust/non-https-url`, for detecting the use of non-HTTPS URLs that can be intercepted by third parties.

View File

@@ -0,0 +1,49 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Constructing URLs with the HTTP protocol can lead to insecure connections.</p>
<p>Furthermore, constructing URLs with the HTTP protocol can create problems if other parts of the
code expect HTTPS URLs. A typical pattern is to use libraries that expect secure connections,
which may fail or fall back to insecure behavior when provided with HTTP URLs instead of HTTPS URLs.</p>
</overview>
<recommendation>
<p>When you construct a URL for network requests, ensure that you use an HTTPS URL rather than an HTTP URL.
Then, any connections that are made using that URL are secure TLS connections.</p>
</recommendation>
<example>
<p>The following examples show two ways of making a network request using a URL. When the request is
made using an HTTP URL rather than an HTTPS URL, the connection is unsecured and can be intercepted
by attackers:</p>
<sample src="UseOfHttpBad.rs" />
<p>A better approach is to use HTTPS. When the request is made using an HTTPS URL, the connection
is a secure TLS connection:</p>
<sample src="UseOfHttpGood.rs" />
</example>
<references>
<li>
OWASP:
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html">Transport Layer Security Cheat Sheet</a>.
</li>
<li>
OWASP Top 10:
<a href="https://owasp.org/Top10/A08_2021-Software_and_Data_Integrity_Failures/">A08:2021 - Software and Data Integrity Failures</a>.
</li>
<li>Rust reqwest documentation:
<a href="https://docs.rs/reqwest/">reqwest crate</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,42 @@
/**
* @name Failure to use HTTPS URLs
* @description Non-HTTPS connections can be intercepted by third parties.
* @kind path-problem
* @problem.severity warning
* @security-severity 8.1
* @precision high
* @id rust/non-https-url
* @tags security
* external/cwe/cwe-319
* external/cwe/cwe-345
*/
import rust
import codeql.rust.dataflow.DataFlow
import codeql.rust.dataflow.TaintTracking
import codeql.rust.security.UseOfHttpExtensions
/**
* A taint configuration for HTTP URL strings that flow to URL-using sinks.
*/
module UseOfHttpConfig implements DataFlow::ConfigSig {
import UseOfHttp
predicate isSource(DataFlow::Node node) { node instanceof Source }
predicate isSink(DataFlow::Node node) { node instanceof Sink }
predicate isBarrier(DataFlow::Node barrier) { barrier instanceof Barrier }
predicate observeDiffInformedIncrementalMode() { any() }
}
module UseOfHttpFlow = TaintTracking::Global<UseOfHttpConfig>;
import UseOfHttpFlow::PathGraph
from UseOfHttpFlow::PathNode sourceNode, UseOfHttpFlow::PathNode sinkNode
where UseOfHttpFlow::flowPath(sourceNode, sinkNode)
select sinkNode.getNode(), sourceNode, sinkNode,
"This URL may be constructed with the HTTP protocol, from $@.", sourceNode.getNode(),
"this HTTP URL"

View File

@@ -0,0 +1,10 @@
// BAD: Using HTTP URL which can be intercepted
use reqwest;
fn main() {
let url = "http://example.com/sensitive-data";
// This makes an insecure HTTP request that can be intercepted
let response = reqwest::blocking::get(url).unwrap();
println!("Response: {}", response.text().unwrap());
}

View File

@@ -0,0 +1,10 @@
// GOOD: Using HTTPS URL which provides encryption
use reqwest;
fn main() {
let url = "https://example.com/sensitive-data";
// This makes a secure HTTPS request that is encrypted
let response = reqwest::blocking::get(url).unwrap();
println!("Response: {}", response.text().unwrap());
}

View File

@@ -27,6 +27,7 @@ private import codeql.rust.security.LogInjectionExtensions
private import codeql.rust.security.SqlInjectionExtensions
private import codeql.rust.security.TaintedPathExtensions
private import codeql.rust.security.UncontrolledAllocationSizeExtensions
private import codeql.rust.security.UseOfHttpExtensions
private import codeql.rust.security.WeakSensitiveDataHashingExtensions
private import codeql.rust.security.HardcodedCryptographicValueExtensions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
#select
| main.rs:12:22:12:43 | ...::get | main.rs:12:45:12:68 | "http://example.com/api" | main.rs:12:22:12:43 | ...::get | This URL may be constructed with the HTTP protocol, from $@. | main.rs:12:45:12:68 | "http://example.com/api" | this HTTP URL |
| main.rs:13:22:13:43 | ...::get | main.rs:13:45:13:68 | "HTTP://EXAMPLE.COM/API" | main.rs:13:22:13:43 | ...::get | This URL may be constructed with the HTTP protocol, from $@. | main.rs:13:45:13:68 | "HTTP://EXAMPLE.COM/API" | this HTTP URL |
| main.rs:14:22:14:43 | ...::get | main.rs:14:45:14:73 | "http://api.example.com/data" | main.rs:14:22:14:43 | ...::get | This URL may be constructed with the HTTP protocol, from $@. | main.rs:14:45:14:73 | "http://api.example.com/data" | this HTTP URL |
| main.rs:26:21:26:42 | ...::get | main.rs:23:20:23:39 | "http://example.com" | main.rs:26:21:26:42 | ...::get | This URL may be constructed with the HTTP protocol, from $@. | main.rs:23:20:23:39 | "http://example.com" | this HTTP URL |
| main.rs:37:30:37:51 | ...::get | main.rs:34:20:34:28 | "http://" | main.rs:37:30:37:51 | ...::get | This URL may be constructed with the HTTP protocol, from $@. | main.rs:34:20:34:28 | "http://" | this HTTP URL |
| main.rs:60:20:60:41 | ...::get | main.rs:60:43:60:65 | "http://172.32.0.0/baz" | main.rs:60:20:60:41 | ...::get | This URL may be constructed with the HTTP protocol, from $@. | main.rs:60:43:60:65 | "http://172.32.0.0/baz" | this HTTP URL |
| main.rs:71:24:71:45 | ...::get | main.rs:68:19:68:53 | "http://example.com/sensitive-... | main.rs:71:24:71:45 | ...::get | This URL may be constructed with the HTTP protocol, from $@. | main.rs:68:19:68:53 | "http://example.com/sensitive-... | this HTTP URL |
edges
| main.rs:12:45:12:68 | "http://example.com/api" | main.rs:12:22:12:43 | ...::get | provenance | MaD:1 Sink:MaD:1 |
| main.rs:13:45:13:68 | "HTTP://EXAMPLE.COM/API" | main.rs:13:22:13:43 | ...::get | provenance | MaD:1 Sink:MaD:1 |
| main.rs:14:45:14:73 | "http://api.example.com/data" | main.rs:14:22:14:43 | ...::get | provenance | MaD:1 Sink:MaD:1 |
| main.rs:23:9:23:16 | base_url | main.rs:25:28:25:53 | MacroExpr | provenance | |
| main.rs:23:20:23:39 | "http://example.com" | main.rs:23:9:23:16 | base_url | provenance | |
| main.rs:25:9:25:16 | full_url | main.rs:26:45:26:52 | full_url | provenance | |
| main.rs:25:20:25:26 | res | main.rs:25:28:25:53 | { ... } | provenance | |
| main.rs:25:28:25:53 | ...::format(...) | main.rs:25:20:25:26 | res | provenance | |
| main.rs:25:28:25:53 | ...::must_use(...) | main.rs:25:9:25:16 | full_url | provenance | |
| main.rs:25:28:25:53 | MacroExpr | main.rs:25:28:25:53 | ...::format(...) | provenance | MaD:2 |
| main.rs:25:28:25:53 | { ... } | main.rs:25:28:25:53 | ...::must_use(...) | provenance | MaD:3 |
| main.rs:26:44:26:52 | &full_url [&ref] | main.rs:26:21:26:42 | ...::get | provenance | MaD:1 Sink:MaD:1 |
| main.rs:26:45:26:52 | full_url | main.rs:26:44:26:52 | &full_url [&ref] | provenance | |
| main.rs:34:9:34:16 | protocol | main.rs:36:32:36:53 | MacroExpr | provenance | |
| main.rs:34:20:34:28 | "http://" | main.rs:34:9:34:16 | protocol | provenance | |
| main.rs:36:9:36:20 | insecure_url | main.rs:37:54:37:65 | insecure_url | provenance | |
| main.rs:36:24:36:30 | res | main.rs:36:32:36:53 | { ... } | provenance | |
| main.rs:36:32:36:53 | ...::format(...) | main.rs:36:24:36:30 | res | provenance | |
| main.rs:36:32:36:53 | ...::must_use(...) | main.rs:36:9:36:20 | insecure_url | provenance | |
| main.rs:36:32:36:53 | MacroExpr | main.rs:36:32:36:53 | ...::format(...) | provenance | MaD:2 |
| main.rs:36:32:36:53 | { ... } | main.rs:36:32:36:53 | ...::must_use(...) | provenance | MaD:3 |
| main.rs:37:53:37:65 | &insecure_url [&ref] | main.rs:37:30:37:51 | ...::get | provenance | MaD:1 Sink:MaD:1 |
| main.rs:37:54:37:65 | insecure_url | main.rs:37:53:37:65 | &insecure_url [&ref] | provenance | |
| main.rs:60:43:60:65 | "http://172.32.0.0/baz" | main.rs:60:20:60:41 | ...::get | provenance | MaD:1 Sink:MaD:1 |
| main.rs:68:13:68:15 | url | main.rs:71:47:71:49 | url | provenance | |
| main.rs:68:19:68:53 | "http://example.com/sensitive-... | main.rs:68:13:68:15 | url | provenance | |
| main.rs:71:47:71:49 | url | main.rs:71:24:71:45 | ...::get | provenance | MaD:1 Sink:MaD:1 |
models
| 1 | Sink: reqwest::blocking::get; Argument[0]; request-url |
| 2 | Summary: alloc::fmt::format; Argument[0]; ReturnValue; taint |
| 3 | Summary: core::hint::must_use; Argument[0]; ReturnValue; value |
nodes
| main.rs:12:22:12:43 | ...::get | semmle.label | ...::get |
| main.rs:12:45:12:68 | "http://example.com/api" | semmle.label | "http://example.com/api" |
| main.rs:13:22:13:43 | ...::get | semmle.label | ...::get |
| main.rs:13:45:13:68 | "HTTP://EXAMPLE.COM/API" | semmle.label | "HTTP://EXAMPLE.COM/API" |
| main.rs:14:22:14:43 | ...::get | semmle.label | ...::get |
| main.rs:14:45:14:73 | "http://api.example.com/data" | semmle.label | "http://api.example.com/data" |
| main.rs:23:9:23:16 | base_url | semmle.label | base_url |
| main.rs:23:20:23:39 | "http://example.com" | semmle.label | "http://example.com" |
| main.rs:25:9:25:16 | full_url | semmle.label | full_url |
| main.rs:25:20:25:26 | res | semmle.label | res |
| main.rs:25:28:25:53 | ...::format(...) | semmle.label | ...::format(...) |
| main.rs:25:28:25:53 | ...::must_use(...) | semmle.label | ...::must_use(...) |
| main.rs:25:28:25:53 | MacroExpr | semmle.label | MacroExpr |
| main.rs:25:28:25:53 | { ... } | semmle.label | { ... } |
| main.rs:26:21:26:42 | ...::get | semmle.label | ...::get |
| main.rs:26:44:26:52 | &full_url [&ref] | semmle.label | &full_url [&ref] |
| main.rs:26:45:26:52 | full_url | semmle.label | full_url |
| main.rs:34:9:34:16 | protocol | semmle.label | protocol |
| main.rs:34:20:34:28 | "http://" | semmle.label | "http://" |
| main.rs:36:9:36:20 | insecure_url | semmle.label | insecure_url |
| main.rs:36:24:36:30 | res | semmle.label | res |
| main.rs:36:32:36:53 | ...::format(...) | semmle.label | ...::format(...) |
| main.rs:36:32:36:53 | ...::must_use(...) | semmle.label | ...::must_use(...) |
| main.rs:36:32:36:53 | MacroExpr | semmle.label | MacroExpr |
| main.rs:36:32:36:53 | { ... } | semmle.label | { ... } |
| main.rs:37:30:37:51 | ...::get | semmle.label | ...::get |
| main.rs:37:53:37:65 | &insecure_url [&ref] | semmle.label | &insecure_url [&ref] |
| main.rs:37:54:37:65 | insecure_url | semmle.label | insecure_url |
| main.rs:60:20:60:41 | ...::get | semmle.label | ...::get |
| main.rs:60:43:60:65 | "http://172.32.0.0/baz" | semmle.label | "http://172.32.0.0/baz" |
| main.rs:68:13:68:15 | url | semmle.label | url |
| main.rs:68:19:68:53 | "http://example.com/sensitive-... | semmle.label | "http://example.com/sensitive-... |
| main.rs:71:24:71:45 | ...::get | semmle.label | ...::get |
| main.rs:71:47:71:49 | url | semmle.label | url |
subpaths

View File

@@ -0,0 +1,4 @@
query: queries/security/CWE-319/UseOfHttp.ql
postprocess:
- utils/test/PrettyPrintModels.ql
- utils/test/InlineExpectationsTestQuery.ql

View File

@@ -0,0 +1,83 @@
use reqwest;
use std::env;
fn main() {
test_direct_literals();
test_dynamic_urls();
test_localhost_exemptions();
}
fn test_direct_literals() {
// BAD: Direct HTTP URLs that should be flagged
let _response1 = reqwest::blocking::get("http://example.com/api").unwrap(); // $ Alert[rust/non-https-url]
let _response2 = reqwest::blocking::get("HTTP://EXAMPLE.COM/API").unwrap(); // $ Alert[rust/non-https-url]
let _response3 = reqwest::blocking::get("http://api.example.com/data").unwrap(); // $ Alert[rust/non-https-url]
// GOOD: HTTPS URLs that should not be flagged
let _response3 = reqwest::blocking::get("https://example.com/api").unwrap();
let _response4 = reqwest::blocking::get("https://api.example.com/data").unwrap();
}
fn test_dynamic_urls() {
// BAD: HTTP URLs constructed dynamically
let base_url = "http://example.com"; // $ Source
let endpoint = "/api/users";
let full_url = format!("{}{}", base_url, endpoint);
let _response = reqwest::blocking::get(&full_url).unwrap(); // $ Alert[rust/non-https-url]
// GOOD: HTTPS URLs constructed dynamically
let secure_base = "https://example.com";
let secure_full = format!("{}{}", secure_base, endpoint);
let _secure_response = reqwest::blocking::get(&secure_full).unwrap();
// BAD: HTTP protocol string
let protocol = "http://"; // $ Source
let host = "api.example.com";
let insecure_url = format!("{}{}", protocol, host);
let _insecure_response = reqwest::blocking::get(&insecure_url).unwrap(); // $ Alert[rust/non-https-url]
// GOOD: HTTPS protocol string
let secure_protocol = "https://";
let secure_url = format!("{}{}", secure_protocol, host);
let _secure_response2 = reqwest::blocking::get(&secure_url).unwrap();
}
fn test_localhost_exemptions() {
// GOOD: localhost URLs should not be flagged (local development)
let _local1 = reqwest::blocking::get("http://localhost:8080/api").unwrap();
let _local2 = reqwest::blocking::get("HTTP://LOCALHOST:8080/api").unwrap();
let _local3 = reqwest::blocking::get("http://127.0.0.1:3000/test").unwrap();
let _local4 = reqwest::blocking::get("http://192.168.1.100/internal").unwrap();
let _local5 = reqwest::blocking::get("http://10.0.0.1/admin").unwrap();
let _local6 = reqwest::blocking::get("http://172.16.0.0/foo").unwrap();
let _local7 = reqwest::blocking::get("http://172.31.255.255/bar").unwrap();
// GOOD: test IPv6 localhost variants
let _local8 = reqwest::blocking::get("http://[::1]:8080/api").unwrap();
let _local9 = reqwest::blocking::get("http://[0:0:0:0:0:0:0:1]/test").unwrap();
// BAD: non-private IP address
let _local10 = reqwest::blocking::get("http://172.32.0.0/baz").unwrap(); // $ Alert[rust/non-https-url]
}
// Additional test cases that mirror the Bad/Good examples
fn test_examples() {
// From UseOfHttpBad.rs - BAD case
{
let url = "http://example.com/sensitive-data"; // $ Source
// This makes an insecure HTTP request that can be intercepted
let response = reqwest::blocking::get(url).unwrap(); // $ Alert[rust/non-https-url]
println!("Response: {}", response.text().unwrap());
}
// From UseOfHttpGood.rs - GOOD case
{
let url = "https://example.com/sensitive-data";
// This makes a secure HTTPS request that is encrypted
let response = reqwest::blocking::get(url).unwrap();
println!("Response: {}", response.text().unwrap());
}
}

View File

@@ -0,0 +1,3 @@
qltest_cargo_check: true
qltest_dependencies:
- reqwest = { version = "0.12.9", features = ["blocking"] }