github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/ssl/ssl.go (about) 1 package ssl 2 3 import ( 4 "fmt" 5 "net" 6 "time" 7 8 "github.com/fatih/structs" 9 jsoniter "github.com/json-iterator/go" 10 "github.com/pkg/errors" 11 12 "github.com/projectdiscovery/fastdialer/fastdialer" 13 "github.com/projectdiscovery/gologger" 14 "github.com/projectdiscovery/nuclei/v2/pkg/model" 15 "github.com/projectdiscovery/nuclei/v2/pkg/operators" 16 "github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors" 17 "github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers" 18 "github.com/projectdiscovery/nuclei/v2/pkg/output" 19 "github.com/projectdiscovery/nuclei/v2/pkg/protocols" 20 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" 21 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions" 22 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" 23 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator" 24 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter" 25 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" 26 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/network/networkclientpool" 27 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils" 28 protocolutils "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils" 29 templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" 30 "github.com/projectdiscovery/nuclei/v2/pkg/types" 31 "github.com/projectdiscovery/tlsx/pkg/tlsx" 32 "github.com/projectdiscovery/tlsx/pkg/tlsx/clients" 33 "github.com/projectdiscovery/tlsx/pkg/tlsx/openssl" 34 errorutil "github.com/projectdiscovery/utils/errors" 35 stringsutil "github.com/projectdiscovery/utils/strings" 36 urlutil "github.com/projectdiscovery/utils/url" 37 ) 38 39 // Request is a request for the SSL protocol 40 type Request struct { 41 // Operators for the current request go here. 42 operators.Operators `yaml:",inline,omitempty" json:",inline,omitempty"` 43 CompiledOperators *operators.Operators `yaml:"-" json:"-"` 44 45 // description: | 46 // Address contains address for the request 47 Address string `yaml:"address,omitempty" json:"address,omitempty" jsonschema:"title=address for the ssl request,description=Address contains address for the request"` 48 // description: | 49 // Minimum tls version - auto if not specified. 50 // values: 51 // - "sslv3" 52 // - "tls10" 53 // - "tls11" 54 // - "tls12" 55 // - "tls13" 56 MinVersion string `yaml:"min_version,omitempty" json:"min_version,omitempty" jsonschema:"title=Min. TLS version,description=Minimum tls version - automatic if not specified.,enum=sslv3,enum=tls10,enum=tls11,enum=tls12,enum=tls13"` 57 // description: | 58 // Max tls version - auto if not specified. 59 // values: 60 // - "sslv3" 61 // - "tls10" 62 // - "tls11" 63 // - "tls12" 64 // - "tls13" 65 MaxVersion string `yaml:"max_version,omitempty" json:"max_version,omitempty" jsonschema:"title=Max. TLS version,description=Max tls version - automatic if not specified.,enum=sslv3,enum=tls10,enum=tls11,enum=tls12,enum=tls13"` 66 // description: | 67 // Client Cipher Suites - auto if not specified. 68 CipherSuites []string `yaml:"cipher_suites,omitempty" json:"cipher_suites,omitempty"` 69 // description: | 70 // Tls Scan Mode - auto if not specified 71 // values: 72 // - "ctls" 73 // - "ztls" 74 // - "auto" 75 // - "openssl" # reverts to "auto" is openssl is not installed 76 ScanMode string `yaml:"scan_mode,omitempty" json:"scan_mode,omitempty" jsonschema:"title=Scan Mode,description=Scan Mode - auto if not specified.,enum=ctls,enum=ztls,enum=auto"` 77 78 // cache any variables that may be needed for operation. 79 dialer *fastdialer.Dialer 80 tlsx *tlsx.Service 81 options *protocols.ExecutorOptions 82 } 83 84 // CanCluster returns true if the request can be clustered. 85 func (request *Request) CanCluster(other *Request) bool { 86 if len(request.CipherSuites) > 0 || request.MinVersion != "" || request.MaxVersion != "" { 87 return false 88 } 89 if request.Address != other.Address || request.ScanMode != other.ScanMode { 90 return false 91 } 92 return true 93 } 94 95 // Compile compiles the request generators preparing any requests possible. 96 func (request *Request) Compile(options *protocols.ExecutorOptions) error { 97 request.options = options 98 99 client, err := networkclientpool.Get(options.Options, &networkclientpool.Configuration{}) 100 if err != nil { 101 return errorutil.NewWithTag("ssl", "could not get network client").Wrap(err) 102 } 103 request.dialer = client 104 switch { 105 //validate scanmode 106 case request.ScanMode == "": 107 request.ScanMode = "auto" 108 109 case !stringsutil.EqualFoldAny(request.ScanMode, "auto", "openssl", "ztls", "ctls"): 110 return errorutil.NewWithTag(request.TemplateID, "template %v does not contain valid scan-mode", request.TemplateID) 111 112 case request.ScanMode == "openssl" && !openssl.IsAvailable(): 113 // if openssl is not installed instead of failing "auto" scanmode is used 114 request.ScanMode = "auto" 115 } 116 117 tlsxOptions := &clients.Options{ 118 AllCiphers: true, 119 ScanMode: request.ScanMode, 120 Expired: true, 121 SelfSigned: true, 122 Revoked: true, 123 MisMatched: true, 124 MinVersion: request.MinVersion, 125 MaxVersion: request.MaxVersion, 126 Ciphers: request.CipherSuites, 127 WildcardCertCheck: true, 128 Retries: request.options.Options.Retries, 129 Timeout: request.options.Options.Timeout, 130 Fastdialer: client, 131 ClientHello: true, 132 ServerHello: true, 133 } 134 135 tlsxService, err := tlsx.New(tlsxOptions) 136 if err != nil { 137 return errorutil.NewWithTag(request.TemplateID, "could not create tlsx service") 138 } 139 request.tlsx = tlsxService 140 141 if len(request.Matchers) > 0 || len(request.Extractors) > 0 { 142 compiled := &request.Operators 143 compiled.ExcludeMatchers = options.ExcludeMatchers 144 compiled.TemplateID = options.TemplateID 145 if err := compiled.Compile(); err != nil { 146 return errorutil.NewWithTag(request.TemplateID, "could not compile operators got %v", err) 147 } 148 request.CompiledOperators = compiled 149 } 150 return nil 151 } 152 153 // Options returns executer options for http request 154 func (r *Request) Options() *protocols.ExecutorOptions { 155 return r.options 156 } 157 158 // Requests returns the total number of requests the rule will perform 159 func (request *Request) Requests() int { 160 return 1 161 } 162 163 // GetID returns the ID for the request if any. 164 func (request *Request) GetID() string { 165 return "" 166 } 167 168 // ExecuteWithResults executes the protocol requests and returns results instead of writing them. 169 func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error { 170 hostPort, err := getAddress(input.MetaInput.Input) 171 if err != nil { 172 return err 173 } 174 hostname, port, _ := net.SplitHostPort(hostPort) 175 176 requestOptions := request.options 177 payloadValues := generators.BuildPayloadFromOptions(request.options.Options) 178 for k, v := range dynamicValues { 179 payloadValues[k] = v 180 } 181 182 payloadValues["Hostname"] = hostPort 183 payloadValues["Host"] = hostname 184 payloadValues["Port"] = port 185 186 hostnameVariables := protocolutils.GenerateDNSVariables(hostname) 187 values := generators.MergeMaps(payloadValues, hostnameVariables) 188 variablesMap := request.options.Variables.Evaluate(values) 189 payloadValues = generators.MergeMaps(variablesMap, payloadValues, request.options.Constants) 190 191 if vardump.EnableVarDump { 192 gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(payloadValues)) 193 } 194 195 finalAddress, dataErr := expressions.EvaluateByte([]byte(request.Address), payloadValues) 196 if dataErr != nil { 197 requestOptions.Output.Request(requestOptions.TemplateID, input.MetaInput.Input, request.Type().String(), dataErr) 198 requestOptions.Progress.IncrementFailedRequestsBy(1) 199 return errors.Wrap(dataErr, "could not evaluate template expressions") 200 } 201 addressToDial := string(finalAddress) 202 host, port, err := net.SplitHostPort(addressToDial) 203 if err != nil { 204 return errorutil.NewWithErr(err).Msgf("could not split input host port") 205 } 206 207 var hostIp string 208 if input.MetaInput.CustomIP != "" { 209 hostIp = input.MetaInput.CustomIP 210 } else { 211 hostIp = host 212 } 213 214 response, err := request.tlsx.Connect(host, hostIp, port) 215 if err != nil { 216 requestOptions.Output.Request(requestOptions.TemplateID, input.MetaInput.Input, request.Type().String(), err) 217 requestOptions.Progress.IncrementFailedRequestsBy(1) 218 return errorutil.NewWithTag(request.TemplateID, "could not connect to server").Wrap(err) 219 } 220 221 requestOptions.Output.Request(requestOptions.TemplateID, hostPort, request.Type().String(), err) 222 gologger.Verbose().Msgf("Sent SSL request to %s", hostPort) 223 224 if requestOptions.Options.Debug || requestOptions.Options.DebugRequests || requestOptions.Options.StoreResponse { 225 msg := fmt.Sprintf("[%s] Dumped SSL request for %s", requestOptions.TemplateID, input.MetaInput.Input) 226 if requestOptions.Options.Debug || requestOptions.Options.DebugRequests { 227 gologger.Debug().Str("address", input.MetaInput.Input).Msg(msg) 228 } 229 if requestOptions.Options.StoreResponse { 230 request.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), msg) 231 } 232 } 233 234 jsonData, _ := jsoniter.Marshal(response) 235 jsonDataString := string(jsonData) 236 237 data := make(map[string]interface{}) 238 for k, v := range payloadValues { 239 data[k] = v 240 } 241 data["type"] = request.Type().String() 242 data["response"] = jsonDataString 243 data["host"] = input.MetaInput.Input 244 data["matched"] = addressToDial 245 if input.MetaInput.CustomIP != "" { 246 data["ip"] = hostIp 247 } else { 248 data["ip"] = request.dialer.GetDialedIP(hostname) 249 } 250 data["template-path"] = requestOptions.TemplatePath 251 data["template-id"] = requestOptions.TemplateID 252 data["template-info"] = requestOptions.TemplateInfo 253 254 // if response is not struct compatible, error out 255 if !structs.IsStruct(response) { 256 return errorutil.NewWithTag("ssl", "response cannot be parsed into a struct: %v", response) 257 } 258 259 // Convert response to key value pairs and first cert chain item as well 260 responseParsed := structs.New(response) 261 for _, f := range responseParsed.Fields() { 262 if !f.IsExported() { 263 // if field is not exported f.IsZero() , f.Value() will panic 264 continue 265 } 266 tag := utils.CleanStructFieldJSONTag(f.Tag("json")) 267 if tag == "" || f.IsZero() { 268 continue 269 } 270 data[tag] = f.Value() 271 } 272 273 // if certificate response is not struct compatible, error out 274 if !structs.IsStruct(response.CertificateResponse) { 275 return errorutil.NewWithTag("ssl", "certificate response cannot be parsed into a struct: %v", response.CertificateResponse) 276 } 277 278 responseParsed = structs.New(response.CertificateResponse) 279 for _, f := range responseParsed.Fields() { 280 if !f.IsExported() { 281 // if field is not exported f.IsZero() , f.Value() will panic 282 continue 283 } 284 tag := utils.CleanStructFieldJSONTag(f.Tag("json")) 285 if tag == "" || f.IsZero() { 286 continue 287 } 288 data[tag] = f.Value() 289 } 290 291 event := eventcreator.CreateEvent(request, data, requestOptions.Options.Debug || requestOptions.Options.DebugResponse) 292 if requestOptions.Options.Debug || requestOptions.Options.DebugResponse || requestOptions.Options.StoreResponse { 293 msg := fmt.Sprintf("[%s] Dumped SSL response for %s", requestOptions.TemplateID, input.MetaInput.Input) 294 if requestOptions.Options.Debug || requestOptions.Options.DebugResponse { 295 gologger.Debug().Msg(msg) 296 gologger.Print().Msgf("%s", responsehighlighter.Highlight(event.OperatorsResult, jsonDataString, requestOptions.Options.NoColor, false)) 297 } 298 if requestOptions.Options.StoreResponse { 299 request.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), fmt.Sprintf("%s\n%s", msg, jsonDataString)) 300 } 301 } 302 callback(event) 303 return nil 304 } 305 306 // RequestPartDefinitions contains a mapping of request part definitions and their 307 // description. Multiple definitions are separated by commas. 308 // Definitions not having a name (generated on runtime) are prefixed & suffixed by <>. 309 var RequestPartDefinitions = map[string]string{ 310 "type": "Type is the type of request made", 311 "response": "JSON SSL protocol handshake details", 312 "not_after": "Timestamp after which the remote cert expires", 313 "host": "Host is the input to the template", 314 "matched": "Matched is the input which was matched upon", 315 } 316 317 // getAddress returns the address of the host to make request to 318 func getAddress(toTest string) (string, error) { 319 urlx, err := urlutil.Parse(toTest) 320 if err != nil { 321 // use given input instead of url parsing failure 322 return toTest, nil 323 } 324 if urlx.Port() == "" { 325 urlx.UpdatePort("443") 326 } 327 return urlx.Host, nil 328 } 329 330 // Match performs matching operation for a matcher on model and returns: 331 // true and a list of matched snippets if the matcher type is supports it 332 // otherwise false and an empty string slice 333 func (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) { 334 return protocols.MakeDefaultMatchFunc(data, matcher) 335 } 336 337 // Extract performs extracting operation for an extractor on model and returns true or false. 338 func (request *Request) Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{} { 339 return protocols.MakeDefaultExtractFunc(data, matcher) 340 } 341 342 // MakeResultEvent creates a result event from internal wrapped event 343 func (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent { 344 return protocols.MakeDefaultResultEvent(request, wrapped) 345 } 346 347 // GetCompiledOperators returns a list of the compiled operators 348 func (request *Request) GetCompiledOperators() []*operators.Operators { 349 return []*operators.Operators{request.CompiledOperators} 350 } 351 352 // Type returns the type of the protocol request 353 func (request *Request) Type() templateTypes.ProtocolType { 354 return templateTypes.SSLProtocol 355 } 356 357 func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent { 358 data := &output.ResultEvent{ 359 TemplateID: types.ToString(wrapped.InternalEvent["template-id"]), 360 TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]), 361 Info: wrapped.InternalEvent["template-info"].(model.Info), 362 Type: types.ToString(wrapped.InternalEvent["type"]), 363 Host: types.ToString(wrapped.InternalEvent["host"]), 364 Matched: types.ToString(wrapped.InternalEvent["matched"]), 365 Metadata: wrapped.OperatorsResult.PayloadValues, 366 ExtractedResults: wrapped.OperatorsResult.OutputExtracts, 367 Timestamp: time.Now(), 368 MatcherStatus: true, 369 IP: types.ToString(wrapped.InternalEvent["ip"]), 370 } 371 return data 372 }