@@ -2,11 +2,13 @@ use super::helpers::{
2
2
self , connect_and_wait, disconnect_and_wait, set_bridge_settings, set_relay_settings,
3
3
} ;
4
4
use super :: { config:: TEST_CONFIG , Error , TestContext } ;
5
- use crate :: network_monitor:: { start_packet_monitor, MonitorOptions } ;
5
+ use crate :: network_monitor:: { start_packet_monitor, MonitorOptions , ParsedPacket } ;
6
6
use crate :: tests:: helpers:: { login_with_retries, ConnChecker } ;
7
7
8
+ use anyhow:: { bail, ensure} ;
8
9
use mullvad_management_interface:: MullvadProxyClient ;
9
10
use mullvad_relay_selector:: query:: builder:: RelayQueryBuilder ;
11
+ use mullvad_types:: states:: TunnelState ;
10
12
use mullvad_types:: {
11
13
constraints:: Constraint ,
12
14
relay_constraints:: {
@@ -802,3 +804,89 @@ pub async fn test_establish_tunnel_without_api(
802
804
// Profit
803
805
Ok ( ( ) )
804
806
}
807
+
808
+ /// Fail to leak traffic to verify that mitigation for MUL-02-002-WP2
809
+ /// ("Firewall allows deanonymization by eavesdropper") works.
810
+ ///
811
+ /// # Vulnerability
812
+ /// 1. Connect to a relay on port 443. Record this relay's IP address (the new gateway of the client)
813
+ /// 2. Start listening for unencrypted traffic on the outbound network interface
814
+ /// (Choose some human-readable, identifiable payload to look for in the outgoing TCP packets)
815
+ /// 3. Start a rogue program which performs a GET request* containing the payload defined in step 2
816
+ /// 4. The network snooper started in step 2 should now be able to observe the network request
817
+ /// containing the identifiable payload being sent unencrypted over the wire
818
+ ///
819
+ /// * or something similiar, as long as it generates some traffic containing UDP and/or TCP packets
820
+ /// with the correct payload.
821
+ #[ test_function]
822
+ pub async fn test_mul_02_002 (
823
+ _: TestContext ,
824
+ rpc : ServiceClient ,
825
+ mut mullvad_client : MullvadProxyClient ,
826
+ ) -> Result < ( ) , anyhow:: Error > {
827
+ // Step 0 - Disconnect from any active tunnel connection
828
+ helpers:: disconnect_and_wait ( & mut mullvad_client) . await ?;
829
+ // Step 1 - Choose a relay
830
+ let relay_constraints = RelayQueryBuilder :: new ( )
831
+ . openvpn ( )
832
+ . transport_protocol ( TransportProtocol :: Tcp )
833
+ . port ( 443 )
834
+ . into_constraint ( ) ;
835
+
836
+ set_relay_settings (
837
+ & mut mullvad_client,
838
+ RelaySettings :: Normal ( relay_constraints) ,
839
+ )
840
+ . await ?;
841
+
842
+ // Step 1.5 - Temporarily connect to the relay to get the target endpoint
843
+ let tunnel_state = helpers:: connect_and_wait ( & mut mullvad_client) . await ?;
844
+ let TunnelState :: Connected { endpoint, .. } = tunnel_state else {
845
+ bail ! ( "Expected tunnel state to be `Connected` - instead it was {tunnel_state:?}" ) ;
846
+ } ;
847
+ helpers:: disconnect_and_wait ( & mut mullvad_client) . await ?;
848
+ let gateway = endpoint. endpoint . address ;
849
+
850
+ // Step 2 - Start a network monitor snooping the outbound network interface for some
851
+ // identifiable payload
852
+ // FIXME: This needs to be kept in sync with the magic payload string defined in `connection_cheker::net`.
853
+ // An easy fix would be to make the payload for `ConnCheck` configurable.
854
+ let unique_identifier = b"Hello there!" ;
855
+ let identify_rogue_packet = move |packet : & ParsedPacket | {
856
+ packet
857
+ . payload
858
+ . windows ( unique_identifier. len ( ) )
859
+ . any ( |window| window == unique_identifier)
860
+ } ;
861
+ let rogue_packet_monitor =
862
+ start_packet_monitor ( identify_rogue_packet, MonitorOptions :: default ( ) ) . await ;
863
+
864
+ // Step 3 - Start the rogue program which will try to leak traffic to the chosen relay endpoint
865
+ let mut checker = ConnChecker :: new ( rpc. clone ( ) , mullvad_client. clone ( ) , gateway) ;
866
+ let mut conn_artist = checker. spawn ( ) . await ?;
867
+ // Before proceeding, assert that the method of detecting identifiable packets work.
868
+ conn_artist. check_connection ( ) . await ?;
869
+ let monitor_result = rogue_packet_monitor. into_result ( ) . await . unwrap ( ) ;
870
+
871
+ log:: info!( "Checking that the identifiable payload was detectable without encryption" ) ;
872
+ ensure ! (
873
+ !monitor_result. packets. is_empty( ) ,
874
+ "Did not observe rogue packets! The method seems to be broken"
875
+ ) ;
876
+
877
+ // Step 4 - Finally, connect to a tunnel and assert that no outgoing traffic contains the
878
+ // payload in plain text.
879
+ helpers:: connect_and_wait ( & mut mullvad_client) . await ?;
880
+ let rogue_packet_monitor =
881
+ start_packet_monitor ( identify_rogue_packet, MonitorOptions :: default ( ) ) . await ;
882
+ conn_artist. check_connection ( ) . await ?;
883
+ let monitor_result = rogue_packet_monitor. into_result ( ) . await . unwrap ( ) ;
884
+
885
+ log:: info!( "Checking that the identifiable payload was not detected" ) ;
886
+ ensure ! (
887
+ monitor_result. packets. is_empty( ) ,
888
+ "Observed rogue packets! The tunnel seems to be leaking traffic"
889
+ ) ;
890
+
891
+ Ok ( ( ) )
892
+ }
0 commit comments