[TIP] en las ultimas versiones de Windows 11 y Server 2025, al lanzar un archivo .rdp para conexión remota nos pedirá permisos para conectar devices.

Colapsar
X
 
  • Filtrar
  • Tiempo
  • Mostrar
Limpiar Todo
nuevos mensajes
  • jmtella
    Administrator
    • Nov
    • 21712

    [TIP] en las ultimas versiones de Windows 11 y Server 2025, al lanzar un archivo .rdp para conexión remota nos pedirá permisos para conectar devices.

    En las ultimas versiones de Windows 11 y Windows Server 2025, si lanzamos un archivo .rdp para conexión remota nos pedirá permisos para conectar devices. De momento este aviso se puede quitar mediante:

    Código:
    reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services\Client" /v RedirectionWarningDialogVersion /t REG_DWORD /d 1 /f
    pero esto no garantiza que en próximas versiones funcione.

    Lo que realmente pide es que el rdp esté firmado. Por ello, debemos firmar los .rdp con rdpsign:


    Código:
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Folder,
    
        [ValidateSet('CurrentUser','LocalMachine')]
        [string]$StoreScope = 'CurrentUser',
    
        [ValidateRange(1,10)]
        [int]$YearsValid = 3,
    
        [ValidateNotNullOrEmpty()]
        [string]$SubjectPrefix = 'RDP Publisher - SoloEstePC',
    
        [switch]$TestOnly,
    
        [switch]$NoBackup,
    
        [switch]$OverwriteExistingBackups
    )
    
    Set-StrictMode -Version Latest
    $ErrorActionPreference = 'Stop'
    
    function Test-IsAdministrator {
        $identity  = [Security.Principal.WindowsIdentity]::GetCurrent()
        $principal = New-Object Security.Principal.WindowsPrincipal($identity)
        return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    }
    
    function Throw-IfFalse {
        param(
            [bool]$Condition,
            [string]$Message
        )
        if (-not $Condition) {
            throw $Message
        }
    }
    
    function Test-RdpSignProbe {
        param(
            [Parameter(Mandatory = $true)]
            [string]$RdpsignPath,
    
            [Parameter(Mandatory = $true)]
            [string]$Thumbprint,
    
            [Parameter(Mandatory = $true)]
            [string]$FilePath
        )
    
        $text = (& $RdpsignPath /sha256 $Thumbprint /l $FilePath 2>&1 | Out-String)
        $exitCode = $LASTEXITCODE
    
        $badPatterns = @(
            'could not be signed',
            'Unable locate the certificate specified',
            'A certificate needs to be specified'
        )
    
        $bad = $false
        foreach ($pattern in $badPatterns) {
            if ($text -match $pattern) {
                $bad = $true
                break
            }
        }
    
        [PSCustomObject]@{
            Ok       = ($exitCode -eq 0 -and -not $bad)
            ExitCode = $exitCode
            Output   = $text.Trim()
        }
    }
    
    # 1) Comprobaciones de entorno
    $requiredCommands = @(
        'rdpsign.exe',
        'New-SelfSignedCertificate',
        'Export-Certificate',
        'Get-FileHash'
    )
    
    foreach ($cmd in $requiredCommands) {
        try {
            $null = Get-Command $cmd -ErrorAction Stop
        }
        catch {
            throw "No encuentro '$cmd' en este equipo."
        }
    }
    
    if ($StoreScope -eq 'LocalMachine' -and -not (Test-IsAdministrator)) {
        throw "Con -StoreScope LocalMachine debes ejecutar PowerShell como administrador."
    }
    
    try {
        $resolved = Resolve-Path -LiteralPath $Folder -ErrorAction Stop
    }
    catch {
        throw "La carpeta '$Folder' no existe o no es accesible."
    }
    
    Throw-IfFalse (@($resolved).Count -eq 1) "La ruta '$Folder' debe resolver a una sola carpeta."
    $Folder = $resolved[0].ProviderPath
    
    Throw-IfFalse ((Test-Path -LiteralPath $Folder -PathType Container)) "'$Folder' no es una carpeta."
    
    # 2) Comprobar que hay .rdp
    $rdpFiles = Get-ChildItem -LiteralPath $Folder -Filter '*.rdp' -File | Sort-Object Name
    Throw-IfFalse ($rdpFiles.Count -gt 0) "No hay archivos .rdp en '$Folder'."
    
    # 3) Comprobar escritura en la carpeta
    try {
        $writeProbe = Join-Path $Folder ([System.IO.Path]::GetRandomFileName())
        Set-Content -LiteralPath $writeProbe -Value '' -NoNewline -ErrorAction Stop
        Remove-Item -LiteralPath $writeProbe -Force -ErrorAction Stop
    }
    catch {
        throw "No tengo permiso de escritura en '$Folder'."
    }
    
    # 4) Comprobar backups existentes
    if (-not $TestOnly -and -not $NoBackup -and -not $OverwriteExistingBackups) {
        $existingBackups = foreach ($file in $rdpFiles) {
            $bak = $file.FullName + '.bak'
            if (Test-Path -LiteralPath $bak) {
                $bak
            }
        }
    
        if ($existingBackups) {
            $list = ($existingBackups | Sort-Object | ForEach-Object { " - $_" }) -join [Environment]::NewLine
            throw "Ya existen backups .bak. Borralos o relanza con -OverwriteExistingBackups.`n$list"
        }
    }
    
    # 5) Crear SIEMPRE un certificado NUEVO
    $stamp     = Get-Date -Format 'yyyyMMdd-HHmmss'
    $subject   = "CN=$SubjectPrefix $stamp"
    $friendly  = "RDP Signer $env:COMPUTERNAME $stamp"
    $storePath = "Cert:\$StoreScope\My"
    
    $cert = New-SelfSignedCertificate `
        -Type CodeSigningCert `
        -Subject $subject `
        -FriendlyName $friendly `
        -CertStoreLocation $storePath `
        -KeySpec Signature `
        -KeyAlgorithm RSA `
        -KeyLength 2048 `
        -HashAlgorithm SHA256 `
        -KeyExportPolicy NonExportable `
        -NotAfter (Get-Date).AddYears($YearsValid)
    
    Throw-IfFalse ($null -ne $cert) "No se pudo crear el certificado."
    Throw-IfFalse ($cert.HasPrivateKey) "El certificado se creó sin clave privada."
    Throw-IfFalse ($cert.NotAfter -gt (Get-Date)) "El certificado ya aparece caducado."
    
    # 6) Usar el Thumbprint del store, sin espacios
    $thumb = $cert.Thumbprint.Replace(' ', '').ToUpperInvariant()
    
    # 7) Exportar CER publico (sin clave privada) por si luego quieres confiarlo/importarlo
    $cerPath = Join-Path $Folder ("RdpPublisher-{0}-{1}.cer" -f $thumb.Substring(0,8), $stamp)
    Export-Certificate -Cert $cert -FilePath $cerPath | Out-Null
    
    # 8) Probar rdpsign contra el primer archivo antes de tocar todo el lote
    $probe = Test-RdpSignProbe -RdpsignPath (Get-Command rdpsign.exe).Source -Thumbprint $thumb -FilePath $rdpFiles[0].FullName
    if (-not $probe.Ok) {
        throw "La prueba de rdpsign ha fallado.`nExitCode: $($probe.ExitCode)`n$($probe.Output)"
    }
    
    # 9) Firmar
    $rdpsignPath = (Get-Command rdpsign.exe).Source
    $results = @()
    
    foreach ($file in $rdpFiles) {
        $beforeHash = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash
        $bakPath    = $file.FullName + '.bak'
    
        if (-not $TestOnly -and -not $NoBackup) {
            if ($OverwriteExistingBackups) {
                Copy-Item -LiteralPath $file.FullName -Destination $bakPath -Force
            }
            else {
                Copy-Item -LiteralPath $file.FullName -Destination $bakPath
            }
        }
    
        $args = @('/v', '/sha256', $thumb)
        if ($TestOnly) {
            $args += '/l'
        }
        $args += $file.FullName
    
        $text = (& $rdpsignPath @args 2>&1 | Out-String)
        $exitCode = $LASTEXITCODE
    
        $bad = $text -match 'could not be signed|Unable locate the certificate specified|A certificate needs to be specified'
        if ($exitCode -ne 0 -or $bad) {
            throw "Error firmando '$($file.FullName)'.`nExitCode: $exitCode`n$text"
        }
    
        $afterHash = if ($TestOnly) {
            $beforeHash
        }
        else {
            (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash
        }
    
        $results += [PSCustomObject]@{
            File        = $file.FullName
            Backup      = if ($TestOnly -or $NoBackup) { $null } else { $bakPath }
            Mode        = if ($TestOnly) { 'TestOnly (/l)' } else { 'Signed' }
            Changed     = if ($TestOnly) { $null } else { ($beforeHash -ne $afterHash) }
        }
    }
    
    # 10) Resumen
    Write-Host ''
    Write-Host '================ RESUMEN ================'
    Write-Host "Carpeta            : $Folder"
    Write-Host "Store certificado  : $storePath"
    Write-Host "Cert path          : $storePath\$thumb"
    Write-Host "Subject            : $($cert.Subject)"
    Write-Host "Thumbprint SHA1    : $thumb"
    Write-Host "CER publico        : $cerPath"
    Write-Host "Archivos procesados: $($results.Count)"
    Write-Host "Modo               : $(if ($TestOnly) { 'Prueba (/l)' } else { 'Firmado real' })"
    Write-Host ''
    Write-Host 'Pega este Thumbprint en la policy de trusted .rdp publishers:'
    Write-Host $thumb
    Write-Host '========================================='
    Write-Host ''
    
    $results
    Ejemplos de uso:

    Código:
    # Prueba: crea un cert NUEVO y valida la firma, pero no toca los .rdp
    .\New-RdpPublisherAndSign.ps1 -Folder "C:\carpeta_donde_esten_los_rdp" -TestOnly
    Código:
    # Firma real, creando .bak
    .\New-RdpPublisherAndSign.ps1 -Folder "C:\carpeta_donde_esten_los_rdp"
    Código:
    # Firma real sin backups
    .\New-RdpPublisherAndSign.ps1 -Folder "C:\carpeta_donde_esten_los_rdp" -NoBackup
    Después de ejecutarlo, abrir gpedit.msc e ir a:

    Configuración de usuario > Plantillas administrativas > Componentes de Windows > Servicios de Escritorio remoto > Cliente de conexión a Escritorio remoto > Specify SHA1 thumbprints of certificates representing trusted .rdp publishers

    Se habilita y pegar el thumbprint que te imprimió el script. Si se quisiera que sirviera para todos los usuarios del equipo, usar la misma policy en Configuración del equipo.

    Este script crea un certificado nuevo en `CurrentUser\My` por defecto, usa `CodeSigningCert`, exporta además el `.cer` público, valida `rdpsign` con `/l` antes de tocar todo el lote y luego firma cada `.rdp` por separado creando `.bak` salvo que le digamos lo contrario. `rdpsign` sobrescribe el archivo firmado, no acepta comodines, y Microsoft indica que el thumbprint debe sacarse del almacén de certificados y usarse sin espacios.

    Cuando acabe, quedarse con el `Thumbprint SHA1` que imprime. Ese es el valor que se debe pegar en la directiva **“Specify SHA1 thumbprints of certificates representing trusted .rdp publishers”**. Microsoft documenta que esa policy existe tanto en **User Configuration** como en **Computer Configuration**, y que si la huella coincide, el usuario no recibe warnings al abrir el `.rdp`.

    Si al ser autofirmado se quiere dejar además confiado en Windows, Microsoft indica que los certificados autofirmados deben estar en el **Trusted Root Certificates store**, e `Import-Certificate` permite importar el `.cer` al store que elijas con `-CertStoreLocation`.

    Detalle importante: cada ejecución de este script crea un certificado distinto, así que si se vuelve a lanzar más adelante se tendrá también un **thumbprint nuevo** y hay que actualizar esa policy con la nueva huella.
  • jquilezl
    Super Moderator
    • Dec
    • 791

    #2
    Que sea el comportamiento por defecto para un rdp lo veo excesivo, más cuando la solución para saltarlo no es ni evidente ni sencilla. Lo lógico es que fuera algo configurable por GPO en entornos corporativos o con esas necesidades de seguridad, pero en otros muchos entornos no lo veo necesario. Lo lógico sería una opción en el equipo de destino de la conexión que requiera o no la firma del archivo rdp de origen, configurable al gusto. Un incordio más, aunque sea leve.

    Comentario

    • jmtella
      Administrator
      • Nov
      • 21712

      #3
      Originalmente publicado por jquilezl Ver Mensaje
      Que sea el comportamiento por defecto para un rdp lo veo excesivo, más cuando la solución para saltarlo no es ni evidente ni sencilla. Lo lógico es que fuera algo configurable por GPO en entornos corporativos o con esas necesidades de seguridad, pero en otros muchos entornos no lo veo necesario. Lo lógico sería una opción en el equipo de destino de la conexión que requiera o no la firma del archivo rdp de origen, configurable al gusto. Un incordio más, aunque sea leve.


      Malicious actors misuse this capability by sending RDP files through phishing emails. When a victim opens the file, their device silently connects to a server controlled by the attacker and shares local resources, giving the attacker access to files, credentials, and more.

      Comentario

      Trabajando...
      X