mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-06-02 03:03:35 -07:00
Remove firewall feature (#996)
https://github.com/EFForg/rayhunter/pull/888 contained an entire set of iptables rules to ensure that no traffic leaked. We know that many of these devices are fairly insecure, that's how we get rayhunter installed on most of them. But if an attacker already is able to run commands on this device, they are most likely going to be able to run iptables -F too. We should discuss real threatmodels before adding stuff like this, because messing with iptables also just makes accidental bricking more likely (see the moxee disk space fiasco)
This commit is contained in:
committed by
GitHub
parent
3c1a164361
commit
54de3b3a38
@@ -46,10 +46,6 @@ pub struct Config {
|
||||
pub wifi_enabled: bool,
|
||||
/// Vector containing wifi client DNS servers
|
||||
pub dns_servers: Option<Vec<String>>,
|
||||
/// Wifi client firewall mode
|
||||
pub firewall_restrict_outbound: bool,
|
||||
/// Vector containing additional wifi client firewall ports to open
|
||||
pub firewall_allowed_ports: Option<Vec<u16>>,
|
||||
/// Optional WebDAV upload configuration. When unset, no upload worker runs.
|
||||
pub webdav: Option<WebdavConfig>,
|
||||
}
|
||||
@@ -109,8 +105,6 @@ impl Default for Config {
|
||||
wifi_security: None,
|
||||
wifi_enabled: false,
|
||||
dns_servers: None,
|
||||
firewall_restrict_outbound: true,
|
||||
firewall_allowed_ports: None,
|
||||
webdav: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
use anyhow::{Result, bail};
|
||||
use log::{info, warn};
|
||||
use tokio::process::Command;
|
||||
|
||||
use wifi_station::detect_bridge_iface;
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
async fn run_iptables(args: &[&str]) -> Result<()> {
|
||||
let out = Command::new("iptables").args(args).output().await?;
|
||||
if !out.status.success() {
|
||||
bail!(
|
||||
"iptables {} failed: {}",
|
||||
args.join(" "),
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn apply(config: &Config) {
|
||||
let _ = Command::new("iptables")
|
||||
.args(["-F", "OUTPUT"])
|
||||
.output()
|
||||
.await;
|
||||
|
||||
if config.firewall_restrict_outbound {
|
||||
// Fail open on partial setup error: reachability beats restriction when recovery means physical access.
|
||||
match setup_outbound_whitelist(&config.firewall_allowed_ports, &config.ntfy_url).await {
|
||||
Ok(()) => info!("outbound firewall active: allowing DHCP, DNS, HTTPS only"),
|
||||
Err(e) => warn!("firewall setup failed: {e} (fail-open, outbound unrestricted)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn setup_outbound_whitelist(
|
||||
extra_ports: &Option<Vec<u16>>,
|
||||
ntfy_url: &Option<String>,
|
||||
) -> Result<()> {
|
||||
run_iptables(&["-A", "OUTPUT", "-o", "lo", "-j", "ACCEPT"]).await?;
|
||||
run_iptables(&["-A", "OUTPUT", "-o", detect_bridge_iface(), "-j", "ACCEPT"]).await?;
|
||||
run_iptables(&[
|
||||
"-A",
|
||||
"OUTPUT",
|
||||
"-m",
|
||||
"state",
|
||||
"--state",
|
||||
"ESTABLISHED,RELATED",
|
||||
"-j",
|
||||
"ACCEPT",
|
||||
])
|
||||
.await?;
|
||||
run_iptables(&[
|
||||
"-A", "OUTPUT", "-p", "udp", "--dport", "67:68", "-j", "ACCEPT",
|
||||
])
|
||||
.await?;
|
||||
run_iptables(&["-A", "OUTPUT", "-p", "udp", "--dport", "53", "-j", "ACCEPT"]).await?;
|
||||
run_iptables(&["-A", "OUTPUT", "-p", "tcp", "--dport", "53", "-j", "ACCEPT"]).await?;
|
||||
run_iptables(&[
|
||||
"-A", "OUTPUT", "-p", "tcp", "--dport", "443", "-j", "ACCEPT",
|
||||
])
|
||||
.await?;
|
||||
|
||||
if let Some(url) = ntfy_url
|
||||
&& let Ok(parsed) = url::Url::parse(url)
|
||||
&& let Some(port) = parsed.port_or_known_default()
|
||||
&& port != 443
|
||||
{
|
||||
let port_str = port.to_string();
|
||||
run_iptables(&[
|
||||
"-A", "OUTPUT", "-p", "tcp", "--dport", &port_str, "-j", "ACCEPT",
|
||||
])
|
||||
.await?;
|
||||
info!("firewall: auto-allowed port {port} for ntfy");
|
||||
}
|
||||
|
||||
if let Some(ports) = extra_ports {
|
||||
for port in ports {
|
||||
let port_str = port.to_string();
|
||||
run_iptables(&[
|
||||
"-A", "OUTPUT", "-p", "tcp", "--dport", &port_str, "-j", "ACCEPT",
|
||||
])
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
run_iptables(&["-A", "OUTPUT", "-j", "DROP"]).await?;
|
||||
|
||||
let _ = tokio::fs::write("/proc/sys/net/bridge/bridge-nf-call-iptables", "0").await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -5,7 +5,6 @@ pub mod crypto_provider;
|
||||
pub mod diag;
|
||||
pub mod display;
|
||||
pub mod error;
|
||||
pub mod firewall;
|
||||
pub mod key_input;
|
||||
pub mod notifications;
|
||||
pub mod pcap;
|
||||
|
||||
@@ -5,7 +5,6 @@ mod crypto_provider;
|
||||
mod diag;
|
||||
mod display;
|
||||
mod error;
|
||||
mod firewall;
|
||||
mod key_input;
|
||||
mod notifications;
|
||||
mod pcap;
|
||||
@@ -288,7 +287,6 @@ async fn run_with_config(
|
||||
shutdown_token.clone(),
|
||||
wifi_status.clone(),
|
||||
);
|
||||
firewall::apply(&config).await;
|
||||
|
||||
if let Some(webdav_config) = config.webdav.clone() {
|
||||
run_webdav_upload_worker(
|
||||
|
||||
@@ -500,63 +500,6 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="border-t border-gray-200 pt-4 mt-6 space-y-3">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-4">Device Security</h3>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="firewall_restrict_outbound"
|
||||
type="checkbox"
|
||||
bind:checked={config.firewall_restrict_outbound}
|
||||
class="h-4 w-4 text-rayhunter-blue focus:ring-rayhunter-blue border-gray-300 rounded-sm"
|
||||
/>
|
||||
<label
|
||||
for="firewall_restrict_outbound"
|
||||
class="ml-2 block text-sm text-gray-700"
|
||||
>
|
||||
Restrict outbound traffic
|
||||
</label>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500">
|
||||
Only allows DNS, DHCP, and HTTPS (port 443) outbound. Blocks all other
|
||||
outbound connections on every interface (WiFi and cellular). Loopback and
|
||||
hotspot traffic are always allowed. Changes take effect immediately.
|
||||
</p>
|
||||
|
||||
{#if config.firewall_restrict_outbound}
|
||||
<div>
|
||||
<label
|
||||
for="firewall_allowed_ports"
|
||||
class="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
Additional Allowed Ports
|
||||
</label>
|
||||
<input
|
||||
id="firewall_allowed_ports"
|
||||
type="text"
|
||||
value={config.firewall_allowed_ports
|
||||
? config.firewall_allowed_ports.join(', ')
|
||||
: ''}
|
||||
oninput={(e) => {
|
||||
const val = (e.target as HTMLInputElement).value.trim();
|
||||
config!.firewall_allowed_ports =
|
||||
val.length > 0
|
||||
? val
|
||||
.split(',')
|
||||
.map((s) => parseInt(s.trim()))
|
||||
.filter((n) => !isNaN(n) && n >= 1 && n <= 65535)
|
||||
: null;
|
||||
}}
|
||||
placeholder="22, 80"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-hidden focus:ring-2 focus:ring-rayhunter-blue"
|
||||
/>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
Comma-separated TCP ports, e.g. 22, 80
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="border-t border-gray-200 pt-4 mt-6">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-4">
|
||||
Analyzer Heuristic Settings
|
||||
|
||||
@@ -33,8 +33,6 @@ export interface Config {
|
||||
wifi_security: 'wpa_psk' | 'sae' | null;
|
||||
wifi_enabled: boolean;
|
||||
dns_servers: string[] | null;
|
||||
firewall_restrict_outbound: boolean;
|
||||
firewall_allowed_ports: number[] | null;
|
||||
}
|
||||
|
||||
export interface WifiStatus {
|
||||
|
||||
Reference in New Issue
Block a user