Skip to content

Commit a850a48

Browse files
authored
Merge pull request #13676 from RasmusWL/aiohttp-ssrf-sink
Python: Relax restriction of flow through `async with`
2 parents b14cac6 + 30cf213 commit a850a48

File tree

3 files changed

+41
-27
lines changed

3 files changed

+41
-27
lines changed

python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,12 @@ module EssaFlow {
304304
// see `with_flow` in `python/ql/src/semmle/python/dataflow/Implementation.qll`
305305
with.getContextExpr() = contextManager.getNode() and
306306
with.getOptionalVars() = var.getNode() and
307-
not with.isAsync() and
308307
contextManager.strictlyDominates(var)
308+
// note: we allow this for both `with` and `async with`, since some
309+
// implementations do `async def __aenter__(self): return self`, so you can do
310+
// both:
311+
// * `foo = x.foo(); await foo.async_method(); foo.close()` and
312+
// * `async with x.foo() as foo: await foo.async_method()`.
309313
)
310314
or
311315
// Async with var definition
@@ -314,6 +318,12 @@ module EssaFlow {
314318
// nodeTo is `x`, essa var
315319
//
316320
// This makes the cfg node the local source of the awaited value.
321+
//
322+
// We have this step in addition to the step above, to handle cases where the QL
323+
// modeling of `f(42)` requires a `.getAwaited()` step (in API graphs) when not
324+
// using `async with`, so you can do both:
325+
// * `foo = await x.foo(); await foo.async_method(); foo.close()` and
326+
// * `async with x.foo() as foo: await foo.async_method()`.
317327
exists(With with, ControlFlowNode var |
318328
nodeFrom.(CfgNode).getNode() = var and
319329
nodeTo.(EssaNode).getVar().getDefinition().(WithDefinition).getDefiningNode() = var and
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Fixed modeling of `aiohttp.ClientSession` so we properly handle `async with` uses. This can impact results of server-side request forgery queries (`py/full-ssrf`, `py/partial-ssrf`).
Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
11
import aiohttp
2-
import asyncio
32
import ssl
43

5-
s = aiohttp.ClientSession()
6-
resp = s.request("method", "url") # $ clientRequestUrlPart="url"
7-
resp = s.request("method", url="url") # $ clientRequestUrlPart="url"
4+
async def test():
5+
s = aiohttp.ClientSession()
6+
resp = await s.request("method", "url") # $ clientRequestUrlPart="url"
7+
resp = await s.request("method", url="url") # $ clientRequestUrlPart="url"
88

9-
with aiohttp.ClientSession() as session:
10-
resp = session.get("url") # $ clientRequestUrlPart="url"
11-
resp = session.request(method="GET", url="url") # $ clientRequestUrlPart="url"
9+
async with aiohttp.ClientSession() as session:
10+
resp = await session.get("url") # $ clientRequestUrlPart="url"
11+
resp = await session.request(method="GET", url="url") # $ clientRequestUrlPart="url"
1212

13-
# other methods than GET
14-
s = aiohttp.ClientSession()
15-
resp = s.post("url") # $ clientRequestUrlPart="url"
16-
resp = s.patch("url") # $ clientRequestUrlPart="url"
17-
resp = s.options("url") # $ clientRequestUrlPart="url"
13+
# other methods than GET
14+
s = aiohttp.ClientSession()
15+
resp = await s.post("url") # $ clientRequestUrlPart="url"
16+
resp = await s.patch("url") # $ clientRequestUrlPart="url"
17+
resp = await s.options("url") # $ clientRequestUrlPart="url"
1818

19-
# disabling of SSL validation
20-
# see https://docs.aiohttp.org/en/stable/client_reference.html#aiohttp.ClientSession.request
21-
s.get("url", ssl=False) # $ clientRequestUrlPart="url" clientRequestCertValidationDisabled
22-
s.get("url", ssl=0) # $ clientRequestUrlPart="url" clientRequestCertValidationDisabled
23-
# None is treated as default and so does _not_ disable the check
24-
s.get("url", ssl=None) # $ clientRequestUrlPart="url"
19+
# disabling of SSL validation
20+
# see https://docs.aiohttp.org/en/stable/client_reference.html#aiohttp.ClientSession.request
21+
s.get("url", ssl=False) # $ clientRequestUrlPart="url" clientRequestCertValidationDisabled
22+
s.get("url", ssl=0) # $ clientRequestUrlPart="url" clientRequestCertValidationDisabled
23+
# None is treated as default and so does _not_ disable the check
24+
s.get("url", ssl=None) # $ clientRequestUrlPart="url"
2525

26-
# deprecated since 3.0, but still supported
27-
s.get("url", verify_ssl=False) # $ clientRequestUrlPart="url" clientRequestCertValidationDisabled
26+
# deprecated since 3.0, but still supported
27+
s.get("url", verify_ssl=False) # $ clientRequestUrlPart="url" clientRequestCertValidationDisabled
2828

29-
# A manually constructed SSLContext does not have safe defaults, so is effectively the
30-
# same as turning off SSL validation
31-
context = ssl.SSLContext()
32-
assert context.check_hostname == False
33-
assert context.verify_mode == ssl.VerifyMode.CERT_NONE
29+
# A manually constructed SSLContext does not have safe defaults, so is effectively the
30+
# same as turning off SSL validation
31+
context = ssl.SSLContext()
32+
assert context.check_hostname == False
33+
assert context.verify_mode == ssl.VerifyMode.CERT_NONE
3434

35-
s.get("url", ssl=context) # $ clientRequestUrlPart="url" MISSING: clientRequestCertValidationDisabled
35+
s.get("url", ssl=context) # $ clientRequestUrlPart="url" MISSING: clientRequestCertValidationDisabled

0 commit comments

Comments
 (0)