-
Notifications
You must be signed in to change notification settings - Fork 372
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'add-e2e-tests-for-arp-ping-leak-des-1438'
- Loading branch information
Showing
7 changed files
with
207 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
#![cfg(target_os = "linux")] | ||
//! Test mitigation for mllvd_cr_24_03 | ||
//! | ||
//! By sending an ARP request for the in-tunnel IP address to any network interface on the device running Mullvad, it | ||
//! will respond and confirm that it owns this address. This means someone on the LAN or similar can figure out the | ||
//! device's in-tunnel IP, and potentially also make an educated guess that they are using Mullvad at all. | ||
//! | ||
//! # Setup | ||
//! | ||
//! Victim: test-runner | ||
//! | ||
//! Network adjacent attacker: test-manager | ||
//! | ||
//! # Procedure | ||
//! Have test-runner connect to relay. Let test-manager know about the test-runner's private in-tunnel IP (such that | ||
//! we don't have to enumerate all possible private IPs). | ||
//! | ||
//! Have test-manager invoke the `arping` command targeting the bridge network between test-manager <-> test-runner. | ||
//! If `arping` times out without a reply, it will exit with a non-0 exit code. If it got a reply from test-runner, it | ||
//! will exit with code 0. | ||
//! | ||
//! Note that only linux was susceptible to this vulnerability. | ||
use std::ffi::OsStr; | ||
use std::process::Output; | ||
|
||
use anyhow::bail; | ||
use mullvad_management_interface::MullvadProxyClient; | ||
use test_macro::test_function; | ||
use test_rpc::ServiceClient; | ||
|
||
use crate::tests::helpers::*; | ||
use crate::tests::TestContext; | ||
use crate::vm::network::bridge; | ||
|
||
#[test_function(target_os = "linux")] | ||
pub async fn test_mllvd_cr_24_03( | ||
_: TestContext, | ||
rpc: ServiceClient, | ||
mut mullvad_client: MullvadProxyClient, | ||
) -> anyhow::Result<()> { | ||
// Get the bridge network between manager and runner. This will be used when invoking `arping`. | ||
let bridge = bridge()?; | ||
// Connect runner to a relay. After this point we will be able to acquire the runner's private in-tunnel IP. | ||
connect_and_wait(&mut mullvad_client).await?; | ||
// Get the private ip address | ||
let in_tunnel_ip = { | ||
let vpn_interface = get_tunnel_interface(&mut mullvad_client).await?; | ||
rpc.get_interface_ip(vpn_interface).await? | ||
}; | ||
// Invoke arping | ||
let malicious_arping = arping([ | ||
"-w", | ||
"5", | ||
"-i", | ||
"1", | ||
"-I", | ||
&bridge, | ||
&in_tunnel_ip.to_string(), | ||
]) | ||
.await?; | ||
// If arping exited with code 0, it means the runner replied to the ARP request, implying the runner leaked its | ||
// private in-tunnel IP! | ||
if let Some(0) = malicious_arping.status.code() { | ||
log::error!("{}", String::from_utf8(malicious_arping.stdout)?); | ||
bail!("ARP leak detected") | ||
} | ||
// test runner did not respond to ARP request, leak mitigation seems to work! | ||
Ok(()) | ||
} | ||
|
||
/// Invoke `arping` on test-manager. | ||
async fn arping<I, S>(args: I) -> std::io::Result<Output> | ||
where | ||
I: IntoIterator<Item = S>, | ||
S: AsRef<OsStr>, | ||
{ | ||
let mut arping = tokio::process::Command::new("arping"); | ||
arping.args(args); | ||
arping.output().await | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
//! This module collects tests for old audit issues to prevent any potential regression. | ||
pub mod cve_2019_14899; | ||
pub mod mllvd_cr_24_03; | ||
pub mod mul_02_002; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
//! Test mitigation for MUL-02-002-WP2 | ||
//! | ||
//! Fail to leak traffic to verify that mitigation "Firewall allows deanonymization by eavesdropper" works. | ||
//! | ||
//! # Vulnerability | ||
//! 1. Connect to a relay on port 443. Record this relay's IP address (the new gateway of the | ||
//! client) | ||
//! 2. Start listening for unencrypted traffic on the outbound network interface | ||
//! (Choose some human-readable, identifiable payload to look for in the outgoing TCP packets) | ||
//! 3. Start a rogue program which performs a GET request\* containing the payload defined in step 2 | ||
//! 4. The network snooper started in step 2 should now be able to observe the network request | ||
//! containing the identifiable payload being sent unencrypted over the wire | ||
//! | ||
//! \* or something similar, as long as it generates some traffic containing UDP and/or TCP packets | ||
//! with the correct payload. | ||
use anyhow::{bail, ensure}; | ||
use mullvad_management_interface::MullvadProxyClient; | ||
use mullvad_relay_selector::query::builder::{RelayQueryBuilder, TransportProtocol}; | ||
use mullvad_types::states::TunnelState; | ||
use test_macro::test_function; | ||
use test_rpc::ServiceClient; | ||
|
||
use crate::network_monitor::{start_packet_monitor, MonitorOptions, ParsedPacket}; | ||
use crate::tests::helpers::{ | ||
connect_and_wait, constrain_to_relay, disconnect_and_wait, ConnChecker, | ||
}; | ||
use crate::tests::TestContext; | ||
|
||
#[test_function] | ||
pub async fn test_mul_02_002( | ||
_: TestContext, | ||
rpc: ServiceClient, | ||
mut mullvad_client: MullvadProxyClient, | ||
) -> anyhow::Result<()> { | ||
// Step 1 - Choose a relay | ||
constrain_to_relay( | ||
&mut mullvad_client, | ||
RelayQueryBuilder::new() | ||
.openvpn() | ||
.transport_protocol(TransportProtocol::Tcp) | ||
.port(443) | ||
.build(), | ||
) | ||
.await?; | ||
|
||
// Step 1.5 - Temporarily connect to the relay to get the target endpoint | ||
let tunnel_state = connect_and_wait(&mut mullvad_client).await?; | ||
let TunnelState::Connected { endpoint, .. } = tunnel_state else { | ||
bail!("Expected tunnel state to be `Connected` - instead it was {tunnel_state:?}"); | ||
}; | ||
disconnect_and_wait(&mut mullvad_client).await?; | ||
let target_endpoint = endpoint.endpoint.address; | ||
|
||
// Step 2 - Start a network monitor snooping the outbound network interface for some | ||
// identifiable payload | ||
let unique_identifier = "Hello there!"; | ||
let identify_rogue_packet = move |packet: &ParsedPacket| { | ||
packet | ||
.payload | ||
.windows(unique_identifier.len()) | ||
.any(|window| window == unique_identifier.as_bytes()) | ||
}; | ||
let rogue_packet_monitor = | ||
start_packet_monitor(identify_rogue_packet, MonitorOptions::default()).await; | ||
|
||
// Step 3 - Start the rogue program which will try to leak the unique identifier payload | ||
// to the chosen relay endpoint | ||
let mut checker = ConnChecker::new(rpc.clone(), mullvad_client.clone(), target_endpoint); | ||
checker.payload(unique_identifier); | ||
let mut conn_artist = checker.spawn().await?; | ||
// Before proceeding, assert that the method of detecting identifiable packets work. | ||
conn_artist.check_connection().await?; | ||
let monitor_result = rogue_packet_monitor.into_result().await?; | ||
|
||
log::info!("Checking that the identifiable payload was detectable without encryption"); | ||
ensure!( | ||
!monitor_result.packets.is_empty(), | ||
"Did not observe rogue packets! The method seems to be broken" | ||
); | ||
log::info!("The identifiable payload was detected! (that's good)"); | ||
|
||
// Step 4 - Finally, connect to a tunnel and assert that no outgoing traffic contains the | ||
// payload in plain text. | ||
connect_and_wait(&mut mullvad_client).await?; | ||
let rogue_packet_monitor = | ||
start_packet_monitor(identify_rogue_packet, MonitorOptions::default()).await; | ||
conn_artist.check_connection().await?; | ||
let monitor_result = rogue_packet_monitor.into_result().await?; | ||
|
||
log::info!("Checking that the identifiable payload was not detected"); | ||
ensure!( | ||
monitor_result.packets.is_empty(), | ||
"Observed rogue packets! The tunnel seems to be leaking traffic" | ||
); | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters