github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/offlinehttp/operators_test.go (about)

     1  package offlinehttp
     2  
     3  import (
     4  	"net/http"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/projectdiscovery/nuclei/v2/pkg/model"
    11  	"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
    12  	"github.com/projectdiscovery/nuclei/v2/pkg/operators"
    13  	"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
    14  	"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
    15  	"github.com/projectdiscovery/nuclei/v2/pkg/output"
    16  	"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
    17  )
    18  
    19  func TestResponseToDSLMap(t *testing.T) {
    20  	options := testutils.DefaultOptions
    21  
    22  	testutils.Init(options)
    23  	templateID := "testing-http"
    24  	request := &Request{}
    25  	executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
    26  		ID:   templateID,
    27  		Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"},
    28  	})
    29  	executerOpts.Operators = []*operators.Operators{{}}
    30  	err := request.Compile(executerOpts)
    31  	require.Nil(t, err, "could not compile file request")
    32  
    33  	resp := &http.Response{}
    34  	resp.Header = make(http.Header)
    35  	resp.Header.Set("Test", "Test-Response")
    36  	host := "http://example.com/test/"
    37  	matched := "http://example.com/test/?test=1"
    38  
    39  	event := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})
    40  	require.Len(t, event, 14, "could not get correct number of items in dsl map")
    41  	require.Equal(t, exampleRawResponse, event["response"], "could not get correct resp")
    42  	require.Equal(t, "Test-Response", event["test"], "could not get correct resp for header")
    43  }
    44  
    45  func TestHTTPOperatorMatch(t *testing.T) {
    46  	options := testutils.DefaultOptions
    47  
    48  	testutils.Init(options)
    49  	templateID := "testing-http"
    50  	request := &Request{}
    51  	executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
    52  		ID:   templateID,
    53  		Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"},
    54  	})
    55  	executerOpts.Operators = []*operators.Operators{{}}
    56  	err := request.Compile(executerOpts)
    57  	require.Nil(t, err, "could not compile file request")
    58  
    59  	resp := &http.Response{}
    60  	resp.Header = make(http.Header)
    61  	resp.Header.Set("Test", "Test-Response")
    62  	host := "http://example.com/test/"
    63  	matched := "http://example.com/test/?test=1"
    64  
    65  	event := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})
    66  	require.Len(t, event, 14, "could not get correct number of items in dsl map")
    67  	require.Equal(t, exampleRawResponse, event["response"], "could not get correct resp")
    68  	require.Equal(t, "Test-Response", event["test"], "could not get correct resp for header")
    69  
    70  	t.Run("valid", func(t *testing.T) {
    71  		matcher := &matchers.Matcher{
    72  			Part:  "body",
    73  			Type:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},
    74  			Words: []string{"1.1.1.1"},
    75  		}
    76  		err = matcher.CompileMatchers()
    77  		require.Nil(t, err, "could not compile matcher")
    78  
    79  		isMatched, matched := request.Match(event, matcher)
    80  		require.True(t, isMatched, "could not match valid response")
    81  		require.Equal(t, matcher.Words, matched)
    82  	})
    83  
    84  	t.Run("negative", func(t *testing.T) {
    85  		matcher := &matchers.Matcher{
    86  			Part:     "body",
    87  			Type:     matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},
    88  			Negative: true,
    89  			Words:    []string{"random"},
    90  		}
    91  		err := matcher.CompileMatchers()
    92  		require.Nil(t, err, "could not compile negative matcher")
    93  
    94  		isMatched, matched := request.Match(event, matcher)
    95  		require.True(t, isMatched, "could not match valid negative response matcher")
    96  		require.Equal(t, []string{}, matched)
    97  	})
    98  
    99  	t.Run("invalid", func(t *testing.T) {
   100  		matcher := &matchers.Matcher{
   101  			Part:  "body",
   102  			Type:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},
   103  			Words: []string{"random"},
   104  		}
   105  		err := matcher.CompileMatchers()
   106  		require.Nil(t, err, "could not compile matcher")
   107  
   108  		isMatched, matched := request.Match(event, matcher)
   109  		require.False(t, isMatched, "could match invalid response matcher")
   110  		require.Equal(t, []string{}, matched)
   111  	})
   112  }
   113  
   114  func TestHTTPOperatorExtract(t *testing.T) {
   115  	options := testutils.DefaultOptions
   116  
   117  	testutils.Init(options)
   118  	templateID := "testing-http"
   119  	request := &Request{}
   120  	executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
   121  		ID:   templateID,
   122  		Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"},
   123  	})
   124  	executerOpts.Operators = []*operators.Operators{{}}
   125  	err := request.Compile(executerOpts)
   126  	require.Nil(t, err, "could not compile file request")
   127  
   128  	resp := &http.Response{}
   129  	resp.Header = make(http.Header)
   130  	resp.Header.Set("Test-Header", "Test-Response")
   131  	host := "http://example.com/test/"
   132  	matched := "http://example.com/test/?test=1"
   133  
   134  	event := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})
   135  	require.Len(t, event, 14, "could not get correct number of items in dsl map")
   136  	require.Equal(t, exampleRawResponse, event["response"], "could not get correct resp")
   137  	require.Equal(t, "Test-Response", event["test-header"], "could not get correct resp for header")
   138  
   139  	t.Run("extract", func(t *testing.T) {
   140  		extractor := &extractors.Extractor{
   141  			Part:  "body",
   142  			Type:  extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},
   143  			Regex: []string{"[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+"},
   144  		}
   145  		err = extractor.CompileExtractors()
   146  		require.Nil(t, err, "could not compile extractor")
   147  
   148  		data := request.Extract(event, extractor)
   149  		require.Greater(t, len(data), 0, "could not extractor valid response")
   150  		require.Equal(t, map[string]struct{}{"1.1.1.1": {}}, data, "could not extract correct data")
   151  	})
   152  
   153  	t.Run("kval", func(t *testing.T) {
   154  		extractor := &extractors.Extractor{
   155  			Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.KValExtractor},
   156  			KVal: []string{"test-header"},
   157  			Part: "header",
   158  		}
   159  		err = extractor.CompileExtractors()
   160  		require.Nil(t, err, "could not compile kval extractor")
   161  
   162  		data := request.Extract(event, extractor)
   163  		require.Greater(t, len(data), 0, "could not extractor kval valid response")
   164  		require.Equal(t, map[string]struct{}{"Test-Response": {}}, data, "could not extract correct kval data")
   165  	})
   166  }
   167  
   168  func TestHTTPMakeResult(t *testing.T) {
   169  	options := testutils.DefaultOptions
   170  
   171  	testutils.Init(options)
   172  	templateID := "testing-http"
   173  	request := &Request{}
   174  	executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
   175  		ID:   templateID,
   176  		Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"},
   177  	})
   178  	executerOpts.Operators = []*operators.Operators{{
   179  		Matchers: []*matchers.Matcher{{
   180  			Name:  "test",
   181  			Part:  "body",
   182  			Type:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},
   183  			Words: []string{"1.1.1.1"},
   184  		}},
   185  		Extractors: []*extractors.Extractor{{
   186  			Part:  "body",
   187  			Type:  extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},
   188  			Regex: []string{"[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+"},
   189  		}},
   190  	}}
   191  	err := request.Compile(executerOpts)
   192  	require.Nil(t, err, "could not compile file request")
   193  
   194  	resp := &http.Response{}
   195  	resp.Header = make(http.Header)
   196  	resp.Header.Set("Test", "Test-Response")
   197  	host := "http://example.com/test/"
   198  	matched := "http://example.com/test/?test=1"
   199  
   200  	event := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})
   201  	require.Len(t, event, 14, "could not get correct number of items in dsl map")
   202  	require.Equal(t, exampleRawResponse, event["response"], "could not get correct resp")
   203  	require.Equal(t, "Test-Response", event["test"], "could not get correct resp for header")
   204  
   205  	event["ip"] = "192.169.1.1"
   206  	finalEvent := &output.InternalWrappedEvent{InternalEvent: event}
   207  	for _, operator := range request.compiledOperators {
   208  		result, ok := operator.Execute(event, request.Match, request.Extract, false)
   209  		if ok && result != nil {
   210  			finalEvent.OperatorsResult = result
   211  			finalEvent.Results = request.MakeResultEvent(finalEvent)
   212  		}
   213  	}
   214  	require.Equal(t, 1, len(finalEvent.Results), "could not get correct number of results")
   215  	require.Equal(t, "test", finalEvent.Results[0].MatcherName, "could not get correct matcher name of results")
   216  	require.Equal(t, "1.1.1.1", finalEvent.Results[0].ExtractedResults[0], "could not get correct extracted results")
   217  }
   218  
   219  const exampleRawRequest = `GET / HTTP/1.1
   220  Host: example.com
   221  Upgrade-Insecure-Requests: 1
   222  User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36
   223  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
   224  Accept-Encoding: gzip, deflate
   225  Accept-Language: en-US,en;q=0.9,hi;q=0.8
   226  If-None-Match: "3147526947+gzip"
   227  If-Modified-Since: Thu, 17 Oct 2019 07:18:26 GMT
   228  Connection: close
   229  
   230  `
   231  
   232  const exampleRawResponse = exampleResponseHeader + exampleResponseBody
   233  const exampleResponseHeader = `
   234  HTTP/1.1 200 OK
   235  Accept-Ranges: bytes
   236  Age: 493322
   237  Cache-Control: max-age=604800
   238  Content-Type: text/html; charset=UTF-8
   239  Date: Thu, 04 Feb 2021 12:15:51 GMT
   240  Etag: "3147526947+ident"
   241  Expires: Thu, 11 Feb 2021 12:15:51 GMT
   242  Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
   243  Server: ECS (nyb/1D1C)
   244  Vary: Accept-Encoding
   245  X-Cache: HIT
   246  Content-Length: 1256
   247  Connection: close
   248  `
   249  
   250  const exampleResponseBody = `
   251  <!doctype html>
   252  <html>
   253  <head>
   254      <title>Example Domain</title>
   255  
   256      <meta charset="utf-8" />
   257      <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
   258      <meta name="viewport" content="width=device-width, initial-scale=1" />
   259      <style type="text/css">
   260      body {
   261          background-color: #f0f0f2;
   262          margin: 0;
   263          padding: 0;
   264          font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
   265          
   266      }
   267      div {
   268          width: 600px;
   269          margin: 5em auto;
   270          padding: 2em;
   271          background-color: #fdfdff;
   272          border-radius: 0.5em;
   273          box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
   274      }
   275      a:link, a:visited {
   276          color: #38488f;
   277          text-decoration: none;
   278      }
   279      @media (max-width: 700px) {
   280          div {
   281              margin: 0 auto;
   282              width: auto;
   283          }
   284      }
   285      </style>    
   286  </head>
   287  <a>1.1.1.1</a>
   288  <body>
   289  <div>
   290      <h1>Example Domain</h1>
   291      <p>This domain is for use in illustrative examples in documents. You may use this
   292      domain in literature without prior coordination or asking for permission.</p>
   293      <p><a href="https://www.iana.org/domains/example">More information...</a></p>
   294  </div>
   295  </body>
   296  </html>
   297  `