OAuth 2.0 Güvenlik Açıkları ve Korunma Yöntemleri
Modern web uygulamalarının neredeyse tamamı artık OAuth 2.0 kullanıyor. Kullanıcı girişleri, üçüncü parti entegrasyonlar, mobil uygulamalar… Hepsi bu protokole dayanıyor. Peki ya güvenlik? OAuth 2.0 iyi tasarlanmış bir protokol olsa da, yanlış implementasyon edildiğinde saldırganlar için adeta kırmızı halı sermiş oluyorsunuz. Bu yazıda gerçek dünya senaryolarıyla OAuth 2.0 güvenlik açıklarını ve bunlara karşı alınabilecek önlemleri ele alacağım.
OAuth 2.0 Nedir ve Neden Önemli?
OAuth 2.0, yetkilendirme için kullanılan açık bir standarttır. Kimlik doğrulama (authentication) ile yetkilendirme (authorization) kavramlarını birbirinden ayırt etmek burada kritik önem taşıyor. OAuth 2.0 temelde yetkilendirme için tasarlanmıştır; kimlik doğrulama için OpenID Connect (OIDC) katmanı üzerine eklenir.
Bir sysadmin olarak şunu net söyleyeyim: OAuth akışlarını tam anlamadan production ortamına almak, yangın çıkmadan önce sigortaları sökmek gibidir. Sorun er ya da geç ortaya çıkar.
Temel bileşenler şunlardır:
- Resource Owner: Kaynağa sahip olan kullanıcı
- Client: Kaynaklara erişmek isteyen uygulama
- Authorization Server: Token’ları veren sunucu
- Resource Server: Korunan kaynakları barındıran sunucu
Yaygın OAuth 2.0 Güvenlik Açıkları
1. CSRF Saldırıları ve State Parametresi Eksikliği
OAuth akışında state parametresi, Cross-Site Request Forgery (CSRF) saldırılarına karşı korunmak için zorunludur. Bu parametreyi kullanmayan pek çok uygulama görüyorum ve bu gerçekten kritik bir hata.
Saldırı senaryosu şöyle işler: Saldırgan, kurbanın tarayıcısında kendi OAuth akışını başlatır. Kurban farkında olmadan saldırganın hesabına bağlanır. Bu sayede saldırgan kurbanın oturumunu ele geçirebilir.
# Zayıf implementasyon - state parametresi yok
curl -X GET "https://auth.example.com/oauth/authorize?
client_id=myapp&
redirect_uri=https://myapp.com/callback&
response_type=code"
# Güvenli implementasyon - state parametresi ile
STATE=$(openssl rand -hex 32)
echo "Generated state: $STATE"
curl -X GET "https://auth.example.com/oauth/authorize?
client_id=myapp&
redirect_uri=https://myapp.com/callback&
response_type=code&
state=$STATE"
State parametresini oluştururken kriptografik olarak güvenli rastgele değerler kullanmak zorunludur. Oturum bazlı olmalı ve tek kullanımlık olacak şekilde tasarlanmalıdır.
2. Redirect URI Manipülasyonu
Bu, en sık rastladığım ve en tehlikeli açıklardan biri. Authorization server’ın redirect URI doğrulamasındaki zayıflıklar, saldırganların authorization code’u çalmasına olanak tanır.
# Saldırgan, redirect_uri parametresini manipüle eder
# Orijinal URI: https://legitimate-app.com/callback
# Saldırgan URI: https://evil.com/steal
curl -X GET "https://auth.example.com/oauth/authorize?
client_id=myapp&
redirect_uri=https://evil.com/steal&
response_type=code&
state=abc123"
# Nginx ile redirect URI whitelist kontrolü
# /etc/nginx/conf.d/oauth-proxy.conf
cat << 'EOF'
server {
listen 443 ssl;
location /oauth/authorize {
# Redirect URI whitelist kontrolü
if ($arg_redirect_uri !~ "^https://myapp.com/callback$") {
return 400 "Invalid redirect_uri";
}
proxy_pass http://auth-server;
}
}
EOF
Authorization server tarafında yapılması gereken kontroller:
- Redirect URI’yı tam eşleşme (exact match) ile doğrulayın
- Wildcard kullanımından kaçının
- Kayıt sırasında izin verilen URI’ları önceden tanımlayın
- Path traversal karakterlerini (/../) filtreleyin
3. Authorization Code Interception
PKCE (Proof Key for Code Exchange) kullanmayan uygulamalar, authorization code interception saldırılarına açıktır. Bu özellikle mobil uygulamalar için kritik bir sorundur.
# PKCE implementasyonu - code_verifier ve code_challenge oluşturma
# Code Verifier: 43-128 karakter arası kriptografik rastgele string
CODE_VERIFIER=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9-._~' | head -c 64)
echo "Code Verifier: $CODE_VERIFIER"
# Code Challenge: code_verifier'ın SHA256 hash'inin Base64URL encoding'i
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr -d '=' | tr '+/' '-_')
echo "Code Challenge: $CODE_CHALLENGE"
# Authorization isteği - PKCE ile
curl -X GET "https://auth.example.com/oauth/authorize?
client_id=myapp&
redirect_uri=https://myapp.com/callback&
response_type=code&
code_challenge=$CODE_CHALLENGE&
code_challenge_method=S256&
state=$(openssl rand -hex 32)"
# Token exchange sırasında code_verifier gönderme
AUTH_CODE="received_authorization_code_here"
curl -X POST "https://auth.example.com/oauth/token"
-H "Content-Type: application/x-www-form-urlencoded"
-d "grant_type=authorization_code"
-d "code=$AUTH_CODE"
-d "redirect_uri=https://myapp.com/callback"
-d "client_id=myapp"
-d "code_verifier=$CODE_VERIFIER"
4. Token Sızdırma ve Güvensiz Depolama
Token’ların nerede ve nasıl saklandığı kritik öneme sahiptir. LocalStorage’da saklanan access token’lar XSS saldırılarına karşı tamamen savunmasızdır.
# Redis ile güvenli token yönetimi
# Token'ları server-side session'da saklama örneği
# Redis'e token kaydetme (TTL ile)
redis-cli SET "session:abc123:access_token" "eyJhbGc..." EX 3600
redis-cli SET "session:abc123:refresh_token" "dGhpcyBp..." EX 86400
# Token doğrulama scripti
cat << 'SCRIPT'
#!/bin/bash
SESSION_ID=$1
TOKEN=$(redis-cli GET "session:${SESSION_ID}:access_token")
if [ -z "$TOKEN" ]; then
echo "Token bulunamadi veya suresi doldu"
exit 1
fi
# Token introspection endpoint'i ile doğrulama
RESPONSE=$(curl -s -X POST "https://auth.example.com/oauth/introspect"
-H "Authorization: Basic $(echo -n 'client_id:client_secret' | base64)"
-d "token=$TOKEN")
ACTIVE=$(echo $RESPONSE | jq -r '.active')
if [ "$ACTIVE" = "true" ]; then
echo "Token gecerli"
exit 0
else
echo "Token gecersiz"
redis-cli DEL "session:${SESSION_ID}:access_token"
exit 1
fi
SCRIPT
5. Implicit Flow Kullanımı
Implicit flow artık deprecated durumdadır ve kullanılmamalıdır. Token’ların URL fragment’ında döndürülmesi, browser history ve log’larda token sızdırılmasına yol açar.
# YANLIS - Implicit flow (kullanmayin)
curl -X GET "https://auth.example.com/oauth/authorize?
response_type=token&
client_id=myapp&
redirect_uri=https://myapp.com/callback"
# Bu akışta token URL'de görünür: https://myapp.com/callback#access_token=TOKEN
# DOGRU - Authorization Code + PKCE kullanin
curl -X GET "https://auth.example.com/oauth/authorize?
response_type=code&
client_id=myapp&
redirect_uri=https://myapp.com/callback&
code_challenge=$CODE_CHALLENGE&
code_challenge_method=S256&
state=$STATE"
Token Güvenliği ve Yönetimi
Access Token Ömrünü Kısaltın
Uzun ömürlü access token’lar, sızdırıldıklarında uzun süre kötüye kullanılabilir. Genel kural olarak access token’lar kısa ömürlü, refresh token’lar ise uzun ömürlü olmalıdır.
# Keycloak üzerinden token süre yapılandırması
# keycloak-config.json
cat << 'EOF'
{
"accessTokenLifespan": 300,
"accessTokenLifespanForImplicitFlow": 900,
"ssoSessionIdleTimeout": 1800,
"ssoSessionMaxLifespan": 36000,
"offlineSessionIdleTimeout": 2592000,
"clientSessionIdleTimeout": 0,
"clientSessionMaxLifespan": 0
}
EOF
# Keycloak realm konfigürasyonu uygulama
curl -X PUT "https://keycloak.example.com/admin/realms/myrealm"
-H "Authorization: Bearer $ADMIN_TOKEN"
-H "Content-Type: application/json"
-d @keycloak-config.json
Refresh Token Rotation
Refresh token rotation, bir refresh token kullanıldığında yenisinin verilmesi ve eskisinin geçersiz kılınması pratiğidir. Bu sayede token çalınması durumunda saldırgan yakalanabilir.
#!/bin/bash
# Refresh token rotation scripti
REFRESH_TOKEN=$1
CLIENT_ID="myapp"
CLIENT_SECRET="mysecret"
# Yeni token almak için refresh token kullan
RESPONSE=$(curl -s -X POST "https://auth.example.com/oauth/token"
-H "Content-Type: application/x-www-form-urlencoded"
-d "grant_type=refresh_token"
-d "refresh_token=$REFRESH_TOKEN"
-d "client_id=$CLIENT_ID"
-d "client_secret=$CLIENT_SECRET")
# Yanıtı parse et
NEW_ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.access_token')
NEW_REFRESH_TOKEN=$(echo $RESPONSE | jq -r '.refresh_token')
ERROR=$(echo $RESPONSE | jq -r '.error')
if [ "$ERROR" != "null" ]; then
echo "Token yenileme basarisiz: $ERROR"
# Kullanıcıyı logout yap
redis-cli DEL "session:$SESSION_ID:*"
exit 1
fi
# Eski refresh token'ı geçersiz kıl ve yenisini kaydet
redis-cli SET "session:$SESSION_ID:access_token" "$NEW_ACCESS_TOKEN" EX 300
redis-cli SET "session:$SESSION_ID:refresh_token" "$NEW_REFRESH_TOKEN" EX 86400
echo "Token basariyla yenilendi"
Token Blacklisting
Kullanıcı logout olduğunda veya şüpheli aktivite tespit edildiğinde token’ları geçersiz kılmak gerekir.
#!/bin/bash
# Token blacklist yönetimi
TOKEN=$1
ACTION=$2 # "blacklist" veya "check"
# Token'ın JTI (JWT ID) değerini al
JTI=$(echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | jq -r '.jti')
EXP=$(echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | jq -r '.exp')
if [ "$ACTION" = "blacklist" ]; then
NOW=$(date +%s)
TTL=$((EXP - NOW))
if [ $TTL -gt 0 ]; then
redis-cli SET "blacklist:$JTI" "1" EX $TTL
echo "Token blacklist'e eklendi. TTL: $TTL saniye"
else
echo "Token zaten suresi dolmus"
fi
elif [ "$ACTION" = "check" ]; then
BLACKLISTED=$(redis-cli EXISTS "blacklist:$JTI")
if [ "$BLACKLISTED" = "1" ]; then
echo "Token blacklist'te - erisim reddedildi"
exit 1
else
echo "Token temiz"
exit 0
fi
fi
Gerçek Dünya Senaryo: API Gateway’de OAuth Güvenliği
Kurumsal bir ortamda API gateway katmanında OAuth güvenliğini nasıl uygularsınız? İşte pratik bir yaklaşım:
# Kong API Gateway ile OAuth 2.0 plugin yapılandırması
# plugin-config.json
curl -X POST "http://kong-admin:8001/services/my-api/plugins"
-H "Content-Type: application/json"
-d '{
"name": "oauth2",
"config": {
"scopes": ["read", "write", "admin"],
"mandatory_scope": true,
"token_expiration": 300,
"enable_authorization_code": true,
"enable_client_credentials": true,
"enable_implicit_grant": false,
"enable_password_grant": false,
"accept_http_if_already_terminated": false,
"anonymous": null,
"global_credentials": false,
"auth_header_name": "Authorization",
"hide_credentials": true,
"provision_key": "'$(openssl rand -hex 32)'"
}
}'
# Rate limiting ekle - brute force koruması
curl -X POST "http://kong-admin:8001/services/my-api/plugins"
-H "Content-Type: application/json"
-d '{
"name": "rate-limiting",
"config": {
"minute": 60,
"hour": 1000,
"policy": "redis",
"redis_host": "redis",
"redis_port": 6379
}
}'
Monitoring ve Anomali Tespiti
OAuth güvenliğinin sadece teknik implementasyondan ibaret olmadığını vurgulamak istiyorum. İzleme ve anomali tespiti en az implementasyon kadar önemlidir.
#!/bin/bash
# OAuth log analizi ve anomali tespiti scripti
LOG_FILE="/var/log/oauth-server/access.log"
ALERT_EMAIL="[email protected]"
THRESHOLD_FAILED=10 # 1 dakikada maksimum başarısız deneme
# Son 1 dakikadaki başarısız token isteklerini say
FAILED_COUNT=$(grep "$(date -d '1 minute ago' '+%Y-%m-%dT%H:%M')" "$LOG_FILE" |
grep "error" |
grep "invalid_grant|invalid_client|access_denied" |
wc -l)
if [ $FAILED_COUNT -gt $THRESHOLD_FAILED ]; then
echo "UYARI: Son 1 dakikada $FAILED_COUNT basarisiz OAuth denemesi tespit edildi" |
mail -s "[GUVENLIK UYARISI] OAuth Brute Force Suphecisi" $ALERT_EMAIL
# Şüpheli IP'leri topla ve geçici olarak engelle
SUSPICIOUS_IPS=$(grep "$(date -d '1 minute ago' '+%Y-%m-%dT%H:%M')" "$LOG_FILE" |
grep "invalid_grant" |
awk '{print $1}' |
sort | uniq -c | sort -rn |
awk '$1 > 5 {print $2}')
for IP in $SUSPICIOUS_IPS; do
echo "Engelleniyor: $IP"
iptables -A INPUT -s $IP -j DROP
# 1 saat sonra kaldır
at now + 1 hour << CMD
iptables -D INPUT -s $IP -j DROP
CMD
done
fi
Scope Doğrulama ve En Az Yetki Prensibi
OAuth scope’ları, uygulamalara verilen izinleri sınırlandırmak için kullanılır. “Her şeye erişim” anlamına gelen geniş scope’lardan kaçınmak gerekir.
Pratikte sık gördüğüm yanlış uygulamalar:
- Tüm API’lere erişim için tek bir
apiscope’u tanımlamak - Scope kontrolünü resource server yerine client tarafında yapmak
- Scope’ları token içinde doğrulamamak
Doğru yaklaşım için scope hiyerarşisi şu şekilde tanımlanabilir:
- read:users: Kullanıcı bilgilerini okuma
- write:users: Kullanıcı bilgilerini güncelleme
- delete:users: Kullanıcı silme
- admin:users: Tüm kullanıcı işlemleri
- read:orders: Sipariş okuma
- write:orders: Sipariş oluşturma/güncelleme
# Resource server'da scope doğrulama
# validate-scope.sh
#!/bin/bash
TOKEN=$1
REQUIRED_SCOPE=$2
# Token'dan scope bilgisini al
TOKEN_SCOPES=$(echo $TOKEN | cut -d'.' -f2 |
base64 -d 2>/dev/null |
jq -r '.scope // ""')
# Gerekli scope'un mevcut olup olmadığını kontrol et
if echo "$TOKEN_SCOPES" | grep -qw "$REQUIRED_SCOPE"; then
echo "Scope dogrulama basarili: $REQUIRED_SCOPE"
exit 0
else
echo "Yetersiz scope. Gerekli: $REQUIRED_SCOPE, Mevcut: $TOKEN_SCOPES"
exit 1
fi
# Kullanım örneği:
# bash validate-scope.sh "$ACCESS_TOKEN" "write:users"
Client Credential Güvenliği
Server-to-server iletişimde kullanılan client credentials akışında, client_secret yönetimi kritik önem taşır.
Client secret yönetimi için dikkat edilmesi gerekenler:
- Rotasyon: Client secret’ları düzenli olarak (90 günde bir) rotate edin
- Depolama: Secret’ları asla source code’da tutmayın, vault kullanın
- Erişim Kontrolü: Hangi servisin hangi secret’a eriştiğini kayıt altına alın
- İzleme: Secret kullanımını loglayın ve anomalileri tespit edin
# HashiCorp Vault ile client secret yönetimi
# Secret kaydetme
vault kv put secret/oauth/myservice
client_id="myservice-client"
client_secret="$(openssl rand -hex 32)"
# Secret okuma ve kullanma
CLIENT_ID=$(vault kv get -field=client_id secret/oauth/myservice)
CLIENT_SECRET=$(vault kv get -field=client_secret secret/oauth/myservice)
# Client credentials token alma
TOKEN_RESPONSE=$(curl -s -X POST "https://auth.example.com/oauth/token"
-H "Content-Type: application/x-www-form-urlencoded"
--cert /etc/ssl/myservice.crt
--key /etc/ssl/myservice.key
-d "grant_type=client_credentials"
-d "client_id=$CLIENT_ID"
-d "client_secret=$CLIENT_SECRET"
-d "scope=read:data")
ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.access_token')
# Token'ı kullan
curl -s "https://api.example.com/data"
-H "Authorization: Bearer $ACCESS_TOKEN"
TLS ve Transport Güvenliği
OAuth üzerinden yapılan tüm iletişim zorunlu olarak HTTPS üzerinden gerçekleşmelidir. Bu basit görünse de production ortamlarında atlanabilen bir detay.
Kontrol edilmesi gereken noktalar:
- HSTS: HTTP Strict Transport Security başlığını etkinleştirin
- TLS Versiyonu: TLS 1.2 minimum, tercihen TLS 1.3 kullanın
- Sertifika Doğrulama: Client tarafında her zaman sertifika doğrulamasını açık tutun
- Certificate Pinning: Yüksek güvenlikli uygulamalarda certificate pinning uygulayın
# Nginx OAuth proxy için TLS yapılandırması
cat << 'EOF' >> /etc/nginx/conf.d/oauth-security.conf
server {
listen 443 ssl http2;
server_name auth.example.com;
ssl_certificate /etc/ssl/certs/auth.crt;
ssl_certificate_key /etc/ssl/private/auth.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_stapling on;
ssl_stapling_verify on;
# HSTS - 1 yil, subdomainler dahil
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Guvenlik basliklarini ekle
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header Content-Security-Policy "default-src 'self'" always;
location / {
proxy_pass http://oauth-server:8080;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
}
EOF
Güvenlik Denetimi Kontrol Listesi
OAuth 2.0 implementasyonunuzu düzenli olarak denetlemek için şu kontrolleri yapın:
- State parametresi tüm authorization code akışlarında kullanılıyor mu?
- PKCE tüm public client’larda (mobil, SPA) uygulanmış mı?
- Redirect URI whitelist tam eşleşme ile mi doğrulanıyor?
- Implicit flow devre dışı bırakılmış mı?
- Access token ömrü 5-15 dakika arasında mı tutulmuş?
- Refresh token rotation etkin mi?
- Token’lar güvenli bir şekilde saklanıyor mu (HttpOnly cookie veya server-side)?
- Token introspection endpoint’i korumalı mı?
- Scope doğrulaması resource server’da yapılıyor mu?
- Başarısız token isteklerine rate limiting uygulanmış mı?
- TLS 1.2+ zorunlu kılınmış mı?
- Client secret’lar vault’ta mı saklanıyor?
- Token blacklist mekanizması mevcut mu?
- Güvenlik logları merkezi olarak toplanıyor ve izleniyor mu?
Sonuç
OAuth 2.0 güçlü bir protokol ama gücünü doğru implementasyondan alıyor. Yanlış yapılandırılmış bir OAuth sistemi, güvenli sandığınız uygulamanızı tamamen açık hale getirebilir.
En önemli pratik çıkarımlar şunlardır: PKCE kullanın, implicit flow’u unutun, redirect URI’larınızı sıkı tutun, token ömürlerini kısaltın, her şeyi loglayın ve izleyin. Bu beş kural bile implementasyonunuzu ciddi ölçüde sağlamlaştırır.
Bir de şunu ekleyeyim: Güvenlik bir kez yapılan bir şey değil, sürekli devam eden bir süreçtir. OAuth implementasyonunuzu her büyük değişiklik öncesinde ve sonrasında gözden geçirin. Dependency’lerinizi takip edin, authorization library’lerinizi güncel tutun ve güvenlik bültenlerini düzenli okuyun.
Sormak istediğiniz bir şey varsa veya kurumunuza özgü bir OAuth senaryosunda takıldıysanız yorumlarda buluşalım.
