scanallip handle edge cases (#3080)

* bug fix:remove port during dns resolution

* scanallip fix edge cases

* add scanallips testcases

* workflow fix

* removing pull cmd

* Auto Generate Syntax Docs + JSONSchema [Sat Dec 24 13:29:21 UTC 2022] 🤖

Co-authored-by: sandeep <8293321+ehsandeep@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
dev
Tarun Koyalwar 2022-12-24 19:03:23 +05:30 committed by GitHub
parent 96646c8f53
commit aee0870617
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 141 additions and 53 deletions

View File

@ -12,6 +12,8 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: "Set up Go"
uses: actions/setup-go@v3
@ -36,7 +38,6 @@ jobs:
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git pull
git add SYNTAX-REFERENCE.md nuclei-jsonschema.json
git commit -m "Auto Generate Syntax Docs + JSONSchema [$(date)] :robot:" -a

View File

@ -3519,6 +3519,19 @@ description: |
<div class="dd">
<code>stop-at-first-match</code> <i>bool</i>
</div>
<div class="dt">
StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.
</div>
<hr />
<div class="dd">
<code>matchers</code> <i>[]<a href="#matchersmatcher">matchers.Matcher</a></i>
</div>

View File

@ -576,6 +576,11 @@
"title": "custom user agent for the headless request",
"description": "Custom user agent for the headless request"
},
"stop-at-first-match": {
"type": "boolean",
"title": "stop at first match",
"description": "Stop the execution after a match is found"
},
"matchers": {
"items": {
"$ref": "#/definitions/matchers.Matcher"

View File

@ -5,7 +5,7 @@ package hybrid
import (
"bufio"
"io"
"net/url"
"net"
"os"
"strings"
"sync"
@ -22,6 +22,7 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/uncover"
"github.com/projectdiscovery/nuclei/v2/pkg/types"
"github.com/projectdiscovery/nuclei/v2/pkg/utils"
fileutil "github.com/projectdiscovery/utils/file"
iputil "github.com/projectdiscovery/utils/ip"
readerutil "github.com/projectdiscovery/utils/reader"
@ -169,39 +170,49 @@ func (i *Input) Set(value string) {
if URL == "" {
return
}
// actual hostname
var host string
// parse hostname if url is given
parsedURL, err := url.Parse(value)
if err == nil && parsedURL.Host != "" {
host = parsedURL.Host
host := utils.ParseHostname(value)
if host == "" {
// not a valid url hence scanallips is skipped
gologger.Debug().Msgf("scanAllIps: failed to parse hostname of %v falling back to default", value)
i.setItem(&contextargs.MetaInput{Input: value})
return
} else {
parsedURL = nil
host = value
// case when hostname contains port
hostwithoutport, _, erx := net.SplitHostPort(host)
if erx == nil && hostwithoutport != "" {
// given host contains port
host = hostwithoutport
}
}
if i.ipOptions.ScanAllIPs {
// scan all ips
dnsData, err := protocolstate.Dialer.GetDNSData(host)
if err == nil && (len(dnsData.A)+len(dnsData.AAAA)) > 0 {
var ips []string
if i.ipOptions.IPV4 {
ips = append(ips, dnsData.A...)
}
if i.ipOptions.IPV6 {
ips = append(ips, dnsData.AAAA...)
}
for _, ip := range ips {
if ip == "" {
continue
if err == nil {
if (len(dnsData.A) + len(dnsData.AAAA)) > 0 {
var ips []string
if i.ipOptions.IPV4 {
ips = append(ips, dnsData.A...)
}
metaInput := &contextargs.MetaInput{Input: value, CustomIP: ip}
i.setItem(metaInput)
if i.ipOptions.IPV6 {
ips = append(ips, dnsData.AAAA...)
}
for _, ip := range ips {
if ip == "" {
continue
}
metaInput := &contextargs.MetaInput{Input: value, CustomIP: ip}
i.setItem(metaInput)
}
return
} else {
gologger.Debug().Msgf("scanAllIps: no ip's found reverting to default")
}
return
} else {
// failed to scanallips falling back to defaults
gologger.Debug().Msgf("scanAllIps: dns resolution failed: %v", err)
}
// failed to scanallips falling back to defaults
gologger.Error().Msgf("failed to scan all ips reverting to default %v", err)
}
ips := []string{}
@ -212,7 +223,7 @@ func (i *Input) Set(value string) {
// pick/ prefer 1st
ips = append(ips, dnsData.AAAA[0])
} else {
gologger.Warning().Msgf("target does not have ipv6 address falling back to ipv4 %s\n", err)
gologger.Warning().Msgf("target does not have ipv6 address falling back to ipv4 %v\n", err)
}
}
if i.ipOptions.IPV4 {

View File

@ -51,7 +51,7 @@ func Test_expandCIDRInputValue(t *testing.T) {
type mockDnsHandler struct{}
func (this *mockDnsHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
func (m *mockDnsHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
msg := dns.Msg{}
msg.SetReply(r)
switch r.Question[0].Qtype {
@ -85,18 +85,14 @@ func Test_scanallips_normalizeStoreInputValue(t *testing.T) {
defaultOpts := types.DefaultOptions()
defaultOpts.InternalResolversList = []string{"127.0.0.1:61234"}
_ = protocolstate.Init(defaultOpts)
tests := []struct {
type testcase struct {
hostname string
ipv4 bool
ipv6 bool
expected []string
}{
}
tests := []testcase{
{
hostname: "scanme.sh",
ipv4: true,
ipv6: true,
expected: []string{"128.199.158.128", "2400:6180:0:d0::91:1001"},
}, {
hostname: "scanme.sh",
ipv4: true,
expected: []string{"128.199.158.128"},
@ -104,12 +100,27 @@ func Test_scanallips_normalizeStoreInputValue(t *testing.T) {
hostname: "scanme.sh",
ipv6: true,
expected: []string{"2400:6180:0:d0::91:1001"},
}, {
hostname: "http://scanme.sh",
},
}
// add extra edge cases
urls := []string{
"https://scanme.sh/",
"http://scanme.sh",
"https://scanme.sh:443/",
"https://scanme.sh:443/somepath",
"http://scanme.sh:80/?with=param",
"scanme.sh/home",
"scanme.sh",
}
resolvedIps := []string{"128.199.158.128", "2400:6180:0:d0::91:1001"}
for _, v := range urls {
tests = append(tests, testcase{
hostname: v,
ipv4: true,
ipv6: true,
expected: []string{"128.199.158.128", "2400:6180:0:d0::91:1001"},
},
expected: resolvedIps,
})
}
for _, tt := range tests {
hm, err := hybrid.New(hybrid.DefaultDiskOptions)
@ -134,7 +145,7 @@ func Test_scanallips_normalizeStoreInputValue(t *testing.T) {
got = append(got, metainput.CustomIP)
return nil
})
require.ElementsMatch(t, tt.expected, got, "could not get correct ips")
require.ElementsMatchf(t, tt.expected, got, "could not get correct ips for hostname %v", tt.hostname)
input.Close()
}
}

View File

@ -1542,7 +1542,7 @@ func init() {
Value: "Headless response received from client (default)",
},
}
HEADLESSRequestDoc.Fields = make([]encoder.Doc, 9)
HEADLESSRequestDoc.Fields = make([]encoder.Doc, 10)
HEADLESSRequestDoc.Fields[0].Name = "id"
HEADLESSRequestDoc.Fields[0].Type = "string"
HEADLESSRequestDoc.Fields[0].Note = ""
@ -1573,22 +1573,27 @@ func init() {
HEADLESSRequestDoc.Fields[5].Note = ""
HEADLESSRequestDoc.Fields[5].Description = "description: |\n If UserAgent is set to custom, customUserAgent is the custom user-agent to use for the request."
HEADLESSRequestDoc.Fields[5].Comments[encoder.LineComment] = " description: |"
HEADLESSRequestDoc.Fields[6].Name = "matchers"
HEADLESSRequestDoc.Fields[6].Type = "[]matchers.Matcher"
HEADLESSRequestDoc.Fields[6].Name = "stop-at-first-match"
HEADLESSRequestDoc.Fields[6].Type = "bool"
HEADLESSRequestDoc.Fields[6].Note = ""
HEADLESSRequestDoc.Fields[6].Description = "Matchers contains the detection mechanism for the request to identify\nwhether the request was successful by doing pattern matching\non request/responses.\n\nMultiple matchers can be combined with `matcher-condition` flag\nwhich accepts either `and` or `or` as argument."
HEADLESSRequestDoc.Fields[6].Comments[encoder.LineComment] = "Matchers contains the detection mechanism for the request to identify"
HEADLESSRequestDoc.Fields[7].Name = "extractors"
HEADLESSRequestDoc.Fields[7].Type = "[]extractors.Extractor"
HEADLESSRequestDoc.Fields[6].Description = "StopAtFirstMatch stops the execution of the requests and template as soon as a match is found."
HEADLESSRequestDoc.Fields[6].Comments[encoder.LineComment] = "StopAtFirstMatch stops the execution of the requests and template as soon as a match is found."
HEADLESSRequestDoc.Fields[7].Name = "matchers"
HEADLESSRequestDoc.Fields[7].Type = "[]matchers.Matcher"
HEADLESSRequestDoc.Fields[7].Note = ""
HEADLESSRequestDoc.Fields[7].Description = "Extractors contains the extraction mechanism for the request to identify\nand extract parts of the response."
HEADLESSRequestDoc.Fields[7].Comments[encoder.LineComment] = "Extractors contains the extraction mechanism for the request to identify"
HEADLESSRequestDoc.Fields[8].Name = "matchers-condition"
HEADLESSRequestDoc.Fields[8].Type = "string"
HEADLESSRequestDoc.Fields[7].Description = "Matchers contains the detection mechanism for the request to identify\nwhether the request was successful by doing pattern matching\non request/responses.\n\nMultiple matchers can be combined with `matcher-condition` flag\nwhich accepts either `and` or `or` as argument."
HEADLESSRequestDoc.Fields[7].Comments[encoder.LineComment] = "Matchers contains the detection mechanism for the request to identify"
HEADLESSRequestDoc.Fields[8].Name = "extractors"
HEADLESSRequestDoc.Fields[8].Type = "[]extractors.Extractor"
HEADLESSRequestDoc.Fields[8].Note = ""
HEADLESSRequestDoc.Fields[8].Description = "MatchersCondition is the condition between the matchers. Default is OR."
HEADLESSRequestDoc.Fields[8].Comments[encoder.LineComment] = "MatchersCondition is the condition between the matchers. Default is OR."
HEADLESSRequestDoc.Fields[8].Values = []string{
HEADLESSRequestDoc.Fields[8].Description = "Extractors contains the extraction mechanism for the request to identify\nand extract parts of the response."
HEADLESSRequestDoc.Fields[8].Comments[encoder.LineComment] = "Extractors contains the extraction mechanism for the request to identify"
HEADLESSRequestDoc.Fields[9].Name = "matchers-condition"
HEADLESSRequestDoc.Fields[9].Type = "string"
HEADLESSRequestDoc.Fields[9].Note = ""
HEADLESSRequestDoc.Fields[9].Description = "MatchersCondition is the condition between the matchers. Default is OR."
HEADLESSRequestDoc.Fields[9].Comments[encoder.LineComment] = "MatchersCondition is the condition between the matchers. Default is OR."
HEADLESSRequestDoc.Fields[9].Values = []string{
"and",
"or",
}

View File

@ -77,3 +77,24 @@ func StringSliceContains(slice []string, item string) bool {
}
return false
}
// ParseHostname returns hostname
func ParseHostname(inputURL string) string {
/*
currently if URL is scanme.sh/path or scanme.sh:443 i.e without protocol then
url.Parse considers this as valid url but fails to parse hostname
this can be handled by adding schema
*/
input, err := url.Parse(inputURL)
if err != nil {
return ""
}
if input.Host == "" {
newinput, err := url.Parse("https://" + inputURL)
if err != nil {
return ""
}
return newinput.Host
}
return input.Host
}

View File

@ -19,3 +19,24 @@ func TestUnwrapError(t *testing.T) {
errThree := fmt.Errorf("error with error: %w", errTwo)
require.Equal(t, errOne, UnwrapError(errThree))
}
func TestParseURL(t *testing.T) {
testcases := []struct {
URL string
Hostname string
}{
{"https://scanme.sh:443", "scanme.sh:443"},
{"http://scanme.sh/path", "scanme.sh"},
{"scanme.sh:443/path", "scanme.sh:443"},
{"scanme.sh/path", "scanme.sh"},
}
for _, v := range testcases {
urlx := ParseHostname(v.URL)
if urlx == "" {
t.Errorf("failed to hostname of url %v", v)
}
if urlx != v.Hostname {
t.Errorf("hostname mismatch expected scanme.sh got %v", urlx)
}
}
}