Simular Autenticação Do Sudo Em C

Introdução

Sempre tive a curiosidade de saber como escrever programas de autenticação como o su, sudo, ksudo e outros. Pesquisando na Internet, encontrei alguns códigos simples que exemplificavam o comportamento desses programas, e a partir deles decidi criar esse post.

Nesse post iremos aprender como fazer um programa de autenticação no linux usando C.

Onde as senhas estão armazenadas no sistema?

As senhas dos usuários não estão armazenadas no sistema em forma limpa por motivos de segurança. Ao invés disso, o que fica armazenado no sistema é  a forma criptografada da senha, que chamaremos nesse tutorial de hash.

O termo hash é usado para indicar a saída de uma função de hash, porém aqui irei utilizá-lo mesmo para os sistemas que utilizam cifras de bloco (ex: Blowfish). Em geral, para simplificação, considere o termo “hash” como o valor de retorno da função crypt() [3], definida nas próximas seções.

Bem, os hashes das senhas estão no arquivo /etc/shadow. Isso é válido para a maioria dos sistemas,  porém alguns utilizam módulos de segurança adicionais que, eventualmente, podem alterar a localização desses hashes.

Abaixo um exemplo de um arquivo /etc/shadow:

$ sudo cat /etc/shadow | head -n 5
root:$6$xdEyUOlpQvHIp4av$dGVtIG5hZGEgYWsgbmFvLCBzYWkgZGFraS4gaGVoZQo:15296:0:99999:7:::
bin:*:15209:0:99999:7:::
daemon:*:15209:0:99999:7:::
adm:*:15209:0:99999:7:::
lp:*:15209:0:99999:7:::

Cada linha desse arquivo armazena informações de um usuário. As linhas possuem campos separados pelo caractere ‘:’ (dois pontos). Desses campos, somente os dois primeiros serão úteis para nossos propósitos.

O primeiro campo representa o nome do usuário. O segundo já é um pouco mais complexo e tem o seguinte formato:

$id$salt$encrypt

O id é a identificação do algoritmo utilizado para gerar o hash (ex: id=1 para MD5 e id=5 para SHA-256). O salt é uma string que serve para modificar o comportamento do algoritmo de hash. Por exemplo, duas senhas iguais, encriptadas com o mesmo algoritmo, mas com salt’s diferentes, irão ter hashes diferentes. Por fim, o encrypt é o hash da senha do usuário.

Se você deseja saber mais sobre isso, veja as referências [2] e [3].

Alguma coisa sobre segurança

Programas que realizam autenticação geralmente têm privilégios altos, pois acessam o arquivo /etc/shadow, que é lido/escrito exclusivamente pelo root. Por esse motivo, esses programas têm o bit set-user-id ativado para permitir que usuários comuns utilizem de funcionalidades que só o root pode utilizar. O problema é que desenvolver aplicações set-user-id não é muito simples e qualquer erro que o programador cometer pode ser uma alvo fácil para usuários mal intecionados querendo ganhar acesso privilegiado ao sistema.

Um outro detalhe é que, assim que o usuário é autenticado, a  senha dele pode estar em texto limpo em algum lugar na memória então, nesse caso, é necessário limpar todos os buffers que contenham a senha para evitar que alguém, de alguma maneira, faça um “dump” na memória e a recupere.

Como funciona um programa de autenticação

Validar um usuário no sistema não é uma tarefa difícil, pois a própria biblioteca padrão do C nos fornece as funções necessárias para isso.

Basicamente, os passos para se realizar uma autenticação no Linux são:

  1. Obter o nome do usuário.
  2. Ler a senha do usuário.
  3. Calcular o hash da senha digitada.
  4. Ler o hash armazenado no sistema.
  5. Comparar o hash obtido em 3) com o hash de 4).
  6. Se esses hashes são iguais, então o acesso ao sistema é liberado. Caso contrário, espera-se um tempo antes de retornar ao passo 2, caso o número de tentativas não tenha excedido o limite.

OBS: Se o número de tentativas superar o limite pré-determinado, então o programa deve sair e tomar alguma providência, como bloquear o usuário e/ou alertar o administrador do fato ocorrido.

Montando o programa

Seguindo o roteiro anterior, vamos criar nosso primeiro programa de autenticação:

1) Obter o nome do usuário

Para obtermos o login do usuário, podemos lê-lo diretamente do teclado ou obter o UID real do programa e traduzir esse número para o login, com ajuda do arquivo password. Aqui, usaremos essa última abordagem, que pode ser feita usando apenas a função:

struct passwd *getpwuid(uid_t uid);

Ela retorna o endereço de uma struct passwd. Essa struct contém o campo pw_name que aponta para o nome do usuário.

2) Ler a senha do usuário

A leitura da senha será feita pela função:

char *getpass(const char *prompt);

Ela recebe uma string que servirá de prompt para o usuário e retorna a senha digitada. O interessante é que a senha é lida sem ser mostrada na tela.

3) Calcular o hash da senha digitada

A função:

char *crypt(const char *key, const char *salt);

calcula o hash da senha do usuário. O primeiro parâmetro é a senha que foi digitada. O segundo é uma string que deverá dizer qual o algoritmo a ser utilizado para gerar o hash e também qual será o salt. Essa string deve ser da seguinte forma:

$id$salt$

sendo que tudo após o último “$” será ignorado. No nosso caso, iremos utilizar os mesmos id e salt armazenados no arquivo shadow.

O retorno dessa função é a senha encriptada.

4) Ler o hash armazenado no sistema

Recuperamos o hash no shadow usando a função:

struct spwd *getspnam(const char *name);

No parâmetro indicamos o nome do usuário em questão. As informações contidas no shadow desse usuário serão retornadas pela função em forma de uma estrutura. Essa struct contém o campo sp_pwdp que aponta para o hash do usuário.

5) Comparando os hashes

Como temos os dois hashes em mãos, agora vamos compará-los. A comparação é direta mesmo e pode ser feita pela função strcmp(). Se esses dois hashes forem iguais, então o usuário foi autenticado com sucesso.

O código

Abaixo o código do mysudo.c. As instruções de compilação e de uso estão no próprio código fonte.

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <pwd.h>
#include <shadow.h>

/* [mysudo.c]
 * Esse programa simula o comportamento do sudo.
 * Ele basicamente autentica um usuário para
 * depois executar comandos como root.
 *
 * Atenção: Esse é um programa didático, criado
 * somente para propósitos educacionais.
 *
 * [Autor]
 * Marcos Paulo Ferreira (Daemonio)
 * undefinido gmail com
 * https://daemoniolabs.wordpress.com
 *
 * [Como compilar]
 * Como root:
 * # gcc -o mysudo mysudo.c -lcrypt
 *
 * Em seguida, ative o suid bit:
 * # chmod +s mysudo
 *
 * [Como executar]
 * Como usuário qualquer:
 * $ ./mysudo <comando> [argumentos do comando]
 * OBS: <comando> deve ser indicado com o caminho completo.
 *
 * [Exemplo]
 * $ ./mysudo /bin/cat /etc/shadow
 *
 * [+] Versão 1.0, by daemonio @ Dom Ago 21 00:48:30 BRT 2011
 * [+] Versão 1.1, by daemonio @ Wed Jun 13 04:00:42 BRT 2012
 *     - Acentuação e organização dos comentários.
 *     - Retirada da função setreuid(). Ela só fazia sentido
 *       se o comando fosse executado via system().
 *       Utilizando execve(), ela não é mais necessária.
 */

int main(int argc, char **argv, char **env) {
    /* Estrutura do arquivo /etc/passwd. */
    struct passwd *usuario ;
    /* Estrutura do arquivo /etc/shadow. */
    struct spwd *hum ; 
    /* Senha digitada pelo usuário. */
    char *typedpass ;
    /* Hash da senha digitada. */
    char *cryptpass ;
    /* Controlador da quantidade de
     * vezes que se pode errar a senha. */
    char count ;
    /* Usada para mostrar o prompt de
     * senha. */
    char mysudo_prompt[101] ;

    /* Testa linha de comando. */
    if ( argc == 1 ) {
        printf("[uso] %s <comando> [<argumentos do comando>]\n", argv[0]) ;
        return -1 ;
    }

    /* Como acessamos os campos do arquivo
     * /etc/shadow, devemos ter privilégios de
     * super usuário. */
    if ( geteuid() != 0 ) {
        printf("Esse programa nao tem privilegios de root.\n") ;
        return -1 ;
    }

    /* Obtém o nome do usuário através do UID. */
    usuario = getpwuid(getuid()) ;

    /* Obtém as informações no arquivo shadow. */
    hum = getspnam(usuario->pw_name) ;

    /* Testamos a saída a função, só por segurança... */
    if ( hum == NULL ) {
        printf("Usuario nao encontrado: %s\n", argv[1]) ;
        return -1;
    }

    /* Prompt. */
    sprintf(mysudo_prompt, "[mysudo] password for %s:", usuario->pw_name) ;

    count = 0 ;
    while ( count < 3 ) {
        /* Obtém a senha digitada. */
        typedpass = (char *)getpass(mysudo_prompt) ;

        /* Calcula o hash da senha usando crypt(). */
        cryptpass = (char *)crypt(typedpass, hum->sp_pwdp) ;

        /* Se os hashes são iguais, finaliza o loop. */
        if ( ! strcmp(cryptpass, hum->sp_pwdp) ) break ;

        sleep(2) ;
        printf("Sorry, try again.\n") ;
        count++ ;
    }

    /* 3 tentativas erradas. */
    if ( count == 3 ) {
        /* Aqui poderíamos colocar um syslog()
         * para alertar o administrador sobre
         * tentativas fracassadas de autenticação.
         */
        return -1 ;
    }

    /* Por segurança, limpa-se a senha na memória. */
    memset(typedpass, 0x0, strlen(typedpass)) ;

    /* Executa o comando com seus argumentos. */
    if ( execve(argv[1], &argv[1], env) < 0 ) {
        printf("%s: comando `%s' nao encontrado.\n", argv[0], argv[1]) ;
        return -1 ;
    }

    return 0 ;
}
/* EOF */

Exemplo de Uso

Agora vamos testar o programa. Para isso, criarei um arquivo no diretório /root utilizando um usuário comum.

$ whoami
daemonio
$ ./mysudo /bin/touch /root/arquivoqualquer # digite a senha
$ ./mysudo /bin/ls -la /root # digite a senha
-rw-rw-r--.  1 root root      0 Jun 13 04:40 arquivoqualquer

Referências

[1] Adding shadow support to a C program (Acessado em: Agosto/2011)
http://tldp.org/HOWTO/Shadow-Password-HOWTO-8.html

[2] Armazenamento de senhas no Linux, by Elgio Schlemer (Acessado em: Agosto/2011)
http://www.vivaolinux.com.br/artigo/Armazenamento-de-senhas-no-Linux

[3] Manpage da função crypt (Acessado em: Agosto/2011)
$ man crypt

[4] How to Check a User Password on Linux (Acessado em: Agosto/2011)
http://www.digitalpeer.com/id/howto

[5] check Linux password from /etc/shadow (Acessado em: Agosto/2011)
http://www.linuxquestions.org/questions/programming-9/check-linux-password-from-etc-shadow-680104/

4 pensamentos sobre “Simular Autenticação Do Sudo Em C

  1. Pingback: Quebrador De Senhas Em C | Daemonio Labs

Deixe um comentário