-
Notifications
You must be signed in to change notification settings - Fork 0
/
slowloris_test.go
156 lines (125 loc) · 4.4 KB
/
slowloris_test.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
154
155
156
package turtle
import (
"net/http"
"sync"
"testing"
"time"
"github.com/b4fun/turtle/internal/testhttp"
"github.com/stretchr/testify/assert"
)
func Test_Slowloris_defaults(t *testing.T) {
v := Slowloris{}
assert.NoError(t, v.defaults())
assert.Equal(t, http.MethodGet, v.Method)
assert.NotEmpty(t, v.UserAgents)
assert.NotNil(t, v.dial)
assert.NotNil(t, v.randn)
}
func Test_Slowloris_single(t *testing.T) {
t.Parallel()
counter := testhttp.NewConnStateCounters()
testCtx, stopTest := getTestContext()
defer stopTest()
const readHeaderTimeout = 300 * time.Millisecond
serverUrl, serverStopped := testhttp.CreateTestServer(testCtx, t, func(s *http.Server) {
s.ReadHeaderTimeout = readHeaderTimeout
s.ConnState = counter.ServerConnState
})
var (
events []Event
eventMu sync.Mutex
)
eventHandler := EventHandleFunc(func(e Event) {
eventMu.Lock()
defer eventMu.Unlock()
events = append(events, e)
})
target := getTestTarget(t, *serverUrl)
target.Connections = 1
target.EventHandler = eventHandler
attack := Slowloris{
Target: target,
SendGibberish: true,
GibberishInterval: 10 * time.Millisecond,
}
assert.NoError(t, attack.Run(testCtx))
stopTest()
<-serverStopped
t.Log(counter.String())
t.Log(eventsSeqToString(events))
conns := counter.GetConns()
assert.NotEmpty(t, conns, "should have at least one connection")
assert.Greater(t, len(conns), target.Connections, "should create more connections")
for _, conn := range conns {
timeline := counter.GetConnStateTimeline(conn)
assert.GreaterOrEqual(t, len(timeline), 1, "should have at least one state")
// a typical state transition is: new - (due to server close) -> active -> closed
assert.Equal(t, http.StateNew, timeline[0])
}
var lastDialEvent *Event
for idx := 0; idx < len(events)-1; idx++ { // NOTE: skip the last event as it might be closed by the worker not server
event := events[idx]
switch event.Name {
case EventTCPDial:
lastDialEvent = &event
case EventTCPClosed:
assert.NotNil(t, lastDialEvent, "missing dial event for closed at index %d", idx)
duration := event.At.Sub(lastDialEvent.At)
timeoutDelta := duration - readHeaderTimeout
if timeoutDelta < 0 {
timeoutDelta = -timeoutDelta
}
assert.LessOrEqual(t, timeoutDelta, 100*time.Millisecond, "timeout delta should be less than 100ms")
}
}
}
func Test_Slowloris_vulnerable(t *testing.T) {
t.Parallel()
counter := testhttp.NewConnStateCounters()
testCtx, stopTest := getTestContext()
defer stopTest()
serverUrl, serverStopped := testhttp.CreateTestServer(testCtx, t, func(s *http.Server) {
s.ReadHeaderTimeout = 0 // hung forever
s.ConnState = counter.ServerConnState
})
target := getTestTarget(t, *serverUrl)
attack := Slowloris{Target: target, SendGibberish: true, GibberishInterval: 10 * time.Millisecond}
assert.NoError(t, attack.Run(testCtx))
stopTest()
<-serverStopped
t.Log(counter.String())
conns := counter.GetConns()
assert.NotEmpty(t, conns, "should have at least one connection")
assert.Len(t, conns, target.Connections, "should not create more connections")
for _, conn := range conns {
timeline := counter.GetConnStateTimeline(conn)
assert.GreaterOrEqual(t, len(timeline), 1, "should have at least one state")
// a typical state transition is: new - (due to server close) -> active -> closed
assert.Equal(t, http.StateNew, timeline[0])
}
}
func Test_Slowloris_invulnerable(t *testing.T) {
t.Parallel()
counter := testhttp.NewConnStateCounters()
testCtx, stopTest := getTestContext()
defer stopTest()
serverUrl, serverStopped := testhttp.CreateTestServer(testCtx, t, func(s *http.Server) {
s.ReadHeaderTimeout = 1 * time.Second
s.ConnState = counter.ServerConnState
})
target := getTestTarget(t, *serverUrl)
attack := Slowloris{Target: target, SendGibberish: true, GibberishInterval: 10 * time.Millisecond}
assert.NoError(t, attack.Run(testCtx))
stopTest()
<-serverStopped
t.Log(counter.String())
conns := counter.GetConns()
assert.NotEmpty(t, conns, "should have at least one connection")
assert.Greater(t, len(conns), target.Connections, "should create more connections")
for _, conn := range conns {
timeline := counter.GetConnStateTimeline(conn)
assert.GreaterOrEqual(t, len(timeline), 1, "should have at least one state")
// a typical state transition is: new - (due to read header timeout close) -> active -> closed
assert.Equal(t, http.StateNew, timeline[0])
}
}