mirror of
https://github.com/cunnie/sslip.io.git
synced 2025-10-07 08:31:02 +08:00
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:
@@ -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())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@@ -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())
|
||||
|
@@ -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()))
|
||||
|
Reference in New Issue
Block a user