Wie schreibe ich einen Test für die Systemanmeldung?


9

Ich habe ein Python-CGI-Skript geschrieben, das bashBefehle aufruft und auf eine erfolgreiche Anmeldung auf dem Host testen muss.

Wie schreibe ich einen Test dafür?

Könnte ich beispielsweise ein bashSkript erstellen , das eine bestimmte Kombination aus Benutzername und Kennwort gegen den auf dem Host registrierten Benutzer testet?


1
Vielleicht könnten Sie sich den Code hinter dem loginProgramm ansehen .
Kevin

Nicht im Zusammenhang mit der Frage, aber ich hoffe, Sie verschlüsseln den Datenverkehr zu Ihrem Webserver, damit Benutzeranmeldungen nicht von der Leitung abgehört werden können.
jw013

Antworten:


8

Die Verwendung von PAM ist die beste Lösung. Sie können kleinen C-Code schreiben oder das Python-Pam-Paket installieren und ein Python-Skript verwenden, das mit dem Python-Pam-Paket geliefert wird. Sehen/usr/share/doc/python-pam/examples/pamtest.py


Ich versuche PAM, aber es hat nicht funktioniert. Aber ich versuche dieses Beispiel noch einmal und es funktioniert.
jcubic

1
In OpenSUSE 12.3 (python-pam 0.5.0-84.1.1) und 13.1 (0.5.0-87.1.2) lautet der vollständige Pfad zu pamtest.py: /usr/share/doc/packages/python-pam/examples/pamtest.pyDas Skript pamtest.py kann zum Testen von Anmeldeinformationen auf Systemen mit PAM verwendet werden zur Authentifizierung ist im Python-Pam-Paket enthalten (für das Python erforderlich ist), und in einigen Distributionen ist der vollständige Pfad angegeben /usr/share/doc/python-pam/examples/pamtest.py.
ShadSterling

5

Der richtige Ansatz zum Testen, ob sich ein Benutzer anmelden kann, besteht darin, sich tatsächlich als dieser Benutzer anzumelden.

Ich empfehle daher, das CGI-Skript expectzum Ausführen zu verwenden su, ein Kennwort zu übergeben und den Befehl auszuführen, der ausgeführt werden muss. Hier ist ein Entwurf eines Expect-Skripts, das genau dies tut (Warnung: absolut ungetestet, und ich spreche nicht fließend). Ersetzen Sie den Benutzernamen, das Passwort und den Befehl (wo ich geschrieben habe bob, swordfishund somecommand); Achten Sie darauf, richtig zu zitieren.

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

Wenn Sie den Befehl wirklich nicht über eine Ebene von ausführen möchten su(z. B. weil das, was Sie tun, vom CGI-Prozess selbst ausgeführt werden muss), verwenden Sie expected, um den Befehl auszuführen trueund zu überprüfen, ob der Rückgabestatus 0 ist.

Ein anderer Ansatz wäre, PAM direkt in Ihrer Anwendung über die PAM-Bindung von Python zu verwenden .


Es ist fantastisch, die einzige Lösung, die ohne Root-Zugriff.
jcubic

das funktioniert su -c true bob && echo successschade, dass su kein passwort als argument akzeptiert
jcubic

Ich habe mit suCGI-Skript getestet und es benötigt ein Terminal, damit es funktioniert.
jcubic

3
@jcubic Es ist eine wirklich dumme Idee, ein Kennwort in ein Befehlszeilenargument einzufügen, da Befehlszeilenargumente auf einem Unix-System öffentlich sind. Das Entfernen des Kennworts bietet die gleiche Sicherheitsstufe wie das Einfügen in die Befehlszeile. Und es wäre viel einfacher zu überprüfen: /bin/truewäre genug.
Ceving

2

Um genauer zu antworten: "Ist es möglich, ein Bash-Skript zu erstellen, das eine bestimmte Kombination aus Benutzername und Passwort gegen den auf dem Host registrierten Benutzer testet?"

Ja.

#!/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
Hat das für dich funktioniert? Ich sehe, dass Sie ein vorhandenes Schattenkennwort mit dem Schattenkennwort vergleichen, aber wo ist das Hashing hier beteiligt?
Nikhil Mulley

Ich habe getestet und es funktioniert nicht
jcubic

3
Schlechte Idee! Sie gehen davon aus, dass Ihr Programm als Root oder zumindest als shadowGruppe ausgeführt wird, was für ein CGI nicht unbedingt empfohlen wird: Sie benötigen eine weitere Ebene der Berechtigungseskalation. Und Sie gehen von einem bestimmten Kennwort-Hashing-Algorithmus (der von openssl unterstützt wird) und einem Kennwortspeicherort ( /etc/shadowim Gegensatz zu z. B. NIS oder LDAP) aus, der möglicherweise tatsächlich für diesen bestimmten Benutzer verwendet wird oder nicht.
Gilles 'SO - hör auf böse zu sein'

2

Es gibt eine 'C', 'Python' PAM-Lösung, die hier zitiert wird. Lassen Sie mich auch die Perl-Lösung verwenden :-)

Quelle: 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();

Ja, ok, aber die Frage betraf ein in Python geschriebenes CGI-Skript.
Gilles 'SO - hör auf böse zu sein'

1

Wenn Sie Root-Zugriff haben und md5-Kennwörter verwenden und nur Kennwörter vergleichen müssen, können Sie das Perl- Modul Crypt :: PasswdMD5 verwenden . Nehmen Sie den MD5-Hash aus / etc / shadow, entfernen Sie die $ 1 $ und teilen Sie die verbleibenden $ auf. Feld 1 = Salz, Feld 2 = verschlüsselter Text. Hashing dann die Texteingabe in dein CGI, vergleiche sie mit dem verschlüsselten Text und Bob ist dein Onkel.

#!/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
schlicht und einfach :-). Dies funktioniert so lange, wie / etc / passwd md5-Hashing verwendet. Falls die Authentifizierung (nsswitch) für das System unterschiedlich ist, sollten Pam-Module am besten verwendet werden.
Nikhil Mulley

2
Schlechte Idee! Sie gehen davon aus, dass Ihr Programm als Root oder zumindest als shadowGruppe ausgeführt wird, was für ein CGI nicht unbedingt empfohlen wird: Sie benötigen eine weitere Ebene der Berechtigungseskalation. Und Sie gehen von einem bestimmten Kennwort-Hashing-Algorithmus (MD5 und nicht bcrypt oder einem anderen empfohlenen Algorithmus) und einem Kennwortspeicherort ( /etc/shadowim Gegensatz zu z. B. NIS oder LDAP) aus, der möglicherweise tatsächlich für diesen bestimmten Benutzer verwendet wird oder nicht.
Gilles 'SO - hör auf böse zu sein'

0

Nach einiger Suche habe ich dieses C-Programm geschrieben, das über ein Skript verwendet werden kann

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

Sie kompilieren es mit:

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

Sie können es als verwenden

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

1
Schlechte Idee! Sie gehen davon aus, dass Ihr Programm als Root oder zumindest als shadowGruppe ausgeführt wird, was für ein CGI nicht unbedingt empfohlen wird: Sie benötigen eine weitere Ebene der Berechtigungseskalation. Und Sie gehen von einem bestimmten Kennwort-Hashing-Algorithmus (der von openssl unterstützt wird) und einem Kennwortspeicherort ( /etc/shadowim Gegensatz zu z. B. NIS oder LDAP) aus, der möglicherweise tatsächlich für diesen bestimmten Benutzer verwendet wird oder nicht. Verwenden Sie PAM, es kennt seinen Job.
Gilles 'SO - hör auf böse zu sein'

Ja ich weiß, dachte aber, dass dies ohne Wurzel nicht möglich ist. Alle anderen Lösungen verwenden ebenfalls root, mit Ausnahme Ihrer.
jcubic

0

Da Sie erwähnt haben, dass Sie CGI in Python verwenden, ist es wahrscheinlich angebracht anzunehmen, dass Sie Apache als httpd-Server verwenden. Wenn ja, überlassen Sie den Authentifizierungsprozess Ihres Programms Apache und lassen Sie nur die authentifizierten Personen Ihre CGI-Skripte / -Programme ausführen.

Es gibt zahlreiche Module, die die Authentifizierung für Sie in Apache durchführen können. Dies hängt wirklich davon ab, nach welcher Art von Authentifizierungsmechanismus Sie suchen. Die Art und Weise, wie Sie in der Frage zitiert haben, scheint mit der Authentifizierung des lokalen Hostkontos basierend auf / etc / passwd, Shadow-Dateien, in Zusammenhang zu stehen. Modul, das zu meiner Schnellsuche dazu kommt, ist mod_auth_shadow. Der Vorteil besteht darin, dass Sie einer autorisierenden Person (die auf dem Berechtigungsport 80 ausgeführt wird) erlauben, den Benutzer / das Kennwort für Sie zu authentifizieren, und dass Sie sich bei Bedarf auf authentifizierte Informationen des Benutzers verlassen können, um die Befehle im Namen des Benutzers auszuführen.

Gute Links zum Starten:

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

Ein anderer Ansatz ist die Verwendung des SuEXEc-Moduls von Apache, das Prozesse (CGI-Programme) im Auftrag des authentifizierten Benutzers ausführt.


Dieses CGI-Skript ist ein JSON-RPC-Dienst, der über Ajax aufgerufen wird. Ich benötige eine Methodenanmeldung, die ein Token zurückgibt. Bei erfolgreicher Anmeldung sollte ein Token zurückgegeben werden. Grundsätzlich muss jeder Benutzer in der Lage sein, dieses Skript auszuführen.
jcubic

0

Dieser Code mit PAM hat bei mir funktioniert:

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

Könnten Sie einige Kontextinformationen hinzufügen, damit es sich eher wie eine Antwort anfühlt?
Volker Siegel

-3

Wenn Sie ein Skript benötigen, um sich bei einem Host anzumelden, können Sie am besten einen SSH-Schlüssel zwischen den Hosts konfigurieren.

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

Ich habe das so ziemlich von der Seite gehoben


Installieren Sie OpenSSH zunächst mühelos auf zwei UNIX-Computern. Soweit ich das beurteilen kann, funktioniert dies am besten mit DSA-Schlüsseln und SSH2. Alle anderen HOWTOs, die ich gesehen habe, scheinen sich mit RSA-Schlüsseln und SSH1 zu befassen, und die Anweisungen funktionieren nicht überraschend nicht mit SSH2. Geben Sie auf jedem Computer ssh somemachine.example.com ein und stellen Sie eine Verbindung mit Ihrem regulären Passwort her. Dadurch wird ein .ssh-Verzeichnis in Ihrem Home-Verzeichnis mit den richtigen Dauerwellen erstellt. Geben Sie auf Ihrem primären Computer, auf dem Ihre geheimen Schlüssel gespeichert werden sollen (sagen wir Hurly), ein

ssh-keygen -t dsa

Dies fordert Sie zur Eingabe einer geheimen Passphrase auf. Wenn dies Ihr primärer Identitätsschlüssel ist, stellen Sie sicher, dass Sie eine gute Passphrase verwenden. Wenn dies richtig funktioniert, erhalten Sie zwei Dateien mit den Namen id_dsa und id_dsa.pub in Ihrem .ssh-Verzeichnis. Hinweis: Sie können einfach die Eingabetaste drücken, wenn Sie zur Eingabe einer Passphrase aufgefordert werden. Dadurch wird eine Taste ohne Passphrase erstellt. Dies ist eine schlechte Idee ™ für einen Identitätsschlüssel. Tun Sie es also nicht! Weiter unten finden Sie Informationen zur Verwendung von Schlüsseln ohne Passphrasen.

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

Kopieren Sie die Datei id_dsa.pub in das .ssh-Verzeichnis des anderen Hosts mit dem Namen authorisation_keys2. Jetzt ist Burly bereit, Ihren SSH-Schlüssel zu akzeptieren. Wie kann man feststellen, welche Schlüssel verwendet werden sollen? Der Befehl ssh-add erledigt dies. Geben Sie für einen Test Folgendes ein

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

Dadurch wird der ssh-Agent gestartet, Ihre Standardidentität hinzugefügt (Sie werden zur Eingabe Ihrer Passphrase aufgefordert) und eine Bash-Shell erzeugt. Von dieser neuen Shell sollten Sie in der Lage sein:

ssh burly

Sie sollten sich anmelden können


Dies ist zwar richtig, scheint jedoch für die Frage, die sich auf eine Anwendung bezieht, auf die über einen Webbrowser zugegriffen wird, nicht relevant zu sein.
Gilles 'SO - hör auf böse zu sein'
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.