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"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"xip/xip"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "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) {
|
func readFrom(conn *net.UDPConn, wg *sync.WaitGroup, etcdCli xip.V3client, xipMetrics *xip.Metrics) {
|
||||||
defer wg.Done()
|
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 {
|
for {
|
||||||
query := make([]byte, 512)
|
query := make([]byte, 512)
|
||||||
_, addr, err := conn.ReadFromUDP(query)
|
_, addr, err := conn.ReadFromUDP(query)
|
||||||
@@ -89,7 +114,12 @@ func readFrom(conn *net.UDPConn, wg *sync.WaitGroup, etcdCli xip.V3client, xipMe
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go func() {
|
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)
|
response, logMessage, err := xipServer.QueryResponse(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err.Error())
|
log.Println(err.Error())
|
||||||
|
@@ -34,6 +34,7 @@ type V3client interface {
|
|||||||
type Xip struct {
|
type Xip struct {
|
||||||
SrcAddr net.IP
|
SrcAddr net.IP
|
||||||
Etcd V3client
|
Etcd V3client
|
||||||
|
DnsAmplificationAttackDelay chan struct{}
|
||||||
Metrics *Metrics
|
Metrics *Metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +110,8 @@ var (
|
|||||||
VersionDate = "0001/01/01-99:99:99-0800"
|
VersionDate = "0001/01/01-99:99:99-0800"
|
||||||
VersionGitHash = "cafexxx"
|
VersionGitHash = "cafexxx"
|
||||||
|
|
||||||
|
MetricsBufferSize = 100
|
||||||
|
|
||||||
TxtKvCustomizations = KvCustomizations{}
|
TxtKvCustomizations = KvCustomizations{}
|
||||||
Customizations = DomainCustomizations{
|
Customizations = DomainCustomizations{
|
||||||
"sslip.io.": {
|
"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
|
// when TXT for "metrics.sslip.io" is queried, return the cumulative metrics
|
||||||
func metricsSslipIo(x Xip) (txtResources []dnsmessage.TXTResource, err error) {
|
func metricsSslipIo(x Xip) (txtResources []dnsmessage.TXTResource, err error) {
|
||||||
|
<-x.DnsAmplificationAttackDelay
|
||||||
var metrics []string
|
var metrics []string
|
||||||
uptime := time.Since(x.Metrics.Start)
|
uptime := time.Since(x.Metrics.Start)
|
||||||
metrics = append(metrics, fmt.Sprintf("uptime (seconds): %.0f", uptime.Seconds()))
|
metrics = append(metrics, fmt.Sprintf("uptime (seconds): %.0f", uptime.Seconds()))
|
||||||
|
Reference in New Issue
Block a user