0

Is there a possible way of injecting code to pull data from Active directory?

I have written a PHP page that executes Powershell and is able to change passwords to a controlled group of people. However I'm still at a loss on whether or not i can inject code into my script.

Currently everything from space and all special characters have not been needed to be escaped.

PHP

<?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');
$rand = mt_rand();

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'.$rand.':') !== false){ //Return True
                $logstr = '========================================'."\r\n";
                $logstr .= '                            ' . $date . ' - Success'."\r\n";
                $logstr .= '========================================'."\r\n";
                $logstr .= $_SERVER['REQUEST_TIME_FLOAT'] . "\r\n";
                $logstr .= $_SERVER['REMOTE_ADDR'] . ' - ' . $user .": Attempted Password Change result \r\n";
                $logstr .= $query;
                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'.$rand.':') !== false){ //Return False
                $logstr = '========================================'."\r\n";
                $logstr .= '                            ' . $date . ' - Failed'."\r\n";
                $logstr .= '========================================'."\r\n";
                $logstr .= $_SERVER['REQUEST_TIME_FLOAT'] . "\r\n";
                $logstr .= $_SERVER['REMOTE_ADDR'] . ' - ' . $user .": Attempted Password Change result \r\n";
                $logstr .= $query;
                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['REQUEST_TIME_FLOAT'] . "\r\n";
                $logstr .= $_SERVER['REMOTE_ADDR'] . ' - ' . $user .": Attempted Password Change result \r\n";
                $logstr .= 'powershell.exe -ExecutionPolicy ByPass -command "' . $psScriptPath . '" < NUL -rand "' . $rand . '" < NUL -username "' . $username . '" < NUL -oldpassword "' . $old_password . '" < NUL -newpassword "' . $new_password . '" < NUL' . "\r\n";
                $logstr .= $query;
                $logstr .= 'Username: ' .$username . "\r\n";
                $logstr .= 'Old Password: ' .$old_password . "\r\n";
                $logstr .= 'New Password: ' .$new_password . "\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
                $headers = 'From: webmaster@example.com' . "\r\n" .
                'Reply-To: no-reply@example.com' . "\r\n" .
                'X-Mailer: PHP/' . phpversion();
                imap_mail('webmaster@example.com', 'PHP/Powershell AD - Error Warning', $logstr, $headers);
                $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>

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,
[string]$rand
)

#*=============================================================================
#* IMPORT LIBRARIES
#*=============================================================================

if ((Get-Module | where {$_.Name -match "ActiveDirectory"}) -eq $null){
    #Loading module
    Write-Host "Loading module AcitveDirectory..."
    Import-Module ActiveDirectory
    }else{
    write-output "Error: Please install ActiveDirectory Module"
    EXIT
    NUL
    Stop-Process -processname powershell*
    }
#*=============================================================================
#* 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:$rand Password Changed"
            }elseif ($authentication -eq $FALSE) {
            Write-Output "Failed$rand: 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 $PSVersionTable
Write-Output "  "
$authentication = Test-ADAuthentication "$username" "$oldpassword"
if ($authentication -eq $TRUE) {
    Set-ADAuthentication $username $oldpassword $newpassword
    }elseif ($authentication -eq $FALSE) {
    Write-Output "Failed$rand: Validation"
    }else {Write-Output "Error: EOS"
    EXIT
    NUL
    Stop-Process -processname powershell*
    }

#*=============================================================================
#* SCRIPT Exit
#*=============================================================================
Write-Output "End Of Script"
EXIT
NUL
Stop-Process -processname powershell*

Notes:

  • I do realise that base64 is only encapsulation
  • I am new to Powershell and the Active Directory module.
  • $Rand Explanation - as php looks for "Success:" or "Failure:" someone could input Success: as a username and have it return Success (although may not be the case). I now look for Success$Rand: or Failure$Rand:
  • If variables are sent from the PHP script to Powershell and these are not sanitized then it might be possible to run other Powershell commands. Since you did not post your PHP script it's hard to tell. – Jeroen Nov 24 '14 at 5:40
  • I didn't want to post code here as i have already posted it to code review. I merely wanted to pose the question is it possible. – Werezwolf Nov 24 '14 at 5:49
  • Base64 is encoding. In the PHP script, the variable $psScriptPath does not seem to be defined, but this might actually be the parameter that could inject other powershell commands. – Jeroen Nov 24 '14 at 6:35
  • Injection also seems possible for $username, $newpasword and $oldpassword. It's recommended to perform input validation on these variables. – Jeroen Nov 24 '14 at 6:36
  • Forgive me i was trying to shorten my post. As i do realise that it is recommended to perform input validation by end-users. My very problem is i havent found a way to escape charicters for active directory. How Powershell handles variables and how active directory handles imput is very different from normal SQL injection attacks. i have tryed the following and couldent brake my script `~!@#$%^&*()_+-={}|[]\:";'<>?,./ thus why i have posed this question is active directory vunrable to injection. – Werezwolf Nov 24 '14 at 6:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.