¿Existe alguna forma de inyectar código para extraer datos de Active Directory?
He escrito una página PHP que ejecuta Powershell y puede cambiar las contraseñas a un grupo controlado de personas. Sin embargo, sigo sin saber si puedo o no inyectar código en mi script.
Actualmente, no se necesita escapar de todo, desde el espacio y todos los caracteres especiales.
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: [email protected]' . "\r\n" .
'Reply-To: [email protected]' . "\r\n" .
'X-Mailer: PHP/' . phpversion();
imap_mail('[email protected]', '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*
Notas:
- Me doy cuenta de que base64 es solo encapsulación
- Soy nuevo en Powershell y en el módulo de Active Directory.
- Explicación de $ Rand - mientras php busca "Success:" o "Failure:" alguien podría ingresar Success: como nombre de usuario y devolverlo Success (aunque puede que no sea así). Ahora busco Success $ Rand: o Failure $ Rand: