tls-attacker/TLS-Attacker

Transport handler exceptions no longer reported

Closed this issue · 2 comments

Hi all,

I noticed that recent versions of TLS-Attacker (e.g., 3.8.1) do not seem to report (UDP) transport handler exceptions caused by 'Destination unreachable' ICMP packets. I was wondering if this is intended behavior? At the end of this post you will can a client program exposing this. Just execute the program as is, without launching any server. If all goes well, isReceivedTransportHandlerException should return false. Using older versions of TLS-Attacker (e.g., 3.2b), the method returns true (note, to use older version you might have to remove the config argument from the MessageActionFactory::createAction method).

I think the reason for the divergence is the re-design of the TransportLayer. Knowing when a transport layer exception occurs is useful when fuzzing, as the exception signals when the server/client is down/no longer responsive. Are there alternative non-hacky ways of figuring this out?

As an aside, something else that sticks our when executing the program is
the presence of Alert(null,null) in outputs. I find it a bit strange to have null values in output.

Thanks, Paul.

P.S. For context, we are the devs of DTLS-Fuzzer, a TLS-Attacker based state fuzzer for DTLS, used in our still recent collaboration with Robert + Juraj + Joeri. Recently we began the process of updating DTLS-Fuzzer to use the latest version of TLS-Attacker.

The program:

import java.security.Security;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import de.rub.nds.tlsattacker.core.config.Config;
import de.rub.nds.tlsattacker.core.connection.AliasedConnection;
import de.rub.nds.tlsattacker.core.constants.ProtocolVersion;
import de.rub.nds.tlsattacker.core.protocol.message.AlertMessage;
import de.rub.nds.tlsattacker.core.protocol.message.ClientHelloMessage;
import de.rub.nds.tlsattacker.core.state.State;
import de.rub.nds.tlsattacker.core.workflow.WorkflowExecutor;
import de.rub.nds.tlsattacker.core.workflow.WorkflowExecutorFactory;
import de.rub.nds.tlsattacker.core.workflow.WorkflowTrace;
import de.rub.nds.tlsattacker.core.workflow.action.MessageActionFactory;
import de.rub.nds.tlsattacker.core.workflow.action.ReceiveAction;
import de.rub.nds.tlsattacker.transport.ConnectionEndType;
import de.rub.nds.tlsattacker.transport.TransportHandlerType;
import de.rub.nds.tlsattacker.util.UnlimitedStrengthEnabler;

public class Main {

    public static void main(String[] args) {
        UnlimitedStrengthEnabler.enable();
        Security.addProvider(new BouncyCastleProvider());
        Config config = Config.createConfig();
        config.setDefaultHighestClientProtocolVersion(ProtocolVersion.DTLS12);
        config.setHighestProtocolVersion(ProtocolVersion.DTLS12);
        config.setDefaultSelectedProtocolVersion(ProtocolVersion.DTLS12);

        AliasedConnection con = config.getDefaultClientConnection();
        con.setPort(20000);
        con.setTransportHandlerType(TransportHandlerType.UDP);
        
        WorkflowTrace trace = new WorkflowTrace();
        trace.addTlsAction(
                MessageActionFactory.createAction(config, con, ConnectionEndType.CLIENT, new AlertMessage(config)));
        trace.addTlsAction(new ReceiveAction());
        trace.addTlsAction(
            MessageActionFactory.createAction(config, con, ConnectionEndType.CLIENT, new AlertMessage(config)));
        trace.addTlsAction(new ReceiveAction());
        trace.addTlsAction(MessageActionFactory.createAction(config, con, ConnectionEndType.CLIENT,
            new ClientHelloMessage(config)));
        trace.addTlsAction(new ReceiveAction());
        State state = new State(config, trace);
        WorkflowExecutor workflowExecutor =
            WorkflowExecutorFactory.createWorkflowExecutor(config.getWorkflowExecutorType(), state);
        workflowExecutor.executeWorkflow();
        System.out.println(state.getTlsContext().isReceivedTransportHandlerException());
    }
}

Example output:

13:59:03.231 [main] INFO  de.rub.nds.tlsattacker.core.workflow.WorkflowExecutor - Connecting to localhost:20000
13:59:03.241 [main] INFO  de.rub.nds.tlsattacker.core.workflow.action.SendAction - Sending messages (client): Alert(null,null), 
13:59:04.246 [main] INFO  de.rub.nds.tlsattacker.core.workflow.action.ReceiveAction - Received Messages (client): 
13:59:04.246 [main] INFO  de.rub.nds.tlsattacker.core.workflow.action.SendAction - Sending messages (client): Alert(null,null), 
13:59:05.248 [main] INFO  de.rub.nds.tlsattacker.core.workflow.action.ReceiveAction - Received Messages (client): 
13:59:05.248 [main] INFO  de.rub.nds.tlsattacker.core.workflow.action.SendAction - Sending messages (client): CLIENT_HELLO, 
13:59:06.278 [main] INFO  de.rub.nds.tlsattacker.core.workflow.action.ReceiveAction - Received Messages (client): 
false
kostis commented

@ic0ns @jurajsomorovsky I am wondering whether you could comment on this issue (e.g. whether the change in behavior is intentional or not) and whether something can be done about it. Thanks in advance!

ic0ns commented

I think the issue is simply that the DatagramSocket is not connected. You only receive ICMP messages if you "register" the socket for it at the OS (by calling connect). Something like this:

diff --git a/Transport/src/main/java/de/rub/nds/tlsattacker/transport/udp/ClientUdpTransportHandler.java b/Transport/src/main/java/de/rub/nds/tlsattacker/transport/udp/ClientUdpTransportHandler.java
index 950e5f632..1c3801b3e 100644
--- a/Transport/src/main/java/de/rub/nds/tlsattacker/transport/udp/ClientUdpTransportHandler.java
+++ b/Transport/src/main/java/de/rub/nds/tlsattacker/transport/udp/ClientUdpTransportHandler.java
@@ -15,6 +15,7 @@ import de.rub.nds.tlsattacker.transport.udp.stream.UdpOutputStream;
 import java.io.IOException;
 import java.io.PushbackInputStream;
 import java.net.DatagramSocket;
+import java.net.InetAddress;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -52,6 +53,7 @@ public class ClientUdpTransportHandler extends UdpTransportHandler {
         } else {
             socket = new DatagramSocket(sourcePort);
         }
+        socket.connect(InetAddress.getByName(hostname), port);
         socket.setSoTimeout((int) timeout);
         cachedSocketState = null;
         setStreams(

The reason why we removed this "connecting" step is, is that some servers change their source port in their response, meaning that a connected socket will not receive the response. I was unaware of the interaction with ICMP - but to me this seems like a lose-lose situation - either you drop ICMP and receive all the messages, or you receive ICMP and drop some messages. Anyways - it's never a good idea to rely on ICMP messages as they are optional. To resolve the issue cleanly, one would need to move one layer down in the API stack which we will probably not do in the near future.