mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2026-11-09 12:21:25 -10:00
Scanning a vhost on a given server, or an origin behind a CDN, needs the
hostname in the Host header, SNI and template variables while the
connection goes to another ip, and one host often has to be scanned on
several ips. -sa scans the ips DNS returns, and a target of the ip with
a Host header sends no SNI and sets {{Hostname}} to the ip.
A target line can now pin its target to an ip, like curl --resolve:
{"target": "https://app.example", "ip": "192.0.2.10"}
Each target and ip pair is a separate target with its own template
selection. The ip is validated and canonicalized, the target must have
a hostname, -sa and -iv don't resolve a pinned target, and an ip the
network policy denies is skipped. Pinned targets are refused with
-proxy and -automatic-scan.
The engine pins the host of every target with a CustomIP, from a target
line or -sa, with fastdialer.WithResolveOverride on the context each
template runs with. HTTP, network, websocket and javascript templates
then connect to the ip, while redirects to other hosts and the proxy
dial are no longer pinned. HTTP keeps a client per pinned ip, network
and websocket results report the ip, and scheme-less targets are probed
through it.
The host errors cache keys a failed dial of a pinned target by its ip,
also for IDN hosts, and -mup counts the urls of each ip apart, so a url
listed with several ips, or resolved to several with -sa, is kept on
each of them.
go.mod points fastdialer at a fork commit with WithResolveOverride until
a fastdialer release includes it.
113 lines
3.0 KiB
Go
113 lines
3.0 KiB
Go
package utils
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/netip"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/projectdiscovery/httpx/common/httpx"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/input/types"
|
|
"github.com/projectdiscovery/useragent"
|
|
sliceutil "github.com/projectdiscovery/utils/slice"
|
|
)
|
|
|
|
var commonHttpPorts = []string{
|
|
"80",
|
|
"8080",
|
|
}
|
|
var defaultHttpSchemes = []string{
|
|
"https",
|
|
"http",
|
|
}
|
|
var httpFirstSchemes = []string{
|
|
"http",
|
|
"https",
|
|
}
|
|
|
|
// DetermineSchemeOrder returns the schemes to try for a scheme-less input,
|
|
// most likely first
|
|
func DetermineSchemeOrder(input string) []string {
|
|
if _, port, err := net.SplitHostPort(input); err == nil {
|
|
// if input has port that is commonly used for HTTP, return http then https
|
|
if sliceutil.Contains(commonHttpPorts, port) {
|
|
return httpFirstSchemes
|
|
}
|
|
|
|
// As of 10/2025 shodan shows that ports > 1024 are more likely to expose HTTP
|
|
// hence we test first http then https on higher ports
|
|
// if input has port > 1024, return http then https
|
|
if port, err := strconv.Atoi(port); err == nil && port > 1024 {
|
|
return httpFirstSchemes
|
|
}
|
|
}
|
|
|
|
return defaultHttpSchemes
|
|
}
|
|
|
|
// ProbeURL probes the scheme for a URL.
|
|
// http schemes are selected with heuristics
|
|
// If none succeeds, probing is abandoned for such URLs.
|
|
func ProbeURL(input string, httpxclient *httpx.HTTPX) string {
|
|
return ProbeURLWithContext(context.Background(), input, httpxclient)
|
|
}
|
|
|
|
// ProbeURLWithContext is ProbeURL with the probe requests made with ctx.
|
|
func ProbeURLWithContext(ctx context.Context, input string, httpxclient *httpx.HTTPX) string {
|
|
normalizedInput := normalizeProbeInput(input)
|
|
schemes := DetermineSchemeOrder(normalizedInput)
|
|
for _, scheme := range schemes {
|
|
formedURL := fmt.Sprintf("%s://%s", scheme, normalizedInput)
|
|
req, err := httpxclient.NewRequestWithContext(ctx, http.MethodHead, formedURL)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
userAgent := useragent.PickRandom()
|
|
req.Header.Set("User-Agent", userAgent.Raw)
|
|
|
|
if _, err = httpxclient.Do(req, httpx.UnsafeOptions{}); err != nil {
|
|
continue
|
|
}
|
|
|
|
return formedURL
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// normalizeProbeInput rewrites unbracketed IPv6 literals to bracketed host form.
|
|
func normalizeProbeInput(input string) string {
|
|
if strings.Contains(input, "://") || strings.HasPrefix(input, "[") {
|
|
return input
|
|
}
|
|
addr, err := netip.ParseAddr(input)
|
|
if err != nil || !addr.Is6() {
|
|
return input
|
|
}
|
|
return fmt.Sprintf("[%s]", addr.String())
|
|
}
|
|
|
|
type inputLivenessChecker struct {
|
|
client *httpx.HTTPX
|
|
}
|
|
|
|
// ProbeURL probes the scheme for a URL.
|
|
func (i *inputLivenessChecker) ProbeURL(input string) (string, error) {
|
|
return ProbeURL(input, i.client), nil
|
|
}
|
|
|
|
func (i *inputLivenessChecker) Close() error {
|
|
if i.client.Dialer != nil {
|
|
i.client.Dialer.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetInputLivenessChecker returns a new input liveness checker using provided httpx client
|
|
func GetInputLivenessChecker(client *httpx.HTTPX) types.InputLivenessProbe {
|
|
x := &inputLivenessChecker{client: client}
|
|
return x
|
|
}
|