Mitigate DNS amplification attack surface

`metrics.status.sslip.io` is a vector for a DNS amplification attack; we
mitigate it by latching a 1/4 second throttle on each query after a
certain amount of queries.

That endpoint is a 4x amplifier: 100byte request with a 400 byte reply.
This commit is contained in:
Brian Cunnie
2022-01-22 15:51:35 -08:00
parent d35cc1faa6
commit bcceeba858
3 changed files with 61 additions and 4 deletions

View File

@@ -4,6 +4,7 @@ import (
"os/exec"
"strings"
"time"
"xip/xip"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
@@ -297,5 +298,27 @@ var _ = Describe("sslip.io-dns-server", func() {
})
})
})
When(`a TXT record for an "metrics.status.sslip.io" domain is repeatedly queries`, func() {
It("rate-limits the queries after some amount requests", func() {
// typically ~9 milliseconds / query, ~125 queries / sec on 4-core Xeon
var start, stop time.Time
throttled := false
// add an extra ten to the loop to really make sure we've exhausted the buffered channel
for i := 0; i < xip.MetricsBufferSize+10; i += 1 {
start = time.Now()
digArgs = "@localhost metrics.status.sslip.io txt"
digCmd = exec.Command("dig", strings.Split(digArgs, " ")...)
_, err := digCmd.Output()
Expect(err).ToNot(HaveOccurred())
stop = time.Now()
// we currently buffer at 250 milliseconds, so for our test we use a smidgen less because jitter
if stop.Sub(start) > 240*time.Millisecond {
throttled = true
break
}
}
Expect(throttled).To(BeTrue())
})
})
})
})

View File

@@ -81,6 +81,31 @@ func main() {
func readFrom(conn *net.UDPConn, wg *sync.WaitGroup, etcdCli xip.V3client, xipMetrics *xip.Metrics) {
defer wg.Done()
// We want to make sure that our DNS server isn't used in a DNS amplification attack.
// The endpoint we're worried about is metrics.status.sslip.io, whose reply is
// ~400 bytes with a query of ~100 bytes (4x amplification). We accomplish this by
// using channels with a quarter-second delay. Max throughput 1.2 kBytes/sec.
//
// We want to balance this delay against our desire to run tests quickly, so we buffer
// the channel with enough room to accommodate our tests.
//
// We realize that, if we're listening on several network interfaces, we're throttling
// _per interface_, not from a global standpoint, but we didn't want to clutter
// main() more than necessary.
//
// We also want to have fun playing with channels
dnsAmplificationAttackDelay := make(chan struct{}, xip.MetricsBufferSize)
go func() {
// fill up the channel's buffer so that our tests aren't slowed down (~85 tests)
for i := 0; i < xip.MetricsBufferSize; i += 1 {
dnsAmplificationAttackDelay <- struct{}{}
}
// now put on the brakes for users trying to leverage our server in a DNS amplification attack
for {
dnsAmplificationAttackDelay <- struct{}{}
time.Sleep(250 * time.Millisecond)
}
}()
for {
query := make([]byte, 512)
_, addr, err := conn.ReadFromUDP(query)
@@ -89,7 +114,12 @@ func readFrom(conn *net.UDPConn, wg *sync.WaitGroup, etcdCli xip.V3client, xipMe
continue
}
go func() {
xipServer := xip.Xip{SrcAddr: addr.IP, Etcd: etcdCli, Metrics: xipMetrics}
xipServer := xip.Xip{
SrcAddr: addr.IP,
Etcd: etcdCli,
Metrics: xipMetrics,
DnsAmplificationAttackDelay: dnsAmplificationAttackDelay,
}
response, logMessage, err := xipServer.QueryResponse(query)
if err != nil {
log.Println(err.Error())

View File

@@ -32,9 +32,10 @@ type V3client interface {
// through the call hierarchy
// (the source address for `ip.sslip.io`, and the etcd client for `k-v.io`)
type Xip struct {
SrcAddr net.IP
Etcd V3client
Metrics *Metrics
SrcAddr net.IP
Etcd V3client
DnsAmplificationAttackDelay chan struct{}
Metrics *Metrics
}
type Metrics struct {
@@ -109,6 +110,8 @@ var (
VersionDate = "0001/01/01-99:99:99-0800"
VersionGitHash = "cafexxx"
MetricsBufferSize = 100
TxtKvCustomizations = KvCustomizations{}
Customizations = DomainCustomizations{
"sslip.io.": {
@@ -793,6 +796,7 @@ func ipSslipIo(x Xip) ([]dnsmessage.TXTResource, error) {
// when TXT for "metrics.sslip.io" is queried, return the cumulative metrics
func metricsSslipIo(x Xip) (txtResources []dnsmessage.TXTResource, err error) {
<-x.DnsAmplificationAttackDelay
var metrics []string
uptime := time.Since(x.Metrics.Start)
metrics = append(metrics, fmt.Sprintf("uptime (seconds): %.0f", uptime.Seconds()))