CVE-2026-40072
web3.py affected by SSRF via CCIP Read (EIP-3668) OffchainLookup URL handling
web3.py allows you to interact with the Ethereum blockchain using Python. From 6.0.0b3 to before 7.15.0 and 8.0.0b2, web3.py implements CCIP Read / OffchainLookup (EIP-3668) by performing HTTP requests to URLs supplied by smart contracts in offchain_lookup_payload["urls"]. The implementation uses these contract-supplied URLs directly (after {sender} / {data} template substitution) without any destination validation. CCIP Read is enabled by default (global_ccip_read_enabled = True on all providers), meaning any application using web3.py's .call() method is exposed without explicit opt-in. This results in Server-Side Request Forgery (SSRF) when web3.py is used in backend services, indexers, APIs, or any environment that performs eth_call / .call() against untrusted or user-supplied contract addresses. A malicious contract can force the web3.py process to issue HTTP requests to arbitrary destinations, including internal network services and cloud metadata endpoints. This vulnerability is fixed in 7.15.0 and 8.0.0b2.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
1.7
web3.py affected by SSRF via CCIP Read (EIP-3668) OffchainLookup URL handling
web3.py allows you to interact with the Ethereum blockchain using Python. From 6.0.0b3 to before 7.15.0 and 8.0.0b2, web3.py implements CCIP Read / OffchainLookup (EIP-3668) by performing HTTP requests to URLs supplied by smart contracts in offchain_lookup_payload["urls"]. The implementation uses these contract-supplied URLs directly (after {sender} / {data} template substitution) without any destination validation. CCIP Read is enabled by default (global_ccip_read_enabled = True on all providers), meaning any application using web3.py's .call() method is exposed without explicit opt-in. This results in Server-Side Request Forgery (SSRF) when web3.py is used in backend services, indexers, APIs, or any environment that performs eth_call / .call() against untrusted or user-supplied contract addresses. A malicious contract can force the web3.py process to issue HTTP requests to arbitrary destinations, including internal network services and cloud metadata endpoints. This vulnerability is fixed in 7.15.0 and 8.0.0b2.
- Record
CVE-2026-40072- CVSS 4.0
- 1.7 Low
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N/E:U - Weakness
- CWE-918
- Updated
- 2026-11-02
EUVDEUVD-2026-21000
1.7
web3.py allows you to interact with the Ethereum blockchain using Python. From 6.0.0b3 to before 7.15.0 and 8.0.0b2, web3.py implements CCIP Read / OffchainLookup (EIP-3668) by performing HTTP requests to URLs supplied by smart contracts in offchain_lookup_payload["urls"]. The implementation uses these contract-supplied URLs directly (after {sender} / {data} template substitution) without any destination validation. CCIP Read is enabled by default (global_ccip_read_enabled = True on all providers), meaning any application using web3.py's .call() method is exposed without explicit opt-in. This results in Server-Side Request Forgery (SSRF) when web3.py is used in backend services, indexers, APIs, or any environment that performs eth_call / .call() against untrusted or user-supplied contract addresses. A malicious contract can force the web3.py process to issue HTTP requests to arbitrary destinations, including internal network services and cloud metadata endpoints. This vulnerability is fixed in 7.15.0 and 8.0.0b2.
- Record
EUVD-2026-21000- CVSS 4.0
- 1.7 Low
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N/E:U - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-5hr4-253g-cpx2
6.9
web3.py: SSRF via CCIP Read (EIP-3668) OffchainLookup URL handling
Summary
web3.py implements CCIP Read / OffchainLookup (EIP-3668) by performing HTTP requests to URLs supplied by smart contracts in offchain_lookup_payload["urls"]. The implementation uses these contract-supplied URLs directly (after {sender} / {data} template substitution) without any destination validation:
- No restriction to
https://(and no opt-in gate forhttp://) - No hostname or IP allowlist
- No blocking of private/reserved IP ranges (loopback, link-local, RFC1918)
- No redirect target validation (both
requestsandaiohttpfollow redirects by default)
CCIP Read is enabled by default (global_ccip_read_enabled = True on all providers), meaning any application using web3.py's .call() method is exposed without explicit opt-in.
This results in Server-Side Request Forgery (SSRF) when web3.py is used in backend services, indexers, APIs, or any environment that performs eth_call / .call() against untrusted or user-supplied contract addresses. A malicious contract can force the web3.py process to issue HTTP requests to arbitrary destinations, including internal network services and cloud metadata endpoints.
Why This Is a Vulnerability
The argument is not that CCIP Read itself is invalid or that web3.py should stop supporting EIP-3668. The issue is that, in server-side deployments (backends, indexers, bots, APIs), the current implementation doesn't provide destination policy controls, such as a validation/override hook, private-range blocking, or redirect target checks, which means contract controlled CCIP URLs can be used as an SSRF primitive.
This is consistent with EIP-3668's own security considerations, which recommends that client libraries "provide clients with a hook to override CCIP read calls, either by rewriting them to use a proxy service, or by denying them entirely" and that "this mechanism or another should be written so as to easily facilitate adding domains to allowlists or blocklists." The mitigations I'm suggesting are meant to align with that guidance without breaking CCIP Read support.
-
Default-on exposure. CCIP Read is enabled by default on all web3.py providers (
global_ccip_read_enabled = True). Users who never intend to use CCIP Read, and who may not even know the feature exists, are silently exposed. A feature that makes unsanitized outbound requests to attacker-controlled URLs should not be enabled by default without safety guardrails. -
Library vs. application responsibility. web3.py is a widely-used library. Expecting every downstream application to independently implement SSRF protections around
.call()is unreasonable, especially for a feature that fires automatically and invisibly on a specific revert pattern. Safe defaults at the library level are the standard expectation for any library that issues outbound HTTP requests to externally-controlled URLs.
Affected Code
Sync CCIP handler
File: web3/utils/exception_handling.py (lines 42-58)
Contract-controlled URLs are requested via requests with no destination validation:
session = requests.Session()
for url in offchain_lookup_payload["urls"]:
formatted_url = URI(
str(url)
.replace("{sender}", str(formatted_sender))
.replace("{data}", str(formatted_data))
)
try:
if "{data}" in url and "{sender}" in url:
response = session.get(formatted_url, timeout=DEFAULT_HTTP_TIMEOUT)
else:
response = session.post(
formatted_url,
json={"data": formatted_data, "sender": formatted_sender},
timeout=DEFAULT_HTTP_TIMEOUT,
)
(The request is issued before response validation; subsequent logic parses JSON and enforces a "data" field.)
Key observations:
requestsfollows redirects by default (allow_redirects=True).- No
allow_redirects=Falseis set. - No validation of
formatted_urlbefore the request. - The placeholder check…
- Record
GHSA-5hr4-253g-cpx2- CVSS 4.0
- 6.9 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N - CVSS 3.1
- 7.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N - Weakness
- CWE-918
- Updated
- 2026-11-02
NVD
1.7
web3.py allows you to interact with the Ethereum blockchain using Python. From 6.0.0b3 to before 7.15.0 and 8.0.0b2, web3.py implements CCIP Read / OffchainLookup (EIP-3668) by performing HTTP requests to URLs supplied by smart contracts in offchain_lookup_payload["urls"]. The implementation uses these contract-supplied URLs directly (after {sender} / {data} template substitution) without any destination validation. CCIP Read is enabled by default (global_ccip_read_enabled = True on all providers), meaning any application using web3.py's .call() method is exposed without explicit opt-in. This results in Server-Side Request Forgery (SSRF) when web3.py is used in backend services, indexers, APIs, or any environment that performs eth_call / .call() against untrusted or user-supplied contract addresses. A malicious contract can force the web3.py process to issue HTTP requests to arbitrary destinations, including internal network services and cloud metadata endpoints. This vulnerability is fixed in 7.15.0 and 8.0.0b2.
- Record
CVE-2026-40072- CVSS 4.0
- 1.7 Low
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N/E:U/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 7.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N - Weakness
- CWE-918
- Updated
- 2026-11-02
OSVCVE-2026-111171
6.3
Web3.py 7.15.0 through 7.16.0 and 8.0.0b2 through 8.0.0 CCIP-Read SSRF Bypass
Web3.py 7.15.0 through 7.16.0 and 8.0.0b2 through 8.0.0 contains a server-side request forgery vulnerability because _check_ip_blocked() does not unwrap IPv4-mapped IPv6 addresses in CCIP Read URL validation. Malicious contracts can return OffchainLookup URLs with IPv4-mapped IPv6 hosts or AAAA records, making clients with CCIP Read enabled send requests to loopback, private, or link-local services.
- Record
CVE-2026-111171- CVSS 4.0
- 6.3 Medium
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N - Weakness
- CWE-918
- Updated
- 2026-11-02
OSV
1.7
web3.py affected by SSRF via CCIP Read (EIP-3668) OffchainLookup URL handling
web3.py allows you to interact with the Ethereum blockchain using Python. From 6.0.0b3 to before 7.15.0 and 8.0.0b2, web3.py implements CCIP Read / OffchainLookup (EIP-3668) by performing HTTP requests to URLs supplied by smart contracts in offchain_lookup_payload["urls"]. The implementation uses these contract-supplied URLs directly (after {sender} / {data} template substitution) without any destination validation. CCIP Read is enabled by default (global_ccip_read_enabled = True on all providers), meaning any application using web3.py's .call() method is exposed without explicit opt-in. This results in Server-Side Request Forgery (SSRF) when web3.py is used in backend services, indexers, APIs, or any environment that performs eth_call / .call() against untrusted or user-supplied contract addresses. A malicious contract can force the web3.py process to issue HTTP requests to arbitrary destinations, including internal network services and cloud metadata endpoints. This vulnerability is fixed in 7.15.0 and 8.0.0b2.
- Record
CVE-2026-40072- CVSS 4.0
- 1.7 Low
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N/E:U - Weakness
- CWE-918
- Updated
- 2026-11-02
OSVGHSA-5hr4-253g-cpx2
6.9
web3.py: SSRF via CCIP Read (EIP-3668) OffchainLookup URL handling
Summary
web3.py implements CCIP Read / OffchainLookup (EIP-3668) by performing HTTP requests to URLs supplied by smart contracts in offchain_lookup_payload["urls"]. The implementation uses these contract-supplied URLs directly (after {sender} / {data} template substitution) without any destination validation:
- No restriction to
https://(and no opt-in gate forhttp://) - No hostname or IP allowlist
- No blocking of private/reserved IP ranges (loopback, link-local, RFC1918)
- No redirect target validation (both
requestsandaiohttpfollow redirects by default)
CCIP Read is enabled by default (global_ccip_read_enabled = True on all providers), meaning any application using web3.py's .call() method is exposed without explicit opt-in.
This results in Server-Side Request Forgery (SSRF) when web3.py is used in backend services, indexers, APIs, or any environment that performs eth_call / .call() against untrusted or user-supplied contract addresses. A malicious contract can force the web3.py process to issue HTTP requests to arbitrary destinations, including internal network services and cloud metadata endpoints.
Why This Is a Vulnerability
The argument is not that CCIP Read itself is invalid or that web3.py should stop supporting EIP-3668. The issue is that, in server-side deployments (backends, indexers, bots, APIs), the current implementation doesn't provide destination policy controls, such as a validation/override hook, private-range blocking, or redirect target checks, which means contract controlled CCIP URLs can be used as an SSRF primitive.
This is consistent with EIP-3668's own security considerations, which recommends that client libraries "provide clients with a hook to override CCIP read calls, either by rewriting them to use a proxy service, or by denying them entirely" and that "this mechanism or another should be written so as to easily facilitate adding domains to allowlists or blocklists." The mitigations I'm suggesting are meant to align with that guidance without breaking CCIP Read support.
-
Default-on exposure. CCIP Read is enabled by default on all web3.py providers (
global_ccip_read_enabled = True). Users who never intend to use CCIP Read, and who may not even know the feature exists, are silently exposed. A feature that makes unsanitized outbound requests to attacker-controlled URLs should not be enabled by default without safety guardrails. -
Library vs. application responsibility. web3.py is a widely-used library. Expecting every downstream application to independently implement SSRF protections around
.call()is unreasonable, especially for a feature that fires automatically and invisibly on a specific revert pattern. Safe defaults at the library level are the standard expectation for any library that issues outbound HTTP requests to externally-controlled URLs.
Affected Code
Sync CCIP handler
File: web3/utils/exception_handling.py (lines 42-58)
Contract-controlled URLs are requested via requests with no destination validation:
session = requests.Session()
for url in offchain_lookup_payload["urls"]:
formatted_url = URI(
str(url)
.replace("{sender}", str(formatted_sender))
.replace("{data}", str(formatted_data))
)
try:
if "{data}" in url and "{sender}" in url:
response = session.get(formatted_url, timeout=DEFAULT_HTTP_TIMEOUT)
else:
response = session.post(
formatted_url,
json={"data": formatted_data, "sender": formatted_sender},
timeout=DEFAULT_HTTP_TIMEOUT,
)
(The request is issued before response validation; subsequent logic parses JSON and enforces a "data" field.)
Key observations:
requestsfollows redirects by default (allow_redirects=True).- No
allow_redirects=Falseis set. - No validation of
formatted_urlbefore the request. - The placeholder check…
- Record
GHSA-5hr4-253g-cpx2- CVSS 4.0
- 6.9 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N - CVSS 3.1
- 7.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N - Weakness
- CWE-918
- Updated
- 2026-11-02
OSVPYSEC-2026-3414
6.9
web3.py: SSRF via CCIP Read (EIP-3668) OffchainLookup URL handling
Summary
web3.py implements CCIP Read / OffchainLookup (EIP-3668) by performing HTTP requests to URLs supplied by smart contracts in offchain_lookup_payload["urls"]. The implementation uses these contract-supplied URLs directly (after {sender} / {data} template substitution) without any destination validation:
- No restriction to
https://(and no opt-in gate forhttp://) - No hostname or IP allowlist
- No blocking of private/reserved IP ranges (loopback, link-local, RFC1918)
- No redirect target validation (both
requestsandaiohttpfollow redirects by default)
CCIP Read is enabled by default (global_ccip_read_enabled = True on all providers), meaning any application using web3.py's .call() method is exposed without explicit opt-in.
This results in Server-Side Request Forgery (SSRF) when web3.py is used in backend services, indexers, APIs, or any environment that performs eth_call / .call() against untrusted or user-supplied contract addresses. A malicious contract can force the web3.py process to issue HTTP requests to arbitrary destinations, including internal network services and cloud metadata endpoints.
Why This Is a Vulnerability
The argument is not that CCIP Read itself is invalid or that web3.py should stop supporting EIP-3668. The issue is that, in server-side deployments (backends, indexers, bots, APIs), the current implementation doesn't provide destination policy controls, such as a validation/override hook, private-range blocking, or redirect target checks, which means contract controlled CCIP URLs can be used as an SSRF primitive.
This is consistent with EIP-3668's own security considerations, which recommends that client libraries "provide clients with a hook to override CCIP read calls, either by rewriting them to use a proxy service, or by denying them entirely" and that "this mechanism or another should be written so as to easily facilitate adding domains to allowlists or blocklists." The mitigations I'm suggesting are meant to align with that guidance without breaking CCIP Read support.
-
Default-on exposure. CCIP Read is enabled by default on all web3.py providers (
global_ccip_read_enabled = True). Users who never intend to use CCIP Read, and who may not even know the feature exists, are silently exposed. A feature that makes unsanitized outbound requests to attacker-controlled URLs should not be enabled by default without safety guardrails. -
Library vs. application responsibility. web3.py is a widely-used library. Expecting every downstream application to independently implement SSRF protections around
.call()is unreasonable, especially for a feature that fires automatically and invisibly on a specific revert pattern. Safe defaults at the library level are the standard expectation for any library that issues outbound HTTP requests to externally-controlled URLs.
Affected Code
Sync CCIP handler
File: web3/utils/exception_handling.py (lines 42-58)
Contract-controlled URLs are requested via requests with no destination validation:
session = requests.Session()
for url in offchain_lookup_payload["urls"]:
formatted_url = URI(
str(url)
.replace("{sender}", str(formatted_sender))
.replace("{data}", str(formatted_data))
)
try:
if "{data}" in url and "{sender}" in url:
response = session.get(formatted_url, timeout=DEFAULT_HTTP_TIMEOUT)
else:
response = session.post(
formatted_url,
json={"data": formatted_data, "sender": formatted_sender},
timeout=DEFAULT_HTTP_TIMEOUT,
)
(The request is issued before response validation; subsequent logic parses JSON and enforces a "data" field.)
Key observations:
requestsfollows redirects by default (allow_redirects=True).- No
allow_redirects=Falseis set. - No validation of
formatted_urlbefore the request. - The placeholder check…
- Record
PYSEC-2026-3414- CVSS 4.0
- 6.9 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N - CVSS 3.1
- 7.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
web3PyPI |
6.0.0b3 up to 7.15.0 | 7.15.0 |
web3PyPI |
8.0.0b1 to 8.0.0b1 | 8.0.0b2 |
web3PyPI From an advisory covering 2 CVEs |
6.0.0b3 up to 7.15.0, plus listed versions 6.0.06.0.0b106.0.0b116.0.0b46.0.0b5 Show 45 moreShow less6.0.0b66.0.0b76.0.0b86.0.0b96.1.06.10.06.11.06.11.16.11.26.11.36.11.46.12.06.13.06.14.06.15.06.15.16.16.06.17.06.17.16.17.26.18.06.19.06.2.06.20.06.20.16.20.26.20.36.20.46.3.06.4.06.5.06.6.06.6.16.7.06.8.06.9.07.0.07.0.0b17.0.0b27.0.0b37.0.0b47.0.0b57.0.0b67.0.0b77.0.0b8 |
7.15.0 |
web3PyPI From an advisory covering 2 CVEs |
8.0.0b1 up to 8.0.0b2 | 8.0.0b2 |
web3PyPI From an advisory covering 2 CVEs |
6.0.0b3 up to 7.15.0, 8.0.0b1 up to 8.0.0b2, plus listed versions 6.0.06.0.0b106.0.0b116.0.0b46.0.0b5 Show 45 moreShow less6.0.0b66.0.0b76.0.0b86.0.0b96.1.06.10.06.11.06.11.16.11.26.11.36.11.46.12.06.13.06.14.06.15.06.15.16.16.06.17.06.17.16.17.26.18.06.19.06.2.06.20.06.20.16.20.26.20.36.20.46.3.06.4.06.5.06.6.06.6.16.7.06.8.06.9.07.0.07.0.0b17.0.0b27.0.0b37.0.0b47.0.0b57.0.0b67.0.0b77.0.0b8 |
7.15.08.0.0b2 |
Public exploits
Links only. Every CVE stores no exploit code.
| Source | Link | Date |
|---|---|---|
| PoC in GitHub | u1tr0nex/cve-2026-40072-ssrf-lab | 2026-06-05 |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected, facts | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV, PoC in GitHub |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-40072 is a low vulnerability (CVSS 1.7) in Apeworx Web3.py, published April 4, 2026. Public exploit code exists. EPSS puts the chance of exploitation in the next 30 days at 0.24%, the 14th percentile. Fixed in web3 7.15.0.
References
github.com/ethereum/web3.py/security/advisories/GHSA-5hr4-253g-cpx2
github.com/ethereum/web3.py/commit/b1c57bb0a124359c9902daaefab4d8af7c3c4c1e
nvd.nist.gov/vuln/detail/CVE-2026-40072
github.com/advisories/GHSA-5hr4-253g-cpx2
pypi.org
github.com/CVEProject/cvelistV5/tree/main/cves/2026/111xxx/CVE-2026-111171.json
Show 8 moreShow less
nvd.nist.gov/vuln/detail/CVE-2026-111171
vulncheck.com/advisories/web3-py-7.15.0-through-7.16.0-and-8.0-0b2-through-8.0.0-ccip-read-ssrf-bypass
github.com/ethereum/web3.py/pull/3875
github.com/ethereum/web3.py
github.com/ethereum/web3.py/blob/c8c1ed0c9e77e1a8b74902e6a6825e0b00534b10/web3/utils/ccip_url_validation.py
github.com/ApeWorX/web3.py/security/advisories/GHSA-5hr4-253g-cpx2
github.com/CVEProject/cvelistV5/tree/main/cves/2026/40xxx/CVE-2026-40072.json
pypi.org/project/web3
Left out here: 40 versions and the rest of 3 source descriptions. The JSON record has them.