Whitelist nie zawsze jest sposobem. CachedView – Hack The Box.

I made a service for people to cache their favourite websites, come and check it out! But don’t try anything funny, after a recent incident we implemented military grade IP based restrictions to keep the hackers at bay…

Pod podanym ip znajduje się prosty formularz, po którego użyciu, ładuje pierwszy widok po wejściu na podany link. Co ciekawe, pole posiada walidację, która nie pozwala nam na wpisanie URI z brakującym protokołem. Spróbujmy podać URI sekuraka:)

Super, działa. Sprawdźmy co znajduje się w kodzie źródłowym aplikacji.

def is_from_localhost(func):
    @functools.wraps(func)
    def check_ip(*args, **kwargs):
        if request.remote_addr != '127.0.0.1' or request.referrer:
            return abort(403)
        return func(*args, **kwargs)
    return check_ip

Znajdujemy w niej funkcję sprawdzającą, czy podana strona jest localhostem.

@web.route('/flag')
@is_from_localhost
def flag():e
    return send_file('flag.png')

Ponadto, funkcja jest następnie użyta jako dekorator w metodzie definiującej endpoint „/flag” przetrzymujący najpewniej plik, którego poszukujemy. Wiemy więc, że zależy nam, aby się do niego dostać. Zgadza to się z podpowiedzią, którą daje nam autor htb w opisie wyzwania. Niestety, w tym samym pliku możemy natrafić na walidację przyjmowanego URI.

def cache_web(url):
    scheme = urlparse(url).scheme
    domain = urlparse(url).hostname

    if not domain or not scheme:
        return flash(f'Malformed url {url}', 'danger')
        
    if scheme not in ['http', 'https']:
        return flash('Invalid scheme', 'danger')

    def ip2long(ip_addr):
        return struct.unpack('!L', socket.inet_aton(ip_addr))[0]
    
    def is_inner_ipaddress(ip):
        ip = ip2long(ip)
        return ip2long('127.0.0.0') >> 24 == ip >> 24 or \
                ip2long('10.0.0.0') >> 24 == ip >> 24 or \
                ip2long('172.16.0.0') >> 20 == ip >> 20 or \
                ip2long('192.168.0.0') >> 16 == ip >> 16 or \
                ip2long('0.0.0.0') >> 24 == ip >> 24
    
    if is_inner_ipaddress(socket.gethostbyname(domain)):
        return flash('IP not allowed', 'danger')
    
    return serve_screenshot_from(url, domain)

Wiemy z niego, że nie możemy użyć żadnego z wskazanych zapisów localhostu.

Po researchu związanym z podatnościami DNS, natrafiamy na artykuł wyjaśniający podatność DNS rebinder. Na czym ona polega?

Na początku atakujący rejestruje domenę i ustawia jej TLL na bardzo niską wartość. Po wejściu na stronę przeglądarka wysyła zapytanie DNS, a następnie wykonywane są w tle zapytania HTTP na adres atakującego. Jak wiemy, TTL ma niewielką wartość ( 1 – 2 sekundy), w związku z czym w pewnym momencie zapytania przestają być wykonywane, ponieważ resolver zauważył, że wpis DNS jest nieaktualny. Przeglądarka ponownie wysyła zapytanie DNS, a serwer DNS odpowiada już innym adresem. W taki sposób kolejne żądania są wysyłane pod inny adres (najczęściej lokalny).

Do stworzenia payloadu w postaci URI użyjemy aplikacji rebinder. Jako ip A podaliśmy swoje własne. W efekcie orzymamy poniższy payload.

XXfb8a0a.7f000001.rbndr.us

Po ustawieniu protokołu oraz endpointu dostajemy flagę.

Źródła

https://danielmiessler.com/blog/dns-rebinding-explained/
https://geleta.eu/2019/my-first-ssrf-using-dns-rebinfing/
https://lock.cmpxchg8b.com/rebinder.html

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.