| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package HTTP::Sessioniser; |
|
2
|
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
24168
|
use 5.008008; |
|
|
1
|
|
|
|
|
4
|
|
|
|
1
|
|
|
|
|
35
|
|
|
4
|
1
|
|
|
1
|
|
5
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
33
|
|
|
5
|
1
|
|
|
1
|
|
4
|
use warnings; |
|
|
1
|
|
|
|
|
6
|
|
|
|
1
|
|
|
|
|
34
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
# We don't actually use this, but if it isn't installed |
|
8
|
|
|
|
|
|
|
# then HTTP::Response will silently not ungzip data |
|
9
|
|
|
|
|
|
|
# Using it here will error if it is installed |
|
10
|
1
|
|
|
1
|
|
6604
|
use IO::Compress::Gzip; |
|
|
1
|
|
|
|
|
88360
|
|
|
|
1
|
|
|
|
|
62
|
|
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
# Be sure to use version 0.05 of HTTP::Parser with the two patches submitted |
|
13
|
|
|
|
|
|
|
# to the public CPAN bug tracker. |
|
14
|
1
|
|
|
1
|
|
961
|
use HTTP::Parser 0.05; |
|
|
1
|
|
|
|
|
44875
|
|
|
|
1
|
|
|
|
|
40
|
|
|
15
|
1
|
|
|
1
|
|
516
|
use Net::LibNIDS 0.02; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
use Carp; |
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
use AutoLoader qw(AUTOLOAD); |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
our $VERSION = '0.05'; |
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=head1 NAME |
|
23
|
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
HTTP::Sessioniser - Rebuild HTTP sessions from pcap streams |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
use HTTP::Sessioniser; |
|
29
|
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
# This will be called once per HTTP request/response pair |
|
31
|
|
|
|
|
|
|
sub my_callback { |
|
32
|
|
|
|
|
|
|
my ($request, $response, $info) = @_; |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# $request is HTTP::Request |
|
35
|
|
|
|
|
|
|
# $response is HTTP::Response |
|
36
|
|
|
|
|
|
|
} |
|
37
|
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
my $s = HTTP::Sessioniser->new(); |
|
39
|
|
|
|
|
|
|
$s->parse_file('/path/to/file.pcap', \&my_callback); |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
42
|
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
This module extracts HTTP sessions from pcap files with the help of Net::LibNIDS. |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
It will piece HTTP data back together and return a pair of HTTP::Request and |
|
46
|
|
|
|
|
|
|
HTTP::Response which correspond to one HTTP 'session'. |
|
47
|
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
HTTP CONNECT sessions are dealt with specially: the first request/response pair will |
|
49
|
|
|
|
|
|
|
be returned as normal, subsequent requests will be skipped (as they do not contain |
|
50
|
|
|
|
|
|
|
HTTP requests or responses, only SSL data). |
|
51
|
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=head2 EXPORT |
|
53
|
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
None by default. |
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
=head2 Methods |
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
=head3 new |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
my $s = HTTP::Sessioniser->new(); |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
Creates a new object. |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=cut |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
sub new { |
|
67
|
|
|
|
|
|
|
my ($class, %args) = @_; |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
my $self = bless({}, $class); |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
# Store statistics about the number of files parsed, requests found, failed |
|
72
|
|
|
|
|
|
|
# requests. Useful if the same instance of this is used to parse multiple files. |
|
73
|
|
|
|
|
|
|
$self->{statistics} = {}; |
|
74
|
|
|
|
|
|
|
$self->{statistics}{open_connections} = 0; |
|
75
|
|
|
|
|
|
|
$self->{statistics}{total_connections} = 0; |
|
76
|
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
@{$self->{portlist}} = qw(80 443 8080 3128); |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
return $self; |
|
80
|
|
|
|
|
|
|
} |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=head3 parse_file |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
$s->parse_file('/path/to/file.pcap', \&callback); |
|
85
|
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
Parses a pcap file using libnids, rebuilding pairs of HTTP::Request and HTTP::Response. These will be passed to the callback function along with a hash of information about the current connection. |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=cut |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
sub parse_file { |
|
91
|
|
|
|
|
|
|
my ($self, $filename, $callback) = @_; |
|
92
|
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
# Reset the current connections table |
|
94
|
|
|
|
|
|
|
$self->{connections} = {}; |
|
95
|
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
# Set this so we have them inside the callback |
|
97
|
|
|
|
|
|
|
$self->{current_filename} = $filename; |
|
98
|
|
|
|
|
|
|
$self->{callback} = $callback; |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
Net::LibNIDS::param::set_filename($filename); |
|
101
|
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
# Set a pcap filter, see the manpage for tcpdump for more information. The manpage for |
|
103
|
|
|
|
|
|
|
# libnids explains why the 'or (..)' is required. |
|
104
|
|
|
|
|
|
|
my $bpf_ports = join(" or ", map { "port $_" } @{$self->{portlist}}); |
|
105
|
|
|
|
|
|
|
Net::LibNIDS::param::set_pcap_filter($bpf_ports . ' or (ip[6:2] & 0x1fff != 0)'); |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
if (!Net::LibNIDS::init()) { |
|
108
|
|
|
|
|
|
|
warn "Uh oh, libnids failed to initialise!\n"; |
|
109
|
|
|
|
|
|
|
warn "Check you have successfully built and installed the module first.\n"; |
|
110
|
|
|
|
|
|
|
return; |
|
111
|
|
|
|
|
|
|
} |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
# Set the callback function and run libnids |
|
114
|
|
|
|
|
|
|
my $data_callback = sub { $self->process_data(@_); }; |
|
115
|
|
|
|
|
|
|
Net::LibNIDS::tcp_callback($data_callback); |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
# libnids resets state for each new file, so reset counter |
|
118
|
|
|
|
|
|
|
$self->{statistics}{open_connections} = 0; |
|
119
|
|
|
|
|
|
|
Net::LibNIDS::run(); |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
# TODO: At the end, go through and return all requests that had no responses? |
|
122
|
|
|
|
|
|
|
} |
|
123
|
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
sub clear_statistics { |
|
125
|
|
|
|
|
|
|
my ($self) = @_; |
|
126
|
|
|
|
|
|
|
undef $self->{statistics}; |
|
127
|
|
|
|
|
|
|
$self->{statistics} = {}; |
|
128
|
|
|
|
|
|
|
} |
|
129
|
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
=head3 ports |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
$s->ports( [ 80, 443, 8080, 3128 ] ); |
|
133
|
|
|
|
|
|
|
my @p = $s->ports; |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
Set or return and array of ports we expect to see HTTP transmission on. The default set of ports is 80, 443, 8080 and 3128. |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
If you are looking for HTTP on other ports (e.g. proxying or application servers) then use this to set the filter appropriately. |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=cut |
|
140
|
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
sub ports { |
|
142
|
|
|
|
|
|
|
my ($self, @ports) = @_; |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
if (@ports) { |
|
145
|
|
|
|
|
|
|
$self->{portlist} = qw(); |
|
146
|
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
foreach my $p (@ports) { |
|
148
|
|
|
|
|
|
|
push(@{$self->{portlist}}, $p) if $p =~ /\d+/ and ($p >= 0 or $p <= 65535); |
|
149
|
|
|
|
|
|
|
} |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# TODO: If our portlist is empty here, should we reset it? |
|
152
|
|
|
|
|
|
|
} |
|
153
|
|
|
|
|
|
|
return @{$self->{portlist}}; |
|
154
|
|
|
|
|
|
|
} |
|
155
|
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
=head3 add_port |
|
157
|
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
$s->add_port(8000); |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
Add one port to the filter list. |
|
161
|
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=cut |
|
163
|
|
|
|
|
|
|
sub add_port { |
|
164
|
|
|
|
|
|
|
my ($self, $tcpport) = @_; |
|
165
|
|
|
|
|
|
|
return 0 if $tcpport !~ /^\d+$/; |
|
166
|
|
|
|
|
|
|
return 0 if $tcpport < 0 or $tcpport > 65535; |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
push(@{$self->{portlist}}, $tcpport); |
|
169
|
|
|
|
|
|
|
return $tcpport; |
|
170
|
|
|
|
|
|
|
} |
|
171
|
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
# The libnids callback |
|
173
|
|
|
|
|
|
|
sub process_data { |
|
174
|
|
|
|
|
|
|
my ($self, $args) = @_; |
|
175
|
|
|
|
|
|
|
my $key = $args->client_ip . ":" . $args->client_port . "-" . $args->server_ip . ":" . $args->server_port; |
|
176
|
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
# If we want to stop processing a certain connection, we need |
|
178
|
|
|
|
|
|
|
# to skip any new events for it. Turning libnids collect_off |
|
179
|
|
|
|
|
|
|
# doesn't work, it will send NIDS_JUST_EST for new packets. |
|
180
|
|
|
|
|
|
|
if (defined $self->{connections}{$key}{ignored}) { |
|
181
|
|
|
|
|
|
|
return; |
|
182
|
|
|
|
|
|
|
} |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
if($args->state != Net::LibNIDS::NIDS_JUST_EST() && !defined $self->{connections}{$key}{request_obj}) { |
|
185
|
|
|
|
|
|
|
#print "ERROR: not just established and no object in $key\n"; |
|
186
|
|
|
|
|
|
|
print "status was: " . $args->state . "\n"; |
|
187
|
|
|
|
|
|
|
exit 1; |
|
188
|
|
|
|
|
|
|
} |
|
189
|
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
# Collect from any new connections |
|
191
|
|
|
|
|
|
|
if($args->state == Net::LibNIDS::NIDS_JUST_EST()) { |
|
192
|
|
|
|
|
|
|
$self->{statistics}{total_connections}++; |
|
193
|
|
|
|
|
|
|
$self->{statistics}{open_connections}++; |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
$args->server->collect_on(); |
|
196
|
|
|
|
|
|
|
$args->client->collect_on(); |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
# Create a request and response object |
|
199
|
|
|
|
|
|
|
$self->{connections}{$key}{request_obj} = HTTP::Parser->new(request => 1); |
|
200
|
|
|
|
|
|
|
$self->{connections}{$key}{response_obj} = HTTP::Parser->new(response => 1); |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
# print STDERR "New connection: $key (" . $statistics{open_connections} . " open)\n"; |
|
203
|
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
} elsif ($args->state == Net::LibNIDS::NIDS_CLOSE()) { |
|
205
|
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
# If this flag has been set, there was a successful response with |
|
207
|
|
|
|
|
|
|
# no content length header. We can assume that the connection close |
|
208
|
|
|
|
|
|
|
# marks the end-of-data, so pass it back now |
|
209
|
|
|
|
|
|
|
if ($self->{connections}{$key}->{no_content_length}) { |
|
210
|
|
|
|
|
|
|
# print STDERR "DEBUG: No content length header, connection closed, assuming finished\n"; |
|
211
|
|
|
|
|
|
|
$self->do_callback($key, $args); |
|
212
|
|
|
|
|
|
|
} |
|
213
|
|
|
|
|
|
|
# print "CLOSED CONNECTION EVENT FOR $key IS CALLING cleanup\n"; |
|
214
|
|
|
|
|
|
|
$self->cleanup($key); |
|
215
|
|
|
|
|
|
|
return; |
|
216
|
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
} elsif ( |
|
218
|
|
|
|
|
|
|
$args->state == Net::LibNIDS::NIDS_RESET() || |
|
219
|
|
|
|
|
|
|
$args->state == Net::LibNIDS::NIDS_TIMED_OUT() || |
|
220
|
|
|
|
|
|
|
$args->state == Net::LibNIDS::NIDS_EXITING() |
|
221
|
|
|
|
|
|
|
) { |
|
222
|
|
|
|
|
|
|
#print "EXIT/RESET CONNECTION EVENT FOR $key IS CALLING cleanup " . $args->state . "\n"; |
|
223
|
|
|
|
|
|
|
$self->cleanup($key); |
|
224
|
|
|
|
|
|
|
return; |
|
225
|
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
} elsif ($args->state == Net::LibNIDS::NIDS_DATA()) { |
|
227
|
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
# Data toward the server |
|
229
|
|
|
|
|
|
|
if ($args->server->count_new) { |
|
230
|
|
|
|
|
|
|
#print "DEBUG: Parsing data client->server\n"; |
|
231
|
|
|
|
|
|
|
my $data = substr($args->server->data, 0, $args->server->count_new); |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
# We should not receive new data for a new request if one is already complete. |
|
234
|
|
|
|
|
|
|
# But HTTP pipelining is allowed, which might get us here. So error. |
|
235
|
|
|
|
|
|
|
# TODO: Check HTTP::Parser when we hit this, or possibly implement pipelining |
|
236
|
|
|
|
|
|
|
if (defined $self->{connections}{$key}{request_complete}) { |
|
237
|
|
|
|
|
|
|
#print "ERROR: Got more client->server data when we we expecting server->client in $key\n"; |
|
238
|
|
|
|
|
|
|
$self->stop_collecting($args, $key); |
|
239
|
|
|
|
|
|
|
return; |
|
240
|
|
|
|
|
|
|
} |
|
241
|
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
if (!defined $self->{connections}{$key}{request_time}) { |
|
243
|
|
|
|
|
|
|
$self->{connections}{$key}{request_time} = $args->lastpacket_sec; |
|
244
|
|
|
|
|
|
|
$self->{connections}{$key}{request_time_usec} = $args->lastpacket_usec; |
|
245
|
|
|
|
|
|
|
} |
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
my $status; |
|
248
|
|
|
|
|
|
|
eval { |
|
249
|
|
|
|
|
|
|
$status = $self->{connections}{$key}{request_obj}->add($data); |
|
250
|
|
|
|
|
|
|
}; |
|
251
|
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
if ($@) { |
|
253
|
|
|
|
|
|
|
chomp $@; |
|
254
|
|
|
|
|
|
|
#print "ERROR: $key HTTP::Parser died for some reason ($@), data was:\n"; |
|
255
|
|
|
|
|
|
|
$self->stop_collecting($args, $key); |
|
256
|
|
|
|
|
|
|
return; |
|
257
|
|
|
|
|
|
|
} |
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
# Once we have enough data, mark the request as complete |
|
260
|
|
|
|
|
|
|
if ($status == 0) { |
|
261
|
|
|
|
|
|
|
if ($self->{connections}{$key}{request_obj}->object->method eq 'CONNECT') { |
|
262
|
|
|
|
|
|
|
# Set a flag for the rest of this connection so it is not parsed |
|
263
|
|
|
|
|
|
|
$self->stop_collecting($args, $key); |
|
264
|
|
|
|
|
|
|
return; |
|
265
|
|
|
|
|
|
|
} |
|
266
|
|
|
|
|
|
|
$self->{connections}{$key}{request_complete} = 1; |
|
267
|
|
|
|
|
|
|
# print "DEBUG: We have a complete request now, data was $data\n"; |
|
268
|
|
|
|
|
|
|
} else { |
|
269
|
|
|
|
|
|
|
#print "added to $key request:\n$data\nwhich got status $status\n"; |
|
270
|
|
|
|
|
|
|
} |
|
271
|
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
return; |
|
273
|
|
|
|
|
|
|
} |
|
274
|
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
# Data toward the client |
|
276
|
|
|
|
|
|
|
if ($args->client->count_new) { |
|
277
|
|
|
|
|
|
|
my $data = substr($args->client->data, 0, $args->client->count_new); |
|
278
|
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
# Data from the server->client before we expected it. Possibly HTTP pipelining, which |
|
280
|
|
|
|
|
|
|
# isn't yet supported. |
|
281
|
|
|
|
|
|
|
if (!defined $self->{connections}{$key}{request_complete}) { |
|
282
|
|
|
|
|
|
|
$self->stop_collecting($args, $key); |
|
283
|
|
|
|
|
|
|
return; |
|
284
|
|
|
|
|
|
|
} |
|
285
|
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
# Set the time from the first packet of the response |
|
287
|
|
|
|
|
|
|
if (!defined $self->{connections}{$key}{response_time}) { |
|
288
|
|
|
|
|
|
|
$self->{connections}{$key}{response_time} = $args->lastpacket_sec; |
|
289
|
|
|
|
|
|
|
$self->{connections}{$key}{response_time_usec} = $args->lastpacket_usec; |
|
290
|
|
|
|
|
|
|
} |
|
291
|
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
# HTTP::Parser uses die(), so catch that here |
|
293
|
|
|
|
|
|
|
my $status; |
|
294
|
|
|
|
|
|
|
eval { |
|
295
|
|
|
|
|
|
|
$status = $self->{connections}{$key}{response_obj}->add($data); |
|
296
|
|
|
|
|
|
|
}; |
|
297
|
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
if ($@) { |
|
299
|
|
|
|
|
|
|
chomp $@; |
|
300
|
|
|
|
|
|
|
# print "ERROR: HTTP::Parser died for some reason in $key: $@\n"; |
|
301
|
|
|
|
|
|
|
$self->stop_collecting($args, $key); |
|
302
|
|
|
|
|
|
|
return; |
|
303
|
|
|
|
|
|
|
} |
|
304
|
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
# Missing content-length header |
|
306
|
|
|
|
|
|
|
if ($status == -3) { |
|
307
|
|
|
|
|
|
|
# Set a flag to show that the response had no content-length header, |
|
308
|
|
|
|
|
|
|
# then assume at the end of this connection that we need to process it |
|
309
|
|
|
|
|
|
|
$self->{connections}{$key}{no_content_length} = 1; |
|
310
|
|
|
|
|
|
|
} |
|
311
|
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
# No more data needed |
|
313
|
|
|
|
|
|
|
if ($status == 0) { |
|
314
|
|
|
|
|
|
|
$self->do_callback($key, $args); |
|
315
|
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
} |
|
317
|
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
return; |
|
319
|
|
|
|
|
|
|
} |
|
320
|
|
|
|
|
|
|
} |
|
321
|
|
|
|
|
|
|
} |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
# We have a complete HTTP transaction, return it to the user |
|
324
|
|
|
|
|
|
|
sub do_callback { |
|
325
|
|
|
|
|
|
|
my ($self, $key, $nids_obj) = @_; |
|
326
|
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
my $request = $self->{connections}{$key}{request_obj}->object; |
|
328
|
|
|
|
|
|
|
my $response = $self->{connections}{$key}{response_obj}->object; |
|
329
|
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
# BUG: Shouldn't ever get this! |
|
331
|
|
|
|
|
|
|
if (!defined $request || !defined $response) { |
|
332
|
|
|
|
|
|
|
print "DEBUG: request or response is not defined in $key\n"; |
|
333
|
|
|
|
|
|
|
exit 1; |
|
334
|
|
|
|
|
|
|
} |
|
335
|
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
my $info = { |
|
337
|
|
|
|
|
|
|
'request_time' => $self->{connections}{$key}{request_time}, |
|
338
|
|
|
|
|
|
|
'request_time_usec' => $self->{connections}{$key}{request_time_usec}, |
|
339
|
|
|
|
|
|
|
'response_time' => $self->{connections}{$key}{response_time}, |
|
340
|
|
|
|
|
|
|
'response_time_usec' => $self->{connections}{$key}{response_time_usec}, |
|
341
|
|
|
|
|
|
|
'filename' => $self->{current_filename}, |
|
342
|
|
|
|
|
|
|
'client_ip' => $nids_obj->client_ip, |
|
343
|
|
|
|
|
|
|
'server_ip' => $nids_obj->server_ip |
|
344
|
|
|
|
|
|
|
}; |
|
345
|
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
$self->{callback}($request, $response, $info); |
|
347
|
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
# Reset state variables so that we can handle multiple HTTP |
|
349
|
|
|
|
|
|
|
# requests per TCP connection |
|
350
|
|
|
|
|
|
|
undef $self->{connections}{$key}{request_obj}; |
|
351
|
|
|
|
|
|
|
undef $self->{connections}{$key}{response_obj}; |
|
352
|
|
|
|
|
|
|
$self->{connections}{$key}{request_obj} = HTTP::Parser->new(request => 1); |
|
353
|
|
|
|
|
|
|
$self->{connections}{$key}{response_obj} = HTTP::Parser->new(response => 1); |
|
354
|
|
|
|
|
|
|
undef $self->{connections}{$key}{request_complete}; |
|
355
|
|
|
|
|
|
|
undef $self->{connections}{$key}{request_time}; |
|
356
|
|
|
|
|
|
|
undef $self->{connections}{$key}{response_time}; |
|
357
|
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
return; |
|
359
|
|
|
|
|
|
|
} |
|
360
|
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
# Stop collecting on a connection for some reason |
|
362
|
|
|
|
|
|
|
sub stop_collecting { |
|
363
|
|
|
|
|
|
|
my ($self, $args, $key) = @_; |
|
364
|
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
# TODO: Can we save the stream out as a pcap if it fails processing? |
|
366
|
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
# We could set libnids collect_off here, but it will just generate |
|
368
|
|
|
|
|
|
|
# NIDS_JUST_EST events for future packets. So we don't bother. |
|
369
|
|
|
|
|
|
|
$self->cleanup($key); |
|
370
|
|
|
|
|
|
|
$self->{connections}{$key}{ignored} = 1; |
|
371
|
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
return; |
|
373
|
|
|
|
|
|
|
} |
|
374
|
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
# A connection has finished, cleanup any associated data |
|
376
|
|
|
|
|
|
|
sub cleanup { |
|
377
|
|
|
|
|
|
|
my ($self, $key) = @_; |
|
378
|
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
delete $self->{connections}{$key}; |
|
380
|
|
|
|
|
|
|
$self->{statistics}{open_connections}--; |
|
381
|
|
|
|
|
|
|
} |
|
382
|
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
=head1 BUGS |
|
384
|
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
This module does not support HTTP pipelining. It could be added if I find data which requires it. |
|
386
|
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
388
|
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
HTTP::Parser - used to parse data into HTTP::Request or HTTP::Response objects |
|
390
|
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
=head1 AUTHOR |
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
David Cannings Edavid@edeca.netE |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
396
|
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
Copyright (C) 2010 by David Cannings |
|
398
|
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
=cut |
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
1; |
|
402
|
|
|
|
|
|
|
__END__ |