-
Notifications
You must be signed in to change notification settings - Fork 86
/
Copy pathheartbleeder.go
153 lines (130 loc) · 3.92 KB
/
heartbleeder.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package main
import (
"encoding/binary"
"flag"
"fmt"
"io"
"log"
"net"
"os"
"runtime"
"strings"
"time"
"github.com/titanous/heartbleeder/tls"
)
var defaultTLSConfig = tls.Config{InsecureSkipVerify: true}
const (
ResultSecure = iota
ResultUnknown
ResultConnectionRefused
ResultVunerable
ResultError
)
type Dialer func(string) (*tls.Conn, error)
func main() {
pg := flag.Bool("pg", false, "Check PostgreSQL TLS, incompatible with -hostfile")
timeout := flag.Duration("timeout", 5*time.Second, "Timeout after sending heartbeat")
hostFile := flag.String("hostfile", "", "Path to a newline separated file with hosts or ips")
workers := flag.Int("workers", runtime.NumCPU()*10, "Number of workers to scan hosts with, only used with hostfile flag")
retryDelay := flag.Duration("retry", 10*time.Second, "Seconds to wait before retesting a host after an unfavorable response")
refreshDelay := flag.Duration("refresh", 10*time.Minute, "Seconds to wait before rechecking secure hosts")
listen := flag.String("listen", "localhost:5000", "Address to serve HTTP dashboard from")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [options] host[:443]\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Options:\n")
flag.PrintDefaults()
}
flag.Parse()
if *hostFile != "" {
checkMultiHosts(*hostFile, *timeout, *retryDelay, *refreshDelay, *workers, *listen)
} else {
if flag.NArg() != 1 {
flag.Usage()
os.Exit(2)
}
checkSingleHost(flag.Arg(0), *timeout, *pg)
}
}
func checkSingleHost(host string, timeout time.Duration, pg bool) {
log.SetFlags(0)
if !strings.Contains(host, ":") {
if pg {
host += ":5432"
} else {
host += ":443"
}
}
var d Dialer
if pg {
d = pgStartTLS
}
ret, _ := checkHeartbeat(host, timeout, d)
os.Exit(ret)
}
func checkHeartbeat(host string, timeout time.Duration, dial Dialer) (int, error) {
var err error
var c *tls.Conn
if dial != nil {
c, err = dial(host)
} else {
c, err = tls.Dial("tcp", host, &defaultTLSConfig)
}
if err != nil {
log.Printf("Error connecting to %s: %s\n", host, err)
return ResultConnectionRefused, err
}
defer c.Close()
err = c.WriteHeartbeat(1, nil)
if err == tls.ErrNoHeartbeat {
log.Printf("SECURE(%s) - does not have the heartbeat extension enabled", host)
return ResultSecure, err
}
if err != nil {
log.Printf("UNKNOWN(%s) - Heartbeat enabled, but there was an error writing the payload:", host, err)
return ResultError, err
}
readErr := make(chan error)
go func() {
_, _, err := c.ReadHeartbeat()
readErr <- err
}()
select {
case err := <-readErr:
if err == nil {
log.Printf("VULNERABLE(%s) - has the heartbeat extension enabled and is vulnerable to CVE-2014-0160", host)
return ResultVunerable, err
}
log.Printf("SECURE(%s) has heartbeat extension enabled but is not vulnerable: %q", host, err)
return ResultSecure, err
case <-time.After(timeout):
}
log.Printf("SECURE(%s) - has the heartbeat extension enabled, but timed out after a malformed heartbeat (this likely means that it is not vulnerable)", host)
return ResultSecure, err
}
func pgStartTLS(addr string) (*tls.Conn, error) {
c, err := net.Dial("tcp", addr)
if err != nil {
return nil, err
}
// send an SSLRequest message as per Postgres protocol documentation:
// http://www.postgresql.org/docs/9.3/static/protocol-flow.html#AEN99228
message := make([]byte, 8)
binary.BigEndian.PutUint32(message[:4], 8)
binary.BigEndian.PutUint32(message[4:], 80877103)
_, err = c.Write(message)
if err != nil {
return nil, fmt.Errorf("could not write to server: %v", err)
}
// read the response
response := make([]byte, 1)
_, err = io.ReadFull(c, response)
if err != nil {
return nil, fmt.Errorf("could not read server response: %v", err)
}
// if the response is not 'S', no ssl
if response[0] != 'S' {
return nil, fmt.Errorf("this server does not support SSL")
}
// otherwise, we have a connection to try to heartbeat
return tls.Client(c, &defaultTLSConfig), nil
}