Skip to content

Merge field/seclab packs #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'csharp', 'go', 'java' ]
language: [ 'csharp', 'go', 'java', 'python' ]

steps:
- uses: actions/checkout@v3
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
language: ["csharp", "java"]
language: ["csharp", "go", "java", "python"]

steps:
- uses: actions/checkout@v3
Expand Down Expand Up @@ -54,7 +54,7 @@ jobs:
strategy:
fail-fast: false
matrix:
language: ["csharp", "java"]
language: ["csharp", "go", "java", "python"]

steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 2 additions & 0 deletions codeql-workspace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ provide:
- csharp/**/qlpack.yml
- go/**/qlpack.yml
- java/**/qlpack.yml
- python/**/qlpack.yml

2 changes: 0 additions & 2 deletions java/src/suites/java-audit.qls
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# This is the field security specialist audit pack

- description: "GitHub's Community Packs Java Audit Suite"

# Audit queries
Expand Down
16 changes: 1 addition & 15 deletions java/src/suites/java-local.qls
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# See https://help.semmle.com/codeql/codeql-cli/procedures/query-suites.html#filtering-the-queries-in-a-query-suite
# for additional ways to exclude queries

- description: "GitHub's Community Packs Java Local Variate Suite"

- import: codeql-suites/java-security-extended.qls
Expand All @@ -15,15 +12,4 @@
- queries: '.'
from: codeql/java-queries
- include:
id:
- java/path-injection-local
- java/command-line-injection-local
- java/xss-local
- java/sql-injection-local
- java/http-response-splitting-local
- java/improper-validation-of-array-construction-local
- java/improper-validation-of-array-index-local
- java/tainted-format-string-local
- java/tainted-arithmetic-local
- java/unvalidated-url-redirection-local
- java/tainted-numeric-cast-local
tags contain: -local
20 changes: 20 additions & 0 deletions python/lib/codeql-pack.lock.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
lockVersion: 1.0.0
dependencies:
codeql/dataflow:
version: 0.0.3
codeql/mad:
version: 0.1.4
codeql/python-all:
version: 0.10.4
codeql/regex:
version: 0.1.4
codeql/ssa:
version: 0.1.4
codeql/tutorial:
version: 0.1.4
codeql/util:
version: 0.1.4
codeql/yaml:
version: 0.1.4
compiled: false
43 changes: 43 additions & 0 deletions python/lib/github/DefaultPasswordDB.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
private import python

// password = db.Column(..., server_default=...)

class DBColumn extends Call {
CallNode call;
string name;
ControlFlowNode object;
Name var;
string id;

DBColumn() {
call.getFunction().(AttrNode).getObject(name) = object
and name = "Column"
and call = this.getAFlowNode()
and object.getNode() = var.getVariable().getAnAccess()
and var.getId() = id
}

string getDbId() {
result = id
}

predicate hasStaticDefault() {
exists(DictItem arg |
arg = call.getNode().getANamedArg()
and arg.(Keyword).getArg() in ["server_default", "default"]
and arg.(Keyword).getValue() instanceof ImmutableLiteral
)
}

string assignedToVariable() {
exists(AssignStmt assign, Variable v|
assign.defines(v)
and v.getId() = result
and assign.getValue() = this
)
}

string getColumnName() {
result = call.getNode().getArg(0).(StrConst).getText()
}
}
203 changes: 203 additions & 0 deletions python/lib/github/HardcodedSecretSinks.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.BarrierGuards
private import semmle.python.ApiGraphs
private import DataFlow::PathGraph
private import semmle.python.frameworks.Flask

abstract class CredentialSink extends DataFlow::Node { }

Expr getDictValueByKey(Dict dict, string key) {
exists(KeyValuePair item |
// d = {KEY: VALUE}
item = dict.getAnItem() and
key = item.getKey().(StrConst).getS() and
result = item.getValue()
)
}

Expr getAssignStmtByKey(AssignStmt assign, string key) {
exists(Subscript sub |
// dict['KEY'] = VALUE
sub = assign.getASubExpression() and
// Make sure the keys match
// TODO: What happens if this value itself is not static?
key = sub.getASubExpression().(StrConst).getS() and
// TODO: Only supports static strings, resolve the value??
// result = assign.getASubExpression().(StrConst)
result = sub.getValue()
)
}

Expr getAnyAssignStmtByKey(string key) { result = getAssignStmtByKey(any(AssignStmt a), key) }

// =========================
// Web Frameworks
// =========================
class FlaskCredentialSink extends CredentialSink {
FlaskCredentialSink() {
exists(API::Node node |
exists(AssignStmt stmt |
// app = flask.Flask(__name__)
// app.secret_key = VALUE
node = Flask::FlaskApp::instance().getMember("secret_key") and
stmt = node.getAValueReachingSink().asExpr().getParentNode() and
this = DataFlow::exprNode(stmt.getValue())
)
or
exists(Expr assign, AssignStmt stmt |
// app.config['SECRET_KEY'] = VALUE
assign = getAnyAssignStmtByKey("SECRET_KEY").getParentNode() and
stmt = assign.getParentNode() and
this = DataFlow::exprNode(stmt.getValue())
)
or
// app.config.update(SECRET_KEY=VALUE)
node = Flask::FlaskApp::instance().getMember("config").getMember("update") and
this = DataFlow::exprNode(node.getACall().getArgByName("SECRET_KEY").asExpr())
)
}
}

class DjangoCredentialSink extends CredentialSink {
DjangoCredentialSink() {
// Check Django import is present
exists(API::moduleImport("django")) and
exists(AssignStmt stmt |
// Check is the SECRET_KEY is in the a settings.py file
// Removed "settings/develop.py"
stmt.getLocation().getFile().getBaseName() = ["settings.py", "settings/production.py"] and
(
stmt.getATarget().toString() = "SECRET_KEY" and
this.asExpr() = stmt.getValue()
)
)
}
}

// =========================
// Databases
// =========================
class MySqlSink extends CredentialSink {
MySqlSink() {
this =
API::moduleImport("mysql")
.getMember("connector")
.getMember("connect")
.getACall()
.getArgByName("password")
}
}

class AsyncpgSink extends CredentialSink {
AsyncpgSink() {
this = API::moduleImport("asyncpg").getMember("connect").getACall().getArgByName("password") or
this =
API::moduleImport("asyncpg")
.getMember("connection")
.getMember("Connection")
.getACall()
.getArgByName("password")
}
}

class PsycopgSink extends CredentialSink {
PsycopgSink() {
this = API::moduleImport("psycopg2").getMember("connect").getACall().getArgByName("password")
}
}

class AioredisSink extends CredentialSink {
AioredisSink() {
this =
API::moduleImport("aioredis")
.getMember("create_connection")
.getACall()
.getArgByName("password")
or
this =
API::moduleImport("aioredis").getMember("create_pool").getACall().getArgByName("password")
or
this =
API::moduleImport("aioredis").getMember("create_redis").getACall().getArgByName("password")
or
// redis = await aioredis.create_redis_pool(
// 'redis://localhost', password='sEcRet')
this =
API::moduleImport("aioredis")
.getMember("create_redis_pool")
.getACall()
.getArgByName("password")
or
this =
API::moduleImport("aioredis")
.getMember("sentinel")
.getMember("create_sentinel")
.getACall()
.getArgByName("password")
or
this =
API::moduleImport("aioredis")
.getMember("sentinel")
.getMember("create_sentinel_pool")
.getACall()
.getArgByName("password")
}
}

// =========================
// Utils
// =========================
class RequestsSink extends CredentialSink {
RequestsSink() {
// from requests.auth import HTTPBasicAuth
// auth = HTTPBasicAuth('user', 'mysecretpassword')
this =
API::moduleImport("requests")
.getMember("auth")
.getMember("HTTPBasicAuth")
.getACall()
.getArg(1)
}
}

class PyJwtSink extends CredentialSink {
PyJwtSink() {
// import jwt
// encoded = jwt.encode({"some": "payload"}, "secret", algorithm="HS256")
this = API::moduleImport("jwt").getMember("encode").getACall().getArg(1)
or
// decode = jwt.decode(encoded, "secret", algorithm="HS256")
this = API::moduleImport("jwt").getMember("decode").getACall().getArg(1)
}
}

class PyOtpSink extends CredentialSink {
PyOtpSink() {
// import pyotp
// totp = pyotp.TOTP('base32secret3232')
this = API::moduleImport("pyotp").getMember("TOTP").getACall().getArg(0)
}
}

class Boto3Sink extends CredentialSink {
Boto3Sink() {
// https://docs.min.io/docs/how-to-use-aws-sdk-for-python-with-minio-server.html
exists(DataFlow::CallCfgNode calls |
// s3 = boto3.resource('s3',
// aws_access_key_id='YOUR-ACCESSKEYID',
// aws_secret_access_key='YOUR-SECRETACCESSKEY'
// aws_session_token="YOUR-SESSION-TOKEN"
// )
calls = API::moduleImport("boto3").getMember(["client", "resource"]).getACall() and
(
this = calls.getArgByName("aws_access_key_id") or
this = calls.getArgByName("aws_secret_access_key") or
this = calls.getArgByName("aws_session_token")
)
)
}
}
45 changes: 45 additions & 0 deletions python/lib/github/Helpers.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.RemoteFlowSources
// Import Sinks
private import semmle.python.security.dataflow.CommandInjectionCustomizations
private import semmle.python.security.dataflow.CodeInjectionCustomizations
private import semmle.python.security.dataflow.ServerSideRequestForgeryCustomizations
private import semmle.python.security.dataflow.SqlInjectionCustomizations
private import semmle.python.security.dataflow.UnsafeDeserializationCustomizations
// Fields Sinks
private import github.HardcodedSecretSinks
private import github.MassAssignment

// Find Node at Location
predicate findByLocation(DataFlow::Node node, string relative_path, int linenumber) {
node.getLocation().getFile().getRelativePath() = relative_path and
node.getLocation().getStartLine() = linenumber
}

// Dangerous Sinks
predicate dangerousSinks(DataFlow::Node sink) {
(
sink instanceof CommandInjection::Sink or
sink instanceof CodeInjection::Sink or
sink instanceof ServerSideRequestForgery::Sink or
sink instanceof SqlInjection::Sink or
sink instanceof UnsafeDeserialization::Sink or
// Fields Query Addtional Sinks
sink instanceof CredentialSink or
sink instanceof MassAssignment::Sinks
) and
sink.getScope().inSource()
}

predicate functionParameters(DataFlow::Node node) {
(
// // Function Call Parameters
node instanceof DataFlow::ParameterNode
or
// Function Call Arguments
node instanceof DataFlow::ArgumentNode
) and
not dangerousSinks(node) and
node.getScope().inSource()
}
Loading