github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/network/request.go (about) 1 package network 2 3 import ( 4 "context" 5 "encoding/hex" 6 "fmt" 7 "io" 8 "net" 9 "net/url" 10 "os" 11 "strings" 12 "time" 13 14 "github.com/pkg/errors" 15 "golang.org/x/exp/maps" 16 17 "github.com/projectdiscovery/gologger" 18 "github.com/projectdiscovery/nuclei/v2/pkg/operators" 19 "github.com/projectdiscovery/nuclei/v2/pkg/output" 20 "github.com/projectdiscovery/nuclei/v2/pkg/protocols" 21 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" 22 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions" 23 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" 24 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator" 25 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter" 26 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh" 27 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/replacer" 28 "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" 29 protocolutils "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils" 30 templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" 31 errorutil "github.com/projectdiscovery/utils/errors" 32 mapsutil "github.com/projectdiscovery/utils/maps" 33 ) 34 35 var _ protocols.Request = &Request{} 36 37 // Type returns the type of the protocol request 38 func (request *Request) Type() templateTypes.ProtocolType { 39 return templateTypes.NetworkProtocol 40 } 41 42 // ExecuteWithResults executes the protocol requests and returns results instead of writing them. 43 func (request *Request) ExecuteWithResults(target *contextargs.Context, metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error { 44 var address string 45 var err error 46 47 input := target.Clone() 48 // use network port updates input with new port requested in template file 49 // and it is ignored if input port is not standard http(s) ports like 80,8080,8081 etc 50 // idea is to reduce redundant dials to http ports 51 if err := input.UseNetworkPort(request.Port, request.ExcludePorts); err != nil { 52 gologger.Debug().Msgf("Could not network port from constants: %s\n", err) 53 } 54 55 if request.SelfContained { 56 address = "" 57 } else { 58 address, err = getAddress(input.MetaInput.Input) 59 } 60 if err != nil { 61 request.options.Output.Request(request.options.TemplatePath, input.MetaInput.Input, request.Type().String(), err) 62 request.options.Progress.IncrementFailedRequestsBy(1) 63 return errors.Wrap(err, "could not get address from url") 64 } 65 variables := protocolutils.GenerateVariables(address, false, nil) 66 variablesMap := request.options.Variables.Evaluate(variables) 67 variables = generators.MergeMaps(variablesMap, variables, request.options.Constants) 68 69 visitedAddresses := make(mapsutil.Map[string, struct{}]) 70 71 for _, kv := range request.addresses { 72 actualAddress := replacer.Replace(kv.address, variables) 73 74 if visitedAddresses.Has(actualAddress) && !request.options.Options.DisableClustering { 75 continue 76 } 77 visitedAddresses.Set(actualAddress, struct{}{}) 78 79 if err := request.executeAddress(variables, actualAddress, address, input.MetaInput.Input, kv.tls, previous, callback); err != nil { 80 outputEvent := request.responseToDSLMap("", "", "", address, "") 81 callback(&output.InternalWrappedEvent{InternalEvent: outputEvent}) 82 gologger.Warning().Msgf("[%v] Could not make network request for (%s) : %s\n", request.options.TemplateID, actualAddress, err) 83 continue 84 } 85 } 86 return nil 87 } 88 89 // executeAddress executes the request for an address 90 func (request *Request) executeAddress(variables map[string]interface{}, actualAddress, address, input string, shouldUseTLS bool, previous output.InternalEvent, callback protocols.OutputEventCallback) error { 91 variables = generators.MergeMaps(variables, map[string]interface{}{"Hostname": address}) 92 payloads := generators.BuildPayloadFromOptions(request.options.Options) 93 94 if !strings.Contains(actualAddress, ":") { 95 err := errors.New("no port provided in network protocol request") 96 request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err) 97 request.options.Progress.IncrementFailedRequestsBy(1) 98 return err 99 } 100 101 if request.generator != nil { 102 iterator := request.generator.NewIterator() 103 104 for { 105 value, ok := iterator.Value() 106 if !ok { 107 break 108 } 109 value = generators.MergeMaps(value, payloads) 110 if err := request.executeRequestWithPayloads(variables, actualAddress, address, input, shouldUseTLS, value, previous, callback); err != nil { 111 return err 112 } 113 } 114 } else { 115 value := maps.Clone(payloads) 116 if err := request.executeRequestWithPayloads(variables, actualAddress, address, input, shouldUseTLS, value, previous, callback); err != nil { 117 return err 118 } 119 } 120 return nil 121 } 122 123 func (request *Request) executeRequestWithPayloads(variables map[string]interface{}, actualAddress, address, input string, shouldUseTLS bool, payloads map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback) error { 124 var ( 125 hostname string 126 conn net.Conn 127 err error 128 ) 129 if host, _, err := net.SplitHostPort(actualAddress); err == nil { 130 hostname = host 131 } 132 133 if shouldUseTLS { 134 conn, err = request.dialer.DialTLS(context.Background(), "tcp", actualAddress) 135 } else { 136 conn, err = request.dialer.Dial(context.Background(), "tcp", actualAddress) 137 } 138 if err != nil { 139 request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err) 140 request.options.Progress.IncrementFailedRequestsBy(1) 141 return errors.Wrap(err, "could not connect to server") 142 } 143 defer conn.Close() 144 _ = conn.SetDeadline(time.Now().Add(time.Duration(request.options.Options.Timeout) * time.Second)) 145 146 var interactshURLs []string 147 148 var responseBuilder, reqBuilder strings.Builder 149 150 interimValues := generators.MergeMaps(variables, payloads) 151 152 if vardump.EnableVarDump { 153 gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(interimValues)) 154 } 155 156 inputEvents := make(map[string]interface{}) 157 158 for _, input := range request.Inputs { 159 data := []byte(input.Data) 160 161 if request.options.Interactsh != nil { 162 var transformedData string 163 transformedData, interactshURLs = request.options.Interactsh.Replace(string(data), []string{}) 164 data = []byte(transformedData) 165 } 166 167 finalData, err := expressions.EvaluateByte(data, interimValues) 168 if err != nil { 169 request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err) 170 request.options.Progress.IncrementFailedRequestsBy(1) 171 return errors.Wrap(err, "could not evaluate template expressions") 172 } 173 174 reqBuilder.Write(finalData) 175 176 if err := expressions.ContainsUnresolvedVariables(string(finalData)); err != nil { 177 gologger.Warning().Msgf("[%s] Could not make network request for %s: %v\n", request.options.TemplateID, actualAddress, err) 178 return nil 179 } 180 181 if input.Type.GetType() == hexType { 182 finalData, err = hex.DecodeString(string(finalData)) 183 if err != nil { 184 request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err) 185 request.options.Progress.IncrementFailedRequestsBy(1) 186 return errors.Wrap(err, "could not write request to server") 187 } 188 } 189 190 if _, err := conn.Write(finalData); err != nil { 191 request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err) 192 request.options.Progress.IncrementFailedRequestsBy(1) 193 return errors.Wrap(err, "could not write request to server") 194 } 195 196 if input.Read > 0 { 197 buffer := make([]byte, input.Read) 198 n, err := conn.Read(buffer) 199 if err != nil { 200 return errorutil.NewWithErr(err).Msgf("could not read response from connection") 201 } 202 203 responseBuilder.Write(buffer[:n]) 204 205 bufferStr := string(buffer[:n]) 206 if input.Name != "" { 207 inputEvents[input.Name] = bufferStr 208 interimValues[input.Name] = bufferStr 209 } 210 211 // Run any internal extractors for the request here and add found values to map. 212 if request.CompiledOperators != nil { 213 values := request.CompiledOperators.ExecuteInternalExtractors(map[string]interface{}{input.Name: bufferStr}, request.Extract) 214 for k, v := range values { 215 payloads[k] = v 216 } 217 } 218 } 219 } 220 221 request.options.Progress.IncrementRequests() 222 223 if request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse { 224 requestBytes := []byte(reqBuilder.String()) 225 msg := fmt.Sprintf("[%s] Dumped Network request for %s\n%s", request.options.TemplateID, actualAddress, hex.Dump(requestBytes)) 226 if request.options.Options.Debug || request.options.Options.DebugRequests { 227 gologger.Info().Str("address", actualAddress).Msg(msg) 228 } 229 if request.options.Options.StoreResponse { 230 request.options.Output.WriteStoreDebugData(address, request.options.TemplateID, request.Type().String(), msg) 231 } 232 if request.options.Options.VerboseVerbose { 233 gologger.Print().Msgf("\nCompact HEX view:\n%s", hex.EncodeToString(requestBytes)) 234 } 235 } 236 237 request.options.Output.Request(request.options.TemplatePath, actualAddress, request.Type().String(), err) 238 gologger.Verbose().Msgf("Sent TCP request to %s", actualAddress) 239 240 bufferSize := 1024 241 if request.ReadSize != 0 { 242 bufferSize = request.ReadSize 243 } 244 245 var ( 246 final []byte 247 n int 248 ) 249 250 if request.ReadAll { 251 readInterval := time.NewTimer(time.Second * 1) 252 // stop the timer and drain the channel 253 closeTimer := func(t *time.Timer) { 254 if !t.Stop() { 255 <-t.C 256 } 257 } 258 readSocket: 259 for { 260 select { 261 case <-readInterval.C: 262 closeTimer(readInterval) 263 break readSocket 264 default: 265 buf := make([]byte, bufferSize) 266 nBuf, err := conn.Read(buf) 267 if err != nil && !os.IsTimeout(err) && err != io.EOF { 268 request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err) 269 closeTimer(readInterval) 270 return errors.Wrap(err, "could not read from server") 271 } 272 responseBuilder.Write(buf[:nBuf]) 273 final = append(final, buf...) 274 n += nBuf 275 } 276 } 277 } else { 278 final = make([]byte, bufferSize) 279 n, err = conn.Read(final) 280 if err != nil && !os.IsTimeout(err) && err != io.EOF { 281 request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err) 282 return errors.Wrap(err, "could not read from server") 283 } 284 responseBuilder.Write(final[:n]) 285 } 286 287 response := responseBuilder.String() 288 outputEvent := request.responseToDSLMap(reqBuilder.String(), string(final[:n]), response, input, actualAddress) 289 outputEvent["ip"] = request.dialer.GetDialedIP(hostname) 290 if request.options.StopAtFirstMatch { 291 outputEvent["stop-at-first-match"] = true 292 } 293 for k, v := range previous { 294 outputEvent[k] = v 295 } 296 for k, v := range interimValues { 297 outputEvent[k] = v 298 } 299 for k, v := range inputEvents { 300 outputEvent[k] = v 301 } 302 if request.options.Interactsh != nil { 303 request.options.Interactsh.MakePlaceholders(interactshURLs, outputEvent) 304 } 305 306 var event *output.InternalWrappedEvent 307 if len(interactshURLs) == 0 { 308 event = eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(payloads, outputEvent), request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) { 309 wrappedEvent.OperatorsResult.PayloadValues = payloads 310 }) 311 callback(event) 312 } else if request.options.Interactsh != nil { 313 event = &output.InternalWrappedEvent{InternalEvent: outputEvent} 314 request.options.Interactsh.RequestEvent(interactshURLs, &interactsh.RequestData{ 315 MakeResultFunc: request.MakeResultEvent, 316 Event: event, 317 Operators: request.CompiledOperators, 318 MatchFunc: request.Match, 319 ExtractFunc: request.Extract, 320 }) 321 } 322 if len(interactshURLs) > 0 { 323 event.UsesInteractsh = true 324 } 325 326 dumpResponse(event, request, response, actualAddress, address) 327 328 return nil 329 } 330 331 func dumpResponse(event *output.InternalWrappedEvent, request *Request, response string, actualAddress, address string) { 332 cliOptions := request.options.Options 333 if cliOptions.Debug || cliOptions.DebugResponse || cliOptions.StoreResponse { 334 requestBytes := []byte(response) 335 highlightedResponse := responsehighlighter.Highlight(event.OperatorsResult, hex.Dump(requestBytes), cliOptions.NoColor, true) 336 msg := fmt.Sprintf("[%s] Dumped Network response for %s\n\n", request.options.TemplateID, actualAddress) 337 if cliOptions.Debug || cliOptions.DebugResponse { 338 gologger.Debug().Msg(fmt.Sprintf("%s%s", msg, highlightedResponse)) 339 } 340 if cliOptions.StoreResponse { 341 request.options.Output.WriteStoreDebugData(address, request.options.TemplateID, request.Type().String(), fmt.Sprintf("%s%s", msg, hex.Dump(requestBytes))) 342 } 343 if cliOptions.VerboseVerbose { 344 displayCompactHexView(event, response, cliOptions.NoColor) 345 } 346 } 347 } 348 349 func displayCompactHexView(event *output.InternalWrappedEvent, response string, noColor bool) { 350 operatorsResult := event.OperatorsResult 351 if operatorsResult != nil { 352 var allMatches []string 353 for _, namedMatch := range operatorsResult.Matches { 354 for _, matchElement := range namedMatch { 355 allMatches = append(allMatches, hex.EncodeToString([]byte(matchElement))) 356 } 357 } 358 tempOperatorResult := &operators.Result{Matches: map[string][]string{"matchesInHex": allMatches}} 359 gologger.Print().Msgf("\nCompact HEX view:\n%s", responsehighlighter.Highlight(tempOperatorResult, hex.EncodeToString([]byte(response)), noColor, false)) 360 } 361 } 362 363 // getAddress returns the address of the host to make request to 364 func getAddress(toTest string) (string, error) { 365 if strings.Contains(toTest, "://") { 366 parsed, err := url.Parse(toTest) 367 if err != nil { 368 return "", err 369 } 370 toTest = parsed.Host 371 } 372 return toTest, nil 373 }