awk ile Apache ve Nginx Log Dosyalarından Coğrafi IP Dağılımı ve Bot Trafik Tespiti
Geceleri production sunucusunda bir şeyler fısıldıyor. Log dosyaları. Kimse bakmıyor, kimse dinlemiyor, ama orada her şey yazıyor: kim geldi, nereden geldi, ne istedi, ne kadar sürdü. Ben yıllarca bu fısıltıları görmezden geldim, ta ki bir müşterinin sitesi DDoS saldırısına uğrayana kadar. O geceden sonra log analizi benim için bir hobby değil, bir reflex haline geldi. Ve bu işin en hızlı, en pratik aracı hala awk.
Log Dosyalarını Tanımak: Nereden Başlayalım?
Önce ne’yle çalıştığımızı anlayalım. Apache Combined Log Format şöyle görünüyor:
192.168.1.105 - - [12/Nov/2024:14:23:01 +0300] "GET /index.php HTTP/1.1" 200 2048 "https://google.com" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
Nginx default log formatı da neredeyse aynı. Alanları sayalım: IP (alan 1), ident (2), auth (3), tarih (4), saat (5), metod+URL+protokol (6), status kodu (7), byte (8), referer (9), user-agent (10).
awk bu yapıyı seviyor çünkü whitespace’e göre alan ayırıyor. Ama dikkat: tarih ve saat köşeli parantez içinde, [12/Nov/2024:14:23:01 tek alan oluyor. Bu küçük detay çok kişiyi yanıltır.
Nginx log yolları genellikle şu konumlarda:
/var/log/nginx/access.log/var/log/nginx/access.log.1(rotate edilmiş)/var/log/nginx/sitead_access.log(virtualhost bazlı)
Apache için:
/var/log/apache2/access.log(Debian/Ubuntu)/var/log/httpd/access_log(RHEL/CentOS)
IP Bazlı Trafik Analizi: Temel Komutlar
En basit soru: “Bugün en çok kim istek attı?”
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
Bu komut size en yüksek istek sayısına sahip 20 IP’yi listeler. Ama ham çıktı pek konuşkan değil. Şöyle bir şey görürsünüz:
4521 203.0.113.45
3218 198.51.100.12
1205 192.0.2.88
4521 istek atan bir IP’yi görünce alarm zilleri çalmalı. Ama bu bot mu, gerçek kullanıcı mı, CDN arkasındaki bir şey mi? Devam edelim.
Zaman damgasıyla birlikte analiz etmek için, belirli bir saatteki trafiği çekelim:
awk '$4 ~ /[12/Nov/2024:14/' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
Bu komut, 12 Kasım 2024 saat 14:xx aralığındaki tüm istekleri filtreliyor. Bir DDoS inceliyorsanız saatleri daraltmak kritik.
Status Kodlarına Göre Bot Tespiti
Bot trafiğinin en belirgin imzalarından biri status kod dağılımı. Meşru kullanıcılar ağırlıklı olarak 200, 301, 302 görür. Botlar ise tarıyor, keşfediyor, brute force yapıyor ve bunların çoğu 404, 403, 401 döner.
awk '{print $1, $7}' /var/log/nginx/access.log | awk '$2 == 403 || $2 == 404 || $2 == 401 {print $1}' | sort | uniq -c | sort -rn | head -20
Burada iki awk zincirliyoruz. İlki IP ve status’u alıyor, ikincisi filtreli IP’leri sayıyor. Bunu tek awk‘a da sığdırabiliriz:
awk '$7 ~ /^(401|403|404)$/ {count[$1]++} END {for (ip in count) print count[ip], ip}' /var/log/nginx/access.log | sort -rn | head -20
Bu çok daha verimli: associative array kullanarak tek geçişte saydık. Büyük log dosyalarında (5GB+) bu fark ciddi oluyor.
Bir IP’nin hem toplam istek sayısını hem de hata oranını aynı anda görmek için:
awk '{
total[$1]++
if ($7 ~ /^(4[0-9][0-9]|5[0-9][0-9])$/) errors[$1]++
} END {
for (ip in total) {
err = (errors[ip] ? errors[ip] : 0)
ratio = (err / total[ip]) * 100
if (ratio > 50 && total[ip] > 100)
printf "%s total=%d errors=%d ratio=%.1f%%n", ip, total[ip], err, ratio
}
}' /var/log/nginx/access.log | sort -t= -k4 -rn
Bu script, 100’den fazla istek atan ve bunların %50’sinden fazlası hata dönen IP’leri listeliyor. Bu pattern’i bir kez görünce vazgeçemezsiniz.
Coğrafi IP Dağılımı: maxminddb ve mmdbinspect
Şimdi asıl meseleye: bu IP’ler nereden geliyor? Linux’ta geoiplookup veya mmdbinspect kullanabiliriz.
Önce kurulum:
# Ubuntu/Debian
apt-get install geoip-bin
# RHEL/CentOS
yum install GeoIP GeoIP-data
# MaxMind GeoLite2 DB indir (ücretsiz ama kayıt gerekli)
wget https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=YOURKEY&suffix=tar.gz
geoiplookup ile awk’ı birleştirip ülke bazlı dağılım çıkaralım:
awk '{print $1}' /var/log/nginx/access.log | sort -u | while read ip; do
country=$(geoiplookup "$ip" 2>/dev/null | awk -F': ' '{print $2}' | cut -d',' -f1)
echo "$country"
done | sort | uniq -c | sort -rn | head -15
Uyarı: Bu yöntem yavaş, her IP için ayrı process açıyor. Büyük log dosyaları için önce unique IP’leri bir dosyaya yazın:
awk '{print $1}' /var/log/nginx/access.log | sort -u > unique_ips.txt
wc -l unique_ips.txt # kaç unique IP var?
Eğer unique IP sayısı 10.000’in altındaysa while döngüsü makul sürede biter. Üstündeyse mmdbinspect veya Python ile MaxMind kütüphanesini kullanmak daha akıllıca.
Ülke dağılımını log’un kendisine gömmek için, eğer Nginx’e GeoIP modülü kurduysanız log formatını değiştirebilirsiniz. Ama bu konuya girmeyeceğim, şimdilik elimizdekilerle çalışalım.
User-Agent Analizi: Bot İmzaları Avcılığı
User-agent alanı ($9 veya sonrası, köşeli parantez sorununu aşana kadar) bot tespitinin altın madeni. İyi botlar kendini tanıtır (Googlebot, Bingbot). Kötü botlar ya sahte browser kimliği kullanır ya da hiç user-agent vermez.
awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -30
Burada field separator olarak " (tırnak işareti) kullandım. Combined log formatında 6. tırnak arası user-agent’tır. Bu çok daha güvenilir bir yöntem.
Bilinen bot imzalarını filtreleyelim:
awk -F'"' '
$6 ~ /[Bb]ot|[Cc]rawler|[Ss]pider|[Ss]craper|python-requests|curl|wget|libwww|Java/|Go-http-client/ {
botcount[$6]++
botip[$1]++
}
END {
print "=== Bot User Agents ==="
for (ua in botcount) printf "%dt%sn", botcount[ua], ua
}
' /var/log/nginx/access.log | sort -rn | head -20
Boş user-agent gönderenleri bulmak da önemli:
awk -F'"' '$6 == "" || $6 == "-" {print $0}' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
Boş user-agent gönderen bir IP büyük ihtimalle script veya bot. Özellikle 500+ istek atmışsa direkt blocklist’e alın.
Rate Limiting Analizi: Saniyede Kaç İstek?
Bir IP’nin belirli bir zaman diliminde ne kadar istek attığını ölçmek, bot tespitinin en güvenilir yöntemi. İnsan bir sayfayı saniyede 3-4 kez yükleyemez.
awk '{
# Tarih-saat alanını parse et: [12/Nov/2024:14:23:01
gsub(/[/, "", $4)
split($4, dt, ":")
# dt[1]=tarih, dt[2]=saat, dt[3]=dakika, dt[4]=saniye
timekey = dt[1] ":" dt[2] ":" dt[3] # dakika bazında gruplama
count[$1][timekey]++
maxcount[$1] = (count[$1][timekey] > maxcount[$1]) ? count[$1][timekey] : maxcount[$1]
} END {
for (ip in maxcount) {
if (maxcount[ip] > 60) # dakikada 60+ istek şüpheli
print maxcount[ip], ip
}
}' /var/log/nginx/access.log | sort -rn | head -20
Bu script biraz karmaşık ama amacı net: her IP’nin her dakikada attığı maksimum istek sayısını buluyor. Dakikada 60 istek, yani saniyede 1 istek, meşru bir kullanıcı için bile yüksek bir bar.
Daha basit bir versiyon, belirli bir IP için dakika bazlı breakdown:
SUSPECT_IP="203.0.113.45"
grep "$SUSPECT_IP" /var/log/nginx/access.log |
awk '{gsub(/[/,"",$4); split($4,t,":"); print t[1]":"t[2]":"t[3]}' |
sort | uniq -c | sort -rn | head -20
Gerçek Dünya Senaryosu: Saldırı Sonrası Analiz
Diyelim ki gece 02:00’de sunucu CPU’su %100’e çıktı ve Nginx yanıt vermemeye başladı. Sabah geldiniz, analiz zamanı.
Önce hangi saatte ne kadar trafik geldi:
awk '{gsub(/[/,"",$4); split($4,t,":"); print t[2]}' /var/log/nginx/access.log |
sort | uniq -c
Saat başı istek sayısı grafiği çıkar, anomaliyi gözle görürsünüz.
Sonra o kritik saatteki en aktif IP’ler:
awk '$4 ~ /[12/Nov/2024:02/' /var/log/nginx/access.log |
awk '{print $1}' | sort | uniq -c | sort -rn | head -20
Bu IP’lerin ülkelerini öğrenin:
awk '$4 ~ /[12/Nov/2024:02/' /var/log/nginx/access.log |
awk '{print $1}' | sort -u |
xargs -I{} sh -c 'echo -n "{}: "; geoiplookup {} 2>/dev/null | head -1'
Ve bu IP’lerin hangi URL’lere saldırdığını görün:
ATTACK_IP="203.0.113.45"
grep "^$ATTACK_IP" /var/log/nginx/access.log |
awk -F'"' '{print $2}' | sort | uniq -c | sort -rn | head -20
Eğer hep aynı URL’yi çekiyorsa resource exhaustion saldırısı (tek endpoint’i yorma). Farklı URL’ler tarıyorsa scanner veya vulnerability probe.
Sürekli İzleme İçin Script
Tek seferlik analizden öte, bunu bir cron job veya izleme scripti haline getirelim:
#!/bin/bash
# /usr/local/bin/log_threat_check.sh
LOG="/var/log/nginx/access.log"
THRESHOLD_REQS=500 # son 10 dakikada max istek
THRESHOLD_ERROR=0.7 # %70 hata oranı
ALERT_EMAIL="[email protected]"
TMP=$(mktemp)
# Son 10 dakikanın logunu çek
START=$(date -d '10 minutes ago' '+%d/%b/%Y:%H:%M' 2>/dev/null ||
date -v-10M '+%d/%b/%Y:%H:%M')
awk -v start="$START" '
$4 > "["start {
total[$1]++
if ($7 ~ /^[45]/) errors[$1]++
}
END {
for (ip in total) {
err = (errors[ip] ? errors[ip] : 0)
ratio = err / total[ip]
if (total[ip] > '"$THRESHOLD_REQS"' || ratio > '"$THRESHOLD_ERROR"') {
printf "IP: %s | Requests: %d | Errors: %d | Error Rate: %.0f%%n",
ip, total[ip], err, ratio*100
}
}
}
' "$LOG" > "$TMP"
if [ -s "$TMP" ]; then
echo "=== Threat Detection Alert - $(date) ===" |
cat - "$TMP" |
mail -s "Log Threat Alert: $(hostname)" "$ALERT_EMAIL"
# Opsiyonel: otomatik block
# while read line; do
# ip=$(echo $line | awk '{print $2}')
# iptables -A INPUT -s "$ip" -j DROP
# done < "$TMP"
fi
rm "$TMP"
Bu scripti her 10 dakikada bir cron’a ekleyin:
*/10 * * * * /usr/local/bin/log_threat_check.sh
Rotated Log’larda Analiz: zcat ile Entegrasyon
Production’da loglar rotate olur. Birden fazla dosyayı analiz etmek için:
# Tüm access logları üzerinde (gzip dahil)
zcat /var/log/nginx/access.log.*.gz |
cat - /var/log/nginx/access.log |
awk '$7 ~ /^(401|403)$/ {print $1}' |
sort | uniq -c | sort -rn | head -20
zcat sıkıştırılmış dosyaları açıp stdout’a veriyor, cat ile güncel log’u da ekliyoruz, awk hepsini tek geçişte işliyor. Bu pattern son 7 günün verisini birkaç saniyede analiz eder.
Belirli bir tarih aralığı için:
zcat /var/log/nginx/access.log.{1,2,3}.gz 2>/dev/null |
awk '$4 >= "[10/Nov/2024" && $4 <= "[12/Nov/2024" {
country_req[$1]++
} END {
for (ip in country_req)
if (country_req[ip] > 100) print country_req[ip], ip
}' | sort -rn | head -30
IP Subnet Analizi: /24 Bloklarında Trafik
Bazen tek IP’ler değil, tüm bir subnet saldırıyor. Özellikle botnet trafiğinde bu çok yaygın.
awk '{
split($1, octets, ".")
subnet = octets[1]"."octets[2]"."octets[3]".0/24"
count[subnet]++
} END {
for (s in count)
if (count[s] > 200) print count[s], s
}' /var/log/nginx/access.log | sort -rn | head -20
Eğer belirli bir /24 bloğu 5000+ istek atmışsa, o bloğu tek tek IP yerine toplu olarak blocklist’e almak daha verimli:
# Şüpheli subnet'i bul ve iptables'a ekle
SUBNET="203.0.113.0/24"
iptables -A INPUT -s "$SUBNET" -j DROP
# ipset kullanıyorsanız çok daha verimli:
ipset add blacklist "$SUBNET"
Googlebot Doğrulama: Sahte Crawler Tespiti
Bir de şu var: “Googlebot” yazıyor ama gerçekten Google mu? Google’ın IP aralıkları belgelenmiş, kontrol edebilirsiniz.
awk -F'"' '$6 ~ /[Gg]ooglebot/' /var/log/nginx/access.log |
awk '{print $1}' | sort -u |
while read ip; do
# Reverse DNS kontrolü
rdns=$(host "$ip" 2>/dev/null | awk '{print $NF}')
if echo "$rdns" | grep -qE 'googlebot.com|google.com'; then
echo "LEGIT: $ip -> $rdns"
else
echo "FAKE: $ip -> $rdns"
fi
done
Sahte Googlebot yazan IP’ler genellikle aggressive scraper veya SEO tool’larıdır. Bunları block etmek güvenli ve genellikle önerilir.
Sonuç
awk log analizinde hala rakipsiz. Python veya Elasticsearch çözümleri var elbette, ama bir üretim sunucusunda SSH bağlantısı açık, log dosyası önünüzde, dakikalar içinde cevap lazım demek log dosyası demek awk demek. Yükleme yok, dependency yok, lisans yok.
Burada anlattığım komutların hiçbirini ezberlemenizi beklemiyorum. Ama şu üç pattern aklınızda kalsın:
awk '{count[$1]++} END {for (k in count) print count[k], k}': Herhangi bir alanı say ve gruplaawk -F'"' '{print $6}': User-agent’ı güvenilir şekilde çekawk '$7 ~ /^[45]/': Hata kodlarını filtrele
Bu üçünü birbirine zincirleyebilirseniz, log dosyası artık bir problem değil, bir kaynak haline gelir.
Son bir not: bu analizleri otomatize edin. Elle bakılan log’lar saldırıdan saatler sonra fark edilir. Script çalışıyor, threshold geçilince alarm geliyor, siz uyurken sistem kendini izliyor. İşte gerçek sysadmin konforu bu.
