Windows 1o: run as different user

repairdzisiejszy wpis przygotowany jest przez Adriana. ostatnio zrobiło się mocno PowerShellowo, a dzięki takim wpisom udaje się wrócić do pierwotnej formuły W-F, ponieważ pomimo, iż pojawiają się fragmenty kodu, to temat jest czysto systemowy (:

„Jako priority number uno przyjmuję udowadnianie jak bardzo wizerunek człowieka związanego z branżą IT różny jest względem przyjętych stereotypów (podobno czasem się udaje) . Jestem pasjonatem technologii Microsoft, a szczególnie rodziny produktów System Center, nie zapominam jednak o innych dobrych rozwiązaniach. Architekt , Analityk, Administrator (AAA?) – a gdy się ściemni PowerShell Ninja. Słowem nieco udomowiony Geek zarażający swoją pasją Syna. I odpowiadając na pytanie – Nie, do snu nie czytam mu dokumentacji technicznej ;-)

.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.

Pracując od dłuższego czasu na najnowszym systemie operacyjnym ze stajni Microsoftu- Windowsie 10 coraz to bardziej doskwierał mi brak bezpośredniej możliwości uruchomienia programu w kontekście innego użytkownika z poziomu GUI a konkretnie z wyszukanych aplikacji w Menu Start.  Administratorom systemowym niejednokrotnie potrzebna będzie możliwość uruchomienia danej aplikacji nie tylko przy pomocy zwiększonych uprawnień (elevated rights) ale również przy wykorzystaniu zupełnie innego konta.

Niestety funkcjonalność ta domyślnie nie jest dostępna  w Menu Start – opcje które widzimy ograniczają się do poniższej listy

image1

Zanim powiemy sobie w jaki sposób dodać tutaj magiczny „Run as different user” powiedzmy w jaki sposób na szybko możemy spróbować obejść ten problem nie rozpisując się zbytnio nad każdym z nich

  1. Możemy wyszukać aplikacje i przypiąć ja do TaskBar’u po czym użyć dobrze nam znanej kombinacji
    Shift + prawy przycisk myszy na skrócie i wybór opcji „Run as a different user”  – tadam
    unnamed
  2. Dla oldschoolowców znających ścieżkę aplikacji lub posiadających ścieżkę do niej w zmiennej typu path możemy wykorzystać  Command Line i opcje Runas z przysłowiowego palca
    Najprostszy syntax wygląda następująco  :

    Runas /user:login@domena aplikacjadouruchomienia.

    unnamed02
    Więcej na temat dodatkowych opcji i samego syntaxu aplikacji Runas znajdziecie tutaj – https://technet.microsoft.com/en-us/library/bb490994.aspx

  3. Oczywiście można by wykorzystać tez powershella który jak zwykle daje nam „nieco” więcej możliwości Commandlet który może przyjść nam z pomocą to „start-process” z przełącznikiem credentials (lub jak kto woli „runas”)

unnamed03Więcej na temat dodatkowych opcji i samego syntaxu commandletu Start-Process znajdziecie tutaj – https://technet.microsoft.com/library/hh849848.aspx lub za pomocą komendy „get-help start-process” (pamiętajcie o aktualizacji „helpa” ;) )

 

Ale przecież nie o to nam chodzi – mimo całej wspaniałości jaka niewątpliwie jest powershell lub możliwość klikania jednak fajnie by było dorzucić sobie opcje uruchomienia w kontekście innego użytkownika bezpośrednio z GUI

Jak więc to zrobić? –  z pomocą przyjdzie nam pewna drobna modyfikacja rejestru – aby nie dawać uprawnienia każdemu polecam dokonać edycji w hive Current User

GUI version  – Regedit

Uruchamiamy Menu start i wpisujemy Regedit – uruchamiamy  i przechodzimy do ścieżki

HKEY_CURRENT_USER\SOFTWARE\Policies\Microsoft\Windows

Tworzymy nowy klucz (Key) o nazwie Explorer  jeżeli jeszcze nie istnieje

W nowo powstałym kluczu tworzymy wartość typu Dword o nazwie ShowRunasDifferentuserinStart i zmieniamy jej wartość na 1

Restart i gotowe ;)

GUI Version – Gpedit

Uruchamiamy Menu start i wpisujemy gpedit  – uruchamiamy

W nowo otwartym oknie przechodzimy do „user Configuration” -> Administrative Templates -> “Start Menu and Taskbar” – następnie wyszukujemy ustawienie „Show ‘Run as Different user’ command on Start”  – przestawiamy ustawienie na Enabled ;)

No Restart Needed!

PS version 

Odpalamy Powershella I

#Begining 
Set-Location HKCU:\SOFTWARE\Policies\Microsoft\Windows 
if((Test-Path .\Explorer) -eq $false) 
    { 
        New-Item Explorer -Force 
    } 
Set-Location .\Explorer 


if((Get-ItemProperty -Path . -Name ShowRunasDifferentuserinStart -ErrorAction SilentlyContinue) -eq $null) 
    { 
        New-ItemProperty -PropertyType Dword -Name "ShowRunasDifferentuserinStart" -Value 1 -Path . 
    } 
        ELSE
   { 
            Set-ItemProperty -NAME "ShowRunasDifferentuserinStart" -Value 1 -Path . 
    } 
#End

Lazy Version

Chcemy mieć to z głowy ? Pobieramy plik Reg i uruchamiamy go dwukrotnie klikająć

http://1drv.ms/1MjsSaT

Restart i gotowe ;)

Jezli opcja konfiguracji która wybraliśmy wymaga restartu to po jego dokonaniu powinniśmy widzieć już dodatkowa opcje w menu kontekstowym po kliknięciu prawym przyciskiem myszy na aplikacji w menu start tadam.

image5

.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.

eN.

 

obsługa błędów

catchAllTheErrors-615x461obsługa błędu może przyprawić nieco problemu, dla tego kilka zebranych tips’n’tricks, które mogą się przydać.

try/catch nie działa!

czasem nie działa. a to dla tego, że klauza catch zostanie wykonana dla błędów 'terminujących’. większość błędów natomiast jest 'zwykłymi’ błędami, nieterminującymi. zachowanie można zmienić albo zmieniając standardowe zachowanie poprzez globalnie ustawienie zmiennej

$ErrorActionPreference="stop"

co spowoduje nadpisanie wartości ustawionych dla commandletu.

można też po prostu dodać parametr ’-ea’ dla konkretnego wywołania. przykład. to nie zadziała zgodnie z oczekiwaniami:

try { gwmi -class win32_bios -ComputerName tralla} catch { echo 'TU NIE DZIAŁA'}

napis 'tu nie działa’ nigdy się nie pojawi. poprawiona wersja:

try { gwmi -class win32_bios -ComputerName tralla -ea stop} catch { echo "TU NIE DZIAŁA" }

zapytanie reqrsywne

problem z takim wymuszeniem jest w przypadq zapytań reqrsywnych. scenariusz: chciałbym wyszukać wszystkie katalogi, do których nie mam dostępu. robię reqrsywne przejście po katalogach i ustawiam błąd jako terminujący:

try { ls -Recurse -Directory C:\Windows\ -ea stop|out-null} catch { write-host "TU NIE DZIAŁA!" }

niby działa… ale, błąd jest terminujący. co oznacza, że po pierwszym wystąpieniu zakończy działanie wykonania. zobaczymy tylko pierwszy katalog, do którego nie mamy dostępu.

należy skorzystać z innej metody – przekazania błędów do zmiennej. uzysqje się to poprzez parametr 'ErrorValue’ [alias 'ev’]. wada jest taka, że zmienna zwracana jest po zakończeniu działania. tutaj już nie ma try/catch!

ls -Recurse -Directory C:\Windows\ -ev LISTINGERRORS|out-null
foreach($lerr in $LISTINGERRORS) {
    write-host "TU NIE DZIAŁA!"
    $lerr
}

oczywiście jeśli nie chce się oglądać błędów z przebiegu, to można sobie dodać ’-ea SilentlyContinue’ .

szczegóły błędu

na ekranie nadal będzie śmietnik. fajnie byłoby złapać tylko nazwy – i np. dla nich coś wykonać. dla tego warto zapoznać się z obiektem błędu:

   TypeName: System.Management.Automation.ErrorRecord

Name                  MemberType     Definition
----                  ----------     ----------
Equals                Method         bool Equals(System.Object obj)
GetHashCode           Method         int GetHashCode()
GetObjectData         Method         void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System....
GetType               Method         type GetType()
ToString              Method         string ToString()
CategoryInfo          Property       System.Management.Automation.ErrorCategoryInfo CategoryInfo {get;}
ErrorDetails          Property       System.Management.Automation.ErrorDetails ErrorDetails {get;set;}
Exception             Property       System.Exception Exception {get;}
FullyQualifiedErrorId Property       string FullyQualifiedErrorId {get;}
InvocationInfo        Property       System.Management.Automation.InvocationInfo InvocationInfo {get;}
PipelineIterationInfo Property       System.Collections.ObjectModel.ReadOnlyCollection[int] PipelineIterationInfo {g...
ScriptStackTrace      Property       string ScriptStackTrace {get;}
TargetObject          Property       System.Object TargetObject {get;}
PSMessageDetails      ScriptProperty System.Object PSMessageDetails {get=& { Set-StrictMode -Version 1; $this.Except...

teraz widać co mamy do dyspozycji. wyjątkowo ciekawe do obsługi będą 'FullyQualifiedErrorId’ pozwalający na zdefiniowanie logiki zależnie od rodzaju błędu, oraz 'TargetObject’ gdzie powinna znaleźć się informacja o obiekcie, który wygenerował błąd. proponuję przetestować, bo tak najlepiej się uczyć:

ls -Recurse -Directory C:\Windows\ -ev LISTINGERRORS -ea SilentlyContinue|out-null
foreach($lerr in $LISTINGERRORS) {
    write-host $lerr.TargetObject
}

nie wszystko jest błędem

i na koniec – nie wszystko co my uważamy za błąd, jest błędem. nie mam teraz przygotowanego przykładu, niemniej zdarzało mi się, że commandlet zwracał null lub informował o niepowodzeniu – co było jak najbardziej prawidłowym zachowaniem a nie błędem per se. w takim przypadq trzeba sobie obsłużyć błąd zwykłym if’em bo try/catch tego nie znajdzie.

eN.

PS Proxy Function

800px-Mucha-jobnie podoba Ci się w jaki sposób działa jakieś polecenie? nie chcesz/nie możesz grzebać w źródłowych plikach?

dziś Kacper pokaże, w jaki sposób nadpisać standardowe polecenie, przesilając je.

.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.

Tym razem dla odmiany najpierw skrypt, potem opis.

function Copy-Item {
    [CmdletBinding(DefaultParameterSetName='Path', SupportsShouldProcess=$true, ConfirmImpact='Medium', SupportsTransactions=$true, HelpUri='http://go.microsoft.com/fwlink/?LinkID=113292')]
    param(
        [Parameter(ParameterSetName='Path', Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [string[]]
        ${Path},

        [Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
        [Alias('PSPath')]
        [string[]]
        ${LiteralPath},

        [Parameter(Position=1, ValueFromPipelineByPropertyName=$true)]
        [string]
        ${Destination},

        [switch]
        ${Container},

        [switch]
        ${Force},

        [string]
        ${Filter},

        [string[]]
        ${Include},

        [string[]]
        ${Exclude},

        [switch]
        ${Recurse},

        [switch]
        ${PassThru},

        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [pscredential]
        [System.Management.Automation.CredentialAttribute()]
        ${Credential},
    
        [switch]
        ${Unbuffered},
    
        [switch]
        ${Progress})

    begin
    {
        try {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
            {
                $PSBoundParameters['OutBuffer'] = 1
            }
            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Management\Copy-Item', [System.Management.Automation.CommandTypes]::Cmdlet)
            if($PSBoundParameters['Unbuffered']) {
                $null = $PSBoundParameters.Remove('Unbuffered')
                $scriptCmd = {& xcopy.exe $PSBoundParameters["Path"] $PSBoundParameters["Destination"] /Y /J}
                
                if($PSBoundParameters['Progress']) {
                    $null = $PSBoundParameters.Remove('Progress')

                    $ScriptBlock = {
                        $XcopyProcess = Start-Process -FilePath xcopy -ArgumentList "$($PSBoundParameters["Path"]) $($PSBoundParameters["Destination"]) /Y /J" -Wait:$false -NoNewWindow -PassThru
                        Write-Verbose -Message "Xcopy process PID: $($XcopyProcess.Id)"
                        $FileSize = (Get-Item $PSBoundParameters["Path"]).Length / 1MB
                   
                        function Get-IoCounterMb {
                            $Runspace = [runspacefactory]::CreateRunspace()
                            $PowerShell = [powershell]::Create()
                            $PowerShell.runspace = $Runspace
                            $Runspace.Open()

                            [void]$PowerShell.AddScript({
                                [math]::Round((Get-Counter -Counter "\Process(xcopy)\IO Write Bytes/sec").CounterSamples.CookedValue / 1MB,0)
                            })

                            $PowerShell.Invoke()
                            $PowerShell.Dispose()
                        }

                        $MBSum = 0
                        do
                        {
                            Write-Verbose -Message "Waiting for xcopy process..."
                            Start-Sleep -Seconds 1
                        }
                        until (Get-Process xcopy -ErrorAction SilentlyContinue)
                        while(Get-Process xcopy -ErrorAction SilentlyContinue) {
                            $MB = Get-IoCounterMb
                            $MBSum = $MBSum + [int]$MB
                            Write-Verbose "MB copied: $MBSum, MB all: $FileSize"
                            Write-Progress -activity "Copying file" -status "Percent finished: " -PercentComplete (($MBSum / $FileSize) * 100)
                            Start-Sleep -Milliseconds 1
                        }
                    }
                    $scriptCmd = {Invoke-Command -ScriptBlock $ScriptBlock}
                }
            } else {
                $scriptCmd = {& $wrappedCmd @PSBoundParameters }
            }
            $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
            $steppablePipeline.Begin($PSCmdlet)
        } catch {
            throw
        }
    }

    process
    {
        try {
            $steppablePipeline.Process($_)
        } catch {
            throw
        }
    }

    end
    {
        try {
            $steppablePipeline.End()
        } catch {
            throw
        }
    }
    <#

    .ForwardHelpTargetName Microsoft.PowerShell.Management\Copy-Item
    .ForwardHelpCategory Cmdlet

    #>
}

Jak widać, dzieje się i to dużo. Zaczynając od początku, to co widać powyżej to tzw. proxy function. Co bystrzejsze oko pewnie zauważyło, funkcja ma dokładnie taką samą nazwę jak jeden z domyślnych cmdletów powershellowych. Proxy function powstały właśnie po to, aby umożliwić modyfikowanie działania wbudowanych cmdletów, czy też rozbudowywanie ich o nowe możliwości. Kod wydaje się być dosyć skomplikowany, ale zdecydowana większość została wygenerowana automatycznie za pomocą poniższych komend:

$cmd = New-Object System.Management.Automation.CommandMetaData (Get-Command Copy-Item)
[System.Management.Automation.ProxyCommand]::Create($cmd)

Generują one szkielet cmdletu, który chcemy modyfikować. Całość trzeba jedynie umieścić w dowolnie nazwanej funkcji. Jeśli nie nazwiemy jej tak samo jak istniejący cmdlet to stworzymy wtedy jego kopię z nowymi funkcjonalnościami oraz nazwą.

Aby zdefiniować nowe parametry, należy dodać ich definicję w sekcji param(), tak jak przypadku zwykłej funkcji. W tym przypadku, jeśli podany zostanie nowy parametr -Unbuffered, zmienna scriptCmd, która decyduje o tym co faktycznie się wykona, zostanie zmodyfikowana tak, żeby uruchamiać narzędzie xcopy, które oferuje kopiowanie bez użycia bufora (przełącznik /J). Cmdlet Copy-Item domyślnie nie posiada takiej możliwości. Ale naprawdę ciekawie zaczyna się robić, dopiero po użyciu parametru -Progress. Najpierw uruchamiane jest narzędzie xcopy z poleceniem -PassThrough, dzięki czemu można zapisać wynik w zmiennej, a potem odczytać chociażby id stworzonego procesu.

Jednak najbardziej interesującym fragmentem w tej części kodu jest funkcja Get-IoCounterMb. Dlaczego powstała i dlaczego wygląda tak, a nie inaczej? Jako, że xcopy nie udostępnia żadnych informacji nt. postępu procesu kopiowania, stwierdziłem, że sprawdzę wielkość kopiowanego pliku, a potem będę w pętli odczytywać z performance counterów ilość megabajtów zapisywanych przez proces xcopy w danym momencie. Użyłem runspace’ów, ponieważ z nieznanych mi obecnie powodów powershell nie widział szukanego countera i zwracał błąd. Dopiero utworzenie nowego wątku, podczas tworzenia runspace’a, pozwoliło mi na odczytanie wartości countera dla procesu xcopy. Dlaczego nie użyłem jobów? Ponieważ joby tworzą nowy proces, a nie wątek w obrębie istniejącego procesu, a tym samym zużywają więcej zasobów. Potem scriptblock już tylko czeka na uruchomienie xcopy i od tego momentu pokazuje postęp za pomocą Write-Progress, dopóki proces nie przestanie istnieć. Okazało się, że nie da się tym sposobem zilustrować postępu kopiowania, ponieważ cmdlet Get-Counter, mimo ustawionego czasu odstępu na 1ms w pętli, nie jest w stanie tak szybko zwracać wyników. I tak, dla pliku o wielkości 16GB, pasek postępu kończy się na 12GB. Postanowiłem zostawić jednak ten fragment w celach dydaktycznych. :) Poza tym, w tym przypadku akurat chodziło mi o orientacyjną informację.

Jedna ważna rzecz na koniec. Jeśli proxy function ma taką samą nazwę jak oryginalny cmdlet to go nadpisuje. Dopiero w nowej sesji w której funkcja nie została załadowana będzie możliwe użycie domyślnej wersji cmdletu.

Kacper.

.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.

jako komentarz dodam trick: jeśli w PS komenda zostanie przesilona, to można zmusić PS do skorzystania z oryginału, podając pełną ścieżkę wraz z modułem. w tym przypadku będzie to:

Microsoft.PowerShell.Management\copy-item

na podobny problem natrafiłem przy SCVMM, który nadpisuje standardowe commandlety hyper-V. i tak aby skorzystać z oryginalnego get-vm trzeba go uruchamiać:

Hyper-v\get-vm

aby usunąć taką funkcję wystarczy … ją usunąć:

rm function:copy-item

w Internetach częściej wykorzystywanym sposobem jest napisanie własnego copy. tworzy się strumień, odczytuje się w chunkach po kilka KB i wyświetla progress bar [np. tu http://stackoverflow.com/questions/2434133/progress-during-large-file-copy-copy-item-write-progress] .pomysł z odczytem liczników wydajności – niezłe (:

niemniej nie mam zaufania to takich metod i pozostaje kwestia mechanizmów cache itd. biblioteka systemowa daje pewne gwarancje. na koniec na wszelki wypadek wolałbym sprawdzić CRC (;

eN.

Excel zamiast CSV

Windows_PowerShell_iconpliki CSV są u mnie w powszechnym użyciu. łatwe w obsłudze, nie wymagają żadnych dodatków, wystarczy podstawowa wersja PS nawet w starej wersji i można generować raporty, łatwy export z excel pozwala to tworzenie pliqw wsadowych z danych z innych działów. wygodnie.

czasem jednak fajnie byłoby ominąć konieczność przejścia xlsx -> csv ->xlsx i wykonać jakieś operacje bezpośrednio. można w tym celu wykorzystać obiekt DCOM. jest sporo doqmentów, opisów – może nie jest to super trywialne i trzeba się przegryźć przez dały model DOM, ale do przeżycia. no i ma podstawową wadę – wymaga zainstalowanego MSExcel na komputerze, na którym wykonuje się operację. na stacji roboczej to zazwyczaj nie problem. na serwerze na odwrót.

Microsoft_Excel_2013_logo.svgrozwiązaniem jest wykorzystanie otwartej biblioteki EPPlus dostępnej na codeplex. nie eliminuje to konieczności znajomości DOM – nadal jest to tylko biblioteka, która pozwala na otwarcie pliq.

jest też gotowy moduł PS, który pozwala wykorzystać ją bez znajomości budowy plików excel. ma oczywiście poważne ograniczenia, jednak implementuje podstawowe operacje, wystarczające do codziennego skryptowania. są nawet funkcje, które wstawiają do arkusza wykresy a więc bardzo dobra opcja dla skryptów raportujących – w pełni zautomatyzowany skrypt, działający na serwerze, bez konieczności instalacji Office, wysyłający raporty xlsx.

w PS5.o można moduł doinstalować bezpośrednio z galerii PS poleceniem

install-module importExcel

można też go zassać z GitHuba ręcznie, dla starszych wersji.

na blogu scripting guy znajdziecie bogatszy opis zaopatrzony w kilka przykładów.

eN.

 

 

Future Visions

future_visions_machine_learning_webjaka będzie przyszłość? futuryzm to nie tylko fajna zabawa, ale bardzo realny projekt socjologiczny. wynalazki definiują całe społeczeństwa, to czym stanie się 'człowiek’, czy przetrwa i w jakiej formie. oczywiście, jest to przeważnie spojrzenie przez pryzmat wynalazków a nie sytuacji geopolitycznej, niemniej warto zawczasu podnieść pewne problemy do dysqsji społecznej tak, aby przygotować grunt pod nowe.

Microsoft zainicjował bardzo ciekawy projekt – 9 pisarzy science fiction spotkało się z naukowcami Microsoft Research Labs, gdzie mieli okazję zobaczyć nad jakimi ideami obecnie pracują. to z kolei zostało inspiracją do stworzenia opowiadań, efektem czego jest ’ Future Visions. Original Science Fiction Inspired by Microsoft’, które można nabyć bezpłatnie.

jeszcze nie czytałem bo… pomimo zapewnienia '[…]is available to anyone as a free download’ – nie każdy korzysta z kindle czy innych wynalazków, a zwykłego PDF niestety nie ma. same pliki są oczywiście szyfrowane DRM. trochę nie rozumiem idei 'udostępniania za darmo’ w szyfrowanej postaci przez wybranych dystrybutorów…

jak już przeczytam, nie omieszkam wrzucić jakiejś krótkiej recki (:

eN.

Obronić się przed MiTM

Minęło wiele lat od kiedy napisałem tu ostatni post a do tego temat na (re)debiut już nie jest gorący. Chociaż z drugiej strony nie zdążył jeszcze wystygnąć i w każdej chwili może ponownie wybuchnąć. Ale do rzeczy. Dziesięć miesięcy temu okazało się, że przez pół roku Lenovo raczyło swoich użytkowników certyfikatami umożliwiającymi ataki MiTM (https://www.us-cert.gov/ncas/alerts/TA15-051A). Wiadomo, generalne oburzenie, Chińczycy nas śledzą, na pewno to sprawa służb, teorie spiskowe  i w ogóle koniec świata. Szczerze mówiąc temat spłynął po mnie jak po kaczce, sprawdziłem swój tablet (kupiony w przypływie weny wracając z baru po spotkaniu świąteczno noworoczny) i zapomniałem o temacie. Potem gdzieś pojawił się problem z Dell System Detect i zdalnym wywołaniem kodu, który został załatany przed opublikowaniem dziury (http://tomforb.es/dell-system-detect-rce-vulnerability). To ruszyło mnie trochę bardziej, sprawdzanie kto ma zainstalowane oprogramowanie i usunięcie starych wersji okazało się nie być trzema kliknięciami ale koniec końców temat załatwiony i systemy załatane w kilka godzin.

Niby dwie z pozoru niepowiązane rzeczy a jednak ostatnio okazało się, że niektórzy wolą się uczyć na własnych błędach zamiast na cudzych i Dell obdarzył nas kolejną serią serią wpadek (http://joenord.blogspot.in/2015/11/new-dell-computer-comes-with-edellroot.html,http://www.kb.cert.org/vuls/id/925497) instalując razem ze swoim oprogramowaniem certyfikaty w Trusted Root razem z ich kluczami prywatnymi. Do tej pory jest jeszcze prawie ok. Oprogramowanie potrzebuje coś samo podpisać żeby system się nie burzył przy instalacji, certyfikat jest oznaczony jako nieeskportowalny. Nie jest najgorzej, w końcu to narzędzie systemowe a nie aplikacja pokazujące  reklamy jak w przypadku Lenovo i jesteśmy zadowoleni dopóki nie zauważymy, że nasz kolega ma ten sam certyfikat a ktoś nam powie, że klucz prywatny można wyeksportować na przykład za pomocą mimikatz.

W głowie zaczynają układać się klocki pokazujące bardzo prosty scenariusz ataku:

  1. Eksportujemy certyfikat (patrz link powyżej)
  2. Stawiamy darmowe WiFi z SSID Starbunio i idziemy w pobliże kawiarni
  3. Czekamy na kogoś z laptopem Della kto podłączy się do naszego WiFi i otworzy stronę, którą warto przechwycić
  4. Zmieniamy SSID i miejsce bo w naszym pierwotnym celu są same błyszczące Maci
  5. Bingo, złapaliśmy klienta otwierającego stronę banku. Dla niego wszystko wygląda w porządku, jest https, jest zielona kłódeczka a przeglądarka nie ma żadnych podejrzeń. Dopiero jak gość będzie bardzo dociekliwy to okaże się, że certyfikat uwierzytelniający bank wystawił Dell a nie Verisign. My sobie po drodze cały ruch odszyfrowujemy, podmieniamy numery kont (bo kto to sprawdza w smssach) i jesteśmy szczęśliwi.

Problem pojawia się kiedy zdamy sobie sprawę, że nie tylko my możemy tak zrobić a nasi użytkownicy na pewno nie sprawdzają za każdym razem kto wystawił certyfikat stronie i możemy się założyć, że kwestią czasu jest kiedy wypłyną dane z naszej firmy albo ktoś dostanie karnego na Facebook’u. A co jeśli nie tylko Dell i Lenovo mają problem z certyfikatami? Będziemy czekać na białe kapelusze aż opublikują artykuły i łatać za każdym razem licząc na to, że czarne kapelusze nie wkroczyły jeszcze do akcji? A może pójdziemy o krok dalej i będziemy bardziej proaktywni sprawdzając czy mamy jakiekolwiek podejrzane certyfikaty na naszych komputerach?

Teoria mówi, że żaden certyfikat znajdujący się w Trusted Root Certification Authorities nie powinien mieć klucza prywatnego. Gdzieś daleko od nas jest CA a my jemu ufamy ponieważ znamy klucz publiczny CA pasujący do klucza prywatnego używanego do podpisywania certyfikatów. Tyle teorii, praktyka okazuje się być trochę bardziej brutalna ale o tym później.

Kiedy mamy problem z pomocą przychodzi korporacyjna nowomowa i nagle problem staje się on wyzwaniem, a kto nie lubi wyzwań? Do tego jeszcze można zaprzęgnąć lubianego PowerShell i System Center lubiane … inaczej ;)

Zaczynamy od kawałka banalnego kodu:

foreach($BaseStore in Get-ChildItem cert:\)
{
   Get-ChildItem $BaseStore.PSPath -Exclude "My","Remote Desktop","SMS" |ForEach-Object{
      [array]$comptmp = Get-ChildItem $_.PSPath |Where-Object {
         $_.HasPrivateKey `
         -and $_.Subject -notmatch 'DO_NOT_TRUST_FiddlerRoot' `
         -and $_.Subject -notmatch $env:COMPUTERNAME
      }
   [array]$Compromised += $comptmp
   }
}
if($Compromised.Count -eq 0){
   return "Compliant"
}
else
{
   $output = ($Compromised | Out-String)
   return $output
}

Który przeleci nam po wszystkich dostępnych certificates stores wyłączając z tego:

  • My – certyfikaty użytkownika/komputera
  • Remote Desktop – to chyba nie wymaga tłumaczenia
  • SMS – certyfikaty używane przez klienta SCCM

w tym kroku można się pokusić jeszcze o wyłączenie innych rzeczy (np. certyfikatów klienta SCOM) albo zamianę

Get-ChildItem $BaseStore.PSPath -Exclude "My","Remote Desktop","SMS"

na

Get-ChildItem $BaseStore.PSPath -Include "Root"

żeby zaglądać tylko do Trusted Roots. Na razie trzymajmy się pierwszej wersji żeby zobaczyć co się w ogóle dzieje.

 

Jak już wiemy jakie mamy Certificates Stores to warto do nich zajrzeć i zobaczyć jakie mamy certyfikaty z kluczami prywatnymi

[array]$comptmp = Get-ChildItem $_.PSPath |Where-Object {
     $_.HasPrivateKey `
     -and $_.Subject -notmatch 'DO_NOT_TRUST_FiddlerRoot' `
     -and $_.Subject -notmatch $env:COMPUTERNAME
}

żeby uniknąć szumu wywalamy:

  • certyfikat Fiddlera – jego celem jest robienie MiTM i osoby mające go na komputerach wiedzą co robią
  • certyfikat zawierający z temacie nazwę hosta – tak to ciągle jest niebezpieczne ale wiemy, że ten certyfikat mamy tylko my i ewentualnie ktoś kto go nam wygenerował a nie cały internet. Takie świństwa podrzuca na przykład Skype ze skonfigurowanym Click to call

Reszta to proste działania mające na celu sprawdzenia czy mamy jakiekolwiek podejrzane certyfikaty i podanie ich listę lub potwierdzenie, że komputer jest ok.

 

Kiedy mamy już PowerShell to warto byłoby uruchomić go na wszystkich komputerach i sprawdzić kto ma coś podejrzanego na swojej maszynie. Fajnie byłoby też aby sprawdzanie odbywało się cyklicznie i informowało nas kiedy jest coś nie tak. Tutaj przydaje się SCCM i Compliance Settings.

Tworzymy Configuration Item uruchamiające skrypt i sprawdzające co jest na wyjściu

Configuration item

2015-12-08_21-47-13

Compliance Rules

2015-12-08_21-47-35

Compliance Rule

Potem robimy Configuration Baseline, dodajemy do niego nasz Configuration Item i robimy Deploy na kolekcje użytkowników oraz na kolekcje komputerów (odpowiednio All Users i All Systems). Ostatnie może się wydać trochę dziwne ale jest potrzebne żeby sprawdzić zarówno co mają użytkownicy jak i konto local system. Configuration baseline uruchomiony w kontekście systemu (Deploy na kolekcję komputerów) nie może zajrzeć do certyfikatów użytkowników bo te są trzymane w profilach*, a użytkownicy nie mogą zobaczyć jakie certyfikaty ma konto local system, za to zarówno użytkownik jak i local system mogą sprawdzić zawartość Certificate Store: Local Machine.

*-to nie jest do końca prawda bo można podmontować Hive i dekodować certyfikaty ale są na to prostsze sposoby

Jakby ktoś się pokusił dodatkowo o Remediation script żeby usuwać podejrzane certyfikaty to warto pamiętać, że Local System będzie mógł usuwać certyfikaty zarówno swoje jak o Local Machine a użytkownik będzie mógł usuwać wyłącznie swoje.

Na koniec pozostaje skonfigurowanie alertów, wysyłanie ich do SCOM i workflow do obsługi zdarzeń ale to już temat na osobny wpis.

 

wyciąganie notatek z kindle

800px-Mucha-jobdziś znów gościnnie Kacper ze skryptem w służbie codzienności. pomysły zastosowania PS nie tylko do zadań administracyjnych, a również w zwykłych domowych warunkach, są mocno inspirujące. ponieważ wpisy dotyczą wyrażeń regularnych, sugeruję zapoznanie się z podstawami – np. na stronie regular-expressions.

dziś skrypt automatyzujący wyciąganie 'clippingów’ z kindle… oddaję głos Kacprowi:

.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.

Od kiedy posiadam Kindle’a kupiłem tylko jedną książkę w wersji papierowej. Wśród wielu zalet Kindle ukrywa się jedna, która w szczególności przypadła mi do gustu. Nazywa się ona – My Clippings.txt. Czytniki Amazonu przechowują w tym pliku wszystkie notatki czy zaznaczenia związane z czytanymi książkami. Przykładowe wpisy w tym pliku wyglądają tak:

Mastering System Center 2012 R2 Configuration Manager (Santos Martinez, Peter Daalmans and Brett Bennett)
- Highlight on Page 183 | Loc. 5477-87  | Added on Friday, January 09, 2015, 03:46 AM

Some of the information that is accessible with this extra level of logging is so useful that it is common practice to configure verbose and debug logging as a standard across the environment. There is additional overhead because more data is written to the logs, but it is minimal and should cause no impact to the operation of the system. Enabling verbose and debug logging requires two registry changes. First, you enable verbose logging by navigating to [HKEY_LOCAL_MACHINE\software\microsoft\ccm\logging@global] and changing the value LogLevel to 0 (which will require a permissions change on the @GLOBAL key). To enable debug logging, create a registry key called DebugLogging directly under [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\CCM\Logging] Inside this registry key create a string value named Enabled and set its value to True.
==========
Mastering System Center 2012 R2 Configuration Manager (Santos Martinez, Peter Daalmans and Brett Bennett)
- Highlight on Page 184 | Loc. 5519-22  | Added on Friday, January 09, 2015, 03:47 AM

smsdbmon.log This log shows all activities such as inserts, updates, drops, and deletes from the Configuration Manager 2012 R2 database. smsprov.log This log shows the SQL transaction calls made from the Configuration Manager console or automation scripts via the SDK.
==========

Jak widać, mamy tu do czynienia z pewnym schematem, a to daje nam możliwość przekształcenia tych danych na inny format. Zazwyczaj kiedy czytam książkę pierwszy raz, robię dużo zaznaczeń, żeby potem mieć możliwość przypomnienia sobie tych kwestii, które mnie najbardziej interesowały. Można oczywiście przeglądać sobie zawartość My Clippings.txt w celu odświeżenia pamięci, jednak Kindle wpisuje tam wszystko w sposób chronologiczny. Jeśli więc czytam w tym samym czasie dwie książki, to i zaznaczenia będą się przeplatały. Aby ułatwić sobie życie postanowiłem napisać skrypt, który przetworzy dane z pliku My Clippings.txt tak, żeby można było np. filtrować z której książki zaznaczenia chcemy wyświetlić.:

[CmdletBinding()]
Param
(
    # Path to My Clippings.txt file
    [Parameter(Mandatory=$true,
                ValueFromPipeline=$true,
                ValueFromPipelineByPropertyName=$true,
                Position=0)]
    [Alias('FullName')]
    [String[]]$Path
)

Begin
{
    $Clippings = @()
}
Process
{
    Foreach($File in $Path){
        $Content = Get-Content -Path $File
        $i = 0

        do{
            $Clipping = ""
            # Prepare clipping
            do{
                $Clipping += $Content[$i]
                if($Clipping.EndsWith('AM') -or $Clipping.EndsWith('PM')){
                    $Clipping += ';'
                }
                $i++
                Write-Verbose -Message "i: $i"
            } while ($Content[$i] -ne '==========')

            $i++
            Write-Verbose -Message "Clipping content: $Clipping"

            # Format clipping
            # Extract clipping type
            $Pattern = "- (.*?) "
            $Match = $Clipping -match $Pattern
            $ClippingType = $Matches[1]
            
            # Extract book title
            $Pattern = ".*(?=- )"
            $Match = $Clipping -match $Pattern
            $BookTitle = $Matches[0]

            # Extract clipping content
            $Pattern = "\;(.*)"
            $Match = $Clipping -match $Pattern
            $ClippingContent = $Matches[1]

            # Prepare object
            $Props = @{
                BookTitle = $BookTitle
                ClippingType = $ClippingType
                Content = $ClippingContent
            }
            $Object = New-Object PSObject -Property $Props

            # Add object to array
            $Clippings += $Object

        } while ($i -ne ($Content.Length))
    }
}
End
{
    # Return clippings array
    return $Clippings
}

Jak widać skrypt nie jest skomplikowany, a najciekawsze fragmenty to regexy, które pobierają określony typ danych.

Na samym początku wstawiam średnik pomiędzy samą zawartość wpisu, a metadane. Prawdopodobnie nie jest to konieczne, ale znacznie mi ułatwiło tworzenie regexa w sekcji oznaczonej komentarzem „Extract clipping content”. Potem za pomocą wyrażenia „- (.*?) ” wydobywany jest typ wpisu. Zastosowanie nawiasów tworzy tzw. capturing groups, dzięki którym zwracana jest sama nazwa bez otaczających znaków. Jest to alternatywna wersja zapisu [Regex]::Matches($Clipping, $Pattern), który omawiałem w poprzednim wpisie.

Przy zapisywaniu tytułu książki do zmiennej wykorzystuję wyrażenie „.*(?=- )”. Ten regex zawiera tzw. positive lookahead, który zwraca wszystko od początku stringa aż do wystąpienia sekwencji znaków „- „, nie uwzględniając samej sekwencji (za to jest odpowiedzialny positive lookahead właśnie).

Na sam koniec, ponownie korzystając z capturing groups, pobieram wszystko co jest za znakiem średnika i zapisuję w zmiennej ClippingContent. Dalej jest już tylko stworzenie obiektu przy pomocy hashtable’a i dodanie go do tablicy Clippings, która jest zwracana przez skrypt.

Kacper

.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.

eN.