Skip to content

Commit

Permalink
Blocklist fix via DNS pinning (#138)
Browse files Browse the repository at this point in the history
* revisit blocklist checking to prevent DNS rebinding for domain coverts

* check port value and add test cases

* comment out unused elligator client code in conjure/libtapdance

* add option to use allowlist and add local addrs automatically

* accomodate github action update ubuntu latest to 20.04

* builds still failing

* builds still failing - try pinning to 18.04

* test builds for pfring nightly on 20.04

* test builds for pfring stable on 20.04
  • Loading branch information
jmwample authored Jul 27, 2022
1 parent 8ddb089 commit ab7766e
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 91 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
build:
name: Build Station pieces
# The type of runner that the job will run on
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
Expand All @@ -36,8 +36,8 @@ jobs:
sudo apt-get install protobuf-compiler gcc curl git wget software-properties-common -y -q
sudo apt-get install libzmq3-dev libssl-dev pkg-config libgmp3-dev -y -q
sudo add-apt-repository universe
wget https://packages.ntop.org/apt-stable/18.04/all/apt-ntop-stable.deb
sudo apt-get install ./apt-ntop-stable.deb
wget https://packages.ntop.org/apt-stable/20.04/all/apt-ntop-stable.deb
sudo apt install ./apt-ntop-stable.deb
sudo apt-get update
sudo apt-get install pfring
echo "Apt dependencies installed"
Expand All @@ -57,7 +57,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17.x
go-version: 1.18.x

- name: Build app
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/golang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
go-version: [1.16.x, 1.17.x]
go-version: [1.16.x, 1.17.x, 1.18.x]

runs-on: ubuntu-latest
steps:
Expand All @@ -37,7 +37,7 @@ jobs:
run: |
cd $GITHUB_WORKSPACE/go/src/github.com/refraction-networking/conjure
export GOPATH="$GITHUB_WORKSPACE/go"
go test -v ./...
go test -v -race ./...
- name: Build app
run: |
Expand Down
20 changes: 13 additions & 7 deletions application/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,35 @@ heartbeat_timeout = 1000
# in the liveness module.
cache_expiration_time = "2.0h"

# Allow the station to opt out of either version of internet protocol to limit a
# Allow the station to opt out of either version of internet protocol to limit a
# statio to handling one or the other. For example, v6 on small station deployment
# with only v6 phantom subnet, v4 only on station with no puvlic v6 address.
# with only v6 phantom subnet, v4 only on station with no puvlic v6 address.
enable_v4 = true
enable_v6 = false

# If a registration is received with a covert address in one of these subnets it will
# be ignored and dropped. This is to prevent clients leveraging the outgoing
# connections from the station to connect to sation infrastructure that would
# connections from the station to connect to sation infrastructure that would
# be othewise firewalled.
covert_blocklist_domains = ["localhost"]
covert_blocklist_subnets = [
"127.0.0.1/32", # localhost ipv4
"10.0.0.0/8", # reserved ipv4
"10.0.0.0/8", # reserved ipv4
"172.16.0.0/12", # reserved ipv4
"192.168.0.0/16", # reserved ipv4
"fc00::/7 ", # private network ipv6
"fe80::0/16", # link local ipv6
"::1/128", # localhost ipv6
]

covert_blocklist_domains = [
"localhost",
]
# Automatically add all addresses and subnets associated with local devices to
# the blocklist.
covert_blocklist_public_addrs = false

# Override the blocklist providing a more restrictive allowlist. Any addresses
# not explicitly included in an allowlisted subnet will be considered
# blocklisted and the registration will be dropped.
covert_allowlist_subnets = []

# If a registration is received and the phantom address is in one of these
# subnets the registration will be dropped. This allows us to exclude subnets to
Expand Down
131 changes: 113 additions & 18 deletions application/lib/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net"
"os"
"regexp"
"strconv"

"github.com/BurntSushi/toml"
)
Expand All @@ -26,6 +27,12 @@ type Config struct {
// Local list of disallowed subnets for covert addresses.
CovertBlocklistSubnets []string `toml:"covert_blocklist_subnets"`
covertBlocklistSubnets []*net.IPNet
// At launch add all public addresses from machine to blocklist.
CovertBlocklistPublicAddrs bool `toml:"covert_blocklist_public_addrs"`
// Local list of allowed subnets for covert addresses.
CovertAllowlistSubnets []string `toml:"covert_allowlist_subnets"`
enableCovertAllowlist bool
covertAllowlistSubnets []*net.IPNet

// Local list of disallowed domain patterns for covert addresses.
CovertBlocklistDomains []string `toml:"covert_blocklist_domains"`
Expand All @@ -39,6 +46,8 @@ type Config struct {
CacheExpirationTime string `toml:"cache_expiration_time"`
}

// ParseConfig parses the config from the CJ_STATION_CONFIG environment
// variable.
func ParseConfig() (*Config, error) {
var c Config
_, err := toml.DecodeFile(os.Getenv("CJ_STATION_CONFIG"), &c)
Expand Down Expand Up @@ -75,38 +84,124 @@ func (c *Config) parseBlocklists() {
c.phantomBlocklist = append(c.phantomBlocklist, ipNet)
}
}

c.covertAllowlistSubnets = []*net.IPNet{}
for _, subnet := range c.CovertAllowlistSubnets {
_, ipNet, err := net.ParseCIDR(subnet)
if err == nil {
c.covertAllowlistSubnets = append(c.covertAllowlistSubnets, ipNet)
}
}
if len(c.covertAllowlistSubnets) > 0 {
c.enableCovertAllowlist = true
}

if c.CovertBlocklistPublicAddrs {
// Add all public local addresses to the blocklist.
ifaces, err := net.Interfaces()
if err != nil {
return
}

for _, i := range ifaces {
addrs, err := i.Addrs()
if err != nil {
continue
}

for _, addr := range addrs {
switch v := addr.(type) {
case *net.IPNet:
c.covertBlocklistSubnets = append(c.covertBlocklistSubnets, v)
case *net.IPAddr:
_, ipNet, err := net.ParseCIDR(v.IP.String() + "\\32")
if err == nil {
c.phantomBlocklist = append(c.phantomBlocklist, ipNet)
}
}

}
}
}
}

func (c *Config) IsBlocklisted(urlStr string) bool {
// ParseOrResolveBlocklisted attempts to return an IP:port string whenever
// possible either by parsing the IP to ensure correct format or resolving
// domain names. It also checks the configuration blocklists for both domain
// name and IP address. The intention of this function is that it be used to
// prevent SSRF DNS rebinding by doing resolution to final address to be used by
// net.Dial and checking blocklists in the same step.
//
// If a bad address / domain is given and empty string will be returned
func (c *Config) ParseOrResolveBlocklisted(provided string) string {

a := net.ParseIP(provided)
if a != nil {
// IP address with no port provided
return ""
}

host, _, err := net.SplitHostPort(urlStr)
if err != nil || host == "" {
// unable to parse host:port
return true
host, port, err := net.SplitHostPort(provided)
if err != nil {
return ""
}
if c.isBlocklistedCovertDomain(host) {
return ""
}

if addr := net.ParseIP(host); addr != nil {
if !addr.IsGlobalUnicast() {
// No anycast / private / loopback allowed.
return true
}
for _, net := range c.covertBlocklistSubnets {
_, err = strconv.ParseUint(port, 10, 16)
if err != nil {
return ""
}

addr, err := net.ResolveIPAddr("ip", host)
if err != nil {
return ""
}
if addr == nil || c.isBlocklistedCovertAddr(addr.IP) {
return ""
}
return net.JoinHostPort(addr.String(), port)
}

// isBlocklistedCovertAddr checks if the provided host string should be
// blocked by on of the blocklisted subnets.
func (c *Config) isBlocklistedCovertAddr(addr net.IP) bool {
if c.enableCovertAllowlist {
// If allowlist check is enabled it takes precedence over blocklist.
for _, net := range c.covertAllowlistSubnets {
if net.Contains(addr) {
// blocked by IP address
return true
return false
}
}
} else {
for _, pattern := range c.covertBlocklistDomains {
if pattern.MatchString(host) {
// blocked by Domain pattern
return true
}
return true
}

for _, net := range c.covertBlocklistSubnets {
if net.Contains(addr) {
// blocked by IP address
return true
}
}

return false
}

// isBlocklistedCovertDomain checks if the provided host string should be
// blocked by on of the blocklisted Domain patterns.
func (c *Config) isBlocklistedCovertDomain(provided string) bool {
for _, pattern := range c.covertBlocklistDomains {
if pattern.MatchString(provided) {
return true
}
}

return false
}

// IsBlocklistedPhantom checks if the provided address should be
// denied by on of the blocklisted Phantom subnets.
func (c *Config) IsBlocklistedPhantom(addr net.IP) bool {
for _, net := range c.phantomBlocklist {
if net.Contains(addr) {
Expand Down
Loading

0 comments on commit ab7766e

Please sign in to comment.