Merge pull request #16697 from egregius313/egregius313/go/dataflow/threat-modeling

Go: Introduce Threat Modeling
This commit is contained in:
Edward Minnix III
2024-06-18 12:25:33 -04:00
committed by GitHub
26 changed files with 395 additions and 1 deletions

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added the `ThreatModelFlowSource` class to `FlowSources.qll`. The `ThreatModelFlowSource` class can be used to include sources which match the current *threat model* configuration. This is the first step in supporting threat modeling for Go.

View File

@@ -8,6 +8,7 @@ upgrades: upgrades
dependencies:
codeql/dataflow: ${workspace}
codeql/mad: ${workspace}
codeql/threat-models: ${workspace}
codeql/tutorial: ${workspace}
codeql/util: ${workspace}
dataExtensions:

View File

@@ -4,6 +4,7 @@
import go
private import semmle.go.dataflow.ExternalFlow as ExternalFlow
private import codeql.threatmodels.ThreatModels
/**
* DEPRECATED: Use `RemoteFlowSource` instead.
@@ -31,7 +32,9 @@ module RemoteFlowSource {
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `RemoteFlowSource` instead.
*/
abstract class Range extends DataFlow::Node { }
abstract class Range extends SourceNode {
override string getThreatModel() { result = "remote" }
}
/**
* A source of data that is controlled by an untrusted user.
@@ -40,3 +43,27 @@ module RemoteFlowSource {
MaDRemoteSource() { ExternalFlow::sourceNode(this, "remote") }
}
}
/**
* A data flow source.
*/
abstract class SourceNode extends DataFlow::Node {
/**
* Gets a string that represents the source kind with respect to threat modeling.
*/
abstract string getThreatModel();
}
/**
* A class of data flow sources that respects the
* current threat model configuration.
*/
class ThreatModelFlowSource extends DataFlow::Node {
ThreatModelFlowSource() {
exists(string kind |
// Specific threat model.
currentThreatModel(kind) and
(this.(SourceNode).getThreatModel() = kind or ExternalFlow::sourceNode(this, kind))
)
}
}

View File

@@ -0,0 +1,14 @@
private import go
private import semmle.go.security.FlowSources
private import semmle.go.dataflow.ExternalFlow
private import semmle.go.dataflow.DataFlow
private module ThreatModelConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof ThreatModelFlowSource }
predicate isSink(DataFlow::Node sink) {
sink = any(DataFlow::CallNode c | c.getTarget().getName() = "sink").getAnArgument()
}
}
module ThreatModelFlow = TaintTracking::Global<ThreatModelConfig>;

View File

@@ -0,0 +1,5 @@
module semmle.go.dataflow.ThreatModels
go 1.22.3
require github.com/nonexistent/sources v0.0.0-20240601000000-0000000000000

View File

@@ -0,0 +1,38 @@
package main
import (
"github.com/nonexistent/sources"
"net/http"
)
func Environment() {
home := sources.ReadEnvironment("HOME")
sink("SELECT * FROM " + home)
}
func Cli() {
arg := sources.GetCliArg("arg")
sink("SELECT * FROM " + arg)
}
func Custom() {
query := sources.GetCustom("query")
sink("SELECT * FROM " + query)
}
func StoredSqlInjection() {
query := sources.ExecuteQuery("SELECT * FROM queries LIMIT 1")
sink(query)
}
func Handler(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("query")
sink("SELECT * FROM " + query)
}
func sink(s string) {
}

View File

@@ -0,0 +1,12 @@
edges
| test.go:32:11:32:15 | selection of URL | test.go:32:11:32:23 | call to Query | provenance | MaD:738 |
| test.go:32:11:32:23 | call to Query | test.go:32:11:32:36 | call to Get | provenance | MaD:745 |
| test.go:32:11:32:36 | call to Get | test.go:34:7:34:30 | ...+... | provenance | |
nodes
| test.go:32:11:32:15 | selection of URL | semmle.label | selection of URL |
| test.go:32:11:32:23 | call to Query | semmle.label | call to Query |
| test.go:32:11:32:36 | call to Get | semmle.label | call to Get |
| test.go:34:7:34:30 | ...+... | semmle.label | ...+... |
subpaths
#select
| test.go:32:11:32:15 | selection of URL | test.go:34:7:34:30 | ...+... |

View File

@@ -0,0 +1,15 @@
extensions:
- addsTo:
pack: codeql/threat-models
extensible: threatModelConfiguration
data: []
- addsTo:
pack: codeql/go-all
extensible: sourceModel
data:
- ["github.com/nonexistent/sources", "", False, "ExecuteQuery", "", "", "ReturnValue", "database", "manual"]
- ["github.com/nonexistent/sources", "", False, "ReadEnvironment", "", "", "ReturnValue", "environment", "manual"]
- ["github.com/nonexistent/sources", "", False, "GetCustom", "", "", "ReturnValue", "custom", "manual"]
- ["github.com/nonexistent/sources", "", False, "GetCliArg", "", "", "ReturnValue", "commandargs", "manual"]

View File

@@ -0,0 +1,10 @@
/**
* This is a dataflow test using the "default" threat model.
*/
import Test
import ThreatModelFlow::PathGraph
from ThreatModelFlow::PathNode source, ThreatModelFlow::PathNode sink
where ThreatModelFlow::flowPath(source, sink)
select source, sink

View File

@@ -0,0 +1,16 @@
edges
| test.go:27:11:27:63 | call to ExecuteQuery | test.go:28:7:28:11 | query | provenance | Src:MaD:1 |
| test.go:32:11:32:15 | selection of URL | test.go:32:11:32:23 | call to Query | provenance | MaD:738 |
| test.go:32:11:32:23 | call to Query | test.go:32:11:32:36 | call to Get | provenance | MaD:745 |
| test.go:32:11:32:36 | call to Get | test.go:34:7:34:30 | ...+... | provenance | |
nodes
| test.go:27:11:27:63 | call to ExecuteQuery | semmle.label | call to ExecuteQuery |
| test.go:28:7:28:11 | query | semmle.label | query |
| test.go:32:11:32:15 | selection of URL | semmle.label | selection of URL |
| test.go:32:11:32:23 | call to Query | semmle.label | call to Query |
| test.go:32:11:32:36 | call to Get | semmle.label | call to Get |
| test.go:34:7:34:30 | ...+... | semmle.label | ...+... |
subpaths
#select
| test.go:27:11:27:63 | call to ExecuteQuery | test.go:28:7:28:11 | query |
| test.go:32:11:32:15 | selection of URL | test.go:34:7:34:30 | ...+... |

View File

@@ -0,0 +1,16 @@
extensions:
- addsTo:
pack: codeql/threat-models
extensible: threatModelConfiguration
data:
- ["database", true, 0]
- addsTo:
pack: codeql/go-all
extensible: sourceModel
data:
- ["github.com/nonexistent/sources", "", False, "ExecuteQuery", "", "", "ReturnValue", "database", "manual"]
- ["github.com/nonexistent/sources", "", False, "ReadEnvironment", "", "", "ReturnValue", "environment", "manual"]
- ["github.com/nonexistent/sources", "", False, "GetCustom", "", "", "ReturnValue", "custom", "manual"]
- ["github.com/nonexistent/sources", "", False, "GetCliArg", "", "", "ReturnValue", "commandargs", "manual"]

View File

@@ -0,0 +1,11 @@
/**
* This is a dataflow test using the "default" threat model with the
* addition of "database".
*/
import Test
import ThreatModelFlow::PathGraph
from ThreatModelFlow::PathNode source, ThreatModelFlow::PathNode sink
where ThreatModelFlow::flowPath(source, sink)
select source, sink

View File

@@ -0,0 +1,24 @@
edges
| test.go:9:10:9:40 | call to ReadEnvironment | test.go:11:7:11:29 | ...+... | provenance | Src:MaD:2 |
| test.go:15:9:15:32 | call to GetCliArg | test.go:17:7:17:28 | ...+... | provenance | Src:MaD:4 |
| test.go:27:11:27:63 | call to ExecuteQuery | test.go:28:7:28:11 | query | provenance | Src:MaD:1 |
| test.go:32:11:32:15 | selection of URL | test.go:32:11:32:23 | call to Query | provenance | MaD:738 |
| test.go:32:11:32:23 | call to Query | test.go:32:11:32:36 | call to Get | provenance | MaD:745 |
| test.go:32:11:32:36 | call to Get | test.go:34:7:34:30 | ...+... | provenance | |
nodes
| test.go:9:10:9:40 | call to ReadEnvironment | semmle.label | call to ReadEnvironment |
| test.go:11:7:11:29 | ...+... | semmle.label | ...+... |
| test.go:15:9:15:32 | call to GetCliArg | semmle.label | call to GetCliArg |
| test.go:17:7:17:28 | ...+... | semmle.label | ...+... |
| test.go:27:11:27:63 | call to ExecuteQuery | semmle.label | call to ExecuteQuery |
| test.go:28:7:28:11 | query | semmle.label | query |
| test.go:32:11:32:15 | selection of URL | semmle.label | selection of URL |
| test.go:32:11:32:23 | call to Query | semmle.label | call to Query |
| test.go:32:11:32:36 | call to Get | semmle.label | call to Get |
| test.go:34:7:34:30 | ...+... | semmle.label | ...+... |
subpaths
#select
| test.go:9:10:9:40 | call to ReadEnvironment | test.go:11:7:11:29 | ...+... |
| test.go:15:9:15:32 | call to GetCliArg | test.go:17:7:17:28 | ...+... |
| test.go:27:11:27:63 | call to ExecuteQuery | test.go:28:7:28:11 | query |
| test.go:32:11:32:15 | selection of URL | test.go:34:7:34:30 | ...+... |

View File

@@ -0,0 +1,17 @@
extensions:
- addsTo:
pack: codeql/threat-models
extensible: threatModelConfiguration
data:
- ["local", true, 0]
- addsTo:
pack: codeql/go-all
extensible: sourceModel
data:
- ["github.com/nonexistent/sources", "", False, "ExecuteQuery", "", "", "ReturnValue", "database", "manual"]
- ["github.com/nonexistent/sources", "", False, "ReadEnvironment", "", "", "ReturnValue", "environment", "manual"]
- ["github.com/nonexistent/sources", "", False, "GetCustom", "", "", "ReturnValue", "custom", "manual"]
- ["github.com/nonexistent/sources", "", False, "GetCliArg", "", "", "ReturnValue", "commandargs", "manual"]

View File

@@ -0,0 +1,11 @@
/**
* This is a dataflow test using the "default" threat model with the
* addition of the threat model group "local".
*/
import Test
import ThreatModelFlow::PathGraph
from ThreatModelFlow::PathNode source, ThreatModelFlow::PathNode sink
where ThreatModelFlow::flowPath(source, sink)
select source, sink

View File

@@ -0,0 +1,28 @@
edges
| test.go:9:10:9:40 | call to ReadEnvironment | test.go:11:7:11:29 | ...+... | provenance | Src:MaD:2 |
| test.go:15:9:15:32 | call to GetCliArg | test.go:17:7:17:28 | ...+... | provenance | Src:MaD:4 |
| test.go:21:11:21:36 | call to GetCustom | test.go:23:7:23:30 | ...+... | provenance | Src:MaD:3 |
| test.go:27:11:27:63 | call to ExecuteQuery | test.go:28:7:28:11 | query | provenance | Src:MaD:1 |
| test.go:32:11:32:15 | selection of URL | test.go:32:11:32:23 | call to Query | provenance | MaD:738 |
| test.go:32:11:32:23 | call to Query | test.go:32:11:32:36 | call to Get | provenance | MaD:745 |
| test.go:32:11:32:36 | call to Get | test.go:34:7:34:30 | ...+... | provenance | |
nodes
| test.go:9:10:9:40 | call to ReadEnvironment | semmle.label | call to ReadEnvironment |
| test.go:11:7:11:29 | ...+... | semmle.label | ...+... |
| test.go:15:9:15:32 | call to GetCliArg | semmle.label | call to GetCliArg |
| test.go:17:7:17:28 | ...+... | semmle.label | ...+... |
| test.go:21:11:21:36 | call to GetCustom | semmle.label | call to GetCustom |
| test.go:23:7:23:30 | ...+... | semmle.label | ...+... |
| test.go:27:11:27:63 | call to ExecuteQuery | semmle.label | call to ExecuteQuery |
| test.go:28:7:28:11 | query | semmle.label | query |
| test.go:32:11:32:15 | selection of URL | semmle.label | selection of URL |
| test.go:32:11:32:23 | call to Query | semmle.label | call to Query |
| test.go:32:11:32:36 | call to Get | semmle.label | call to Get |
| test.go:34:7:34:30 | ...+... | semmle.label | ...+... |
subpaths
#select
| test.go:9:10:9:40 | call to ReadEnvironment | test.go:11:7:11:29 | ...+... |
| test.go:15:9:15:32 | call to GetCliArg | test.go:17:7:17:28 | ...+... |
| test.go:21:11:21:36 | call to GetCustom | test.go:23:7:23:30 | ...+... |
| test.go:27:11:27:63 | call to ExecuteQuery | test.go:28:7:28:11 | query |
| test.go:32:11:32:15 | selection of URL | test.go:34:7:34:30 | ...+... |

View File

@@ -0,0 +1,17 @@
extensions:
- addsTo:
pack: codeql/threat-models
extensible: threatModelConfiguration
data:
- ["all", true, 0]
- addsTo:
pack: codeql/go-all
extensible: sourceModel
data:
- ["github.com/nonexistent/sources", "", False, "ExecuteQuery", "", "", "ReturnValue", "database", "manual"]
- ["github.com/nonexistent/sources", "", False, "ReadEnvironment", "", "", "ReturnValue", "environment", "manual"]
- ["github.com/nonexistent/sources", "", False, "GetCustom", "", "", "ReturnValue", "custom", "manual"]
- ["github.com/nonexistent/sources", "", False, "GetCliArg", "", "", "ReturnValue", "commandargs", "manual"]

View File

@@ -0,0 +1,10 @@
/**
* This is a dataflow test using "all" threat models.
*/
import Test
import ThreatModelFlow::PathGraph
from ThreatModelFlow::PathNode source, ThreatModelFlow::PathNode sink
where ThreatModelFlow::flowPath(source, sink)
select source, sink

View File

@@ -0,0 +1,20 @@
edges
| test.go:9:10:9:40 | call to ReadEnvironment | test.go:11:7:11:29 | ...+... | provenance | Src:MaD:3 |
| test.go:15:9:15:32 | call to GetCliArg | test.go:17:7:17:28 | ...+... | provenance | Src:MaD:5 |
| test.go:32:11:32:15 | selection of URL | test.go:32:11:32:23 | call to Query | provenance | MaD:738 |
| test.go:32:11:32:23 | call to Query | test.go:32:11:32:36 | call to Get | provenance | MaD:745 |
| test.go:32:11:32:36 | call to Get | test.go:34:7:34:30 | ...+... | provenance | |
nodes
| test.go:9:10:9:40 | call to ReadEnvironment | semmle.label | call to ReadEnvironment |
| test.go:11:7:11:29 | ...+... | semmle.label | ...+... |
| test.go:15:9:15:32 | call to GetCliArg | semmle.label | call to GetCliArg |
| test.go:17:7:17:28 | ...+... | semmle.label | ...+... |
| test.go:32:11:32:15 | selection of URL | semmle.label | selection of URL |
| test.go:32:11:32:23 | call to Query | semmle.label | call to Query |
| test.go:32:11:32:36 | call to Get | semmle.label | call to Get |
| test.go:34:7:34:30 | ...+... | semmle.label | ...+... |
subpaths
#select
| test.go:9:10:9:40 | call to ReadEnvironment | test.go:11:7:11:29 | ...+... |
| test.go:15:9:15:32 | call to GetCliArg | test.go:17:7:17:28 | ...+... |
| test.go:32:11:32:15 | selection of URL | test.go:34:7:34:30 | ...+... |

View File

@@ -0,0 +1,18 @@
extensions:
- addsTo:
pack: codeql/threat-models
extensible: threatModelConfiguration
data:
- ["environment", true, 0]
- ["commandargs", true, 0]
- addsTo:
pack: codeql/go-all
extensible: sourceModel
data:
- ["github.com/nonexistent/sources", "", False, "ExecuteQuery", "", "", "ReturnValue", "database", "manual"]
- ["github.com/nonexistent/sources", "", False, "ReadEnvironment", "", "", "ReturnValue", "environment", "manual"]
- ["github.com/nonexistent/sources", "", False, "GetCustom", "", "", "ReturnValue", "custom", "manual"]
- ["github.com/nonexistent/sources", "", False, "GetCliArg", "", "", "ReturnValue", "commandargs", "manual"]

View File

@@ -0,0 +1,11 @@
/**
* This is a dataflow test using the "default" threat model with the
* addition of "environment" and "commandargs".
*/
import Test
import ThreatModelFlow::PathGraph
from ThreatModelFlow::PathNode source, ThreatModelFlow::PathNode sink
where ThreatModelFlow::flowPath(source, sink)
select source, sink

View File

@@ -0,0 +1,20 @@
edges
| test.go:15:9:15:32 | call to GetCliArg | test.go:17:7:17:28 | ...+... | provenance | Src:MaD:5 |
| test.go:27:11:27:63 | call to ExecuteQuery | test.go:28:7:28:11 | query | provenance | Src:MaD:2 |
| test.go:32:11:32:15 | selection of URL | test.go:32:11:32:23 | call to Query | provenance | MaD:738 |
| test.go:32:11:32:23 | call to Query | test.go:32:11:32:36 | call to Get | provenance | MaD:745 |
| test.go:32:11:32:36 | call to Get | test.go:34:7:34:30 | ...+... | provenance | |
nodes
| test.go:15:9:15:32 | call to GetCliArg | semmle.label | call to GetCliArg |
| test.go:17:7:17:28 | ...+... | semmle.label | ...+... |
| test.go:27:11:27:63 | call to ExecuteQuery | semmle.label | call to ExecuteQuery |
| test.go:28:7:28:11 | query | semmle.label | query |
| test.go:32:11:32:15 | selection of URL | semmle.label | selection of URL |
| test.go:32:11:32:23 | call to Query | semmle.label | call to Query |
| test.go:32:11:32:36 | call to Get | semmle.label | call to Get |
| test.go:34:7:34:30 | ...+... | semmle.label | ...+... |
subpaths
#select
| test.go:15:9:15:32 | call to GetCliArg | test.go:17:7:17:28 | ...+... |
| test.go:27:11:27:63 | call to ExecuteQuery | test.go:28:7:28:11 | query |
| test.go:32:11:32:15 | selection of URL | test.go:34:7:34:30 | ...+... |

View File

@@ -0,0 +1,17 @@
extensions:
- addsTo:
pack: codeql/threat-models
extensible: threatModelConfiguration
data:
- ["local", true, 0]
- ["environment", false, 1]
- addsTo:
pack: codeql/go-all
extensible: sourceModel
data:
- ["github.com/nonexistent/sources", "", False, "ExecuteQuery", "", "", "ReturnValue", "database", "manual"]
- ["github.com/nonexistent/sources", "", False, "ReadEnvironment", "", "", "ReturnValue", "environment", "manual"]
- ["github.com/nonexistent/sources", "", False, "GetCustom", "", "", "ReturnValue", "custom", "manual"]
- ["github.com/nonexistent/sources", "", False, "GetCliArg", "", "", "ReturnValue", "commandargs", "manual"]

View File

@@ -0,0 +1,12 @@
/**
* This is a dataflow test using the "default" threat model with the
* addition of the threat model group "local", but without the
* "environment" threat model.
*/
import Test
import ThreatModelFlow::PathGraph
from ThreatModelFlow::PathNode source, ThreatModelFlow::PathNode sink
where ThreatModelFlow::flowPath(source, sink)
select source, sink

View File

@@ -0,0 +1,17 @@
package sources
func ExecuteQuery(query string) string {
return ""
}
func ReadEnvironment(env string) string {
return ""
}
func GetCustom(s string) string {
return ""
}
func GetCliArg(s string) string {
return ""
}

View File

@@ -0,0 +1,3 @@
# github.com/nonexistent/sources v0.0.0-20240601000000-0000000000000
## explicit
github.com/nonexistent/sources