bezpieczne hardocodowanie hasła?
uruchamiając skrypt, najbezpieczniej jest NIE hardcodować hasła. jeśli coś jest w skrypcie, to znaczy, że może być wykorzystane w nieautoryzowany sposób. jednak przykłady z życia pokazują, że czasem taka jest konieczność .. czy po prostu zlecenie (; przykład – obsługa wsparcia, która ma korzystać ze skryptu, który gdzieś tam sobie sięga. to 'gdzieś tam’ to środowisko, w którym nawet owa obsługa nie ma konta.
podstawowe zasady bezpieczeństwa to 3xA (AAA) – Authentication, Authorization & Accounting. jeśli utworzymy konto serwisowe, które ma coś wykonywać w imieniu uruchamiającego, to aby nie złamać zasady AAA i być w stanie stwierdzić kto wykonał daną akcję, trzeba to przesunąć do miejsca, gdzie uruchamia się skrypt/aplikację i umieć to powiązać… dalej mogłoby być o SIEM i tak dalej… ale nie o tym tutaj. ogólnie najważniejsze – wykorzystanie konta serwisowego (w środowisq legacy-domain), czy aplikacji proxy (tak jak działa to w Azure) nie musi koniecznie być złem, ale pozostaje pytanie… *jak zamieścić hasło skrypcie tak, aby nie wyciekło wraz ze skryptem?*
najbezpieczniej byłoby wykorzystać certyfikat… tak to można zrobić np. dla aplikacji Azure. niestety w środowisq klasycznym, gdzie mamy np. wykonać akcję w domenie, nie jest to łatwe. najbezpieczniej byłoby umieszczenie serwisu aka proxy, który działa sobie na serwerze w owej domenie i do tegoż serwisu uwierzytelniać się certyfikatem. spoko ale to developerka i do 'małych projekcików’ czy 'drobnych narzędzi’ – po prostu jest za duża czasochłonność (czyt. koszt), nie wspominając o tym że napisanie własnego serwisu (np. wystawiającego REST API) to nie jest już skryptowanie. to developerka.
no więc zróbmy to, co da się zrobić tak, aby było szybko i *możliwie* bezpiecznie.
…na ile się da
skoro nie jesteśmy w stanie uwierzytelnić się certyfikatem, to przynajmniej zaszyfrujmy hasło certyfikatem. co nam to da:
- dany skrypt będzie działał wyłącznie na tej maszynie gdzie jest certyfikat i dla tych osób, którym na to pozwolimy.
żeby była jasność – jeśli ktoś, kto ma złe intencje będzie tego skryptu używał, to sobie hasło odkoduje. ale po pierwsze – 'pierwszy lepszy’ tego nie zrobi, po drugie – jeśli założylibyśmy takiej osobie konto, to i tak wykorzysta je do niecnych celów. trzeba wiedzieć że to może być rozwiązanie *good enough* i tylko w tym kontekście można nazwać je 'bezpiecznym’. spory disclaimer ale nie chcę przypadkowych czytelników wprowadzić w błąd.
jak już wyjaśniłem co rozumiem przez 'bezpieczne’, recepta:
- załóż konto w domenie, bez żadnych dodatkowych uprawnień
- dobrze byłoby poustawiać 'deny’ dla takiego konta, przykrajając odpowiednio uprawnienia – wedle potrzeby, uznania i paranoia-level
- nadać uprawnienia do tego, do czego będzie wykorzystywane
- wygenerować odpowiedni certyfikat, zainstalować go i odpowiednio nadać uprawnienia
- zaszyfrować hasło certyfikatem
- i voila.. można go wykorzystać w skrypcie
punkty 1-3 pominę, natomiast 4-6 zaraz opiszę.
dzięki takiej konfiguracji – skrypt, nawet skopiowany czy przypadkowo udostępniony, na niewiele się zda. aby wydobyć hasło – trzeba mieć dostęp do klucza prywatnego certyfikatu.
generowanie certyfikatu
super proste, przy czym do tego celu cert musi spełniać odpowiednie funkcje, czyli (nomen-omen) kluczem jest dobry key-usage i document type:
$cert=New-SelfSignedCertificate -DnsName "script automation" -CertStoreLocation "Cert:\CurrentUser\My" -KeyUsage KeyEncipherment,DataEncipherment,KeyAgreement -Type DocumentEncryptionCert
$cert|Export-PfxCertificate -FilePath C:\temp\scriptautomation.pfx -Password (ConvertTo-SecureString -String "secret-string" -Force -AsPlainText)
dodatkowo można certyfikat usunąć z własnego komputera tak, aby jedna kopia była w pliq:
$cert|remove-Item
…albo zachować go jako backup a usunąć potem plik. up2u.
instalacja w środowisq docelowym
najłatwiej oczywiście kliknąć sobie na pliq pfx i wybrać opcję 'local machine’. jeśli umieścimy w 'current user’ to będzie dostępny tylko dla nas.
kolejną hiper-istotną kwestią jest *NIE włączanie* możliwości exportu klucza.
jeśli ktoś woli z PowerShell – należy użyć ’import-pfxCertificate’ *bez* parametru -exportable.
Import-PfxCertificate -FilePath C:\temp\scriptautomation.pfx -Password (ConvertTo-SecureString -string "secret-string" -force -AsPlainText) -CertStoreLocation Cert:\LocalMachine\My\
ciekawostka: w PS 5.1 ten commandlet ma dodatkowy parametr, nieopisany w doc, protectPrivateKey, który pozwala wykorzystać Virtual Secure Mode.
to połowa sukcesu – bo do tak zaimportowanego certyfikatu, nikt bez uprawnień administratora nie będzie mógł skorzystać. należy nadać uprawnienia do klucza prywatnego:
- odpal 'certlm.msc’ (zarządzanie certyfikatami komputera)
- wybierz certyfikat i kliknij PGM
- jest tam opcja 'manage private keys…’
4. nadaj uprawnienie READ (nie FC!) dla wybranych osób
kodowanie i odkodowanie hasła
teraz już z górki. przygotowanie środowiska to bardziej wymagający element, ponieważ hasło można zaszyfrować za pomocą polecenia ’protect-cmsMessage’:
C:\_scriptz :))o- Protect-CmsMessage -To 'cn=script automation' -Content 'moje tajne hasło'
-----BEGIN CMS-----
MIIBtAYJKoZIhvcNAQcDoIIBpTCCAaECAQAxggFMMIIBSAIBADAwMBwxGjAYBgNVBAMMEXNjcmlw
dCBhdXRvbWF0aW9uAhB2BdZafxn2nkcWjQNvBi+pMA0GCSqGSIb3DQEBBzAABIIBAFY0TU8LAPl4
4IAWp9tGDOZK/bAEwQpZPix5dz8AoOpaDckpZ8dqatggEKq7vo4VX6wL4sBkImQoZrCZjkT7oCSi
N1YFDOIghjD7e2mIvEl/Gq2bReUbsFQ0iw6Wg1K8zVLBzDQAIUHgJvaV2Ssf4nCJ8WfW5d9UzmBB
JpHAmy7cdofboQt6hRz0wafGNivD47z2xNSSVyhqHVUzBt6NWmAYouFMGYGsBZBuOHXnQJzh+saG
Q5VpWxZGRHzM3igl8IgYDF75R+b/FSfciPu5Se2eeD6xxHrPj9rfLm37KVIab/4+QA/lRFVt4cwA
ztjhh8puyPqYgKCE7t0Tp5VNQv4wTAYJKoZIhvcNAQcBMB0GCWCGSAFlAwQBKgQQjmYztOm64boC
6XqnH/9kMYAgYTQlOXeGex7qMH5S5gVRKy/JOhfIdqlabP9e/N/6iRk=
-----END CMS-----
ciekawostka: przy tym mechaniźmie szyfrowania (Cryptographic Message Syntax), uruchomienie tego polecenia, da za każdym razem inny wynik
jeśli ktoś nie pamięta nazwy certu, to można je szybko wylistować:
C:\_scriptz :))o- ls Cert:\LocalMachine\My\
PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\My
Thumbprint Subject
---------- -------
B643393E34BDC10BE2266F307F63A712578416F1 CN=script automation
wkleić sobie ten ciąg do skryptu i kiedy potrzebne jest hasło, użyć unprotect-CMSMessage… np. tak:
[string]$encPass="-----BEGIN CMS-----
MIIBtAYJKoZIhvcNAQcDoIIBpTCCAaECAQAxggFMMIIBSAIBADAwMBwxGjAYBgNVBAMMEXNjcmlw
dCBhdXRvbWF0aW9uAhB2BdZafxn2nkcWjQNvBi+pMA0GCSqGSIb3DQEBBzAABIIBAFY0TU8LAPl4
4IAWp9tGDOZK/bAEwQpZPix5dz8AoOpaDckpZ8dqatggEKq7vo4VX6wL4sBkImQoZrCZjkT7oCSi
N1YFDOIghjD7e2mIvEl/Gq2bReUbsFQ0iw6Wg1K8zVLBzDQAIUHgJvaV2Ssf4nCJ8WfW5d9UzmBB
JpHAmy7cdofboQt6hRz0wafGNivD47z2xNSSVyhqHVUzBt6NWmAYouFMGYGsBZBuOHXnQJzh+saG
Q5VpWxZGRHzM3igl8IgYDF75R+b/FSfciPu5Se2eeD6xxHrPj9rfLm37KVIab/4+QA/lRFVt4cwA
ztjhh8puyPqYgKCE7t0Tp5VNQv4wTAYJKoZIhvcNAQcBMB0GCWCGSAFlAwQBKgQQjmYztOm64boC
6XqnH/9kMYAgYTQlOXeGex7qMH5S5gVRKy/JOhfIdqlabP9e/N/6iRk=
-----END CMS-----"
try {
$cert = Get-ChildItem Cert:\LocalMachine\My\B643393E34BDC10BE2266F307F63A712578416F1 -ErrorAction stop
} catch {
write-log "missing authentication certificate. quitting" -type error
retrun $null
}
if(-not $cert.HasPrivateKey) {
write-log "missing decription factor. quitting." -type error
return $null
}
$certCN = $cert.Subject
try {
[securestring]$secStringPassword = ConvertTo-SecureString (Unprotect-CmsMessage -Content $encPass -To $certCN -ErrorAction stop) -AsPlainText -Force
} catch {
write-log "error decrypting auth password $($_.exception)" -type error
return $null
}
[pscredential]$creds = New-Object System.Management.Automation.PSCredential ('CORP\SVC-contactCreator', $secStringPassword)
inne scenariusze i metody
jakiś czas temu linkowałem dostęp do EXO certyfikatem. wygodne.
a podczas uruchamiania własnych skryptów, i tam gdzie wystarczą zwykłe credsy, nie chce mi się wpisywać hasła. zapisuję więc je sobie do pliq:
if(test-path $credsFile) {
$myCreds = Import-CliXml -Path $credsFile
write-log "used saved credentials in $credsFile" -type info
} else {
$myCreds=Get-Credential
if($NULL -eq $myCreds) {
write-log 'Cancelled.' -type error
exit -3
}
if($saveCredentials) {
$myCreds | Export-Clixml -Path $credsFile
write-log "credentials saved as $credsFile" -type info
}
}
get-Credential używa DPAPI i w 'zamkniętym’ środowisq (np. na własnym lapq) w zupełności wystarcza. z tego co kojarzę szyfrowane jest kluczem użytkownika i komputera więc taki plik, aby był odczytany, musi być w tym samym środowisq. mimo wszystko w 'otwartym’ środowisq raczej nie należy stosować takich metod.
eN.
SecureString
scenariusz użycia
używanie SecureStringów w PS jest poniekąd wymuszone – w taki sposób przechowywane są hasła, np. w credentialsach:
SecureString przydaje się do przechowywania haseł, używanych w skryptach – np. skrypt do automatycznego podpięcia licencji office365, który wymaga podania credsów użytkownika z uprawnieniami co najmniej 'User Manager’. można hasło zapisać w pliq jako SS a potem wewnątrz skryptu je odczytać i użyć do złożenia credsów:
bezpieczeństwo SS
rodzą się natychmiast pytania o bezpieczeństwo SecureString – czy takie hasło da się odszyfrować i jak łatwo? hasło jest szyfrowane kluczem, który generowany jest na podstawie hasła użytkownika, a masterkey jest przechowywany na komputerze lokalnym. odpowiedzialne jest za to DPAPI – tutaj znajdziecie więcej informacji. klucz jest przechowywany w $env:USERPROFILE\Application Data\Microsoft\Protect\<GUID>. w efekcie
czyli nic da 'wykradzenie’ pliq lub użycie go w kontexcie innego użytkownika.
są potencjalnie metody potrafiące złamać klucz [np. hawkeye inject], ale nie znalazłem praktycznego opisu i na pewno nie są to metody na poziomie script kiddie. można zatem uznać, że jest to świetne narzędzie dla skryptów, które wymagają podania hasła.
addendum
na koniec – jak odczytać hasło zapisane jako SS? podam dwie metody – dla devów i dla opsów (;
dla devów:
dla opsów:
po zbudowaniu obiektu credential z wcześniejszego przykładu, mamy zmienną $creds
eN.