nuclei
nuclei copied to clipboard
SNI override is being reused in cross domain redirects
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
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)
}
On Hold - Needs more investigation as there is no direct way to handle it correctly