开发者

PHP/PAM to change user password?

Are there any working packages to change a linux user passwords using PHP?

I've tried using PECL:PAM but ther开发者_C百科es an error when it tries to change the password.

Edit:

PHP code:

echo pam_chpass($username, $password, $new_pass, &$error) ? 'good' : $error;

PHP (echo) output:

Permission denied (in pam_authenticate)

From /var/log/auth (these are actually from before, the log doesn't seem to be working ATM for some reason yet to be determined):

Jun 11 15:30:20 veda php: pam_unix(php:chauthtok): conversation failed
Jun 11 15:30:20 veda php: pam_unix(php:chauthtok): password - (old) token not obtained
Jun 11 15:30:20 veda php: pam_winbind(php:chauthtok): valid_user: wbcGetpwnam gave WBC_ERR_DOMAIN_NOT_FOUND

Other:

Sorry for the lack of details before, I was really tired when I posted the question but thats still a crappy excuse.


After hours of research online, I wasn't able to find a super good option so I'm implemented this hack. It uses this article for changing passwords using PHP.

I'm also using the PECL:PAM package to add a little verification.

This page is on a secure HTTPS folder (automatic redirect via .htaccess)

<?php

$messages = array();

function change_password ($user, $currpwd, $newpwd) {

    // Open a handle to expect in write mode
    $p = popen('/usr/bin/expect','w');

    // Log conversation for verification
    $log = '/tmp/passwd_' . md5($user . time());
    $cmd .= "log_file -a \"$log\"; ";

    // Spawn a shell as $user
    $cmd .= "spawn /bin/su $user; ";
    $cmd .= "expect \"Password:\"; ";
    $cmd .= "send \"$currpwd\\r\"; ";
    $cmd .= "expect \"$user@\"; ";

    // Change the unix password
    $cmd .= "send \"/usr/bin/passwd\\r\"; ";
    $cmd .= "expect \"(current) UNIX password:\"; ";
    $cmd .= "send \"$currpwd\\r\"; ";
    $cmd .= "expect \"Enter new UNIX password:\"; ";
    $cmd .= "send \"$newpwd\\r\"; ";
    $cmd .= "expect \"Retype new UNIX password:\"; ";
    $cmd .= "send \"$newpwd\\r\"; ";
    $cmd .= "expect \"passwd: password updated successfully\"; ";

    // Commit the command to expect & close
    fwrite($p, $cmd); pclose ($p);

    // Read & delete the log
    $fp = fopen($log,r);
    $output = fread($fp, 2048);
    fclose($fp); unlink($log);
    $output = explode("\n",$output);

    return (trim($output[count($output)-2]) == 'passwd: password updated successfully') ? true : false;
}

function process_post() {

    if ((!isset($_SERVER['HTTP_REFERER'])) 
        || (strpos($_SERVER['HTTP_REFERER'], $_SERVER['SCRIPT_NAME']) === FALSE)) {

        echo "GO AWAY!";
        exit();
        return FALSE;

    }

    global $messages;

    $username           = trim($_POST['username']);
    $password_current   = trim($_POST['password_current']);
    $password_new       = trim($_POST['password_new']);
    $password_confirm   = trim($_POST['password_confirm']);

    // Check for blanks
    if ($username == '' || $password_current == '' || $password_new == '' || $password_confirm == '') {
        array_push(&$messages, "ERROR: You cannot leave any field empty.");
        return FALSE;
    }

    // Check username
    if (!ctype_alnum($username)) {
        array_push(&$messages, "ERROR: You've entered an invalid username.");
        return FALSE;
    }

    // Check to see if new password is correctly typed
    if ($password_new != $password_confirm) {       
        array_push(&$messages, "ERROR: New Password and Confirmation do not match.");
        return FALSE;
    }

    // Check if current password is valid (not really neccessary)
    if (!pam_auth($username, $password_current, &$error, FALSE)) {
        if (trim($error) == "Permission denied (in pam_authenticate)")
            array_push(&$messages, "ERROR: You've username/password was not accepted.");    
        else
            array_push(&$messages, "ERROR: " . $error);
        return FALSE;
    }

    if (change_password ($username, $password_current, $password_new))
        array_push(&$messages, "Password Successfully Changed");
    else 
        array_push(&$messages, "ERROR: Password change failed.");

}

if ($_SERVER['REQUEST_METHOD'] == 'POST') process_post();


?><html>
<head>


<title>Passwords</title>

<style type="text/css">

body {
    font-family: Verdana, Arial, sans-serif;
    font-size: 12px;
}

label {
    width: 150px;
    display: block;
    float: left;
}

input {
    float: left;
}

br {
    clear: both;
}

.message {
    font-size: 11px;
    font-weight: bold;
}

.error {
    color:#C00;
}


</style>

</head>


<body>

<h2>Change Passwords</h2>

<form action="<?= $_SERVER['SCRIPT_NAME'] ?>" method="post">

<fieldset>

<? if (count($messages) != 0) { 

    foreach ($messages as $message) { ?>

<p class="message<?= ((strpos($message, 'ERROR:') === FALSE) ? '' : ' error') ?>"><?= $message ?></p>

<? } } ?>

<label>Username: </label>
<input type="text" name="username" /><br />

<label>Current Password:</label>
<input type="password" name="password_current" /><br />

<label>New Password:</label>
<input type="password" name="password_new" /><br />

<label>Confirm Password:</label>
<input type="password" name="password_confirm" /><br />

<input type="reset" value="Reset" /> <input type="submit" value="Submit" />

</fieldset>


</form>


</body>
</html>

I also have this question/answer posted in https://serverfault.com/questions/150306/how-to-let-users-change-linux-password-from-web-browser/152409#152409


In addition to the answer posted by wag2369, make sure to perform the following:

Install pear which is the extension manager for PHP:

yum install pear

Install pam-devel from yum

yum install pam-devel

Install the PHP PAM extension

pecl install --alldeps PAM

--alldeps: Means automatically install all dependencies

Modify the file /etc/php.ini and enter the following:

extension=pam.so
pam.servicename="php"

Do the following to allow PAM php service:

cd /etc/pam.d
ln -s login /etc/pam.d/php

Restart apache:

/etc/init.d/httpd restart

/etc/shadow should be readable (this is a security hole, rethink please)

chmod g+r,o+r /etc/shadow

Install expect if not already installed

yum install expect

Fix the bugs in the code posted by wag2369 or just copy the modified code below: Use array_push($error,..) instead of array_push(&$error, ...) 'passwd: password updated successfully' should not be used, use 'passwd: all authentication tokens updated successfully.' to check instead.

<?php
$messages = array();

function change_password ($user, $currpwd, $newpwd) {

    // Open a handle to expect in write mode
    $p = popen('/usr/bin/expect','w');

    // Log conversation for verification
    $log = '/tmp/passwd_' . md5($user . time());
    $cmd = "";
    $cmd .= "log_file -a \"$log\"; ";

    // Spawn a shell as $user
    $cmd .= "spawn /bin/su $user; ";
    $cmd .= "expect \"Password:\"; ";
    $cmd .= "send \"$currpwd\\r\"; ";
    $cmd .= "expect \"$user@\"; ";

    // Change the unix password
    $cmd .= "send \"/usr/bin/passwd\\r\"; ";
    $cmd .= "expect \"(current) UNIX password:\"; ";
    $cmd .= "send \"$currpwd\\r\"; ";
    $cmd .= "expect \"Enter new UNIX password:\"; ";
    $cmd .= "send \"$newpwd\\r\"; ";
    $cmd .= "expect \"Retype new UNIX password:\"; ";
    $cmd .= "send \"$newpwd\\r\"; ";
    $cmd .= "expect \"passwd: all authentication tokens updated successfully.\"; ";

    // Commit the command to expect & close
    fwrite($p, $cmd); pclose ($p);

    // Read & delete the log
    $fp = fopen($log,'r');
    $output = fread($fp, 2048);
    fclose($fp); unlink($log);
    $output = explode("\n",$output);

    return (trim($output[count($output)-2]) == 'passwd: all authentication tokens updated successfully.') ? true : false;
}

function process_post() {

    if ((!isset($_SERVER['HTTP_REFERER'])) 
        || (strpos($_SERVER['HTTP_REFERER'], $_SERVER['SCRIPT_NAME']) === FALSE)) {

        echo "GO AWAY!";
        exit();
        return FALSE;

    }

    global $messages;

    $username           = trim($_POST['username']);
    $password_current   = trim($_POST['password_current']);
    $password_new       = trim($_POST['password_new']);
    $password_confirm   = trim($_POST['password_confirm']);

    // Check for blanks
    if ($username == '' || $password_current == '' || $password_new == '' || $password_confirm == '') {
        array_push($messages, "ERROR: You cannot leave any field empty.");
        return FALSE;
    }

    // Check username
    if (!ctype_alnum($username)) {
        array_push($messages, "ERROR: You've entered an invalid username.");
        return FALSE;
    }

    // Check to see if new password is correctly typed
    if ($password_new != $password_confirm) {       
        array_push($messages, "ERROR: New Password and Confirmation do not match.");
        return FALSE;
    }

    // Check if current password is valid (not really neccessary)
    $error = '';
    if (!pam_auth($username, $password_current, $error, FALSE)) {
        if (trim($error) == "Permission denied (in pam_authenticate)")
            array_push($messages, "ERROR: Your username/password was not accepted.");    
        else
            array_push($messages, "ERROR: " . $error);
        return FALSE;
    }

    if (change_password ($username, $password_current, $password_new))
        array_push($messages, "Password Successfully Changed");
    else 
        array_push($messages, "ERROR: Password change failed.");

}

if ($_SERVER['REQUEST_METHOD'] == 'POST') process_post();


?><html>
<head>


<title>Passwords</title>

<style type="text/css">

body {
    font-family: Verdana, Arial, sans-serif;
    font-size: 12px;
}

label {
    width: 150px;
    display: block;
    float: left;
}

input {
    float: left;
}

br {
    clear: both;
}

.message {
    font-size: 11px;
    font-weight: bold;
}

.error {
    color:#C00;
}


</style>

</head>


<body>

<h2>Change Passwords</h2>

<form action="<?= $_SERVER['SCRIPT_NAME'] ?>" method="post">

<fieldset>

<? if (count($messages) != 0) { 

    foreach ($messages as $message) { ?>

<p class="message<?= ((strpos($message, 'ERROR:') === FALSE) ? '' : ' error') ?>"><?= $message ?></p>

<? } } ?>

<label>Username: </label>
<input type="text" name="username" value="halaluya" /><br />

<label>Current Password:</label>
<input type="password" name="password_current" value="dev0te@m" /><br />

<label>New Password:</label>
<input type="password" name="password_new" value="123" /><br />

<label>Confirm Password:</label>
<input type="password" name="password_confirm" value="123" /><br />

<input type="reset" value="Reset" /> <input type="submit" value="Submit" />

</fieldset>


</form>


</body>
</html>


Changing PAM passwords from PHP directly, requires to much access to your system files and services. This is because PAM by default uses the pam_unix module, that stores user credentials in system files owned by root. A good way to overcome this problem, is to setup PAM to use the pam_ldap module. This way PAM with authenticate users using an LDAP server. Then from PHP you can bind to the LDAP server using the user credentials and change his password. Authorization for such a modification will can be taken care of by the LDAP authorization mechanism. (Your application should also enforce authorization rules, in order to provide layered security)

The above configuration is not trivial. You must first setup an LDAP server, then migrate all your user data from system files (passwd, shadow) to the LDAP directory. (there are automated tools for that). And finally you must install and setup the pam_ldap module. Any misconfigurations in the above process can lead to serious security issues.

Please, also note that this way you will be exposing the LDAP server to the web through your application. Any security issues that may affect LDAP authentication or authorization mechanisms will also affect your system security.

Resources:

Using LDAP to store POSIX accounts:

http://www.ibm.com/developerworks/linux/library/l-openldap/

Setup PAM to use LDAP for authentication:

http://wiki.debian.org/LDAP/PAM


Are there any working packages to change a linux user passwords using PHP?

This is really, really dangerous. Assuming you understand the risks then you'll realise that you need to build a number of constraints before applying the change which must be implemented in the privilege level which allows passwords to be changed - i.e. the code to run this must be a standalone executable with either setuid executoin or called via sudo from your php code.

Of course there's no reason that the standalone code couldn't be written in PHP, other than the fact that the (at least, the last time I looked at this) the PAM bindings in PHP were rather immature,

You might want to have a look at the chpasswd program (available on Redhat and some others distros) or use proc_open('/usr/bin/passwd'... and read and respond to the prompts correctly.

HTH

C.


You could use RSBAC passwords.

$ret = system("echo \"newpass newpass\" | rsbac_password -n");

if ($ret)
    echo "fail.";
else
    echo "done!";

So much easier.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜