libssh code audit

Focus

  • Check the state machine and all transitions
  • 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