mirror of
https://github.com/lunchcat/sif.git
synced 2026-07-28 14:37:01 -07:00
fix(scan): percent-encode dork queries before search (#327)
the google-search client only swaps spaces for '+' and drops the term into the query string verbatim, so a dork carrying a raw '#' or '&' cut the request url short at the fragment or split it into stray query params. the search then ran against a fragment of the intended dork. encode the term up front so the whole dork survives.
This commit is contained in:
+12
-5
@@ -18,8 +18,8 @@ package scan
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -44,11 +44,18 @@ type DorkResult struct {
|
||||
Count int `json:"count"` // The number of times this URL appeared in the dork's results
|
||||
}
|
||||
|
||||
// dorkQuery percent-encodes the dork and host together. the google-search
|
||||
// client drops the term into the query string verbatim, so an unencoded '#'
|
||||
// or '&' (both common in the dork list) truncates or splits the query.
|
||||
func dorkQuery(dork, host string) string {
|
||||
return url.QueryEscape(dork + " " + host)
|
||||
}
|
||||
|
||||
// Dork performs Google dorking operations on the target URL.
|
||||
// It uses a predefined list of dorks to search for potentially sensitive information.
|
||||
//
|
||||
// Parameters:
|
||||
// - url: The target URL to dork
|
||||
// - targetURL: The target URL to dork
|
||||
// - timeout: Maximum duration for each dork search
|
||||
// - threads: Number of concurrent threads to use
|
||||
// - logdir: Directory to store log files (empty string for no logging)
|
||||
@@ -56,13 +63,13 @@ type DorkResult struct {
|
||||
// Returns:
|
||||
// - []DorkResult: A slice of results from the dorking operation
|
||||
// - error: Any error encountered during the dorking process
|
||||
func Dork(url string, timeout time.Duration, threads int, logdir string) ([]DorkResult, error) {
|
||||
func Dork(targetURL string, timeout time.Duration, threads int, logdir string) ([]DorkResult, error) {
|
||||
output.ScanStart("URL dorking")
|
||||
|
||||
spin := output.NewSpinner("Running Google dorks")
|
||||
spin.Start()
|
||||
|
||||
sanitizedURL := stripScheme(url)
|
||||
sanitizedURL := stripScheme(targetURL)
|
||||
|
||||
if logdir != "" {
|
||||
if err := logger.WriteHeader(sanitizedURL, logdir, "URL dorking"); err != nil {
|
||||
@@ -98,7 +105,7 @@ func Dork(url string, timeout time.Duration, threads int, logdir string) ([]Dork
|
||||
|
||||
dorkResults := []DorkResult{}
|
||||
pool.Each(dorks, threads, func(dork string) {
|
||||
results, err := googlesearch.Search(context.TODO(), fmt.Sprintf("%s %s", dork, sanitizedURL))
|
||||
results, err := googlesearch.Search(context.TODO(), dorkQuery(dork, sanitizedURL))
|
||||
if err != nil {
|
||||
log.Debugf("error searching for dork %s: %v", dork, err)
|
||||
return
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2026 vmfunc, xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// buildSearchURL mirrors how the google-search client turns a search term into
|
||||
// a request url: it trims spaces, swaps the rest for '+' and drops the term
|
||||
// into the query string verbatim without any encoding.
|
||||
func buildSearchURL(term string) string {
|
||||
term = strings.Trim(term, " ")
|
||||
term = strings.ReplaceAll(term, " ", "+")
|
||||
return "https://www.google.com/search?q=" + term + "&hl=en"
|
||||
}
|
||||
|
||||
func TestDorkQueryPreservesSpecialCharacters(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
dork string
|
||||
}{
|
||||
{"hash", `intext:"#mysql dump" filetype:sql`},
|
||||
{"ampersand", `filetype:sql "a & b"`},
|
||||
{"question", `inurl:index.php?id=`},
|
||||
{"plain", `intitle:"index of" passwd`},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
want := tc.dork + " example.com"
|
||||
|
||||
u, err := url.Parse(buildSearchURL(dorkQuery(tc.dork, "example.com")))
|
||||
if err != nil {
|
||||
t.Fatalf("parse encoded url: %v", err)
|
||||
}
|
||||
if got := u.Query().Get("q"); got != want {
|
||||
t.Errorf("encoded query = %q, want %q", got, want)
|
||||
}
|
||||
// a fragment or a stray query param means the dork leaked out of q.
|
||||
if u.Fragment != "" {
|
||||
t.Errorf("encoded url has fragment %q, query truncated", u.Fragment)
|
||||
}
|
||||
if len(u.Query()) != 2 { // q and hl only
|
||||
t.Errorf("encoded url has stray params: %v", u.Query())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// guards the regression directly: the old naive term drops everything after a
|
||||
// '#' into the fragment, so the search runs against a fragment of the dork.
|
||||
func TestDorkQueryFixesNaiveTruncation(t *testing.T) {
|
||||
dork := `intext:"#mysql dump" filetype:sql`
|
||||
|
||||
naive, err := url.Parse(buildSearchURL(dork + " example.com"))
|
||||
if err != nil {
|
||||
t.Fatalf("parse naive url: %v", err)
|
||||
}
|
||||
if naive.Query().Get("q") == dork+" example.com" {
|
||||
t.Fatal("expected the naive term to truncate, but it survived")
|
||||
}
|
||||
|
||||
fixed, err := url.Parse(buildSearchURL(dorkQuery(dork, "example.com")))
|
||||
if err != nil {
|
||||
t.Fatalf("parse fixed url: %v", err)
|
||||
}
|
||||
if got, want := fixed.Query().Get("q"), dork+" example.com"; got != want {
|
||||
t.Errorf("fixed query = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user