Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
47fac88
Azure SDK models for SSRF analysis.
bdrodes Sep 26, 2025
d27d4fd
Updating comments.
bdrodes Sep 30, 2025
704e296
Adding azure sdk test cases and updated test expected file.
bdrodes Sep 30, 2025
341f553
Added change logs.
bdrodes Sep 30, 2025
5ca9ff2
Update python/ql/lib/semmle/python/frameworks/SSRFSink.qll
bdrodes Sep 30, 2025
fab96d9
Update python/ql/test/query-tests/Security/CWE-918-ServerSideRequestF…
bdrodes Sep 30, 2025
d790c6d
Update python/ql/test/query-tests/Security/CWE-918-ServerSideRequestF…
bdrodes Sep 30, 2025
acddb2c
Moved change log to correct location.
bdrodes Sep 30, 2025
a660eab
Adding docs.
bdrodes Sep 30, 2025
26b8a39
Adjusting acryonym for SSRF for casing standards.
bdrodes Sep 30, 2025
7ddfa80
Merge branch 'main' into azure_python_sdk_url_summary_upstream
bdrodes Feb 2, 2026
cd73dcf
Merge branch 'main' into azure_python_sdk_url_summary_upstream
bdrodes Feb 4, 2026
0a88425
Python: Altering SSRF MaD to use 'request-forgery' tag. Update to tes…
bdrodes Feb 4, 2026
ac1987f
Update python/ql/lib/change-notes/2025-09-30-azure_ssrf_models.md
bdrodes Feb 5, 2026
8459eec
Moving the SsrfSink concept into Concepts.qll, and renaming to HttpCl…
bdrodes Feb 6, 2026
9912aaa
Adding azure sdk test cases and updated test expected file.
bdrodes Sep 30, 2025
b8ba905
Added change logs.
bdrodes Sep 30, 2025
46a2a24
Update python/ql/test/query-tests/Security/CWE-918-ServerSideRequestF…
bdrodes Sep 30, 2025
08b72d0
Update python/ql/test/query-tests/Security/CWE-918-ServerSideRequestF…
bdrodes Sep 30, 2025
7db9779
Moved change log to correct location.
bdrodes Sep 30, 2025
265922d
Adding docs.
bdrodes Sep 30, 2025
88adb05
Adjusting acryonym for SSRF for casing standards.
bdrodes Sep 30, 2025
27e1981
Removing an upstream change log, not needed for local fork update.
bdrodes Feb 2, 2026
97ddab0
Added support for new URIValidator in AntiSSRF library. Updated test …
bdrodes Feb 2, 2026
97f19d0
Updating test case expected alerts.
bdrodes Feb 2, 2026
42f6e6a
Fixing inefficiently passed variable in nested existential quantifica…
bdrodes Feb 3, 2026
4f11913
removing SSRFSink.qll
bdrodes Feb 6, 2026
f6c302b
Removing commented out test cases.
bdrodes Feb 6, 2026
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
29 changes: 29 additions & 0 deletions python/ql/lib/semmle/python/Concepts.qll
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

private import python
private import semmle.python.dataflow.new.DataFlow

Check warning

Code scanning / CodeQL

Redundant import Warning

Redundant import, the module is already imported inside
semmle.python.ApiGraphs
.
private import semmle.python.dataflow.new.internal.DataFlowImplSpecific
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
Expand All @@ -15,6 +15,8 @@
private import semmle.python.dataflow.new.SensitiveDataSources
private import codeql.threatmodels.ThreatModels
private import codeql.concepts.ConceptsShared
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.data.ModelsAsData

private module ConceptsShared = ConceptsMake<Location, PythonDataFlow>;

Expand Down Expand Up @@ -1656,8 +1658,35 @@
}

import ConceptsShared::Http::Client as Client

// TODO: investigate whether we should treat responses to client requests as
// remote-flow-sources in general.
/**
* An HTTP request modeled from `request-forgery` sinks, modeled using MaD.
*/
class HttpClientRequestFromModel extends Http::Client::Request::Range instanceof API::CallNode {
DataFlow::Node urlArg;

HttpClientRequestFromModel() {
(
this.getArg(_) = urlArg
or
this.getArgByName(_) = urlArg
) and
ModelOutput::sinkNode(urlArg, "request-forgery")
}

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()
}
}
}

/**
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:]', 'request-forgery']
- ['azure.keyvault.certificates.DeletedCertificate!', 'Call.Argument[recovery_id:]', 'request-forgery']
- ['azure.keyvault.keys.KeyClient!', 'Call.Argument[0,vault_url:]', 'request-forgery']
- ['azure.keyvault.secrets.SecretClient!', 'Call.Argument[0,vault_url:]', 'request-forgery']
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:]', 'request-forgery']
- ['azure.storage.blob.BlobClient', 'Member[append_block_from_url].Argument[0,copy_source_url:]', 'request-forgery']
- ['azure.storage.blob.BlobClient', 'Member[get_page_range_diff_for_managed_disk].Argument[0,previous_snapshot_url:]', 'request-forgery']
- ['azure.storage.blob.BlobClient', 'Member[stage_block_from_url].Argument[1,source_url:]', 'request-forgery']
- ['azure.storage.blob.BlobClient', 'Member[start_copy_from_url].Argument[0,source_url:]', 'request-forgery']
- ['azure.storage.blob.BlobClient', 'Member[upload_blob_from_url].Argument[0,source_url:]', 'request-forgery']
- ['azure.storage.blob.BlobClient', 'Member[upload_pages_from_url].Argument[0,source_url:]', 'request-forgery']
- ['azure.storage.blob.BlobClient!', 'Member[from_blob_url].Argument[0,blob_url:]', 'request-forgery']
- ['azure.storage.blob.BlobServiceClient!', 'Call.Argument[0,account_url:]', 'request-forgery']
- ['azure.storage.blob.ContainerClient!', 'Call.Argument[0,account_url:]', 'request-forgery']
- ['azure.storage.blob.ContainerClient!', 'Member[from_container_url].Argument[0,container_url:]', 'request-forgery']
- ['azure', 'Member[storage].Member[blob].Member[download_blob_from_url].Argument[0,blob_url:]', 'request-forgery']
- ['azure', 'Member[storage].Member[blob].Member[upload_blob_to_url].Argument[0,blob_url:]', 'request-forgery']
- ['azure.storage.filedatalake.DataLakeDirectoryClient!', 'Call.Argument[0,account_url:]', 'request-forgery']
- ['azure.storage.filedatalake.DataLakeFileClient!', 'Call.Argument[0,account_url:]', 'request-forgery']
- ['azure.storage.filedatalake.DataLakeServiceClient!', 'Call.Argument[0,account_url:]', 'request-forgery']
- ['azure.storage.filedatalake.FileSystemClient!', 'Call.Argument[0,account_url:]', 'request-forgery']
- ['azure.storage.fileshare.ShareClient!', 'Call.Argument[0,account_url:]', 'request-forgery']
- ['azure.storage.fileshare.ShareClient!', 'Member[from_share_url].Argument[0,share_url:]', 'request-forgery']
- ['azure.storage.fileshare.ShareDirectoryClient!', 'Call.Argument[0,account_url:]', 'request-forgery']
- ['azure.storage.fileshare.ShareDirectoryClient!', 'Member[from_directory_url].Argument[0,directory_url:]', 'request-forgery']
- ['azure.storage.fileshare.ShareFileClient!', 'Call.Argument[0,account_url:]', 'request-forgery']
- ['azure.storage.fileshare.ShareFileClient!', 'Member[from_file_url].Argument[0,file_url:]', 'request-forgery']
- ['azure.storage.fileshare.ShareFileClient', 'Member[start_copy_from_url].Argument[0,source_url:]', 'request-forgery']
- ['azure.storage.fileshare.ShareFileClient', 'Member[upload_range_from_url].Argument[0,source_url:]', 'request-forgery']
- ['azure.storage.fileshare.ShareServiceClient!', 'Call.Argument[0,account_url:]', 'request-forgery']
- ['azure.storage.queue.QueueClient!', 'Call.Argument[0,account_url:]', 'request-forgery']
- ['azure.storage.queue.QueueClient', 'Member[from_queue_url].Argument[0,queue_url:]', 'request-forgery']
- ['azure.storage.queue.QueueServiceClient!', 'Call.Argument[0,account_url:]', 'request-forgery']
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