NFsec Logo

Mini złodziej certyfikatów i kluczy SSL

07/02/2018 w Bezpieczeństwo, Pen Test Brak komentarzy.  (artykuł nr 652, ilość słów: 925)

B

łędy popełnia każdy z nas. Zazwyczaj są to nasze błędy “autorskie”, ale często są to też błędy powielające błędy innych. Te drugie zazwyczaj wynikają z ślepej wiary w ścieżki, którymi prowadzą nas inne osoby – mówiąc prościej – podążając za tutorialami / przewodnikami z sieci zakładamy, że ich autor nie popełnił błędów, a my właśnie nie kopiujemy ich dalej. Jeśli wpiszemy w dowolną wyszukiwarkę frazy typu: “nginx ssl tutorial“, “apache ssl tutorial“, “haproxy ssl tutorial” itd. to możemy odnaleźć pewne wzory, którymi rządzą się te przewodniki…

Zasada działania poniższego skryptu jest bardzo prosta. Wykonuje on kombinacje zmiennych prefix oraz suffix i pyta o nie wybrany serwer WWW np. server.(key|crt|pem), ca.(key|crt|pem) itd. Dodatkowymi zapytaniami jest kombinacja rozszerzeń: .key, .crt oraz .pem z adresem do którego kierujemy żądanie HTTP. Czyli serwer nfsec.pl zostanie odpytany o adres URL: nfsec.pl/nfsec.pl.(key|crt|pem):

#!/usr/bin/env python

import requests

headers = { 'User-Agent': 'SSL Security Check Experiment from nfsec.pl' }

prefix = ["server","myserver","privatekey","private","privkey","priv","key","apache",
          "nginx", "certificate","ca","ssl","vhost"]
suffix = ["key","crt","pem"]

variants = [(x,y) for x in prefix for y in suffix]
tieflist = []

for i in range(len(variants)):
  tieflist.append('.'.join(map(str,variants[i])))

with open("toscan.txt") as f:
  urls = f.readlines()
  urls = [x.strip() for x in urls]
  for x in urls:
      for y in ['.key','.crt','.pem']:
          try:
              r1 = requests.get('https://' + x + '/' + x + y, verify=False, timeout=5, 
                   headers=headers)
              print str(r1.status_code) + ',' + str(r1.url) + ',' + 
                    str(r1.headers['Content-Type'])
          except:
              pass
      for z in tieflist:
          try:
              r2 = requests.get('https://' + x + '/' + z, verify=False, timeout=5, 
                   headers=headers)
              print str(r2.status_code) + ',' + str(r2.url) + ',' + 
                    str(r2.headers['Content-Type'])
          except:
              pass
print "|| Done."

Dlaczego został wybrany taki układ adresów URL? Ponieważ są to najczęściej pojawiające się nazwy plików, pod którymi zapisywane są certyfikaty i klucze w wspomnianych tutorialach o instalacji SSL. Czy to są te wspomniane błędy przewodników? Oczywiście, że nie. Musimy jeszcze dodać do nich fakt, że osoby postępujące według ich zaleceń popełniają błąd w umiejscowieniu tych certyfikatów. W ten sposób istnieje szansa, że przeszukując odpowiednio duży zbiór adresów prędzej czy później trafimy na taką “anomalię”. Niestety Alexa nie udostępnia już miliona najczęściej odwiedzanych stron, ale możemy skorzystać z projektu Cisco Umbrella lub Majestic. Po przeskanowaniu 21% każdego z plików wyniki prezentują się następująco:

  • ca.crt – 27
  • ca.pem – 1
  • key.pem – 1
  • ssl.crt – 7
  • nginx.crt – 1
  • vhost.pem – 2
  • vhost.crt – 1
  • server.crt – 22
  • private.pem – 2
  • privkey.pem – 1
  • myserver.crt – 2
  • myserver.pem – 1
  • certificate.pem – 1
  • nazwa_strony.crt – 25
  • nazwa_strony.pem – 3

Mimo szczątkowej analizy całego zbioru (nasz skaner i tak został zauważony) możemy potwierdzić naszą tezę, że przewodniki publikowane w internecie mają realny wpływ na nazywanie plików z certyfikatami. W dodatku ich złe umiejscowienie (o ile sam certyfikat jest częścią eksponowaną to żadna część klucza prywatnego nie powinna być publicznie dostępna przez serwer web) powoduje, że narażamy na jawność szyfrowanie naszej transmisji z użytkownikami. Kilka trafień plików .pem zawierało zarówno certyfikat i klucz prywatny, co daje możliwość pełnego odszyfrowania ruchu w ataku MITM (ang. A man in the middle).

False positives i false negatives

W sieci trudno o przestrzeganie standardów. Dlatego skrypt dodatkowo zwracał Content-Type odpytywanych stron. Gdyby jego wyniki bez weryfikacji miały polegać tylko na kodach HTTP (200) to trafień statystycznie byłoby znacznie więcej dla każdego szukanego pliku. Wynika to z faktu, że bardzo wiele stron posiada mechanizm catch-all – nie zwraca już kodów 404 dla nieistniejących stron, a 200 tylko, że Content-Type jest wówczas ustawiony na text/html:

200,https://www.facebook.com/priv.key,text/html; charset=UTF-8

Wyświetla się nam wówczas strona główna lub mechanizm ładujący inne sugestie, co do szukanych treści (tutaj została wczytana strona użytkownika privkey z portalu Facebook). Skrypt zrzucał wszystkie wyniki do pliku raport.log, który został odfiltrowany z tego typu przypadków przez wyświetlenie tylko stron zwracających kod 200 oraz Content-Type application/x-x509-ca-cert:

grep x509 raport.log | grep '200,'

Kolejnym poziomem weryfikacji było sprawdzenie, czy strona taka rzeczywiście zwraca treść zawierającą frazy: “-----BEGIN” oraz “-----END“:

-----BEGIN CERTIFICATE-----
MIIDpDCCAw2gAwIBAgIBADANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCVVMx
...
-----END CERTIFICATE-----

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAySGLqimUXaSWnfSf+Rmab+SDG3I8a/HZZnR5cm8E/yxJO1Mj
...
-----END RSA PRIVATE KEY-----

ponieważ bardzo wiele stron zwracało prawidłowy Content-Type, ale już Content-Length był na poziomie 0 (najczęściej strony stojące za CloudFlare) lub kilku bajtów z np. wiadomością: /*m*/, czy kodem JavaScript.

Więcej informacji: Kilka słów o wdrożeniu SSL i TLS cz. I i II, Rekonesans przez listę certyfikatów, Wyciek informacji przez rozszerzenie Server Name Indication (SNI)

Kategorie K a t e g o r i e : Bezpieczeństwo, Pen Test

Tagi T a g i : , , , , , ,

Komentowanie tego wpisu jest zablokowane.