diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 71ecbf5ff6..454fc52aa1 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -35,7 +35,7 @@ jobs: - name: Setup go uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version: '1.21.0' check-latest: true - name: Checkout repository uses: actions/checkout@v4 @@ -43,6 +43,7 @@ jobs: uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} + queries: ./codeql/ - name: Autobuild uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis diff --git a/codeql/addipamconfig-to-exec.ql b/codeql/addipamconfig-to-exec.ql new file mode 100644 index 0000000000..eaa9314e3f --- /dev/null +++ b/codeql/addipamconfig-to-exec.ql @@ -0,0 +1,52 @@ +/** + * @name Command Injection From CNS ipam add result / CNS multitenancy ipam add result + * @description Flow exists from CNS ipam add result / CNS multitenancy ipam add result (untrusted) to exec command + * @kind path-problem + * @problem.severity error + * @id go/cmd-inject-ipam-add-result + * @tags security + * @security-severity 9.8 + * @precision high + */ + +// Detect inputs from CNS add ipam result / CNS multitenancy ipam add result to command injection +import go + +private class Sink extends DataFlow2::Node { + Sink() { + exists(DataFlow::CallNode c | + c.getTarget().hasQualifiedName("os/exec", "CommandContext") and + (c.getArgument(2) = this or c.getArgument(1) = this) + or + c.getTarget().hasQualifiedName("os/exec", "Command") and + (c.getArgument(0) = this or c.getArgument(1) = this) + ) + } +} + +private class Source extends DataFlow2::Node { + Source() { + exists(DataFlow::CallNode c, Method m | + //m.hasQualifiedName("github.com/Azure/azure-container-networking/cni/network", "NetPlugin", + // "addIpamInvoker") or // this is not necessary since we call GetAllNetworkContainers right next to this = duplicated results, but if this call moves, uncomment this + m.hasQualifiedName("github.com/Azure/azure-container-networking/cni/network", "Multitenancy", + "GetAllNetworkContainers") and + c = m.getACall() and + c.getResult(0) = this + ) + } +} + +module MyConfiguration implements DataFlow::ConfigSig { + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isSource(DataFlow::Node source) { source instanceof Source } +} + +module Flow = TaintTracking::Global; + +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "potential command injection" diff --git a/codeql/cni-args-to-exec.ql b/codeql/cni-args-to-exec.ql new file mode 100644 index 0000000000..ed45b29e6f --- /dev/null +++ b/codeql/cni-args-to-exec.ql @@ -0,0 +1,58 @@ +/** + * @name Command Injection From CNI Args + * @description Flow exists from CNI Args (untrusted) to exec command + * @kind path-problem + * @problem.severity error + * @id go/cmd-inject-cni + * @tags security + * @security-severity 9.8 + * @precision high + */ + +// Detect inputs from CNI ARGS to command injection +import go + +private class Sink extends DataFlow2::Node { + Sink() { + exists(DataFlow::CallNode c | + c.getTarget().hasQualifiedName("os/exec", "CommandContext") and + (c.getArgument(2) = this or c.getArgument(1) = this) + or + c.getTarget().hasQualifiedName("os/exec", "Command") and + (c.getArgument(0) = this or c.getArgument(1) = this) + ) + } +} + +private class Source extends DataFlow2::Node { + Source() { + exists(DataFlow::CallNode c, Method m | + ( + m.hasQualifiedName("github.com/Azure/azure-container-networking/cni/network", "NetPlugin", + "Add") or + m.hasQualifiedName("github.com/Azure/azure-container-networking/cni/network", "NetPlugin", + "Delete") or + m.hasQualifiedName("github.com/Azure/azure-container-networking/cni/network", "NetPlugin", + "Update") or + m.hasQualifiedName("github.com/Azure/azure-container-networking/cni/network", "NetPlugin", + "Get") + ) and + c = m.getACall() and + c.getArgument(0) = this + ) + } +} + +module MyConfiguration implements DataFlow::ConfigSig { + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isSource(DataFlow::Node source) { source instanceof Source } +} + +module Flow = TaintTracking::Global; + +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "potential command injection" diff --git a/codeql/cns-invoker-to-exec.ql b/codeql/cns-invoker-to-exec.ql new file mode 100644 index 0000000000..180786462f --- /dev/null +++ b/codeql/cns-invoker-to-exec.ql @@ -0,0 +1,59 @@ +/** + * @name Command Injection From CNS Invoker + * @description Flow exists from CNS Invoker (untrusted) to exec command + * @kind path-problem + * @problem.severity error + * @id go/cmd-inject-cns-invoker + * @tags security + * @security-severity 9.8 + * @precision high + */ + +// Detect inputs from CNS Invoker to command injection +// Does not detect flow to outside the enclosed method (which is why we analyze addIpamInvoker's results too) +import go + +private class Sink extends DataFlow2::Node { + Sink() { + exists(DataFlow::CallNode c | + c.getTarget().hasQualifiedName("os/exec", "CommandContext") and + (c.getArgument(2) = this or c.getArgument(1) = this) + or + c.getTarget().hasQualifiedName("os/exec", "Command") and + (c.getArgument(0) = this or c.getArgument(1) = this) + ) + } +} + +private class Source extends DataFlow2::Node { + Source() { + exists(DataFlow::CallNode c, Method m | + ( + m.hasQualifiedName("github.com/Azure/azure-container-networking/cns/client", "Client", + "RequestIPs") or + m.hasQualifiedName("github.com/Azure/azure-container-networking/cns/client", "Client", + "RequestIPAddress") or + m.hasQualifiedName("github.com/Azure/azure-container-networking/cns/client", "Client", + "GetNetworkContainer") or + m.hasQualifiedName("github.com/Azure/azure-container-networking/cns/client", "Client", + "GetAllNetworkContainers") + ) and + c = m.getACall() and + c.getResult(0) = this + ) + } +} + +module MyConfiguration implements DataFlow::ConfigSig { + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isSource(DataFlow::Node source) { source instanceof Source } +} + +module Flow = TaintTracking::Global; + +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "potential command injection" diff --git a/codeql/codeql-pack.lock.yml b/codeql/codeql-pack.lock.yml new file mode 100644 index 0000000000..5300427457 --- /dev/null +++ b/codeql/codeql-pack.lock.yml @@ -0,0 +1,4 @@ +--- +lockVersion: 1.0.0 +dependencies: {} +compiled: false diff --git a/codeql/decode-to-exec.ql b/codeql/decode-to-exec.ql new file mode 100644 index 0000000000..5c9f700aad --- /dev/null +++ b/codeql/decode-to-exec.ql @@ -0,0 +1,48 @@ +/** + * @name Command Injection From Decode + * @description Flow exists from decodes (untrusted) to exec command + * @kind path-problem + * @problem.severity error + * @id go/cmd-inject-decode + * @tags security + * @security-severity 9.8 + * @precision high + */ + +// Detect flow from the DECODE method (which decodes http requests) to a command execution +import go + +private class Sink extends DataFlow2::Node { + Sink() { + exists(DataFlow::CallNode c | + c.getTarget().hasQualifiedName("os/exec", "CommandContext") and + (c.getArgument(2) = this or c.getArgument(1) = this) + or + c.getTarget().hasQualifiedName("os/exec", "Command") and + (c.getArgument(0) = this or c.getArgument(1) = this) + ) + } +} + +private class Source extends DataFlow2::Node { + Source() { + exists(DataFlow::CallNode c | + c.getTarget().hasQualifiedName("github.com/Azure/azure-container-networking/common", "Decode") and + c.getArgument(2) = this + ) + } +} + +module MyConfiguration implements DataFlow::ConfigSig { + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isSource(DataFlow::Node source) { source instanceof Source } +} + +module Flow = TaintTracking::Global; + +import Flow::PathGraph + +from Flow::PathNode source, Flow::PathNode sink +where Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "potential command injection" diff --git a/codeql/qlpack.yml b/codeql/qlpack.yml new file mode 100644 index 0000000000..833313ee1a --- /dev/null +++ b/codeql/qlpack.yml @@ -0,0 +1,7 @@ +--- +library: false +warnOnImplicitThis: false +name: codeql +version: 0.0.1 +dependencies: + codeql/go-all: ^1.1.3