libssh code audit ================= Focus ----- * Check the state machine and all transitions * Note: The state machine is the same for the client and the server * We had a CVE in this area last year: https://www.libssh.org/security/advisories/CVE-2018-10933.txt * Check input sanitization (both from function parameters and configuration files) * config file parsing (`src/config.c`) * ssh key parsing (`src/pki_container*.c`) * known_hosts file parsing (`src/knownhosts.c`) Security process ---------------- https://www.libssh.org/development/security-process/ Examples -------- Server: `examples/ssh_server_fork.c` Client: `examples/ssh_client.c` Fuzzing ------- See `tests/fuzz` https://github.com/google/oss-fuzz/tree/master/projects/libssh Currently only the first state of the state machine after connect is fuzzed. We miss inputs (starting points) for oss-fuzz. For this libssh would need to be extened that in src/socket.c the packets are also written to a file (client and server). https://gitlab.com/libssh/libssh-mirror/merge_requests/59 We need a client and server similar to the fuzzer so we can create traces. Example: https://gitlab.com/gnutls/gnutls/blob/master/fuzz/README-adding-traces.md Those traces should be feeded into American Fuzzy Loop so it creates variation of those inputs (needs to run for 2 days). Communication ------------- IRC: #libssh-pentest @ Freenode People to talk to: * Andreas Schneider * IRC: gladiac * Matrix: @asn:matrix.org * Jakub Jelen * IRC: jjelen * Anderson Sasaki * IRC: ansasaki * Aris Adamantiadis * IRC: ar1s Schedule -------- Possibly CW39 & CW40 State machine: -------------- The state machine is not implemented in a single file, but it is scattered in the code, which makes it hard to find the exact global state. It is basically a composition of various state machines. The global state is composed by the session state (``session->session_state``) and the states of the other contexts (``session->socket_state``, ``session->dh_handshake_state``, ``session->auth.state``, ``session->global_req_state``, ``channel->state``, etc.). When a state machine is started, the callbacks and an initial state are set. These callbacks usually change the ``session->pending_call_state`` to the current operation being performed, which is expected to be called again in non-blocking sessions. Then a message is sent to the server and a reaction from the server is expected. The packets from the server are captured by the socket polling, which calls the appropriate callbacks for each message type (see ``packet.c``). These callbacks change the global state by changing the session state or the state of one of the other contexts. The incoming packets are filtered (in ``ssh_packet_incoming_filter()`` in ``packet.c``) by the expected global state before being processed (by ``ssh_packet_process()``). Although not complete, it is possible to see the majority of the expected transitions made by the callbacks, documented in comments. In the beginning of the ``packet.c`` file, it is possible to see the default callbacks called for each message type. If the session is non-blocking, it is expected the API to be called many times, while the return is ``SSH_AGAIN``. If the session is blocking, the call will block in a loop. For example to connect to a host as a client: * Call ``ssh_connect()``, which sets the callback as ``ssh_client_connection_callback``, the session state to ``SSH_SESSION_STATE_CONNECTING``, and pending_call_state to ``PENDING_CALL_CONNECT`` * This will call ``ssh_handle_packets()``, which will poll the socket and call the appropriate callbacks for each packet arriving * The session state changes to ``SSH_SESSION_STATE_SOCKET_CONNECTED`` when the socket connection is stablished * The client sends the banner. Then it expects the server to send its banner * The server sends its banner. The polling gets the packet and calls ``callback_receive_banner()`` setting the state to ``SSH_SESSION_BANNER_RECEIVED`` * The client sends the initial key exchange info and sets the session state as ``SSH_SESSION_STATE_INITIAL_KEX`` * The polling gets the server packet with its initial key exchange info; the client changes the state to ``SSH_SESSION_STATE_KEXINIT_RECEIVED`` * The client selects the key exchange methods, sets the session state as ``SSH_SESSION_STATE_DH``, and starts the key exchange (a.k.a DH) state machine by calling ``dh_handshake()`` * The session state does not change until the state machine handling the key exchange is finished, which is informed by the ``session->dh_handshake_state`` * When the key exchange is finished, the state is changed to ``SSH_SESSION_AUTHENTICATING``. * Again, this doesn't change until the state machine handling the authentication finishes, setting the ``session->session_state`` to ``SSH_SESSION_AUTHENTICATED`` or ``SSH_SESSION_STATE_ERROR`` Now the key exchange is finished and the user can use one of the authentication methods to authenticate. ###### tags: `libssh`