mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-05-30 05:39:26 -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 {
|
||||
|
||||
12
dist/config.toml.in
vendored
12
dist/config.toml.in
vendored
@@ -43,18 +43,6 @@ wifi_enabled = false
|
||||
# Defaults to ["9.9.9.9", "149.112.112.112"] (Quad9) if not specified.
|
||||
# dns_servers = ["9.9.9.9", "149.112.112.112"]
|
||||
|
||||
# Device Security
|
||||
# Restrict outbound traffic to essential services only (DHCP, DNS,
|
||||
# HTTPS, and replies to inbound connections). Applies to all outbound
|
||||
# interfaces (WiFi and cellular). Loopback and hotspot bridge traffic
|
||||
# are always allowed. Defaults to true (recommended).
|
||||
firewall_restrict_outbound = true
|
||||
|
||||
# Additional TCP ports to allow outbound when the firewall is active.
|
||||
# DHCP (67-68), DNS (53), and HTTPS (443) are always allowed.
|
||||
# Example: allow HTTP (80) and SSH (22).
|
||||
# firewall_allowed_ports = [80, 22]
|
||||
|
||||
# WebDAV Upload
|
||||
# If a [webdav] section is present, finished recordings (both the raw .qmdl file
|
||||
# and its .ndjson analysis output) are uploaded in the background to a WebDAV
|
||||
|
||||
24
dist/scripts/S01iptables
vendored
24
dist/scripts/S01iptables
vendored
@@ -1,24 +0,0 @@
|
||||
#!/bin/sh
|
||||
CONFIG="/data/rayhunter/config.toml"
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
if grep -q '^firewall_restrict_outbound = true' "$CONFIG" 2>/dev/null; then
|
||||
iptables -F OUTPUT
|
||||
iptables -A OUTPUT -o lo -j ACCEPT
|
||||
for br in bridge0 br0; do
|
||||
[ -d "/sys/class/net/$br" ] && iptables -A OUTPUT -o "$br" -j ACCEPT
|
||||
done
|
||||
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
iptables -A OUTPUT -p udp --dport 67:68 -j ACCEPT
|
||||
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
|
||||
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
|
||||
iptables -A OUTPUT -j DROP
|
||||
echo 0 > /proc/sys/net/bridge/bridge-nf-call-iptables 2>/dev/null
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
iptables -F OUTPUT
|
||||
iptables -P OUTPUT ACCEPT
|
||||
;;
|
||||
esac
|
||||
@@ -51,10 +51,6 @@ You can also configure WiFi during installation:
|
||||
./installer orbic --admin-password 'mypassword' --wifi-ssid 'MyNetwork' --wifi-password 'networkpass'
|
||||
```
|
||||
|
||||
## Device Security
|
||||
|
||||
- **Restrict outbound traffic** limits what the device can send over the network. When enabled, only DNS, DHCP, and HTTPS traffic is allowed; everything else is blocked. This is enabled by default and prevents the device from phoning home to the carrier over cellular. If you need to allow additional ports (for example, port 80 for HTTP or port 22 for SSH), add them to the **Additional allowed ports** list.
|
||||
|
||||
## WebDAV Upload
|
||||
|
||||
Rayhunter can automatically upload finished recordings to a WebDAV server. When a `[webdav]` section is present in `config.toml`, a background worker periodically scans the recording store and uploads any closed entry that is older than `min_age_secs`. Each eligible entry uploads two files: the raw `.qmdl` capture and its `.ndjson` analysis output. After a successful upload the entry is either marked as uploaded in the manifest (and skipped on subsequent polls), or deleted locally if `delete_on_upload = true`. With no `[webdav]` section, no upload worker runs.
|
||||
|
||||
@@ -185,15 +185,8 @@ async fn setup_rayhunter(mut adb_device: ADBUSBDevice, reset_config: bool) -> Re
|
||||
include_bytes!("../../dist/scripts/misc-daemon"),
|
||||
)
|
||||
.await?;
|
||||
install_file(
|
||||
&mut adb_device,
|
||||
"/etc/init.d/S01iptables",
|
||||
include_bytes!("../../dist/scripts/S01iptables"),
|
||||
)
|
||||
.await?;
|
||||
adb_at_syscmd(&mut adb_device, "chmod 755 /etc/init.d/rayhunter_daemon").await?;
|
||||
adb_at_syscmd(&mut adb_device, "chmod 755 /etc/init.d/misc-daemon").await?;
|
||||
adb_at_syscmd(&mut adb_device, "chmod 755 /etc/init.d/S01iptables").await?;
|
||||
println!("done");
|
||||
print!("Waiting for reboot... ");
|
||||
adb_at_syscmd(&mut adb_device, "shutdown -r -t 1 now").await?;
|
||||
|
||||
@@ -267,13 +267,6 @@ async fn setup_rayhunter(admin_ip: &str, reset_config: bool, data_dir: &str) ->
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
telnet_send_file(
|
||||
addr,
|
||||
"/etc/init.d/S01iptables",
|
||||
include_bytes!("../../dist/scripts/S01iptables"),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
telnet_send_command(
|
||||
addr,
|
||||
@@ -296,13 +289,6 @@ async fn setup_rayhunter(admin_ip: &str, reset_config: bool, data_dir: &str) ->
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
telnet_send_command(
|
||||
addr,
|
||||
"chmod 755 /etc/init.d/S01iptables",
|
||||
"exit code 0",
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("Installation complete. Rebooting device...");
|
||||
telnet_send_command(addr, "shutdown -r -t 1 now", "", false)
|
||||
|
||||
Reference in New Issue
Block a user