mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-14 05:11:19 -04:00
hardening(proxy): don't proxy private IP addresses
...by default. This is useful when `RelayDomainNamePattern` is lax (e.g. just "$") (which is not the case by default, so this is simply a hardening measure).
This commit is contained in:
parent
399bda5257
commit
94c6089cdd
4 changed files with 45 additions and 9 deletions
|
@ -31,6 +31,9 @@ The Snowflake proxy can be run with the following options:
|
||||||
Usage of ./proxy:
|
Usage of ./proxy:
|
||||||
-allow-non-tls-relay
|
-allow-non-tls-relay
|
||||||
allow relay without tls encryption
|
allow relay without tls encryption
|
||||||
|
-allow-proxying-to-private-addresses
|
||||||
|
allow forwarding client connections to private IP addresses.
|
||||||
|
Useful when a Snowflake server (relay) is hosted on the same private network as this proxy.
|
||||||
-allowed-relay-hostname-pattern string
|
-allowed-relay-hostname-pattern string
|
||||||
a pattern to specify allowed hostname pattern for relay URL. (default "snowflake.torproject.net$")
|
a pattern to specify allowed hostname pattern for relay URL. (default "snowflake.torproject.net$")
|
||||||
-broker string
|
-broker string
|
||||||
|
|
|
@ -496,6 +496,7 @@ func TestUtilityFuncs(t *testing.T) {
|
||||||
Convey("isRelayURLAcceptable", t, func() {
|
Convey("isRelayURLAcceptable", t, func() {
|
||||||
testingVector := []struct {
|
testingVector := []struct {
|
||||||
pattern string
|
pattern string
|
||||||
|
allowPrivateAddresses bool
|
||||||
allowNonTLS bool
|
allowNonTLS bool
|
||||||
targetURL string
|
targetURL string
|
||||||
expects error
|
expects error
|
||||||
|
@ -525,6 +526,20 @@ func TestUtilityFuncs(t *testing.T) {
|
||||||
{pattern: "^1.1.1.1$", allowNonTLS: true, targetURL: "ws://1.1.1.1/test?test=test#test", expects: nil},
|
{pattern: "^1.1.1.1$", allowNonTLS: true, targetURL: "ws://1.1.1.1/test?test=test#test", expects: nil},
|
||||||
{pattern: "^1.1.1.1$", allowNonTLS: true, targetURL: "ws://231.1.1.1/test?test=test#test", expects: fmt.Errorf("")},
|
{pattern: "^1.1.1.1$", allowNonTLS: true, targetURL: "ws://231.1.1.1/test?test=test#test", expects: fmt.Errorf("")},
|
||||||
{pattern: "1.1.1.1$", allowNonTLS: true, targetURL: "ws://231.1.1.1/test?test=test#test", expects: nil},
|
{pattern: "1.1.1.1$", allowNonTLS: true, targetURL: "ws://231.1.1.1/test?test=test#test", expects: nil},
|
||||||
|
// Private IP address
|
||||||
|
{pattern: "$", allowNonTLS: true, targetURL: "ws://192.168.1.1", expects: fmt.Errorf("")},
|
||||||
|
{pattern: "$", allowNonTLS: true, targetURL: "ws://127.0.0.1", expects: fmt.Errorf("")},
|
||||||
|
{pattern: "$", allowNonTLS: true, targetURL: "ws://[fc00::]/", expects: fmt.Errorf("")},
|
||||||
|
{pattern: "$", allowNonTLS: true, targetURL: "ws://[::1]/", expects: fmt.Errorf("")},
|
||||||
|
{pattern: "$", allowNonTLS: true, targetURL: "ws://0.0.0.0/", expects: fmt.Errorf("")},
|
||||||
|
{pattern: "$", allowNonTLS: true, targetURL: "ws://169.254.1.1/", expects: fmt.Errorf("")},
|
||||||
|
{pattern: "$", allowNonTLS: true, targetURL: "ws://100.111.1.1/", expects: fmt.Errorf("")},
|
||||||
|
{pattern: "192.168.1.100$", allowPrivateAddresses: true, allowNonTLS: true, targetURL: "ws://192.168.1.100/test?test=test", expects: nil},
|
||||||
|
{pattern: "localhost$", allowPrivateAddresses: true, allowNonTLS: true, targetURL: "ws://localhost/test?test=test", expects: nil},
|
||||||
|
{pattern: "::1$", allowPrivateAddresses: true, allowNonTLS: true, targetURL: "ws://[::1]/test?test=test", expects: nil},
|
||||||
|
// Multicast IP address. `checkIsRelayURLAcceptable` allows it,
|
||||||
|
// but it's not valid in the context of WebSocket
|
||||||
|
{pattern: "255.255.255.255$", allowPrivateAddresses: true, allowNonTLS: true, targetURL: "ws://255.255.255.255/test?test=test", expects: nil},
|
||||||
|
|
||||||
// Port
|
// Port
|
||||||
{pattern: "^snowflake.torproject.net$", allowNonTLS: false, targetURL: "wss://snowflake.torproject.net:8080/test?test=test#test", expects: nil},
|
{pattern: "^snowflake.torproject.net$", allowNonTLS: false, targetURL: "wss://snowflake.torproject.net:8080/test?test=test#test", expects: nil},
|
||||||
|
@ -551,7 +566,7 @@ func TestUtilityFuncs(t *testing.T) {
|
||||||
{pattern: "snowflake.torproject.net$", allowNonTLS: true, targetURL: "ftp://snowflake.torproject.net", expects: fmt.Errorf("")},
|
{pattern: "snowflake.torproject.net$", allowNonTLS: true, targetURL: "ftp://snowflake.torproject.net", expects: fmt.Errorf("")},
|
||||||
}
|
}
|
||||||
for _, v := range testingVector {
|
for _, v := range testingVector {
|
||||||
err := checkIsRelayURLAcceptable(v.pattern, v.allowNonTLS, v.targetURL)
|
err := checkIsRelayURLAcceptable(v.pattern, v.allowPrivateAddresses, v.allowNonTLS, v.targetURL)
|
||||||
if v.expects != nil {
|
if v.expects != nil {
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -138,6 +138,11 @@ type SnowflakeProxy struct {
|
||||||
// There is no look ahead assertion when matching domain name suffix,
|
// There is no look ahead assertion when matching domain name suffix,
|
||||||
// thus the string prepend the suffix does not need to be empty or ends with a dot.
|
// thus the string prepend the suffix does not need to be empty or ends with a dot.
|
||||||
RelayDomainNamePattern string
|
RelayDomainNamePattern string
|
||||||
|
// AllowProxyingToPrivateAddresses determines whether to allow forwarding
|
||||||
|
// client connections to private IP addresses.
|
||||||
|
// Useful when a Snowflake server (relay) is hosted on the same private network
|
||||||
|
// as this proxy.
|
||||||
|
AllowProxyingToPrivateAddresses bool
|
||||||
AllowNonTLSRelay bool
|
AllowNonTLSRelay bool
|
||||||
// NATProbeURL is the URL of the probe service we use for NAT checks
|
// NATProbeURL is the URL of the probe service we use for NAT checks
|
||||||
NATProbeURL string
|
NATProbeURL string
|
||||||
|
@ -601,7 +606,7 @@ func (sf *SnowflakeProxy) runSession(sid string) {
|
||||||
log.Printf("Received Offer From Broker: \n\t%s", strings.ReplaceAll(offer.SDP, "\n", "\n\t"))
|
log.Printf("Received Offer From Broker: \n\t%s", strings.ReplaceAll(offer.SDP, "\n", "\n\t"))
|
||||||
|
|
||||||
if relayURL != "" {
|
if relayURL != "" {
|
||||||
if err := checkIsRelayURLAcceptable(sf.RelayDomainNamePattern, sf.AllowNonTLSRelay, relayURL); err != nil {
|
if err := checkIsRelayURLAcceptable(sf.RelayDomainNamePattern, sf.AllowProxyingToPrivateAddresses, sf.AllowNonTLSRelay, relayURL); err != nil {
|
||||||
log.Printf("bad offer from broker: %v", err)
|
log.Printf("bad offer from broker: %v", err)
|
||||||
tokens.ret()
|
tokens.ret()
|
||||||
return
|
return
|
||||||
|
@ -644,6 +649,7 @@ func (sf *SnowflakeProxy) runSession(sid string) {
|
||||||
// Returns nil if the relayURL is acceptable
|
// Returns nil if the relayURL is acceptable
|
||||||
func checkIsRelayURLAcceptable(
|
func checkIsRelayURLAcceptable(
|
||||||
allowedHostNamePattern string,
|
allowedHostNamePattern string,
|
||||||
|
allowPrivateIPs bool,
|
||||||
allowNonTLSRelay bool,
|
allowNonTLSRelay bool,
|
||||||
relayURL string,
|
relayURL string,
|
||||||
) error {
|
) error {
|
||||||
|
@ -651,6 +657,16 @@ func checkIsRelayURLAcceptable(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("bad Relay URL %w", err)
|
return fmt.Errorf("bad Relay URL %w", err)
|
||||||
}
|
}
|
||||||
|
if !allowPrivateIPs {
|
||||||
|
ip := net.ParseIP(parsedRelayURL.Hostname())
|
||||||
|
// Otherwise it's a domain name, or an invalid IP.
|
||||||
|
if ip != nil {
|
||||||
|
// We should probably use a ready library for this.
|
||||||
|
if !isRemoteAddress(ip) {
|
||||||
|
return fmt.Errorf("rejected Relay URL: private IPs are not allowed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if !allowNonTLSRelay && parsedRelayURL.Scheme != "wss" {
|
if !allowNonTLSRelay && parsedRelayURL.Scheme != "wss" {
|
||||||
return fmt.Errorf("rejected Relay URL protocol: non-TLS not allowed")
|
return fmt.Errorf("rejected Relay URL protocol: non-TLS not allowed")
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ func main() {
|
||||||
probeURL := flag.String("nat-probe-server", sf.DefaultNATProbeURL, "NAT check probe server URL")
|
probeURL := flag.String("nat-probe-server", sf.DefaultNATProbeURL, "NAT check probe server URL")
|
||||||
outboundAddress := flag.String("outbound-address", "", "prefer the given address as outbound address")
|
outboundAddress := flag.String("outbound-address", "", "prefer the given address as outbound address")
|
||||||
allowedRelayHostNamePattern := flag.String("allowed-relay-hostname-pattern", "snowflake.torproject.net$", "a pattern to specify allowed hostname pattern for relay URL.")
|
allowedRelayHostNamePattern := flag.String("allowed-relay-hostname-pattern", "snowflake.torproject.net$", "a pattern to specify allowed hostname pattern for relay URL.")
|
||||||
|
allowProxyingToPrivateAddresses := flag.Bool("allow-proxying-to-private-addresses", false, "allow forwarding client connections to private IP addresses.\nUseful when a Snowflake server (relay) is hosted on the same private network as this proxy.")
|
||||||
allowNonTLSRelay := flag.Bool("allow-non-tls-relay", false, "allow relay without tls encryption")
|
allowNonTLSRelay := flag.Bool("allow-non-tls-relay", false, "allow relay without tls encryption")
|
||||||
NATTypeMeasurementInterval := flag.Duration("nat-retest-interval", time.Hour*24,
|
NATTypeMeasurementInterval := flag.Duration("nat-retest-interval", time.Hour*24,
|
||||||
"the time interval in second before NAT type is retested, 0s disables retest. Valid time units are \"s\", \"m\", \"h\". ")
|
"the time interval in second before NAT type is retested, 0s disables retest. Valid time units are \"s\", \"m\", \"h\". ")
|
||||||
|
@ -106,6 +107,7 @@ func main() {
|
||||||
EventDispatcher: eventLogger,
|
EventDispatcher: eventLogger,
|
||||||
|
|
||||||
RelayDomainNamePattern: *allowedRelayHostNamePattern,
|
RelayDomainNamePattern: *allowedRelayHostNamePattern,
|
||||||
|
AllowProxyingToPrivateAddresses: *allowProxyingToPrivateAddresses,
|
||||||
AllowNonTLSRelay: *allowNonTLSRelay,
|
AllowNonTLSRelay: *allowNonTLSRelay,
|
||||||
|
|
||||||
SummaryInterval: *summaryInterval,
|
SummaryInterval: *summaryInterval,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue