Skip to content

Commit

Permalink
introduce ja3s (#395)
Browse files Browse the repository at this point in the history
* introduce ja3s

* fixing go.sum

* docker build update

---------

Co-authored-by: mzack <[email protected]>
Co-authored-by: sandeep <[email protected]>
Co-authored-by: Sandeep Singh <[email protected]>
  • Loading branch information
4 people authored Aug 12, 2024
1 parent 0397444 commit 5a888d8
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Base
FROM golang:1.21.7-alpine AS builder
FROM golang:1.21.4-alpine AS builder
RUN apk add --no-cache build-base
WORKDIR /app
COPY . /app
Expand Down
1 change: 1 addition & 0 deletions cmd/tlsx/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func readFlags() error {
flagSet.StringVar(&options.Hash, "hash", "", "display certificate fingerprint hashes (md5,sha1,sha256)"),
flagSet.BoolVar(&options.Jarm, "jarm", false, "display jarm fingerprint hash"),
flagSet.BoolVar(&options.Ja3, "ja3", false, "display ja3 fingerprint hash (using ztls)"),
flagSet.BoolVar(&options.Ja3s, "ja3s", false, "display ja3s fingerprint hash (using ztls)"),
flagSet.BoolVarP(&options.WildcardCertCheck, "wildcard-cert", "wc", false, "display host with wildcard ssl certificate"),
flagSet.BoolVarP(&options.ProbeStatus, "probe-status", "tps", false, "display tls probe status"),
flagSet.BoolVarP(&options.TlsVersionsEnum, "version-enum", "ve", false, "enumerate and display supported tls versions"),
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
github.com/projectdiscovery/utils v0.2.4
github.com/rs/xid v1.5.0
github.com/stretchr/testify v1.9.0
github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968
github.com/zmap/zcrypto v0.0.0-20231106212110-94c8f62efae4
go.uber.org/multierr v1.11.0
golang.org/x/exp v0.0.0-20230420155640-133eef4313cb
)
Expand Down Expand Up @@ -111,7 +111,7 @@ require (
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/weppos/publicsuffix-go v0.30.1 // indirect
github.com/weppos/publicsuffix-go v0.30.2-0.20230730094716-a20f9abcc222 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
Expand Down
11 changes: 4 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,8 @@ github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oW
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db/go.mod h1:aiQaH1XpzIfgrJq3S1iw7w+3EDbRP7mF5fmwUhWyRUs=
github.com/weppos/publicsuffix-go v0.30.1 h1:8q+QwBS1MY56Zjfk/50ycu33NN8aa1iCCEQwo/71Oos=
github.com/weppos/publicsuffix-go v0.30.1/go.mod h1:s41lQh6dIsDWIC1OWh7ChWJXLH0zkJ9KHZVqA7vHyuQ=
github.com/weppos/publicsuffix-go v0.30.2-0.20230730094716-a20f9abcc222 h1:h2JizvZl9aIj6za9S5AyrkU+OzIS4CetQthH/ejO+lg=
github.com/weppos/publicsuffix-go v0.30.2-0.20230730094716-a20f9abcc222/go.mod h1:s41lQh6dIsDWIC1OWh7ChWJXLH0zkJ9KHZVqA7vHyuQ=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
Expand All @@ -306,8 +305,8 @@ github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54t
github.com/zmap/zcertificate v0.0.1/go.mod h1:q0dlN54Jm4NVSSuzisusQY0hqDWvu92C+TWveAxiVWk=
github.com/zmap/zcrypto v0.0.0-20201128221613-3719af1573cf/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ=
github.com/zmap/zcrypto v0.0.0-20201211161100-e54a5822fb7e/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ=
github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968 h1:YOQ1vXEwE4Rnj+uQ/3oCuJk5wgVsvUyW+glsndwYuyA=
github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968/go.mod h1:xIuOvYCZX21S5Z9bK1BMrertTGX/F8hgAPw7ERJRNS0=
github.com/zmap/zcrypto v0.0.0-20231106212110-94c8f62efae4 h1:YBEjlA0uAnTqljTgqFgA3NQUrcDSc850G2KuWnZ91UQ=
github.com/zmap/zcrypto v0.0.0-20231106212110-94c8f62efae4/go.mod h1:Z2SNNuFhO+AAsezbGEHTWeW30hHv5niUYT3fwJ61Nl0=
github.com/zmap/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
Expand All @@ -321,10 +320,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
Expand Down
2 changes: 1 addition & 1 deletion internal/runner/banner.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (r *Runner) validateOptions() error {
if r.options.CertsOnly && !(r.options.ScanMode == "ztls" || r.options.ScanMode == "auto") {
return errorutils.New("scan-mode must be ztls or auto with certs-only option")
}
if r.options.CertsOnly || r.options.Ja3 {
if r.options.CertsOnly || r.options.Ja3 || r.options.Ja3s {
r.options.ScanMode = "ztls" // force setting ztls when using certs-only
}
if r.options.Verbose {
Expand Down
6 changes: 6 additions & 0 deletions pkg/output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,12 @@ func (w *StandardWriter) formatStandard(output *clients.Response) ([]byte, error
builder.WriteString("]")
}

if w.options.Ja3s && output.Ja3sHash != "" {
builder.WriteString(" [")
builder.WriteString(w.aurora.Magenta(output.Ja3sHash).String())
builder.WriteString("]")
}

if w.options.TlsCiphersEnum {
for _, v := range output.TlsCiphers {
ct := v.Ciphers.ColorCode(w.aurora)
Expand Down
5 changes: 4 additions & 1 deletion pkg/tlsx/clients/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ type Options struct {
Cert bool
// Ja3 displays ja3 fingerprint hash
Ja3 bool
// Ja3s displays ja3s fingerprint hash
Ja3s bool
// Scan all IP's
ScanAllIPs bool
// IP Version to use for scanning
Expand Down Expand Up @@ -192,6 +194,7 @@ type Response struct {
Chain []*CertificateResponse `json:"chain,omitempty"`
JarmHash string `json:"jarm_hash,omitempty"`
Ja3Hash string `json:"ja3_hash,omitempty"`
Ja3sHash string `json:"ja3s_hash,omitempty"`
ServerName string `json:"sni,omitempty"`
VersionEnum []string `json:"version_enum,omitempty"`
TlsCiphers []TlsCiphers `json:"cipher_enum,omitempty"`
Expand Down Expand Up @@ -389,7 +392,7 @@ func IsMisMatchedCert(host string, alternativeNames []string) bool {

// IsTLSRevoked returns true if the certificate has been revoked or failed to parse
func IsTLSRevoked(options *Options, cert *x509.Certificate) bool {
if !options.Revoked || cert == nil {
if cert == nil {
return options.HardFail
}
// - false, false: an error was encountered while checking revocations.
Expand Down
73 changes: 72 additions & 1 deletion pkg/tlsx/ztls/ja3/ja3.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ const (
extensionHeartbeat uint16 = 15
)

// GetJa3SHash returns the JA3 fingerprint hash of the tls client hello.
// GetJa3Hash computes the JA3 fingerprint hash from a TLS ClientHello message.
// It structures the fingerprint as: SSLVersion,Cipher,SSLExtension,EllipticCurve,EllipticCurvePointFormat
func GetJa3Hash(clientHello *tls.ClientHello) string {
byteString := make([]byte, 0)

Expand Down Expand Up @@ -147,6 +148,76 @@ func GetJa3Hash(clientHello *tls.ClientHello) string {
return hex.EncodeToString(h[:])
}

// GetJa3sHash computes the JA3S fingerprint hash from a TLS ServerHello message.
// JA3S is the server-side counterpart to JA3, profiling how servers react to client hellos in SSL/TLS communication.
// It structures the fingerprint as: SSLVersion,Cipher,SSLExtension.
func GetJa3sHash(serverHello *tls.ServerHello) string {
// NOTE: The original JA3S implementation only uses the extensions that are present,
// as demonstrated in the Python implementation at salesforce/ja3 (https://github.com/salesforce/ja3/blob/421dd4f3616b533e6971bb700289c6bb8355e707/python/ja3s.py#L39).
// Keep an eye on updates in the ZCrypto library for potential inclusion of additional fields in the future.
// For current implementation details, see: https://github.com/zmap/zcrypto/blob/master/tls/tls_handshake.go#L56

byteString := make([]byte, 0)

// Version
byteString = strconv.AppendUint(byteString, uint64(serverHello.Version), 10)
byteString = append(byteString, commaByte)

// Cipher Suites
if len(serverHello.CipherSuite.Bytes()) != 0 {
byteString = strconv.AppendUint(byteString, uint64(serverHello.CipherSuite), 10)
byteString = append(byteString, commaByte)
} else {
byteString = append(byteString, commaByte)
}

// Extensions
if serverHello.NextProtoNeg {
byteString = appendExtension(byteString, extensionNextProtoNeg)
}

if serverHello.OcspStapling {
byteString = appendExtension(byteString, extensionStatusRequest)
}

if serverHello.TicketSupported {
byteString = appendExtension(byteString, extensionSessionTicket)
}

if serverHello.SecureRenegotiation {
byteString = appendExtension(byteString, extensionRenegotiationInfo)
}

if serverHello.HeartbeatSupported {
byteString = appendExtension(byteString, extensionHeartbeat)
}

if len(serverHello.ExtendedRandom) > 0 {
byteString = appendExtension(byteString, extensionExtendedRandom)
}

if serverHello.ExtendedMasterSecret {
byteString = appendExtension(byteString, extensionExtendedMasterSecret)
}

if len(serverHello.UnknownExtensions) > 0 {
for _, ext := range serverHello.UnknownExtensions {
exType := uint16(ext[0])<<8 | uint16(ext[1])
byteString = appendExtension(byteString, exType)
}
}
// If dash found replace it with a comma
if byteString[len(byteString)-1] == dashByte {
byteString[len(byteString)-1] = commaByte
} else {
// else add a comma (no extension present)
byteString = append(byteString, commaByte)
}

h := md5.Sum(byteString)
return hex.EncodeToString(h[:])
}

func appendExtension(byteString []byte, exType uint16) []byte {
// Ignore any GREASE extensions
if exType&greaseBitmask != 0x0A0A {
Expand Down
3 changes: 3 additions & 0 deletions pkg/tlsx/ztls/ztls.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ func (c *Client) ConnectWithOptions(hostname, ip, port string, options clients.C
if c.options.Ja3 {
response.Ja3Hash = ja3.GetJa3Hash(hl.ClientHello)
}
if c.options.Ja3s {
response.Ja3sHash = ja3.GetJa3sHash(hl.ServerHello)
}
if c.options.ClientHello {
response.ClientHello = hl.ClientHello
}
Expand Down

0 comments on commit 5a888d8

Please sign in to comment.