Bug 1652612 - More explictly only allow connections from local origins, r=whimboo,webdriver-reviewers

If an origin header is supplied with the request, validate it
corresponds to a service running on localhost.

Differential Revision: https://phabricator.services.mozilla.com/D120387
This commit is contained in:
James Graham 2021-07-29 19:34:08 +00:00
parent 871fb42930
commit 158ea02bba

View File

@ -389,6 +389,21 @@ fn host_and_port_match_server(
host_matches && port_matches
}
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()))
}
fn build_route<U: 'static + WebDriverExtensionRoute + Send + Sync>(
server_host: Host,
server_address: SocketAddr,
@ -454,26 +469,6 @@ fn build_route<U: 'static + WebDriverExtensionRoute + Send + Sync>(
if method == Method::HEAD {
return warp::reply::with_status("".into(), StatusCode::OK);
}
if let Some(origin) = origin_header {
let mut valid_host = false;
let host_url = Url::parse(&origin).ok();
let host = host_url.as_ref().and_then(|x| x.host());
if let Some(host) = host {
valid_host = match host {
Host::Domain("localhost") => true,
Host::Domain(_) => false,
Host::Ipv4(x) => server_address.is_ipv4() && x == server_address.ip(),
Host::Ipv6(x) => server_address.is_ipv6() && x == server_address.ip(),
};
}
if !valid_host {
let err = WebDriverError::new(ErrorStatus::UnknownError, "Invalid Origin");
return warp::reply::with_status(
serde_json::to_string(&err).unwrap(),
StatusCode::INTERNAL_SERVER_ERROR,
);
};
}
if let Some(host) = host_header {
let host_port = match parse_host(&host) {
Ok(x) => x,
@ -504,6 +499,22 @@ fn build_route<U: 'static + WebDriverExtensionRoute + Send + Sync>(
StatusCode::INTERNAL_SERVER_ERROR,
);
}
if let Some(origin) = origin_header {
let origin_match_err = match origin_is_local(&origin) {
Ok(true) => None,
Ok(false) => Some(WebDriverError::new(
ErrorStatus::UnknownError,
format!("Request Origin {} isn't local", origin),
)),
Err(err) => Some(err),
};
if origin_match_err.is_some() {
return warp::reply::with_status(
serde_json::to_string(&origin_match_err).unwrap(),
StatusCode::INTERNAL_SERVER_ERROR,
);
}
}
if method == Method::POST {
// Disallow CORS-safelisted request headers
// c.f. https://fetch.spec.whatwg.org/#cors-safelisted-request-header
@ -746,4 +757,24 @@ mod tests {
(localhost_host.clone(), None)
));
}
#[test]
fn test_origin_is_local() {
// This depends on network configuration; we assume that localhost, 127.0.0.1 and [::1] are loopback
// and that example.org and 1.1.1.1 are not.
assert!(origin_is_local("https://127.0.0.1").unwrap());
assert!(origin_is_local("http://127.0.0.1:8000").unwrap());
assert!(origin_is_local("http://[::1]").unwrap());
assert!(origin_is_local("https://[::1]:9999").unwrap());
assert!(origin_is_local("http://localhost").unwrap());
assert!(origin_is_local("https://localhost").unwrap());
assert!(origin_is_local("http://localhost:4444").unwrap());
assert!(!origin_is_local("http://example.org").unwrap());
assert!(!origin_is_local("https://example.org:1000").unwrap());
assert!(!origin_is_local("http://1.1.1.1").unwrap());
assert!(origin_is_local("localhost").is_err());
assert!(origin_is_local("localhost:443").is_err());
assert!(origin_is_local("127.0.0.1:443").is_err());
assert!(origin_is_local("[::1]").is_err());
}
}