Como escrevo um teste para o login do sistema?


9

Eu escrevi um script CGI Python que chama bashcomandos e precisa testar se há um login bem-sucedido no host.

Como escrevo um teste para isso?

Por exemplo, eu poderia criar um bashscript que teste uma determinada combinação de nome de usuário e senha em relação ao usuário registrado no host?


11
Talvez você possa ver o código por trás do loginprograma.
Kevin

Não está relacionado à pergunta, mas espero que você esteja criptografando o tráfego no servidor da Web para que os logins dos usuários não possam ser detectados.
Jw013

Respostas:


8

Usar o PAM é a melhor solução. Você pode escrever um código C pequeno ou instalar o pacote python-pam e usar um script python que acompanha o pacote python-pam. Vejo/usr/share/doc/python-pam/examples/pamtest.py


Eu tento o PAM, mas não funcionou. Mas tento novamente este exemplo e funciona.
jcubic

11
No OpenSUSE 12.3 (python-pam 0.5.0-84.1.1) e 13.1 (0.5.0-87.1.2), o caminho completo para pamtest.py é /usr/share/doc/packages/python-pam/examples/pamtest.pyO script pamtest.py pode ser usado para testar credenciais em sistemas usando PAM para autenticação, está incluído no pacote python-pam (que requer python) e, em algumas distribuições, o caminho completo é /usr/share/doc/python-pam/examples/pamtest.py.
precisa saber é o seguinte

5

A abordagem correta para testar se um usuário pode efetuar login é efetivamente fazer login como esse usuário.

Portanto, o que eu recomendo é usar o script CGI expectpara executar su, passar uma senha e executar o comando que deve ser executado. Aqui está um rascunho de um script expect que faz exatamente isso (aviso: absolutamente não testado, e eu não sou fluente em esperar). Substitua o nome de usuário, senha e de comando (onde eu escrevi bob, swordfishe somecommand); não se esqueça de citar corretamente.

spawn "/bin/su" "bob"
expect "Password:"
send "swordfish\r"
expect "^\\$"
send "somecommand"
expect -re "^\\$"
send "exit\r"
expect eof

Se você realmente não deseja executar o comando por meio de uma camada de su(por exemplo, porque o que você faz deve ser executado pelo próprio processo CGI), use expect para executar o comando truee verifique se o status de retorno é 0.

Outra abordagem seria usar o PAM diretamente no seu aplicativo, através da ligação do PAM ao Python .


É incrível, a única solução que sem acesso root.
jcubic

isso funciona su -c true bob && echo successisso vergonha que su não aceitam senha como argumento
jcubic

Eu testei a supartir do script CGI e ele precisa de um terminal para funcionar.
jcubic

3
@jcubic É realmente uma idéia estúpida colocar uma senha em um argumento de linha de comando, porque os argumentos de linha de comando são públicos em um sistema Unix. A remoção da senha forneceria o mesmo nível de segurança que colocá-la na linha de comando. E seria muito mais fácil verificar: /bin/trueseria suficiente.
ceving 25/03/2015

2

Para responder mais especificamente: "É possível criar um script bash que testará uma combinação de nome de usuário e senha específica contra o usuário registrado no host?"

Sim.

#!/bin/bash
uid=`id -u`

if [ $uid -ne 0 ]; then 
    echo "You must be root to run this"
    exit 1
fi

if [ $# -lt 1 ]; then
    echo "You must supply a username to check - ($# supplied)"
    exit 1
fi

username=$1
salt=`grep $username /etc/shadow | awk -F: ' {print substr($2,4,8)}'`

if [ "$salt" != "" ]; then

        newpass=`openssl passwd -1 -salt $salt`
        grep $username  /etc/shadow | grep -q  $newpass && echo "Success" || echo "Failure"

fi

2
Isso funcionou para você? Estou vendo que você está verificando uma senha de sombra existente contra a senha de sombra, mas onde está o hash envolvido aqui?
Nikhil Mulley

Eu testei e não fazer o trabalho
jcubic

3
Péssima ideia! Você está assumindo que seu programa está sendo executado como root ou, pelo menos, como o shadowgrupo, o que não é muito recomendado para um CGI: você precisaria de outra camada de escalonamento de privilégios. E você está assumindo um algoritmo de hash de senha específico (um suportado pelo openssl) e um local de armazenamento de senhas ( /etc/shadowem oposição a, por exemplo, NIS ou LDAP), que pode ou não ser o realmente usado para esse usuário em particular.
Gilles 'SO- stop be evil'

2

Há uma solução PAM 'C', 'Python' citada aqui, deixe-me colocar a solução perl também :-)

Fonte: http://search.cpan.org/~nikip/Authen-PAM-0.16/PAM/FAQ.pod#1._Can_I_authenticate_a_user_non_interactively ?

#!/usr/bin/perl

  use Authen::PAM;
  use POSIX qw(ttyname);

  $service = "login";
  $username = "foo";
  $password = "bar";
  $tty_name = ttyname(fileno(STDIN));

  sub my_conv_func {
    my @res;
    while ( @_ ) {
        my $code = shift;
        my $msg = shift;
        my $ans = "";

        $ans = $username if ($code == PAM_PROMPT_ECHO_ON() );
        $ans = $password if ($code == PAM_PROMPT_ECHO_OFF() );

        push @res, (PAM_SUCCESS(),$ans);
    }
    push @res, PAM_SUCCESS();
    return @res;
  }

  ref($pamh = new Authen::PAM($service, $username, \&my_conv_func)) ||
         die "Error code $pamh during PAM init!";

  $res = $pamh->pam_set_item(PAM_TTY(), $tty_name);
  $res = $pamh->pam_authenticate;
  print $pamh->pam_strerror($res),"\n" unless $res == PAM_SUCCESS();

Sim, ok, mas a pergunta era sobre um script CGI escrito em Python.
Gilles 'SO- stop be evil' (

1

Se você tiver acesso root e estiver usando senhas md5, e você apenas precisar comparar senhas, poderá usar o módulo perl Crypt :: PasswdMD5 . Pegue o MD5 Hash em / etc / shadow, retire os $ 1 $ e depois divida os $ restantes. Campo 1 = Sal, Campo 2 = texto criptografado. Em seguida, faça o hash da entrada de texto em seu CGI, compare com o texto criptografado e Bob é seu tio.

#!/usr/bin/env perl

use Crypt::PasswdMD5;

my $user                = $ARGV[0];
my $plain               = $ARGV[1];
my $check               = qx{ grep $user /etc/shadow | cut -d: -f2 };
chomp($check);
my($salt,$md5txt)       = $check =~ m/\$1\$([^\$].+)\$(.*)$/;
my $pass                = unix_md5_crypt($plain, $salt);

if ( "$check" eq "$pass" ) {
        print "OK","\n";
} else {
        print "ERR","\n";
}

2
claro e simples :-). Isso funcionará desde que o / etc / passwd use o hash md5. Caso a autenticação (nsswitch) seja diferente para o sistema, é melhor usar os módulos pam.
Nikhil Mulley

2
Péssima ideia! Você está assumindo que seu programa está sendo executado como root ou, pelo menos, como o shadowgrupo, o que não é muito recomendado para um CGI: você precisaria de outra camada de escalonamento de privilégios. E você está assumindo um algoritmo de hash de senha específico (MD5 e não o bcrypt ou outro algoritmo recomendado) e o local de armazenamento da senha ( /etc/shadowem oposição a, por exemplo, NIS ou LDAP), que pode ou não ser o realmente usado para esse usuário em particular.
Gilles 'SO- stop be evil' '

0

Depois de alguma pesquisa, escrevi este programa em C que pode ser usado no script

#include <stdlib.h>
#include <stdio.h>
#include <pwd.h>
#include <shadow.h>
#include <string.h>
#include <crypt.h>
#include <unistd.h>
#include <libgen.h>

int main(int argc, char **argv) {
    struct spwd *pwd;
    if (argc != 3) {
        printf("usage:\n\t%s [username] [password]\n", basename(argv[0]));
        return 1;
    } else if (getuid() == 0) {
        pwd = getspnam(argv[1]);
        return strcmp(crypt(argv[2], pwd->sp_pwdp), pwd->sp_pwdp);
    } else {
        printf("You need to be root\n");
        return 1;
    }
}

Você o compila com:

gcc -Wall password_check.c /usr/lib/libcrypt.a -o check_passwd

Você pode usá-lo como

sudo ./check_passwd <user> <password> && echo "success" || echo "failure"

11
Péssima ideia! Você está assumindo que seu programa está sendo executado como root ou, pelo menos, como o shadowgrupo, o que não é muito recomendado para um CGI: você precisaria de outra camada de escalonamento de privilégios. E você está assumindo um algoritmo de hash de senha específico (um suportado pelo openssl) e um local de armazenamento de senhas ( /etc/shadowem oposição a, por exemplo, NIS ou LDAP), que pode ou não ser o realmente usado para esse usuário em particular. Use o PAM, ele conhece seu trabalho.
Gilles 'SO- stop be evil'

Sim, eu sei, mas pensei que isso não é possível sem raiz. Todas as outras soluções também usam raiz, exceto a sua.
jcubic

0

Como você mencionou que está usando CGI em python, provavelmente é apropriado supor que você esteja usando o Apache como seu servidor httpd. Nesse caso, deixe o processo de autenticação do seu programa no Apache e deixe apenas as pessoas autenticadas executarem seus scripts / programas cgi.

Existem amplos módulos que podem fazer autenticação para você no Apache, isso realmente depende do tipo de mecanismo de autenticação que você está procurando. A maneira que você citou na pergunta parece estar relacionada à autenticação da conta do host local com base nos arquivos / etc / passwd, shadow. Módulo que vem à minha pesquisa rápida sobre isso é mod_auth_shadow. A vantagem é que você está permitindo que alguém com autoridade (executando na porta de privilégio 80) autentique o usuário / senha para você e pode confiar nas informações autenticadas do usuário para executar os comandos em nome do usuário, se necessário.

Bons links para começar:

http://adam.shand.net/archives/2008/apache_tips_and_tricks/#index5h2

http://mod-auth-shadow.sourceforge.net/

http://www.howtoforge.com/apache_mod_auth_shadow_debian_ubuntu

Outra abordagem é usar o módulo SuEXEc do Apache, que executa processos (programas cgi) em nome do usuário autenticado.


Esse script CGI é um serviço JSON-RPC chamado via Ajax e preciso do método de login que retorne um token, o token deve ser retornado se o login for bem-sucedido. Então, basicamente, todo usuário precisa ser capaz de executar esse script.
jcubic

0

Este código usando PAM funcionou para mim:

#include <security/pam_appl.h>
#include <security/pam_misc.h>
#include <stdio.h>
#include <string.h>

// Define custom PAM conversation function
int custom_converation(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr)
{
    // Provide password for the PAM conversation response that was passed into appdata_ptr
    struct pam_response* reply = (struct pam_response* )malloc(sizeof(struct pam_response));
    reply[0].resp = (char*)appdata_ptr;
    reply[0].resp_retcode = 0;

    *resp = reply;

    return PAM_SUCCESS;
}

int main (int argc, char* argv[]) 
{
    if (argc > 2)
    {
        // Set up a custom PAM conversation passing in authentication password
        char* password = (char*)malloc(strlen(argv[2]) + 1);
        strcpy(password, argv[2]);        
        struct pam_conv pamc = { custom_converation, password };
        pam_handle_t* pamh; 
        int retval;

        // Start PAM - just associate with something simple like the "whoami" command
        if ((retval = pam_start("whoami", argv[1], &pamc, &pamh)) == PAM_SUCCESS)
        {
            // Authenticate the user
            if ((retval = pam_authenticate(pamh, 0)) == PAM_SUCCESS) 
                fprintf(stdout, "OK\n"); 
            else 
                fprintf(stderr, "FAIL: pam_authentication failed.\n"); 

            // All done
            pam_end(pamh, 0); 
            return retval; 
        }
        else
        {
            fprintf(stderr, "FAIL: pam_start failed.\n"); 
            return retval;
        }
    }

    fprintf(stderr, "FAIL: expected two arguments for user name and password.\n"); 
    return 1; 
}

Você poderia adicionar algumas informações de contexto para que "parecesse" mais uma resposta?
Volker Siegel

-3

A melhor coisa que você pode fazer, se precisar de um script para efetuar login em um host, é configurar uma chave ssh entre os hosts.

Link: http://pkeck.myweb.uga.edu/ssh/

Eu praticamente tirei isso da página


Primeiro, instale o OpenSSH em duas máquinas UNIX, com muita dificuldade. Isso funciona melhor usando chaves DSA e SSH2 por padrão, tanto quanto eu sei. Todos os outros HOWTOs que eu vi parecem lidar com chaves RSA e SSH1, e as instruções não surpreendentemente falham ao trabalhar com SSH2. Em cada máquina, digite ssh somemachine.example.com e faça uma conexão com sua senha normal. Isso criará um diretório .ssh no diretório inicial com as permissões adequadas. Na sua máquina principal, na qual você deseja que suas chaves secretas estejam (digamos muito), digite

ssh-keygen -t dsa

Isso solicitará uma senha secreta. Se essa é sua chave de identidade principal, use uma boa senha. Se isso funcionar corretamente, você obterá dois arquivos chamados id_dsa e id_dsa.pub no seu diretório .ssh. Nota: é possível apenas pressionar a tecla Enter quando for solicitada uma senha, que fará uma tecla sem senha. Esta é uma Bad Idea ™ para uma chave de identidade, então não faça isso! Veja abaixo o uso de teclas sem senhas.

scp ~/.ssh/id_dsa.pub burly:.ssh/authorized_keys2

Copie o arquivo id_dsa.pub para o diretório .ssh do outro host com o nome author_keys2. Agora, o corpulento está pronto para aceitar sua chave ssh. Como saber quais teclas usar? O comando ssh-add fará isso. Para um teste, digite

ssh-agent sh -c 'ssh-add < /dev/null && bash'

Isso iniciará o ssh-agent, adicionará sua identidade padrão (solicitando sua senha) e gerará um shell bash. Nesse novo shell, você deve ser capaz de:

ssh burly

Você deve conseguir fazer login


Embora isso seja verdade, não parece ser relevante para a pergunta, que é sobre um aplicativo acessado por meio de um navegador da web.
Gilles 'SO- stop be evil'
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.