Skip to content

Commit 0154382

Browse files
authored
Adding --proxy argument and environment variables detection (#14)
* Add `--proxy` option
1 parent 7ff941f commit 0154382

File tree

14 files changed

+104
-32
lines changed

14 files changed

+104
-32
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,14 @@ Save scan results to a JSON file:
188188
./wpprobe scan -f targets.txt -t 20 -o results.json
189189
```
190190

191+
### Use a custom HTTP/HTTPS proxy
192+
Route all requests through a specific proxy using `--proxy` argument. If `--proxy` is not specified, `WPProbe` will automatically check for the following environment variables (in priority order):
193+
- `HTTPS_PROXY` / `https_proxy`
194+
- `HTTP_PROXY` / `http_proxy`
195+
- `ALL_PROXY` / `all_proxy`
196+
197+
Will also respect `NO_PROXY` / `no_proxy` for bypass rules.
198+
191199
---
192200

193201
## 📜 Example Output

cmd/scan.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,35 @@ package cmd
2121

2222
import (
2323
"os"
24+
"strings"
2425

2526
"github.com/Chocapikk/wpprobe/internal/scanner"
2627
"github.com/Chocapikk/wpprobe/internal/utils"
2728
"github.com/spf13/cobra"
2829
)
2930

31+
func getProxyFromEnv() (string, string) {
32+
if v := firstNonEmpty(os.Getenv("HTTPS_PROXY"), os.Getenv("https_proxy")); v != "" {
33+
return v, "HTTPS_PROXY"
34+
}
35+
if v := firstNonEmpty(os.Getenv("HTTP_PROXY"), os.Getenv("http_proxy")); v != "" {
36+
return v, "HTTP_PROXY"
37+
}
38+
if v := firstNonEmpty(os.Getenv("ALL_PROXY"), os.Getenv("all_proxy")); v != "" {
39+
return v, "ALL_PROXY"
40+
}
41+
return "", ""
42+
}
43+
44+
func firstNonEmpty(vals ...string) string {
45+
for _, v := range vals {
46+
if strings.TrimSpace(v) != "" {
47+
return v
48+
}
49+
}
50+
return ""
51+
}
52+
3053
var scanCmd = &cobra.Command{
3154
Use: "scan",
3255
Short: "Scan a WordPress site for installed plugins and vulnerabilities",
@@ -37,6 +60,25 @@ var scanCmd = &cobra.Command{
3760

3861
headers, _ := cmd.Flags().GetStringArray("header")
3962

63+
proxyURL := cmd.Flag("proxy").Value.String()
64+
65+
if strings.TrimSpace(proxyURL) != "" {
66+
utils.DefaultLogger.Info("Using given proxy: " + proxyURL)
67+
} else {
68+
utils.DefaultLogger.Info("No proxy URL provided, checking environment variables")
69+
if envProxy, from := getProxyFromEnv(); envProxy != "" {
70+
proxyURL = envProxy
71+
utils.DefaultLogger.Info("Using proxy from " + from + ": " + proxyURL)
72+
} else {
73+
noProxy := firstNonEmpty(os.Getenv("NO_PROXY"), os.Getenv("no_proxy"))
74+
if noProxy != "" {
75+
utils.DefaultLogger.Info("No explicit proxy; NO_PROXY is set: " + noProxy)
76+
} else {
77+
utils.DefaultLogger.Info("No proxy configured; using direct connection")
78+
}
79+
}
80+
}
81+
4082
opts := scanner.ScanOptions{
4183
URL: cmd.Flag("url").Value.String(),
4284
File: cmd.Flag("file").Value.String(),
@@ -48,6 +90,7 @@ var scanCmd = &cobra.Command{
4890
ScanMode: cmd.Flag("mode").Value.String(),
4991
PluginList: cmd.Flag("plugin-list").Value.String(),
5092
Headers: headers,
93+
Proxy: proxyURL,
5194
}
5295

5396
if opts.URL == "" && opts.File == "" {
@@ -71,6 +114,7 @@ func init() {
71114
StringP("plugin-list", "p", "", "Path to a custom plugin list file for bruteforce mode")
72115
scanCmd.Flags().
73116
StringArrayP("header", "H", []string{}, "HTTP header to include in requests. Can be specified multiple times.")
117+
scanCmd.Flags().String("proxy", "", "HTTP/HTTPS proxy URL (e.g., http://127.0.0.1:8080)")
74118
}
75119

76120
func mustBool(value bool, err error) bool {

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ require (
1111
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213
1212
github.com/schollz/progressbar/v3 v3.18.0
1313
github.com/spf13/cobra v1.8.1
14-
golang.org/x/net v0.40.0
15-
golang.org/x/text v0.25.0
14+
golang.org/x/net v0.43.0
15+
golang.org/x/text v0.28.0
1616
)
1717

1818
require (
@@ -26,6 +26,6 @@ require (
2626
github.com/muesli/termenv v0.15.2 // indirect
2727
github.com/rivo/uniseg v0.4.7 // indirect
2828
github.com/spf13/pflag v1.0.5 // indirect
29-
golang.org/x/sys v0.33.0 // indirect
30-
golang.org/x/term v0.32.0 // indirect
29+
golang.org/x/sys v0.35.0 // indirect
30+
golang.org/x/term v0.34.0 // indirect
3131
)

go.sum

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,15 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
4545
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
4646
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
4747
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
48-
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
49-
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
48+
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
49+
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
5050
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
51-
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
52-
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
53-
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
54-
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
55-
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
56-
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
51+
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
52+
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
53+
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
54+
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
55+
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
56+
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
5757
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
5858
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
5959
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/scanner/bruteforce.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func BruteforcePlugins(
5656
threads int,
5757
progress *utils.ProgressManager,
5858
headers []string,
59+
proxyURL string,
5960
) []string {
6061
if len(plugins) == 0 {
6162
utils.DefaultLogger.Warning("No plugins provided for brute-force scan")
@@ -89,7 +90,7 @@ func BruteforcePlugins(
8990
progress.SetMessage(fmt.Sprintf("🔎 Bruteforcing plugin %-30.30s", p))
9091
}
9192

92-
version := utils.GetPluginVersion(normalized, p, threads, headers)
93+
version := utils.GetPluginVersion(normalized, p, threads, headers, proxyURL)
9394
if version != "" && version != "unknown" {
9495
if progress != nil {
9596
progress.ClearLine()
@@ -122,9 +123,10 @@ func HybridScan(
122123
threads int,
123124
progress *utils.ProgressManager,
124125
headers []string,
126+
proxyURL string,
125127
) []string {
126128
if len(stealthyPlugins) == 0 {
127-
return BruteforcePlugins(target, bruteforcePlugins, threads, progress, headers)
129+
return BruteforcePlugins(target, bruteforcePlugins, threads, progress, headers, proxyURL)
128130
}
129131

130132
detectedMap := make(map[string]bool, len(stealthyPlugins))
@@ -139,7 +141,7 @@ func HybridScan(
139141
}
140142
}
141143

142-
brutefound := BruteforcePlugins(target, remaining, threads, progress, headers)
144+
brutefound := BruteforcePlugins(target, remaining, threads, progress, headers, proxyURL)
143145
result := make([]string, len(stealthyPlugins), len(stealthyPlugins)+len(brutefound))
144146
copy(result, stealthyPlugins)
145147
return append(result, brutefound...)

internal/scanner/endpoints.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ func fetchEndpointsFromPath(target, path string, httpClient *utils.HTTPClientMan
5151
return endpoints
5252
}
5353

54-
func FetchEndpoints(target string, headers []string) []string {
55-
httpClient := utils.NewHTTPClient(10*time.Second, headers)
54+
func FetchEndpoints(target string, headers []string, proxyURL string) []string {
55+
httpClient := utils.NewHTTPClient(10*time.Second, headers, proxyURL)
5656

5757
endpointsChan := make(chan []string, 2)
5858
var wg sync.WaitGroup

internal/scanner/endpoints_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func TestFetchEndpoints(t *testing.T) {
104104
server := httptest.NewServer(http.HandlerFunc(tt.mockServer))
105105
defer server.Close()
106106

107-
got := FetchEndpoints(server.URL, tt.headers)
107+
got := FetchEndpoints(server.URL, tt.headers, "")
108108

109109
sort.Strings(got)
110110
sort.Strings(tt.want)

internal/scanner/html.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ import (
2929
"golang.org/x/net/html"
3030
)
3131

32-
func discoverPluginsFromHTML(target string, headers []string) ([]string, error) {
32+
func discoverPluginsFromHTML(target string, headers []string, proxyURL string) ([]string, error) {
3333
normalized := utils.NormalizeURL(target)
34-
client := utils.NewHTTPClient(10*time.Second, headers)
34+
client := utils.NewHTTPClient(10*time.Second, headers, proxyURL)
3535

3636
slugsSet := make(map[string]struct{})
3737

internal/scanner/html_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func TestDiscoverPluginsFromHTML(t *testing.T) {
104104
}))
105105
defer ts.Close()
106106

107-
slugs, err := discoverPluginsFromHTML(ts.URL, nil)
107+
slugs, err := discoverPluginsFromHTML(ts.URL, nil, "")
108108
if err != nil {
109109
t.Fatalf("discoverPluginsFromHTML returned error: %v", err)
110110
}

internal/scanner/scan.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,13 @@ type ScanOptions struct {
3939
ScanMode string
4040
PluginList string
4141
Headers []string
42+
Proxy string
4243
}
4344

4445
func ScanTargets(opts ScanOptions) {
46+
4547
var targets []string
48+
4649
if opts.File != "" {
4750
lines, err := utils.ReadLines(opts.File)
4851
if err != nil {
@@ -117,7 +120,7 @@ func performStealthyScan(
117120
progress.SetMessage("🔎 Discovering plugins from HTML...")
118121
}
119122

120-
htmlSlugs, err := discoverPluginsFromHTML(target, opts.Headers)
123+
htmlSlugs, err := discoverPluginsFromHTML(target, opts.Headers, opts.Proxy)
121124
if err != nil {
122125
utils.DefaultLogger.Warning(fmt.Sprintf("HTML discovery failed on %s: %v", target, err))
123126
}
@@ -137,7 +140,7 @@ func performStealthyScan(
137140
return nil, PluginDetectionResult{}
138141
}
139142

140-
endpoints := FetchEndpoints(target, opts.Headers)
143+
endpoints := FetchEndpoints(target, opts.Headers, opts.Proxy)
141144

142145
var result PluginDetectionResult
143146
if len(endpoints) > 0 {
@@ -191,7 +194,7 @@ func performBruteforceScan(
191194
}
192195
}
193196

194-
detected := BruteforcePlugins(target, plugins, threads, pb, opts.Headers)
197+
detected := BruteforcePlugins(target, plugins, threads, pb, opts.Headers, opts.Proxy)
195198

196199
pr := PluginDetectionResult{
197200
Plugins: make(map[string]*PluginData, len(detected)),
@@ -247,7 +250,7 @@ func performHybridScan(
247250
bruteBar = utils.NewProgressBar(len(remaining), "🔎 Bruteforcing remaining")
248251
}
249252

250-
brutefound := BruteforcePlugins(target, remaining, threads, bruteBar, opts.Headers)
253+
brutefound := BruteforcePlugins(target, remaining, threads, bruteBar, opts.Headers, opts.Proxy)
251254

252255
if bruteBar != nil {
253256
bruteBar.Finish()
@@ -344,7 +347,7 @@ func ScanSite(
344347

345348
version := "unknown"
346349
if !opts.NoCheckVersion {
347-
version = utils.GetPluginVersion(target, pl, opts.Threads, opts.Headers)
350+
version = utils.GetPluginVersion(target, pl, opts.Threads, opts.Headers, opts.Proxy)
348351
}
349352

350353
var matched []wordfence.Vulnerability

0 commit comments

Comments
 (0)