Prevención de ataques de inyección de scripts
Los scripts de PowerShell, al igual que otros lenguajes de programación, pueden ser vulnerables a ataques de inyección. Un ataque de inyección ocurre cuando un usuario proporciona información sobre una función vulnerable que incluye comandos adicionales. La función vulnerable ejecuta comandos adicionales, lo que puede representar una vulnerabilidad de seguridad grave. Por ejemplo, un usuario malintencionado podría abusar de la función vulnerable para ejecutar código arbitrario en una computadora remota, posiblemente comprometiendo esa computadora y obteniendo acceso a otras máquinas en la red.
Una vez que sea consciente del problema, existen varias formas de protegerse contra los ataques de inyección.
Ejemplo de código vulnerable
Las vulnerabilidades de inyección de código de PowerShell implican la entrada del usuario que contiene código de script. La entrada del usuario se agrega al script vulnerable donde PowerShell la analiza y ejecuta.
function Get-ProcessById
{
param ($ProcId)
Invoke-Expression -Command "Get-Process -Id $ProcId"
}
La función Get-ProcessById busca un proceso local por su valor de Id. Toma un argumento de parámetro $ProcId de cualquier tipo. Luego, $ProcId se convierte en una cadena y se inserta en otro script que se analiza y ejecuta mediante el cmdlet Invoke-Expression. Esta función funciona bien cuando se pasa un número entero de ID de proceso válido.
Get-ProcessById $pid
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
97 50.09 132.72 1.20 12528 3 pwsh
Sin embargo, el parámetro $ProcId no especifica un tipo. Acepta cualquier valor de cadena arbitrario que pueda incluir otros comandos.
Get-ProcessById "$pid; Write-Host 'pwnd!'"
En este ejemplo, la función recuperó correctamente el proceso identificado por $pid, pero también ejecutó el script inyectado Write-Host 'pwnd!'.
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
92 45.66 122.52 1.06 21736 3 pwsh
pwnd!
Formas de protegerse contra ataques de inyección
Hay varias formas de protegerse contra un ataque de inyección.
Usar entrada escrita
Puede especificar un tipo para el argumento $ProcId.
function Get-ProcessById
{
param ([int] $ProcId)
Invoke-Expression -Command "Get-Process -Id $ProcId"
}
Get-ProcessById "$pid; Write-Host 'pwnd!'"
Get-ProcessById:
Line |
7 | Get-ProcessById "$pid; Write-Host 'pwnd!'"
| ~~~~~~~~~~~~~~~~~~~~~~~~~
| Cannot process argument transformation on parameter 'ProcId'. Cannot convert value
"8064; Write-Host 'pwnd!'" to type "System.Int32". Error: "The input string '8064; Write-Host 'pwnd!'
was not in a correct format."
Aquí, el parámetro de entrada $ProcId está restringido a un tipo entero, por lo que se produce un error cuando se pasa una cadena que no se puede convertir a un número entero.
No utilice Invoke-Expression
En lugar de utilizar Invoke-Expression, llame directamente a Get-Process y deje que el enlazador de parámetros de PowerShell valide la entrada.
function Get-ProcessById
{
param ($ProcId)
Get-Process -Id $ProcId
}
Get-ProcessById "$pid; Write-Host 'pwnd!'"
Get-Process:
Line |
5 | Get-Process -Id $ProcId
| ~~~~~~~
| Cannot bind parameter 'Id'. Cannot convert value "8064; Write-Host 'pwnd!'" to type
"System.Int32". Error: "The input string '8064; Write-Host 'pwnd!' was not in a correct
format."
Como práctica recomendada, debe evitar el uso de Invoke-Expression, especialmente al manejar la entrada del usuario. Invoke-Expression es peligroso porque analiza y ejecuta cualquier contenido de cadena que proporciones, haciéndolo vulnerable a ataques de inyección. Es mejor confiar en el enlace de parámetros de PowerShell.
Envolver cadenas entre comillas simples
Sin embargo, hay ocasiones en las que el uso de Invoke-Expression es inevitable y también es necesario manejar la entrada de cadenas del usuario. Puede manejar de forma segura la entrada del usuario utilizando comillas simples alrededor de cada variable de entrada de cadena. La comilla simple garantiza que el analizador de PowerShell trate la entrada del usuario como un literal de cadena única.
function Get-ProcessById
{
param ($ProcId)
Invoke-Expression -Command "Get-Process -Id '$ProcId'"
}
Get-ProcessById "$pid; Write-Host 'pwnd!'"
Get-Process: Cannot bind parameter 'Id'. Cannot convert value "8064; Write-Host " to type
"System.Int32". Error: "The input string '8064; Write-Host' was not in a correct format."
Sin embargo, esta versión de la función aún no está completamente a salvo de ataques de inyección. Un usuario malintencionado aún puede utilizar comillas simples en sus entradas para inyectar código.
Get-ProcessById "$pid'; Write-Host 'pwnd!';'"
Este ejemplo utiliza comillas simples en la entrada del usuario para forzar a la función a ejecutar tres declaraciones separadas, una de las cuales es código arbitrario inyectado por el usuario.
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
97 46.08 183.10 1.08 2524 3 pwsh
pwnd!
Utilice el método EscapeSingleQuotedStringContent()
Para protegerse contra el usuario que inserta sus propios caracteres de comillas simples para explotar la función, debe utilizar la API EscapeSingleQuotedStringContent(). Este es un método público estático de la clase System.Management.Automation.Language.CodeGeneration de PowerShell. Este método hace que la entrada del usuario sea segura al evitar las comillas simples incluidas en la entrada del usuario.
function Get-ProcessById
{
param ($ProcId)
$ProcIdClean = [System.Management.Automation.Language.CodeGeneration]::
EscapeSingleQuotedStringContent("$ProcId")
Invoke-Expression -Command "Get-Process -Id '$ProcIdClean'"
}
Get-ProcessById "$pid'; Write-Host 'pwnd!';'"
Get-Process: Cannot bind parameter 'Id'. Cannot convert value "8064'; Write-Host 'pwnd!';'" to type
"System.Int32". Error: "The input string '8064'; Write-Host 'pwnd!';'' was not in a correct format."
Para obtener más información, consulte EscapeSingleQuotedStringContent().
Detectando código vulnerable con Inyección Hunter
Injection Hunter es un módulo escrito por Lee Holmes que contiene reglas de PowerShell Script Analyzer para detectar vulnerabilidades de inyección de código. Utilice uno de los siguientes comandos para instalar el módulo desde la Galería de PowerShell:
# Use PowerShellGet v2.x
Install-Module InjectionHunter
# Use PowerShellGet v3.x
Install-PSResource InjectionHunter
Puede utilizar esto para automatizar el análisis de seguridad durante compilaciones, procesos de integración continua, implementaciones y otros escenarios.
$RulePath = (Get-Module -list InjectionHunter).Path
Invoke-ScriptAnalyzer -CustomRulePath $RulePath -Path .\Invoke-Dangerous.ps1
RuleName Severity ScriptName Line Message
-------- -------- ---------- ---- -------
InjectionRisk.InvokeExpression Warning Invoke-Dan 3 Possible script injection risk via the
gerous.ps1 Invoke-Expression cmdlet. Untrusted input can cause
arbitrary PowerShell expressions to be run.
Variables may be used directly for dynamic parameter
arguments, splatting can be used for dynamic
parameter names, and the invocation operator can be
used for dynamic command names. If content escaping
is truly needed, PowerShell has several valid quote
characters, so [System.Management.Automation.Languag
e.CodeGeneration]::Escape* should be used.
Para obtener más información, consulte PSScriptAnalyzer.