Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ Specify http server in proxy configuration of Postman
- **Traffic sniffing**\
Proxy is able to parse HTTP headers and TLS handshake metadata

- **ARP spoofing**\
Proxy entire subnets with ARP spoofing approach

- **DNS Leak Protection**\
DNS resolution occurs on SOCKS5 server side.

Expand Down Expand Up @@ -98,7 +101,7 @@ You can download the binary for your platform from [Releases](https://github.com
Example:

```shell
HPTS_RELEASE=v1.8.5; wget -v https://github.com/shadowy-pycoder/go-http-proxy-to-socks/releases/download/$HPTS_RELEASE/gohpts-$HPTS_RELEASE-linux-amd64.tar.gz -O gohpts && tar xvzf gohpts && mv -f gohpts-$HPTS_RELEASE-linux-amd64 gohpts && ./gohpts -h
HPTS_RELEASE=v1.9.0; wget -v https://github.com/shadowy-pycoder/go-http-proxy-to-socks/releases/download/$HPTS_RELEASE/gohpts-$HPTS_RELEASE-linux-amd64.tar.gz -O gohpts && tar xvzf gohpts && mv -f gohpts-$HPTS_RELEASE-linux-amd64 gohpts && ./gohpts -h
```

Alternatively, you can install it using `go install` command (requires Go [1.24](https://go.dev/doc/install) or later):
Expand Down Expand Up @@ -165,7 +168,7 @@ Options:
-T Address of transparent proxy server (no HTTP)
-M Transparent proxy mode: (redirect, tproxy)
-auto Automatically setup iptables for transparent proxy (requires elevated privileges)
-arp Automatically setup iptables to proxy ARP spoofed traffic (use tools like bettercap to perform actual attack)
-arpspoof Enable ARP spoof proxy for selected targets (Example: "targets 10.0.0.1,10.0.0.5-10,192.168.1.*,192.168.10.0/24;fullduplex false;debug true")
-mark Set mark for each packet sent through transparent proxy (Default: redirect 0, tproxy 100)
```

Expand Down Expand Up @@ -483,13 +486,28 @@ fi

### ARP spoofing

`GoHPTS` can be used with tools like [Bettercap](https://github.com/bettercap/bettercap) to proxy ARP spoofed traffic.
`GoHPTS` has in-built ARP spoofer that can be used to make all TCP talking devices of your LAN to use proxy server to connect to the Internet.
This is achieved by adding `-arpspoof` flag with couple of parameters, separated by semicolon.

Example:

```shell
ssh remote -D 1080 -Nf
sudo env PATH=$PATH gohpts -d -T 8888 -M tproxy -sniff -body -auto -mark 100 -arpspoof "targets 192.168.10.0/24;fullduplex true;debug true"
```

Proxy will scan for devices in subnet `192.168.10.0/24` and send them ARP packets to pretend to be a gateway, if `fullduplex` is true,
proxy will send ARP packets to gateway as well to make it believe our proxy has each IP on the subnet.

After proxy is stopped with `Ctrl+C`, it will automatically unspoof all targets.

`GoHPTS` can also be used with tools like [Bettercap](https://github.com/bettercap/bettercap) to proxy ARP spoofed traffic.

Run the proxy with `-arp` flag
Run the proxy:

```shell
ssh remote -D 1080 -Nf
sudo env PATH=$PATH gohpts -d -T 8888 -M tproxy -sniff -body -auto -mark 100 -arp
sudo env PATH=$PATH gohpts -d -T 8888 -M tproxy -sniff -body -auto -mark 100
```

Run `bettercap` with this command (see [documentation](https://www.bettercap.org/)):
Expand Down
17 changes: 6 additions & 11 deletions cmd/gohpts/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const usageTproxy string = `
-T Address of transparent proxy server (no HTTP)
-M Transparent proxy mode: (redirect, tproxy)
-auto Automatically setup iptables for transparent proxy (requires elevated privileges)
-arp Automatically setup iptables to proxy ARP spoofed traffic (use tools like bettercap to perform actual attack)
-arpspoof Enable ARP spoof proxy for selected targets (Example: "targets 10.0.0.1,10.0.0.5-10,192.168.1.*,192.168.10.0/24;fullduplex false;debug true")
-mark Set mark for each packet sent through transparent proxy (Default: redirect 0, tproxy 100)
`

Expand Down Expand Up @@ -114,11 +114,11 @@ func root(args []string) error {
0,
"Set mark for each packet sent through transparent proxy (Default: redirect 0, tproxy 100)",
)
flags.BoolVar(
&conf.ARP,
"arp",
false,
"Automatically setup iptables to proxy ARP spoofed traffic (use tools like bettercap to perform actual attack)",
flags.StringVar(
&conf.ARPSpoof,
"arpspoof",
"",
"Enable ARP spoof proxy for selected targets",
)
}
flags.StringVar(&conf.LogFilePath, "logfile", "", "Log file path (Default: stdout)")
Expand Down Expand Up @@ -179,11 +179,6 @@ func root(args []string) error {
return fmt.Errorf("-mark requires -t or -T flag")
}
}
if seen["arp"] {
if !seen["auto"] {
return fmt.Errorf("-arp requires -auto flag")
}
}
if seen["f"] {
for _, da := range []string{"s", "u", "U", "c", "k", "l"} {
if seen[da] {
Expand Down
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@ require (
github.com/google/uuid v1.6.0
github.com/rs/zerolog v1.34.0
github.com/shadowy-pycoder/colors v0.0.1
github.com/shadowy-pycoder/mshark v0.0.6
github.com/shadowy-pycoder/mshark v0.0.7
golang.org/x/net v0.40.0
golang.org/x/sys v0.33.0
golang.org/x/term v0.32.0
)

require (
github.com/josharian/native v1.1.0 // indirect
github.com/malfunkt/iprange v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mdlayher/packet v1.1.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/sync v0.16.0 // indirect
)
17 changes: 15 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,24 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/malfunkt/iprange v0.9.0 h1:VCs0PKLUPotNVQTpVNszsut4lP7OCGNBwX+lOYBrnVQ=
github.com/malfunkt/iprange v0.9.0/go.mod h1:TRGqO/f95gh3LOndUGTL46+W0GXA91WTqyZ0Quwvt4U=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY=
github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand All @@ -19,12 +30,14 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/shadowy-pycoder/colors v0.0.1 h1:weCj/YIOupqy4BSP8KuVzr20fC+cuAv/tArz7bhhkP4=
github.com/shadowy-pycoder/colors v0.0.1/go.mod h1:lkrJS1PY2oVigNLTT6pkbF7B/v0YcU2LD5PZnss1Q4U=
github.com/shadowy-pycoder/mshark v0.0.6 h1:XmIoj9+uHEwc8RmiPT2iMYEdTvyiJ+zBTRBhIkx9JTg=
github.com/shadowy-pycoder/mshark v0.0.6/go.mod h1:Txx0p8JxYOGd+0V+6N9MeCUGtGdfHAATWE8KB1nd7H0=
github.com/shadowy-pycoder/mshark v0.0.7 h1:iuCLxKXh0HhukrZOQIbXjGneTTOZSPIS0mkIBSJm/4U=
github.com/shadowy-pycoder/mshark v0.0.7/go.mod h1:FqbHFdsx0zMnrZZH0+oPzaFcleP4O+tUWv8i5gxo87k=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
142 changes: 94 additions & 48 deletions gohpts.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/google/uuid"
"github.com/rs/zerolog"
"github.com/shadowy-pycoder/colors"
"github.com/shadowy-pycoder/mshark/arpspoof"
"github.com/shadowy-pycoder/mshark/layers"
"golang.org/x/net/proxy"
)
Expand Down Expand Up @@ -66,6 +67,7 @@ var (
credsPattern = regexp.MustCompile(
`(?i)(?:"|')?(username|user|login|email|password|pass|pwd)(?:"|')?\s*[:=]\s*(?:"|')?([^\s"'&]+)`,
)
macPattern = regexp.MustCompile(`(?i)([a-z0-9_]+_[0-9a-f]{2}(?::[0-9a-f]{2}){2}|(?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2})`)
)

// Hop-by-hop headers
Expand Down Expand Up @@ -97,7 +99,7 @@ type Config struct {
TProxyMode string
Auto bool
Mark uint
ARP bool
ARPSpoof string
LogFilePath string
Debug bool
JSON bool
Expand All @@ -121,7 +123,7 @@ type proxyapp struct {
tproxyMode string
auto bool
mark uint
arp bool
arpspoofer *arpspoof.ARPSpoofer
user string
pass string
proxychain chain
Expand Down Expand Up @@ -1396,52 +1398,48 @@ func (p *proxyapp) applyRedirectRules() string {
cmdForward.Stdout = os.Stdout
cmdForward.Stderr = os.Stderr
_ = cmdForward.Run()
if p.arp {
cmdClear := exec.Command("bash", "-c", `
set -ex
iptables -t filter -F GOHPTS 2>/dev/null || true
iptables -t filter -D FORWARD -j GOHPTS 2>/dev/null || true
iptables -t filter -X GOHPTS 2>/dev/null || true
`)
cmdClear.Stdout = os.Stdout
cmdClear.Stderr = os.Stderr
if err := cmdClear.Run(); err != nil {
p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?")
}
iface, err := getDefaultInterface()
if err != nil {
p.logger.Fatal().Err(err).Msg("failed getting default network interface")
}
cmdForward := exec.Command("bash", "-c", fmt.Sprintf(`
set -ex
iptables -t filter -N GOHPTS 2>/dev/null
iptables -t filter -F GOHPTS
iptables -t filter -A FORWARD -j GOHPTS
iptables -t filter -A GOHPTS -i %s -j ACCEPT
iptables -t filter -A GOHPTS -o %s -j ACCEPT
`, iface.Name, iface.Name))
cmdForward.Stdout = os.Stdout
cmdForward.Stderr = os.Stderr
if err := cmdForward.Run(); err != nil {
p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?")
}
cmdClearForward := exec.Command("bash", "-c", `
set -ex
iptables -t filter -F GOHPTS 2>/dev/null || true
iptables -t filter -D FORWARD -j GOHPTS 2>/dev/null || true
iptables -t filter -X GOHPTS 2>/dev/null || true
`)
cmdClearForward.Stdout = os.Stdout
cmdClearForward.Stderr = os.Stderr
if err := cmdClearForward.Run(); err != nil {
p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?")
}
iface, err := getDefaultInterface()
if err != nil {
p.logger.Fatal().Err(err).Msg("failed getting default network interface")
}
cmdForwardFilter := exec.Command("bash", "-c", fmt.Sprintf(`
set -ex
iptables -t filter -N GOHPTS 2>/dev/null
iptables -t filter -F GOHPTS
iptables -t filter -A FORWARD -j GOHPTS
iptables -t filter -A GOHPTS -i %s -j ACCEPT
iptables -t filter -A GOHPTS -o %s -j ACCEPT
`, iface.Name, iface.Name))
cmdForwardFilter.Stdout = os.Stdout
cmdForwardFilter.Stderr = os.Stderr
if err := cmdForwardFilter.Run(); err != nil {
p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?")
}
return string(output)
}

func (p *proxyapp) clearRedirectRules(output string) error {
if p.arp {
cmdClear := exec.Command("bash", "-c", `
set -ex
iptables -t filter -F GOHPTS 2>/dev/null || true
iptables -t filter -D FORWARD -j GOHPTS 2>/dev/null || true
iptables -t filter -X GOHPTS 2>/dev/null || true
`)
cmdClear.Stdout = os.Stdout
cmdClear.Stderr = os.Stderr
if err := cmdClear.Run(); err != nil {
p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?")
}
cmdClear := exec.Command("bash", "-c", `
set -ex
iptables -t filter -F GOHPTS 2>/dev/null || true
iptables -t filter -D FORWARD -j GOHPTS 2>/dev/null || true
iptables -t filter -X GOHPTS 2>/dev/null || true
`)
cmdClear.Stdout = os.Stdout
cmdClear.Stderr = os.Stderr
if err := cmdClear.Run(); err != nil {
p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?")
}
var cmd *exec.Cmd
switch p.tproxyMode {
Expand Down Expand Up @@ -1481,6 +1479,9 @@ func (p *proxyapp) Run() {
quit := make(chan os.Signal, 1)
p.closeConn = make(chan bool)
signal.Notify(quit, os.Interrupt)
if p.arpspoofer != nil {
go p.arpspoofer.Start()
}
var tproxyServer *tproxyServer
if p.tproxyAddr != "" {
tproxyServer = newTproxyServer(p)
Expand Down Expand Up @@ -1508,6 +1509,12 @@ func (p *proxyapp) Run() {
if p.httpServer != nil {
go func() {
<-quit
if p.arpspoofer != nil {
err := p.arpspoofer.Stop()
if err != nil {
p.logger.Error().Err(err).Msg("Failed stopping arp spoofer")
}
}
if p.auto {
err := p.clearRedirectRules(output)
if err != nil {
Expand Down Expand Up @@ -1550,6 +1557,12 @@ func (p *proxyapp) Run() {
} else {
go func() {
<-quit
if p.arpspoofer != nil {
err := p.arpspoofer.Stop()
if err != nil {
p.logger.Error().Err(err).Msg("Failed stopping arp spoofer")
}
}
if p.auto {
err := p.clearRedirectRules(output)
if err != nil {
Expand Down Expand Up @@ -1706,6 +1719,9 @@ func New(conf *Config) *proxyapp {
result = domainPattern.ReplaceAllStringFunc(result, func(match string) string {
return colors.Yellow(match).String()
})
result = macPattern.ReplaceAllStringFunc(result, func(match string) string {
return colors.Yellow(match).String()
})
return result
}

Expand Down Expand Up @@ -1819,11 +1835,41 @@ func New(conf *Config) *proxyapp {
if p.mark == 0 && p.tproxyMode == "tproxy" {
p.mark = 100
}
p.arp = conf.ARP
if p.arp && runtime.GOOS != "linux" {
p.logger.Fatal().Msg("ARP setup is available only for linux system")
} else if p.arp && !p.auto {
p.logger.Fatal().Msg("ARP setup requires auto configuration")
if conf.ARPSpoof != "" {
if runtime.GOOS != "linux" {
p.logger.Fatal().Msg("ARP spoof setup is available only for linux system")
}
if !p.auto {
p.logger.Warn().Msg("ARP spoof setup requires iptables configuration")
}
asc := &arpspoof.ARPSpoofConfig{Logger: p.logger}
errMsg := `Failed parsing arp options. Example: "targets 10.0.0.1,10.0.0.5-10,192.168.1.*,192.168.10.0/24;fullduplex false;debug true"`
for opt := range strings.SplitSeq(strings.ToLower(conf.ARPSpoof), ";") {
keyval := strings.SplitN(strings.Trim(opt, " "), " ", 2)
if len(keyval) < 2 {
p.logger.Fatal().Msg(errMsg)
}
key := keyval[0]
val := keyval[1]
switch key {
case "targets":
asc.Targets = val
case "fullduplex":
if val == "true" {
asc.FullDuplex = true
}
case "debug":
if val == "true" {
asc.Debug = true
}
default:
p.logger.Fatal().Msg(errMsg)
}
}
p.arpspoofer, err = arpspoof.NewARPSpoofer(asc)
if err != nil {
p.logger.Fatal().Err(err).Msg("Failed creating arp spoofer")
}
}
var addrHTTP, addrSOCKS, certFile, keyFile string
if conf.ServerConfPath != "" {
Expand Down
2 changes: 1 addition & 1 deletion version.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package gohpts

const Version string = "gohpts v1.8.5"
const Version string = "gohpts v1.9.0"