Skip to content

Commit

Permalink
Implement test for audit ticket MUL-02-002 WP2
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkusPettersson98 committed Mar 28, 2024
1 parent 6c58ebc commit 3a2ca13
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 4 deletions.
6 changes: 4 additions & 2 deletions test/connection-checker/src/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use std::{

use crate::cli::Opt;

const PAYLOAD: &[u8] = b"Hello there!";

pub fn send_tcp(opt: &Opt, destination: SocketAddr) -> eyre::Result<()> {
let bind_addr: SocketAddr = SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 0);

Expand All @@ -31,7 +33,7 @@ pub fn send_tcp(opt: &Opt, destination: SocketAddr) -> eyre::Result<()> {

let mut stream = std::net::TcpStream::from(sock);
stream
.write_all(b"hello there")
.write_all(PAYLOAD)
.wrap_err(eyre!("Failed to send message to {destination}"))?;

Ok(())
Expand All @@ -56,7 +58,7 @@ pub fn send_udp(_opt: &Opt, destination: SocketAddr) -> Result<(), eyre::Error>

let std_socket = std::net::UdpSocket::from(sock);
std_socket
.send_to(b"Hello there!", destination)
.send_to(PAYLOAD, destination)
.wrap_err(eyre!("Failed to send message to {destination}"))?;

Ok(())
Expand Down
10 changes: 9 additions & 1 deletion test/test-manager/src/network_monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct ParsedPacket {
pub source: SocketAddr,
pub destination: SocketAddr,
pub protocol: IpNextHeaderProtocol,
pub payload: Vec<u8>,
}

impl PacketCodec for Codec {
Expand Down Expand Up @@ -75,9 +76,9 @@ impl Codec {

let mut source = SocketAddr::new(IpAddr::V4(packet.get_source()), 0);
let mut destination = SocketAddr::new(IpAddr::V4(packet.get_destination()), 0);
let mut payload = vec![];

let protocol = packet.get_next_level_protocol();

match protocol {
IpHeaderProtocols::Tcp => {
let seg = TcpPacket::new(packet.payload()).or_else(|| {
Expand All @@ -86,6 +87,7 @@ impl Codec {
})?;
source.set_port(seg.get_source());
destination.set_port(seg.get_destination());
payload = seg.payload().to_vec();
}
IpHeaderProtocols::Udp => {
let seg = UdpPacket::new(packet.payload()).or_else(|| {
Expand All @@ -94,6 +96,7 @@ impl Codec {
})?;
source.set_port(seg.get_source());
destination.set_port(seg.get_destination());
payload = seg.payload().to_vec();
}
IpHeaderProtocols::Icmp => {}
proto => log::debug!("ignoring v4 packet, transport/protocol type {proto}"),
Expand All @@ -103,6 +106,7 @@ impl Codec {
source,
destination,
protocol,
payload,
})
}

Expand All @@ -114,6 +118,7 @@ impl Codec {

let mut source = SocketAddr::new(IpAddr::V6(packet.get_source()), 0);
let mut destination = SocketAddr::new(IpAddr::V6(packet.get_destination()), 0);
let mut payload = vec![];

let protocol = packet.get_next_header();
match protocol {
Expand All @@ -124,6 +129,7 @@ impl Codec {
})?;
source.set_port(seg.get_source());
destination.set_port(seg.get_destination());
payload = seg.payload().to_vec();
}
IpHeaderProtocols::Udp => {
let seg = UdpPacket::new(packet.payload()).or_else(|| {
Expand All @@ -132,6 +138,7 @@ impl Codec {
})?;
source.set_port(seg.get_source());
destination.set_port(seg.get_destination());
payload = seg.payload().to_vec();
}
IpHeaderProtocols::Icmpv6 => {}
proto => log::debug!("ignoring v6 packet, transport/protocol type {proto}"),
Expand All @@ -141,6 +148,7 @@ impl Codec {
source,
destination,
protocol,
payload,
})
}
}
Expand Down
1 change: 1 addition & 0 deletions test/test-manager/src/tests/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ pub async fn test_installation_idempotency(
// Connect to any relay. This forces the daemon to enter a secured target state
connect_and_wait(&mut mullvad_client)
.await
.map(|_| ()) // Discard the new tunnel state
.or_else(|error| match error {
Error::UnexpectedErrorState(_) => Ok(()),
err => Err(err),
Expand Down
90 changes: 89 additions & 1 deletion test/test-manager/src/tests/tunnel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ use super::helpers::{
self, connect_and_wait, disconnect_and_wait, set_bridge_settings, set_relay_settings,
};
use super::{config::TEST_CONFIG, Error, TestContext};
use crate::network_monitor::{start_packet_monitor, MonitorOptions};
use crate::network_monitor::{start_packet_monitor, MonitorOptions, ParsedPacket};
use crate::tests::helpers::{login_with_retries, ConnChecker};

use anyhow::{bail, ensure};
use mullvad_management_interface::MullvadProxyClient;
use mullvad_relay_selector::query::builder::RelayQueryBuilder;
use mullvad_types::states::TunnelState;
use mullvad_types::{
constraints::Constraint,
relay_constraints::{
Expand Down Expand Up @@ -802,3 +804,89 @@ pub async fn test_establish_tunnel_without_api(
// Profit
Ok(())
}

/// Fail to leak traffic to verify that mitigation for MUL-02-002-WP2
/// ("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 similiar, as long as it generates some traffic containing UDP and/or TCP packets
/// with the correct payload.
#[test_function]
pub async fn test_mul_02_002(
_: TestContext,
rpc: ServiceClient,
mut mullvad_client: MullvadProxyClient,
) -> Result<(), anyhow::Error> {
// Step 0 - Disconnect from any active tunnel connection
helpers::disconnect_and_wait(&mut mullvad_client).await?;
// Step 1 - Choose a relay
let relay_constraints = RelayQueryBuilder::new()
.openvpn()
.transport_protocol(TransportProtocol::Tcp)
.port(443)
.into_constraint();

set_relay_settings(
&mut mullvad_client,
RelaySettings::Normal(relay_constraints),
)
.await?;

// Step 1.5 - Temporarily connect to the relay to get the target endpoint
let tunnel_state = helpers::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:?}");
};
helpers::disconnect_and_wait(&mut mullvad_client).await?;
let gateway = endpoint.endpoint.address;

// Step 2 - Start a network monitor snooping the outbound network interface for some
// identifiable payload
// FIXME: This needs to be kept in sync with the magic payload string defined in `connection_cheker::net`.
// An easy fix would be to make the payload for `ConnCheck` configurable.
let unique_identifier = b"Hello there!";
let identify_rogue_packet = move |packet: &ParsedPacket| {
packet
.payload
.windows(unique_identifier.len())
.any(|window| window == unique_identifier)
};
let rogue_packet_monitor =
start_packet_monitor(identify_rogue_packet, MonitorOptions::default()).await;

// Step 3 - Start the rogue program which will try to leak traffic to the chosen relay endpoint
let mut checker = ConnChecker::new(rpc.clone(), mullvad_client.clone(), gateway);
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.unwrap();

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"
);

// Step 4 - Finally, connect to a tunnel and assert that no outgoing traffic contains the
// payload in plain text.
helpers::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.unwrap();

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(())
}

0 comments on commit 3a2ca13

Please sign in to comment.