Skip to content

Commit e2253a8

Browse files
authored
Merge pull request #697 from projectdiscovery/feat-detect-tls
Adding half-handshake tls detect
2 parents 0b5fd22 + ef99769 commit e2253a8

File tree

3 files changed

+157
-2
lines changed

3 files changed

+157
-2
lines changed

net/net.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package netutil
1+
package net
22

33
import (
44
"errors"

net/net_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package netutil
1+
package net
22

33
import (
44
"testing"

net/tls.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package net
2+
3+
import (
4+
"crypto/rand"
5+
"net"
6+
"os"
7+
"time"
8+
)
9+
10+
// DetectTLS attempts to detect TLS on a connection by sending a ClientHello
11+
// and checking for early TLS fingerprints without performing a full handshake.
12+
//
13+
// It returns true if TLS is detected, false otherwise.
14+
//
15+
// The function uses the following steps:
16+
// 1. Generate a random 32-byte value for the ClientHello.
17+
// 2. Create a ServerNameIndication (SNI) extension for the hostname.
18+
// 3. Create a ClientHello message with the random value and the SNI extension.
19+
// 4. Send the ClientHello message to the server.
20+
// 5. Read the response from the server.
21+
// 6. Check if the response contains a ServerHello or tls alert message.
22+
// 7. If the response contains a ServerHello or tls alert message, return true.
23+
// 8. Otherwise, return false.
24+
func DetectTLS(conn net.Conn, host string, timeout time.Duration) bool {
25+
hostname := ""
26+
if host != "" {
27+
if ip := net.ParseIP(host); ip == nil {
28+
hostname = host
29+
}
30+
}
31+
32+
var sniExtension []byte
33+
var extensions []byte
34+
var extensionsLength int
35+
36+
if hostname != "" {
37+
hostnameBytes := []byte(hostname)
38+
sniListLength := 1 + 2 + len(hostnameBytes)
39+
sniLength := 2 + sniListLength
40+
sniExtension = make([]byte, 4+sniLength)
41+
sniExtension[0] = 0x00 // extension type: server_name
42+
sniExtension[1] = 0x00
43+
sniExtension[2] = byte(sniLength >> 8) // extension length
44+
sniExtension[3] = byte(sniLength)
45+
sniExtension[4] = byte(sniListLength >> 8) // server_name_list length
46+
sniExtension[5] = byte(sniListLength)
47+
sniExtension[6] = 0x00 // name_type: host_name
48+
sniExtension[7] = byte(len(hostnameBytes) >> 8) // hostname length
49+
sniExtension[8] = byte(len(hostnameBytes))
50+
copy(sniExtension[9:], hostnameBytes)
51+
52+
extensions = sniExtension
53+
extensionsLength = len(extensions)
54+
}
55+
56+
clientHelloBodyLength := 2 + 32 + 1 + 2 + 2 + 1 + 1 + 2 + extensionsLength
57+
handshakeLength := 4 + clientHelloBodyLength
58+
recordLength := handshakeLength
59+
clientHello := make([]byte, 5+handshakeLength)
60+
offset := 0
61+
62+
// TLS record header
63+
clientHello[offset] = 0x16 // content type: Handshake
64+
clientHello[offset+1] = 0x03 // version: TLS 1.0 (major)
65+
clientHello[offset+2] = 0x01 // version: TLS 1.0 (minor)
66+
clientHello[offset+3] = byte(recordLength >> 8) // length (high)
67+
clientHello[offset+4] = byte(recordLength) // length (low)
68+
offset += 5
69+
70+
// Handshake header
71+
clientHello[offset] = 0x01 // handshake type: ClientHello
72+
clientHello[offset+1] = byte(clientHelloBodyLength >> 16) // length (high)
73+
clientHello[offset+2] = byte(clientHelloBodyLength >> 8) // length (mid)
74+
clientHello[offset+3] = byte(clientHelloBodyLength) // length (low)
75+
offset += 4
76+
77+
// ClientHello message
78+
clientHello[offset] = 0x03 // version: TLS 1.2 (major)
79+
clientHello[offset+1] = 0x03 // version: TLS 1.2 (minor)
80+
offset += 2
81+
random := make([]byte, 32)
82+
if _, err := rand.Read(random); err != nil {
83+
return false
84+
}
85+
copy(clientHello[offset:], random) // random (32 bytes)
86+
offset += 32
87+
clientHello[offset] = 0x00 // session_id length
88+
offset++
89+
clientHello[offset] = 0x00 // cipher_suites length (high)
90+
clientHello[offset+1] = 0x02 // cipher_suites length (low)
91+
offset += 2
92+
clientHello[offset] = 0x00 // cipher_suite: TLS_RSA_WITH_AES_128_CBC_SHA (high)
93+
clientHello[offset+1] = 0x2f // cipher_suite (low)
94+
offset += 2
95+
clientHello[offset] = 0x01 // compression_methods length
96+
offset++
97+
clientHello[offset] = 0x00 // compression_method: null
98+
offset++
99+
clientHello[offset] = byte(extensionsLength >> 8) // extensions length (high)
100+
clientHello[offset+1] = byte(extensionsLength) // extensions length (low)
101+
offset += 2
102+
if extensionsLength > 0 {
103+
copy(clientHello[offset:], extensions)
104+
offset += extensionsLength
105+
}
106+
107+
actualRecordLength := offset - 5
108+
clientHello[3] = byte(actualRecordLength >> 8)
109+
clientHello[4] = byte(actualRecordLength)
110+
111+
if err := conn.SetWriteDeadline(time.Now().Add(timeout)); err != nil {
112+
return false
113+
}
114+
115+
if _, err := conn.Write(clientHello[:offset]); err != nil {
116+
return false
117+
}
118+
readTimeout := 2 * time.Second
119+
if timeout < readTimeout {
120+
readTimeout = timeout
121+
}
122+
if err := conn.SetReadDeadline(time.Now().Add(readTimeout)); err != nil {
123+
return false
124+
}
125+
126+
buffer := make([]byte, 1024)
127+
n, err := conn.Read(buffer)
128+
if err != nil && !os.IsTimeout(err) {
129+
return false
130+
}
131+
132+
if n < 5 {
133+
return false
134+
}
135+
136+
// check content type (0x15=Alert, 0x16=Handshake)
137+
contentType := buffer[0]
138+
if contentType != 0x15 && contentType != 0x16 {
139+
return false
140+
}
141+
142+
// check TLS version (major must be 0x03)
143+
if buffer[1] != 0x03 {
144+
return false
145+
}
146+
147+
// if handshake, check for ServerHello (0x02)
148+
if contentType == 0x16 && n >= 6 {
149+
if buffer[5] == 0x02 {
150+
return true
151+
}
152+
}
153+
154+
return true
155+
}

0 commit comments

Comments
 (0)