Запит пароля командного рядка в PHP


77

Я пишу інструмент командного рядка, щоб допомогти моєму веб-додатку. Для підключення до служби потрібен пароль. Я хотів би, щоб сценарій відображав підказку про введення пароля, тому мені не потрібно передавати його як аргумент командного рядка.

Це досить просто, але я хотів би, щоб він не повторював пароль на екрані під час введення. Як я можу це зробити за допомогою PHP?

Бонусні бали за те, що ви робите це в чистому PHP (ні system('stty')) та замінюєте символи на *.

РЕДАГУВАТИ:

Сценарій буде працювати в системі, подібній до unix (linux або mac). Сценарій написаний на PHP і, швидше за все, таким і залишиться.

Крім того, для запису, sttyспосіб це зробити:

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";

Я волів би, щоб там не було system()дзвінків.


На якій ОС працюватиме сценарій командного рядка? Чи буде сценарій командного рядка написаний на PHP або на пакетній мові сценаріїв ОС?
Brendan Kidwell

Відповіді:


42

Знайдено на сайті .

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

3
Не працює в Windows 7. Згідно з різними форумами в Інтернеті, не працюватиме ні на чому іншому, крім Windows XP та 2003 Server.
Tgr

Дивіться мою відповідь нижче (або перейдіть безпосередньо на github.com/Seldaek/hidden-input ), щоб знайти рішення, яке працює від XP до 7, 32/64 біт, і ніяких потворних підказок не з’являється.
Селдак

1
VBS і bash? Нам слід кинути ще кілька мов.
Ivan Vučica

1
rtrimможе видалити дійсні символи (тобто будь-який пробіл, що закінчує рядок), але ви можете пропустити його та використовувати echo -nзамість нього.
Synexis

Рішення @JMW, розташоване внизу цієї сторінки, працює для Windows 7 64. Можливо, воно буде працювати і на Win 7 32. Це лише кілька рядків, що робить роботу і вимагає лише встановлення Powershell.
Майкл

11

Залежно від вашого середовища (тобто не в Windows), ви можете використовувати бібліотеку ncurses (зокрема, функцію ncurses_noecho (), щоб зупинити ехо клавіатури та ncurses_getch (), щоб прочитати введення), щоб отримати пароль, не відображаючи його на екрані.


9

Ви можете використовувати мій файл hiddeninput.exe, щоб отримати справжній прихований ввід, не витікаючи інформацію в будь-якому місці екрана.

<?php

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

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

Якщо ви видалите останнє відлуння, пароль ніколи не повинен відображатися, але ви можете використовувати його для перевірки навпаки.


9
Я впевнений, що hiddeninput.exe від проекту не є масовою загрозою безпеці .. але просто не є доброю практикою використовувати випадкові двійкові краплі з Інтернету для обробки паролів. Навіть якщо цей код надійний, це просто заповнювач для зловмисного актора, щоб вколоти щось неприємне ...
ftrotter

5

Наведений нижче метод працює під Linux CLI, але не під Windows CLI або Apache. Він також працює лише з символами в стандартній таблиці Ascii (хоча для того, щоб зробити його сумісним із розширеними наборами символів, знадобиться небагато).

Я вклав трохи коду, щоб захистити від копіювання та вставлення паролів. Якщо біт між двома коментарями видалено, тоді можна вставити / вставити пароль.

Сподіваюся, це комусь допомагає.

<?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);
    }
?>

Було б значно покращено за допомогою вступної мінімальної версії, яка не друкує зірочки, не запобігає копіруванню, обробці видалення тощо: тим не менш, це все одно, мабуть, має бути прийнятою відповіддю, враховуючи відредаговане запитання; а додаткова вишуканість у правильній обробці рядків - це круто.
Dewi Morgan

3

Це найпростіше рішення для всіх платформ:

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

Потім створіть prompt_win.batу тому ж каталозі:

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!

rtrimможе видалити дійсні символи (тобто будь-який пробіл, що закінчує рядок), але ви можете пропустити його та використовувати echo -nзамість нього.
Synexis

2

Я думаю, що немає простого способу зробити це (насправді я не можу придумати жодного способу) без використання stty -echo. Якщо ви маєте намір запустити його у вікнах, ви можете створити пакетний скрипт, який надаватиме невідповідну набрану інформацію вашому php-скрипту.

@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

приклад взято з http://www.indiangnu.org/2008/php-hide-user-input-using-batch-script-windows/


2
приємний трюк для створення текстового COM-файлу (схоже на антивірусний тест EICAR ;-)), на жаль, це не працює під 64-розрядною Windows ... (більше немає 16-розрядної підтримки COM ... і створення файл EXE таким чином ... удачі!)
Ель

2

Працює в будь-якій системі Windows, яка підтримує PowerShell. (джерело: 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";

0

Чому б не використовувати з'єднання SSH? Ви можете абстрагувати команди, перенаправити вхід / вихід і мати повний контроль.

Ви можете надати комусь чисто чисту оболонку з мінімальними правами, наскільки це необхідно, і нехай пароль буде просто POST'ed разом із SSH2 :: Connect (), щоб відкрити оболонку.

Я створив приємний клас для роботи з розширенням php SSH2, можливо, це допоможе вам; (а також захищає передачу файлів)

<?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);
        }
    }       


}

Приклад використання:

$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();    
}

Я отримую помилку: PHP Фатальна помилка: Клас "Налаштування" не знайдено в /home/tester/tools/SSH/conn_ssh3.php у рядку 2, я назвав клас ssh2 як Settings.php, а також спробував змінити Параметри: : Load () до SSH2 :: Load ()
kamal

0

Теоретично ви можете зробити це за допомогою stream_set_blocking (), але схоже, що є деякі помилки PHP, що керують STDIN.

Подивіться: http://bugs.php.net/bug.php?id=34972 http://bugs.php.net/bug.php?id=36030

Спробуйте самі:

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');

Здається, у фрагменті бракує рядка для читання зі stdin, щоб отримати пароль; але ця відповідь, принаймні, не передбачає Windows CLI, як і багато інших, і відповідає на "відсутність системних викликів" та "чистий PHP" у частинах відредагованого запитання.
Dewi Morgan

0

Прийнята відповідь недостатньо хороша. Перш за все, рішення Windows не працює на Windows 7 і вище. Рішення для інших ОС залежить від вбудованого "читання" Bash і bash. Однак існують системи, які не використовують Bash (наприклад, OpenBSD) і де це, очевидно, не буде працювати.

У цьому блозі я обговорював рішення, яке працює практично на будь-якій ОС на основі Unix та Windows від 95 до 8. Рішення Windows використовує зовнішню програму, написану на C на верхньому API Win32. Рішення для інших ОС використовує зовнішню команду 'stty'. Я ще не бачив системи на основі Unix, яка не має "stty"


1
Я думаю, що буде краще, якщо ви надасте стислий варіант своєї публікації тут (можливо, будуть наведені лише приклади для кожного підходу), оскільки просто розміщення посилання суперечить початковій ідеї сайтів SE.
Дмитро Корольов

Чи існує загальна бібліотека, яка пропонує можливість запитувати у користувача паролі, звичайний звичайний текст та параметри меню на всіх платформах?
CMCDragonkai

@CMCDragonkai Ні, немає. Така функція не реалізована в PHP, тому її не можна робити, використовуючи лише PHP. Існує розширення ncurses PHP, але воно не працює в Windows.
Роберт Петранович,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.