Skip to content
Open
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
1 change: 1 addition & 0 deletions python/ql/lib/semmle/python/Frameworks.qll
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ private import semmle.python.frameworks.Setuptools
private import semmle.python.frameworks.Simplejson
private import semmle.python.frameworks.Socketio
private import semmle.python.frameworks.SqlAlchemy
private import semmle.python.frameworks.SSRFSink
private import semmle.python.frameworks.Starlette
private import semmle.python.frameworks.Stdlib
private import semmle.python.frameworks.Streamlit
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extensions:
- addsTo:
pack: codeql/python-all
extensible: sinkModel
data:
- ['azure.keyvault.certificates.CertificateClient!', 'Call.Argument[0,vault_url:]', 'ssrf']
- ['azure.keyvault.certificates.DeletedCertificate!', 'Call.Argument[recovery_id:]', 'ssrf']
- ['azure.keyvault.keys.KeyClient!', 'Call.Argument[0,vault_url:]', 'ssrf']
- ['azure.keyvault.secrets.SecretClient!', 'Call.Argument[0,vault_url:]', 'ssrf']
34 changes: 34 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Azure.Storage.model.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
extensions:
- addsTo:
pack: codeql/python-all
extensible: sinkModel
data:
- ['azure.storage.blob.BlobClient!', 'Call.Argument[0,account_url:]', 'ssrf']
- ['azure.storage.blob.BlobClient', 'Member[append_block_from_url].Argument[0,copy_source_url:]', 'ssrf']
- ['azure.storage.blob.BlobClient', 'Member[get_page_range_diff_for_managed_disk].Argument[0,previous_snapshot_url:]', 'ssrf']
- ['azure.storage.blob.BlobClient', 'Member[stage_block_from_url].Argument[1,source_url:]', 'ssrf']
- ['azure.storage.blob.BlobClient', 'Member[start_copy_from_url].Argument[0,source_url:]', 'ssrf']
- ['azure.storage.blob.BlobClient', 'Member[upload_blob_from_url].Argument[0,source_url:]', 'ssrf']
- ['azure.storage.blob.BlobClient', 'Member[upload_pages_from_url].Argument[0,source_url:]', 'ssrf']
- ['azure.storage.blob.BlobClient!', 'Member[from_blob_url].Argument[0,blob_url:]', 'ssrf']
- ['azure.storage.blob.BlobServiceClient!', 'Call.Argument[0,account_url:]', 'ssrf']
- ['azure.storage.blob.ContainerClient!', 'Call.Argument[0,account_url:]', 'ssrf']
- ['azure.storage.blob.ContainerClient!', 'Member[from_container_url].Argument[0,container_url:]', 'ssrf']
- ['azure', 'Member[storage].Member[blob].Member[download_blob_from_url].Argument[0,blob_url:]', 'ssrf']
- ['azure', 'Member[storage].Member[blob].Member[upload_blob_to_url].Argument[0,blob_url:]', 'ssrf']
- ['azure.storage.filedatalake.DataLakeDirectoryClient!', 'Call.Argument[0,account_url:]', 'ssrf']
- ['azure.storage.filedatalake.DataLakeFileClient!', 'Call.Argument[0,account_url:]', 'ssrf']
- ['azure.storage.filedatalake.DataLakeServiceClient!', 'Call.Argument[0,account_url:]', 'ssrf']
- ['azure.storage.filedatalake.FileSystemClient!', 'Call.Argument[0,account_url:]', 'ssrf']
- ['azure.storage.fileshare.ShareClient!', 'Call.Argument[0,account_url:]', 'ssrf']
- ['azure.storage.fileshare.ShareClient!', 'Member[from_share_url].Argument[0,share_url:]', 'ssrf']
- ['azure.storage.fileshare.ShareDirectoryClient!', 'Call.Argument[0,account_url:]', 'ssrf']
- ['azure.storage.fileshare.ShareDirectoryClient!', 'Member[from_directory_url].Argument[0,directory_url:]', 'ssrf']
- ['azure.storage.fileshare.ShareFileClient!', 'Call.Argument[0,account_url:]', 'ssrf']
- ['azure.storage.fileshare.ShareFileClient!', 'Member[from_file_url].Argument[0,file_url:]', 'ssrf']
- ['azure.storage.fileshare.ShareFileClient', 'Member[start_copy_from_url].Argument[0,source_url:]', 'ssrf']
- ['azure.storage.fileshare.ShareFileClient', 'Member[upload_range_from_url].Argument[0,source_url:]', 'ssrf']
- ['azure.storage.fileshare.ShareServiceClient!', 'Call.Argument[0,account_url:]', 'ssrf']
- ['azure.storage.queue.QueueClient!', 'Call.Argument[0,account_url:]', 'ssrf']
- ['azure.storage.queue.QueueClient', 'Member[from_queue_url].Argument[0,queue_url:]', 'ssrf']
- ['azure.storage.queue.QueueServiceClient!', 'Call.Argument[0,account_url:]', 'ssrf']
42 changes: 42 additions & 0 deletions python/ql/lib/semmle/python/frameworks/SSRFSink.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Provides classes for SSRF sinks modeled using Models as Data (MaD).
*/

private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.data.ModelsAsData

/**
* INTERNAL: Do not use.
*
* Sets up SSRF sinks as Http::Client::Request
*/
module SsrfMaDModel {
/**
* An HTTP request modeled from `ssrf` sinks, modeled using MaD.
*/
class SsrfSink extends Http::Client::Request::Range instanceof API::CallNode {
DataFlow::Node urlArg;

SsrfSink() {
(
this.getArg(_) = urlArg
or
this.getArgByName(_) = urlArg
) and
urlArg = ModelOutput::getASinkNode("ssrf").asSink()
}

override DataFlow::Node getAUrlPart() { result = urlArg }

override string getFramework() { result = "MaD" }

override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
// NOTE: if you need to define this, you have to special case it for every possible API in MaD
none()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,48 @@ module ServerSideRequestForgery {
strNode = [call.getArg(0), call.getArgByName("string")]
)
}

/** A validation that a string does not contain certain characters, considered as a sanitizer. */
private class UriValidator extends FullUrlControlSanitizer {
UriValidator() { this = DataFlow::BarrierGuard<uri_validator/3>::getABarrierNode() }
}

import semmle.python.dataflow.new.internal.DataFlowPublic

private predicate uri_validator(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) {
exists(DataFlow::CallCfgNode call, string funcs |
funcs in ["in_domain", "in_azure_keyvault_domain", "in_azure_storage_domain"]
|
call = API::moduleImport("AntiSSRF").getMember("URIValidator").getMember(funcs).getACall() and
call.getArg(0).asCfgNode() = node and
(
// validator used in a comparison
exists(CompareNode cn, Cmpop op, Node n | cn = g and n.getALocalSource() = call |
(
// validator == true or validator == false or validator is True or validator is False
(op instanceof Eq or op instanceof Is) and
exists(ControlFlowNode l, boolean bool |
l.getNode().(BooleanLiteral).booleanValue() = bool and
bool in [true, false] and
branch = bool and
cn.operands(n.asCfgNode(), op, l)
)
or
// validator != false or validator != true or validator is not True or validator is not False
(op instanceof NotEq or op instanceof IsNot) and
exists(ControlFlowNode l, boolean bool |
l.getNode().(BooleanLiteral).booleanValue() = bool and
bool in [true, false] and
branch = bool.booleanNot() and
cn.operands(n.asCfgNode(), op, l)
)
)
)
or
// validator call directly (e.g., if URIValidator.in_domain(...) )
g = call.asCfgNode() and
branch = true
)
)
}
}
Loading