Eingabeaufforderung für das Befehlszeilenkennwort in PHP

77

Ich schreibe ein Befehlszeilentool, um meiner Web-App zu helfen. Es benötigt ein Passwort, um eine Verbindung zum Dienst herzustellen. Ich möchte, dass das Skript eine Kennwortabfrage anzeigt, damit ich sie nicht als Befehlszeilenargument übergeben muss.

Das ist einfach genug, aber ich möchte, dass das Passwort nicht während der Eingabe auf dem Bildschirm angezeigt wird. Wie kann ich das mit PHP machen?

Bonuspunkte für reines PHP (nein system('stty')) und Ersetzen der Charaktere durch *.

BEARBEITEN:

Das Skript wird unter einem Unix-ähnlichen System (Linux oder Mac) ausgeführt. Das Skript ist in PHP geschrieben und wird höchstwahrscheinlich so bleiben.

Für die Aufzeichnung ist die sttyArt und Weise, wie es gemacht wird:

echo "Password: ";
system('stty -echo');
$password = trim(fgets(STDIN));
system('stty echo');
// add a new line since the users CR didn't echo
echo "\n";

Ich würde es vorziehen, die system()Anrufe dort nicht zu haben .

Gary Richardson
quelle
Auf welchem ​​Betriebssystem wird das Befehlszeilenskript ausgeführt? Wird das Befehlszeilenskript in PHP oder in der Batch-Skriptsprache des Betriebssystems geschrieben?
Brendan Kidwell

Antworten:

42

Auf Sitepoint gefunden .

function prompt_silent($prompt = "Enter Password:") {
  if (preg_match('/^win/i', PHP_OS)) {
    $vbscript = sys_get_temp_dir() . 'prompt_password.vbs';
    file_put_contents(
      $vbscript, 'wscript.echo(InputBox("'
      . addslashes($prompt)
      . '", "", "password here"))');
    $command = "cscript //nologo " . escapeshellarg($vbscript);
    $password = rtrim(shell_exec($command));
    unlink($vbscript);
    return $password;
  } else {
    $command = "/usr/bin/env bash -c 'echo OK'";
    if (rtrim(shell_exec($command)) !== 'OK') {
      trigger_error("Can't invoke bash");
      return;
    }
    $command = "/usr/bin/env bash -c 'read -s -p \""
      . addslashes($prompt)
      . "\" mypassword && echo \$mypassword'";
    $password = rtrim(shell_exec($command));
    echo "\n";
    return $password;
  }
}
DaveHauenstein
quelle
3
Funktioniert nicht unter Windows 7. Laut verschiedenen Online-Foren funktioniert es nur unter Windows XP und 2003 Server.
Tgr
In meiner Antwort unten (oder direkt unter github.com/Seldaek/hidden-input ) finden Sie eine Lösung, die von XP bis 7, 32 / 64bit funktioniert und bei der keine hässliche Eingabeaufforderung auftaucht.
Seldaek
1
VBS und Bash? Wir sollten noch ein paar Sprachen einwerfen.
Ivan Vučica
1
rtrimSie können gültige Zeichen entfernen (dh alle Leerzeichen, die die Zeichenfolge beenden), aber Sie können sie weglassen und echo -nstattdessen verwenden.
Synexis
Die Lösung von @JMW am Ende dieser Seite funktioniert unter Windows 7 64. Funktioniert wahrscheinlich auch unter Win 7 32. Es sind nur ein paar Zeilen, erledigt den Job und erfordert nur die Installation von Powershell.
Michael
11

Abhängig von Ihrer Umgebung (dh nicht unter Windows) können Sie die Bibliothek ncurses (insbesondere die Funktion ncurses_noecho () zum Stoppen des Tastaturechos und ncurses_getch () zum Lesen der Eingabe) verwenden, um das Kennwort abzurufen, ohne es auf dem Bildschirm anzuzeigen.

Geil
quelle
9

Sie können meine Datei hiddeninput.exe verwenden, um echte versteckte Eingaben zu erhalten, ohne die Informationen irgendwo auf dem Bildschirm zu verlieren .

<?php

echo 'Enter password: ';
$password = exec('hiddeninput.exe');
echo PHP_EOL;

echo 'Password was: ' . $password . PHP_EOL;

Wenn Sie das letzte Echo entfernen, sollte das Passwort niemals angezeigt werden, aber Sie können es offensichtlich zur Validierung verwenden.

Seldaek
quelle
9
Ich bin sicher, dass hiddeninput.exe aus dem Projekt keine massive Sicherheitsbedrohung darstellt. Es ist jedoch keine gute Praxis, zufällige binäre Blobs aus dem Internet für den Umgang mit Passwörtern zu verwenden. Selbst wenn dieser Code sicher ist, ist er nur ein Platzhalter für einen böswilligen Schauspieler, um etwas
Böses
5

Die folgende Methode funktioniert unter Linux CLI, jedoch nicht unter Windows CLI oder Apache. Es funktioniert auch nur mit Zeichen in der Standard-Ascii-Tabelle (es würde jedoch nicht viel kosten, um es mit erweiterten Zeichensätzen kompatibel zu machen).

Ich habe ein bisschen Code eingegeben, um mich vor dem Kopieren und Einfügen von Passwörtern zu schützen. Wenn das Bit zwischen den beiden Kommentaren entfernt wird, kann ein Passwort eingefügt / eingefügt werden.

Ich hoffe das hilft jemandem.

<?php

    echo("Password: ");
    $strPassword=getObscuredText();
    echo("\n");
    echo("You entered: ".$strPassword."\n");

    function getObscuredText($strMaskChar='*')
    {
        if(!is_string($strMaskChar) || $strMaskChar=='')
        {
            $strMaskChar='*';
        }
        $strMaskChar=substr($strMaskChar,0,1);
        readline_callback_handler_install('', function(){});
        $strObscured='';
        while(true)
        {
            $strChar = stream_get_contents(STDIN, 1);
            $intCount=0;
// Protect against copy and paste passwords
// Comment \/\/\/ to remove password injection protection
            $arrRead = array(STDIN);
            $arrWrite = NULL;
            $arrExcept = NULL;
            while (stream_select($arrRead, $arrWrite, $arrExcept, 0,0) && in_array(STDIN, $arrRead))            
            {
                stream_get_contents(STDIN, 1);
                $intCount++;
            }
//        /\/\/\
// End of protection against copy and paste passwords
            if($strChar===chr(10))
            {
                break;
            }
            if ($intCount===0)
            {
                if(ord($strChar)===127)
                {
                    if(strlen($strObscured)>0)
                    {
                        $strObscured=substr($strObscured,0,strlen($strObscured)-1);
                        echo(chr(27).chr(91)."D"." ".chr(27).chr(91)."D");
                    }
                }
                elseif ($strChar>=' ')
                {
                    $strObscured.=$strChar;
                    echo($strMaskChar);
                    //echo(ord($strChar));
                }
            }
        }
        readline_callback_handler_remove();
        return($strObscured);
    }
?>
Pitpat
quelle
Würde mit einer einführenden Minimalversion, die keine Sternchen druckt, Kopieren, Einfügen, Löschen von Handles usw. verhindert, erheblich verbessert. Dies sollte jedoch angesichts der bearbeiteten Frage wahrscheinlich immer noch die akzeptierte Antwort sein. und die zusätzliche Raffinesse, um Saiten richtig zu handhaben, ist cool.
Dewi Morgan
3

Dies ist die einfachste Lösung für alle Plattformen:

function prompt($message = 'prompt: ', $hidden = false) {
    if (PHP_SAPI !== 'cli') {
        return false;
    }
    echo $message;
    $ret = 
        $hidden
        ? exec(
            PHP_OS === 'WINNT' || PHP_OS === 'WIN32'
            ? __DIR__ . '\prompt_win.bat'
            : 'read -s PW; echo $PW'
        )
        : rtrim(fgets(STDIN), PHP_EOL)
    ;
    if ($hidden) {
        echo PHP_EOL;
    }
    return $ret;
}

Dann erstellen Sie prompt_win.batim selben Verzeichnis:

SetLocal DisableDelayedExpansion
Set "Line="
For /F %%# In ('"Prompt;$H & For %%# in (1) Do Rem"') Do (
    Set "BS=%%#"
)

:loop_start
    Set "Key="
    For /F "delims=" %%# In ('Xcopy /L /W "%~f0" "%~f0" 2^>Nul') Do (
        If Not Defined Key (
            Set "Key=%%#"
        )
    )
    Set "Key=%Key:~-1%"
    SetLocal EnableDelayedExpansion
    If Not Defined Key (
        Goto :loop_end
    )
    If %BS%==^%Key% (
        Set "Key="
        If Defined Line (
            Set "Line=!Line:~0,-1!"
        )
    )
    If Not Defined Line (
        EndLocal
        Set "Line=%Key%"
    ) Else (
        For /F "delims=" %%# In ("!Line!") Do (
            EndLocal
            Set "Line=%%#%Key%"
        )
    )
    Goto :loop_start
:loop_end

Echo;!Line!
mpyw
quelle
rtrimSie können gültige Zeichen entfernen (dh alle Leerzeichen, die die Zeichenfolge beenden), aber Sie können sie weglassen und echo -nstattdessen verwenden.
Synexis
2

Ich denke, dass es keinen einfachen Weg gibt, dies zu tun (eigentlich kann ich mir keinen Weg vorstellen), ohne stty -echo zu verwenden. Wenn Sie beabsichtigen, es unter Windows auszuführen, können Sie ein Batch-Skript erstellen, das die nicht verschlüsselten eingegebenen Informationen für Ihr PHP-Skript bereitstellt.

@echo off
cls
SET /P uname=Enter Username:
echo hP1X500P[PZBBBfh#b##fXf-V@`$fPf]f3/f1/5++u5>in.com
set /p password=Enter password :<nul
for /f “tokens=*” %%i in (’in.com’) do set password=%%i
del in.com
echo.
c:\php\php.exe d:\php\test.php %uname% “%password%”
Pause

Beispiel aus http://www.indiangnu.org/2008/php-hide-user-input-using-batch-script-windows/

Gabriel Gilini
quelle
2
netter Trick, um eine Nur-Text-COM-Datei zu erstellen (sieht ein bisschen wie der EICAR-Antiviren-Test aus ;-)) Leider funktioniert dies unter 64-Bit-Windows nicht ... (keine 16-Bit-COM-Unterstützung mehr ... und Erstellen eine EXE-Datei auf diese Weise ... viel Glück!)
Ale
2

Funktioniert auf jedem Windows-System, das Powershell-Unterstützung bietet. (Quelle: http://www.qxs.ch/2013/02/08/php-cli-password-prompts-on-windows-7/ )

<?php
// please set the path to your powershell, here it is: C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe
$pwd=shell_exec('C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe -Command "$Password=Read-Host -assecurestring \"Please enter your password\" ; $PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)) ; echo $PlainPassword;"');
$pwd=explode("\n", $pwd); $pwd=$pwd[0];
echo "You have entered the following password: $pwd\n";
JMW
quelle
0

Warum nicht eine SSH-Verbindung verwenden? Sie können die Befehle weg abstrahieren, Eingabe / Ausgabe umleiten und die volle Kontrolle haben.

Sie können jemandem eine reine, saubere Shell mit so wenig Rechten wie nötig zur Verfügung stellen und das Passwort einfach zusammen mit SSH2 :: Connect () POSTEN lassen, um die Shell zu öffnen.

Ich habe eine nette Klasse erstellt, um mit der PHP-SSH2-Erweiterung zu arbeiten. Vielleicht hilft sie Ihnen. (und es macht auch sichere Dateiübertragungen)

<?php

/**
 * SSH2
 * 
 * @package Pork
 * @author SchizoDuckie
 * @version 1.0
 * @access public
 */
class SSH2
{
    private $host;
    private $port;
    private $connection;
    private $timeout;
    private $debugMode;
    private $debugPointer;
    public $connected; 
    public $error;


    /**
     * SSH2::__construct()
     * 
     * @param mixed $host
     * @param integer $port
     * @param integer $timeout
     * @return
     */
    function __construct($host, $port=22, $timeout=10)
    {
        $this->host = $host;
        $this->port = $port;
        $this->timeout = 10;
        $this->error = 'not connected';
        $this->connection = false;
        $this->debugMode = Settings::Load()->->get('Debug', 'Debugmode');
        $this->debugPointer = ($this->debugMode) ? fopen('./logs/'.date('Y-m-d--H-i-s').'.log', 'w+') : false;
        $this->connected = false;

    }


    /**
     * SSH2::connect()
     * 
     * @param mixed $username
     * @param mixed $password
     * @return
     */
    function connect($username, $password)
    {
        $this->connection = ssh2_connect($this->host, $this->port);
        if (!$this->connection) return $this->error("Could not connect to {$this->host}:{$this->port}");
        $this->debug("Connected to {$this->host}:{$this->port}");
        $authenticated = ssh2_auth_password($this->connection, $username, $password);
        if(!$authenticated) return $this->error("Could not authenticate: {$username}, check your password");
        $this->debug("Authenticated successfully as {$username}");
        $this->connected = true;

        return true;
    }

    /**
     * SSH2::exec()
     *
     * @param mixed $command shell command to execute
     * @param bool $onAvailableFunction a function to handle any available data.
     * @param bool $blocking blocking or non-blocking mode. This 'hangs' php execution until the command has completed if you set it to true. If you just want to start an import and go on, use this icm onAvailableFunction and false
     * @return
     */
    function exec($command, $onAvailableFunction=false, $blocking=true)
    {
        $output = '';
        $stream = ssh2_exec($this->connection, $command);
        $this->debug("Exec: {$command}");
        if($onAvailableFunction !== false)
        {
            $lastReceived = time();
            $timeout =false;
            while (!feof($stream) && !$timeout)
            {
                $input = fgets($stream, 1024);
                if(strlen($input) >0)
                {
                    call_user_func($onAvailableFunction, $input);
                    $this->debug($input);
                    $lastReceived = time();
                }
                else
                {
                    if(time() - $lastReceived >= $this->timeout)
                    {
                        $timeout = true;
                        $this->error('Connection timed out');
                        return($this->error);
                    }
                }
            }
        }
        if($blocking === true && $onAvailableFunction === false)
        {
            stream_set_blocking($stream, true);
            $output = stream_get_contents($stream);
            $this->debug($output);
        }
        fclose($stream);
        return($output);
    }


    /**
     * SSH2::createDirectory()
     *
     * Creates a directory via sftp
     *
     * @param string $dirname
     * @return boolean success
     *  
     */
    function createDirectory($dirname)
    {
        $ftpconnection = ssh2_sftp ($this->connection);
        $dircreated = ssh2_sftp_mkdir($ftpconnection, $dirname, true);
        if(!$dircreated) 
        {
            $this->debug("Directory not created: ".$dirname);
        }
        return $dircreated;
    }

    public function listFiles($dirname)
    {
        $input = $this->exec(escapeshellcmd("ls  {$dirname}"));
        return(explode("\n", trim($input)));

    }

    public function sendFile($filename, $remotename)
    {
        $this->debug("sending {$filename} to {$remotename} ");
        if(file_exists($filename) && is_readable($filename))
        {
            $result = ssh2_scp_send($this->connection, $filename, $remotename, 0664);
        }
        else
        {
            $this->debug("Unable to read file : ".$filename);
            return false;
        }
        if(!$result) $this->debug("Failure uploading {$filename} to {$remotename}");
        return $result;
    }

    public function getFile($remotename, $localfile)
    {
        $this->debug("grabbing {$remotename} to {$localfile}");
        $result = ssh2_scp_recv($this->connection, $remotename, $localfile);

        if(!$result) $this->debug("Failure downloading {$remotename} to {$localfile}");
        return $result;
    }

    /**
     * SSH2::debug()
     * 
     * @param mixed $message
     * @return
     */
    function debug($message) 
    {
        if($this->debugMode)
        {
            fwrite($this->debugPointer, date('Y-m-d H:i:s')." : ".$message."\n");
        }
    }



    /**
     * SSH2::error()
     * 
     * @param mixed $errorMsg
     * @return
     */
    function error($errorMsg) 
    {
        $this->error = $errorMsg;
        $this->debug($errorMsg);
        return false;
    }   

    /**
     * SSH2::__destruct()
     * 
     * @return
     */
    function __destruct() 
    {
        if($this->connection){
            $this->connection = null;
        }
        if($this->debugMode && $this->debugPointer)
        {
            fclose($this->debugPointer);
        }
    }       


}

Anwendungsbeispiel:

$settings = Settings::Load()->Get("SecureServer");
$ssh = new SSH2($settings['host']);
if( $ssh->connect($settings['username'], $settings['password']))
{
    echo $ssh->exec("ls -la ".$settings['path'], false, true);  
    flush();    
}
SchizoDuckie
quelle
Ich erhalte eine Fehlermeldung: PHP Schwerwiegender Fehler: Klasse 'Einstellungen' nicht in /home/tester/tools/SSH/conn_ssh3.php in Zeile 2 gefunden. Ich habe die Klasse ssh2 als Settings.php bezeichnet und auch versucht, die Einstellungen zu ändern: : Load () zu SSH2 :: Load ()
Kamal
0

Theoretisch können Sie dies mit stream_set_blocking () tun, aber es sieht so aus, als ob es einige PHP-Fehler gibt, die STDIN verwalten.

Schauen Sie: http://bugs.php.net/bug.php?id=34972 http://bugs.php.net/bug.php?id=36030

Versuchen Sie es selbst:

echo "Enter Password: ";
$stdin = fopen('php://stdin','r');
// Trying to disable stream blocking
stream_set_blocking($stdin, FALSE) or die ('Failed to disable stdin blocking');
// Trying to set stream timeout to 1sec
stream_set_timeout ($stdin, 1) or die ('Failed to enable stdin timeout');
Slipo
quelle
Snippet scheint die Zeile zu fehlen, die von stdin eingelesen werden muss, um das Passwort zu erhalten. Diese Antwort setzt jedoch zumindest keine Windows-CLI wie so viele andere voraus und beantwortet die Teile "Keine Systemaufrufe" und "Reines PHP" der bearbeiteten Frage.
Dewi Morgan
0

Die akzeptierte Antwort ist nicht gut genug. Erstens funktioniert die Windows-Lösung unter Windows 7 und höher nicht. Die Lösung für andere Betriebssysteme hängt von dem in Bash und Bash integrierten "Lesen" ab. Es gibt jedoch Systeme, die Bash nicht verwenden (z. B. OpenBSD) und bei denen dies offensichtlich nicht funktioniert.

In diesem Blog habe ich eine Lösung besprochen, die auf fast jedem Unix-basierten Betriebssystem und Windows von 95 bis 8 funktioniert. Die Windows-Lösung verwendet ein externes Programm, das in C auf der obersten Win32-API geschrieben ist. Die Lösung für andere Betriebssysteme verwendet den externen Befehl 'stty'. Ich habe noch kein Unix-basiertes System gesehen, das kein 'stty' hat.

robert petranovic
quelle
1
Ich denke, es ist besser, wenn Sie hier eine kurze Version Ihres Beitrags bereitstellen (möglicherweise nur mit Beispielen für jeden Ansatz), da das einfache Posten eines Links gegen die ursprüngliche Idee von SE-Sites verstößt.
Dmitry Koroliov
Gibt es eine generische Bibliothek, die den Benutzer plattformübergreifend zur Eingabe von Passwörtern, normalem Klartext und Menüoptionen auffordert?
CMCDragonkai
@CMCDragonkai Nein, gibt es nicht. Eine solche Funktion ist in PHP nicht implementiert, daher kann sie nicht nur mit PHP ausgeführt werden. Es gibt eine ncurses PHP-Erweiterung, die jedoch unter Windows nicht funktioniert.
Robert Petranovic