fix(cdp): gate file:// navigation behind --allow-file-access
GHSA-q55h-vfv9-qcr5. Any process able to reach the CDP port could
send Page.navigate {"url":"file:///etc/shadow"} and read arbitrary
local files (the process's UID's worth of read access). When obscura
runs as root inside Docker or as a systemd service that translates
straight into /etc/shadow, /root/.ssh/id_rsa, /proc/1/environ, etc.
Validate at the CDP boundary, not in obscura-net, so direct CLI
callers ("obscura fetch file://...") keep working. A new
BrowserContext.allow_file_access flag defaults to false; Page.navigate
returns an error when the requested URL is file:// and the flag is
off. CdpContext::new_with_security threads the flag from the server
entry point, and "obscura serve --allow-file-access" opts in for
local-HTML testing workflows.
Tested:
- Default obscura serve: CDP Page.navigate {file:///etc/passwd} ->
refused with descriptive error.
- obscura serve --allow-file-access: same call succeeds.
- obscura fetch file://... CLI path: unchanged, still reads the file.
- All earlier PoCs (GHSA-6pj9 JS->file SOP, GHSA-4789 sub-resource
SSRF, GHSA-4789 CSS template injection) still pass.
- cargo test --workspace clean.
This commit is contained in:
1 parent
ac0efd1798
commit
18dc796df0
6 files changed
+75
-7
No files matched your search
@@ -55,6 +55,17 @@ pub async fn start_with_host(
|
||||
proxy: Option<String>,
|
||||
stealth: bool,
|
||||
user_agent: Option<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
start_with_host_and_security(port, host, proxy, stealth, user_agent, false).await
|
||||
}
|
||||
|
||||
pub async fn start_with_host_and_security(
|
||||
port: u16,
|
||||
host: &str,
|
||||
proxy: Option<String>,
|
||||
stealth: bool,
|
||||
user_agent: Option<String>,
|
||||
allow_file_access: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let ip: std::net::IpAddr = host
|
||||
.parse()
|
||||
@@ -67,13 +78,16 @@ pub async fn start_with_host(
|
||||
"DevTools endpoint: ws://{}:{}/devtools/browser",
|
||||
host, port
|
||||
);
|
||||
if allow_file_access {
|
||||
info!("file:// navigation enabled (--allow-file-access). Do not expose this port to untrusted networks.");
|
||||
}
|
||||
|
||||
let local = tokio::task::LocalSet::new();
|
||||
local
|
||||
.run_until(async {
|
||||
let (msg_tx, msg_rx) = mpsc::unbounded_channel::<ServerMessage>();
|
||||
|
||||
let _processor_handle = tokio::task::spawn_local(cdp_processor(msg_rx, proxy, stealth, user_agent));
|
||||
let _processor_handle = tokio::task::spawn_local(cdp_processor(msg_rx, proxy, stealth, user_agent, allow_file_access));
|
||||
|
||||
loop {
|
||||
match listener.accept().await {
|
||||
@@ -100,8 +114,9 @@ async fn cdp_processor(
|
||||
proxy: Option<String>,
|
||||
stealth: bool,
|
||||
user_agent: Option<String>,
|
||||
allow_file_access: bool,
|
||||
) {
|
||||
let mut ctx = CdpContext::new_with_full_options(proxy, stealth, user_agent);
|
||||
let mut ctx = CdpContext::new_with_security(proxy, stealth, user_agent, allow_file_access);
|
||||
let (itx, irx) = mpsc::unbounded_channel::<obscura_js::ops::InterceptedRequest>();
|
||||
ctx.intercept_tx = Some(itx);
|
||||
let mut intercept_rx: Option<mpsc::UnboundedReceiver<obscura_js::ops::InterceptedRequest>> = Some(irx);
|
||||
|
||||
Reference in new issue
Block a user