PowerShell ile Günlük Dosyalarının Analizi
Powershell bize güvenlik görevlerini gerçekleştirmek için çok kullanışlı araçlar sağlar. Bu dersimizde bunları tartışacağız ve güvenliğin çeşitli aşamalarında Powershell’den nasıl faydalanabileceğimizi inceleyeceğiz.
Öncelikle PowerShell kullanarak farklı ürün/hizmetlerin log dosyaları üzerinde nasıl araştırma/inceleme yapabileceğimize bakalım:
PowerShell, günlük dosyalarını analiz etmek için çok kullanışlı bir araçtır. Get-Content , Select-String , ConvertFrom-Json ve ConvertTo-Json gibi cmdlet’leri kullanarak günlük dosyalarını okuyabilir, arayabilir ve değiştirebilirsiniz .
Eğitimimizin bir sonraki bölümünde IIS Web sunucumuzun log dosyası üzerinde çalışacağız. Önce bu dosyanın içeriğini okuyalım:
Get-Content -Path "C:\inetpub\logs\LogFiles\W3SVC1\u_ex230629.log"
Bu cmdlet’i çalıştırdığımızda parametre olarak verdiğimiz yoldaki log dosyasını okuyacak ve içeriğini ekrana yazdıracaktır.
Peki ya amacımız dosyanın tamamı yerine “letsdefend” ifadesini içeren satırları listelemek olsaydı? Evet, bunu yapmanın basit bir yolu var:
Get-Content -Path "C:\inetpub\logs\LogFiles\W3SVC1\u_ex230629.log" | Select-String -Pattern "letsdefend"
Bu komutu çalıştırdığımızda log dosyasının tamamında sadece “letsdefend” ifadesini içeren satırları yazdıracaktır:
Bu dosya üzerindeki değişiklikleri gerçek zamanlı olarak izlemek istediğimizde -Wait parametresini kullanabiliriz. Aşağıdaki komutu çalıştırdığımızda Get-Content cmdlet’i önce mevcut dosyanın içeriğini ekrana döküyor ve ardından komut satırına geri dönmek yerine beklemeye başlıyor. Dosyaya yeni içerik eklendiğinde (örneğimizde bir web sunucusunun erişim loglarını incelediğimiz için loglarını incelediğimiz sunucuya yeni bir istek gelirse) bu içeriği ekrandaki dump’a ekler. .
Get-Content -Path "C:\inetpub\logs\LogFiles\W3SVC1\u_ex230629.log" -Wait | Select-String -Pattern "letsdefend"
Bu özelliği bir önceki örnekte kullandığımız Select-String örneği ile birleştirdiğimizde , izlediğimiz log dosyasına aradığımız kalıba uyan bir logun ne zaman yazıldığını görebileceğiz.
Bunu aşağıdaki ekran görüntüsünde görebiliriz. Komutu ilk çalıştırdığımızda “letsdefend” içeren satırları ilgili log dosyasına atıyor ve ardından log dosyasını izlemeye başlıyor. get ile web sunucusunun indeks dosyasına “ waitExample=letsdefend ” parametrelerini gönderdiğimizde son satır ekleniyor.
Aynı günlük dosyasına, aynı IP adresinden kaç isteğin geldiğini sayan bir komut dosyası yazalım:
# log file location $filePath = "C:\inetpub\logs\LogFiles\W3SVC1\u_ex230629.log" # Get 'c-ip' (client IP) field index from log file header $ipFieldIndex = (Get-Content -Path $filePath | Where-Object { $_ -match "^#Fields:" }).Split(' ').IndexOf('c-ip') -1 # Skip informational lines $logContent = Get-Content -Path $filePath | Where-Object { $_ -notmatch "^#" } $ipCount = @{} foreach($line in $logContent) { $ip = $line.Split(' ')[$ipFieldIndex] if ($ipCount.ContainsKey($ip)) { $ipCount[$ip]++ } else { $ipCount[$ip] = 1 } } $ipCount
Betiğimizde öncelikle IIS Log dosyalarında standart olarak yer alan Fields satırını okuyarak c-ip sütununun sırasını buluyoruz. Bu sayede log satırlarındaki hangi sütunları okumamız gerektiğini biliyoruz.
Bu ayrıntıları aşağıdaki günlük dosyasının ekran görüntüsünde görebilirsiniz:
Daha sonra bir hash tablosu ($ipCount) oluşturuyoruz ve ilk kez gördüğümüz her IP adresini bu hash tablosuna ekliyoruz. Hashtable’da bir IP adresi zaten mevcutsa sayısını bir artırırız. Hashtable’da yoksa yeni bir anahtar-değer çifti oluşturup numarasını 1 olarak ayarlıyoruz. Betik bittiğinde $ipCount hashtable’ını yazdırıp her IP adresinin kaç istek yaptığını gösteriyoruz.
Son olarak çıktımıza bakalım:
#Fields satırındaki alanları değiştirerek, kaynak IP adresi yerine istekte bulunan URL’lerdeki sayılarını çıkarabiliriz, örneğin:
Şimdi bu sefer DHCP log dosyası üzerinde çalışalım ve bu örnekte PowerShell Object yapısını kullanalım:
Aşağıdaki gibi bir log dosyamız olsun. Bu günlük dosyası genellikle DHCP Sunucusu rolünün yüklü olduğu DC Sunucularında bulunur ve DHCP işlemlerinin kaydını tutar.
Aşağıdaki scriptin amacı bu log dosyasını inceleyerek vermiş olduğumuz MAC adresinin aktivitelerini kaydetmektir:
$macAddress = "00-10-22-01-23-45" $logPath = "dhcp.log" $events = Get-Content -Path $logPath | Where-Object { $_ -match $macAddress } | ForEach-Object { $fields = $_.Split(',') $eventDate = [DateTime]::ParseExact($fields[1], "MM/dd/yy HH:mm:ss", $null) $eventType = $fields[2] $ipAddress = $fields[3] $deviceName = $fields[4] $mac = $fields[5] if ($mac -eq $macAddress) { New-Object -TypeName PSObject -Property @{ EventDate = $eventDate EventType = $eventType IPAddress = $ipAddress DeviceName = $deviceName MACAddress = $mac } } } $events | Sort-Object -Property EventDate
Bu scriptin hangi satırında ne yaptığımızı detaylandıralım:
$macAddress = "00-14-22-01-23-45"
Bu satırda log dosyasında aratacağımız mac adresini $macAddress değişkenine değer olarak veriyoruz.
$logPath = "dhcp.log"
Bu satırda inceleyeceğimiz log dosyasının yolunu $logPath değişkenine değer olarak atadık (bu örnekte script ve log dosyası aynı dizinde olduğundan sadece adını yazmamız yeterli olacaktır). log dosyası).
$events = Get-Content -Path $logPath | Where-Object { $_ -match $macAddress } | ForEach-Object { …
Bu kısımda az önce tanımladığımız $logPath değişkenini -Path parametresi ile Get-Content cmdlet’ine ekliyoruz . Daha sonra | ile okunan içeriği atarız. Where-Object cmdlet’ine – match $macAddress parametresini kullanarak ekleyin . Buradaki amacımız daha önce $macAddress değişkeninde tanımladığımız değeri log dosyasından gelen verilerde aramaktır .
Bir sonraki adımda elde ettiğimiz çıktıyı ikinci | ForEach -Object cmdlet’ine. Artık belirttiğimiz MAC adreslerini içeren satır(lar)a sahibiz. Artık bu verileri işleyebiliriz:
ForEach-Object { $fields = $_.Split(',') $eventDate = [DateTime]::ParseExact($fields[1], "MM/dd/yy HH:mm:ss", $null) $eventType = $fields[2] $ipAddress = $fields[3] $deviceName = $fields[4] $mac = $fields[5]
Bu bölümde her gelen satırı “,” karakterini kullanarak sütunlara ayırıyoruz ve her parçayı $fields değişkenine bir dizi öğesi olarak ekleyin.
Daha sonra alanları daha sonra kullanmak üzere değişkenlere atarız. Burada $fields[2] alanındaki verileri bir dize olarak DateTime türü verilere dönüştürdüğümüze dikkat edin . Çıktıyı ekrana yazdırırken kronolojik sıraya göre doğru şekilde sıralayabilmemiz için bunu yapıyoruz.
İşlediğimiz verilerin aradığımız MAC adresini içerip içermediğini kontrol ederek devam ediyoruz:
if ($mac -eq $macAddress) { New-Object -TypeName PSObject -Property @{ EventDate = $eventDate EventType = $eventType IPAddress = $ipAddress DeviceName = $deviceName MACAddress = $mac } }
Satırda MAC adresi bulunursa ( $mac değişkeninin değeri ) betiğin başında tanımladığımız MAC adresine ( $macAddress ) eşitse ( -eq ), bu satırın içeriğini yeni bir powershell nesnesine yazıyoruz.
$events | Sort-Object -Property EventDate
Son satırımızda, sahip olduğumuz olayları ($events değişkeninin içeriğini) Sort-Object -Property cmdlet’ine atadık. Bunları EventDate sütununa göre sıralamak için .
Ve scriptimizi çalıştırdığımızda elde ettiğimiz sonuç şu şekildedir:
Aradığımız verileri powershell nesne veri tipine dönüştürürken bunu verileri tarihe göre sıralamak için yaptığımızı söylemiştik. Veriyi nesne tipine dönüştürdüğümüzde artık onu farklı şekillerde kullanmak çok kolay. Örnek olarak bu veriyi JSON formatına dönüştürmek istediğimizi varsayalım. Bunu yapmak için elimizdeki verileri ConvertTo-Json cmdlet’ine teslim etmemiz yeterli :
$events | Sort-Object -Property EventDate | ConvertTo-Json
Bunu yaptığımızda çıktımız şu şekilde görünecektir:
Farklı türde çıktılara ihtiyaç duyarsak ConvertTo-Json yerine ConvertTo-Csv, ConvertTo-Xml, ConvertTo-Html gibi cmdlet’leri de kullanabilirsiniz .
Herhangi bir formatta elde ettiğimiz çıktıyı ekran yerine bir dosyaya yazmak istersek tek yapmamız gereken çıktıyı Set-Content cmdlet’ine atayıp -Path parametresi ile hangi dosyaya yazmak istediğimizi belirtmektir. :
$events | Sort-Object -Property EventDate | ConvertTo-Json | Set-Content -Path "result.json"
PowerShell ile Windows Olay Günlüğü Analizi
Windows Olay Günlükleri, Microsoft Windows işletim sisteminin, işletim sistemi bileşenlerinin ve uygulamalarının çeşitli olayları hakkındaki bilgileri günlüğe kaydeden bir özelliğidir. Bu olaylar, hizmetlerin başlatılmasını veya durdurulmasını, uygulama hatalarını, güvenlik uyarılarını ve daha fazlasını içerebilir. Windows Olay Günlükleri, sistem yöneticilerine veya BT uzmanlarına sistemde neler olduğunu anlama ve olası sorunları teşhis etme yeteneği sağlar.
Windows Olay Günlükleri genellikle üç ana kategoriye ayrılır:
Uygulama günlükleri
Bu günlükler, Windows uygulamalarının hatalarını, uyarılarını ve diğer olaylarını izler. Uygulamalar, kullanıcıların veya sistem yöneticilerinin bilgilendirilmesi gereken durumlar hakkında bu günlüğe bilgi yazabilir.
Sistem günlükleri
Bu günlükler, işletim sistemi bileşenlerinin olaylarını izler. Sistem günlükleri donanım hatalarını, sürücü kurulumlarını, sistem hatalarını, donanım arızalarını ve sistemle ilgili diğer olayları içerebilir.
Güvenlik günlükleri
Bu günlükler güvenlikle ilgili olayları kaydeder. Güvenlik günlükleri oturum açma ve oturum kapatma girişimlerini, dosya ve nesne erişimini, kullanıcı ve grup yönetimini, güvenlik politikası değişikliklerini ve güvenlikle ilgili diğer olayları içerebilir.
Bunlara ek olarak Windows konfigürasyonuna, aktif olarak ayarlanan denetimlere vb. bağlı olarak başka olay günlüğü türleri de vardır.
Aracın yukarıdaki ekran görüntüsünde görebileceğiniz gibi, bu günlükleri görüntülemek ve analiz etmek için Olay Görüntüleyici aracı kullanılır.
Olay Görüntüleyici aracı güçlü bir araç olmasına rağmen, farklı log başlıklarındaki farklı olaylar arasında ilişkisel analiz oluşturmak ve bunlardan raporlar oluşturmak için kullanışlı bir araç değildir. Bu gibi durumlarda PowerShell, olay günlükleri üzerinde çok esnek ve hızlı çalışmamıza olanak tanıyan güçlü seçenekler sunar.
PowerShell’de Windows olay günlüklerini aramak için Get-EventLog’u veya daha güncel ve daha kapsamlı Get-WinEvent cmdlet’ini kullanabilirsiniz .
Örneklere geçmeden önce Get-WinEvent cmdlet’ine daha yakından bakalım . Bu güçlü araç sayesinde EventLogs Viewer ile grafik arayüzde yapabildiğimiz birçok görevi komut satırında da yapabiliyoruz.
Örneğin, aşağıdaki komutu çalıştırdığımızda mevcut günlük kaynaklarının bir listesini alabiliriz:
Get-WinEvent -ListLog * | Select-Object LogName, RecordCount, IsClassicLog, IsEnabled, logMode, logType | Format-Table -AutoSize
Bu komutu çalıştırdığınızda aşağıdakine benzer bir çıktı göreceksiniz:
Bu çıktıda RecordCount sütunu, listeyi çektiğimizde o girişte kaç adet log bulunduğunu gösterir.
IsClassicLog sütunu bu kaydın klasik tarzda mı yoksa yeni tarzda mı tutulduğunu belirtir. Windows’ta klasik tarzda loglar .mc dosyasında tutulurken , yeni tarzda loglar yerini .xml dosyalarına bırakıyor.
IsEnabled sütunu, sistemin bu kaynaktaki günlükleri toplayacak şekilde yapılandırılıp yapılandırılmadığı hakkında bilgi sağlar. Sütun değeri False ise bu, günlüklerin toplanmadığı anlamına gelir.
LogMode sütunu, günlüğe kaydetme için izin verilen disk boyutu aşıldığında sistemin nasıl davranacağını belirler. Üç değeri vardır:
- Dairesel: En eski kayıt silinir ve üzerine yazılır.
- Tut: Alan sağlanana kadar günlüğe kaydetmeyi durdurur.
- AutoBackup: Arşivleri otomatik olarak oluşturur ve devam ettirir.
LogType sütunu ilgili alanın günlük kategorisini belirler. Burada İdari , Analitik , Hata Ayıklama ve Operasyonel tanımlarını görebiliriz .
Tamam, şimdi herhangi bir alandaki logları nasıl listeleyebileceğimize bakalım. Aşağıdaki komutla Uygulama Kategorisindeki son 100 Olay kaydını TimeCreated, ID, ProviderName, LevelDisplayName, message sütunlarıyla listeliyoruz:
Get-WinEvent -LogName 'Application' -MaxEvents 100 | Select-Object TimeCreated, ID, ProviderName, LevelDisplayName, Message | Format-Table -AutoSize
Çıktımız şu şekilde olacaktır:
Get-WinEvent cmdlet’i ve EventLogs bu dersin konusu olmadığından burada duralım ve Powershell scriptleri ile neler yapabileceğimizi incelemeye devam edelim.
Bir örnek yapalım ve LetsDefend kullanıcısının sistemimizdeki giriş kayıtlarını listeleyen bir script yazalım. Bildiğiniz gibi Windows işletim sisteminde başarılı girişler Event ID 4624 ile kaydedilmektedir. Ayrıca belirttiğimiz kullanıcıya ait 4624 Event ID’yi de verilen tarih aralığında listeleyebiliriz:
# Set Username $username = "LetsDefend" # Set start and end dates $startDate = Get-Date "2023/01/01 00:00:00" $endDate = Get-Date "2023/12/31 23:59:59" # Get 4624 Events into HashTable $events = Get-WinEvent -FilterHashTable @{ LogName = 'Security' ID = 4624 StartTime = $startDate EndTime = $endDate } # Print data to screen foreach ($event in $events) { $xml = [xml]$event.ToXml() $eventUsername = $xml.Event.EventData.Data | Where-Object {$_.Name -eq 'TargetUserName'} | Select-Object -ExpandProperty '#text' if ($eventUsername -eq $username) { Write-Host "User $username, Login Date: $($event.TimeCreated)" } }
Scriptimizi çalıştırdığımızda aşağıdaki gibi bir çıktı elde edeceğiz:
Şimdi senaryomuza daha yakından bakalım:
# Set Username $username = "LetsDefend" # Set start and end dates $startDate = Get-Date "2023/01/01 00:00:00" $endDate = Get-Date "2023/12/31 23:59:59"
Kullanıcı adını ve aramanın başlangıç ve bitiş tarihlerini ilgili değişkenlere atadık. Bunları betiğin ilerleyen bölümlerinde kullanacağız.
# Get 4624 Events into HashTable $events = Get-WinEvent -FilterHashTable @{ LogName = 'Security' ID = 4624 StartTime = $startDate EndTime = $endDate }
Bu blokta, Get-WinEvent cmdlet’ini kullanarak belirli bir tarih aralığındaki ve belirli bir olay kimliğine sahip olayları (bu durumda 4624; yani kullanıcı oturum açma olayları) alırız. Bu cmdlet’in -FilterHashTable parametresi ile olayları belirli kriterlere göre filtreleyebiliriz. Burada, seçtiğimiz başlangıç ve bitiş tarihlerindeki tüm Güvenlik 4624 Olaylarını yakalıyoruz. Bu aşamada kullanıcı adını kullanmadığımızı fark ettiniz mi?
# Print data to screen foreach ($event in $events) { $xml = [xml]$event.ToXml() $eventUsername = $xml.Event.EventData.Data | Where-Object {$_.Name -eq 'TargetUserName'} | Select-Object -ExpandProperty '#text' if ($eventUsername -eq $username) { Write-Host "User $username, Login Date: $($event.TimeCreated)" } }
Ve son bloğumuza geliyoruz, $events değişkeninde kaydettiğimiz olayları ilk önce XML’e ayrıştırıyoruz ve daha sonra bu XML nesnelerindeki TargetUserName alanının değeri LetsDefend ise ekrana yazdırıyoruz . Write -Host cmdlet’i.
Yukarıda da belirttiğimiz gibi bu örnekte öncelikle 4624 eventin tamamını çekiyoruz ve sonrasında aradığımız kullanıcı adına sahip olanları tespit ediyoruz. Bu kullanım az sayıda log kaydı olan küçük log dosyaları için kullanılabilse de logların çok fazla olduğu durumlarda ciddi kaynak tüketimine neden olacaktır. Bunu önlemek için yine de Get-WinEvent cmdlet’ini kullanabiliriz ancak en kötü yanı bu cmdlet’te doğrudan “SubjectUserName” gibi bir alanın olmamasıdır . Bunun için daha önce XML ile çalışmış olanlarınızın aşina olacağı XPath özelliğini kullanabiliriz.
$username = "LetsDefend" $startDate = Get-Date "2023/01/01 00:00:00" $endDate = Get-Date "2023/12/31 23:59:59" $query = @" *[System[(EventID=4624) and TimeCreated[@SystemTime>='{0}' and @SystemTime<='{1}']]] oath *[EventData[Data[@Name='TargetUserName'] and (Data='{2}')]] "@ -f $startDate.ToUniversalTime().ToString("o"), $endDate.ToUniversalTime().ToString("o"), $username $events = Get-WinEvent -FilterXml $query foreach ($event in $events) { Write-Host "User $username, Login Date: $($event.TimeCreated)" }
Bu komut dosyası, XPath sorgusunu kullanarak belirli bir kullanıcının belirli bir tarih aralığındaki oturum açma etkinliklerini doğrudan alır. Bu sayede gereksiz olaylar almak yerine sadece ilgilendiğimiz olayları alarak kaynak kullanımını azaltıyoruz.
-f operatörü, dizedeki {0}, {1} ve {2} yer tutucularını sırasıyla $startDate, $endDate ve $username değişkenlerinin değerleriyle değiştirir. ToString(“o”) yöntemi, tarihleri ISO 8601 biçimine dönüştürür çünkü XPath sorgusu bu biçimdeki tarihleri bekler.
Bu scriptimizin çıktısı yine bir önceki scriptimizin çıktısı ile aynı olacaktır fakat ikisini birden çalıştırdığınızda ikinci örneğimizin çok daha hızlı çalıştığını ve çok daha az sistem kaynağı tükettiğini göreceksiniz.
Evet, Powershell ile eventLog analizini burada bitirebiliriz. Bu konu üzerinde çalışabilmek için hedeflediğiniz event ID’lerin yapısını ve içeriğini bilmeniz, taşıdıkları alan adlarını ve değerleri tanımanız gerektiğini anladığınızı düşünüyoruz.
Web Scraping Nedir?
Web Scraping Nedir ve Neden Bilmeliyiz?
Web kazıma, bir web sitesinin HTML içeriğini ayrıştırma ve bu içerikten veri çıkarma işlemidir. Web kazıma genellikle otomatik yazılım veya komut dosyaları kullanılarak yapılır. Bu komut dosyaları genellikle bir web tarayıcısının yaptığı şeyin aynısını yapar: bir web sitesini açın, belirli bilgileri bulun ve bu bilgileri bir şekilde toplayın veya saklayın.
Web kazıma çok kullanışlıdır ve birçok senaryoda yaygın olarak kullanılır. Güvenlik perspektifinde kullanımı zaten konumuzdur ancak yararlı olabileceği diğer konulara da örnekler vermek aydınlatıcı olacaktır:
Veri Bilimi ve Makine Öğrenimi
Çoğu makine öğrenimi modelinin verilere ihtiyacı vardır ve bu verileri toplamak için web kazıma kullanılabilir. Örneğin, bir kişi sosyal medya yorumlarından duygusal analizleri analiz etmek ve bu yorumları toplamak için web kazımayı kullanmak isteyebilir.
Pazar araştırması
Şirketler ve bireysel araştırmacılar, çevrimiçi ürünler hakkında fiyatlar, özellikler, tüketici incelemeleri ve daha fazlası gibi bilgi toplamak için web kazımayı kullanabilir. Bu, eğilimleri izlemek, rakipleri analiz etmek veya pazar analizi gerçekleştirmek için kullanılabilir.
Web İçeriği Kazıma
Bir kişi veya şirket, belirli bir konu hakkında bilgi toplamak için web kazımayı kullanabilir. Örneğin bir kişi tüm tarifleri toplayıp tek bir veritabanında bir araya getirebilir.
Veri Entegrasyonu
Web kazıma, farklı sitelerden veri toplamak ve bunları tek bir yerde birleştirmek için de kullanılabilir. Bu, bir haber portalı veya farklı kaynaklardan bilgi sağlayan bir fiyat karşılaştırma sitesi oluşturmak için kullanılabilir.
Konuya devam etmeden önce Web Scraping uygulamalarının hukuki sorumluluklar doğurabileceğini belirtmeliyiz. Örneğin, bir web sitesinin kullanım koşulları o sitede kazıma yapılmasını yasaklayabilir. Ayrıca toplanan verilerin kullanımı çeşitli veri koruma ve gizlilik yasalarına da tabi olabilir. Bu nedenle, bir web kazıma projesine başlarken bu hususların dikkate alınması önemlidir.
PowerShell ve Web Scraping – 1
PowerShell, arkasındaki .NET gücü sayesinde Web Scraping için çok güçlü ve kullanışlı araçlar sunar. Web Scraping genel olarak istekte bulunma, gelen yanıtı ayrıştırma, ihtiyacımız olan veriyi çıkarma, çıkarılan veriyi standartlaştırma ve saklama adımlarından oluşur. PowerShell bu adımların her biri için işimizi kolaylaştıracak araçlar sunuyor. Bunları inceleyelim:
HTTP İstekleri
PowerShell , Invoke-WebRequest ve Invoke-RestMethod cmdlet’leriyle HTTP isteklerini destekler . Bu cmdlet’ler bir web sitesine GET veya POST isteği gönderilmesine ve sunucunun yanıtının işlenmesine olanak tanır.
Invoke-WebRequest
Invoke-WebRequest cmdlet’ini kullanarak bir HTTP isteği oluşturabilir ve web sunucusunun yanıtını alabilirsiniz. Bu çok güçlü araç, şimdiye kadar incelediğimiz cmdlet’lerden aşina olduğumuz birçok parametre ile çalışmamızı özelleştirmemize olanak sağlıyor.
Şimdi en basit web isteğini nasıl yapabileceğimizi görelim:
Invoke-WebRequest -Uri "https://letsdefend.io"
Varsayılan metodun GET olması ve aksini belirtmediğimiz için bu istek GET metodu ile yapılmıştır. Aşağıda isteğimizi GET ile değil POST ile yapıyoruz:
Invoke-WebRequest -Uri "https://letsdefend.io" -Method 'POST'
Şimdi isteğimize kullanıcı adımızı ve şifremizi ekleyelim:
$body = @{ 'username' = 'admin' 'password' = 'p@ssw0rd' } Invoke-WebRequest -Uri "https://letsdefend.io" -Method 'POST' -Body $body
Son olarak isteğimizin başlığına bir token ekleyelim:
$token =”LetsDefendisGreat/andThisIsToken” $headers = @{ 'Authorization' = 'Bearer ' + $token } $body = @{ 'username' = 'admin' 'password' = 'p@ssw0rd' } Invoke-WebRequest -Uri "https://letsdefend.io" -Headers $headers -Method 'POST' -Body $body
Invoke-WebRequest ile talebimizi gerçekleştirdik. Şimdi tek yapmamız gereken gelen yanıtı işlemek.
HTML Ayrıştırma
Invoke-WebRequest cmdlet’i, .Content veya .ParsedHtml özellikleri aracılığıyla erişilebilen web sayfasının HTML içeriğini içerir . Bu özellikler, HTML içeriğini ayrıştırmak ve belirli HTML etiketlerine, sınıflarına ve kimliklerine erişmek için kullanılabilir.
Bunu yapmanın en basit yolu isteğimizi aşağıdaki gibi bir değişkene atayıp daha sonra bu değişkenin .Content özelliğini çağırmaktır.
$token =”LetsDefendisGreat/andThisIsToken” $headers = @{ 'Authorization' = 'Bearer ' + $token } $body = @{ 'username' = 'admin' 'password' = 'p@ssw0rd' } $response = Invoke-WebRequest -Uri "https://letsdefend.io" -Headers $headers -Method 'POST' -Body $body $response.Content
Bu scripti çalıştırdığımızda, aşağıdaki ekran görüntüsünde de görebileceğiniz gibi,letsdefend.io adresindeki web sunucusundan gelen yanıt ekrana yazdırılacaktır. Cevabın HTML formatında olduğuna dikkat edin.
Bu çıktıyı bir dosyaya kaydetmek için yapabileceğimiz en basit şey şunu kullanmaktır: Out -File cmdlet’i. Kodumuzun son satırını aşağıdaki gibi değiştirdiğimizde çıktımız scriptimizin çalıştığı dizindeki LetsDefendResponse.html dosyasına kaydedilecektir :
$token ="LetsDefendisGreat/andThisIsToken" $headers = @{ 'Authorization' = 'Bearer ' + $token } $body = @{ 'username' = 'admin' 'password' = 'p@ssw0rd' } $response = Invoke-WebRequest -Uri "https://letsdefend.io" -Headers $headers -Method 'POST' -Body $body $response.Content | Out-File -FilePath "LetsDefendResponse.html"
Powershell Invoke-WebRequest cmdlet’i, yanıtları bir BasicHtmlWebResponseObject nesnesinde saklar. Yukarıda bu nesnenin .Content özelliğine ulaştık ancak bu nesnenin başka özellikleri de var. Bunları aşağıdaki tabloda görebilirsiniz:
Şimdi bu özelliklerin nasıl kullanılacağına bir göz atalım. Öncelikle scriptimizi aşağıdaki gibi yeniden düzenleyelim:
$response=Invoke-WebRequest -Uri “https://google.com” -Method ‘GET’
$response=Invoke-WebRequest -Uri "https://google.com" -Method 'GET' Write-Host "BaseResponse : " $response.BaseResponse Write-Host "Encoding : " $response.Encoding Write-Host "RawContentLength : " $response.RawContentLength Write-Host "RelationLink : " $response.RelationLink Write-Host "StatusCode : " $response.StatusCode Write-Host "StatusDescription : " $response.StatusDescription Write-Host "Images : " $response.Images Write-Host "InputFields : " $response.InputFields Write-Host "Links : " $response.Links Write-Host "Reply Headers" Write-Host "================================= =" $response. Headers
Ve şimdi sonuca bakalım:
Ekran görüntüsünde görebileceğiniz gibi BasicHtmlWebResponseObject nesnesinde de yalnızca yanıtın içeriğinin değil aynı zamanda HTTP protokolünün başlık bilgilerinin de saklandığı alanlar bulunmaktadır. Bu sayede, örneğin StatusCode özelliğini kullanarak, içeriği hiç işlemeden, çağırdığımız sayfanın geçerli olup olmadığını (Kod: 200 vs 404) kontrol edebiliriz. Bir diğer kullanışlı özelliği ise işlediğimiz sayfa içerisinde Bağlantılar ve Görseller gibi ihtiyaç duyabileceğimiz içerikleri otomatik olarak ayrıştırıp Görseller , Bağlantılar, Giriş Alanları alanlarında saklamasıdır. Bunu bizim için yapmasaydı, yukarıdaki ilk örneğimizde yaptığımız gibi önce İçeriği almamız ve ardından içindeki , img , input vb. gibi alanları aramamız ve ayrıştırmamız gerekecekti. kod.
Öte yandan ekran görüntüsünde de görebileceğiniz gibi çıktılar hala biraz karmaşık. Örneğin, Bağlantılar çıktısına bakarsanız devasa bir HTML kodu bloğu görürsünüz.
Links: @{innerHTML=Search; innerText=Search; outerHTML=Search; outerText=Search; tagName=A; id=gb_1; class=gbzt gbz0l gbp1; href=https://www.google.com.tr/webhp?tab=ww} @{innerHTML=Images; innerText=Images; outerHTML= Images; outerText=Images; tagName=A; id=gb_2; class=gbzt; href=https://www.google.com/imghp?hl=tr&tab=wi} @{innerHTML=Maps; innerText=Maps; outerHTML=Maps; outerText=Maps; tagName=A; id=gb_8; class=gbzt; href=https://maps.google.com.tr/maps?hl=tr&tab=wl} @{innerHTML=Play; innerText=Play; outerHTML=Play ; outerText=Play; tagName=A; id=gb_78; class=gbzt; href=https://play.google.com/?hl=tr&tab=w8} @{innerHTML=YouTube; innerText=YouTube; outerHTML=YouTube ; outerText=YouTube; tagName=A; id=gb_36; class=gbzt; href=https://www.youtube.com/?tab=w1} @{innerHTML=News; innerText=News; outerHTML=News ; outerText=News; tagName=A; id=gb_426; class=gbzt; href=https://news.google.com/?tab=wn} @{innerHTML=Gmail; innerText=Gmail; outerHTML=Gmail; outerText=Gmail; tagName=A; id=gb_23; class=gbzt; href=https://mail.google.com/mail/?tab=wm} @{innerHTML=Drive; innerText=Drive; outerHTML=Drive ; outerText=Drive; tagName=A; id=gb_49; class=gbzt; href=https://drive.google.com/?tab=wo} @{innerHTML=More more; innerText=More; outerHTML=More ; outerText=More; tagName=A; aria-haspopup=true; id=gbztm; class=gbgt; aria-owns=gbd; href=https://www.google.com.tr/intl/tr/about/products?tab=wh} @{innerHTML=Calendar; innerText=Calendar; outerHTML=Calendar; outerText=Calendar; tagName=A; id=gb_24; class=gbmt; href=https://calendar.google.com/calendar?tab=wc} @{innerHTML=Translation; innerText=Translation; outerHTML=Translation; outerText=Translation; tagName=A; id=gb_51; class=gbmt; href=https://translate.google.com.tr/?hl=tr&tab=wT} @{innerHTML=Books; innerText=Books; outerHTML=Books; outerText=Books; tagName=A; id=gb_10; class=gbmt; href=https://books.google.com.tr/?hl=tr&tab=wp} @{innerHTML=Shopping; innerText=Shopping; outerHTML=Shopping; outerText=Shopping; tagName=A; id=gb_6; class=gbmt; href=https://www.google.com.tr/shopping?hl=tr&source=og&tab=wf} @{innerHTML=Blogger; innerText=Blogger; outerHTML=Blogger; outerText=Blogger; tagName=A; id=gb_30; class=gbmt; href=https://www.blogger.com/?tab=wj} @{innerHTML=Finance; innerText=Finance; outerHTML=Finance; outerText=Finance; tagName=A; id=gb_27; class=gbmt; href=https://www.google.com/finance?tab=we} @{innerHTML=Photos; innerText=Photos; outerHTML=Photos; outerText=Photos; tagName=A; id=gb_31; class=gbmt; href=https://photos.google.com/?tab=wq&pageId=none} @{innerHTML=Documents; innerText=Documents; outerHTML=Documents; outerText=Documents; tagName=A; id=gb_25; class=gbmt; href=https://docs.google.com/document/?usp=docs_alc} @{innerHTML=Even more »; innerText=Even more »; outerHTML=Even more »; outerText=Even more »; tagName=A; class=gbmt; href=https://www.google.com.tr/intl/tr/about/products?tab=wh} @{innerHTML=< SPAN id=gbi4s1>Sign in; innerText=Sign in; outerHTML= Sign in; outerText=Sign in; tagName=A; onclick=gbar.logger.il(9,{l:'i'}); id=gb_70; class=gbgt; href=https://accounts.google.com/ServiceLogin?hl=tr&passive=true&continue=https://www.google.com/&ec=GAZAAQ; target=_top} @{innerHTML=; innerText=; outerHTML=; outerText=; tagName=A; aria-haspopup=true; id=gbg5; title=Options; class=gbgt; aria-owns=gbd5; href=http://www.google.com.tr/preferences?hl=tr} @{innerHTML=Search settings; innerText=Search settings; outerHTML=Search settings; outerText=Search settings; tagName=A; class=gbmt; href=/preferences?hl=tr} @{innerHTML=Web History; innerText=Web History; outerHTML=Web History; outerText=Web History; tagName=A; class=gbmt; href=http://www.google.com.tr/history/optout?hl=tr} @{innerHTML=Advanced search; innerText=Advanced search; outerHTML=Advanced search; outerText=Advanced search; tagName=A; href=/advanced_search?hl=tr&authuser=0} @{innerHTML=Advertisement; innerText=Advertisement; outerHTML=Ad; outerText=Advertisement; tagName=A; href=/intl/tr/ads/} @{innerHTML=Business Solutions; innerText=Business Solutions; outerHTML=Business Solutions; outerText=Business Solutions; tagName=A; href=http://www.google.com.tr/intl/tr/services/} @{innerHTML=About Google; innerText=About Google; outerHTML=About Google; outerText=About Google; tagName=A; href=/intl/tr/about.html} @{innerHTML=Google.com.tr; innerText=Google.com.tr; outerHTML=Google.com. tr; outerText=Google.com.tr; tagName=A; href=https://www.google.com/setprefdomain?prefdom=TR&prev=https://www.google.com.tr/&sig=K_EgsQi_VqHUnTVfTtru1YxCgPIis%3D} @{innerHTML=Privacy; innerText=Privacy; outerHTML=Privacy; outerText=Privacy; tagName=A; href=/intl/tr/policies/privacy/} @{innerHTML=Terms; innerText=Terms; outerHTML=Terms; outerText=Terms; tagName=A; href=/intl/tr/policies/terms/}
Aslında çoğu zaman çıktının tamamına değil, https://play.google.com/?hl=tr&tab=w8 gibi bağlantı bölümüne ihtiyacımız var. Bu durumda ihtiyacımız olan verileri çıkarmak için hâlâ yapmamız gereken işler var.
Veri Çıkarma
HTML içeriğini ayrıştırdıktan sonra ilgilendiğiniz verileri çıkarabilirsiniz. Bu işlem için genellikle belirli bir etiket, sınıf veya kimlik bulup bu öğenin `innerText` veya `innerHTML` özelliklerini okumamız gerekir. Şimdi yukarıdaki örneğimize geri dönelim ve Bağlantıların ve Görsellerin yalnızca işimize yarayacak kısımlarını kaldırmaya çalışalım.
Linklerin Powershell nesnesinde saklandığını ve bu nesnenin innerHTML, innerTEXT, externalHTML, externalTEXT, tagName ve href gibi özelliklere sahip olduğunu görüyorsunuz . Sadece href özelliğinin değerine ihtiyacımız var .
Benzer şekilde Görüntüler için , src özelliğini kullanmanız yeterli olacaktır .
$response=Invoke-WebRequest -Uri "https://google.com" -Method 'GET' Write-Host "---------------------------------------------- ------------------" Write-Host "Links" Write-Host "---------------------------------------------- ------------------" $response.Links.href Write-Host "---------------------------------------------- ------------------" Write-Host "Images" Write-Host "---------------------------------------------- ------------------" $response.Images.src
Aradığımız şey Content’te ise , yani BasicHtmlWebResponseObject nesnesi üzerinde tanımlanan özel alanlardan birinde tutulmuyorsa ne yapacağız ?
Bu durumda yapmamız gereken şey Content içerisinde ihtiyacımız olanı aramaktır . Aşağıdaki örnekte yanıtın İçerik alanında bulunan span etiketlerinin içeriğini arıyoruz :
$response=Invoke-WebRequest -Uri "https://google.com" -Method 'GET' $response -match '' $matches
Çalıştırdığımızda REGEX ile yanıtta span etiketlerini arıyoruz ve bulursak içeriğini yazıyoruz.
Dikkatinizi çeken bir şey var mı? Hiçbir aşamada $matches adında bir değişken tanımlamadık ancak $response -match ile yaptığımız aramanın sonuçlarını bu değişken üzerinden elde ettik.
Bunun nedeni $matches değişkeninin otomatik bir değişken olmasıdır. Bu değişken , çağrılmadan hemen önce yapılan -match ve -notmatch normal ifade aramalarının sonuçlarını otomatik olarak saklar .
Artık bir web sayfasının başlık, resim, bağlantı gibi içeriklerine ulaşabiliyor, tüm bu veriler içerisinde arama yapabiliyor ve istediğimizi kolaylıkla bulabiliyoruz. Artık verileri temizlemeye ve saklamaya geçebiliriz.
PowerShell ve Web Scraping – 2
Verileri Hassaslaştırma ve Depolama
Çıkarılan veriler genellikle hassaslaştırılır (örneğin, gereksiz boşlukların veya özel karakterlerin kaldırılması) ve uygun şekilde saklanır (örneğin, bir CSV dosyasına veya veritabanına yazılır).
Örneğe devam edelim, önceki dersteki örnekte cevaptaki Bağlantıları listeledik. Şimdi tüm linkleri değil sadece FQDN kısımlarını ayrıştıralım ve elde ettiğimiz sonuçları bir dosyaya yazalım.
Başlamak için öncelikle href özelliğindeki URL’lerden FQDN’leri çıkarmamız gerekecek. Bu yüzden https://accounts.google.com/ServiceLogin?hl=tr&passive=true gibi bir veriden account.google.com bölümünü almamız gerekiyor .
Bunu yapmanın birçok yolu vardır; örneğin regex kullanarak yapabiliriz ya da string fonksiyonları ile “ https:// ” ile “ / ” arasındaki kısmı elde edebiliriz.
Öncelikle .NET’in Uri veri tipini kullanacağız ve ardından Uri nesnesinin Host özelliğine erişerek FQDN’leri alacağız :
$response=Invoke-WebRequest -Uri "https://google.com" -Method 'GET' foreach ($link in $response.links.href) { ([System.Uri]$link).Host }
Invoke-WebRequest cmdlet’inden gelen verileri $response değişkenine atıyoruz . Ardından, $response değişkenindeki her bağlantının her bir href değerini $link değişkenine atayacağımız bir döngü başlatın .
Bu döngüde gelen her $link değerini bir Uri nesnesine dönüştürüyoruz [System.Uri]$link ile .
Bir Uri Nesnesi şuna benzer:
Bu listede OriginalString özelliği $link değişkenindeki URL’yi taşıyor ve bu stringi Uri türüne cast ettiğimizde tıpkı yukarıdaki ekran görüntüsünde gördüğümüz gibi diğer alanlar da hesaplanarak bir HashTable oluşturuluyor. İhtiyacımız olan şey bunların arasında Host özelliğidir.
Bu değeri aşağıdaki kullanımla ekrana yazıyoruz:
… ([System.Uri]$link).Host …
Aynı örneği REGEX ile yapalım:
Bu sefer öncelikle $links adında bir dizi oluşturuyoruz . $Response nesnesindeki Links Hashtable’daki her URL’yi -match işleviyle bir REGEX modeli aracılığıyla iletiyoruz ve sonuçları az önce oluşturduğumuz $links dizisine ekliyoruz. Son olarak $links dizisinin içeriğini ekrana yazdırıyoruz.
Not: Oluşturduğumuz $links ile $Response nesnesinin Links özelliğinin farklı olduğunu lütfen unutmayın .
$response=Invoke-WebRequest -Uri "https://google.com" -Method 'GET' $links = @() foreach ($link in $response.links.href) { $link -match '(?<=\/\/)[^\/]+(?=\/)' $links += ($matches[0]) } $links
Tamam artık elimizdeki verileri bir dosyaya yazabiliriz. Bunun için daha önce incelediğimiz ConvertTo-Csv, ConvertTo-Xml, ConvertTo-Html, ConvertTo-Json cmdlet’lerini kullanarak uygun bir formata dönüştürebilir ve sonucu bir dosyaya yazabiliriz:
$response=Invoke-WebRequest -Uri "https://google.com" -Method 'GET' $links = @() foreach ($link in $response.links.href) { $link -match '(?<=\/\/)[^\/]+(?=\/)' $links += ($matches[0]) } $links | ConvertTo-Json | Set-Content -Path "links.json"
Örneğimizde çıktımızı JSON formatına dönüştürdük ve ardından links.json dosyasına kaydettik. Sonuç dosyamız aşağıdaki gibidir:
Örneğimizde elde ettiğimiz verileri JSON formatında bir metin dosyasına yazdık. Uygulamanızın ihtiyacına göre bu verileri uzak konumdaki bir disk alanına yazmak, örneğin FTP yoluyla göndermek veya bir veritabanına yazmak da mümkündür.
Invoke-RestMethod
Günümüz yazılım dünyasında web servislerinin önemli bir kısmı REST API’leri kullanılarak çalışmaktadır.
Invoke-RestMethod temel olarak Invoke-WebRequest’e benzer mantıkla çalışan bir cmdlet’tir. Farkı ise REST API Servislerine yönelik işlemleri kolaylaştıran farklı destek seçenekleri sunmasıdır.
Bu desteklerden en önemlisi Invoke-RestMethod’un yanıtı otomatik olarak ayrıştırmasıdır. Örneğin JSON verilerini otomatik olarak PowerShell nesnelerine dönüştürür. Daha iyi anlamak için bir örnekle inceleyelim:
$response = Invoke-WebRequest -Uri "https://my-json-server.typicode.com/typicode/demo/comments" $response Write-Host "---------------------------------------------- --" $response = Invoke-RestMethod -Uri "https://my-json-server.typicode.com/typicode/demo/comments" $response Write-Host "---------------------------------------------- --" $response = Invoke-RestMethod -Uri "https://my-json-server.typicode.com/typicode/demo/comments" $response.body
Betiğimizde öncelikle klasik Invoke-WebRequest Metodu ile aynı URL’yi çağırıyoruz. Daha sonra Invoke-RestMethod cmdlet’i ile çağırıyoruz ve yanıt nesnesinin tamamını ekrana yazıyoruz. Son olarak Invoke-RestMethod ile aynı URL’yi çağırıyoruz ve ekrana gelen yanıtta sadece “body” alanlarını yazıyoruz.
Çıktımız şu şekilde:
Gördüğünüz gibi GET İsteğinden REST Hizmetine dönen JSON Format yanıtını herhangi bir ekstra işleme gerek kalmadan PowerShellObject olarak kullanabildik.
Invoke-RestMethod ile kullanabileceğiniz parametrelerin listesi aşağıdaki gibidir:
Hata yönetimi
PowerShell’deki herhangi bir komut dosyasında olduğu gibi, web kazıma komut dosyalarında da hata işleme önemlidir. ‘Try/Catch’ blokları potansiyel hataları yakalamak ve bunları uygun şekilde ele almak için kullanılabilir.