Directiva de ejecución de PowerShell y firma de scripts — Guía práctica para dejar de "tapar con Bypass"

· Actualizado el: · · PowerShell, Windows, Directiva de ejecución, Firma de código, Seguridad, Scripts, Mejora operativa, Automatización

«En un PC nuevo, un script no se ejecuta porque aparece el mensaje “la ejecución de scripts está deshabilitada en este sistema…”», «Por probar añadí -ExecutionPolicy Bypass y funcionó, así que ahora aparece en todas las tareas», «El .ps1 que dejé en una carpeta compartida da un error de firma solo en el equipo de una persona» ── la directiva de ejecución de PowerShell es un obstáculo con el que casi con toda seguridad se topará al avanzar en la automatización interna. Y en muchos entornos se van acumulando soluciones de “tapar con Bypass” sin entender realmente el mecanismo.

Lo complicado es que se malinterpreta con facilidad para qué sirve la directiva de ejecución. Si se la trata como una función de seguridad y se endurece en exceso, el trabajo se detiene. En el extremo contrario, si se piensa que “de todos modos no sirve de nada” y se pone todo en Bypass, se termina desactivando también el último dispositivo de seguridad que evita accidentes por descuido. Ambos errores se pueden evitar si se conoce el mecanismo.

Este artículo está dirigido al personal de sistemas de pequeñas y medianas empresas, y a quienes automatizan tareas rutinarias internas con PowerShell. Repasamos, con respaldo de la documentación oficial, qué es realmente la directiva de ejecución, el orden de prioridad de sus ámbitos, su relación con Zone.Identifier (el llamado Mark of the Web) y, finalmente, la operación de distribución mediante firma de scripts.

1. Conclusión general

  • La directiva de ejecución no es un límite de seguridad, sino un dispositivo de seguridad. La documentación oficial afirma explícitamente que “la directiva de ejecución no es un sistema de seguridad que restrinja las acciones del usuario” y que “se puede eludir fácilmente escribiendo el contenido del script en la línea de comandos”. Su objetivo es fijar una regla básica que evite ejecuciones no intencionadas (accidentes por descuido).1
  • La directiva de ejecución solo afecta a la ejecución de scripts; la ejecución interactiva de comandos siempre es posible. En Windows PowerShell 5.1, el valor predeterminado es Restricted (no se pueden ejecutar scripts) en los sistemas operativos cliente, y RemoteSigned en Windows Server. En PowerShell 7, el valor predeterminado es RemoteSigned.21
  • Existen cinco ámbitos de directiva, con el siguiente orden de prioridad: MachinePolicy > UserPolicy > Process > CurrentUser > LocalMachine. MachinePolicy y UserPolicy son exclusivos de la directiva de grupo: no se pueden cambiar con Set-ExecutionPolicy ni sobrescribir mediante un parámetro en la línea de comandos.13
  • RemoteSigned solo exige firma a los scripts de “origen de Internet”. El origen se determina mediante el flujo de datos alternativo Zone.Identifier (Mark of the Web) que se añade al archivo; si tras revisar el contenido lo elimina con Unblock-File, el script se ejecutará sin necesidad de cambiar la directiva.14
  • AllSigned exige la firma de un editor de confianza para todos los scripts, incluidos los creados localmente. La firma se aplica con Set-AuthenticodeSignature y se incrusta al final del archivo como un bloque de comentario # SIG #.15
  • La firma siempre debe incluir una marca de tiempo (-TimestampServer). Con ella, el script sigue siendo válido incluso después de que caduque el certificado de firma. Como la mayoría de los certificados de firma de código tienen una vigencia de aproximadamente un año, omitir la marca de tiempo se convierte en una bomba de tiempo anual.65
  • Los certificados autofirmados son solo para pruebas. Un script firmado con un certificado autofirmado no se puede ejecutar en otros equipos. Para la distribución dentro de la organización, use un certificado de firma de código emitido por una entidad de certificación (una CA interna o una CA comercial).57
  • Para gestionar esto de forma centralizada en la organización, use la directiva de grupo “Activar la ejecución de scripts”. Esta configuración tiene prioridad sobre todos los ámbitos definidos en PowerShell.1

2. Qué es realmente la directiva de ejecución ── un “dispositivo de seguridad”, no un “límite de seguridad”

Empecemos por confirmar la premisa. La redacción de la documentación oficial (about_Execution_Policies) es directa. La directiva de ejecución es una “función de seguridad” (safety feature) que controla las condiciones bajo las que PowerShell carga archivos de configuración y ejecuta scripts, y “no es un sistema de seguridad que restrinja las acciones del usuario”. Incluso un usuario que no pueda ejecutar scripts puede realizar el mismo procesamiento pegando el contenido del script en la línea de comandos. Se especifica que el papel de la directiva de ejecución es establecer una regla básica y evitar que se infrinja de forma no intencionada.1 La documentación introductoria repite la misma idea: “no es un límite de seguridad; no detiene a un usuario que intente ejecutar un script deliberadamente”.2

Una vez asumida esta posición, el diseño operativo queda claro. Tanto “endurecer la directiva de ejecución evita ataques” como “de todos modos se puede eludir, así que no sirve de nada” son ideas equivocadas. La protección frente a ataques es tarea de otra capa (control de aplicaciones, mínimo privilegio, registros de auditoría); la directiva de ejecución tiene la tarea de prevenir accidentes.8

A continuación se resumen las diferencias entre las principales directivas.1

Directiva Ejecución de scripts Requiere firma Uso previsto
Restricted No (solo comandos individuales) Valor predeterminado en sistemas operativos cliente de Windows PowerShell 5.12
AllSigned Se exige en todos los scripts y archivos de configuración. Los editores no clasificados requieren confirmación antes de ejecutarse Para organizaciones con operación de firma establecida
RemoteSigned Solo se exige en scripts de origen de Internet. No se requiere en los creados localmente Estándar en la práctica. Valor predeterminado en PowerShell 71
Unrestricted Ninguna (advertencia fuera de la zona de intranet) Valor predeterminado en plataformas no Windows (no se puede cambiar)1
Bypass Ninguna. Sin advertencias ni solicitudes de confirmación Pensado para aplicaciones que integran PowerShell con su propio modelo de seguridad1

Es fácil pasarlo por alto, pero Restricted no solo detiene los scripts de negocio: también impide cargar perfiles (.ps1), módulos (.psm1) y archivos de configuración de formato (.ps1xml).1 Un caso de consulta habitual es descubrir que la causa de que “el perfil no se carga en un equipo nuevo” era la directiva de ejecución.

3. Ámbitos y orden de prioridad ── qué hay detrás de “lo configuré, pero no cambia”

La directiva de ejecución no es un único valor: se puede configurar de forma independiente en cinco ámbitos, y prevalece el de mayor prioridad como valor efectivo.1

Ámbito Medio de configuración Ubicación de almacenamiento Prioridad
MachinePolicy Directiva de grupo (configuración del equipo) GPO 1 (máxima)
UserPolicy Directiva de grupo (configuración de usuario) GPO 2
Process Parámetro de inicio -ExecutionPolicy / -Scope Process Variable de entorno $Env:PSExecutionPolicyPreference (desaparece al cerrar la sesión) 3
CurrentUser Set-ExecutionPolicy -Scope CurrentUser Configuración del usuario 4
LocalMachine Set-ExecutionPolicy (ámbito predeterminado, requiere administrador) Configuración común a todos los usuarios 5

Para diagnosticar el problema de “lo configuré, pero no cambia”, la práctica habitual es consultar el listado completo, no solo el valor efectivo.

# Compruebe siempre no solo la directiva efectiva, sino qué ámbito la está aplicando
Get-ExecutionPolicy -List

# Ejemplo: aunque LocalMachine esté en AllSigned, el CurrentUser en RemoteSigned prevalece
#         Scope ExecutionPolicy
#         ----- ---------------
# MachinePolicy       Undefined
#    UserPolicy       Undefined
#       Process       Undefined
#   CurrentUser    RemoteSigned
#  LocalMachine       AllSigned

# Para cambiar solo su propio entorno, el ámbito CurrentUser es cómodo porque no requiere privilegios de administrador
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Como CurrentUser tiene prioridad sobre LocalMachine, la directiva efectiva del ejemplo anterior es RemoteSigned.1 Set-ExecutionPolicy escribe de forma predeterminada en el ámbito LocalMachine, por lo que requiere privilegios de administrador; en cambio, el ámbito CurrentUser puede modificarlo un usuario estándar.3

Y la vía principal para la gestión organizativa es la directiva de grupo. La directiva “Activar la ejecución de scripts” (Turn on Script Execution) tiene prioridad sobre todos los ámbitos configurados por PowerShell. Si está deshabilitada equivale a Restricted; si está habilitada, puede elegir entre “Permitir todos los scripts” (Unrestricted), “Permitir scripts locales y scripts remotos firmados” (RemoteSigned) o “Permitir solo scripts firmados” (AllSigned), y se encuentra en la plantilla administrativa bajo Componentes de Windows\Windows PowerShell. La configuración del equipo tiene prioridad sobre la del usuario.1 En un entorno gestionado por GPO, Set-ExecutionPolicy guarda la configuración, pero no la aplica, y muestra un mensaje que explica el conflicto.3

De aquí se desprende una consecuencia importante: si el entorno se gestiona mediante GPO, el -ExecutionPolicy Bypass escrito en una tarea o un acceso directo no tiene efecto. Se indica explícitamente que la especificación del ámbito Process prevalece sobre la configuración de LocalMachine/CurrentUser, pero no sobre la directiva de grupo.1 Dicho de otro modo, “escribir Bypass y ya está” solo funciona en los entornos donde la organización no gestiona la directiva de ejecución.

4. Zone.Identifier (Mark of the Web) y Unblock-File

El criterio “remoto (origen de Internet)” de RemoteSigned no se determina por la ubicación del archivo, sino por una marca. Programas como los navegadores añaden un flujo de datos alternativo a los archivos descargados y los marcan como “archivo procedente de Internet”.1 Ese flujo es Zone.Identifier, y contiene el valor 3, que indica la zona de Internet. Es lo que se conoce como Mark of the Web.4

Los flujos de datos alternativos (Alternate Data Streams) son una función de NTFS. En NTFS, un mismo archivo puede tener varios flujos de datos: el “contenido del archivo” que habitualmente vemos está en el flujo predeterminado, que no tiene nombre. Además de ese, existe un mecanismo para añadir flujos con nombre mediante la notación nombre_de_archivo:nombre_de_flujo.9 Zone.Identifier es uno de esos flujos con nombre, de modo que el cuerpo del script no cambia ni un solo byte, pero cambia si se puede ejecutar o no. Por eso un mismo contenido puede funcionar en un equipo y no en otro, y a la inversa: si el archivo pasa por un sistema de archivos distinto de NTFS (como el FAT32 de una memoria USB), el flujo completo se pierde, la marca desaparece y el script puede volver a funcionar.

En un entorno con RemoteSigned, si intenta ejecutar un script sin firmar que lleva esta marca, se bloqueará con el error de que “no está firmado digitalmente”. La solución tiene dos pasos.

# 1) Primero, compruebe qué archivos están bloqueados (operación segura, de solo lectura)
Get-Item -Path C:\Tools\*.ps1 -Stream Zone.Identifier -ErrorAction SilentlyContinue

# 2) Desbloquee únicamente los archivos que, tras revisar su contenido, considere seguros
#    (Unblock-File elimina el flujo Zone.Identifier; la directiva de ejecución en sí no cambia)
Unblock-File -Path C:\Tools\Get-InventoryReport.ps1

Unblock-File es el cmdlet que elimina el flujo de datos alternativo Zone.Identifier; equivale a pulsar el botón “Desbloquear” (Unblock) en las propiedades del Explorador de archivos. Lo importante es que permite dejar pasar solo los archivos ya revisados sin relajar la directiva de ejecución.4 La documentación oficial también establece como paso obligatorio “verificar el archivo y su procedencia, y confirmar que es seguro antes de usarlo”.43

También vale la pena conocer la trampa en sentido contrario. No todas las vías de descarga añaden Mark of the Web. Se indica explícitamente que los archivos descargados con curl.exe, Invoke-WebRequest o Invoke-RestMethod a veces no reciben la marca de la zona de Internet.1 Es decir, RemoteSigned no garantiza que “bloqueará todos los archivos peligrosos descargados” (precisamente por eso es un dispositivo de seguridad, y no un límite). Además, hay una advertencia sobre sistemas configurados sin distinguir entre rutas UNC y rutas de Internet, en los que RemoteSigned puede rechazar la ejecución de un script ubicado en una carpeta compartida.1 Cuando un .ps1 colocado en un recurso compartido “se bloquea solo en algunos equipos”, sospeche primero de la configuración de zonas y de la presencia de Zone.Identifier.

Por cierto, el mecanismo de SmartScreen, que provoca un síntoma similar con los archivos ejecutables (.exe) descargados, se trata en «Por qué aparece “Windows protegió su PC” en Windows».

5. La práctica de firmar scripts ── Set-AuthenticodeSignature y la marca de tiempo

Para avanzar hacia una operación AllSigned, o hacia la firma de los archivos que se distribuyen en un entorno RemoteSigned, necesita un certificado de firma de código. Las vías para obtenerlo se pueden resumir en tres.5

Vía de obtención Alcance de confianza Criterio
Autofirmado (New-SelfSignedCertificate) Solo el equipo propio. No se puede ejecutar en otros equipos Exclusivo para pruebas y verificación. No usar para distribución57
Emitido por una CA interna (entidad de certificación) Equipos de la organización configurados para confiar en la CA interna La opción principal en entornos con dominio de AD. Permite controlar la emisión y revocación desde la organización
Emitido por una CA comercial (de pago) Windows en general (las CA públicas ya son de confianza) Cuando se distribuyen scripts fuera de la organización

La documentación oficial ofrece el mismo resumen: “un certificado emitido por una entidad de certificación también es de confianza en otros equipos” y “un certificado autogenerado es gratuito, pero está limitado a su propio equipo, por lo que debe restringirse a fines de prueba”.5 Si el objetivo es la distribución interna, lo más realista es emitir el certificado de firma de código desde una CA interna, como los Servicios de certificados de Active Directory (AD CS).

Qué hacer a continuación si emite el certificado desde una CA interna

Si nos quedamos en “lo realista es una CA interna”, resulta imposible dar el siguiente paso, así que aquí detallamos el procedimiento. AD CS es un rol de Windows Server que proporciona una infraestructura de clave pública (PKI) y se compone del servicio de rol de entidad de certificación (CA), además de otros servicios de rol como la inscripción web o el respondedor en línea (OCSP).10

  1. Compruebe si existe una CA interna. Primero, el personal de sistemas debe verificar si existe una CA empresarial de AD CS dentro de la organización. Si no existe, crear una CA solo para la firma de código es una decisión de peso. Compare el coste de comprar un único certificado con el esfuerzo operativo de construir la CA, hacer copias de seguridad, publicar la lista de revocación de certificados (CRL) y proteger las claves, y sopéselo frente a la compra de un certificado de una CA comercial.
  2. Pida que se prepare una plantilla de certificado para firma de código. Es tarea del lado de la CA. Hay que duplicar la plantilla predeterminada “Firma de código”, configurar en la copia el período de validez, la longitud de clave y el grupo de seguridad que puede solicitarla (limitado a los responsables de firma), y añadirla como plantilla de emisión desde la consola de administración “Entidad de certificación”. No se edita directamente la plantilla predeterminada porque el cambio afectaría a todo el dominio y sería difícil de revertir.
  3. El responsable de firma solicita el certificado. Desde el equipo donde se firmará, se solicita indicando el nombre de la plantilla en el cmdlet Get-Certificate. Una vez emitido, se coloca directamente en el almacén personal (este cmdlet solo puede guardar certificados en el almacén My).11

    # Solicite a la CA empresarial interna, indicando el nombre de la plantilla (autenticación integrada de Windows)
    # Sustituya 'CodeSigning' por el nombre de plantilla que el administrador de la CA publicó para emisión
    $result = Get-Certificate -Template 'CodeSigning' -Url ldap: `
        -CertStoreLocation Cert:\CurrentUser\My
    $result.Status        # Issued significa emitido, Pending significa pendiente de aprobación
    
  4. Distribuya la confianza a cada equipo. Distribuya el certificado raíz de la CA interna al almacén “Entidades de certificación raíz de confianza” y el certificado del editor usado para firmar al almacén “Editores de confianza”, mediante la directiva de grupo (Configuración del equipo > Directivas > Configuración de Windows > Configuración de seguridad > Directivas de clave pública). La documentación oficial describe el procedimiento de importar certificados haciendo clic con el botón derecho en cada almacén bajo las directivas de clave pública.12 Al llegar a este punto, puede eliminar de la operación diaria el aviso “¿Desea ejecutar software de este editor no confiable?” que aparece en un entorno AllSigned.15

El lado de la CA (paso 2) a menudo no puede resolverlo solo el personal de sistemas, así que decidir primero la comprobación del paso 1 y la política de distribución del paso 4, y luego consultar al administrador de la CA, agiliza la conversación.

Qué hacer en entornos sin dominio (grupo de trabajo)

En las pequeñas y medianas empresas a las que se dirige este artículo, también se dan casos en los que directamente no existe un dominio de AD, o solo algunos equipos están unidos a él. Partiendo de que no se puede usar ni GPO ni una CA interna, la solución realista se construye en el siguiente orden.

  • Configure la directiva de ejecución equipo por equipo y verifique que sea uniforme. Incluya Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser (o LocalMachine con privilegios de administrador si debe aplicarse a todos los usuarios) en el procedimiento de preparación de equipos.3 Como no hay una aplicación forzada mediante GPO, siempre queda margen para que alguien la cambie por su cuenta en un equipo concreto, así que conviene emparejarlo con una operación que revise el resultado de Get-ExecutionPolicy -List en cada inventario periódico.
  • Centralice la copia maestra de los scripts en un único lugar y restrinja quién puede escribir en ella. Designe una única carpeta compartida como copia maestra y limite el permiso de escritura al administrador. Como la directiva de ejecución solo evita los “descuidos”, impedir modificaciones no autorizadas es tarea de los permisos de acceso de NTFS.
  • Si además necesita firma, compare el coste de distribuir un certificado autofirmado frente al de comprar una CA comercial. La documentación oficial afirma que “un script firmado con un certificado autofirmado no se puede ejecutar en otros equipos” y que “no es adecuado para scripts que se quieran compartir”.5 Sin embargo, esto es una descripción del estado predeterminado: no funciona porque el otro equipo no confía en ese certificado. Dicho de otro modo, si distribuye la confianza, sí funciona. Lo detallamos por separado en el siguiente apartado.
  • Documente en el procedimiento cómo tratar Mark of the Web. Los archivos .ps1 recibidos por correo o a través de un recurso compartido externo a veces llevan la marca, así que deje por escrito el paso de “revisar el contenido y luego aplicar Unblock-File” (capítulo 4).4

Si usa un certificado autofirmado en un grupo de trabajo

Si nos detenemos en “el certificado autofirmado solo se puede usar en el equipo donde se firmó”, obligamos a comprar una CA comercial incluso a una empresa con diez o veinte equipos. En realidad, si distribuye la parte pública del certificado a cada equipo, AllSigned funciona. La única diferencia es que, en lugar de la GPO, la distribución se hace manualmente, mediante un instalador o con un MDM (como Intune).

Solo se distribuye la parte pública; la clave privada nunca sale del equipo donde se firma.

# 【Una vez, en el equipo que firma】Exporte solo la parte pública (-Cert no incluye la clave privada)
$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert |
        Where-Object Subject -eq 'CN=KomuraSoft Code Signing (Test)'
Export-Certificate -Cert $cert -FilePath .\KomuraSoftCodeSigning.cer

# 【Una vez por equipo, requiere privilegios de administrador】Impórtelo tanto en el almacén raíz como en el de editores.
# Si no lo importa en la raíz, la propia verificación de la firma falla (al ser autofirmado, es su propia raíz);
# si no lo importa en editores, la solicitud de AllSigned sigue apareciendo
Import-Certificate -FilePath .\KomuraSoftCodeSigning.cer `
    -CertStoreLocation Cert:\LocalMachine\Root
Import-Certificate -FilePath .\KomuraSoftCodeSigning.cer `
    -CertStoreLocation Cert:\LocalMachine\TrustedPublisher

Dicho esto, conviene ver antes qué coste está asumiendo. Aquí es donde se nota la diferencia con una CA comercial.

Aspecto Distribuir un certificado autofirmado Comprar una CA comercial
Coste inicial 0 yenes, pero requiere el trabajo de distribuirlo a todos los equipos Coste de compra del certificado (anual)
Protección de la clave privada El equipo de firma es toda la línea de defensa. Si esta clave se filtra, se puede firmar código que confiarán todos los equipos a los que se distribuyó. Hay que definir dónde se guarda la clave y prohibir sacarla del equipo Igual de importante, pero existen opciones como HSM o servicios de firma en la nube
Revocación No hay ningún medio. Si se filtra, hay que ir eliminando el certificado del almacén de cada equipo uno por uno Se puede revocar mediante CRL/OCSP
Caducidad Hay que volver a distribuirlo a todos los equipos en cada renovación (si se incluyó marca de tiempo, las firmas existentes siguen siendo válidas después de la caducidad5) No requiere trabajo en los equipos
Equipo nuevo Hay que incorporarlo al procedimiento de preparación de equipos No es necesario
Efecto de añadirlo a “raíz de confianza” El equipo confía en ese certificado concreto. Al no ser una CA, no puede emitir certificados subordinados, pero cualquier cosa firmada con esa clave se acepta Sin cambios

Si hay pocos equipos, el procedimiento de preparación está controlado y se puede proteger el equipo de firma, basta con la configuración de distribuir un certificado autofirmado. Por el contrario, si el número de equipos sigue creciendo, la preparación depende de una sola persona, o cualquiera puede acceder al equipo de firma —cualquiera de estas condiciones—, sale más barato externalizar la molestia de la revocación y la renovación a una CA comercial.

El almacén de certificados y la unidad Cert:

Antes de entrar en el código de firma, una nota sobre Cert:. Cert: es una unidad virtual que proporciona el proveedor de certificados de PowerShell, y permite recorrer el almacén de certificados mediante rutas, como si fuera un sistema de archivos.13 Su estructura tiene dos niveles, Cert:\<ubicación del almacén>\<nombre del almacén>: las ubicaciones son CurrentUser (para el usuario con la sesión iniciada) y LocalMachine (para todo el equipo), y los nombres de almacén incluyen My (personal), Root (entidades de certificación raíz de confianza) y TrustedPublisher (editores de confianza), entre otros. Cada certificado se identifica por su huella digital (Thumbprint) y se puede listar con Get-ChildItem.13

En resumen, Cert:\CurrentUser\My es “el almacén personal del usuario actual”, y el Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert que aparece más adelante significa “muéstrame todos los certificados de firma de código que haya ahí”. -CodeSigningCert es un parámetro específico del proveedor de certificados que filtra los certificados cuyo uso de clave extendida (EKU) incluye la firma de código.13

Primero verificamos el flujo con un certificado autofirmado de prueba.

# Cree un certificado de firma de código para pruebas (autofirmado ── solo es de confianza en este equipo)
$params = @{
    Subject           = 'CN=KomuraSoft Code Signing (Test)'
    Type              = 'CodeSigningCert'
    CertStoreLocation = 'Cert:\CurrentUser\My'
    HashAlgorithm     = 'sha256'
}
$cert = New-SelfSignedCertificate @params

New-SelfSignedCertificate es el cmdlet que crea certificados autofirmados con fines de prueba; si especifica -Type CodeSigningCert, se incluye la extensión para firma de código. El período de validez predeterminado es de un año.7 Si va a usar un certificado autofirmado para probar un entorno AllSigned, debe registrarlo en el almacén de entidades de certificación raíz de confianza de ese equipo.5

La firma en sí se reduce a esta línea.

# Obtenga el certificado de firma de código del almacén y firme con él
# -CodeSigningCert solo filtra "certificados utilizables para firma de código", así que
# limítelo a los que tengan clave privada y no hayan caducado, y en entornos con varios use Subject o Thumbprint
$cert = Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert |
    Where-Object { $_.HasPrivateKey -and $_.NotAfter -gt (Get-Date) -and
                   $_.Subject -eq 'CN=KomuraSoft Code Signing (Test)' } |
    Sort-Object NotAfter -Descending |
    Select-Object -First 1   # Si hay varios con el mismo Subject por una renovación, quédese con el de fecha de caducidad más lejana

# La marca de tiempo es obligatoria. Aunque caduque el certificado, la firma sigue siendo válida
# La URL debe apuntar a un servicio de marca de tiempo compatible con RFC 3161. La indicación del emisor del certificado (dónde lo compró) es la prioridad.
# Ejemplo: el servidor de marca de tiempo que publica DigiCert http://timestamp.digicert.com
Set-AuthenticodeSignature -FilePath .\Invoke-NightlyBatch.ps1 -Certificate $cert `
    -HashAlgorithm SHA256 -TimestampServer 'http://timestamp.digicert.com'

# El estado de la firma se puede comprobar con Get-AuthenticodeSignature (Valid/NotSigned/HashMismatch, etc.)
# Si TimeStamperCertificate no está vacío, la marca de tiempo se aplicó correctamente
Get-AuthenticodeSignature -FilePath .\Invoke-NightlyBatch.ps1 |
    Format-List -Property Status, SignerCertificate, TimeStamperCertificate

Hay tres características que conviene tener presentes.65

  • La firma se incrusta como un bloque de comentario # SIG # al final del archivo. Si ya existía una firma, se reemplaza. Es decir, si modifica un solo carácter del script después de firmarlo, la firma queda invalidada. Esto funciona como detección de manipulaciones.
  • Si especifica -TimestampServer, el script no dejará de funcionar aunque caduque el certificado. La validez de la firma dura “mientras el certificado de firma sea válido”, o bien, “mientras el servidor de marca de tiempo pueda verificar que se firmó cuando el certificado era válido”; como la mayoría de los certificados de firma de código tienen una vigencia de un año, la marca de tiempo es la clave para una operación a largo plazo.65 La URL no debe ser un valor ficticio, sino la de un servicio real compatible con RFC 3161. La primera opción es la URL que indique el emisor del certificado (la CA a la que lo compró); por ejemplo, DigiCert publica http://timestamp.digicert.com.14 Además, como los servicios de rol de AD CS no incluyen uno para responder a marcas de tiempo10, aunque firme con un certificado emitido por una CA interna, lo habitual es que la fuente de la marca de tiempo sea un servicio externo. Este punto se pasa por alto con frecuencia al plantear la implantación de una CA interna, así que verifique también si esa URL es accesible en la red (permisos de proxy y de cortafuegos). Tras firmar, puede confirmar si realmente se aplicó la marca de tiempo comprobando que TimeStamperCertificate no esté vacío en Get-AuthenticodeSignature.
  • En Windows PowerShell 5.1 y en versiones anteriores a PowerShell 7.2, un script que se fuera a firmar debía guardarse en ASCII o en UTF8NoBOM. A partir de PowerShell 7.2 se admite cualquier codificación en scripts firmados.5 En entornos donde todavía se usa 5.1, hay que tener cuidado, porque es fácil que la codificación de un script con comentarios en japonés rompa la verificación de la firma. Para las diferencias generales entre 5.1 y 7, consulte «Diferencias y migración entre Windows PowerShell 5.1 y PowerShell 7».

En un entorno AllSigned, si el script está firmado pero el editor todavía no se ha clasificado como de confianza o rechazado, al ejecutarlo aparece el aviso “¿Desea ejecutar software de este editor no confiable?”. Si elige “Ejecutar siempre”, ese editor dejará de solicitar confirmación a partir de entonces.15 En una operación con CA interna, si además distribuye el certificado del editor al almacén “Editores de confianza” de cada equipo, puede eliminar esta confirmación de la operación diaria.

6. Reglas prácticas (tabla de decisión) ── de “tapar con Bypass” a una operación de firma

Para gobernar la distribución y ejecución de los scripts internos, lo habitual es razonar con la siguiente tabla de decisión.

Aspecto Opciones Criterio de decisión
Directiva del entorno Dejar Restricted / RemoteSigned / AllSigned Si avanza en la automatización, RemoteSigned es el mínimo. Si existe una operación de firma, AllSigned1
Distribución de la configuración Cada usuario con Set-ExecutionPolicy / Directiva de grupo En un entorno con dominio, la GPO es la única opción razonable: tiene prioridad sobre todos los ámbitos y bloquea los cambios no autorizados1
Confianza en lo distribuido Sin firmar, en un recurso compartido / Firma de código Una operación sin firma no puede detectar manipulaciones. Empiece firmando los scripts operativos que se ejecutan periódicamente
Certificado Autofirmado / CA interna / CA comercial Para distribución interna, CA interna. El autofirmado, solo para pruebas; para distribución externa, CA comercial5
Trato de los archivos descargados Relajar la directiva / Revisar y aplicar Unblock-File No toque la directiva; deje pasar solo los archivos ya verificados4
Inicio de tareas periódicas Uso habitual de -ExecutionPolicy Bypass / Directiva del entorno bien configurada + firma El uso habitual de Bypass renuncia al dispositivo de seguridad. Además, no tiene efecto bajo gestión de GPO1

Una aclaración sobre la última fila. El problema de la operación de “tapar con ExecutionPolicy Bypass” no es en sí mismo abrir un agujero de seguridad (porque la directiva de ejecución nunca fue un límite). El problema son estos tres puntos.

  • Renuncia al dispositivo de seguridad ── se retira de forma permanente la última red que evita el accidente de ejecutar por error otro script, o de ejecutar sin darse cuenta un script que ha sido modificado.
  • Distanciamiento del gobierno de TI ── en el momento en que se empieza a gestionar la directiva de ejecución mediante GPO, la especificación de Bypass deja de tener efecto, y todas las tareas que estaban “tapadas” fallan a la vez.1 Es una deuda técnica: la razón por la que “funcionaba” era una vía de escape fuera del control organizativo.
  • Interferencia en la investigación de causas ── cuando Bypass está disperso por todas partes, ya no se puede inferir el comportamiento a partir de la directiva efectiva del entorno, y se complica investigar las diferencias entre equipos (por qué falla solo en este equipo en concreto).

Bypass en sí es una configuración prevista para aplicaciones que integran PowerShell y lo gobiernan con su propio modelo de seguridad.1 Asuma con claridad que no es algo que deba usarse de forma habitual como opción de inicio de un script operativo escrito por una persona. El diseño de una ejecución periódica segura con el Programador de tareas se trata en detalle en «Las tareas del Programador de tareas no se ejecutan o terminan con 0x1 ── diagnóstico de la causa y diseño operativo seguro».

7. Resumen

  • La directiva de ejecución es un dispositivo de seguridad, no un límite de seguridad. La protección frente a ataques se gestiona en otra capa; a la directiva de ejecución le corresponde “prevenir accidentes por descuido”.
  • La directiva tiene cinco ámbitos, con el orden de prioridad MachinePolicy > UserPolicy > Process > CurrentUser > LocalMachine. El diagnóstico empieza con Get-ExecutionPolicy -List.
  • Lo que observa RemoteSigned es Zone.Identifier (Mark of the Web). Los archivos ya verificados se dejan pasar con Unblock-File, sin relajar la directiva.
  • La firma se realiza con Set-AuthenticodeSignature, indicando siempre -TimestampServer. Para la distribución interna, use un certificado emitido por una CA interna; el autofirmado se limita a las pruebas.
  • El gobierno organizativo se centraliza con la directiva de grupo “Activar la ejecución de scripts”. Esta configuración tiene prioridad sobre todos los ámbitos y sobre la especificación de -ExecutionPolicy.
  • El uso habitual de -ExecutionPolicy Bypass renuncia al dispositivo de seguridad y no tiene efecto bajo gestión de GPO. El camino correcto es configurar bien la directiva del entorno y establecer una operación de firma, de modo que ya no haga falta “taparlo”.

Artículos relacionados

Áreas de consultoría relacionadas

KomuraSoft LLC se ocupa del diseño de la directiva de ejecución y de la operación de firma para scripts internos de PowerShell, del estudio de su despliegue mediante directiva de grupo, y de la investigación de diferencias de entorno como “el script solo falla en un equipo concreto”. También podemos ayudar a migrar los activos existentes de archivos por lotes y scripts hacia una operación segura.

Referencias

  1. Microsoft Learn, about_Execution_Policies. Sobre que la directiva de ejecución es una función de seguridad y no un sistema de seguridad que restrinja al usuario; la definición de cada directiva (AllSigned/Bypass/RemoteSigned/Restricted/Unrestricted, etc.); los cinco ámbitos y su orden de prioridad; que el ámbito Process se guarda en $Env:PSExecutionPolicyPreference y no prevalece sobre la GPO; que la directiva de grupo “Turn on Script Execution” tiene prioridad sobre todos los ámbitos; la adición de flujos de datos alternativos a los archivos descargados y los casos en que curl.exe, etc., no añaden la marca; y la advertencia sobre rutas UNC.  2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

  2. Microsoft Learn, Chapter 1 - Getting started with PowerShell. Sobre que la directiva de ejecución no es un límite de seguridad; que el valor predeterminado en Windows 10/11 es Restricted y en Windows Server 2016/2019/2022 es RemoteSigned; y que la directiva de ejecución solo afecta a los scripts, mientras que los comandos interactivos siempre se pueden ejecutar.  2 3

  3. Microsoft Learn, Set-ExecutionPolicy. Sobre que el ámbito predeterminado es LocalMachine y requiere privilegios de administrador; que no se pueden cambiar los ámbitos MachinePolicy/UserPolicy; que no se puede sobrescribir la directiva de grupo y que se muestra un mensaje en caso de conflicto; y el ejemplo de cómo Unblock-File desbloquea un script sin cambiar la directiva de ejecución.  2 3 4 5

  4. Microsoft Learn, Unblock-File. Sobre que Unblock-File elimina el flujo de datos alternativo Zone.Identifier con el valor 3, que indica la zona de Internet; que equivale al botón “Desbloquear” de las propiedades del Explorador de archivos; que debe verificarse la seguridad del archivo y de su procedencia antes de usarlo; y que Get-Item -Stream permite detectar archivos con Zone.Identifier.  2 3 4 5 6

  5. Microsoft Learn, about_Signing. Sobre los tipos de archivo que se pueden firmar; la diferencia entre un certificado emitido por una entidad de certificación y uno autofirmado (el autofirmado se limita a pruebas y no se puede ejecutar en otros equipos); que la firma se añade como un bloque de comentario # SIG #; que antes de PowerShell 7.2 era necesario guardar en ASCII/UTF8NoBOM; que el servidor de marca de tiempo mantiene la firma válida después de la caducidad del certificado; y el aviso de editor no confiable.  2 3 4 5 6 7 8 9 10 11 12 13 14 15

  6. Microsoft Learn, Set-AuthenticodeSignature. Sobre la aplicación de una firma Authenticode; el reemplazo de una firma existente; que el parámetro -TimestampServer evita que el script falle tras la caducidad del certificado; y el ejemplo de obtener un certificado de firma de código con clave privada mediante el parámetro -CodeSigningCert de la unidad Cert: 2 3

  7. Microsoft Learn, New-SelfSignedCertificate. Sobre que es el cmdlet que crea certificados autofirmados con fines de prueba; que el parámetro -Type permite especificar un certificado de firma de código; y que el período de validez predeterminado es de un año.  2 3

  8. Microsoft Learn, PowerShell security features. Sobre que la directiva de ejecución se sitúa como una de varias funciones que refuerzan la seguridad del entorno de ejecución de scripts, y se la describe como una función de seguridad que ayuda a evitar la ejecución de scripts maliciosos. 

  9. Microsoft Learn, File Streams. Sobre que, en el sistema de archivos NTFS, los datos que se escriben en un archivo se mantienen como flujos, y un mismo archivo puede tener varios flujos; que el flujo de datos predeterminado no tiene nombre; y que los flujos con nombre se especifican con el formato “nombre_de_archivo:nombre_de_flujo:tipo_de_flujo”. 

  10. Microsoft Learn, Active Directory Certificate Services documentation. Sobre que AD CS proporciona una infraestructura de clave pública (PKI) para las funciones de cifrado, certificados digitales y firma; y que se compone de servicios de rol como entidad de certificación, inscripción web, respondedor en línea (OCSP), servicio de inscripción de dispositivos de red y servicio web de inscripción de certificados, entre otros (sin incluir un servicio de respuesta de marca de tiempo).  2

  11. Microsoft Learn, Get-Certificate. Sobre que es el cmdlet que envía una solicitud de certificado al servidor de inscripción e instala la respuesta; que -Template especifica el nombre o el OID de la plantilla de certificado; que, si no se indican credenciales, se usa autenticación Kerberos; que -CertStoreLocation solo admite el almacén My; y que Status es Issued si ya se emitió, o Pending si está pendiente de aprobación. 

  12. Microsoft Learn, Configure trusted roots and disallowed certificates in Windows. Sobre el procedimiento para importar y distribuir certificados a almacenes como “Entidades de certificación raíz de confianza” desde la directiva de grupo (Configuración del equipo > Directivas > Configuración de Windows > Configuración de seguridad > Directivas de clave pública). 

  13. Microsoft Learn, about_Certificate_Provider. Sobre que el proveedor de certificados de PowerShell ofrece acceso al almacén de certificados y a los certificados a través de la unidad Cert:; las dos ubicaciones de almacén, CurrentUser y LocalMachine, y los almacenes que contienen; que cada certificado se identifica por su huella digital; que se puede recorrer con Get-ChildItem, igual que un sistema de archivos; y que el parámetro -CodeSigningCert obtiene los certificados cuyo uso de clave extendida incluye la firma de código.  2 3

  14. DigiCert, RFC 3161 compliant Time Stamp Authority server. Sobre que DigiCert publica el servidor de marca de tiempo compatible con RFC 3161 http://timestamp.digicert.com, que se puede usar para las marcas de tiempo de firmas Authenticode. 

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.

¿La directiva de ejecución de PowerShell es una función de seguridad?
La documentación oficial afirma explícitamente que "la directiva de ejecución no es un sistema de seguridad que restrinja las acciones del usuario". La razón es que basta con pegar el contenido de un script en la línea de comandos para eludirla fácilmente. El propósito de la directiva de ejecución es establecer una regla básica que evite el accidente de "ejecutar sin querer un script no deseado"; no debe diseñarse como un límite de seguridad para detener a un atacante. La protección frente a ataques corresponde a otra capa (control de aplicaciones, mínimo privilegio, etc.).
¿En qué se diferencian RemoteSigned y AllSigned?
RemoteSigned solo exige la firma de un editor de confianza en los scripts de origen de Internet (con Mark of the Web); los creados localmente se pueden ejecutar sin firmar. AllSigned exige firma en todos los scripts y archivos de configuración, incluidos los creados localmente, y solicita confirmación antes de ejecutar scripts de editores no clasificados. Si puede establecer una operación de firma interna (distribución de certificados de firma de código y procedimiento de firma), AllSigned es la opción adecuada; si no cuenta con ese nivel de organización, RemoteSigned es la elección realista.
¿Por qué un script descargado no se puede ejecutar y aparece el mensaje de que "no está firmado digitalmente"?
Los archivos descargados con un navegador u otra herramienta reciben un flujo de datos alternativo llamado Zone.Identifier y se tratan como "archivos procedentes de Internet". Si la directiva de ejecución es RemoteSigned, un script sin firmar con esta marca queda bloqueado. Si revisa el contenido y determina que es seguro, puede eliminar Zone.Identifier con el cmdlet Unblock-File o con el botón "Desbloquear" de las propiedades del Explorador de archivos, y así ejecutarlo sin cambiar la directiva de ejecución.
¿Cómo es realista operar la distribución interna de scripts de PowerShell?
Hay básicamente dos opciones. La primera es RemoteSigned combinado con un servidor de archivos, que se puede empezar sin montar un mecanismo de firma, aunque según la vía de distribución puede detenerse por recibir Mark of the Web, y no permite detectar manipulaciones del script. La segunda es AllSigned con operación de firma: se firma con Set-AuthenticodeSignature usando un certificado de firma de código (lo realista es que lo emita una CA interna) y se gestiona la directiva de forma centralizada con directiva de grupo. A cambio de contar con control de la directiva de ejecución y detección de manipulaciones, se asume el coste operativo de distribuir y renovar el certificado.
¿Está bien seguir usando -ExecutionPolicy Bypass en el Programador de tareas?
Funciona, pero no se recomienda. Primero, en un entorno donde la directiva de ejecución se gestiona mediante directiva de grupo, la especificación de ExecutionPolicy en la línea de comandos no prevalece sobre la directiva de grupo, así que directamente no tiene efecto. Además, el uso habitual de Bypass equivale a desmontar usted mismo el dispositivo de seguridad que "evita ejecuciones por descuido", lo que provoca que la política de la organización y la realidad del día a día se vayan distanciando. Si va a convertirlo en una operación permanente, lo correcto es configurar adecuadamente la directiva del entorno mediante directiva de grupo o con Set-ExecutionPolicy por un administrador, y que el script demuestre su confianza mediante la firma.
¿Por qué es necesaria la marca de tiempo (-TimestampServer) al firmar un script?
En principio, la validez de una firma está ligada al período de validez del certificado de firma, pero con una marca de tiempo, el servidor de marca de tiempo garantiza que "se firmó cuando el certificado era válido", de modo que puede seguir usando el script incluso después de que caduque el certificado. Como la mayoría de los certificados de firma de código tienen una vigencia de aproximadamente un año, operar sin marca de tiempo implica asumir cada año el riesgo de que "un día, de repente, todos los scripts firmados internamente dejen de funcionar a la vez". Especifique siempre -TimestampServer al firmar.

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