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  }