
Python DPAPI NG Decryptor for non-Windows Platforms
Libreria per la DPAPI NG, conosciuta anche come CNG DPAPI, per la decifratura e cifratura in Python. È progettata per replicare il comportamento di NCryptUnprotectSecret e NCryptProtectSecret. Può essere utilizzata su host non Windows per decifrare/cifrare segreti protetti da DPAPI NG, come la password protetta dall'utente PFX o la password cifrata LAPS. Può decifrare qualsiasi blob DPAPI NG utilizzando una copia offline della chiave radice del dominio oppure decifrare/cifrare utilizzando le credenziali dell'utente fornito per recuperare le informazioni richieste tramite RPC.
Attualmente sono supportati solo questi descrittori di protezione:
| Tipo | Scopo |
|---|---|
| SID | Solo l'utente SID o i membri del gruppo SID possono decifrare il segreto |
Questo implementa il MS-GKDI Group Key Distribution Protocol.
Per installare dpapi-ng con tutte le funzionalità di base, esegui
python -m pip install dpapi-ng
Il supporto per l'autenticazione Kerberos non verrà installato di default poiché richiede librerie di sistema e un compilatore valido. La libreria krb5 e il compilatore possono essere installati tramite l'installazione di questi pacchetti:
# 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 volta installati, gli extra Python per Kerberos possono essere installati con
python -m pip install dpapi-ng[kerberos]
Kerberos deve anche essere configurato per comunicare con il dominio, ma questo esula dallo scopo di questa pagina.
git clone https://github.com/jborean93/dpapi-ng.git
cd dpapi-ng
pip install -e .
È disponibile sia un'API sincrona che asincrona (asyncio) per decifrare/cifrare 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)
Per decifrare il blob, la chiave specificata nel blob deve essere recuperata dal controller di dominio da cui è stato generato il blob.
Per cifrare un blob, deve essere recuperata la chiave di gruppo del SID di destinazione specificato.
Per la decifratura, il nome host del controller di dominio viene recuperato automaticamente tramite una query DNS SRV di _ldap._tcp.dc._msdcs.{domain_name} usando il nome di dominio trovato nel blob DPAPI-NG o con il valore specificato nel kwarg server.
Per la cifratura, il nome host del controller di dominio viene determinato usando il kwarg domain_name per una query DNS SRV, il kwarg server direttamente oppure usando il dominio di ricerca di sistema (se disponibile).
Tenterà di autenticarsi con l'identificatore utente corrente che su Linux esiste solo se kinit è già stato chiamato per recuperare il ticket di un utente.
In caso contrario, se non è disponibile alcuna identità, i kwarg username e password possono essere usati per specificare un utente personalizzato.
I seguenti kwarg possono essere usati per ncrypt_unprotect_secret, async_ncrypt_unprotect_secret, ncrypt_protect_secret e async_ncrypt_protect_secret.
server: Usa questo server come destinazione RPC se è necessario recuperare una chiaveusername: Il nome utente con cui autenticarsi per la connessione RPCpassword: La password con cui autenticarsi per la connessione RPCauth_protocol: Il protocollo di autenticazione (negotiate, kerberos, ntlm) da usare per la connessione RPCcache: Una cache per memorizzare le chiavi recuperate per operazioni futureIn aggiunta, ncrypt_protect_secret e async_ncrypt_protect_secret accettano il seguente kwarg aggiuntivo.
domain_name: Il nome del dominio/forest da usare quando si cerca la destinazione RPC da utilizzare per recuperare le informazioni sulla chiaveroot_key_identifier: L'UUID dell'identificatore della chiave radice, necessario per far funzionare la cache per queste funzioniÈ anche possibile cifrare e decifrare il blob DPAPI-NG fornendo la chiave radice memorizzata nel dominio. Questa può essere recuperata tramite un attacco offline oppure tramite una query LDAP se si opera come utente Domain Admin. Per recuperare le chiavi radice del dominio usando PowerShell, è possibile eseguire quanto segue:
$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')
}
}
Il seguente comando ldapsearch può essere usato al di fuori di 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: ldapsearch molto probabilmente avrà bisogno delle informazioni -H e di bind utente per avere successo.
Le informazioni recuperate possono essere memorizzate in una cache e utilizzate per le successive chiamate a ncrypt_protect_secret e 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)
Attualmente l'algoritmo KDF SP800_108_CTR_HMAC e gli algoritmi di scambio segreto DH, ECDH_P256 e ECDH_P384 sono stati testati e funzionano.
L'algoritmo di scambio segreto ECDH_P521 dovrebbe funzionare, ma non è stato testato poiché al momento non è possibile creare un ambiente di test con esso.
Vorrei ringraziare le seguenti persone (handle GitHub o Twitter tra parentesi) per il loro aiuto su questo progetto:
Senza la loro pazienza e conoscenza, questo probabilmente non sarebbe stato possibile.