
Python DPAPI NG Decryptor for non-Windows Platforms
Biblioteca para el des-/cifrado de DPAPI NG, también conocido como CNG DPAPI, en Python. Está diseñada para replicar el comportamiento de NCryptUnprotectSecret y NCryptProtectSecret. Puede utilizarse en hosts que no sean Windows para des-/cifrar secretos protegidos por DPAPI NG, como la contraseña protegida por usuario de un PFX o la contraseña cifrada de LAPS. Puede descifrar cualquier blob de DPAPI NG usando una copia offline de la clave raíz del dominio, o des-/cifrar usando las credenciales del usuario suministrado para recuperar la información necesaria a través de RPC.
Actualmente solo se admiten estos descriptores de protección:
| Tipo | Propósito |
|---|---|
| SID | Solo el usuario del SID o los miembros del grupo del SID pueden descifrar el secreto |
Esto implementa el Protocolo de distribución de claves de grupo MS-GKDI.
Para instalar dpapi-ng con todas las funciones básicas, ejecute
python -m pip install dpapi-ng
El soporte de autenticación Kerberos no se instalará por defecto, ya que depende de bibliotecas del sistema y de que haya un compilador válido presente. La biblioteca krb5 y el compilador se pueden instalar instalando estos paquetes:
# Debian/Ubuntu
apt-get install gcc python3-dev libkrb5-dev
# Centos/RHEL
yum install gcc python-devel krb5-devel
# Fedora
dnf install gcc python-devel krb5-devel
# Arch Linux
pacman -S gcc krb5
Una vez instalados, los extras de Python para Kerberos se pueden instalar con
python -m pip install dpapi-ng[kerberos]
Kerberos también debe configurarse para comunicarse con el dominio, pero eso está fuera del alcance de esta página.
git clone https://github.com/jborean93/dpapi-ng.git
cd dpapi-ng
pip install -e .
Hay una API síncrona y otra asyncio disponibles para des-/cifrar un blob.
import dpapi_ng
### DECRYPTION ###
dpapi_ng_blob = b"..."
decrypted_blob = dpapi_ng.ncrypt_unprotect_secret(dpapi_ng_blob)
# async equivalent to the above
decrypted_blob = await dpapi_ng.async_ncrypt_unprotect_secret(dpapi_ng_blob)
### ENCRYPTION ###
data = b"..."
target_sid = "S-1-5-21-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-XXXX"
dpapi_ng_blob = dpapi_ng.ncrypt_protect_secret(data, target_sid)
# async equivalent to the above
dpapi_ng_blob = await dpapi_ng.async_ncrypt_protect_secret(data, target_sid)
Para descifrar el blob, la clave especificada en el blob debe recuperarse del controlador de dominio que generó el blob.
Para cifrar un blob, se debe recuperar la clave de grupo del SID objetivo especificado.
Para el descifrado, el nombre de host del controlador de dominio se obtiene automáticamente mediante una consulta DNS SRV de _ldap._tcp.dc._msdcs.{domain_name} con el nombre de dominio encontrado en el blob DPAPI-NG o con el valor especificado en el kwarg server.
Para el cifrado, el nombre de host del controlador de dominio se determina mediante el kwarg domain_name para una consulta DNS SRV, el kwarg server directamente, o usando el dominio de búsqueda del sistema (si está disponible).
Intentará autenticarse con el identificador de usuario actual, que en Linux solo existirá si ya se ha llamado a kinit para obtener un ticket de usuario.
De lo contrario, si no hay una identidad disponible, se pueden usar los kwargs username y password para especificar un usuario personalizado.
Los siguientes kwargs se pueden usar con ncrypt_unprotect_secret, async_ncrypt_unprotect_secret, ncrypt_protect_secret y async_ncrypt_protect_secret.
server: Usar este servidor como destino RPC si es necesario recuperar una claveusername: El nombre de usuario con el que autenticarse para la conexión RPCpassword: La contraseña con la que autenticarse para la conexión RPCauth_protocol: El protocolo de autenticación (negotiate, kerberos, ntlm) a usar para la conexión RPCcache: Una caché para almacenar claves recuperadas para operaciones futurasAdemás de eso, ncrypt_protect_secret y async_ncrypt_protect_secret aceptan el siguiente kwarg adicional.
domain_name: El nombre del dominio/forest a usar al buscar el destino RPC para recuperar la información de la claveroot_key_identifier: El UUID del identificador de la clave raíz, necesario para hacer que la caché funcione con estas funcionesTambién es posible cifrar y descifrar el blob DPAPI-NG proporcionando la clave raíz almacenada en el dominio. Esta se puede recuperar mediante un ataque offline o a través de una consulta LDAP si se ejecuta como usuario Domain Admin. Para recuperar las claves raíz del dominio usando PowerShell, se puede ejecutar lo siguiente:
$configurationContext = (Get-ADRootDSE).configurationNamingContext
$getParams = @{
LDAPFilter = '(objectClass=msKds-ProvRootKey)'
SearchBase = "CN=Master Root Keys,CN=Group Key Distribution Service,CN=Services,$configurationContext"
SearchScope = 'OneLevel'
Properties = @(
'cn'
'msKds-KDFAlgorithmID'
'msKds-KDFParam'
'msKds-SecretAgreementAlgorithmID'
'msKds-SecretAgreementParam'
'msKds-PrivateKeyLength'
'msKds-PublicKeyLength'
'msKds-RootKeyData'
)
}
Get-ADObject @getParams | ForEach-Object {
[PSCustomObject]@{
Version = 1
RootKeyId = [Guid]::new($_.cn)
KdfAlgorithm = $_.'msKds-KDFAlgorithmID'
KdfParameters = [System.Convert]::ToBase64String($_.'msKds-KDFParam')
SecretAgreementAlgorithm = $_.'msKds-SecretAgreementAlgorithmID'
SecretAgreementParameters = [System.Convert]::ToBase64String($_.'msKds-SecretAgreementParam')
PrivateKeyLength = $_.'msKds-PrivateKeyLength'
PublicKeyLength = $_.'msKds-PublicKeyLength'
RootKeyData = [System.Convert]::ToBase64String($_.'msKds-RootKeyData')
}
}
El siguiente comando ldapsearch se puede usar fuera de Windows:
ldapsearch \
-b 'CN=Master Root Keys,CN=Group Key Distribution Service,CN=Services,CN=Configuration,DC=domain,DC=test' \
-s one \
'(objectClass=msKds-ProvRootKey)' \
cn \
msKds-KDFAlgorithmID \
msKds-KDFParam \
msKds-SecretAgreementAlgorithmID \
msKds-SecretAgreementParam \
msKds-PrivateKeyLength \
msKds-PublicKeyLength \
msKds-RootKeyData
Nota: lo más probable es que ldapsearch necesite la opción -H y la información de vinculación (bind) del usuario para tener éxito.
La información recuperada allí se puede almacenar en una caché y usarse para llamadas posteriores a ncrypt_protect_secret y ncrypt_unprotect_secret:
import uuid
import dpapi_ng
cache = dpapi_ng.KeyCache()
root_key_id = uuid.UUID("76ec8b2d-d444-4f67-9db7-2f62b4358b35")
cache.load_key(
b"...", # msKds-RootKeydata
root_key_id, # cn
version=1,
kdf_algorithm="SP800_108_CTR_HMAC", # msKds-KDFAlgorithmID
kdf_parameters=b"...", # msKds-KDFParam
secret_algorithm="DH", # mskds-SecretAgreementAlgorithmID
secret_parameters=b"...", # msKds-SecretAgreementParam
private_key_length=512, # msKds-PrivateKeyLength
public_key_length=2048, # msKds-PublicKeyLength
)
dpapi_ng.ncrypt_unprotect_secret(b"...", cache=cache)
Actualmente se ha probado que funcionan el algoritmo KDF SP800_108_CTR_HMAC y los algoritmos de acuerdo secreto DH, ECDH_P256 y ECDH_P384.
El algoritmo de acuerdo secreto ECDH_P521 también debería funcionar, pero no ha sido probado ya que no se puede crear un entorno de prueba con él en este momento.
Me gustaría agradecer a las siguientes personas (sus handles de GitHub o Twitter entre paréntesis) por su ayuda en este proyecto:
Sin su paciencia y conocimiento, esto probablemente no habría sido posible.