El manejo seguro de credenciales en PowerShell — Cómo desterrar las contraseñas en texto plano de los scripts

· Actualizado el: · · PowerShell, Windows, Seguridad, Credenciales, DPAPI, Automatización, Mejora operativa, Scripts

Al investigar incidencias o revisar scripts de PowerShell de nuestros clientes, hay algo con lo que nos encontramos con bastante frecuencia. $password = "P@ssw0rd123" — la conexión a un servidor de archivos, la base de datos del sistema troncal, el envío de correo, la clave de una API web. Priorizando que el script funcionara, la contraseña quedó incrustada en texto plano dentro del propio script, y así sigue viva desde hace años en una carpeta compartida o en un repositorio de Git.

Lo complicado es que quien lo escribió también sabe que “no está bien”. Entonces, ¿cuál es la forma correcta de hacerlo? Las opciones se multiplican — SecureString, Export-Clixml, SecretManagement, el Administrador de credenciales, Azure Key Vault… — y no queda claro cuál es el alcance y el límite de cada una. Y encima llega el rumor de que “SecureString ya no se recomienda”, con lo que uno termina sin saber en qué confiar. Esta confusión tiene una razón de ser, y una vez que se ordena, el camino queda claro.

En este artículo, dirigido al personal de sistemas de información y de operaciones que administra scripts operativos internos y procesos por lotes periódicos, partimos de qué es exactamente el problema de las contraseñas en texto plano, organizamos el conjunto de herramientas de credenciales de PowerShell según su mecanismo, y resumimos en una tabla de decisión la solución realista para “qué hacer en ejecuciones desatendidas”. Tomamos como referencia PowerShell 7.x, indicando también las particularidades para quienes trabajan con Windows PowerShell 5.1.

1. Ante todo, la conclusión

  • El problema de la contraseña en texto plano es que “en el momento en que alguien ve el archivo, la fuga ya está confirmada”. El historial de Git, las carpetas compartidas, las copias de seguridad y los registros son, todos ellos, rutas de fuga cada vez que aparece una copia del script. Escribir código que convierte el texto plano en SecureString (ConvertTo-SecureString -AsPlainText) no resuelve nada si el texto plano original permanece en el script o en los registros. 12
  • La forma básica para los scripts de uso interactivo es recibir un PSCredential con Get-Credential. La contraseña no se muestra en pantalla y puede pasarse como objeto al parámetro -Credential de cada comando. 3
  • La documentación oficial de .NET indica explícitamente que SecureString “no se recomienda para nuevos desarrollos”. El cifrado solo se realiza en Windows; en plataformas distintas de Windows el contenido interno no se cifra. Mientras tanto, PowerShell sigue usando SecureString por compatibilidad, así que por ahora hay que seguir conviviendo con él como formato estándar de intercambio. No confíe en exceso en él ni lo use como base de un mecanismo de protección propio. 45
  • Para la ejecución desatendida, guardar el archivo de credenciales cifrado con DPAPI mediante Export-Clixml es la solución mínima y práctica. Solo puede descifrarse con el usuario y en la máquina donde se guardó, de modo que, aunque el archivo se filtre por sí solo, no se puede abrir. La contrapartida es que hay que “guardarlo con la propia cuenta de ejecución de la tarea programada”. En sistemas distintos de Windows no se cifra. 6
  • Cuando hay que manejar varios secretos, gestiónelos de forma centralizada con SecretManagement + SecretStore. Con la interfaz unificada Set-Secret/Get-Secret puede cambiar el destino de almacenamiento, desde el SecretStore local hasta Azure Key Vault. No obstante, en ejecución desatendida queda pendiente el tratamiento de la contraseña del almacén (vault). 78
  • La consideración de máxima prioridad es “un diseño que directamente no tenga credenciales que guardar”. Si se otorgan a la propia cuenta de ejecución (una cuenta de dominio o una gMSA) los permisos sobre el destino de conexión, la contraseña desaparece del script. Con una gMSA se puede delegar en el sistema operativo la propia gestión de la contraseña. 910
  • Incluya en el diseño la fuga hacia registros y transcripciones. La documentación oficial advierte de que, si se habilita el registro de bloques de script, datos sensibles como las credenciales usadas en el script pueden quedar escritos en el registro de eventos. En cuanto se escribe una contraseña en texto plano en la línea de comandos, dé por hecho que queda registrada. 11

2. Qué tiene de malo el texto plano — hay tantas rutas de fuga como copias

Antes de nada, veamos el patrón típico que hay que reescribir.

# Antipatrón: en ambos casos el problema es el mismo, "el texto plano permanece en el script"
$password = "P@ssw0rd123"

# Aunque se convierta a SecureString, el texto plano original sigue escrito en la primera línea.
# PSScriptAnalyzer detecta este uso de -AsPlainText como un error
$secure = ConvertTo-SecureString $password -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential('svc-transfer', $secure)

El motivo por el que escribir la contraseña directamente es peligroso no es solo que “un intruso malintencionado pueda leerla”. Por la propia naturaleza de un script como producto, el mero hecho de operarlo con normalidad hace que las copias se multipliquen.

Dónde se generan las copias Qué ocurre
Repositorio de Git Una contraseña que se confirma (commit) una sola vez queda guardada para siempre en el historial. Aunque se corrija el archivo después, permanece en el historial
Carpeta compartida Se difunde a todos los que tienen permiso de lectura, más las copias de seguridad y las copias por generaciones
Correo/chat En el momento en que se adjunta con un “usa este script”, queda fuera de control
Registro de eventos Si el registro de bloques de script está habilitado, el contenido del código ejecutado queda registrado en el log11
Transcripción Start-Transcript o la configuración de transcripción de la organización registran el contenido que pasó por la pantalla11

Esta estructura significa que la solución al problema de las contraseñas en texto plano no se logra “impidiendo que se vea el archivo”. Por mucho que se restrinjan los permisos de acceso, eso no cierra el historial de Git ni las copias de seguridad. Lo esencial es sacar la contraseña fuera del script, hacia un lugar de almacenamiento cifrado. Esta idea es la misma que expuse sobre los archivos de configuración de aplicaciones .NET en “Almacenamiento seguro de información confidencial en aplicaciones de Windows: evite la configuración en texto plano con DPAPI”, y el principio no cambia en PowerShell.

Cabe señalar que convertir el texto plano en SecureString sobre la marcha con ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force no es una solución. PSScriptAnalyzer (la herramienta oficial de análisis estático) detecta esta forma de escribirlo como un error (Severity: Error). Esto se debe a que sortea el cifrado y expone el texto plano en memoria, además de que el texto plano original sigue permaneciendo dentro del script. 1

3. La realidad de las herramientas — PSCredential, SecureString y sus límites

3.1. Get-Credential y PSCredential

La forma básica para los scripts de uso interactivo se reduce a esto.

# Solicita el nombre de usuario y la contraseña, y los recibe como un objeto PSCredential
$cred = Get-Credential -Message 'Introduzca la cuenta de conexión a la base de datos troncal'

# Se puede pasar tal cual a cualquier comando que tenga el parámetro -Credential
Invoke-Command -ComputerName APPSV01 -Credential $cred -ScriptBlock { hostname }

Get-Credential solicita la introducción del nombre de usuario y la contraseña, y devuelve un objeto PSCredential. En Windows PowerShell 5.1 se muestra un cuadro de diálogo, y desde PowerShell 6 en adelante la entrada se hace por consola. 3 La contraseña se conserva dentro del PSCredential como un SecureString y no se muestra en pantalla. En los escenarios donde se permite que “una persona la introduzca en el momento”, no hace falta hacer nada más complicado que esto.

Quien diseña funciones propias debe evitar recibir la contraseña como [string] y, en cambio, diseñar el parámetro -Credential para que reciba un [PSCredential]. Los detalles de cómo escribirlo están recopilados en la guía oficial “Add Credential support to PowerShell functions”. 2

3.2. La realidad de SecureString — cómo interpretar correctamente el “no recomendado”

Hay tres hechos que conviene conocer sobre SecureString.

  • La documentación oficial de .NET recomienda “no usarlo en nuevos desarrollos”. El cifrado del array interno solo se realiza en Windows; en plataformas distintas de Windows el almacenamiento interno no se cifra. Además, en el momento de usarlo hay que convertirlo de todos modos a una representación en texto plano, así que su único efecto es acortar el tiempo de exposición. La alternativa recomendada es “un identificador opaco hacia una credencial almacenada fuera del proceso”, es decir, mecanismos como el almacén de credenciales del sistema operativo o Key Vault. 4
  • Aun así, el equipamiento estándar de PowerShell da por hecho el uso de SecureString. PowerShell sigue admitiendo SecureString por compatibilidad, y todavía se usa para evitar la exposición accidental en la consola o en los registros. Se posiciona como algo más seguro que una cadena de texto plano, nada más. 5
  • Es fácil volver a convertirlo en texto plano. En PowerShell 7, basta un ConvertFrom-SecureString -AsPlainText para obtener el texto plano. 12 Ajustarse a la realidad significa pensar en SecureString no como “una caja fuerte ilegible”, sino como “un sobre para que no se vea por descuido”.

La conclusión práctica es esta: no se lance a construir un cifrado propio complicado solo porque exista SecureString. Úselo como el formato que exigen las herramientas de PowerShell (Get-Credential, SecretManagement), y ponga el núcleo de la protección en un mecanismo externo al proceso, como DPAPI o un almacén (vault).

4. La opción estándar para ejecución desatendida — el almacenamiento con DPAPI de Export-Clixml y el muro de “mismo usuario, misma máquina”

Un script desatendido que se ejecuta desde el Programador de tareas no puede preguntarle a una persona con Get-Credential. La solución práctica mínima es el archivo de credenciales mediante Export-Clixml.

# --- Preparación (solo una vez, se ejecuta con la cuenta de ejecución de la tarea) ---
$cred = Get-Credential -Message 'Cuenta de enlace'
$cred | Export-Clixml -Path 'D:\Jobs\secrets\transfer.credential'

# --- Script de producción (ejecución desatendida) ---
$cred = Import-Clixml -Path 'D:\Jobs\secrets\transfer.credential'
# Ejemplo: se usa para conectarse a un recurso compartido que requiere otra credencial o para ejecución remota
Invoke-Command -ComputerName FILESV01 -Credential $cred -ScriptBlock { Get-ChildItem D:\Export }

Export-Clixml cifra el objeto de credencial con la DPAPI (interfaz de protección de datos) de Windows y lo guarda. Solo puede descifrarse cuando lo abre la misma cuenta de usuario que lo guardó, en el mismo equipo donde se guardó. El archivo exportado no puede usarse en otra máquina ni con otro usuario. 613 Esta propiedad de que “aunque se sustraiga el archivo, no se puede abrir” es precisamente lo que lo hace conveniente como destino de almacenamiento para la ejecución desatendida.

Por qué ocurre esto se entiende con solo asomarse un nivel al mecanismo. DPAPI cifra una “clave maestra” con una clave derivada de las credenciales de inicio de sesión del usuario (normalmente, el hash de la contraseña) y la coloca dentro del perfil de ese usuario. Los datos en sí se cifran con una clave de sesión generada a partir de esa clave maestra. Es decir, lo que sostiene la clave es, en sí mismo, “poder iniciar sesión como ese usuario”: no hace falta guardar un archivo de claves aparte en algún otro lugar, pero a cambio la clave queda atada al usuario y a la máquina. La documentación oficial también explica que “normalmente, solo puede descifrar quien tenga las mismas credenciales de inicio de sesión que el usuario que cifró, y el cifrado y el descifrado deben realizarse en el mismo equipo”. 14 Esto es, en esencia, lo que significa “limitado a mismo usuario, misma máquina”.

Sin embargo, este “límite de mismo usuario, misma máquina” es a la vez una protección y una trampa operativa.

  • Debe coincidir con la cuenta de ejecución del Programador de tareas. Un archivo .credential creado con su propia cuenta deja de poder descifrarse en el instante en que la tarea se ejecuta con la cuenta de servicio. El trabajo de preparación siempre debe hacerse “como la cuenta de ejecución de la tarea” (iniciar PowerShell con esa cuenta y guardar desde ahí). El planteamiento de la cuenta de ejecución de la tarea y del tipo de inicio de sesión se detalla en “La tarea del Programador de tareas no se ejecuta y termina con 0x1”.
  • Al sustituir el servidor o cambiar de cuenta, es obligatorio volver a crearlo. Es la causa típica de “tras la migración deja de funcionar”, así que conviene convertir el procedimiento de preparación en un script y dejarlo en el repositorio (sin incluir, por supuesto, la contraseña en sí).
  • Se puede descifrar desde cualquier proceso que se ejecute con la misma cuenta. Como DPAPI está abierto a cualquier código que se ejecute como ese usuario, sigue siendo necesaria la higiene habitual: no alojar software innecesario en la cuenta de ejecución y mantener sus permisos al mínimo.
  • En sistemas distintos de Windows no se cifra. La documentación oficial indica explícitamente que en macOS/Linux se genera en la práctica texto plano (una matriz de caracteres Unicode). 6

También dejamos por escrito el procedimiento concreto de “iniciar PowerShell con esa cuenta y guardar”. Guarde la parte de preparación anterior (las dos líneas de Get-Credential y Export-Clixml) como D:\Jobs\save-credential.ps1, e inicie y ejecute PowerShell con esa cuenta.

# Realiza la preparación con la cuenta de ejecución de la tarea: usa runas para iniciar PowerShell como esa cuenta
# Como runas pide la contraseña de forma interactiva, no queda texto plano en la línea de comandos, el historial ni la transcripción
runas /user:CONTOSO\svc-transfer "pwsh.exe -NoProfile -NoExit -File D:\Jobs\save-credential.ps1"

Como runas utiliza un privilegio equivalente al de inicio de sesión interactivo, fallará en entornos donde no se haya concedido a esa cuenta el derecho de “inicio de sesión local”. En ese caso, lo realista es conceder el inicio de sesión solo durante el trabajo de preparación y revertir la configuración una vez terminado el guardado. Dado que la propia entrada de Get-Credential requiere una sesión interactiva, no se puede recurrir al método de “crear una tarea desatendida para hacer la preparación”. Cabe señalar además que, en una máquina donde el perfil de la cuenta de ejecución nunca se ha creado, la clave maestra de DPAPI no se coloca hasta que se crea el perfil en el primer inicio de sesión; esta es otra de las razones para seguir este procedimiento.

También existe la opción de especificar -Key en ConvertFrom-SecureString para cifrar con AES, pero en ese caso suele quedar exactamente el mismo problema desplazado un nivel: “cómo proteger el archivo de la clave”. 12 En cuanto surja la necesidad de cruzar entre máquinas, conviene avanzar hacia el almacén (vault) del siguiente capítulo o hacia el “diseño sin credenciales” del capítulo 6.

5. Cuando aumentan los secretos — SecretManagement y SecretStore

Cuando aumentan los destinos de conexión y se mezclan también claves de API y tokens, la dispersión de archivos .credential llega a su límite de gestión. Ahí es donde entra el módulo SecretManagement. Se trata de una interfaz unificada hacia el lugar de almacenamiento de secretos (el almacén, o vault), y el almacenamiento real lo asume un almacén de extensión. Además de SecretStore (de Microsoft) para almacenamiento local, se pueden manejar con los mismos comandos almacenes de extensión como Azure Key Vault o KeePass. 715

# Solo la primera vez: instalación del módulo y registro del almacén
Install-Module Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore
Register-SecretVault -Name SecretStore -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault

# Registro del secreto (en el primer acceso se pide establecer la contraseña del almacén)
Set-Secret -Name TransferJobCred -Secret (Get-Credential)

# Lado del script: se recupera por nombre. Esta línea no cambia aunque cambie el destino de almacenamiento
$cred = Get-Secret -Name TransferJobCred

Como valor del secreto se pueden almacenar, además de PSCredential y SecureString, cadenas de texto, matrices de bytes y tablas hash, y en el primer acceso se solicita establecer la contraseña que protege el propio almacén. 16 La ventaja es que el conocimiento de “dónde está guardado” desaparece del script. La sustitución de SecretStore en la máquina de desarrollo por Azure Key Vault en producción (el módulo Az.KeyVault aporta el almacén de extensión) se resuelve solo con un cambio de configuración en Register-SecretVault. 177

Por otro lado, hay tres advertencias a tener en cuenta al llevarlo a la ejecución desatendida.

  • De forma predeterminada, SecretStore solicita la contraseña del almacén de manera interactiva. Si se pone tal cual en el Programador de tareas, se queda esperando el aviso. La documentación oficial recomienda, para ejecución desatendida, poner Interaction en None y pasar mediante Unlock-SecretStore la contraseña del almacén guardada en un archivo protegido con DPAPI (Export-Clixml). 8 Es decir, la clave del almacén termina protegiéndose con DPAPI de todos modos, heredando la restricción de mismo usuario, misma máquina del capítulo anterior. También es posible desactivar del todo la solicitud de contraseña (Authentication None), pero entonces la clave queda protegida únicamente por los permisos del sistema de archivos, por lo que no se recomienda para usos que requieran una protección fuerte. 18
  • No funciona con cuentas administradas como las gMSA. SecretManagement depende del perfil ($env:LOCALAPPDATA) y de DPAPI, y se indica explícitamente que actualmente no admite las cuentas administradas de Windows, que no tienen perfil. 15 Si necesita que un trabajo que se ejecuta con una gMSA maneje secretos, esta combinación no es una opción (de hecho, con una gMSA suele ser posible inclinarse directamente hacia un diseño sin secretos — ver el siguiente capítulo).
  • SecretManagement/SecretStore se consideran de funcionalidad completa (feature complete), y su desarrollo activo de nuevas funciones ha terminado. Las correcciones de seguridad y de errores graves continúan, así que usarlos no supone un problema en sí, pero la propia documentación oficial señala que “la naturaleza de los secretos está pasando a modelos sin contraseña o con credenciales federadas”, por lo que en diseños a largo plazo conviene también contemplar una revisión del propio método de autenticación (por ejemplo, identidades administradas de Entra ID). 7 Una identidad administrada es un mecanismo que obtiene un token con una identidad asignada a un recurso de Azure y se autentica ante servicios compatibles con Entra ID sin poseer contraseñas ni claves. En cuanto a qué leer a continuación, primero conviene entender en la página de visión general la diferencia entre asignación por sistema y asignación por usuario, 19 y si va a usarse desde PowerShell, tomar como punto de entrada Connect-AzAccount -Identity (inicio de sesión con identidad administrada) del módulo Az, para no perderse.

También existe una extensión de la comunidad que usa el Administrador de credenciales de Windows (Credential Manager) como almacén. 7 El comportamiento del propio Administrador de credenciales —como el mecanismo por el que las credenciales registradas con cmdkey se usan automáticamente— se trató en “Trampas de las unidades de red y las rutas UNC”, y también en este caso funciona por perfil de usuario, igual que antes.

6. La solución realista de máxima prioridad — no tener credenciales, directamente

Hasta aquí hemos acumulado ideas sobre “cómo almacenar de forma segura”, pero, en realidad, la respuesta más limpia para la ejecución desatendida no está en perfeccionar el método de almacenamiento, sino en eliminar la necesidad de que el script tenga credenciales.

Las tareas y los servicios de Windows se ejecutan dentro del contexto de seguridad de la cuenta de ejecución. Si el destino de conexión está protegido con autenticación de Windows —la ACL de una carpeta compartida, la autenticación de Windows de SQL Server, la autenticación integrada de Windows de una API interna—, basta con otorgar a la propia cuenta de ejecución el permiso sobre el destino de conexión, y ni la contraseña ni Get-Secret aparecen en el script. La autenticación la resuelve Kerberos por su cuenta, usando la identidad de la cuenta de ejecución.

Lo que sostiene esta configuración es la gMSA (cuenta de servicio administrada de grupo). Una gMSA es una cuenta administrada del dominio en la que la gestión de la contraseña la asume el propio sistema operativo Windows. 9 Como la contraseña se genera de forma aleatoria con 240 bytes y el sistema operativo la cambia automáticamente cada 30 días, ningún ser humano conoce la contraseña y tampoco se producen interrupciones por caducidad. 10 Y una gMSA puede usarse no solo en servicios de Windows, sino también en tareas del Programador de tareas. 20

También dejamos por escrito el esqueleto del procedimiento de implantación de una gMSA, porque quedarse solo en “se puede usar” no deja claro qué investigar a continuación. Como requisitos previos, hace falta que el nivel funcional del dominio y del bosque sea Windows Server 2012 o superior, que la cuenta de trabajo sea equivalente a Domain Admins, y que, si se trabaja fuera de un controlador de dominio, esté instalado RSAT (el módulo ActiveDirectory). 20

# 1) Una sola vez por dominio: crear la clave raíz de KDS (si ya existe, este paso no es necesario)
#    Como hay que esperar la replicación a todos los controladores de dominio, crear realmente la gMSA puede tardar hasta 10 horas
Add-KdsRootKey -EffectiveImmediately

# 2) Crear la gMSA. En PrincipalsAllowedToRetrieveManagedPassword se especifica
#    el equipo (o el grupo de seguridad que los agrupa) al que se permite obtener la contraseña
New-ADServiceAccount -Name svc-transfer -DNSHostName svc-transfer.contoso.local `
    -PrincipalsAllowedToRetrieveManagedPassword 'gMSA-Hosts'

# 3) En el servidor donde realmente se ejecutará el trabajo: instalar la gMSA y comprobar que se puede obtener la contraseña
Install-ADServiceAccount -Identity svc-transfer
Test-ADServiceAccount -Identity svc-transfer

La creación de la clave raíz de KDS es un trabajo que se hace una sola vez por dominio. Justo después de crearla no se puede crear la gMSA por cuestión de replicación, así que, para quien no quiera esperar en un entorno de pruebas, la documentación oficial describe el procedimiento de indicar una fecha pasada con -EffectiveTime ((Get-Date).AddHours(-10)) (no lo use en producción). 21 Una vez creada, se puede confirmar comprobando si el ID de evento 4004 queda registrado en el registro operativo del servicio KDS. 20

Para asignarla a una tarea, se especifica como cuenta de ejecución la gMSA (un nombre de cuenta que termina en $, como CONTOSO\svc-transfer$). Como con una gMSA ningún humano conoce la contraseña, el procedimiento difiere del de la GUI habitual, que da por hecho “introducir la contraseña de la cuenta de ejecución”. En la práctica, el registro se hace con New-ScheduledTaskPrincipal y Register-ScheduledTask del módulo ScheduledTasks, así que tome la documentación de estos dos cmdlets como el siguiente lugar donde investigar. Además, puede que sea necesario, en ese servidor, conceder a la gMSA el derecho de “Iniciar sesión como trabajo por lotes”.

Ordenando la prioridad de las decisiones, queda así.

  1. Si el destino de conexión puede usar autenticación de Windows, elimine las credenciales otorgando permisos a la cuenta de ejecución (cuenta de dominio o gMSA). Esto es lo primero que debe evaluarse en los trabajos desatendidos de un entorno de dominio. 9
  2. Guarde secretos únicamente para aquellos destinos con los que esto no sea posible (una base de datos con autenticación SQL, claves de API, un NAS de grupo de trabajo, etc.). Si es un solo secreto, el almacenamiento con DPAPI de Export-Clixml; si son varios, SecretManagement + SecretStore. 68
  3. Para las conexiones a recursos en la nube, priorice el uso de identidades administradas o credenciales federadas antes que el almacenamiento de claves. Aun cuando se opte por almacenar, hágalo en un almacén dedicado como Azure Key Vault. 177

En los escenarios donde se pasan credenciales de forma explícita a un servidor remoto para ejecutar procesos, también entra en juego el mecanismo de autenticación de PowerShell Remoting. Consulte el artículo publicado simultáneamente “Introducción a PowerShell Remoting/WinRM”.

7. No dejar secretos en registros ni transcripciones

Aunque se blinde el almacenamiento, no sirve de nada si el secreto se filtra a través de los registros de la ejecución. Hay dos puntos que hay que tener presentes.

En primer lugar, PowerShell tiene varias funciones que registran lo que se ejecuta, y puede que estén habilitadas por la configuración de la organización. Si se habilita el registro de bloques de script, el contenido de todos los bloques de script procesados queda registrado en el registro de eventos. La documentación oficial advierte explícitamente que “si se habilita el registro de scripts, las credenciales y los datos sensibles usados en el script pueden quedar escritos en el registro de eventos”, y recomienda combinar, para usos que no sean de diagnóstico, el registro de eventos protegido (Protected Event Logging). 11 El registro de eventos protegido es un mecanismo que cifra el contenido del registro de eventos correspondiente con la clave pública de CMS (sintaxis de mensajes criptográficos) al escribirlo, de modo que solo se puede descifrar en otro lugar seguro que posea la clave privada. 11 Lo mismo ocurre con la transcripción (la transcripción de las operaciones): el contenido que pasó por la consola queda en un archivo.

Puede comprobar cómo está configurado su entorno mediante el valor del registro que escribe la directiva. El registro de eventos protegido se configura desde “Habilitar el registro de eventos protegido (Enable Protected Event Logging)”, dentro de la directiva de grupo “Configuración del equipo > Plantillas administrativas > Componentes de Windows > Registro de eventos (Event Logging)”, y el registro correspondiente es el siguiente. 22

# Si el registro de eventos protegido está habilitado (si la directiva no está configurada, la clave ni siquiera existe)
Get-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\EventLog\ProtectedEventLogging' `
    -Name EnableProtectedEventLogging -ErrorAction SilentlyContinue

# Directiva del lado del registro de bloques de script (serie PowerShell 7)
Get-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\PowerShellCore\ScriptBlockLogging' `
    -Name EnableScriptBlockLogging -ErrorAction SilentlyContinue

# El lado de Windows PowerShell 5.1 usa una clave distinta
Get-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging' `
    -Name EnableScriptBlockLogging -ErrorAction SilentlyContinue

El valor de registro del registro de bloques de script está separado: la serie PowerShell 7 lo tiene bajo PowerShellCore11, y Windows PowerShell 5.1 bajo Windows\PowerShell23. En un servidor donde convivan 5.1 y 7, compruebe ambos.

El punto clave de la comprobación es la combinación en la que solo el registro de bloques de script está habilitado y el registro de eventos protegido está deshabilitado. Este es el estado de “se registra, pero se acumula en texto plano”, así que asegúrese de conocerlo antes de poner en marcha un script que maneje credenciales.

En segundo lugar, precisamente por eso, hay que ser estricto con escribir el código de forma que “el texto plano no pase por la línea de comandos ni por la pantalla”.

  • No diseñe el script para recibir la contraseña como argumento. En cuanto se escribe .\job.ps1 -Password "P@ssw0rd", dé por hecho que queda en los registros, en el historial y en la lista de procesos. Si hay que recibirla, recíbala como PSCredential o como SecureString. 2
# Norma de diseño de la interfaz de un script propio: no recibir la contraseña como [string]
[CmdletBinding()]
param(
    # Reciba la credencial como PSCredential. Si se omite, se obtiene de forma interactiva con Get-Credential;
    # en ejecución desatendida, pase el resultado de Import-Clixml o de Get-Secret
    [Parameter(Mandatory)]
    [System.Management.Automation.PSCredential]$Credential
)
# No emita en la salida de depuración las variables que contienen secretos. Como máximo, muestre el nombre de usuario
Write-Verbose "Cuenta de conexión: $($Credential.UserName)"
  • No emita en la salida de depuración con Write-Host ni Write-Verbose las variables que contienen secretos. La salida que se pensaba “temporal” durante la resolución de un problema queda registrada de forma permanente en la transcripción.
  • Preste atención a que no se cuele en los mensajes de excepción. Si se construye la cadena de conexión y luego se lanza el error, es habitual que quede registrada en el log capturado con catch junto con toda la cadena de conexión. Sobre el diseño de qué escribir en el log durante el manejo de errores, consulte también “Diseño del manejo de errores y los reintentos en PowerShell”.

8. Reglas prácticas habituales (tabla de decisión)

Situación Recomendación Criterio de decisión
Una persona lo ejecuta en el momento Get-Credential No guardar nada es lo más seguro. Recíbala como PSCredential y páselo a -Credential3
Trabajo desatendido dentro del dominio (destino con autenticación de Windows) Otorgar permisos a la cuenta de ejecución/gMSA Priorizar el diseño sin credenciales. La gMSA también puede usarse en el Programador de tareas920
Trabajo desatendido con 1 o 2 secretos Almacenamiento con DPAPI de Export-Clixml Guardarlo con la propia cuenta de ejecución de la tarea. Volver a crearlo al cambiar de máquina o de cuenta6
Trabajo desatendido con muchos secretos y voluntad de sustituir el destino en el futuro SecretManagement + SecretStore Suministrar la contraseña del almacén con almacenamiento DPAPI + Unlock-SecretStore. No se puede usar con una gMSA815
Secreto compartido entre recursos en la nube o varios servidores Azure Key Vault (+ SecretManagement) La DPAPI local de la máquina no permite compartir. Cuando haga falta gestión centralizada y auditoría17
Texto plano + ConvertTo-SecureString dentro del script Objeto de reescritura Una mala práctica que PSScriptAnalyzer marca como error. Migre a alguna de las anteriores1

Si tiene dudas, evalúe en este orden: “no tenerlas > tenerlas fijadas a la máquina (DPAPI) > tenerlas en un almacén”, empezando siempre por la primera opción.

9. Resumen

  • El problema esencial de la contraseña en texto plano es que todas las rutas por las que se multiplican las copias —el historial de Git, las carpetas compartidas, los registros— se convierten en rutas de fuga. La gestión de los permisos de acceso no basta para protegerla del todo.
  • Para la ejecución interactiva, basta con Get-Credential + PSCredential. No cree funciones propias que reciban la contraseña como [string].
  • SecureString queda, por un lado, como no recomendado para nuevos desarrollos según la documentación oficial de .NET, pero, por otro, sigue siendo el formato estándar de intercambio de PowerShell. Trátelo como “un sobre que evita la exposición accidental” y ponga el núcleo de la protección en un mecanismo externo.
  • El almacenamiento con DPAPI de Export-Clixml está “limitado a mismo usuario, misma máquina”. Lo esencial es crear el archivo de guardado con la propia cuenta de ejecución del Programador de tareas, y en sistemas distintos de Windows no se cifra.
  • SecretManagement + SecretStore es eficaz para la gestión centralizada de varios secretos, pero en ejecución desatendida hace falta diseñar el suministro de la contraseña del almacén, y no puede usarse con una gMSA. Adóptelo teniendo también en cuenta que se considera de funcionalidad completa.
  • La máxima prioridad es un diseño sin credenciales. Evalúe primero si puede eliminar la contraseña del script mediante autenticación de Windows y la concesión de permisos a la cuenta de ejecución (gMSA).
  • El registro de bloques de script y las transcripciones pueden conservar datos sensibles. Sea estricto en escribir el código de forma que el texto plano no pase por la línea de comandos, la pantalla ni los mensajes de excepción.

Artículos relacionados

Áreas de consultoría relacionadas

KomuraSoft LLC se ocupa del inventario de las credenciales incrustadas en scripts operativos y procesos por lotes periódicos y de su migración hacia un almacenamiento seguro, del diseño de cuentas de ejecución (incluidas las gMSA), y de la revisión de seguridad de trabajos desatendidos. Puede empezar por consultarnos incluso la simple limpieza de contraseñas en texto plano que se han dejado como están bajo el argumento de “funciona, así que no se toca”.

Referencias

  1. Microsoft Learn, AvoidUsingConvertToSecureStringWithPlainText. Sobre que PSScriptAnalyzer detecta el uso de -AsPlainText en ConvertTo-SecureString como un error (Severity: Error), que sortea el cifrado y expone información sensible en texto plano en memoria, y que como alternativas se citan Read-Host -AsSecureString y el módulo SecretStore.  2 3

  2. Microsoft Learn, Add Credential support to PowerShell functions. Sobre cómo añadir un parámetro PSCredential a funciones propias, y sobre por qué se advierte al usar ConvertTo-SecureString -AsPlainText, dado que la contraseña en texto plano queda registrada en diversos logs.  2 3

  3. Microsoft Learn, Get-Credential. Sobre que Get-Credential solicita el nombre de usuario y la contraseña y devuelve un objeto PSCredential, que en Windows PowerShell 5.1 se pide mediante un cuadro de diálogo y desde PowerShell 6 en adelante por consola, y sobre su uso pasándolo a comandos que tienen el parámetro -Credential.  2 3

  4. Microsoft Learn, SecureString Class. Sobre que se recomienda no usar SecureString en nuevos desarrollos de .NET (Core), que el cifrado solo se realiza en Windows y que en plataformas distintas de Windows el almacenamiento interno no se cifra, que en el momento de usarlo es necesaria la conversión a una representación en texto plano, y que la alternativa recomendada es un identificador opaco hacia una credencial almacenada fuera del proceso.  2

  5. Microsoft Learn, Advisory Development Guidelines. Sobre que, aunque .NET no recomienda el nuevo uso de SecureString, PowerShell sigue admitiéndolo por compatibilidad hacia atrás, que es más seguro que una cadena en texto plano y se usa para evitar la exposición accidental en la consola o en los registros, y que debe usarse con cuidado porque se puede convertir fácilmente a texto plano.  2

  6. Microsoft Learn, Export-Clixml. Sobre que Export-Clixml cifra y guarda el objeto de credencial con la DPAPI de Windows, que solo puede descifrarse con la cuenta de usuario que lo guardó y en el equipo donde se guardó, sin poder usar el archivo exportado en otra máquina ni con otro usuario, y que en sistemas distintos de Windows (macOS/Linux) no se cifra y se genera en la práctica en texto plano.  2 3 4 5

  7. Microsoft Learn, Overview of the SecretManagement and SecretStore modules. Sobre que SecretManagement es una interfaz unificada hacia almacenes de extensión, que SecretStore es un almacén de extensión multiplataforma que guarda de forma cifrada en un archivo local, que existen almacenes de extensión como Azure Key Vault, KeePass y el Administrador de credenciales (CredMan), y que el conjunto de módulos Secret se considera de funcionalidad completa, con el desarrollo activo terminado y solo continuidad en correcciones de seguridad.  2 3 4 5 6

  8. Microsoft Learn, Use the SecretStore in automation. Sobre configurar Interaction de SecretStore en None para la ejecución desatendida, la configuración de guardar la contraseña del almacén en un archivo cifrado con DPAPI mediante Export-Clixml y desbloquearla con Unlock-SecretStore, y que esta es una solución exclusiva de Windows.  2 3 4

  9. Microsoft Learn, Group Managed Service Accounts overview. Sobre que una gMSA es una cuenta de dominio administrada que ofrece gestión automática de la contraseña y simplifica la gestión del SPN, y que la gestión de la contraseña la asume el sistema operativo Windows y no el administrador.  2 3 4

  10. Microsoft Learn, Secure group managed service accounts. Sobre que la contraseña de una gMSA se genera de forma aleatoria con 240 bytes, que el sistema operativo la cambia automáticamente cada 30 días, sin que haga falta planificar el cambio de contraseña ni provoque interrupciones del servicio, y que se recomienda usar gMSA como tipo de cuenta para los servicios locales.  2

  11. Microsoft Learn, about_Logging_Windows. Sobre que, al habilitar el registro de bloques de script, el contenido de todos los bloques de script que procesa PowerShell queda registrado en el registro de eventos, que al subir el nivel de registro pueden incluirse en el log datos sensibles como las credenciales usadas en el script, que el valor de registro que habilita el registro de bloques de script en la serie PowerShell 7 es EnableScriptBlockLogging en HKLM:\Software\Policies\Microsoft\PowerShellCore\ScriptBlockLogging, y que Protected Event Logging es un mecanismo que cifra el contenido del registro de eventos con la clave pública de CMS (sintaxis de mensajes criptográficos) y lo descifra en un lugar seguro que posee la clave privada.  2 3 4 5 6

  12. Microsoft Learn, ConvertFrom-SecureString. Sobre que permite convertir un SecureString en una cadena estándar cifrada, que sin especificar una clave se usa la DPAPI de Windows y, al especificar Key/SecureKey, se usa AES, y que con -AsPlainText se puede convertir directamente a una cadena de texto plano.  2

  13. Microsoft Learn, Import-Clixml. Sobre que Import-Clixml permite restaurar las credenciales y las cadenas seguras guardadas por Export-Clixml, y que esto permite evitar el riesgo de escribir contraseñas en texto plano dentro del script. 

  14. Microsoft Learn, CryptProtectData function. Sobre que, normalmente, solo puede descifrar quien tenga las mismas credenciales de inicio de sesión que el usuario que cifró, que el cifrado y el descifrado normalmente deben realizarse en el mismo equipo, y que la función genera una clave de sesión a partir de las credenciales de inicio de sesión del usuario para realizar el cifrado. 

  15. Microsoft Learn, Understanding the SecretManagement module. Sobre que SecretManagement almacena y recupera secretos a través de almacenes de extensión registrados, que el registro de almacenes es por contexto de usuario, y que, por su dependencia de $env:LOCALAPPDATA y de DPAPI, actualmente no funciona con las cuentas administradas (managed accounts) de Windows.  2 3

  16. Microsoft Learn, Get started with the SecretStore module. Sobre el registro de SecretStore con Register-SecretVault, las operaciones básicas de Set-Secret/Get-Secret/Get-SecretInfo, y que en el primer acceso se solicita establecer la contraseña del almacén. 

  17. Microsoft Learn, Use Azure Key Vault in automation. Sobre que Az.KeyVault 3.3.0 y versiones posteriores incluyen la extensión de SecretManagement, y que se puede registrar Azure Key Vault como almacén de SecretManagement con Register-SecretVault y manejarlo con Get-Secret y otros comandos.  2 3

  18. Microsoft Learn, Understanding the security features of SecretManagement and SecretStore. Sobre que, si se deshabilita por completo la autenticación por contraseña, la clave de descifrado queda protegida únicamente por los permisos del sistema de archivos, y no se recomienda para sistemas que requieran una protección de seguridad fuerte. 

  19. Microsoft Learn, What are managed identities for Azure resources?. Sobre que una identidad administrada es un mecanismo que obtiene un token usando una identidad de Entra ID sin gestionar credenciales, y sobre los dos tipos —asignada por sistema y asignada por usuario— y sus diferencias. 

  20. Microsoft Learn, Manage group Managed Service Accounts. Sobre que una gMSA puede usarse en servicios configurados a través del Administrador de control de servicios, en grupos de aplicaciones de IIS y en tareas del Programador de tareas; sobre los requisitos previos (nivel funcional del dominio y del bosque de Windows Server 2012 o superior, pertenencia a Domain Admins/Enterprise Admins, existencia de la clave raíz de KDS y su confirmación mediante el ID de evento 4004 en el registro operativo KdsSvc, necesidad de RSAT para trabajar fuera de un controlador de dominio); sobre el procedimiento de creación con New-ADServiceAccount y la especificación de PrincipalsAllowedToRetrieveManagedPassword; y sobre la implantación y la comprobación con Install-ADServiceAccount/Test-ADServiceAccount.  2 3 4

  21. Microsoft Learn, Create the Key Distribution Services KDS Root Key. Sobre que la generación de la contraseña de una gMSA requiere la clave raíz de KDS, la creación con Add-KdsRootKey -EffectiveImmediately y la especificación de que hay que esperar hasta 10 horas para la convergencia de la replicación a todos los controladores de dominio, y el procedimiento con Add-KdsRootKey -EffectiveTime para indicar una fecha pasada en entornos de prueba y evitar la espera. 

  22. Microsoft Learn, ADMX_EventLogging Policy CSP. Sobre que la directiva EnableProtectedEventLogging se encuentra en “Configuración del equipo” bajo “Componentes de Windows > Registro de eventos”, que la clave de registro correspondiente es Software\Policies\Microsoft\Windows\EventLog\ProtectedEventLogging y que el nombre del valor es EnableProtectedEventLogging

  23. Microsoft Learn, about_Logging (Windows PowerShell 5.1). Sobre que el valor de registro que habilita el registro de bloques de script en Windows PowerShell 5.1 es EnableScriptBlockLogging en HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging

Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

El artículo está directamente relacionado con los siguientes servicios.

Preguntas frecuentes

Preguntas habituales en las consultas sobre el tema del artículo.

¿Por qué no se debe escribir la contraseña en texto plano dentro de un script de PowerShell?
Porque un script es un producto que, por su propia naturaleza, se copia, se comparte y queda registrado en un historial. Una vez confirmado (commit) en Git, es difícil borrarlo del historial; si se coloca en una carpeta compartida, lo ve todo el que tenga permiso de lectura; y el registro de bloques de script y las transcripciones registran el contenido de los comandos tal cual. En otras palabras, una contraseña en texto plano crea la estructura de que "en el momento en que se ve el archivo, la fuga ya está confirmada". La vía principal de la solución no es cerrar primero las rutas de fuga, sino sacar la contraseña fuera del script hacia un lugar protegido.
¿Hasta qué punto son seguras las credenciales guardadas con Export-Clixml?
En Windows se cifran con DPAPI (interfaz de protección de datos) y solo pueden descifrarse con la cuenta de usuario que las guardó y en ese mismo equipo. Aunque se robe el archivo, no puede abrirse en otra máquina ni con otro usuario, por lo que es una opción práctica como destino de almacenamiento para la ejecución desatendida. Sin embargo, no es infalible, porque puede descifrarse desde cualquier proceso que se ejecute con la misma cuenta, y hay que tener presente que en macOS o Linux no se cifra y se genera en la práctica en texto plano. Si se usa desde el Programador de tareas, el archivo de guardado debe crearlo la propia cuenta de ejecución de la tarea.
¿Todavía conviene usar SecureString?
La documentación oficial de .NET indica explícitamente que no recomienda el uso de SecureString en nuevos desarrollos. El cifrado solo se realiza en Windows, y en sistemas distintos de Windows el contenido interno no se cifra. Además, en el momento de usarlo, al final hay que volver a convertirlo en texto plano. Por otro lado, en el mundo de PowerShell, herramientas estándar como Get-Credential y SecretManagement dan por hecho el uso de SecureString, y como reduce la exposición frente a llevar el dato como cadena de texto plano, por ahora resulta realista seguir conviviendo con él como "el formato estándar de intercambio de PowerShell". No lo use como base para construir un mecanismo de cifrado propio.
¿No se detiene por la solicitud de contraseña si se usa SecretStore en la ejecución desatendida del Programador de tareas?
Con la configuración predeterminada, sí se detiene, porque SecretStore solicita de forma predeterminada la contraseña del almacén y muestra un aviso interactivo. La documentación oficial describe, para la ejecución desatendida, una configuración en la que se pone Interaction en None, se lee la contraseña del almacén desde un archivo protegido con DPAPI (Export-Clixml) y se desbloquea con Unlock-SecretStore. Sin embargo, esta es una configuración en la que "la clave del almacén se protege con DPAPI", y termina heredando la restricción de DPAPI (mismo usuario, misma máquina). Antes de llegar ahí, evalúe primero si puede eliminar por completo la necesidad de tener credenciales mediante una gMSA o la concesión de permisos a la cuenta de ejecución.
¿Existe, para empezar, una forma de no guardar credenciales en absoluto?
Sí existe, y de hecho es la primera opción. Como las tareas y los servicios de Windows se ejecutan con los permisos de la cuenta de ejecución, si se otorgan al destino de conexión (una carpeta compartida, la autenticación de Windows de SQL Server, etc.) los permisos de la propia cuenta de ejecución, el script no necesita tener contraseña alguna. Si la cuenta de ejecución es una gMSA (cuenta de servicio administrada de grupo), la contraseña es un valor aleatorio de 240 bytes que el sistema operativo renueva automáticamente cada 30 días, de modo que se puede lograr que ningún ser humano la conozca. Una gMSA también puede usarse en tareas del Programador de tareas.

Perfil del autor

Página de presentación del autor del artículo.

Go Komura

Representante de KomuraSoft LLC

Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.

Volver al blog