mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
Merge pull request #20221 from github/copilot/fix-20220
Rust: Implement a new query for Log Injection
This commit is contained in:
@@ -11,6 +11,7 @@ ql/rust/ql/src/queries/diagnostics/UnresolvedMacroCalls.ql
|
||||
ql/rust/ql/src/queries/security/CWE-020/RegexInjection.ql
|
||||
ql/rust/ql/src/queries/security/CWE-022/TaintedPath.ql
|
||||
ql/rust/ql/src/queries/security/CWE-089/SqlInjection.ql
|
||||
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
|
||||
|
||||
@@ -11,6 +11,7 @@ ql/rust/ql/src/queries/diagnostics/UnresolvedMacroCalls.ql
|
||||
ql/rust/ql/src/queries/security/CWE-020/RegexInjection.ql
|
||||
ql/rust/ql/src/queries/security/CWE-022/TaintedPath.ql
|
||||
ql/rust/ql/src/queries/security/CWE-089/SqlInjection.ql
|
||||
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
|
||||
|
||||
45
rust/ql/lib/codeql/rust/security/LogInjectionExtensions.qll
Normal file
45
rust/ql/lib/codeql/rust/security/LogInjectionExtensions.qll
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Provides classes and predicates for reasoning about log injection
|
||||
* vulnerabilities.
|
||||
*/
|
||||
|
||||
import rust
|
||||
private import codeql.rust.dataflow.DataFlow
|
||||
private import codeql.rust.dataflow.FlowSink
|
||||
private import codeql.rust.Concepts
|
||||
private import codeql.util.Unit
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and barriers for detecting log injection
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
module LogInjection {
|
||||
/**
|
||||
* A data flow source for log injection vulnerabilities.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for log injection vulnerabilities.
|
||||
*/
|
||||
abstract class Sink extends QuerySink::Range {
|
||||
override string getSinkType() { result = "LogInjection" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A barrier for log injection vulnerabilities.
|
||||
*/
|
||||
abstract class Barrier extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* An active threat-model source, considered as a flow source.
|
||||
*/
|
||||
private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { }
|
||||
|
||||
/**
|
||||
* A sink for log-injection from model data.
|
||||
*/
|
||||
private class ModelsAsDataSink extends Sink {
|
||||
ModelsAsDataSink() { sinkNode(this, "log-injection") }
|
||||
}
|
||||
}
|
||||
4
rust/ql/src/change-notes/2025-08-18-log-injection.md
Normal file
4
rust/ql/src/change-notes/2025-08-18-log-injection.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* Added a new query, `rust/log-injection`, for detecting cases where log entries could be forged by a malicious user.
|
||||
47
rust/ql/src/queries/security/CWE-117/LogInjection.qhelp
Normal file
47
rust/ql/src/queries/security/CWE-117/LogInjection.qhelp
Normal file
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
|
||||
<p>If unsanitized user input is written to a log entry, a malicious user may be able to forge new log entries.</p>
|
||||
|
||||
<p>Forgery can occur if a user provides some input with characters that are interpreted
|
||||
when the log output is displayed. If the log is displayed as a plain text file, then new
|
||||
line characters can be used by a malicious user. If the log is displayed as HTML, then
|
||||
arbitrary HTML may be included to spoof log entries.</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
User input should be suitably sanitized before it is logged.
|
||||
</p>
|
||||
<p>
|
||||
If the log entries are in plain text, then line breaks should be removed from user input using
|
||||
<code>String::replace</code> or similar. Care should also be taken that user input is clearly marked
|
||||
in log entries.
|
||||
</p>
|
||||
<p>
|
||||
For log entries that will be displayed in HTML, user input should be HTML-encoded before being logged, to prevent forgery and
|
||||
other forms of HTML injection.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>In the first example, a username, provided by the user via command line arguments, is logged using the <code>log</code> crate.
|
||||
If a malicious user provides <code>Guest\n[INFO] User: Admin\n</code> as a username parameter,
|
||||
the log entry will be split into multiple lines, where the second line will appear as <code>[INFO] User: Admin</code>,
|
||||
potentially forging a legitimate admin login entry.
|
||||
</p>
|
||||
<sample src="LogInjectionBad.rs" />
|
||||
|
||||
<p>In the second example, <code>String::replace</code> is used to ensure no line endings are present in the user input before logging.</p>
|
||||
<sample src="LogInjectionGood.rs" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>OWASP: <a href="https://owasp.org/www-community/attacks/Log_Injection">Log Injection</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
41
rust/ql/src/queries/security/CWE-117/LogInjection.ql
Normal file
41
rust/ql/src/queries/security/CWE-117/LogInjection.ql
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @name Log injection
|
||||
* @description Building log entries from user-controlled sources is vulnerable to
|
||||
* insertion of forged log entries by a malicious user.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 2.6
|
||||
* @precision medium
|
||||
* @id rust/log-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-117
|
||||
*/
|
||||
|
||||
import rust
|
||||
import codeql.rust.dataflow.DataFlow
|
||||
import codeql.rust.dataflow.TaintTracking
|
||||
import codeql.rust.security.LogInjectionExtensions
|
||||
|
||||
/**
|
||||
* A taint configuration for tainted data that reaches a log injection sink.
|
||||
*/
|
||||
module LogInjectionConfig implements DataFlow::ConfigSig {
|
||||
import LogInjection
|
||||
|
||||
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 LogInjectionFlow = TaintTracking::Global<LogInjectionConfig>;
|
||||
|
||||
import LogInjectionFlow::PathGraph
|
||||
|
||||
from LogInjectionFlow::PathNode sourceNode, LogInjectionFlow::PathNode sinkNode
|
||||
where LogInjectionFlow::flowPath(sourceNode, sinkNode)
|
||||
select sinkNode.getNode(), sourceNode, sinkNode, "Log entry depends on a $@.", sourceNode.getNode(),
|
||||
"user-provided value"
|
||||
13
rust/ql/src/queries/security/CWE-117/LogInjectionBad.rs
Normal file
13
rust/ql/src/queries/security/CWE-117/LogInjectionBad.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use std::env;
|
||||
use log::info;
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
// Get username from command line arguments
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let username = args.get(1).unwrap_or(&String::from("Guest")).clone();
|
||||
|
||||
// BAD: log message constructed with unsanitized user input
|
||||
info!("User login attempt: {}", username);
|
||||
}
|
||||
19
rust/ql/src/queries/security/CWE-117/LogInjectionGood.rs
Normal file
19
rust/ql/src/queries/security/CWE-117/LogInjectionGood.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use std::env;
|
||||
use log::info;
|
||||
|
||||
fn sanitize_for_logging(input: &str) -> String {
|
||||
// Remove newlines and carriage returns to prevent log injection
|
||||
input.replace('\n', "").replace('\r', "")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
// Get username from command line arguments
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let username = args.get(1).unwrap_or(&String::from("Guest")).clone();
|
||||
|
||||
// GOOD: log message constructed with sanitized user input
|
||||
let sanitized_username = sanitize_for_logging(username.as_str());
|
||||
info!("User login attempt: {}", sanitized_username);
|
||||
}
|
||||
@@ -22,6 +22,7 @@ private import codeql.rust.security.AccessInvalidPointerExtensions
|
||||
private import codeql.rust.security.CleartextLoggingExtensions
|
||||
private import codeql.rust.security.CleartextStorageDatabaseExtensions
|
||||
private import codeql.rust.security.CleartextTransmissionExtensions
|
||||
private import codeql.rust.security.LogInjectionExtensions
|
||||
private import codeql.rust.security.SqlInjectionExtensions
|
||||
private import codeql.rust.security.TaintedPathExtensions
|
||||
private import codeql.rust.security.UncontrolledAllocationSizeExtensions
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
multipleCallTargets
|
||||
| main.rs:9:43:9:63 | ...::from(...) |
|
||||
| main.rs:44:19:44:32 | username.len() |
|
||||
1646
rust/ql/test/query-tests/security/CWE-117/Cargo.lock
generated
Normal file
1646
rust/ql/test/query-tests/security/CWE-117/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
111
rust/ql/test/query-tests/security/CWE-117/LogInjection.expected
Normal file
111
rust/ql/test/query-tests/security/CWE-117/LogInjection.expected
Normal file
@@ -0,0 +1,111 @@
|
||||
#select
|
||||
| main.rs:16:5:16:45 | ...::log | main.rs:10:22:10:34 | ...::var | main.rs:16:5:16:45 | ...::log | Log entry depends on a $@. | main.rs:10:22:10:34 | ...::var | user-provided value |
|
||||
| main.rs:17:5:17:47 | ...::log | main.rs:11:23:11:44 | ...::get | main.rs:17:5:17:47 | ...::log | Log entry depends on a $@. | main.rs:11:23:11:44 | ...::get | user-provided value |
|
||||
| main.rs:19:5:19:40 | ...::log | main.rs:10:22:10:34 | ...::var | main.rs:19:5:19:40 | ...::log | Log entry depends on a $@. | main.rs:10:22:10:34 | ...::var | user-provided value |
|
||||
| main.rs:30:5:30:67 | ...::log | main.rs:11:23:11:44 | ...::get | main.rs:30:5:30:67 | ...::log | Log entry depends on a $@. | main.rs:11:23:11:44 | ...::get | user-provided value |
|
||||
| main.rs:108:9:108:36 | ...::log | main.rs:105:25:105:38 | ...::args | main.rs:108:9:108:36 | ...::log | Log entry depends on a $@. | main.rs:105:25:105:38 | ...::args | user-provided value |
|
||||
| main.rs:109:9:109:39 | ...::log | main.rs:105:25:105:38 | ...::args | main.rs:109:9:109:39 | ...::log | Log entry depends on a $@. | main.rs:105:25:105:38 | ...::args | user-provided value |
|
||||
| main.rs:110:9:110:38 | ...::log | main.rs:105:25:105:38 | ...::args | main.rs:110:9:110:38 | ...::log | Log entry depends on a $@. | main.rs:105:25:105:38 | ...::args | user-provided value |
|
||||
| main.rs:111:9:111:38 | ...::log | main.rs:105:25:105:38 | ...::args | main.rs:111:9:111:38 | ...::log | Log entry depends on a $@. | main.rs:105:25:105:38 | ...::args | user-provided value |
|
||||
| main.rs:112:9:112:38 | ...::log | main.rs:105:25:105:38 | ...::args | main.rs:112:9:112:38 | ...::log | Log entry depends on a $@. | main.rs:105:25:105:38 | ...::args | user-provided value |
|
||||
| main.rs:115:9:115:76 | ...::log | main.rs:105:25:105:38 | ...::args | main.rs:115:9:115:76 | ...::log | Log entry depends on a $@. | main.rs:105:25:105:38 | ...::args | user-provided value |
|
||||
| main.rs:122:9:122:39 | ...::_print | main.rs:119:25:119:37 | ...::var | main.rs:122:9:122:39 | ...::_print | Log entry depends on a $@. | main.rs:119:25:119:37 | ...::var | user-provided value |
|
||||
| main.rs:123:9:123:50 | ...::_eprint | main.rs:119:25:119:37 | ...::var | main.rs:123:9:123:50 | ...::_eprint | Log entry depends on a $@. | main.rs:119:25:119:37 | ...::var | user-provided value |
|
||||
edges
|
||||
| main.rs:10:9:10:18 | user_input | main.rs:16:11:16:44 | MacroExpr | provenance | |
|
||||
| main.rs:10:9:10:18 | user_input | main.rs:19:12:19:39 | MacroExpr | provenance | |
|
||||
| main.rs:10:22:10:34 | ...::var | main.rs:10:22:10:48 | ...::var(...) [Ok] | provenance | Src:MaD:6 |
|
||||
| main.rs:10:22:10:48 | ...::var(...) [Ok] | main.rs:10:22:10:81 | ... .unwrap_or(...) | provenance | MaD:10 |
|
||||
| main.rs:10:22:10:81 | ... .unwrap_or(...) | main.rs:10:9:10:18 | user_input | provenance | |
|
||||
| main.rs:11:9:11:19 | remote_data | main.rs:17:12:17:46 | MacroExpr | provenance | |
|
||||
| main.rs:11:9:11:19 | remote_data | main.rs:30:11:30:66 | MacroExpr | provenance | |
|
||||
| main.rs:11:23:11:44 | ...::get | main.rs:11:23:11:71 | ...::get(...) [Ok] | provenance | Src:MaD:4 |
|
||||
| main.rs:11:23:11:71 | ...::get(...) [Ok] | main.rs:11:23:12:17 | ... .unwrap() | provenance | MaD:9 |
|
||||
| main.rs:11:23:12:17 | ... .unwrap() | main.rs:11:23:12:24 | ... .text() [Ok] | provenance | MaD:12 |
|
||||
| main.rs:11:23:12:24 | ... .text() [Ok] | main.rs:11:23:12:61 | ... .unwrap_or(...) | provenance | MaD:10 |
|
||||
| main.rs:11:23:12:61 | ... .unwrap_or(...) | main.rs:11:9:11:19 | remote_data | provenance | |
|
||||
| main.rs:16:11:16:44 | MacroExpr | main.rs:16:5:16:45 | ...::log | provenance | MaD:1 Sink:MaD:1 |
|
||||
| main.rs:17:12:17:46 | MacroExpr | main.rs:17:5:17:47 | ...::log | provenance | MaD:1 Sink:MaD:1 |
|
||||
| main.rs:19:12:19:39 | MacroExpr | main.rs:19:5:19:40 | ...::log | provenance | MaD:1 Sink:MaD:1 |
|
||||
| main.rs:30:11:30:66 | MacroExpr | main.rs:30:5:30:67 | ...::log | provenance | MaD:1 Sink:MaD:1 |
|
||||
| main.rs:105:13:105:21 | user_data | main.rs:108:15:108:35 | MacroExpr | provenance | |
|
||||
| main.rs:105:13:105:21 | user_data | main.rs:109:15:109:38 | MacroExpr | provenance | |
|
||||
| main.rs:105:13:105:21 | user_data | main.rs:110:16:110:37 | MacroExpr | provenance | |
|
||||
| main.rs:105:13:105:21 | user_data | main.rs:111:16:111:37 | MacroExpr | provenance | |
|
||||
| main.rs:105:13:105:21 | user_data | main.rs:112:16:112:37 | MacroExpr | provenance | |
|
||||
| main.rs:105:13:105:21 | user_data | main.rs:115:15:115:75 | MacroExpr | provenance | |
|
||||
| main.rs:105:25:105:38 | ...::args | main.rs:105:25:105:40 | ...::args(...) [element] | provenance | Src:MaD:5 |
|
||||
| main.rs:105:25:105:40 | ...::args(...) [element] | main.rs:105:25:105:47 | ... .nth(...) [Some] | provenance | MaD:7 |
|
||||
| main.rs:105:25:105:47 | ... .nth(...) [Some] | main.rs:105:25:105:67 | ... .unwrap_or_default() | provenance | MaD:8 |
|
||||
| main.rs:105:25:105:67 | ... .unwrap_or_default() | main.rs:105:13:105:21 | user_data | provenance | |
|
||||
| main.rs:108:15:108:35 | MacroExpr | main.rs:108:9:108:36 | ...::log | provenance | MaD:1 Sink:MaD:1 |
|
||||
| main.rs:109:15:109:38 | MacroExpr | main.rs:109:9:109:39 | ...::log | provenance | MaD:1 Sink:MaD:1 |
|
||||
| main.rs:110:16:110:37 | MacroExpr | main.rs:110:9:110:38 | ...::log | provenance | MaD:1 Sink:MaD:1 |
|
||||
| main.rs:111:16:111:37 | MacroExpr | main.rs:111:9:111:38 | ...::log | provenance | MaD:1 Sink:MaD:1 |
|
||||
| main.rs:112:16:112:37 | MacroExpr | main.rs:112:9:112:38 | ...::log | provenance | MaD:1 Sink:MaD:1 |
|
||||
| main.rs:115:15:115:75 | MacroExpr | main.rs:115:9:115:76 | ...::log | provenance | MaD:1 Sink:MaD:1 |
|
||||
| main.rs:119:13:119:21 | user_data | main.rs:122:18:122:38 | MacroExpr | provenance | |
|
||||
| main.rs:119:13:119:21 | user_data | main.rs:123:19:123:49 | MacroExpr | provenance | |
|
||||
| main.rs:119:25:119:37 | ...::var | main.rs:119:25:119:45 | ...::var(...) [Ok] | provenance | Src:MaD:6 |
|
||||
| main.rs:119:25:119:45 | ...::var(...) [Ok] | main.rs:119:25:119:65 | ... .unwrap_or_default() | provenance | MaD:11 |
|
||||
| main.rs:119:25:119:65 | ... .unwrap_or_default() | main.rs:119:13:119:21 | user_data | provenance | |
|
||||
| main.rs:122:18:122:38 | MacroExpr | main.rs:122:9:122:39 | ...::_print | provenance | MaD:3 Sink:MaD:3 |
|
||||
| main.rs:123:19:123:49 | MacroExpr | main.rs:123:9:123:50 | ...::_eprint | provenance | MaD:2 Sink:MaD:2 |
|
||||
models
|
||||
| 1 | Sink: log::__private_api::log; Argument[0]; log-injection |
|
||||
| 2 | Sink: std::io::stdio::_eprint; Argument[0]; log-injection |
|
||||
| 3 | Sink: std::io::stdio::_print; Argument[0]; log-injection |
|
||||
| 4 | Source: reqwest::blocking::get; ReturnValue.Field[core::result::Result::Ok(0)]; remote |
|
||||
| 5 | Source: std::env::args; ReturnValue.Element; commandargs |
|
||||
| 6 | Source: std::env::var; ReturnValue.Field[core::result::Result::Ok(0)]; environment |
|
||||
| 7 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Element; ReturnValue.Field[core::option::Option::Some(0)]; value |
|
||||
| 8 | Summary: <core::option::Option>::unwrap_or_default; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value |
|
||||
| 9 | Summary: <core::result::Result>::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value |
|
||||
| 10 | Summary: <core::result::Result>::unwrap_or; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value |
|
||||
| 11 | Summary: <core::result::Result>::unwrap_or_default; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value |
|
||||
| 12 | Summary: <reqwest::blocking::response::Response>::text; Argument[self]; ReturnValue.Field[core::result::Result::Ok(0)]; taint |
|
||||
nodes
|
||||
| main.rs:10:9:10:18 | user_input | semmle.label | user_input |
|
||||
| main.rs:10:22:10:34 | ...::var | semmle.label | ...::var |
|
||||
| main.rs:10:22:10:48 | ...::var(...) [Ok] | semmle.label | ...::var(...) [Ok] |
|
||||
| main.rs:10:22:10:81 | ... .unwrap_or(...) | semmle.label | ... .unwrap_or(...) |
|
||||
| main.rs:11:9:11:19 | remote_data | semmle.label | remote_data |
|
||||
| main.rs:11:23:11:44 | ...::get | semmle.label | ...::get |
|
||||
| main.rs:11:23:11:71 | ...::get(...) [Ok] | semmle.label | ...::get(...) [Ok] |
|
||||
| main.rs:11:23:12:17 | ... .unwrap() | semmle.label | ... .unwrap() |
|
||||
| main.rs:11:23:12:24 | ... .text() [Ok] | semmle.label | ... .text() [Ok] |
|
||||
| main.rs:11:23:12:61 | ... .unwrap_or(...) | semmle.label | ... .unwrap_or(...) |
|
||||
| main.rs:16:5:16:45 | ...::log | semmle.label | ...::log |
|
||||
| main.rs:16:11:16:44 | MacroExpr | semmle.label | MacroExpr |
|
||||
| main.rs:17:5:17:47 | ...::log | semmle.label | ...::log |
|
||||
| main.rs:17:12:17:46 | MacroExpr | semmle.label | MacroExpr |
|
||||
| main.rs:19:5:19:40 | ...::log | semmle.label | ...::log |
|
||||
| main.rs:19:12:19:39 | MacroExpr | semmle.label | MacroExpr |
|
||||
| main.rs:30:5:30:67 | ...::log | semmle.label | ...::log |
|
||||
| main.rs:30:11:30:66 | MacroExpr | semmle.label | MacroExpr |
|
||||
| main.rs:105:13:105:21 | user_data | semmle.label | user_data |
|
||||
| main.rs:105:25:105:38 | ...::args | semmle.label | ...::args |
|
||||
| main.rs:105:25:105:40 | ...::args(...) [element] | semmle.label | ...::args(...) [element] |
|
||||
| main.rs:105:25:105:47 | ... .nth(...) [Some] | semmle.label | ... .nth(...) [Some] |
|
||||
| main.rs:105:25:105:67 | ... .unwrap_or_default() | semmle.label | ... .unwrap_or_default() |
|
||||
| main.rs:108:9:108:36 | ...::log | semmle.label | ...::log |
|
||||
| main.rs:108:15:108:35 | MacroExpr | semmle.label | MacroExpr |
|
||||
| main.rs:109:9:109:39 | ...::log | semmle.label | ...::log |
|
||||
| main.rs:109:15:109:38 | MacroExpr | semmle.label | MacroExpr |
|
||||
| main.rs:110:9:110:38 | ...::log | semmle.label | ...::log |
|
||||
| main.rs:110:16:110:37 | MacroExpr | semmle.label | MacroExpr |
|
||||
| main.rs:111:9:111:38 | ...::log | semmle.label | ...::log |
|
||||
| main.rs:111:16:111:37 | MacroExpr | semmle.label | MacroExpr |
|
||||
| main.rs:112:9:112:38 | ...::log | semmle.label | ...::log |
|
||||
| main.rs:112:16:112:37 | MacroExpr | semmle.label | MacroExpr |
|
||||
| main.rs:115:9:115:76 | ...::log | semmle.label | ...::log |
|
||||
| main.rs:115:15:115:75 | MacroExpr | semmle.label | MacroExpr |
|
||||
| main.rs:119:13:119:21 | user_data | semmle.label | user_data |
|
||||
| main.rs:119:25:119:37 | ...::var | semmle.label | ...::var |
|
||||
| main.rs:119:25:119:45 | ...::var(...) [Ok] | semmle.label | ...::var(...) [Ok] |
|
||||
| main.rs:119:25:119:65 | ... .unwrap_or_default() | semmle.label | ... .unwrap_or_default() |
|
||||
| main.rs:122:9:122:39 | ...::_print | semmle.label | ...::_print |
|
||||
| main.rs:122:18:122:38 | MacroExpr | semmle.label | MacroExpr |
|
||||
| main.rs:123:9:123:50 | ...::_eprint | semmle.label | ...::_eprint |
|
||||
| main.rs:123:19:123:49 | MacroExpr | semmle.label | MacroExpr |
|
||||
subpaths
|
||||
@@ -0,0 +1,4 @@
|
||||
query: queries/security/CWE-117/LogInjection.ql
|
||||
postprocess:
|
||||
- utils/test/PrettyPrintModels.ql
|
||||
- utils/test/InlineExpectationsTestQuery.ql
|
||||
125
rust/ql/test/query-tests/security/CWE-117/main.rs
Normal file
125
rust/ql/test/query-tests/security/CWE-117/main.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use std::env;
|
||||
use log::{info, warn, error, debug, trace};
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
// Sources of user input
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let username = args.get(1).unwrap_or(&String::from("Guest")).clone(); // $ MISSING: Source=commandargs
|
||||
let user_input = std::env::var("USER_INPUT").unwrap_or("default".to_string()); // $ Source=environment
|
||||
let remote_data = reqwest::blocking::get("http://example.com/user") // $ Source=remote
|
||||
.unwrap().text().unwrap_or("remote_user".to_string());
|
||||
|
||||
// BAD: Direct logging of user input
|
||||
info!("User login: {}", username); // $ MISSING: Alert[rust/log-injection]
|
||||
warn!("Warning for user: {}", user_input); // $ Alert[rust/log-injection]=environment
|
||||
error!("Error processing: {}", remote_data); // $ Alert[rust/log-injection]=remote
|
||||
debug!("Debug info: {}", username); // $ MISSING: Alert[rust/log-injection]
|
||||
trace!("Trace data: {}", user_input); // $ Alert[rust/log-injection]=environment
|
||||
|
||||
// BAD: Formatted strings with user input
|
||||
let formatted_msg = format!("Processing user: {}", username);
|
||||
info!("{}", formatted_msg); // $ MISSING: Alert[rust/log-injection]
|
||||
|
||||
// BAD: String concatenation with user input
|
||||
let concat_msg = "User activity: ".to_string() + &username;
|
||||
info!("{}", concat_msg); // $ MISSING: Alert[rust/log-injection]
|
||||
|
||||
// BAD: Complex formatting
|
||||
info!("User {} accessed resource at {}", username, remote_data); // $ Alert[rust/log-injection]=remote
|
||||
|
||||
// GOOD: Sanitized input
|
||||
let sanitized_username = username.replace('\n', "").replace('\r', "");
|
||||
info!("Sanitized user login: {}", sanitized_username);
|
||||
|
||||
// GOOD: Constant strings
|
||||
info!("System startup complete");
|
||||
|
||||
// GOOD: Non-user-controlled data
|
||||
let system_time = std::time::SystemTime::now();
|
||||
info!("Current time: {:?}", system_time);
|
||||
|
||||
// GOOD: Numeric data derived from user input (not directly logged)
|
||||
let user_id = username.len();
|
||||
info!("User ID length: {}", user_id);
|
||||
|
||||
// More complex test cases
|
||||
test_complex_scenarios(&username, &user_input);
|
||||
test_indirect_flows(&remote_data);
|
||||
}
|
||||
|
||||
fn test_complex_scenarios(username: &str, user_input: &str) {
|
||||
// BAD: Indirect logging through variables
|
||||
let log_message = format!("Activity for {}", username);
|
||||
info!("{}", log_message); // $ MISSING: Alert[rust/log-injection]
|
||||
|
||||
// BAD: Through function parameters
|
||||
log_user_activity(username); // Function call - should be tracked
|
||||
|
||||
// BAD: Through struct fields
|
||||
let user_info = UserInfo { name: username.to_string() };
|
||||
info!("User info: {}", user_info.name); // $ MISSING: Alert[rust/log-injection]
|
||||
|
||||
// GOOD: After sanitization
|
||||
let clean_input = sanitize_input(user_input);
|
||||
info!("Clean input: {}", clean_input);
|
||||
}
|
||||
|
||||
fn log_user_activity(user: &str) {
|
||||
info!("User activity: {}", user); // $ MISSING: Alert[rust/log-injection]
|
||||
}
|
||||
|
||||
fn sanitize_input(input: &str) -> String {
|
||||
input.replace('\n', "").replace('\r', "").replace('\t', " ")
|
||||
}
|
||||
|
||||
struct UserInfo {
|
||||
name: String,
|
||||
}
|
||||
|
||||
fn test_indirect_flows(data: &str) {
|
||||
// BAD: Flow through intermediate variables
|
||||
let temp_var = data;
|
||||
let another_var = temp_var;
|
||||
info!("Indirect flow: {}", another_var); // $ MISSING: Alert[rust/log-injection]
|
||||
|
||||
// BAD: Flow through collections
|
||||
let data_vec = vec![data];
|
||||
if let Some(item) = data_vec.first() {
|
||||
info!("Vector item: {}", item); // $ MISSING: Alert[rust/log-injection]
|
||||
}
|
||||
|
||||
// BAD: Flow through Option/Result
|
||||
let optional_data = Some(data);
|
||||
if let Some(unwrapped) = optional_data {
|
||||
info!("Unwrapped data: {}", unwrapped); // $ MISSING: Alert[rust/log-injection]
|
||||
}
|
||||
}
|
||||
|
||||
// Additional test patterns for different logging scenarios
|
||||
mod additional_tests {
|
||||
use log::*;
|
||||
|
||||
pub fn test_macro_variations() {
|
||||
let user_data = std::env::args().nth(1).unwrap_or_default(); // $ Source=commandargs
|
||||
|
||||
// BAD: Different log macro variations
|
||||
info!("Info: {}", user_data); // $ Alert[rust/log-injection]=commandargs
|
||||
warn!("Warning: {}", user_data); // $ Alert[rust/log-injection]=commandargs
|
||||
error!("Error: {}", user_data); // $ Alert[rust/log-injection]=commandargs
|
||||
debug!("Debug: {}", user_data); // $ Alert[rust/log-injection]=commandargs
|
||||
trace!("Trace: {}", user_data); // $ Alert[rust/log-injection]=commandargs
|
||||
|
||||
// BAD: Complex format strings
|
||||
info!("User {} did action {} at time {}", user_data, "login", "now"); // $ Alert[rust/log-injection]=commandargs
|
||||
}
|
||||
|
||||
pub fn test_println_patterns() {
|
||||
let user_data = std::env::var("USER").unwrap_or_default(); // $ Source=environment
|
||||
|
||||
// These might not be caught depending on model coverage, but are potential logging sinks
|
||||
println!("User: {}", user_data); // $ Alert[rust/log-injection]=environment
|
||||
eprintln!("Error for user: {}", user_data); // $ Alert[rust/log-injection]=environment
|
||||
}
|
||||
}
|
||||
5
rust/ql/test/query-tests/security/CWE-117/options.yml
Normal file
5
rust/ql/test/query-tests/security/CWE-117/options.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
qltest_cargo_check: true
|
||||
qltest_dependencies:
|
||||
- log = "0.4"
|
||||
- env_logger = "0.10"
|
||||
- reqwest = { version = "0.12.9", features = ["blocking"] }
|
||||
Reference in New Issue
Block a user