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 `