Přeskočit na hlavní obsah

Prevence Server-Side Request Forgery (SSRF)

  • Zneužívá aplikaci k interakci s interní / externí sítí nebo počítačem
  • Jedním z důvodů je špatná manipulace s URL
    • Obrázek na externím serveru - uživatel zadá URL avatara, aby ji aplikace stáhla a použila
    • Webhook nebo callback URL
    • Interní požadavky na interakci s jinou služnou pro obsluhu konkrétní funkce
  • Neomezuje se pouze na HTTP (může jít o jakoukoliv cestu file://, phar://)
  • Pokud je aplikace zranitelná vůči XXE, pak ji lze zneužít k SSRF
  • Příklad provedení útoku

Příklad SSRF flow

Případy

Aplikace posílá požadavky pouze identifikovaným a důvěryhodným aplikacím

  • Někdy aplikace potřebuje provést požadavek na jinou aplikaci, aby provedla určitou úlohu
  • V závislosti na situaci je pro fungování vyžadován vstup uživatele

Příklad

  • Aplikace přijímá a používá osobní údaje uživatele k vytvoření profilu
  • Tato aplikace komunikuje s interním HR systémem
  • Uživatel nemá přímý přístup do HR systému, ale provolá se tam prostřednictvím zadání jeho osobních údajů
  • Pokud je aplikace zranitelná na SSRF, uživatel ji může využít jako zprostředkovatele přístupu k HR systému

Zabezpečení

Aplikační vrstva

  1. Validujte vstupy
    1. Zajistěte, že vstup dodrží očekávaný formát
    2. Zkontrolujte, že je adresa na seznamu povolených a důvěryhodných adres v případě zadání IP / URL adresy
      1. Identifikujte verzi IP adresy (v4, v6)
      2. Striktně porovnejte řetězec (malá, velká písměna)
      3. Nepřijímejte od uživatele kompletní URL (přijměte pouze IP nebo název domény)
    3. Zkontrolujte doménová jména
      1. Ověřte, zda je doména validní (pomocí knihoven)
      2. Ověřte, zda doména spadá do seznamu povolených a důvěryhodných adres
      3. Zajistěte překlad DNS interním serverem jako první (pro domény ve vaší organizaci)
      4. Monitorujte seznam povolených domém
  2. Použijte seznam povolených položek (allow list) při validaci
  3. Vypněte podporu následovaného přesměrování (follow redirect) na klientovi, abyste zabránili obcházení validace vstupu
// Regex validation for a data having a simple format
if (pattern.matches("[a-zA-Z0-9\\s\\-]{1,50}", userInput)) {
// Continue the processing because the input data is valid
} else {
// Stop the processing and reject the request
}
  1. Použijte uvedený regex pro validaci doménového jména
domain_names = ["owasp.org","owasp-test.org","doc-test.owasp.org","doc.owasp.org",
"<script>alert(1)</script>","<script>alert(1)</script>.owasp.org"]
domain_names.each { |domain_name|
if ( domain_name =~ /^(((?!-))(xn--|_{1,1})?[a-z0-9-]{0,61}[a-z0-9]{1,1}\.)*(xn--)?([a-z0-9][a-z0-9\-]{0,60}|[a-z0-9-]{1,30}\.[a-z]{2,})$/ )
puts "[i] #{domain_name} is VALID"
else
puts "[!] #{domain_name} is INVALID"
end
}
$ ruby test.rb
[i] owasp.org is VALID
[i] owasp-test.org is VALID
[i] doc-test.owasp.org is VALID
[i] doc.owasp.org is VALID
[!] <script>alert(1)</script> is INVALID
[!] <script>alert(1)</script>.owasp.org is INVALID
  • DNS monitoring příklad
# Dependencies: pip install ipaddress dnspython
import ipaddress
import dns.resolver

# Configure the allow list to check
DOMAINS_ALLOWLIST = ["owasp.org", "labslinux"]

# Configure the DNS resolver to use for all DNS queries
DNS_RESOLVER = dns.resolver.Resolver()
DNS_RESOLVER.nameservers = ["1.1.1.1"]

def verify_dns_records(domain, records, type):
"""
Verify if one of the DNS records resolve to a non public IP address.
Return a boolean indicating if any error has been detected.
"""
error_detected = False
if records is not None:
for record in records:
value = record.to_text().strip()
try:
ip = ipaddress.ip_address(value)
# See https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Address.is_global
if not ip.is_global:
print("[!] DNS record type '%s' for domain name '%s' resolve to
a non public IP address '%s'!" % (type, domain, value))
error_detected = True
except ValueError:
error_detected = True
print("[!] '%s' is not valid IP address!" % value)
return error_detected

def check():
"""
Perform the check of the allow list of domains.
Return a boolean indicating if any error has been detected.
"""
error_detected = False
for domain in DOMAINS_ALLOWLIST:
# Get the IPs of the current domain
# See https://en.wikipedia.org/wiki/List_of_DNS_record_types
try:
# A = IPv4 address record
ip_v4_records = DNS_RESOLVER.query(domain, "A")
except Exception as e:
ip_v4_records = None
print("[i] Cannot get A record for domain '%s': %s\n" % (domain,e))
try:
# AAAA = IPv6 address record
ip_v6_records = DNS_RESOLVER.query(domain, "AAAA")
except Exception as e:
ip_v6_records = None
print("[i] Cannot get AAAA record for domain '%s': %s\n" % (domain,e))
# Verify the IPs obtained
if verify_dns_records(domain, ip_v4_records, "A")
or verify_dns_records(domain, ip_v6_records, "AAAA"):
error_detected = True
return error_detected

if __name__== "__main__":
if check():
exit(1)
else:
exit(0)

Síťová vrstva

  • Cílem je zabránit provádění libovolných volání aplikace
  1. Využijte síťovou segregaci nebo definujte oprávnění toků na firewallu

Příklad předcházení SSRF pomocí firewallu

Aplikace posílá požadavky na libovolnou adresu

  • Uživatel může ovládat URL externího zdroje (webhooks)
  • Nelze použít seznam povolených adres (většinou není předem znám)

Zabezpečení

Aplikační vrstva

  1. Validujte vstupy
    1. Ověřte, zda jde o veřejnou adresu nebo doménové jméno
    2. Ověřte, zda je protokol v seznamu povolených protokolů
    3. Povolte pouze určitou množinu znaků pro token ([a-z]{1,10}) a ověřte ho
    4. Odesílejte pouze POST požadavky
    5. Vypněte přesměrování na klientovi
  2. Vygenerujte náhodný token, který má být předán volajícím
  3. Přijímejte pouze POST požadavky

AWS IMDSv2

  • V cloudu se SSRF používá k přístupu a krádeži credentials z metadatových služeb (AWS Instance Metadata Service, Azure Instance Metadata Service, GCP metadata server)
  • IMDSv2 je mechanismus ochrany pro AWS, který zmírňuje SSRF dopady

Semgrep

  • Command-line nástroj pro offline statickou analýzu
  • Předpřipravená nebo vlastní pravidla

Kam dál