Dwi Siswanto 13af7ccd49
fix: data race at protocolstate, contextargs & some outdated test cases (#5820)
* test(flow): update outdated test cases

Signed-off-by: Dwi Siswanto <git@dw1.io>

* test(multiproto): update outdated test cases

Signed-off-by: Dwi Siswanto <git@dw1.io>

* feat: fixed failing tests

* fixed data race

* fixed memgaurdian race conditiong

* test(customtemplates): use test repo

Signed-off-by: Dwi Siswanto <git@dw1.io>

* feat(customtemplates): add more `{Clone,Pull}Options`

Signed-off-by: Dwi Siswanto <git@dw1.io>

* feat(customtemplates): validate `{Clone,Pull}Options`

Signed-off-by: Dwi Siswanto <git@dw1.io>

* bugfix: fixed failing integration tests for flow and multi

* chore: either 1 or 2 results in interactsh

---------

Signed-off-by: Dwi Siswanto <git@dw1.io>
Co-authored-by: Ice3man <nizamulrana@gmail.com>
2024-11-19 11:08:25 +05:30

190 lines
5.0 KiB
Go

package contextargs
import (
"bytes"
"crypto/md5"
"fmt"
"net"
"strings"
"sync"
jsoniter "github.com/json-iterator/go"
"github.com/projectdiscovery/nuclei/v3/pkg/input/types"
urlutil "github.com/projectdiscovery/utils/url"
"github.com/segmentio/ksuid"
)
// MetaInput represents a target with metadata (TODO: replace with https://github.com/projectdiscovery/metainput)
type MetaInput struct {
// Input represent the target
Input string `json:"input,omitempty"`
// CustomIP to use for connection
CustomIP string `json:"customIP,omitempty"`
// hash of the input
hash string `json:"-"`
// ReqResp is the raw request for the input
ReqResp *types.RequestResponse `json:"raw-request,omitempty"`
mu *sync.Mutex
}
func NewMetaInput() *MetaInput {
return &MetaInput{mu: &sync.Mutex{}}
}
func (metaInput *MetaInput) marshalToBuffer() (bytes.Buffer, error) {
var b bytes.Buffer
err := jsoniter.NewEncoder(&b).Encode(metaInput)
return b, err
}
// Target returns the target of the metainput
func (metaInput *MetaInput) Target() string {
if metaInput.ReqResp != nil && metaInput.ReqResp.URL.URL != nil {
return metaInput.ReqResp.URL.String()
}
return metaInput.Input
}
// URL returns request url
func (metaInput *MetaInput) URL() (*urlutil.URL, error) {
instance, err := urlutil.ParseAbsoluteURL(metaInput.Target(), false)
if err != nil {
return nil, err
}
return instance, nil
}
// Port returns the port of the target
// if port is not present then empty string is returned
func (metaInput *MetaInput) Port() string {
target, err := urlutil.ParseAbsoluteURL(metaInput.Input, false)
if err != nil {
return ""
}
return target.Port()
}
// Address return the remote address of target
// Note: it does not resolve the domain to ip
// it is meant to be used by hosterrorsCache if invalid metainput
// is provided then it uses a random ksuid as hostname to avoid skipping valid targets
func (metaInput *MetaInput) Address() string {
var hostname, port string
target, err := urlutil.ParseAbsoluteURL(metaInput.Target(), false)
if err != nil {
if metaInput.CustomIP == "" {
// since this is used in hosterrorscache we add a random id
// which will never be used to avoid skipping valid targets
hostname = fmt.Sprintf("invalid-%s", ksuid.New().String())
}
} else {
hostname = target.Hostname()
port = target.Port()
if port == "" {
switch target.Scheme {
case urlutil.HTTP:
port = "80"
case urlutil.HTTPS:
port = "443"
default:
port = "80"
}
}
}
if metaInput.CustomIP != "" {
hostname = metaInput.CustomIP
}
if port == "" {
if strings.HasPrefix(hostname, "http://") {
port = "80"
} else if strings.HasPrefix(hostname, "https://") {
port = "443"
}
}
return net.JoinHostPort(hostname, port)
}
// ID returns a unique id/hash for metainput
func (metaInput *MetaInput) ID() string {
if metaInput.CustomIP != "" {
return fmt.Sprintf("%s-%s", metaInput.Input, metaInput.CustomIP)
}
if metaInput.ReqResp != nil {
return metaInput.ReqResp.ID()
}
return metaInput.Input
}
func (metaInput *MetaInput) MarshalString() (string, error) {
b, err := metaInput.marshalToBuffer()
return b.String(), err
}
func (metaInput *MetaInput) MustMarshalString() string {
marshaled, _ := metaInput.MarshalString()
return marshaled
}
func (metaInput *MetaInput) MarshalBytes() ([]byte, error) {
b, err := metaInput.marshalToBuffer()
return b.Bytes(), err
}
func (metaInput *MetaInput) MustMarshalBytes() []byte {
marshaled, _ := metaInput.MarshalBytes()
return marshaled
}
func (metaInput *MetaInput) Unmarshal(data string) error {
return jsoniter.NewDecoder(strings.NewReader(data)).Decode(metaInput)
}
func (metaInput *MetaInput) Clone() *MetaInput {
metaInput.mu.Lock()
defer metaInput.mu.Unlock()
input := NewMetaInput()
input.Input = metaInput.Input
input.CustomIP = metaInput.CustomIP
input.hash = metaInput.hash
if metaInput.ReqResp != nil {
input.ReqResp = metaInput.ReqResp.Clone()
}
return input
}
func (metaInput *MetaInput) PrettyPrint() string {
if metaInput.CustomIP != "" {
return fmt.Sprintf("%s [%s]", metaInput.Input, metaInput.CustomIP)
}
if metaInput.ReqResp != nil {
return fmt.Sprintf("%s [%s]", metaInput.ReqResp.URL.String(), metaInput.ReqResp.Request.Method)
}
return metaInput.Input
}
// GetScanHash returns a unique hash that represents a scan by hashing (metainput + templateId)
func (metaInput *MetaInput) GetScanHash(templateId string) string {
// there may be some cases where metainput is changed ex: while executing self-contained template etc
// but that totally changes the scanID/hash so to avoid that we compute hash only once
// and reuse it for all subsequent calls
metaInput.mu.Lock()
defer metaInput.mu.Unlock()
if metaInput.hash == "" {
var rawRequest string
if metaInput.ReqResp != nil {
rawRequest = metaInput.ReqResp.ID()
}
metaInput.hash = getMd5Hash(templateId + ":" + metaInput.Input + ":" + metaInput.CustomIP + rawRequest)
}
return metaInput.hash
}
func getMd5Hash(data string) string {
bin := md5.Sum([]byte(data))
return string(bin[:])
}