I have written this Active Directory password changer script. Comments and testing is appreciated for things I may have overlooked, since this is my first AD Script.There are two parts: PHP and PowerShell.This is being run on an IIS Server with PHP 5.4.x with Fast CGI and PowerShell 2.x.The IIS_IUSRS is a manager of a group of all people who can change their own password with this method (as I exclude any account with access to the server).If IIS_IUSRS is not a manager of a group of people you will get Access Denied in the log.My goal with this script is to change passwords of people that cannot locally access domain computers. While I want this script as secure as possible, limiting those that can change their password and not allowing those with elevated permissions to change password. I was unable to find an escape character function and had to make my own. Feel free to test this script beyond breaking point and comment, as I would like to see how well I have done in scripting something for Active Directory.Can anyone confirm that I don't need to escape if possible? Post results if you do break it, please.Here is the PHP script:<?php setlocale(LC_CTYPE, en_US.UTF-8); ?><!DOCTYPE HTML PUBLIC -//W3C//DTD HTML 4.01 Transitional//EN http://www.w3.org/TR/html4/loose.dtd><html><head><title>Pw Changer</title><link rel=stylesheet href=style.css></head><body><?php/** Note: Errorstate var is changeable client side and should not be trusted*/$psScriptPath = .\Bin\Non-Auth\adpwchange2014.ps1;// Path to the PowerShell script.$logfile = './Logging/Phpruntime.txt';$date = date('D, d M Y H:i:s');if(!empty($_GET[successstate])){//Achievement Get: PW Changer    echo '<div class=successstate>'. $_GET[successstate] .'</div>';    exit(header('refresh:5; ../index.php'));    }if(!isset($_POST[submit])){    if(!empty($_GET[errorstate])){echo '<div class=errorstate>' . $_GET[errorstate] . '</div><br /><br />';}    // if there was no submit variable passed to the     // script (i.e. user has visited the page without clicking submit), display the form:    echo '<form name=testForm class=formbox id=testForm action=index.php method=post />    Username: <input type=text name=username id=username/><br />    Old Password: <input type=password name=old_password><br />    New Password: <input type=password name=new_password><br />    Confirm New Password: <input type=password name=confirm><br />    <input type=submit name=submit id=submit value=submit />    </form>';}elseif(!empty($_POST[username]) && !empty($_POST[old_password]) && !empty($_POST[new_password]) && !empty($_POST[confirm])){// Else if submit was pressed, check if all of the required variables have a value and then Use PHP to check for risks such as Username in password or useing old password    $errorstate = '';    if($_POST[new_password] != $_POST[confirm]){        $errorstate .= 'New Password and Confirm do not match</br>';        }    $username = utf8_decode($_POST[username]);    $old_password = utf8_decode($_POST[old_password]);    $new_password = utf8_decode($_POST[new_password]);    $confirm = utf8_decode($_POST[confirm]);    if(strlen($new_password) <= 8){//Length Check equal or greater then        $errorstate .= 'Eight or more charictors needed</br>';        }    if(strpos($new_password,$old_password) !== false){//New Password Matches username or old password        $errorstate .= 'Can not contain your old password</br>';        }    if(strpos($new_password, $username) !== false){        $errorstate .= 'Can not contain your Username</br>';        }    $operator = array('\\','#','+','<','>',';','\','=',',');//Operators that need to be escaped with    $replace = array('\\\\','\\#','\\+','\\<','\\>','\\;','\\','\\=','\,');//replacement    $username = str_replace ($operator, $replace, $username);    #$new_password = str_replace ($operator, $replace, $new_password);    #$old_password = str_replace ($operator, $replace, $old_password);    $check_upper = 0;    $check_lower = 0;    $check_digit = 0;    $check_punct = 0;    foreach(count_chars($new_password, 1) as $key => $value){//Strength Test Results can be derived from $value        if(!ctype_upper(chr($key))){$check_upper=1;}//if Upper-case        if(!ctype_lower(chr($key))){$check_lower=1;}//if Lower-case        if(!ctype_digit(chr($key))){$check_digit=1;}//if Numeric        if(!ctype_punct(chr($key))){$check_punct=1;}//if Symbol        if($check_upper + $check_lower + $check_digit + $check_punct>= 3){}//Save us from checking the entire string        }    if($check_upper + $check_lower + $check_digit + $check_punct<= 2){        $errorstate .= 'Password needs to contain at least 3 of the following criteria: Upper-case, Lower-case, Numeric and/or Symbol</br>';        }    if(!empty($errorstate)){//EXIT if error state is set. Do not pass go, do not collect $200.        exit(header('Location: .?errorstate='.$errorstate));        }    $user = $username;    $username = base64_encode($username); //Transport Layer Base64    $new_password = base64_encode($new_password); //Transport Layer Base64    $old_password = base64_encode($old_password); //Transport Layer Base64    /*    * The danger happens here as it is sent to powershell.    */    $query = shell_exec('powershell.exe -ExecutionPolicy ByPass -command ' . $psScriptPath . ' < NUL -base64_username ' . $username . ' < NUL -base64_oldpassword ' . $old_password . ' < NUL -base64_newpassword ' . $new_password . ' < NUL');// Execute the PowerShell script, passing the parameters    /*    *Log the query result    */    if(stristr($query, 'Success:') !== false){ //Return True        $logstr = '========================================'.\r\n;        $logstr .= '    ' . $date . ' - Success'.\r\n;        $logstr .= '========================================'.\r\n;        $logstr .= $_SERVER['REMOTE_ADDR'] . ' - ' . $user .: Attempted Password Change result \r\n;        $logstr .= $query . \r\n;        $logstr .= \r\n;        file_put_contents($logfile, $logstr, FILE_APPEND | LOCK_EX);        $errorstate = '</br>Success: Password was changed</br>';        exit(header('Location: ./index.php?successstate='.$errorstate));    }elseif(stristr($query, 'Failed:') !== false){ //Return False        $logstr = '========================================'.\r\n;        $logstr .= '    ' . $date . ' - Failed'.\r\n;        $logstr .= '========================================'.\r\n;        $logstr .= $_SERVER['REMOTE_ADDR'] . ' - ' . $user .: Attempted Password Change result \r\n;        $logstr .= $query . \r\n;        $logstr .= \r\n;        file_put_contents($logfile, $logstr, FILE_APPEND | LOCK_EX);        $errorstate = '</br>Failed: Password was not changed</br>';        exit(header('Location: .?errorstate='.$errorstate));    }else{//someone broke something not that we tell them but we log the entry        $logstr = '========================================'.\r\n;        $logstr .= '            ' . $date . ' - Error Warning'.\r\n;        $logstr .= '========================================'.\r\n;        $logstr .= $_SERVER['REMOTE_ADDR'] . ' - ' . $user .: Attempted Password Change result \r\n;        $logstr .= 'powershell.exe -ExecutionPolicy ByPass -command ' . $psScriptPath . ' < NUL -username ' . $username . ' < NUL -oldpassword ' . $old_password . ' < NUL -newpassword ' . $new_password . ' < NUL' . \r\n;        $logstr .= $query . \r\n;        $logstr .= 'Username: ' .$username . \r\n;        $logstr .= 'Old Password: ' .$old_password . \r\n;        $logstr .= 'New Password: ' .$new_password . \r\n;        $logstr .= \r\n;        file_put_contents($logfile, $logstr, FILE_APPEND | LOCK_EX);        //You could go one step further and ban IP for X time // you could also send an email to yourself        $errorstate = '</br>Failed: Password was not changed</br>';        exit(header('Location: .?errorstate='.$errorstate));        }}else{// Else the user hit submit without all required fields being filled out:    $errorstate = 'Please Complete all fields</br>';    exit(header('Location: .?errorstate='.$errorstate));    }?></body></html>And here is the PowerShell:#*=============================================================================#* Script Name: adpwchange2014.ps1#* Created: 2014-10-07#* Author:#* Purpose: This is a simple script that queries AD users.#* Reference Website: http://theboywonder.co.uk/2012/07/29/executing-powershell-using-php-and-iis/#* #*=============================================================================#*=============================================================================#* PARAMETER DECLARATION#*=============================================================================param([string]$base64_username,[string]$base64_newpassword,[string]$base64_oldpassword)#*=============================================================================#* IMPORT LIBRARIES#*=============================================================================if ((Get-Module | where {$_.Name -match ActiveDirectory}) -eq $null){#Loading moduleWrite-Host Loading module AcitveDirectory...Import-Module ActiveDirectory}#*=============================================================================#* PARAMETERS#*=============================================================================$username = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($base64_username))$newpassword = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($base64_newpassword))$oldpassword = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($base64_oldpassword))#*=============================================================================#* INITIALISE VARIABLES#*=============================================================================# Increase buffer width/height to avoid PowerShell from wrapping the text before# sending it back to PHP (this results in weird spaces).$pshost = Get-Host$pswindow = $pshost.ui.rawui$newsize = $pswindow.buffersize$newsize.height = 1000$newsize.width = 300$pswindow.buffersize = $newsize#*=============================================================================#* EXCEPTION HANDLER#*=============================================================================#*=============================================================================#* FUNCTION LISTINGS#*=============================================================================    Function Test-ADAuthentication  {        Param($Auth_User, $Auth_Pass)        $domain = $env:USERDOMAIN        Add-Type -AssemblyName System.DirectoryServices.AccountManagement        $ct = [System.DirectoryServices.AccountManagement.ContextType]::Domain        $pc = New-Object System.DirectoryServices.AccountManagement.PrincipalContext($ct, $domain)        $pc.ValidateCredentials($Auth_User, $Auth_Pass).ToString()        }    Function Set-ADAuthentication{        Param($Auth_User,$Auth_OldPass, $Auth_NewPass)        $domain = $env:USERDOMAIN        $Auth_NewPass = ConvertTo-SecureString $Auth_NewPass -AsPlainText -Force        $Auth_OldPass = ConvertTo-SecureString $Auth_OldPass -AsPlainText -Force        #Running -whatif to simulate results        #Therefore we expect Failed: Password change as it was not changed        Set-ADAccountPassword -Identity $Auth_User -NewPassword $Auth_NewPass -OldPassword $Auth_OldPass -PassThru        $authentication = Test-ADAuthentication $username $newpassword        if ($authentication -eq $TRUE) {            Write-Output Success: Password Changed            }elseif ($authentication -eq $FALSE) {            Write-Output Failed: Password Change            }else {                Write-Output Error: EOS                EXIT                NUL                Stop-Process -processname powershell*                }        }#*=============================================================================#* Function: function1#* Purpose: This function does X Y Z#* =============================================================================#*=============================================================================#* END OF FUNCTION LISTINGS#*=============================================================================#*=============================================================================#* SCRIPT BODY#*=============================================================================Write-Output $PSVersionTableWrite-Output   $authentication = Test-ADAuthentication $username $oldpasswordif ($authentication -eq $TRUE) {    Set-ADAuthentication $username $oldpassword $newpassword    }elseif ($authentication -eq $FALSE) {    Write-Output Failed: Validation    }else {Write-Output Error: EOS    EXIT    NUL    Stop-Process -processname powershell*    }#*=============================================================================#* SCRIPT Exit#*=============================================================================EXITNULStop-Process -processname powershell*
Active directory password changer
php;powershell;active directory
I don't really know anything about powershell, so I will only look at your PHP script.XSSecho '<div class=successstate>'. $_GET[successstate] .'</div>';This is vulnerable to reflected XSS, with which an attacker could execute arbitrary Javascript on a victims computer (and thus steal cookies, deface the website, display a phishing form, etc). Use htmlspecialchars to prevent this (same with errorstate).FunctionsYour code isn't all that long, but for 150 lines, I would extract some code to functions to structure it. For example, displayPasswordChangeForm and processPasswordChangeForm, and checkPasswordStrength.I would also add a logQueryResult function to avoid duplicate code:function logQueryResult($queryString, $date, $result, $redirect, $additional) {    $logstr = '========================================'.\r\n;    $logstr .= '    ' . $date . ' - ' . $result . .\r\n;    $logstr .= '========================================'.\r\n;    $logstr .= $_SERVER['REMOTE_ADDR'] . ' - ' . $user .: Attempted Password Change result \r\n;    $logstr .= $query . \r\n;    $logstr .= $additional;    $logstr .= \r\n;    file_put_contents($logfile, $logstr, FILE_APPEND | LOCK_EX);    $errorstate = '</br>' . $result . ': Password was changed</br>';    exit(header('Location: ' $redirect));}// use like this:logQueryResult($query, $date, 'Success', './index.php?successstate='.$errorstate, '');logQueryResult($query, $date, 'Failed', '.?errorstate='.$errorstate, '');$plainUserPass = 'Username: ' .$username . \r\n;$plainUserPass .= 'Old Password: ' .$old_password . \r\n;$plainUserPass .= 'New Password: ' .$new_password . \r\n;logQueryResult($query, $date, 'Error Warning', '.?errorstate='.$errorstate, $plainUserPass);Miscwhen building a string, either use all double or all single quote. Code Like $var = 'foo' . $test . 'bar' . '\n' for example is hard to read.always close your curly brackets at the same time (I would close it where the opening line began, it's easier to see which block it closes that way).personally, I would use more spaces (before {, after } and ,, around ., etc.)