nuclei icon indicating copy to clipboard operation
nuclei copied to clipboard

SNI override is being reused in cross domain redirects

Open brenocss opened this issue 2 years ago • 1 comments

Nuclei version:

2.7.3

Current Behavior:

  • SNI override is set globally, when we follow redirects for cross domain/hosts the SNI is stil being overwritten.
URLS First Request Redirect
HOST https://redirect-localhost.free.beeceptor.com https://localhost
SNI redirect-localhost.free.beeceptor.com redirect-localhost.free.beeceptor.com

Expected Behavior:

  • SNI override should only be applied for ip's from the nuclei target (redirect-localhost.free.beeceptor.com)
URLS First Request Redirect
HOST https://redirect-localhost.free.beeceptor.com https://localhost
SNI redirect-localhost.free.beeceptor.com localhost

Steps To Reproduce:

nuclei -t sniproblem.yaml -u 'https://redirect-localhost.free.beeceptor.com' -debug -duc -v -fr
id: basic-raw-http-example

info:
  name: Test RAW GET Template
  author: pdteam
  severity: info
        # https://redirect-localhost.free.beeceptor.com
        # 302 redirect for https://localhost
        # should use localhost as sni
requests:
  - raw:
      - |
        @tls-sni:redirect-localhost.free.beeceptor.com
        GET / HTTP/1.1
        Host: redirect-localhost.free.beeceptor.com
    matchers:
      - type: word
        words:
          - "test-ok"
  • Simple python server that show sni received
from os import dup
import socket
import ssl

HOST = "127.0.0.1"
PORT = 443

ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.minimum_version = ssl.TLSVersion.TLSv1_3
ctx.maximium_version = ssl.TLSVersion.TLSv1_3
ctx.check_hostname = False
ctx.load_cert_chain('test.pem', 'test.key')
ctx.sni_callback = lambda _a,sni,_b: print("SNI: " + sni)

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

server = ctx.wrap_socket(
    server, server_side=True)

if __name__ == "__main__":
    server.bind((HOST, PORT))
    server.listen(0)

    while True:
        connection, client_address = server.accept()
        while True:
            data = connection.recv(1024)
            if not data:
                break
            print(f"Received: {data.decode('utf-8')}")
            connection.sendall('''HTTP/1.1 200 OK
date: Wed, 13 Jul 2022 17:08:03 GMT
content-type: application/json
access-control-allow-origin: *
vary: Accept-Encoding

{ "status": "test-ok!"}'''.encode('utf-8'))
            connection.close()
            break

image

Anything else:

  • Here we globally override the SNI. https://github.com/projectdiscovery/nuclei/blob/a31bca524d42bfdec9985e96c65aa1fc943d2b8e/v2/pkg/protocols/headless/engine/http_client.go#L31-L33 it overrides SNI for redirects for cross domain/hosts requests as well.
  • We could use DialContext using the target ip and the url with the host overwritten. this way redirects for cross domain wil not override SNI.
package main

import (
	"context"
	"crypto/tls"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"
)

func main() {
	// target: https://localhost/test
	// sni_overwrite: google.com
	// url_string: https://google.com/test
	// target_ip : 127.0.0.1
	target_url := "https://redirect-localhost.free.beeceptor.com"
	parsed_url, _ := url.Parse(target_url)
	target_ip, _ := net.LookupIP(parsed_url.Host)

	sni_overwrite_host := "redirect-localhost.free.beeceptor.com"
	parsed_url.Host = sni_overwrite_host
	response, err := sni_overwrite(parsed_url.String(), target_ip[0].String())

	if err != nil {
		panic(err)
	}
	defer response.Body.Close()
	body, _ := ioutil.ReadAll(response.Body)

	fmt.Println(string(body))
}
func sni_overwrite(url_with_sni string, target_ip string) (response *http.Response, err error) {
	parsed_url, _ := url.Parse(url_with_sni)

	dialer := &net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
		DualStack: true,
	}

	client := http.Client{
		Transport: &http.Transport{
			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
				if strings.Split(addr, ":")[0] == parsed_url.Host {
					addr = target_ip + addr[strings.LastIndex(addr, ":"):]
				}
				return dialer.DialContext(ctx, network, addr)
			},
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
		},
	}

	req, err := http.NewRequest("GET", url_with_sni, nil)
	if err != nil {
		panic(err)
	}
	// set user agent
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0")

	return client.Do(req)
}

brenocss avatar Jul 13 '22 22:07 brenocss

On Hold - Needs more investigation as there is no direct way to handle it correctly

Mzack9999 avatar Aug 10 '22 13:08 Mzack9999