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ří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
- Validujte vstupy
- Zajistěte, že vstup dodrží očekávaný formát
- Zkontrolujte, že je adresa na seznamu povolených a důvěryhodných adres v případě zadání IP / URL adresy
- Identifikujte verzi IP adresy (v4, v6)
- Striktně porovnejte řetězec (malá, velká písměna)
- Nepřijímejte od uživatele kompletní URL (přijměte pouze IP nebo název domény)
- Zkontrolujte doménová jména
- Ověřte, zda je doména validní (pomocí knihoven)
- Ověřte, zda doména spadá do seznamu povolených a důvěryhodných adres
- Zajistěte překlad DNS interním serverem jako první (pro domény ve vaší organizaci)
- Monitorujte seznam povolených domém
- Použijte seznam povolených položek (allow list) při validaci
- 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
}
- 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
- Využijte síťovou segregaci nebo definujte oprávnění toků na 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
- Validujte vstupy
- Ověřte, zda jde o veřejnou adresu nebo doménové jméno
- Ověřte, zda je protokol v seznamu povolených protokolů
- Povolte pouze určitou množinu znaků pro token (
[a-z]{1,10}
) a ověřte ho - Odesílejte pouze POST požadavky
- Vypněte přesměrování na klientovi
- Vygenerujte náhodný token, který má být předán volajícím
- 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