Secure Shell - SSH

Implementação de um Cliente SSH

Trabalho Final da Disciplina Segurança de Dados 2/03

Edans Flavius de Oliveira Sandes

Departamento de Ciência da Computação
Universidade de Brasília
17 de Dezembro de 2003

 

 

Introdução

 

SSH é um protocolo para login remoto seguro e outros serviços contextualizados em redes abertas. Esse protocolo é dividido em três componentes básicos:

 

·        Camada de Transporte: Provê sigilo, integridade e autenticação do servidor. Opcionalmente, pode ser oferecido um serviço de compressão de dados. Essa camada geralmente é rodada em cima de uma conexão TCP/IP, mas é possivel que ela seja usada em cima de qualquer outro tipo de protocolo confiável e orientado a conexão.

·        Protocolo de Autenticação: Autentica o usuário perante o servidor. Esse protocolo necessita da camada de transporte para o seu funcionamento.

·        Protocolo de Conexão: Permite a multiplexação da conexão entre vários canais lógicos. Esse protocolo depende do protocolo de autenticação. Nessa camada encontramos recursos como shell interativo, tunelamento de portas TCP/IP e conexões X11.

 

 

            O SSH é bastante flexível no sentido de que os algoritmos usados em uma conexão são negociados entre o cliente e o servidor. Isso permite que algoritmos fracos sejam removidos ou novos algoritmos sejam testados sem que haja uma mudança no protocolo.

 

            A seguir será mostrado como funciona cada um dos componentes que foram citados acima.


 

1. Camada de Transporte

 

 

            A camada de tranporte do SSH é um protocolo seguro de baixo nivel que fornece encriptação, integridade e autenticação do servidor. Observe que aqui somente é feita a autenticação do servidor, sendo que a autenticação do usuario é responsabilidade do protocolo de autenticação.

           

            Os pacotes que são recebidos e enviados por essa camada possuem o seguinte formato:

Tamanho (4 bytes)

Tamanho do preenchimento (1 byte)

Conteúdo (n1 bytes)

 

 

Preenchimento (n2 bytes)

MAC (m bytes)

 

            Tamanho: Indica o comprimento do pacote sem incluir o MAC e o próprio campo Tamanho. Tamanho = 1 + n1 + n2.

            Tamanho do preenchimento = “n2”: Número  de bytes que serão colocados no pacote para que o tamanho total (sem o MAC) seja múltiplo de 8 (ou do tamanho do bloco usado na encriptacao). n2 não pode ser menor que 4.

            Conteúdo: Conteúdo útil do pacote. Se algum algoritmo de compressão estiver sendo usado, esse campo é compactado.

            Preenchimento: “n2”  bytes aleatórios.

            MAC: Código de autenticação de mensagem. O número “m” de bytes varia de acordo com o algoritmo que está sendo utilizado. O cálculo do MAC é: mac = MAC(key, no_do_pacote || pacote_não_encriptado), onde no_do_pacote é um contador de pacotes enviados (ou recebidos).

 

 


 

1.1 Protocolo de troca de chaves

 

0-     Troca de strings de versão: Após a conexão ser estabelecida, cliente e servidor trocam strings no formato: "SSH-protoversion-softwareversion comentários\r\n". Essa string é chamada V_S para a string enviada pelo servidor e V_C para a do cliente. A partir de então, todos os dados são transmitidos no formato do pacote citado no item anterior.

 

1-     Negociação dos algoritmos usados: Ambos os lados enviam o pacote SSH_MSG_KEXINIT a seguir:

 

 

byte     

 SSH_MSG_KEXINIT

byte[16]

 cookie (bytes aleatórios)

string  

 Algoritmos de troca de chaves

string  

 Algoritmos de chave pública

string  

 Algoritmos de encriptação cliente-servidor

string  

 Algoritmos de encriptação servidor-cliente

string  

 Algoritmos MAC cliente-servidor

string  

 Algoritmos MAC servidor-cliente

string  

 Algoritmos compressão cliente-servidor

string  

 Algoritmos compressão servidor-cliente

string  

 Linguagens cliente-servidor

string  

 Linguagens servidor-cliente

boolean

 first_kex_packet_follows

uint32 

 0 (reservado)

 

            Os campos em destaque são as strings com os nomes dos algoritmos em ordem de preferência. Após os dois lados enviarem esse pacote, a camada de transporte decide qual o algoritmo que será usado no protocolo em cada um dos campos. O algoritmo usado é o primeiro que o cliente possuir que o servidor suporte.


 

2.      Protocolo de troca de chaves. A saída dessa etapa é um segredo compartilhado K e o resultado H de um hash. Esse hash é usado como identificador de secao e como parte dos dados assinados pelo servidor para ele se autenticar perante o cliente.

O protocolo de troca de chaves padrão do SSH é o diffie-hellman-group1-sha1 explicado a seguir:

2.1   Cliente:

2.1.1          gera X randômico

2.1.2          envia

byte
SSH_MSG_KEXDH_INIT
mpint
e = (g^x mod p)

Onde g=2 e p o seguinte primo(120 bits):

 

FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1

29024E088A67CC74020BBEA63B139B22514A08798E3404DD

EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245

E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED

EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381

 

2.2. Servidor:

2.2.1          gera Y randômico

2.2.2          calcula:

·         f = g y mod p

·         K = e y mod p

·         H = SHA-1(V_C || V_S || I_C || I_S || K_S || e || f || K)

Onde V_C, V_S são as strings de versão; I_C e I_S os conteúdos dos pacotes SSH_MSG_KEXINIT; e K_S a chave pública do servidor.

 

2.2.3          gera S = assinatura sobre H usando a chave privada

2.2.4          envia:

byte  

SSH_MSG_KEXDH_REPLY

string

K_S (chave publica)

mpint 

f

string

S – Assinatura sobre H

 

2.3 Cliente:

2.3.1          Verifica se K_S é realmente a chave pública do servidor, podendo ser feito uma busca em um histórico de conexões. Caso esse passo não seja executado corretamente, o protocolo está sujeito ao ataque man-in-the-middle.

2.3.2          Calcula:

·         K = f k mod p

·         H = SHA-1(V_C || V_S || I_C || I_S || K_S || e || f || K)

2.3.2          Verifica S com K_S

 


3. Efetivação do uso de novas chaves com os algoritmos negociados.

Cada lado da conexão envia a seguinte mensagem

byte  

SSH_MSG_NEWKEYS

A partir de então, todos os pacotes enviados são encriptados de acordo com o algoritmo negociado

 

 

1.2 Reexecução da troca de chaves

 

            É recomendável que as chaves de sessão sejam trocadas a cada 1Gb de dados transmitidos ou de hora em hora. Para efetuar essa troca basta que um dos lados mande um pacote do tipo SSH_MSG_KEXINIT. Isso for ç a a execução dos passos 1 ao 3 da troca de chaves. O Identificador da sessão é o “H” da primeira troca de chave, e ele mantêm o seu valor até o término da conecção.

 

1.3 Vetores de inicialização e chaves de Sessão

                       

                        Os vetores de inicializacao e chaves de sessao para os algoritmos de encriptação e MACs são criados da seguinte forma:

 

 

 

cliente - servidor

servidor - cliente

VI

HASH(K || H || "A" || ID)

HASH(K || H || "B" || ID)

Chave de Encriptação

HASH(K || H || "C" || ID)

HASH(K || H || "D" || ID)

Chave do MAC

HASH(K || H || "E" || ID)

HASH(K || H || "F" || ID)

 

            “HASH” aqui é o algoritmo utilizado na troca de chaves negociada. No caso do diffie-hellman-group1-sha1 por exemplo, HASH é o SHA1.

 

1.4 Requisição de Serviços

 

                        Após o protocolo terminar a troca de chaves e receber o pacote SSH_MSG_NEWKEYS, o sistema pode tomar vários rumos, onde o caso mais comum é a autenticação do usuário. Para isso, deve-se requisitar um serviço através do seguinte pacote:

byte  

SSH_MSG_SERVICE_REQUEST

string

Nome do serviço

            Se o servidor reconhecer esse serviço, ele deve retornar o próximo pacote:

byte  

SSH_MSG_SERVICE_ACCEPT

string

Nome do serviço

Se o servidor recusar o pedido, o cliente deve receber SSH_MSG_DISCONNECT e em seguida ser desconectado.

            Obs.: Para o caso da autenticação, nome do serviço é “ssh-userauth”.

 

 

1.5 Algoritmos

 

            A seguir temos a tabela com os algoritmos padronizados que foram definidos no protocolo de transporte do SSH. Como citado anteriormente, outros algoritmos podem ser acrescentados nessa lista. Basta utilizar nomes diferente dos citados a seguir, pois estes são reservados para o protocolo.

 

Compressão

none, zlib  

Encriptação

3des-cbc,blowfish-cbc, twofish256-cbc,twofish-, twofish192-cbc, twofish128-cbc, aes256-cbc, aes192-cbc, aes128-cbc, serpent256-cbc, serpent192-cbc, serpent128-cbc, arcfour, idea-cbc, cast128-cbc, none

MAC

hmac-sha1, hmac-sha1-96, hmac-md5, hmac-md5-96, none

Troca de Chaves

diffie-hellman-group1-sha1

Algoritmos de chave pública

ssh-dss, ssh-rsa, x509v3-sign-rsa, x509v3-sign-dss, spki-sign-rsa, spki-sign-dss, pgp-sign-rsa, pgp-sign-dss

Os algoritmos em negrito são obrigatórios.


 

2. Protocolo de Autenticação

 

            Aqui são executados os procedimentos relativos a autenticação do usuário. É assumido que a camada de transporte já está provendo integridade e sigilo na comunicação.

           

            Para ocorrer a autenticação, o cliente deve enviar a seguinte mensagem:

byte  

SSH_MSG_USERAUTH_REQUEST

string

user name

string 

Nome do serviço

string

Nome do método

… extra …

            O nome do serviço especifica qual o serviço que será iniciado apóso a autenticaçao. Se o serviço não estiver disponível, o servidor irá desconectar o cliente.

            O método é o nome do procedimento usado para a autenticação.

 

            O servidor pode responder de duas formas:

                        Autenticação confirmada:

byte  

SSH_MSG_USERAUTH_SUCCESS

                        Autenticação falhou:

byte  

SSH_MSG_USERAUTH_FAILURE

string

Métodos disponíveis

boolean 

Sucesso parcial

            Onde sucesso parcial indica que o método de autenticação aceitou o usuário, mas o servidor não aceitou essa autenticação.

 

            Um exemplo de método de autenticação é o que utiliza senha. O pacote SSH_MSG_USERAUTH_REQUEST relative a ele é o seguinte:

byte  

SSH_MSG_USERAUTH_REQUEST

string

user name

string 

Nome do serviço

string

“password”

boolean

FALSE

string

senha

            Embora a senha é informada “às claras”, o pacote todo é criptografado (ao contrário do telnet, por exemplo), impedindo assim o vazamento da senha se alguém estiver ouvindo o tráfego.

            O servidor também pode responder com SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, informando que a senha do usuário expirou e que ela deve ser trocada. Para isso, o cliente deve enviadar o seguinte pacote:

byte  

SSH_MSG_USERAUTH_REQUEST

string

user name

string 

Nome do serviço

string

“password”

boolean

TRUE

string

Senha antiga

string

Senha nova

 

 

 

3. Protocolo de Comunicação

 

           

            Esta é aparte que provê execução de commandos remotos, abertura de shells, tunelamento de conexões TCP/IP e de conexões X11. Todos esses canais são multiplexados em um único tunel encriptado.

 

            O protocolo de comunicação tem o seu funcionamento baseado em canais. Cada canal é identificado por um número em cada uma das pontas. Esse número pode ser diferente em cada um dos lados.

           

            Canais possuem fluxo controlado. Os dados são transmitidos através deles até quando o espaço disponível em suas janelas se esgotar. Quando isso ocorrer, o tamanho das janelas deve ser incrementado com o envio de uma mensagem adequada.

 

            Para abrir um canal na conexão, a próxima mensagem deve ser transmitida por um dos lados.

byte  

SSH_MSG_CHANNEL_OPEN

string

Tipo do canal

uint32 

no do canal remetente

uint32

Tamanho inicial da janela

uint32

Tamanho máximo dos pacotes

… extra …

            O outro lado deve responder o pedido com um dos dois pacotes a seguir:

byte  

SSH_MSG_CHANNEL_OPEN_CONFIRMATION

uint32 

no do canal destinatário

uint32

no do canal remetente

uint32

Tamanho inicial da janela

uint32

Tamanho máximo dos pacotes

… extra …

 

byte  

SSH_MSG_CHANNEL_OPEN_FAILURE

uint32 

no do canal destinatário

uint32

código do motivo

string

Informaçao adicional

string

Tag de lingugagem

                        Observe que o no do canal destinatário dessas duas ultimas mensagens é o #do canal remetente de quem requisitou a abertura do canal (SSH_MSG_CHANNEL_OPEN).

     Para transferir dados pelo canal, utiliza-se a seguinte mensagem:

byte  

SSH_MSG_CHANNEL_DATA

uint32 

no do canal destinatário

string

Dados

                        ou

byte  

SSH_MSG_CHANNEL_EXTENDED_DATA

uint32

no do canal destinatário

uint32 

Tipo dos dados

string

Dados

            O tamanho disponível da janela de quem receber os dados deve ser decrementada pelo número de bytes do campo dados, e ao se esgotar a janela a mensagem a seguir deve ser enviada pelo destinatário:

byte  

SSH_MSG_CHANNEL_WINDOW_ADJUST

uint32 

no do canal destinatário

uint32

no de bytes incremental

            Com isso o a janela disponibiliza o número de bytes solicitado.

 

           

 

 

4. Implementação do cliente

 

           

A seguir, foi implementado um protótipo de um cliente que se comunica com um servidor SSH. Este cliente possui os seguintes recursos:

 

·         Algoritmo de encriptação: 3des-cbc, none

·         Algoritmos MAC: hmac-md5, hmac-sha1

·         Algoritmo de troca de chaves: diffie-hellman-group1-sha1

·         Algoritmo de chave pública: ssh-dss

·         Protocolo de autenticação: password

·         Tipo de serviço suportado: shell remoto

·        Visualização de pacotes

·        Botão de reexecutar troca de chaves

Interface Principal

 

O 1º botão conecta ou disconecta em um servidor.

O 2º botão (reexecutar troca de chaves) funciona da seguinte forma: Ao clicá-lo, novas chaves são estabelecidas. Como uma funcionalidade didática, os algoritmos de encriptação (3des-cbc e none) são alternados em cada nova troca, assim como os algoritmos de MAC (hmac-md5, hmac-sha1). Se o servidor não suportar o hmac-md5 ou o none, o cliente não funcionará. Logo, não use o botão de reexecutar troca de chaves se nao se conhecer os algoritmos suportados pelo servidor.

A listagem que aparece no lado direito são os pacotes que entram e saem do aplicativo. Ao dar um duplo clique é possível visualizar o conteúdo de cada pacote.

 

Alguns exemplos de pacotes (decriptados):

 

pacote SSH_MSG_USERAUTH_REQUEST para autenticação

 

 

 

 

pacote SSH_MSG_CHANNEL_DATA enviando o commando “date” para o shell

 

 

resultado de “date” enviado para o shell:”Tue Dec 16 23:52:26 BRST 2003”

 

Na barra de status são mostrados os algoritmos utilizados para encriptar e para gerar os MAC’s. Nessa barra existe um botão (“KEX Status”) que mostra um janela com os dados da sessão (H, K, Pub.Key do Server). O MD5 fingerprint da chave pública é mostrada diretamente na barra de status.

 

barra de status com os dados da conexão

janela informando K, H e a Chave Pública do servidor

           

 

Baixe aqui o programa com fonte incluído: cliente_ssh.jar

5. Bibliografia

 

 

IETF - Internet-Drafts:

 

 [SSH-ARCH]

              Ylonen, T., "SSH Protocol Architecture", I-D

              draft-ietf-architecture-15.txt, Oct 2003.

[SSH-TRANS]

              Ylonen, T., "SSH Transport Layer Protocol", I-D

              draft-ietf-transport-17.txt, Oct 2003.

[SSH-USERAUTH]

              Ylonen, T., "SSH Authentication Protocol", I-D

              draft-ietf-userauth-18.txt, Oct 2003.

[SSH-CONNECT]

              Ylonen, T., "SSH Connection Protocol", I-D

              draft-ietf-connect-18.txt, Oct 2003.

[SSH-NUMBERS]

              Lehtinen, S. and D. Moffat, "SSH Protocol Assigned

              Numbers", I-D draft-ietf-secsh-assignednumbers-05.txt, Oct

              2003.

 

Outros Links:

 

http://bob.marlboro.edu/~msie/2004/it/nov7-ether-crypto/lecture.html

http://javassh.org/

http://www.jcraft.com/jsch/

http://www.petitcolas.net/fabien/software/java%2Dssh/

http://3sp.com/download/download.php

http://www.lysator.liu.se/~nisse/lsh/