Skip to content

Commit 683985a

Browse files
committed
Python: Expand azure blob modeling
Now we can differentiate between the classes
1 parent 8ea6b6f commit 683985a

File tree

3 files changed

+122
-28
lines changed

3 files changed

+122
-28
lines changed

python/ql/src/experimental/Security/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,100 @@ import python
1515
import semmle.python.dataflow.new.DataFlow
1616
import semmle.python.ApiGraphs
1717

18-
API::Node getClient() {
18+
API::Node getBlobServiceClient() {
1919
result =
2020
API::moduleImport("azure")
2121
.getMember("storage")
2222
.getMember("blob")
23-
.getMember(["ContainerClient", "BlobClient", "BlobServiceClient"])
24-
.getAMember()
23+
.getMember("BlobServiceClient")
24+
.getReturn()
25+
or
26+
result =
27+
API::moduleImport("azure")
28+
.getMember("storage")
29+
.getMember("blob")
30+
.getMember("BlobServiceClient")
31+
.getMember("from_connection_string")
32+
.getReturn()
33+
}
34+
35+
API::CallNode getTransitionToContainerClient() {
36+
result = getBlobServiceClient().getMember("get_container_client").getACall()
37+
or
38+
result = getBlobClient().getMember("_get_container_client").getACall()
39+
}
40+
41+
API::Node getContainerClient() {
42+
result = getTransitionToContainerClient().getReturn()
43+
or
44+
result =
45+
API::moduleImport("azure")
46+
.getMember("storage")
47+
.getMember("blob")
48+
.getMember("ContainerClient")
49+
.getReturn()
50+
or
51+
result =
52+
API::moduleImport("azure")
53+
.getMember("storage")
54+
.getMember("blob")
55+
.getMember("ContainerClient")
56+
.getMember(["from_connection_string", "from_container_url"])
2557
.getReturn()
2658
}
2759

60+
API::CallNode getTransitionToBlobClient() {
61+
result = [getBlobServiceClient(), getContainerClient()].getMember("get_blob_client").getACall()
62+
}
63+
64+
API::Node getBlobClient() {
65+
result = getTransitionToBlobClient().getReturn()
66+
or
67+
result =
68+
API::moduleImport("azure")
69+
.getMember("storage")
70+
.getMember("blob")
71+
.getMember("BlobClient")
72+
.getReturn()
73+
or
74+
result =
75+
API::moduleImport("azure")
76+
.getMember("storage")
77+
.getMember("blob")
78+
.getMember("BlobClient")
79+
.getMember(["from_connection_string", "from_blob_url"])
80+
.getReturn()
81+
}
82+
83+
API::Node anyClient() { result in [getBlobServiceClient(), getContainerClient(), getBlobClient()] }
84+
2885
module AzureBlobClientConfig implements DataFlow::ConfigSig {
2986
predicate isSource(DataFlow::Node node) {
3087
exists(DataFlow::AttrWrite attr |
31-
node = getClient().getAValueReachableFromSource() and
88+
node = anyClient().getAValueReachableFromSource() and
3289
attr.accesses(node, ["key_encryption_key", "key_resolver_function"])
3390
)
3491
}
3592

3693
predicate isBarrier(DataFlow::Node node) {
3794
exists(DataFlow::AttrWrite attr |
38-
node = getClient().getAValueReachableFromSource() and
95+
node = anyClient().getAValueReachableFromSource() and
3996
attr.accesses(node, "encryption_version") and
4097
attr.getValue().asExpr().(StrConst).getText() in ["'2.0'", "2.0"]
4198
)
4299
}
43100

101+
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
102+
exists(DataFlow::MethodCallNode call |
103+
call in [getTransitionToContainerClient(), getTransitionToBlobClient()] and
104+
node1 = call.getObject() and
105+
node2 = call
106+
)
107+
}
108+
44109
predicate isSink(DataFlow::Node node) {
45110
exists(DataFlow::MethodCallNode call |
46-
call = getClient().getMember("upload_blob").getACall() and
111+
call = getBlobClient().getMember("upload_blob").getACall() and
47112
node = call.getObject()
48113
)
49114
}
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
edges
2-
| test.py:8:5:8:15 | ControlFlowNode for blob_client | test.py:10:9:10:19 | ControlFlowNode for blob_client |
3-
| test.py:16:5:16:15 | ControlFlowNode for blob_client | test.py:22:9:22:19 | ControlFlowNode for blob_client |
4-
| test.py:38:5:38:15 | ControlFlowNode for blob_client | test.py:39:12:39:22 | ControlFlowNode for blob_client |
5-
| test.py:39:12:39:22 | ControlFlowNode for blob_client | test.py:43:10:43:33 | ControlFlowNode for get_unsafe_blob_client() |
6-
| test.py:43:10:43:33 | ControlFlowNode for get_unsafe_blob_client() | test.py:45:9:45:10 | ControlFlowNode for bc |
2+
| test.py:9:5:9:15 | ControlFlowNode for blob_client | test.py:11:9:11:19 | ControlFlowNode for blob_client |
3+
| test.py:17:5:17:23 | ControlFlowNode for blob_service_client | test.py:21:9:21:19 | ControlFlowNode for blob_client |
4+
| test.py:27:5:27:20 | ControlFlowNode for container_client | test.py:31:9:31:19 | ControlFlowNode for blob_client |
5+
| test.py:37:5:37:15 | ControlFlowNode for blob_client | test.py:43:9:43:19 | ControlFlowNode for blob_client |
6+
| test.py:59:5:59:15 | ControlFlowNode for blob_client | test.py:60:12:60:22 | ControlFlowNode for blob_client |
7+
| test.py:60:12:60:22 | ControlFlowNode for blob_client | test.py:64:10:64:33 | ControlFlowNode for get_unsafe_blob_client() |
8+
| test.py:64:10:64:33 | ControlFlowNode for get_unsafe_blob_client() | test.py:66:9:66:10 | ControlFlowNode for bc |
79
nodes
8-
| test.py:8:5:8:15 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client |
9-
| test.py:10:9:10:19 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client |
10-
| test.py:16:5:16:15 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client |
11-
| test.py:22:9:22:19 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client |
12-
| test.py:38:5:38:15 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client |
13-
| test.py:39:12:39:22 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client |
14-
| test.py:43:10:43:33 | ControlFlowNode for get_unsafe_blob_client() | semmle.label | ControlFlowNode for get_unsafe_blob_client() |
15-
| test.py:45:9:45:10 | ControlFlowNode for bc | semmle.label | ControlFlowNode for bc |
10+
| test.py:9:5:9:15 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client |
11+
| test.py:11:9:11:19 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client |
12+
| test.py:17:5:17:23 | ControlFlowNode for blob_service_client | semmle.label | ControlFlowNode for blob_service_client |
13+
| test.py:21:9:21:19 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client |
14+
| test.py:27:5:27:20 | ControlFlowNode for container_client | semmle.label | ControlFlowNode for container_client |
15+
| test.py:31:9:31:19 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client |
16+
| test.py:37:5:37:15 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client |
17+
| test.py:43:9:43:19 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client |
18+
| test.py:59:5:59:15 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client |
19+
| test.py:60:12:60:22 | ControlFlowNode for blob_client | semmle.label | ControlFlowNode for blob_client |
20+
| test.py:64:10:64:33 | ControlFlowNode for get_unsafe_blob_client() | semmle.label | ControlFlowNode for get_unsafe_blob_client() |
21+
| test.py:66:9:66:10 | ControlFlowNode for bc | semmle.label | ControlFlowNode for bc |
1622
subpaths
1723
#select
18-
| test.py:10:9:10:19 | ControlFlowNode for blob_client | test.py:8:5:8:15 | ControlFlowNode for blob_client | test.py:10:9:10:19 | ControlFlowNode for blob_client | Unsafe usage of v1 version of Azure Storage client-side encryption |
19-
| test.py:22:9:22:19 | ControlFlowNode for blob_client | test.py:16:5:16:15 | ControlFlowNode for blob_client | test.py:22:9:22:19 | ControlFlowNode for blob_client | Unsafe usage of v1 version of Azure Storage client-side encryption |
20-
| test.py:45:9:45:10 | ControlFlowNode for bc | test.py:38:5:38:15 | ControlFlowNode for blob_client | test.py:45:9:45:10 | ControlFlowNode for bc | Unsafe usage of v1 version of Azure Storage client-side encryption |
24+
| test.py:11:9:11:19 | ControlFlowNode for blob_client | test.py:9:5:9:15 | ControlFlowNode for blob_client | test.py:11:9:11:19 | ControlFlowNode for blob_client | Unsafe usage of v1 version of Azure Storage client-side encryption |
25+
| test.py:21:9:21:19 | ControlFlowNode for blob_client | test.py:17:5:17:23 | ControlFlowNode for blob_service_client | test.py:21:9:21:19 | ControlFlowNode for blob_client | Unsafe usage of v1 version of Azure Storage client-side encryption |
26+
| test.py:31:9:31:19 | ControlFlowNode for blob_client | test.py:27:5:27:20 | ControlFlowNode for container_client | test.py:31:9:31:19 | ControlFlowNode for blob_client | Unsafe usage of v1 version of Azure Storage client-side encryption |
27+
| test.py:43:9:43:19 | ControlFlowNode for blob_client | test.py:37:5:37:15 | ControlFlowNode for blob_client | test.py:43:9:43:19 | ControlFlowNode for blob_client | Unsafe usage of v1 version of Azure Storage client-side encryption |
28+
| test.py:66:9:66:10 | ControlFlowNode for bc | test.py:59:5:59:15 | ControlFlowNode for blob_client | test.py:66:9:66:10 | ControlFlowNode for bc | Unsafe usage of v1 version of Azure Storage client-side encryption |

python/ql/test/experimental/query-tests/Security/CWE-327-UnsafeUsageOfClientSideEncryptionVersion/test.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,38 @@
1-
from azure.storage.blob import BlobServiceClient
1+
from azure.storage.blob import BlobServiceClient, ContainerClient, BlobClient
22

3+
BSC = BlobServiceClient.from_connection_string(...)
34

45
def unsafe():
56
# does not set encryption_version to 2.0, default is unsafe
6-
blob_client = BlobServiceClient.get_blob_client(...)
7+
blob_client = BSC.get_blob_client(...)
78
blob_client.require_encryption = True
89
blob_client.key_encryption_key = ...
910
with open("decryptedcontentfile.txt", "rb") as stream:
1011
blob_client.upload_blob(stream) # BAD
1112

1213

14+
def unsafe_setting_on_blob_service_client():
15+
blob_service_client = BlobServiceClient.from_connection_string(...)
16+
blob_service_client.require_encryption = True
17+
blob_service_client.key_encryption_key = ...
18+
19+
blob_client = blob_service_client.get_blob_client(...)
20+
with open("decryptedcontentfile.txt", "rb") as stream:
21+
blob_client.upload_blob(stream)
22+
23+
24+
def unsafe_setting_on_container_client():
25+
container_client = ContainerClient.from_connection_string(...)
26+
container_client.require_encryption = True
27+
container_client.key_encryption_key = ...
28+
29+
blob_client = container_client.get_blob_client(...)
30+
with open("decryptedcontentfile.txt", "rb") as stream:
31+
blob_client.upload_blob(stream)
32+
33+
1334
def potentially_unsafe(use_new_version=False):
14-
blob_client = BlobServiceClient.get_blob_client(...)
35+
blob_client = BSC.get_blob_client(...)
1536
blob_client.require_encryption = True
1637
blob_client.key_encryption_key = ...
1738

@@ -23,7 +44,7 @@ def potentially_unsafe(use_new_version=False):
2344

2445

2546
def safe():
26-
blob_client = BlobServiceClient.get_blob_client(...)
47+
blob_client = BSC.get_blob_client(...)
2748
blob_client.require_encryption = True
2849
blob_client.key_encryption_key = ...
2950
# GOOD: Must use `encryption_version` set to `2.0`
@@ -33,7 +54,7 @@ def safe():
3354

3455

3556
def get_unsafe_blob_client():
36-
blob_client = BlobServiceClient.get_blob_client(...)
57+
blob_client = BSC.get_blob_client(...)
3758
blob_client.require_encryption = True
3859
blob_client.key_encryption_key = ...
3960
return blob_client
@@ -46,7 +67,7 @@ def unsafe_with_calls():
4667

4768

4869
def get_safe_blob_client():
49-
blob_client = BlobServiceClient.get_blob_client(...)
70+
blob_client = BSC.get_blob_client(...)
5071
blob_client.require_encryption = True
5172
blob_client.key_encryption_key = ...
5273
blob_client.encryption_version = '2.0'

0 commit comments

Comments
 (0)