add support for proxy socks5

Signed-off-by: Ronni Skansing <rskansing@gmail.com>
This commit is contained in:
Ronni Skansing
2025-11-09 10:03:11 +01:00
parent 6e60d23643
commit acb7a4a8ee
3 changed files with 105 additions and 9 deletions

View File

@@ -9,6 +9,7 @@ import (
"github.com/enetx/surf"
"github.com/phishingclub/phishingclub/service"
"golang.org/x/net/proxy"
)
// browserProfile represents detected browser and platform information
@@ -133,9 +134,13 @@ func (m *ProxyHandler) createSurfClient(userAgent string, proxyConfig *service.P
// configure proxy if specified
if proxyConfig.Proxy != "" {
builder = builder.Proxy("http://" + proxyConfig.Proxy)
proxyURL, err := m.parseProxyURL(proxyConfig.Proxy)
if err != nil {
return nil, err
}
builder = builder.Proxy(proxyURL.String())
m.logger.Debugw("configured surf client with proxy",
"proxy", proxyConfig.Proxy,
"proxy", proxyURL.String(),
)
}
@@ -179,6 +184,29 @@ func (m *ProxyHandler) createHTTPClientWithImpersonation(req *http.Request, reqC
return client, nil
}
// parseProxyURL parses and normalizes the proxy URL string
// if the proxy string is just an IP:port, it prepends "http://"
// otherwise it uses the full string to support socks4/socks5 and authentication
func (m *ProxyHandler) parseProxyURL(proxyStr string) (*url.URL, error) {
// check if the string already contains a scheme (http://, https://, socks4://, socks5://)
hasScheme := strings.Contains(proxyStr, "://")
// check if it contains authentication credentials
hasAuth := strings.Contains(proxyStr, "@")
// if it has a scheme or auth, use it as-is
if hasScheme || hasAuth {
// if it has auth but no scheme, default to http://
if hasAuth && !hasScheme {
proxyStr = "http://" + proxyStr
}
return url.Parse(proxyStr)
}
// otherwise, it's just an IP:port, so prepend http://
return url.Parse("http://" + proxyStr)
}
// createStandardHTTPClient creates a standard http client without impersonation
func (m *ProxyHandler) createStandardHTTPClient(proxyConfig *service.ProxyServiceConfigYAML) (*http.Client, error) {
client := &http.Client{
@@ -187,15 +215,42 @@ func (m *ProxyHandler) createStandardHTTPClient(proxyConfig *service.ProxyServic
}
if proxyConfig.Proxy != "" {
proxyURL, err := url.Parse("http://" + proxyConfig.Proxy)
proxyURL, err := m.parseProxyURL(proxyConfig.Proxy)
if err != nil {
return nil, err
}
client.Transport = &http.Transport{
Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
// handle socks5 proxies
if proxyURL.Scheme == "socks5" {
var auth *proxy.Auth
if proxyURL.User != nil {
password, _ := proxyURL.User.Password()
auth = &proxy.Auth{
User: proxyURL.User.Username(),
Password: password,
}
}
// create socks5 dialer
dialer, err := proxy.SOCKS5("tcp", proxyURL.Host, auth, proxy.Direct)
if err != nil {
return nil, err
}
client.Transport = &http.Transport{
Dial: dialer.Dial,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
} else {
// handle http/https proxies
client.Transport = &http.Transport{
Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
}
}
return client, nil

View File

@@ -1408,6 +1408,11 @@ func (m *Proxy) validateProxyConfig(ctx context.Context, proxy *model.Proxy) err
return validate.WrapErrorWithField(err, "proxyConfig")
}
// validate forward proxy configuration
if err := m.validateForwardProxy(&config); err != nil {
return validate.WrapErrorWithField(err, "proxyConfig")
}
// validate that at least one domain mapping exists
if len(config.Hosts) == 0 {
return validate.WrapErrorWithField(errors.New("at least one domain mapping must be specified"), "proxyConfig")
@@ -1583,6 +1588,34 @@ func (m *Proxy) validateProxyConfig(ctx context.Context, proxy *model.Proxy) err
return nil
}
// validateForwardProxy validates the forward proxy configuration
func (m *Proxy) validateForwardProxy(config *ProxyServiceConfigYAML) error {
if config.Proxy == "" {
return nil // proxy is optional
}
// check if it contains socks4 scheme
if strings.HasPrefix(config.Proxy, "socks4://") {
return errors.New("socks4 proxies are not supported. please use socks5:// instead")
}
// validate that it can be parsed as a URL if it has a scheme
if strings.Contains(config.Proxy, "://") {
parsedURL, err := url.Parse(config.Proxy)
if err != nil {
return errors.New("invalid proxy URL format: " + err.Error())
}
// validate supported schemes
scheme := parsedURL.Scheme
if scheme != "http" && scheme != "https" && scheme != "socks5" {
return errors.New(fmt.Sprintf("unsupported proxy scheme '%s'. supported schemes: http, https, socks5", scheme))
}
}
return nil
}
// validateGlobalCaptureNameUniqueness ensures all capture rule names are unique across the entire Proxy configuration
func (m *Proxy) validateGlobalCaptureNameUniqueness(config *ProxyServiceConfigYAML) error {
allCaptureNames := make(map[string]string) // name -> location

View File

@@ -67,7 +67,15 @@
let isLoadingIPAllowList = false;
const currentExample = `version: "0.0"
proxy: "My Proxy Campaign"
# optional: forward proxy for outbound requests
# if just ip:port is provided, http:// is automatically prepended
# supported formats:
# proxy: "192.168.1.100:8080" # http proxy (ip:port)
# proxy: "http://192.168.1.100:8080" # http proxy with scheme
# proxy: "socks5://192.168.1.100:1080" # socks5 proxy
# proxy: "socks5://user:pass@192.168.1.100:1080" # socks5 with auth
# proxy: "http://user:pass@192.168.1.100:8080" # http with auth
# global TLS configuration (applies to all hosts unless overridden)
global: