github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/http/http.go (about) 1 package http 2 3 import ( 4 "bytes" 5 "fmt" 6 "strings" 7 8 json "github.com/json-iterator/go" 9 "github.com/pkg/errors" 10 11 "github.com/projectdiscovery/nuclei/v2/pkg/operators" 12 "github.com/projectdiscovery/nuclei/v2/pkg/protocols" 13 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions" 14 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/fuzz" 15 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" 16 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/httpclientpool" 17 httputil "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils/http" 18 "github.com/projectdiscovery/rawhttp" 19 "github.com/projectdiscovery/retryablehttp-go" 20 fileutil "github.com/projectdiscovery/utils/file" 21 ) 22 23 // Request contains a http request to be made from a template 24 type Request struct { 25 // Operators for the current request go here. 26 operators.Operators `yaml:",inline" json:",inline"` 27 // description: | 28 // Path contains the path/s for the HTTP requests. It supports variables 29 // as placeholders. 30 // examples: 31 // - name: Some example path values 32 // value: > 33 // []string{"{{BaseURL}}", "{{BaseURL}}/+CSCOU+/../+CSCOE+/files/file_list.json?path=/sessions"} 34 Path []string `yaml:"path,omitempty" json:"path,omitempty" jsonschema:"title=path(s) for the http request,description=Path(s) to send http requests to"` 35 // description: | 36 // Raw contains HTTP Requests in Raw format. 37 // examples: 38 // - name: Some example raw requests 39 // value: | 40 // []string{"GET /etc/passwd HTTP/1.1\nHost:\nContent-Length: 4", "POST /.%0d./.%0d./.%0d./.%0d./bin/sh HTTP/1.1\nHost: {{Hostname}}\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0\nContent-Length: 1\nConnection: close\n\necho\necho\ncat /etc/passwd 2>&1"} 41 Raw []string `yaml:"raw,omitempty" json:"raw,omitempty" jsonschema:"http requests in raw format,description=HTTP Requests in Raw Format"` 42 // ID is the optional id of the request 43 ID string `yaml:"id,omitempty" json:"id,omitempty" jsonschema:"title=id for the http request,description=ID for the HTTP Request"` 44 // description: | 45 // Name is the optional name of the request. 46 // 47 // If a name is specified, all the named request in a template can be matched upon 48 // in a combined manner allowing multi-request based matchers. 49 Name string `yaml:"name,omitempty" json:"name,omitempty" jsonschema:"title=name for the http request,description=Optional name for the HTTP Request"` 50 // description: | 51 // Attack is the type of payload combinations to perform. 52 // 53 // batteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates 54 // permutations and combinations for all payloads. 55 // values: 56 // - "batteringram" 57 // - "pitchfork" 58 // - "clusterbomb" 59 AttackType generators.AttackTypeHolder `yaml:"attack,omitempty" json:"attack,omitempty" jsonschema:"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=batteringram,enum=pitchfork,enum=clusterbomb"` 60 // description: | 61 // Method is the HTTP Request Method. 62 Method HTTPMethodTypeHolder `yaml:"method,omitempty" json:"method,omitempty" jsonschema:"title=method is the http request method,description=Method is the HTTP Request Method,enum=GET,enum=HEAD,enum=POST,enum=PUT,enum=DELETE,enum=CONNECT,enum=OPTIONS,enum=TRACE,enum=PATCH,enum=PURGE"` 63 // description: | 64 // Body is an optional parameter which contains HTTP Request body. 65 // examples: 66 // - name: Same Body for a Login POST request 67 // value: "\"username=test&password=test\"" 68 Body string `yaml:"body,omitempty" json:"body,omitempty" jsonschema:"title=body is the http request body,description=Body is an optional parameter which contains HTTP Request body"` 69 // description: | 70 // Payloads contains any payloads for the current request. 71 // 72 // Payloads support both key-values combinations where a list 73 // of payloads is provided, or optionally a single file can also 74 // be provided as payload which will be read on run-time. 75 Payloads map[string]interface{} `yaml:"payloads,omitempty" json:"payloads,omitempty" jsonschema:"title=payloads for the http request,description=Payloads contains any payloads for the current request"` 76 77 // description: | 78 // Headers contains HTTP Headers to send with the request. 79 // examples: 80 // - value: | 81 // map[string]string{"Content-Type": "application/x-www-form-urlencoded", "Content-Length": "1", "Any-Header": "Any-Value"} 82 Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty" jsonschema:"title=headers to send with the http request,description=Headers contains HTTP Headers to send with the request"` 83 // description: | 84 // RaceCount is the number of times to send a request in Race Condition Attack. 85 // examples: 86 // - name: Send a request 5 times 87 // value: "5" 88 RaceNumberRequests int `yaml:"race_count,omitempty" json:"race_count,omitempty" jsonschema:"title=number of times to repeat request in race condition,description=Number of times to send a request in Race Condition Attack"` 89 // description: | 90 // MaxRedirects is the maximum number of redirects that should be followed. 91 // examples: 92 // - name: Follow up to 5 redirects 93 // value: "5" 94 MaxRedirects int `yaml:"max-redirects,omitempty" json:"max-redirects,omitempty" jsonschema:"title=maximum number of redirects to follow,description=Maximum number of redirects that should be followed"` 95 // description: | 96 // PipelineConcurrentConnections is number of connections to create during pipelining. 97 // examples: 98 // - name: Create 40 concurrent connections 99 // value: 40 100 PipelineConcurrentConnections int `yaml:"pipeline-concurrent-connections,omitempty" json:"pipeline-concurrent-connections,omitempty" jsonschema:"title=number of pipelining connections,description=Number of connections to create during pipelining"` 101 // description: | 102 // PipelineRequestsPerConnection is number of requests to send per connection when pipelining. 103 // examples: 104 // - name: Send 100 requests per pipeline connection 105 // value: 100 106 PipelineRequestsPerConnection int `yaml:"pipeline-requests-per-connection,omitempty" json:"pipeline-requests-per-connection,omitempty" jsonschema:"title=number of requests to send per pipelining connections,description=Number of requests to send per connection when pipelining"` 107 // description: | 108 // Threads specifies number of threads to use sending requests. This enables Connection Pooling. 109 // 110 // Connection: Close attribute must not be used in request while using threads flag, otherwise 111 // pooling will fail and engine will continue to close connections after requests. 112 // examples: 113 // - name: Send requests using 10 concurrent threads 114 // value: 10 115 Threads int `yaml:"threads,omitempty" json:"threads,omitempty" jsonschema:"title=threads for sending requests,description=Threads specifies number of threads to use sending requests. This enables Connection Pooling"` 116 // description: | 117 // MaxSize is the maximum size of http response body to read in bytes. 118 // examples: 119 // - name: Read max 2048 bytes of the response 120 // value: 2048 121 MaxSize int `yaml:"max-size,omitempty" json:"max-size,omitempty" jsonschema:"title=maximum http response body size,description=Maximum size of http response body to read in bytes"` 122 123 // Fuzzing describes schema to fuzz http requests 124 Fuzzing []*fuzz.Rule `yaml:"fuzzing,omitempty" json:"fuzzing,omitempty" jsonschema:"title=fuzzin rules for http fuzzing,description=Fuzzing describes rule schema to fuzz http requests"` 125 126 CompiledOperators *operators.Operators `yaml:"-" json:"-"` 127 128 options *protocols.ExecutorOptions 129 connConfiguration *httpclientpool.Configuration 130 totalRequests int 131 customHeaders map[string]string 132 generator *generators.PayloadGenerator // optional, only enabled when using payloads 133 httpClient *retryablehttp.Client 134 rawhttpClient *rawhttp.Client 135 136 // description: | 137 // SelfContained specifies if the request is self-contained. 138 SelfContained bool `yaml:"-" json:"-"` 139 140 // description: | 141 // Signature is the request signature method 142 // values: 143 // - "AWS" 144 Signature SignatureTypeHolder `yaml:"signature,omitempty" json:"signature,omitempty" jsonschema:"title=signature is the http request signature method,description=Signature is the HTTP Request signature Method,enum=AWS"` 145 146 // description: | 147 // CookieReuse is an optional setting that enables cookie reuse for 148 // all requests defined in raw section. 149 CookieReuse bool `yaml:"cookie-reuse,omitempty" json:"cookie-reuse,omitempty" jsonschema:"title=optional cookie reuse enable,description=Optional setting that enables cookie reuse"` 150 // description: | 151 // Enables force reading of the entire raw unsafe request body ignoring 152 // any specified content length headers. 153 ForceReadAllBody bool `yaml:"read-all,omitempty" json:"read-all,omitempty" jsonschema:"title=force read all body,description=Enables force reading of entire unsafe http request body"` 154 // description: | 155 // Redirects specifies whether redirects should be followed by the HTTP Client. 156 // 157 // This can be used in conjunction with `max-redirects` to control the HTTP request redirects. 158 Redirects bool `yaml:"redirects,omitempty" json:"redirects,omitempty" jsonschema:"title=follow http redirects,description=Specifies whether redirects should be followed by the HTTP Client"` 159 // description: | 160 // Redirects specifies whether only redirects to the same host should be followed by the HTTP Client. 161 // 162 // This can be used in conjunction with `max-redirects` to control the HTTP request redirects. 163 HostRedirects bool `yaml:"host-redirects,omitempty" json:"host-redirects,omitempty" jsonschema:"title=follow same host http redirects,description=Specifies whether redirects to the same host should be followed by the HTTP Client"` 164 // description: | 165 // Pipeline defines if the attack should be performed with HTTP 1.1 Pipelining 166 // 167 // All requests must be idempotent (GET/POST). This can be used for race conditions/billions requests. 168 Pipeline bool `yaml:"pipeline,omitempty" json:"pipeline,omitempty" jsonschema:"title=perform HTTP 1.1 pipelining,description=Pipeline defines if the attack should be performed with HTTP 1.1 Pipelining"` 169 // description: | 170 // Unsafe specifies whether to use rawhttp engine for sending Non RFC-Compliant requests. 171 // 172 // This uses the [rawhttp](https://github.com/projectdiscovery/rawhttp) engine to achieve complete 173 // control over the request, with no normalization performed by the client. 174 Unsafe bool `yaml:"unsafe,omitempty" json:"unsafe,omitempty" jsonschema:"title=use rawhttp non-strict-rfc client,description=Unsafe specifies whether to use rawhttp engine for sending Non RFC-Compliant requests"` 175 // description: | 176 // Race determines if all the request have to be attempted at the same time (Race Condition) 177 // 178 // The actual number of requests that will be sent is determined by the `race_count` field. 179 Race bool `yaml:"race,omitempty" json:"race,omitempty" jsonschema:"title=perform race-http request coordination attack,description=Race determines if all the request have to be attempted at the same time (Race Condition)"` 180 // description: | 181 // ReqCondition automatically assigns numbers to requests and preserves their history. 182 // 183 // This allows matching on them later for multi-request conditions. 184 // Deprecated: request condition will be detected automatically (https://github.com/projectdiscovery/nuclei/issues/2393) 185 ReqCondition bool `yaml:"req-condition,omitempty" json:"req-condition,omitempty" jsonschema:"title=preserve request history,description=Automatically assigns numbers to requests and preserves their history"` 186 // description: | 187 // StopAtFirstMatch stops the execution of the requests and template as soon as a match is found. 188 StopAtFirstMatch bool `yaml:"stop-at-first-match,omitempty" json:"stop-at-first-match,omitempty" jsonschema:"title=stop at first match,description=Stop the execution after a match is found"` 189 // description: | 190 // SkipVariablesCheck skips the check for unresolved variables in request 191 SkipVariablesCheck bool `yaml:"skip-variables-check,omitempty" json:"skip-variables-check,omitempty" jsonschema:"title=skip variable checks,description=Skips the check for unresolved variables in request"` 192 // description: | 193 // IterateAll iterates all the values extracted from internal extractors 194 IterateAll bool `yaml:"iterate-all,omitempty" json:"iterate-all,omitempty" jsonschema:"title=iterate all the values,description=Iterates all the values extracted from internal extractors"` 195 // description: | 196 // DigestAuthUsername specifies the username for digest authentication 197 DigestAuthUsername string `yaml:"digest-username,omitempty" json:"digest-username,omitempty" jsonschema:"title=specifies the username for digest authentication,description=Optional parameter which specifies the username for digest auth"` 198 // description: | 199 // DigestAuthPassword specifies the password for digest authentication 200 DigestAuthPassword string `yaml:"digest-password,omitempty" json:"digest-password,omitempty" jsonschema:"title=specifies the password for digest authentication,description=Optional parameter which specifies the password for digest auth"` 201 // description: | 202 // DisablePathAutomerge disables merging target url path with raw request path 203 DisablePathAutomerge bool `yaml:"disable-path-automerge,omitempty" json:"disable-path-automerge,omitempty" jsonschema:"title=disable auto merging of path,description=Disable merging target url path with raw request path"` 204 } 205 206 // Options returns executer options for http request 207 func (r *Request) Options() *protocols.ExecutorOptions { 208 return r.options 209 } 210 211 // RequestPartDefinitions contains a mapping of request part definitions and their 212 // description. Multiple definitions are separated by commas. 213 // Definitions not having a name (generated on runtime) are prefixed & suffixed by <>. 214 var RequestPartDefinitions = map[string]string{ 215 "template-id": "ID of the template executed", 216 "template-info": "Info Block of the template executed", 217 "template-path": "Path of the template executed", 218 "host": "Host is the input to the template", 219 "matched": "Matched is the input which was matched upon", 220 "type": "Type is the type of request made", 221 "request": "HTTP request made from the client", 222 "response": "HTTP response received from server", 223 "status_code": "Status Code received from the Server", 224 "body": "HTTP response body received from server (default)", 225 "content_length": "HTTP Response content length", 226 "header,all_headers": "HTTP response headers", 227 "duration": "HTTP request time duration", 228 "all": "HTTP response body + headers", 229 "cookies_from_response": "HTTP response cookies in name:value format", 230 "headers_from_response": "HTTP response headers in name:value format", 231 } 232 233 // GetID returns the unique ID of the request if any. 234 func (request *Request) GetID() string { 235 return request.ID 236 } 237 238 func (request *Request) isRaw() bool { 239 return len(request.Raw) > 0 240 } 241 242 // Compile compiles the protocol request for further execution. 243 func (request *Request) Compile(options *protocols.ExecutorOptions) error { 244 if err := request.validate(); err != nil { 245 return errors.Wrap(err, "validation error") 246 } 247 248 connectionConfiguration := &httpclientpool.Configuration{ 249 Threads: request.Threads, 250 MaxRedirects: request.MaxRedirects, 251 NoTimeout: false, 252 CookieReuse: request.CookieReuse, 253 Connection: &httpclientpool.ConnectionConfiguration{ 254 DisableKeepAlive: httputil.ShouldDisableKeepAlive(options.Options), 255 }, 256 RedirectFlow: httpclientpool.DontFollowRedirect, 257 } 258 259 if request.Redirects || options.Options.FollowRedirects { 260 connectionConfiguration.RedirectFlow = httpclientpool.FollowAllRedirect 261 } 262 if request.HostRedirects || options.Options.FollowHostRedirects { 263 connectionConfiguration.RedirectFlow = httpclientpool.FollowSameHostRedirect 264 } 265 266 // If we have request level timeout, ignore http client timeouts 267 for _, req := range request.Raw { 268 if reTimeoutAnnotation.MatchString(req) { 269 connectionConfiguration.NoTimeout = true 270 } 271 } 272 request.connConfiguration = connectionConfiguration 273 274 client, err := httpclientpool.Get(options.Options, connectionConfiguration) 275 if err != nil { 276 return errors.Wrap(err, "could not get dns client") 277 } 278 request.customHeaders = make(map[string]string) 279 request.httpClient = client 280 request.options = options 281 for _, option := range request.options.Options.CustomHeaders { 282 parts := strings.SplitN(option, ":", 2) 283 if len(parts) != 2 { 284 continue 285 } 286 request.customHeaders[parts[0]] = strings.TrimSpace(parts[1]) 287 } 288 289 if request.Body != "" && !strings.Contains(request.Body, "\r\n") { 290 request.Body = strings.ReplaceAll(request.Body, "\n", "\r\n") 291 } 292 if len(request.Raw) > 0 { 293 for i, raw := range request.Raw { 294 if !strings.Contains(raw, "\r\n") { 295 request.Raw[i] = strings.ReplaceAll(raw, "\n", "\r\n") 296 } 297 } 298 request.rawhttpClient = httpclientpool.GetRawHTTP(options.Options) 299 } 300 if len(request.Matchers) > 0 || len(request.Extractors) > 0 { 301 compiled := &request.Operators 302 compiled.ExcludeMatchers = options.ExcludeMatchers 303 compiled.TemplateID = options.TemplateID 304 if compileErr := compiled.Compile(); compileErr != nil { 305 return errors.Wrap(compileErr, "could not compile operators") 306 } 307 request.CompiledOperators = compiled 308 } 309 310 // Resolve payload paths from vars if they exists 311 for name, payload := range request.options.Options.Vars.AsMap() { 312 payloadStr, ok := payload.(string) 313 // check if inputs contains the payload 314 var hasPayloadName bool 315 // search for markers in all request parts 316 var inputs []string 317 inputs = append(inputs, request.Method.String(), request.Body) 318 inputs = append(inputs, request.Raw...) 319 for k, v := range request.customHeaders { 320 inputs = append(inputs, fmt.Sprintf("%s: %s", k, v)) 321 } 322 for k, v := range request.Headers { 323 inputs = append(inputs, fmt.Sprintf("%s: %s", k, v)) 324 } 325 326 for _, input := range inputs { 327 if expressions.ContainsVariablesWithNames(map[string]interface{}{name: payload}, input) == nil { 328 hasPayloadName = true 329 break 330 } 331 } 332 if ok && hasPayloadName && fileutil.FileExists(payloadStr) { 333 if request.Payloads == nil { 334 request.Payloads = make(map[string]interface{}) 335 } 336 request.Payloads[name] = payloadStr 337 } 338 } 339 340 // tries to drop unused payloads - by marshaling sections that might contain the payload 341 unusedPayloads := make(map[string]struct{}) 342 requestSectionsToCheck := []interface{}{ 343 request.customHeaders, request.Headers, request.Matchers, 344 request.Extractors, request.Body, request.Path, request.Raw, request.Fuzzing, 345 } 346 if requestSectionsToCheckData, err := json.Marshal(requestSectionsToCheck); err == nil { 347 for payload := range request.Payloads { 348 if bytes.Contains(requestSectionsToCheckData, []byte(payload)) { 349 continue 350 } 351 unusedPayloads[payload] = struct{}{} 352 } 353 } 354 for payload := range unusedPayloads { 355 delete(request.Payloads, payload) 356 } 357 358 if len(request.Payloads) > 0 { 359 request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Options.AllowLocalFileAccess, request.options.Catalog, request.options.Options.AttackType) 360 if err != nil { 361 return errors.Wrap(err, "could not parse payloads") 362 } 363 } 364 request.options = options 365 request.totalRequests = request.Requests() 366 367 if len(request.Fuzzing) > 0 { 368 if request.Unsafe { 369 return errors.New("cannot use unsafe with http fuzzing templates") 370 } 371 for _, rule := range request.Fuzzing { 372 if fuzzingMode := options.Options.FuzzingMode; fuzzingMode != "" { 373 rule.Mode = fuzzingMode 374 } 375 if fuzzingType := options.Options.FuzzingType; fuzzingType != "" { 376 rule.Type = fuzzingType 377 } 378 if err := rule.Compile(request.generator, request.options); err != nil { 379 return errors.Wrap(err, "could not compile fuzzing rule") 380 } 381 } 382 } 383 return nil 384 } 385 386 // Requests returns the total number of requests the YAML rule will perform 387 func (request *Request) Requests() int { 388 if request.generator != nil { 389 payloadRequests := request.generator.NewIterator().Total() 390 if len(request.Raw) > 0 { 391 payloadRequests = payloadRequests * len(request.Raw) 392 } 393 if len(request.Path) > 0 { 394 payloadRequests = payloadRequests * len(request.Path) 395 } 396 return payloadRequests 397 } 398 if len(request.Raw) > 0 { 399 requests := len(request.Raw) 400 if requests == 1 && request.RaceNumberRequests != 0 { 401 requests *= request.RaceNumberRequests 402 } 403 return requests 404 } 405 return len(request.Path) 406 }