Jak napisać test na logowanie do systemu?

9

Napisałem skrypt CGI w Pythonie, który wywołuje bashpolecenia i musi przetestować udane logowanie do hosta.

Jak napisać na to test?

Na przykład, czy mogę utworzyć bashskrypt, który przetestuje daną kombinację nazwy użytkownika i hasła względem zarejestrowanego użytkownika na hoście?

jcubic
źródło
1
Być może mógłbyś spojrzeć na kod loginprogramu.
Kevin
Nie ma związku z pytaniem, ale mam nadzieję, że szyfrujesz ruch do swojego serwera WWW, aby nie można było węszyć loginu użytkownika.
jw013

Odpowiedzi:

8

Korzystanie z PAM jest najlepszym rozwiązaniem. Możesz napisać mały kod C lub zainstalować pakiet python-pam i użyć skryptu python, który jest dołączony do pakietu python-pam. Widzieć/usr/share/doc/python-pam/examples/pamtest.py

Michał Šrajer
źródło
Próbuję PAM, ale to nie zadziałało. Ale próbuję ponownie ten przykład i działa.
jcubic
1
W OpenSUSE 12.3 (python-pam 0.5.0-84.1.1) i 13.1 (0.5.0-87.1.2) pełna ścieżka do pamtest.py to /usr/share/doc/packages/python-pam/examples/pamtest.pySkrypt pamtest.py może być używany do testowania poświadczeń w systemach korzystających z PAM do uwierzytelnienia jest zawarty w pakiecie python-pam (który wymaga Pythona), aw niektórych dystrybucjach pełna ścieżka jest /usr/share/doc/python-pam/examples/pamtest.py.
ShadSterling
5

Właściwym podejściem do testowania, czy użytkownik może się zalogować, jest zalogowanie się jako ten użytkownik.

Dlatego zalecam użycie skryptu CGI expectdo uruchomienia su, podania hasła i uruchomienia polecenia, które należy wykonać. Oto szkic skryptu expect, który właśnie to robi (ostrzeżenie: absolutnie niesprawdzony i nie jestem biegły w oczekiwaniu). Zastąp w nazwie użytkownika, haśle i poleceniu (gdzie napisałem bob, swordfishi somecommand); pamiętaj, aby podać poprawnie.

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

Jeśli naprawdę nie chcesz wykonywać polecenia przez warstwę su(na przykład dlatego, że to, co robisz, musi być wykonane przez sam proces CGI), użyj funkcji expect i uruchom polecenie truei sprawdź, czy zwracany jest status 0.

Innym podejściem byłoby użycie PAM bezpośrednio w aplikacji, poprzez powiązanie PAM Pythona .

Gilles „SO- przestań być zły”
źródło
To jest niesamowite, jedyne rozwiązanie, które bez dostępu roota.
jcubic
to działa su -c true bob && echo successTo wstyd, że su nie akceptują hasło jako argumentu
jcubic
Testowałem suze skryptu CGI i do jego działania potrzebny jest terminal.
jcubic
3
@jcubic To naprawdę głupie pomysł, aby umieścić hasło w argumencie wiersza poleceń, ponieważ argumenty wiersza poleceń są publiczne w systemie uniksowym. Usunięcie hasła zapewniłoby ten sam poziom bezpieczeństwa, co umieszczenie go w wierszu polecenia. I byłoby o wiele łatwiej sprawdzić: /bin/truewystarczyłoby.
ceving
2

Aby dokładniej odpowiedzieć: „Czy można utworzyć skrypt bash, który przetestuje daną kombinację nazwy użytkownika i hasła względem zarejestrowanego użytkownika na hoście?”

Tak.

#!/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
Brian Redbeard
źródło
2
Czy to zadziałało dla ciebie? Widzę, że sprawdzasz istniejące hasło cienia w stosunku do hasła cienia, ale gdzie w tym przypadku występuje skrót?
Nikhil Mulley,
Przetestowałem i to nie działa
jcubic
3
Kiepski pomysł! Zakładasz, że twój program działa jako root, a przynajmniej jako shadowgrupa, co jest bardzo niezalecane dla CGI: potrzebna byłaby kolejna warstwa eskalacji uprawnień. I zakładasz, że określony algorytm mieszania hasła (obsługiwany przez openssl) i miejsce przechowywania haseł ( /etc/shadoww przeciwieństwie do np. NIS lub LDAP), który może, ale nie musi być tym, który jest faktycznie używany dla tego konkretnego użytkownika.
Gilles „SO- przestań być zły”
2

Cytowane jest tutaj rozwiązanie PAM „C”, „Python”, pozwólcie, że dodam też perla :-)

Źródło: http://search.cpan.org/~nikip/Authen-PAM-0.16/PAM/FAQ.pod#1._Can_I_authenticate_a_user_non_interactactive ?

#!/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();
Nikhil Mulley
źródło
Tak, ok, ale pytanie dotyczyło skryptu CGI napisanego w języku Python.
Gilles „SO- przestań być zły”,
1

Jeśli masz dostęp do konta root i korzystasz z haseł md5, a po prostu musisz porównać hasła, możesz użyć modułu Perl Crypt :: PasswdMD5 . Weź skrót MD5 z / etc / shadow, usuń 1 $ $, a następnie podziel na pozostałe $. Pole 1 = Sól, Pole 2 = zaszyfrowany tekst. Następnie zaszyfruj tekst wprowadzony do twojego CGI, porównaj go z zaszyfrowanym tekstem, a Bob jest twoim wujem.

#!/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";
}
Tim Kennedy
źródło
2
jasne i proste :-). Działa to tak długo, jak długo / etc / passwd używa skrótu md5. W przypadku, gdy uwierzytelnianie (nsswitch) jest inne dla systemu, najlepiej użyć modułów pam.
Nikhil Mulley,
2
Kiepski pomysł! Zakładasz, że twój program działa jako root, a przynajmniej jako shadowgrupa, co jest bardzo niezalecane dla CGI: potrzebna byłaby kolejna warstwa eskalacji uprawnień. I zakładasz, że masz określony algorytm mieszania hasła (MD5, a nie bcrypt lub inny zalecany algorytm) i lokalizację przechowywania hasła ( /etc/shadoww przeciwieństwie np. Do NIS lub LDAP), który może, ale nie musi być tym, który jest faktycznie używany dla tego konkretnego użytkownika.
Gilles „SO- przestań być zły”
0

Po kilku poszukiwaniach napisałem ten program C, którego można używać ze skryptu

#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;
    }
}

Kompilujesz to z:

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

Możesz użyć go jako

sudo ./check_passwd <user> <password> && echo "success" || echo "failure"
jcubic
źródło
1
Kiepski pomysł! Zakładasz, że twój program działa jako root, a przynajmniej jako shadowgrupa, co jest bardzo niezalecane dla CGI: potrzebna byłaby kolejna warstwa eskalacji uprawnień. I zakładasz, że określony algorytm mieszania hasła (obsługiwany przez openssl) i miejsce przechowywania haseł ( /etc/shadoww przeciwieństwie do np. NIS lub LDAP), który może, ale nie musi być tym, który jest faktycznie używany dla tego konkretnego użytkownika. Użyj PAM, zna swoją pracę.
Gilles „SO- przestań być zły”
Tak, wiem, ale pomyślałem, że nie jest to możliwe bez rootowania. Wszystkie inne rozwiązania używają również rootowania, z wyjątkiem twojego.
jcubic
0

Ponieważ wspomniałeś, że używasz CGI w pythonie, prawdopodobnie należy założyć, że używasz Apache jako serwera httpd. Jeśli tak, pozostaw proces uwierzytelniania swojego programu Apache i pozwól uwierzytelnionym osobom wykonywać skrypty / programy cgi.

Istnieje wiele modułów, które mogą przeprowadzać dla Ciebie uwierzytelnianie na Apache, tak naprawdę zależy to od tego, jakiego rodzaju mechanizmu uwierzytelniania szukasz. Sposób, w jaki zacytowałeś pytanie, wydaje się być związany z uwierzytelnianiem lokalnego konta hosta na podstawie plików / etc / passwd, shadow. Moduł, który przychodzi do mojego szybkiego wyszukiwania w tym zakresie, jest mod_auth_shadow. Zaletą jest to, że pozwalasz komuś autorytatywnemu (działającemu na porcie uprawnień 80) uwierzytelnić użytkownika / hasło i możesz polegać na uwierzytelnionych informacjach użytkownika w celu uruchomienia poleceń w imieniu użytkownika, jeśli to konieczne.

Dobre linki na początek:

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

Innym podejściem jest użycie modułu SuEXEc Apache, który wykonuje procesy (programy cgi) w imieniu uwierzytelnionego użytkownika.

Nikhil Mulley
źródło
Ten skrypt CGI to usługa JSON-RPC wywoływana przez Ajax i potrzebuję metody logowania, która zwraca token, token powinien zostać zwrócony, jeśli logowanie się powiedzie. Zasadniczo więc każdy użytkownik musi mieć możliwość wykonania tego skryptu.
jcubic
0

Ten kod używający PAM działał dla mnie:

#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; 
}
Ritchie Carroll
źródło
Czy możesz dodać jakieś informacje kontekstowe, aby „poczuć” się bardziej jak odpowiedź?
Volker Siegel
-3

Najlepszą rzeczą, jaką możesz zrobić, jeśli potrzebujesz skryptu do zalogowania się na hoście, jest skonfigurowanie klucza ssh między hostami.

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

Prawie podniosłem to ze strony


Najpierw zainstaluj OpenSSH na dwóch komputerach z systemem UNIX, szybko i wydajnie. To działa najlepiej przy użyciu kluczy DSA i SSH2 domyślnie, o ile wiem. Wszystkie pozostałe HOWTO, które widziałem, wydają się mieć do czynienia z kluczami RSA i SSH1, a instrukcje nie zaskakująco nie działają z SSH2. Na każdym komputerze wpisz ssh somemachine.example.com i nawiąż połączenie za pomocą zwykłego hasła. Spowoduje to utworzenie katalogu .ssh w twoim katalogu domowym z odpowiednimi perms. Na podstawowym komputerze, na którym chcesz mieszkać tajne klucze (powiedzmy szybko), wpisz

ssh-keygen -t dsa

Spowoduje to monit o tajne hasło. Jeśli jest to twój główny klucz tożsamości, upewnij się, że używasz dobrego hasła. Jeśli to zadziała, otrzymasz dwa pliki o nazwach id_dsa i id_dsa.pub w katalogu .ssh. Uwaga: możliwe jest naciśnięcie klawisza Enter, gdy pojawi się monit o podanie hasła, co spowoduje utworzenie klucza bez hasła. To jest zły pomysł ™ na klucz tożsamości, więc nie rób tego! Poniżej przedstawiono zastosowania kluczy bez haseł.

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

Skopiuj plik id_dsa.pub do katalogu .ssh drugiego hosta o nazwie klucze_autoryzowane2. Teraz krzepki jest gotowy na przyjęcie twojego klucza ssh. Jak powiedzieć, jakich kluczy użyć? Zrobi to polecenie ssh-add. Aby przeprowadzić test, wpisz

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

Spowoduje to uruchomienie ssh-agent, dodanie domyślnej tożsamości (monitowanie o podanie hasła) i odrodzenie powłoki bash. Z tej nowej powłoki powinieneś być w stanie:

ssh burly

Powinieneś być w stanie się zalogować

Roy Rico
źródło
Chociaż jest to prawda, wydaje się, że nie ma to związku z pytaniem dotyczącym aplikacji dostępnej za pośrednictwem przeglądarki internetowej.
Gilles „SO- przestań być zły”