Geçenlerde sunucu tarafındaki isteklerin imzasını doğrulayan bir servisle karşılaştım. Bu, her istek için kullanıcının tarayıcıdan gönderdiği bir değeri kontrol eden küçük bir çevrimiçi kumarhaneydi. Kumarhanede ne yapıyor olursanız olun: bahis oynamak veya para yatırmak, her istekteki ek bir parametre, görünüşte rastgele karakterlerden oluşan bir diziden oluşan "sign" değeriydi. Bu olmadan bir istek göndermek imkansızdı - site bir hata döndürdü ve kendi özel isteklerimi göndermemi engelledi.
Bu değer olmasaydı, o anda siteden ayrılırdım ve bir daha asla düşünmezdim. Ancak, tüm ihtimallere rağmen, beni heyecanlandıran şey hızlı kar duygusu değil, kumarhanenin bana kusursuzluğuyla verdiği araştırma ilgisi ve meydan okumaydı.
Geliştiricilerin bu parametreyi eklerken akıllarında ne varsa, bana göre bu zaman kaybıydı. Sonuçta imzanın kendisi istemci tarafında üretilir ve istemci tarafındaki herhangi bir eylem tersine mühendisliğe tabi tutulabilir.
Bu yazımda şunları nasıl başardığımı anlatacağım:
Bu makale, güvenli projeler yapmakla ilgilenen bir geliştiriciyseniz değerli zamanınızdan nasıl tasarruf edeceğinizi ve işe yaramaz çözümleri nasıl reddedeceğinizi öğretecektir. Ve bir pentester iseniz, bu makaleyi okuduktan sonra, hata ayıklama ve güvenliğin İsviçre Çakısı için kendi uzantılarınızı programlama konusunda bazı yararlı dersler öğrenebilirsiniz. Kısacası, herkes artı taraftadır.
Şimdi asıl konuya gelelim.
Yani hizmet, klasik oyunlardan oluşan bir diziye sahip çevrimiçi bir kumarhanedir:
Sunucuyla etkileşim tamamen HTTP istekleri temelinde çalışır. Seçtiğiniz oyun ne olursa olsun, sunucuya yapılan her POST isteği imzalanmalıdır - aksi takdirde sunucu bir hata üretir. Bu oyunların her birindeki imzalama istekleri aynı prensipte çalışır - aynı işi iki kez yapmak zorunda kalmamak için araştırmak üzere yalnızca bir oyun alırım.
Ve Dragon Dungeon adında bir oyun oynayacağım.
Bu oyunun özü, bir şövalye rolünde kaledeki kapıları sırayla seçmektir. Her kapının ardında bir hazine veya bir ejderha gizlidir. Oyuncu bir kapının ardında bir ejderhayla karşılaşırsa, oyun durur ve para kaybeder. Hazine bulunursa - başlangıç bahsinin miktarı artar ve oyun, oyuncu kazançları alana, kaybedene veya tüm seviyeleri geçene kadar devam eder.
Oyuna başlamadan önce oyuncunun bahis miktarını ve ejderha sayısını belirtmesi gerekmektedir.
Toplam olarak 10 sayısını giriyorum, bir ejderha bırakıyorum ve gönderilecek isteğe bakıyorum. Bu herhangi bir tarayıcıdaki geliştirici araçlarından yapılabilir, Chromium'da Ağ sekmesi bundan sorumludur.
Burada ayrıca isteğin /srv/api/v1/dungeon
uç noktasına gönderildiğini görebilirsiniz.
Yük sekmesi istek gövdesini JSON biçiminde görüntüler
İlk iki parametre aşikar - bunları kullanıcı arayüzünden seçtim; sonuncusu ise tahmin edebileceğiniz gibi timestamp
veya 1 Ocak 1970'ten bu yana geçen zamandır, tipik Javascript hassasiyeti olan milisaniyelerle.
Bu, çözülmemiş bir parametre bırakır ve - ve bu imzanın kendisidir. Nasıl oluşturulduğunu anlamak için Kaynaklar sekmesine gidiyorum - bu yer, tarayıcının yüklediği hizmetin tüm kaynaklarını içerir. Sitenin istemci kısmının tüm mantığından sorumlu olan Javascript dahil.
Bu kodu anlamak o kadar kolay değil - küçültülmüş. Her şeyi gizlemeyi deneyebilirsiniz - ancak bu çok zaman alacak uzun ve sıkıcı bir süreç (kaynak kodu miktarını göz önünde bulundurarak), bunu yapmaya hazır değilim.
İkinci ve daha basit seçenek, bir anahtar sözcükle kodun gerekli kısmını bulmak ve hata ayıklayıcıyı kullanmaktır. Ben bunu yapacağım çünkü tüm sitenin nasıl çalıştığını bilmeme gerek yok, sadece imzanın nasıl oluşturulduğunu bilmem gerekiyor.
Yani, kodun üretilmesinden sorumlu olan kısmını bulmak için, CTRL+SHIFT+F
tuş kombinasyonunu kullanarak tüm kaynaklar arasında bir arama açabilir ve istekte gönderilen sign
anahtarına bir değer atanmasını arayabilirsiniz.
Neyse ki sadece bir eşleşme var, bu da doğru yolda olduğumu gösteriyor.
Bir eşleşmeye tıklarsanız, imzanın kendisinin oluşturulduğu kod bölümüne ulaşabilirsiniz. Kod daha önce olduğu gibi gizlenmiştir, bu yüzden okunması hala zordur.
Kod satırının karşısına bir kesme noktası koydum, sayfayı yeniledim ve "ejderhalar"da yeni bir teklif verdim - şimdi komut dosyası tam olarak imza oluşumu anında çalışmasını durdurdu ve bazı değişkenlerin durumunu görebiliyorsunuz.
Çağrılan fonksiyon bir harften oluşuyor, değişkenler de öyle - ama sorun yok. Konsola gidip her birinin değerlerini görüntüleyebilirsiniz. Durum daha da netleşmeye başlıyor.
Çıktı olarak verdiğim ilk değer, bir fonksiyon olan H
değişkeninin değeridir. Konsoldan üzerine tıklayıp kodda bildirildiği yere gidebilirsiniz, aşağıda listelenmiştir.
Bu, bir ipucu gördüğüm oldukça büyük bir kod parçası - SHA256. Bu bir karma algoritması. Ayrıca, fonksiyona iki parametrenin geçirildiğini görebilirsiniz, bu da bunun sadece SHA256 değil, gizli bir HMAC SHA256 olabileceğine işaret ediyor.
Muhtemelen buraya geçirilen değişkenler (konsol çıktısı da):
10;1;6693a87bbd94061678473bfb;1732817300080;gRdVWfmU-YR_RCuSkWFLCUTly_GZfDx3KEM8
- doğrudan HMAC SHA256 işleminin uygulandığı değer.31754cff-be0f-446f-9067-4cd827ba8707
gizli bir sabit olarak işlev gören statik bir sabittir
Bundan emin olmak için fonksiyonu çağırıyorum ve varsayılan imzayı alıyorum
Şimdi HMAC SHA256'yı sayan siteye gidiyorum ve oraya değerleri geçiriyorum.
Ve bunu, teklifi verdiğimde bana gönderilen taleple karşılaştırıyorum.
Sonuç aynı, yani tahminlerim doğru çıktı - gerçekten de statik bir gizli anahtarla HMAC SHA256 kullanıyor, bu gizli anahtara oran, ejderha sayısı ve makalenin ilerleyen kısımlarında size anlatacağım bazı diğer parametrelerle özel olarak oluşturulmuş bir dize geçiriliyor.
Algoritma oldukça basit ve anlaşılır. Ancak yine de yeterli değil - eğer bir çalışma projesinin hedefi güvenlik açıklarını bulmak olsaydı, Burp Suite kullanarak kendi sorgularımı nasıl göndereceğimi öğrenmem gerekirdi.
Ve bunun kesinlikle otomasyona ihtiyacı var, şimdi bundan bahsedeceğim.
İmza oluşturma algoritmasını çözdüm. Şimdi, istek gönderirken gereksiz şeyleri soyutlamak için otomatik olarak nasıl oluşturulacağını öğrenme zamanı.
ZAP, Caido, Burp Suite ve diğer pentest araçlarını kullanarak istek gönderebilirsiniz. Bu makale Burp Suite'e odaklanacak, çünkü onu en kullanıcı dostu ve neredeyse mükemmel buluyorum. Community Edition resmi siteden ücretsiz olarak indirilebilir , tüm deneyler için yeterlidir.
Kutudan çıktığı haliyle Burp Suite, HMAC SHA256'yı nasıl üreteceğini bilmez. Bu nedenle, bunu yapmak için Burp Suite'in işlevselliğini tamamlayan uzantıları kullanabilirsiniz.
Uzantılar hem topluluk üyeleri hem de geliştiricilerin kendileri tarafından oluşturulur. Bunlar yerleşik ücretsiz BApp Store, Github veya diğer kaynak kodu depoları aracılığıyla dağıtılır.
İki yol var:
Her birinin kendine göre artıları ve eksileri var, ikisini de göstereceğim.
Hazır uzantılı yöntem en kolay olanıdır. Bunu BApp Store'dan indirmek ve özelliklerini kullanarak sign
parametresi için bir değer üretmektir.
Kullandığım eklentinin adı Hackvertor . XML benzeri sözdizimini kullanmanıza olanak tanır, böylece çeşitli verileri dinamik olarak kodlayabilir/kodunu çözebilir, şifreleyebilir/şifresini çözebilir ve karma işlemi yapabilirsiniz.
Burp'u kurmak için şunlara ihtiyacınız var:
Uzantılar sekmesine gidin
Aramaya Hackvertor yazın
Listede bulunan uzantıyı seçin
Yükle'ye tıklayın
Kurulduktan sonra, Burp'ta aynı adı taşıyan bir sekme görünecektir. Oraya gidip uzantının yeteneklerini ve her biri birbiriyle birleştirilebilen kullanılabilir etiket sayısını değerlendirebilirsiniz.
Örnek vermek gerekirse, <@aes_encrypt('supersecret12356','AES/ECB/PKCS5PADDING')>MySuperSecretText<@/aes_encrypt>
etiketini kullanarak bir şeyi simetrik AES ile şifreleyebilirsiniz.
Sır ve algoritma parantez içindedir ve etiketlerin arasında şifrelenecek metnin kendisi bulunur. Herhangi bir etiket Repeater, Intruder ve diğer yerleşik Burp Suite araçlarında kullanılabilir.
Hackvertor eklentisinin yardımıyla bir imzanın etiket düzeyinde nasıl oluşturulacağını açıklayabilirsiniz. Bunu gerçek bir istek örneği üzerinde yapacağım.
Yani, Dragon Dungeon'da bir bahis yapıyorum, bu makalenin başında yakaladığım aynı isteği Intercept Proxy ile yakalıyorum ve düzenleyebilmek ve yeniden gönderebilmek için Repeater'a yüklüyorum.
Şimdi ae04afe621864f569022347f1d1adcaa3f11bebec2116d49c4539ae1d2c825fc
değeri yerine, Hackvertor tarafından sağlanan etiketleri kullanarak HMAC SHA256 üreten algoritmayı değiştirmemiz gerekiyor.
Формула генерации у меня получилась следующая <@hmac_sha256('31754cff-be0f-446f-9067-4cd827ba8707')>10;1;6693a87bbd94061678473bfb;<@timestamp/>000;MDWpmNV9-j8tKbk-evbVLtwMsMjKwQy5YEs4<@/hmac_sha256>
.
Tüm parametreleri göz önünde bulundurun:
10
- bahis miktarı1
- ejderha sayısı6693a87bbd94061678473bfb
- MongoDB veritabanından benzersiz kullanıcı kimliği, tarayıcıdan imzayı analiz ederken gördüm, ancak o zaman bunun hakkında yazmadım. Burp Suite'teki sorguların içeriklerini arayarak bulabildim, /srv/api/v1/profile/me
uç nokta sorgusundan geri geliyor.
<@timestamp/>000
- zaman damgası oluşturma, son üç sıfır zamanı milisaniyelere indirgerMDWpmNV9-j8tKbk-evbVLtwMsMjKwQy5YEs4
- /srv/api/v1/csrf
uç noktasından döndürülen ve her istekte X-Xsrf-Token
başlığında değiştirilen CSRF belirteci.<@hmac_sha256('31754cff-be0f-446f-9067-4cd827ba8707')>
ve <@/hmac_sha256>
- sabit 31754cff-be0f-446f-9067-4cd827ba8707
sırrını kullanarak ikame edilmiş değerden HMAC SHA256 üretmek için açılış ve kapanış etiketleri.
Dikkat edilmesi gereken önemli nokta: parametreler ;
ile birbirine sıkı bir sırayla bağlanmalıdır, - aksi takdirde imza yanlış bir şekilde oluşturulacaktır - bu ekran görüntüsünde olduğu gibi, oranı ve ejderha sayısını değiştirdim
İşte bütün sihir burada yatıyor.
Şimdi parametreleri doğru sırayla belirttiğim ve her şeyin başarılı olduğu ve oyunun başladığı bilgisini aldığım doğru bir sorgu yapıyorum - bu, Hackvertor'ın bir formül yerine bir imza ürettiği, bunu sorguya koyduğu ve her şeyin çalıştığı anlamına geliyor.
Ancak bu yöntemin önemli bir dezavantajı vardır - manuel işten tamamen kurtulamazsınız. JSON'daki ejderhaların oranını veya sayısını her değiştirdiğinizde, bunları eşleştirmek için imzanın kendisinde değiştirmeniz gerekir.
Ayrıca Proxy sekmesinden Intruder veya Repeater'a yeni bir istek gönderdiğinizde formülü yeniden yazmanız gerekiyor ki bu da farklı test durumları için birçok sekmeye ihtiyaç duyduğunuzda çok ama çok sakıncalı.
Bu formül, diğer parametrelerin kullanıldığı diğer sorgularda da başarısız olacaktır.
Bu dezavantajların üstesinden gelmek için kendi eklentimi yazmaya karar verdim.
Burp Suite için eklentileri Java ve Python'da yazabilirsiniz. Daha basit ve daha görsel olduğu için ikinci programlama dilini kullanacağım. Ancak önceden kendinizi hazırlamanız gerekir: önce resmi web sitesinden Jython Standalone'ı indirmeniz ve ardından Burp Suite ayarlarında indirilen dosyanın yolunu bulmanız gerekir.
Daha sonra kaynak kodunun kendisini ve *.py
uzantısını içeren bir dosya oluşturmanız gerekiyor.
Temel mantığı tanımlayan bir bloğum zaten var, içeriği şöyle:
Her şey sezgisel olarak basit ve anlaşılırdır:
getActionName
- bu yöntem, uzantı tarafından gerçekleştirilecek eylemin adını döndürür. Uzantının kendisi, herhangi bir isteğe esnek bir şekilde uygulanabilen bir Oturum İşleme Kuralı ekler, ancak buna daha sonra değineceğiz. Bu adın, uzantının adından farklı olabileceğini ve arayüzden seçilebileceğini bilmek önemlidir.performAction
- seçili isteklere uygulanacak olan kuralın mantığı burada açıklanacaktır
Her iki yöntem de ISessionHandlingAction arayüzüne göre tanımlanmıştır.
Şimdi IBurpExtender arayüzüne geçelim. Uzantıyı yükledikten hemen sonra çalıştırılan ve çalışması için gereken tek gerekli method olan registerExtenderCallbacks
bildirir.
Temel yapılandırmanın yapıldığı yer burasıdır:
callbacks.setExtensionName(EXTENSION_NAME)
- geçerli uzantıyı oturumları işlemek için bir eylem olarak kaydedersys.stdout = callbacks.getStdout()
- standart çıktıyı (stdout) Burp Suite çıktı penceresine (“Uzantılar” paneli) yönlendirirself.stderr = PrintWriter(callbacks.getStdout(), True)
- hataların çıktısını almak için bir akış oluştururself.stdout.println(EXTENSION_NAME)
- Burp Suite'teki uzantının adını yazdırırself.callbacks = callbacks
- callbacks nesnesini self niteliği olarak kaydeder. Bu, Burp Suite API'sinin uzantı kodunun diğer bölümlerinde daha sonra kullanılması için gereklidir.self.helpers = callbacks.getHelpers()
- ayrıca uzantı çalışırken ihtiyaç duyulacak yararlı yöntemleri de alır
Ön hazırlıklar tamam, hepsi bu. Şimdi eklentiyi yükleyebilir ve çalıştığından emin olabilirsiniz. Bunu yapmak için Eklentiler sekmesine gidin ve Ekle'ye tıklayın.
Görünen pencerede şunu belirtin:
Ve İleri’ye tıklayın.
Kaynak kod dosyası düzgün bir şekilde biçimlendirilmişse, hiçbir hata oluşmamalı ve Çıktı sekmesi uzantının adını gösterecektir. Bu, her şeyin düzgün çalıştığı anlamına gelir.
Eklenti yükleniyor ve çalışıyor - ancak yüklenen tek şey mantıksız bir sarmalayıcıydı, şimdi isteği imzalamak için doğrudan koda ihtiyacım var. Zaten yazdım ve aşağıdaki ekran görüntüsünde gösteriliyor.
Tüm eklentinin çalışma şekli, istek sunucuya gönderilmeden önce eklentim tarafından değiştirilecek olmasıdır.
Öncelikle uzantının yakaladığı isteği alıyorum ve onun gövdesinden oranı ve ejderha sayısını alıyorum
json_body = json.loads(message_body) amount_currency = json_body["amountCurrency"] dragons = json_body["dragons"]
Sonra, geçerli Zaman Damgasını okurum ve karşılık gelen başlıktan CSRF belirtecini alırım
currentTime = str(time.time()).split('.')[0]+'100' xcsrf_token = None for header in headers: if header.startswith("X-Xsrf-Token"): xcsrf_token = header.split(":")[1].strip()
Daha sonra, isteğin kendisi HMAC SHA256 kullanılarak imzalanır
hmac_sign = hmac_sha256(key, message=";".join([str(amount_currency), str(dragons), user_id, currentTime, xcsrf_token]))
Fonksiyonun kendisi ve sırrı ve kullanıcı kimliğini belirten sabitler en üstte önceden bildirilmiştir
def hmac_sha256(key, message): return hmac.new( key.encode("utf-8"), message.encode("utf-8"), hashlib.sha256 ).hexdigest() key = "434528cb-662f-484d-bda9-1f080b861392" user_id = "zex2q6cyc4ba3gvkyex5f80m"
Daha sonra değerler istek gövdesine yazılır ve JSON'a dönüştürülür
json_body["sign"] = hmac_sign json_body["t"] = currentTime message_body = json.dumps(json_body)
Son adım, imzalanmış ve değiştirilmiş bir istek oluşturmak ve bunu göndermektir.
httpRequest = self.helpers.buildHttpMessage(get_final_headers, message_body) baseRequestResponse.setRequest(httpRequest)
Hepsi bu kadar, kaynak kodu yazıldı. Şimdi Burp Suite'te eklentiyi yeniden yükleyebilirsiniz (bu her betik değişikliğinden sonra yapılmalıdır) ve her şeyin çalıştığından emin olabilirsiniz.
Ancak önce istekleri işlemek için yeni bir kural eklemeniz gerekir. Bunu yapmak için Ayarlar'a, Oturumlar bölümüne gidin. Burada istekler gönderilirken tetiklenen tüm farklı kuralları bulacaksınız.
Belirli istek türlerinde tetiklenen bir uzantı eklemek için Ekle'ye tıklayın.
Açılan pencerede her şeyi olduğu gibi bırakıp Kural eylemleri kısmında Ekle'yi seçiyorum
Bir açılır liste görünecektir. İçinde, Burp uzantısını çağır'ı seçin.
Ve istekler gönderilirken çağrılacak uzantıyı belirtin. Benim bir tane var ve o da Burp Extension.
Uzantıyı seçtikten sonra Tamam'a tıklıyorum. Ve Kapsam sekmesine gidiyorum, burada şunları belirtiyorum:
Araç kapsamı - Tekrarlayıcı (uzantı, Tekrarlayıcı aracılığıyla manuel olarak istek gönderdiğimde tetiklenmelidir)
URL Kapsamı - Tüm URL'leri dahil et (böylece gönderdiğim tüm isteklerde çalışır).
Aşağıdaki ekran görüntüsündeki gibi çalışması gerekir.
Tamam'a tıkladıktan sonra uzantı kuralı genel listede göründü.
Son olarak, her şeyi eylem halinde test edebilirsiniz! Şimdi bazı sorguları değiştirebilir ve imzanın dinamik olarak nasıl güncelleneceğini görebilirsiniz. Ve sorgu başarısız olsa bile, bunun nedeni imzada bir sorun olması değil, negatif bir oran seçmem olacaktır (sadece para harcamak istemiyorum 😀). Uzantı kendi kendine çalışıyor ve imza doğru şekilde üretiliyor.
Her şey güzel ama üç sorun var:
Bunu çözmek için, herhangi bir üçüncü parti requests
yerine, yerleşik Burp Suite kütüphanesi tarafından yapılabilen iki ek istek eklememiz gerekiyor.
Bunu yapmak için, sorguları daha kullanışlı hale getirmek için bazı standart mantıkları sardım. Burp'un standart yöntemleri aracılığıyla, sorgularla etkileşim pleintext'te yapılır.
def makeRequest(self, method="GET", path="/", headers=None, body=None): first_line = method + " " + path + " HTTP/1.1" headers[0] = first_line if body is None: body = "{}" http_message = self.helpers.buildHttpMessage(headers, body) return self.callbacks.makeHttpRequest(self.request_host, self.request_port, True, http_message)
Ve ihtiyacım olan verileri çıkaran iki fonksiyon ekledim, CSRF token ve UserID.
def get_csrf_token(self, headers): response = self.makeRequest("GET", "/srv/api/v1/csrf", headers) message = self.helpers.analyzeRequest(response) raw_headers = str(message.getHeaders()) match = re.search(r'XSRF-TOKEN=([a-zA-Z0-9_-]+)', raw_headers) return match.group(1) def get_user_id(self, headers): raw_response = self.makeRequest("POST", "/srv/api/v1/profile/me", headers) response = self.helpers.bytesToString(raw_response) match = re.search(r'"_id":"([a-f0-9]{24})"', response) return match.group(1)
Ve gönderilen başlıklardaki belirtecin kendisini güncelleyerek
def update_csrf(self, headers, token): for i, header in enumerate(headers): if header.startswith("X-Xsrf-Token:"): headers[i] = "X-Xsrf-Token: " + token return headers
İmza fonksiyonu şu şekilde görünüyor. Burada, istekte gönderilen tüm özel parametreleri aldığımı, bunların sonuna standart user_id
, currentTime
, csrf_token
eklediğimi ve hepsini ayırıcı olarak ;
kullanarak birlikte imzaladığımı belirtmek önemlidir.
def sign_body(self, json_body, user_id, currentTime, csrf_token): values = [] for key, value in json_body.items(): if key == "sign": break values.append(str(value)) values.extend([str(user_id), str(currentTime), str(csrf_token)]) return hmac_sha256(hmac_secret, message=";".join(values))
Ana metin birkaç satıra indirildi:
OrderedDict
kullandığımı belirtmek önemlidir. csrf_token = self.get_csrf_token(headers) final_headers = self.update_csrf(final_headers, csrf_token) user_id = self.get_user_id(headers) currentTime = str(time.time()).split('.')[0]+'100' json_body = json.loads(message_body, object_pairs_hook=OrderedDict) sign = self.sign_body(json_body, user_id, currentTime, csrf_token) json_body["sign"] = sign json_body["t"] = currentTime message_body = json.dumps(json_body) httpRequest = self.helpers.buildHttpMessage(final_headers, message_body) baseRequestResponse.setRequest(httpRequest)
Emin olmak için bir ekran görüntüsü
Şimdi, özel parametrelerin 2 yerine 3 olduğu başka bir oyuna giderseniz ve bir istek gönderirseniz, bunun başarılı bir şekilde gönderileceğini görebilirsiniz. Bu, uzantımın artık evrensel olduğu ve tüm istekler için çalıştığı anlamına gelir.
Hesap yenileme talebi gönderme örneği
Uzantılar, Burp Suite'in ayrılmaz bir parçasıdır. Genellikle hizmetler, sizden başka kimsenin önceden yazmayacağı özel işlevler uygular. Bu yüzden yalnızca hazır uzantıları indirmek değil, aynı zamanda kendi uzantınızı yazmak da önemlidir; bu makalede size öğretmeye çalıştığım şey de budur.
Şimdilik bu kadar, kendinizi geliştirin ve hasta olmayın.
Eklentinin kaynak koduna bağlantı: *tık* .