Precision pass on auth and resource analysis (#63)

This commit is contained in:
Eli Peter 2026-05-03 13:51:46 -04:00 committed by GitHub
parent 064801a3a4
commit c7c5e0f3a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 4248 additions and 138 deletions

View file

@ -68,7 +68,23 @@ pub static RULES: &[LabelRule] = &[
case_sensitive: false,
},
LabelRule {
matchers: &["db.Query", "db.Exec", "db.QueryRow", "db.Prepare"],
matchers: &[
"db.Query",
"db.Exec",
"db.QueryRow",
"db.Prepare",
// goqu raw SQL literal builders: `goqu.L(s)` and the alias
// `goqu.Lit(s)` insert `s` verbatim into the generated SQL with no
// parameterisation. CVE-2026-41422 (daptin) loops a user-controlled
// `c.QueryArray("column")` value into `goqu.L(project)` to allow
// arbitrary SELECT subqueries. Modelled by name — `goqu.L` is the
// documented escape hatch for raw SQL. The safe siblings
// `goqu.I` (identifier), `goqu.C` (column), `goqu.T` (table),
// `goqu.V` (parameterised value), and the typed function
// constructors (`goqu.COUNT`, `goqu.SUM`, …) are not sinks.
"goqu.L",
"goqu.Lit",
],
label: DataLabel::Sink(Cap::SQL_QUERY),
case_sensitive: false,
},
@ -538,6 +554,16 @@ pub fn framework_rules(ctx: &FrameworkContext) -> Vec<RuntimeLabelRule> {
"c.Cookie".into(),
"c.BindJSON".into(),
"c.ShouldBindJSON".into(),
// Array-returning sibling helpers. `c.QueryArray("k")` returns
// every value of repeated query param `k`; `c.PostFormArray`
// and `c.GetQueryArray` / `c.GetPostFormArray` are the
// documented `[]string` counterparts of the scalar methods
// above. CVE-2026-41422 (daptin) reads `c.QueryArray("column")`
// and loops directly into a SQL_QUERY sink.
"c.QueryArray".into(),
"c.GetQueryArray".into(),
"c.PostFormArray".into(),
"c.GetPostFormArray".into(),
],
label: DataLabel::Source(Cap::all()),
case_sensitive: false,

View file

@ -103,6 +103,21 @@ pub static RULES: &[LabelRule] = &[
label: DataLabel::Sink(Cap::SQL_QUERY),
case_sensitive: false,
},
// JDBC `Statement.execute(String)` / `executeBatch` / `executeLargeUpdate`.
// Bare `execute` over-fires (Runnable.run callbacks, Executor.execute,
// HttpClient.execute), so these only fire via type-qualified resolution
// when the receiver's TypeKind is DatabaseConnection (the kind both
// `Connection` and `Statement` map to in `class_name_to_type_kind`).
// Surfaced by GHSA-h8cj-hpmg-636v (Appsmith FilterDataServiceCE.dropTable).
LabelRule {
matchers: &[
"DatabaseConnection.execute",
"DatabaseConnection.executeBatch",
"DatabaseConnection.executeLargeUpdate",
],
label: DataLabel::Sink(Cap::SQL_QUERY),
case_sensitive: true,
},
LabelRule {
matchers: &["Class.forName"],
label: DataLabel::Sink(Cap::CODE_EXEC),

View file

@ -1626,6 +1626,30 @@ mod tests {
assert_eq!(result, Some(DataLabel::Sink(Cap::FILE_IO)));
}
// CVE Hunt Session 6 (Go CVE-2026-41422 daptin SQL injection): goqu's
// raw SQL literal builders `goqu.L(s)` / `goqu.Lit(s)` insert `s`
// verbatim into the generated query. Modeled by name as SQL_QUERY
// sinks; the safe siblings `goqu.I` (identifier), `goqu.C`, `goqu.T`,
// `goqu.V`, `goqu.SUM`, `goqu.COUNT`, etc. are typed and stay
// unlabeled.
#[test]
fn classify_go_goqu_l_is_sql_query_sink() {
let result = classify("go", "goqu.L", None);
assert_eq!(result, Some(DataLabel::Sink(Cap::SQL_QUERY)));
}
#[test]
fn classify_go_goqu_lit_is_sql_query_sink() {
let result = classify("go", "goqu.Lit", None);
assert_eq!(result, Some(DataLabel::Sink(Cap::SQL_QUERY)));
}
#[test]
fn classify_go_goqu_i_is_not_sink() {
let result = classify("go", "goqu.I", None);
assert_eq!(result, None);
}
// CVE Hunt Session 2 (Go CVE-2023-3188 Owncast SSRF):
// `http.DefaultClient.Get/Post/Head/Do/PostForm` is the idiomatic Go
// SSRF sink shape (`http.DefaultClient` is the package-level shared