diff --git a/python/ql/lib/semmle/python/security/dataflow/CleartextLoggingCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/CleartextLoggingCustomizations.qll index c00106a1260e..e477ba7c616f 100644 --- a/python/ql/lib/semmle/python/security/dataflow/CleartextLoggingCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/CleartextLoggingCustomizations.qll @@ -18,6 +18,32 @@ private import semmle.python.dataflow.new.BarrierGuards * vulnerabilities, as well as extension points for adding your own. */ module CleartextLogging { + private predicate inFunction(DataFlow::Node node, string moduleName, string functionName) { + exists(Function function | + node.getScope() = function and + function.getEnclosingModule().getName() = moduleName and + function.getName() = functionName + ) + } + + private predicate inMethod( + DataFlow::Node node, string moduleName, string className, string methodName + ) { + exists(Function method | + node.getScope() = method and + method.getEnclosingModule().getName() = moduleName and + method.getEnclosingScope().(Class).getName() = className and + method.getName() = methodName + ) + } + + private predicate isCallOn( + DataFlow::CallCfgNode call, string receiverName, string methodName + ) { + call.getFunction().(DataFlow::AttrRead).getObject().asExpr().(Name).getId() = receiverName and + call.getFunction().(DataFlow::AttrRead).getAttributeName() = methodName + } + /** * A data flow source for "Clear-text logging of sensitive information" vulnerabilities. */ @@ -51,6 +77,101 @@ module CleartextLogging { } } + /** + * A known non-sensitive Airflow configuration value, considered as a sanitizer. + */ + private class AirflowNonSensitiveConfigValue extends Sanitizer, DataFlow::CallCfgNode { + AirflowNonSensitiveConfigValue() { + ( + inMethod(this, "airflow.executors.executor_loader", "ExecutorLoader", + "_get_executor_names") and + isCallOn(this, "conf", "get_mandatory_list_value") and + this.getArg(1).asExpr().(StringLiteral).getText().toLowerCase() = "executor" + or + inMethod(this, "airflow.api_internal.internal_api_call", "InternalApiConfig", + "_init_values") and + isCallOn(this, "conf", "get") and + this.getArg(1).asExpr().(StringLiteral).getText().toLowerCase() = "internal_api_url" + or + this.getScope().(Module).getName() = "airflow.settings" and + isCallOn(this, "conf", "get_mandatory_value") and + this.getArg(1).asExpr().(StringLiteral).getText().toLowerCase() = "dags_folder" + ) and + this.getArg(0).asExpr().(StringLiteral).getText().toLowerCase() = "core" + } + } + + /** + * A known non-sensitive Airflow object rendering, considered as a sanitizer. + */ + private class AirflowNonSensitiveRendering extends Sanitizer { + AirflowNonSensitiveRendering() { + this instanceof LoggingAsSink and + inFunction(this, "airflow.cli.commands.task_command", "task_run") and + this.asExpr().(Name).getId() = "ti" + or + this instanceof PrintedDataAsSink and + inFunction(this, "airflow.cli.commands.task_command", "_run_task_by_executor") and + exists(Fstring fstring | + this.asExpr() = fstring and + fstring.getAValue().(FormattedValue).getValue().(Name).getId() = "dag" and + not exists(FormattedValue value | + value = fstring.getAValue() and + not value.getValue().(Name).getId() in ["dag", "pickle_id"] + ) + ) + } + } + + /** + * A known non-sensitive Airflow identifier or diagnostic, considered as a sanitizer. + */ + private class AirflowNonSensitiveDiagnostic extends Sanitizer { + AirflowNonSensitiveDiagnostic() { + this instanceof LoggingAsSink and + ( + inMethod(this, "airflow.models.xcom", "BaseXCom", "set") and + ( + this.asExpr().(Name).getId() in ["task_id", "dag_id"] + or + exists(BoolExpr fallback | + this.asExpr() = fallback and + fallback.getOp() instanceof Or and + count(Expr value | value = fallback.getAValue()) = 2 and + exists(Name run, Name execution | + run = fallback.getAValue() and + run.getId() = "run_id" and + execution = fallback.getAValue() and + execution.getId() = "execution_date" + ) + ) + ) + or + inFunction(this, "airflow.utils.db", "upgradedb") and + exists(For loop, Call check, Name error | + this.asExpr() = error and + this.asExpr().getParentNode*() = loop and + error.getVariable() = loop.getTarget().(Name).getVariable() and + loop.getIter() = check and + check.getFunc().(Name).getId() = "_check_migration_errors" and + not exists(Name redefinition | + redefinition != loop.getTarget() and + redefinition.defines(error.getVariable()) and + redefinition.getParentNode*() = loop + ) + ) + or + inMethod(this, "airflow.serialization.serialized_objects", "SerializedBaseOperator", + "_deserialize_deps") and + this.asExpr().(Name).getId() = "qn" + or + inMethod(this, "airflow.serialization.serialized_objects", "SerializedBaseOperator", + "_deserialize_operator_extra_links") and + this.asExpr().(Name).getId() = "_operator_link_class_path" + ) + } + } + /** A piece of data logged, considered as a flow sink. */ class LoggingAsSink extends Sink { LoggingAsSink() { this = any(Logging write).getAnInput() } diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/CleartextLogging.expected b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/CleartextLogging.expected new file mode 100644 index 000000000000..9a1ae703ab18 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/CleartextLogging.expected @@ -0,0 +1,111 @@ +#select +| airflow/api_internal/internal_api_call.py:17:15:17:44 | Attribute() | airflow/api_internal/internal_api_call.py:7:16:7:29 | get_password() | airflow/api_internal/internal_api_call.py:17:15:17:44 | Attribute() | This expression logs $@ as clear text. | airflow/api_internal/internal_api_call.py:7:16:7:29 | get_password() | sensitive data (password) | +| airflow/api_internal/internal_api_call.py:19:15:19:51 | Attribute() | airflow/api_internal/internal_api_call.py:7:16:7:29 | get_password() | airflow/api_internal/internal_api_call.py:19:15:19:51 | Attribute() | This expression logs $@ as clear text. | airflow/api_internal/internal_api_call.py:7:16:7:29 | get_password() | sensitive data (password) | +| airflow/cli/commands/task_command.py:10:11:10:52 | Fstring | airflow/cli/commands/task_command.py:8:32:8:39 | password | airflow/cli/commands/task_command.py:10:11:10:52 | Fstring | This expression logs $@ as clear text. | airflow/cli/commands/task_command.py:8:32:8:39 | password | sensitive data (password) | +| airflow/cli/commands/task_command.py:11:11:11:33 | Fstring | airflow/cli/commands/task_command.py:8:32:8:39 | password | airflow/cli/commands/task_command.py:11:11:11:33 | Fstring | This expression logs $@ as clear text. | airflow/cli/commands/task_command.py:8:32:8:39 | password | sensitive data (password) | +| airflow/cli/commands/task_command.py:18:30:18:37 | other_ti | airflow/cli/commands/task_command.py:14:18:14:25 | password | airflow/cli/commands/task_command.py:18:30:18:37 | other_ti | This expression logs $@ as clear text. | airflow/cli/commands/task_command.py:14:18:14:25 | password | sensitive data (password) | +| airflow/cli/commands/task_command.py:19:34:19:41 | password | airflow/cli/commands/task_command.py:14:18:14:25 | password | airflow/cli/commands/task_command.py:19:34:19:41 | password | This expression logs $@ as clear text. | airflow/cli/commands/task_command.py:14:18:14:25 | password | sensitive data (password) | +| airflow/cli/commands/task_command.py:21:45:21:63 | connection_password | airflow/cli/commands/task_command.py:20:27:20:45 | Attribute | airflow/cli/commands/task_command.py:21:45:21:63 | connection_password | This expression logs $@ as clear text. | airflow/cli/commands/task_command.py:20:27:20:45 | Attribute | sensitive data (password) | +| airflow/executors/executor_loader.py:17:15:17:65 | Attribute() | airflow/executors/executor_loader.py:7:16:7:29 | get_password() | airflow/executors/executor_loader.py:17:15:17:65 | Attribute() | This expression logs $@ as clear text. | airflow/executors/executor_loader.py:7:16:7:29 | get_password() | sensitive data (password) | +| airflow/executors/executor_loader.py:19:15:19:64 | Attribute() | airflow/executors/executor_loader.py:7:16:7:29 | get_password() | airflow/executors/executor_loader.py:19:15:19:64 | Attribute() | This expression logs $@ as clear text. | airflow/executors/executor_loader.py:7:16:7:29 | get_password() | sensitive data (password) | +| airflow/models/xcom.py:14:13:14:15 | key | airflow/models/xcom.py:6:18:6:25 | password | airflow/models/xcom.py:14:13:14:15 | key | This expression logs $@ as clear text. | airflow/models/xcom.py:6:18:6:25 | password | sensitive data (password) | +| airflow/models/xcom.py:19:41:19:48 | or | airflow/models/xcom.py:6:18:6:25 | password | airflow/models/xcom.py:19:41:19:48 | or | This expression logs $@ as clear text. | airflow/models/xcom.py:6:18:6:25 | password | sensitive data (password) | +| airflow/models/xcom.py:20:40:20:47 | password | airflow/models/xcom.py:6:18:6:25 | password | airflow/models/xcom.py:20:40:20:47 | password | This expression logs $@ as clear text. | airflow/models/xcom.py:6:18:6:25 | password | sensitive data (password) | +| airflow/serialization/serialized_objects.py:10:41:10:48 | other_qn | airflow/serialization/serialized_objects.py:6:32:6:39 | password | airflow/serialization/serialized_objects.py:10:41:10:48 | other_qn | This expression logs $@ as clear text. | airflow/serialization/serialized_objects.py:6:32:6:39 | password | sensitive data (password) | +| airflow/serialization/serialized_objects.py:11:40:11:47 | password | airflow/serialization/serialized_objects.py:6:32:6:39 | password | airflow/serialization/serialized_objects.py:11:40:11:47 | password | This expression logs $@ as clear text. | airflow/serialization/serialized_objects.py:6:32:6:39 | password | sensitive data (password) | +| airflow/serialization/serialized_objects.py:18:56:18:71 | other_class_path | airflow/serialization/serialized_objects.py:14:48:14:55 | password | airflow/serialization/serialized_objects.py:18:56:18:71 | other_class_path | This expression logs $@ as clear text. | airflow/serialization/serialized_objects.py:14:48:14:55 | password | sensitive data (password) | +| airflow/serialization/serialized_objects.py:19:38:19:45 | password | airflow/serialization/serialized_objects.py:14:48:14:55 | password | airflow/serialization/serialized_objects.py:19:38:19:45 | password | This expression logs $@ as clear text. | airflow/serialization/serialized_objects.py:14:48:14:55 | password | sensitive data (password) | +| airflow/settings.py:13:7:13:52 | Attribute() | airflow/settings.py:7:16:7:29 | get_password() | airflow/settings.py:13:7:13:52 | Attribute() | This expression logs $@ as clear text. | airflow/settings.py:7:16:7:29 | get_password() | sensitive data (password) | +| airflow/settings.py:15:7:15:54 | Attribute() | airflow/settings.py:7:16:7:29 | get_password() | airflow/settings.py:15:7:15:54 | Attribute() | This expression logs $@ as clear text. | airflow/settings.py:7:16:7:29 | get_password() | sensitive data (password) | +| airflow/utils/db.py:11:47:11:54 | password | airflow/utils/db.py:8:15:8:22 | password | airflow/utils/db.py:11:47:11:54 | password | This expression logs $@ as clear text. | airflow/utils/db.py:8:15:8:22 | password | sensitive data (password) | +| airflow/utils/db.py:14:40:14:42 | err | airflow/utils/db.py:8:15:8:22 | password | airflow/utils/db.py:14:40:14:42 | err | This expression logs $@ as clear text. | airflow/utils/db.py:8:15:8:22 | password | sensitive data (password) | +| airflow/utils/db.py:16:35:16:37 | err | airflow/utils/db.py:8:15:8:22 | password | airflow/utils/db.py:16:35:16:37 | err | This expression logs $@ as clear text. | airflow/utils/db.py:8:15:8:22 | password | sensitive data (password) | +| airflow/utils/db.py:17:34:17:41 | password | airflow/utils/db.py:8:15:8:22 | password | airflow/utils/db.py:17:34:17:41 | password | This expression logs $@ as clear text. | airflow/utils/db.py:8:15:8:22 | password | sensitive data (password) | +edges +| airflow/api_internal/internal_api_call.py:7:16:7:29 | get_password() | airflow/api_internal/internal_api_call.py:17:15:17:44 | Attribute() | provenance | | +| airflow/api_internal/internal_api_call.py:7:16:7:29 | get_password() | airflow/api_internal/internal_api_call.py:19:15:19:51 | Attribute() | provenance | | +| airflow/cli/commands/task_command.py:8:32:8:39 | password | airflow/cli/commands/task_command.py:10:11:10:52 | Fstring | provenance | | +| airflow/cli/commands/task_command.py:8:32:8:39 | password | airflow/cli/commands/task_command.py:11:11:11:33 | Fstring | provenance | | +| airflow/cli/commands/task_command.py:14:18:14:25 | password | airflow/cli/commands/task_command.py:17:5:17:12 | other_ti | provenance | | +| airflow/cli/commands/task_command.py:14:18:14:25 | password | airflow/cli/commands/task_command.py:19:34:19:41 | password | provenance | | +| airflow/cli/commands/task_command.py:17:5:17:12 | other_ti | airflow/cli/commands/task_command.py:18:30:18:37 | other_ti | provenance | | +| airflow/cli/commands/task_command.py:20:5:20:23 | connection_password | airflow/cli/commands/task_command.py:21:45:21:63 | connection_password | provenance | | +| airflow/cli/commands/task_command.py:20:27:20:45 | Attribute | airflow/cli/commands/task_command.py:20:5:20:23 | connection_password | provenance | | +| airflow/executors/executor_loader.py:7:16:7:29 | get_password() | airflow/executors/executor_loader.py:17:15:17:65 | Attribute() | provenance | | +| airflow/executors/executor_loader.py:7:16:7:29 | get_password() | airflow/executors/executor_loader.py:19:15:19:64 | Attribute() | provenance | | +| airflow/models/xcom.py:6:18:6:25 | password | airflow/models/xcom.py:7:9:7:11 | key | provenance | | +| airflow/models/xcom.py:6:18:6:25 | password | airflow/models/xcom.py:10:9:10:14 | run_id | provenance | | +| airflow/models/xcom.py:6:18:6:25 | password | airflow/models/xcom.py:19:41:19:48 | or | provenance | | +| airflow/models/xcom.py:6:18:6:25 | password | airflow/models/xcom.py:20:40:20:47 | password | provenance | | +| airflow/models/xcom.py:7:9:7:11 | key | airflow/models/xcom.py:14:13:14:15 | key | provenance | | +| airflow/models/xcom.py:10:9:10:14 | run_id | airflow/models/xcom.py:19:41:19:48 | or | provenance | | +| airflow/serialization/serialized_objects.py:6:32:6:39 | password | airflow/serialization/serialized_objects.py:9:9:9:16 | other_qn | provenance | | +| airflow/serialization/serialized_objects.py:6:32:6:39 | password | airflow/serialization/serialized_objects.py:11:40:11:47 | password | provenance | | +| airflow/serialization/serialized_objects.py:9:9:9:16 | other_qn | airflow/serialization/serialized_objects.py:10:41:10:48 | other_qn | provenance | | +| airflow/serialization/serialized_objects.py:14:48:14:55 | password | airflow/serialization/serialized_objects.py:17:9:17:24 | other_class_path | provenance | | +| airflow/serialization/serialized_objects.py:14:48:14:55 | password | airflow/serialization/serialized_objects.py:19:38:19:45 | password | provenance | | +| airflow/serialization/serialized_objects.py:17:9:17:24 | other_class_path | airflow/serialization/serialized_objects.py:18:56:18:71 | other_class_path | provenance | | +| airflow/settings.py:7:16:7:29 | get_password() | airflow/settings.py:13:7:13:52 | Attribute() | provenance | | +| airflow/settings.py:7:16:7:29 | get_password() | airflow/settings.py:15:7:15:54 | Attribute() | provenance | | +| airflow/utils/db.py:8:15:8:22 | password | airflow/utils/db.py:11:47:11:54 | password | provenance | | +| airflow/utils/db.py:8:15:8:22 | password | airflow/utils/db.py:13:21:13:28 | password | provenance | | +| airflow/utils/db.py:8:15:8:22 | password | airflow/utils/db.py:15:17:15:24 | password | provenance | | +| airflow/utils/db.py:8:15:8:22 | password | airflow/utils/db.py:17:34:17:41 | password | provenance | | +| airflow/utils/db.py:13:13:13:15 | err | airflow/utils/db.py:14:40:14:42 | err | provenance | | +| airflow/utils/db.py:13:13:13:15 | err [List element] | airflow/utils/db.py:14:40:14:42 | err | provenance | | +| airflow/utils/db.py:13:20:13:29 | List [List element] | airflow/utils/db.py:13:13:13:15 | err | provenance | | +| airflow/utils/db.py:13:20:13:29 | List [List element] | airflow/utils/db.py:13:13:13:15 | err [List element] | provenance | | +| airflow/utils/db.py:13:21:13:28 | password | airflow/utils/db.py:13:20:13:29 | List [List element] | provenance | | +| airflow/utils/db.py:15:9:15:11 | err | airflow/utils/db.py:16:35:16:37 | err | provenance | | +| airflow/utils/db.py:15:9:15:11 | err [List element] | airflow/utils/db.py:16:35:16:37 | err | provenance | | +| airflow/utils/db.py:15:16:15:25 | List [List element] | airflow/utils/db.py:15:9:15:11 | err | provenance | | +| airflow/utils/db.py:15:16:15:25 | List [List element] | airflow/utils/db.py:15:9:15:11 | err [List element] | provenance | | +| airflow/utils/db.py:15:17:15:24 | password | airflow/utils/db.py:15:16:15:25 | List [List element] | provenance | | +nodes +| airflow/api_internal/internal_api_call.py:7:16:7:29 | get_password() | semmle.label | get_password() | +| airflow/api_internal/internal_api_call.py:17:15:17:44 | Attribute() | semmle.label | Attribute() | +| airflow/api_internal/internal_api_call.py:19:15:19:51 | Attribute() | semmle.label | Attribute() | +| airflow/cli/commands/task_command.py:8:32:8:39 | password | semmle.label | password | +| airflow/cli/commands/task_command.py:10:11:10:52 | Fstring | semmle.label | Fstring | +| airflow/cli/commands/task_command.py:11:11:11:33 | Fstring | semmle.label | Fstring | +| airflow/cli/commands/task_command.py:14:18:14:25 | password | semmle.label | password | +| airflow/cli/commands/task_command.py:17:5:17:12 | other_ti | semmle.label | other_ti | +| airflow/cli/commands/task_command.py:18:30:18:37 | other_ti | semmle.label | other_ti | +| airflow/cli/commands/task_command.py:19:34:19:41 | password | semmle.label | password | +| airflow/cli/commands/task_command.py:20:5:20:23 | connection_password | semmle.label | connection_password | +| airflow/cli/commands/task_command.py:20:27:20:45 | Attribute | semmle.label | Attribute | +| airflow/cli/commands/task_command.py:21:45:21:63 | connection_password | semmle.label | connection_password | +| airflow/executors/executor_loader.py:7:16:7:29 | get_password() | semmle.label | get_password() | +| airflow/executors/executor_loader.py:17:15:17:65 | Attribute() | semmle.label | Attribute() | +| airflow/executors/executor_loader.py:19:15:19:64 | Attribute() | semmle.label | Attribute() | +| airflow/models/xcom.py:6:18:6:25 | password | semmle.label | password | +| airflow/models/xcom.py:7:9:7:11 | key | semmle.label | key | +| airflow/models/xcom.py:10:9:10:14 | run_id | semmle.label | run_id | +| airflow/models/xcom.py:14:13:14:15 | key | semmle.label | key | +| airflow/models/xcom.py:19:41:19:48 | or | semmle.label | or | +| airflow/models/xcom.py:20:40:20:47 | password | semmle.label | password | +| airflow/serialization/serialized_objects.py:6:32:6:39 | password | semmle.label | password | +| airflow/serialization/serialized_objects.py:9:9:9:16 | other_qn | semmle.label | other_qn | +| airflow/serialization/serialized_objects.py:10:41:10:48 | other_qn | semmle.label | other_qn | +| airflow/serialization/serialized_objects.py:11:40:11:47 | password | semmle.label | password | +| airflow/serialization/serialized_objects.py:14:48:14:55 | password | semmle.label | password | +| airflow/serialization/serialized_objects.py:17:9:17:24 | other_class_path | semmle.label | other_class_path | +| airflow/serialization/serialized_objects.py:18:56:18:71 | other_class_path | semmle.label | other_class_path | +| airflow/serialization/serialized_objects.py:19:38:19:45 | password | semmle.label | password | +| airflow/settings.py:7:16:7:29 | get_password() | semmle.label | get_password() | +| airflow/settings.py:13:7:13:52 | Attribute() | semmle.label | Attribute() | +| airflow/settings.py:15:7:15:54 | Attribute() | semmle.label | Attribute() | +| airflow/utils/db.py:8:15:8:22 | password | semmle.label | password | +| airflow/utils/db.py:11:47:11:54 | password | semmle.label | password | +| airflow/utils/db.py:13:13:13:15 | err | semmle.label | err | +| airflow/utils/db.py:13:13:13:15 | err [List element] | semmle.label | err [List element] | +| airflow/utils/db.py:13:20:13:29 | List [List element] | semmle.label | List [List element] | +| airflow/utils/db.py:13:21:13:28 | password | semmle.label | password | +| airflow/utils/db.py:14:40:14:42 | err | semmle.label | err | +| airflow/utils/db.py:15:9:15:11 | err | semmle.label | err | +| airflow/utils/db.py:15:9:15:11 | err [List element] | semmle.label | err [List element] | +| airflow/utils/db.py:15:16:15:25 | List [List element] | semmle.label | List [List element] | +| airflow/utils/db.py:15:17:15:24 | password | semmle.label | password | +| airflow/utils/db.py:16:35:16:37 | err | semmle.label | err | +| airflow/utils/db.py:17:34:17:41 | password | semmle.label | password | +subpaths diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/CleartextLogging.qlref b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/CleartextLogging.qlref new file mode 100644 index 000000000000..2309f8c643a1 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/CleartextLogging.qlref @@ -0,0 +1,2 @@ +query: Security/CWE-312/CleartextLogging.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/__init__.py b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/api_internal/__init__.py b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/api_internal/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/api_internal/internal_api_call.py b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/api_internal/internal_api_call.py new file mode 100644 index 000000000000..6fdee4a2446c --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/api_internal/internal_api_call.py @@ -0,0 +1,19 @@ +def get_password(): + return "secret" + + +class Conf: + def get(self, section, key): + return get_password() # $ Source + + +conf = Conf() + + +class InternalApiConfig: + @staticmethod + def _init_values(): + print(conf.get("core", "internal_api_url")) + print(conf.get("core", "fernet_key")) # $ Alert + other = Conf() + print(other.get("core", "internal_api_url")) # $ Alert diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/cli/__init__.py b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/cli/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/cli/commands/__init__.py b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/cli/commands/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/cli/commands/task_command.py b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/cli/commands/task_command.py new file mode 100644 index 000000000000..eecfb3f5d29d --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/cli/commands/task_command.py @@ -0,0 +1,21 @@ +import logging + + +class Connection: + password = "secret" + + +def _run_task_by_executor(dag, password): # $ Source + print(f"Pickled dag {dag} as pickle_id: 1") + print(f"Pickled dag {dag}, password: {password}") # $ Alert + print(f"Password: {password}") # $ Alert + + +def task_run(ti, password, connection): # $ Source + ti = password + logging.info("Running %s", ti) + other_ti = password + logging.info("Other %s", other_ti) # $ Alert + logging.info("Password: %s", password) # $ Alert + connection_password = connection.password # $ Source + logging.info("Connection password: %s", connection_password) # $ Alert diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/executors/__init__.py b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/executors/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/executors/executor_loader.py b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/executors/executor_loader.py new file mode 100644 index 000000000000..1ede3812bd1f --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/executors/executor_loader.py @@ -0,0 +1,19 @@ +def get_password(): + return "secret" + + +class Conf: + def get_mandatory_list_value(self, section, key): + return get_password() # $ Source + + +conf = Conf() + + +class ExecutorLoader: + @classmethod + def _get_executor_names(cls): + print(conf.get_mandatory_list_value("core", "EXECUTOR")) + print(conf.get_mandatory_list_value("core", "FERNET_KEY")) # $ Alert + other = Conf() + print(other.get_mandatory_list_value("core", "EXECUTOR")) # $ Alert diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/models/__init__.py b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/models/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/models/xcom.py b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/models/xcom.py new file mode 100644 index 000000000000..29a6113c3a96 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/models/xcom.py @@ -0,0 +1,20 @@ +import logging + + +class BaseXCom: + @classmethod + def set(cls, password, key, task_id, dag_id, run_id, execution_date): # $ Source + key = password + task_id = password + dag_id = password + run_id = password + execution_date = password + logging.warning( + "value %s from task %s (DAG %s, run %s)", + key, # $ Alert + task_id, + dag_id, + run_id or execution_date, + ) + logging.warning("mixed run %s", password or run_id) # $ Alert + logging.warning("password %s", password) # $ Alert diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/serialization/__init__.py b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/serialization/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/serialization/serialized_objects.py b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/serialization/serialized_objects.py new file mode 100644 index 000000000000..af6635adfe48 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/serialization/serialized_objects.py @@ -0,0 +1,19 @@ +import logging + + +class SerializedBaseOperator: + @classmethod + def _deserialize_deps(cls, password): # $ Source + qn = password + logging.warning("Error importing dep %r", qn) + other_qn = password + logging.warning("Other dep %r", other_qn) # $ Alert + logging.warning("Password %r", password) # $ Alert + + @classmethod + def _deserialize_operator_extra_links(cls, password): # $ Source + _operator_link_class_path = password + logging.error("Operator Link class %r not registered", _operator_link_class_path) + other_class_path = password + logging.error("Other class %r not registered", other_class_path) # $ Alert + logging.error("Password %r", password) # $ Alert diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/settings.py b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/settings.py new file mode 100644 index 000000000000..28df5a1e660a --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/settings.py @@ -0,0 +1,15 @@ +def get_password(): + return "secret" + + +class Conf: + def get_mandatory_value(self, section, key): + return get_password() # $ Source + + +conf = Conf() + +print(conf.get_mandatory_value("core", "DAGS_FOLDER")) +print(conf.get_mandatory_value("core", "FERNET_KEY")) # $ Alert +other = Conf() +print(other.get_mandatory_value("core", "DAGS_FOLDER")) # $ Alert diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/utils/__init__.py b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/utils/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/utils/db.py b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/utils/db.py new file mode 100644 index 000000000000..0888717e523b --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/airflow/utils/db.py @@ -0,0 +1,17 @@ +import logging + + +def _check_migration_errors(password): + return [password] + + +def upgradedb(password): # $ Source + for err in _check_migration_errors(password): + logging.error("%s", err) + logging.error("password in check %s", password) # $ Alert + for err in _check_migration_errors(password): + for err in [password]: + logging.error("nested %s", err) # $ Alert + for err in [password]: + logging.error("other %s", err) # $ Alert + logging.error("password %s", password) # $ Alert diff --git a/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/options b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/options new file mode 100644 index 000000000000..51401c7abfe8 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-312-CleartextLoggingAirflow/options @@ -0,0 +1 @@ +semmle-extractor-options: --max-import-depth=1 -r airflow