]> git.ipfire.org Git - people/stevee/guardian.git/blame - modules/Parser.pm
Introduce message parser for owncloud.
[people/stevee/guardian.git] / modules / Parser.pm
CommitLineData
88d9af2c
SS
1package Guardian::Parser;
2use strict;
3use warnings;
4
5use Exporter qw(import);
6
cfe5a220 7our @EXPORT_OK = qw(IsSupportedParser Parser);
88d9af2c 8
cfe5a220
SS
9# This hash contains all supported parsers and which function
10# has to be called to parse messages in the right way.
88d9af2c 11my %logfile_parsers = (
55852ce7 12 "httpd" => \&message_parser_httpd,
7306aaf8 13 "owncloud" => \&message_parser_owncloud,
cfe5a220 14 "snort" => \&message_parser_snort,
0b1fe046 15 "ssh" => \&message_parser_ssh,
88d9af2c
SS
16);
17
18#
19## The main parsing function.
20#
21## It is used to determine which sub-parser has to be used to
22## parse the given message in the right way and to return if
23## any action should be performed.
24#
25sub Parser ($$) {
cfe5a220 26 my ($parser, @message) = @_;
88d9af2c
SS
27
28 # If no responsible message parser could be found, just return nothing.
cfe5a220 29 unless (exists($logfile_parsers{$parser})) {
88d9af2c
SS
30 return;
31 }
32
cfe5a220 33 # Call responsible message parser.
43fdb161 34 my @actions = $logfile_parsers{$parser}->(@message);
88d9af2c 35
8fba3c57 36 # In case an action has been returned, return it too.
43fdb161
SS
37 if (@actions) {
38 # Return which actions should be performed.
39 return @actions;
8fba3c57
SS
40 }
41
43fdb161 42 # Return undef, if no actions are required.
8fba3c57 43 return undef;
88d9af2c
SS
44}
45
cfe5a220
SS
46#
47## IsSupportedParser function.
48#
49## This very tiny function checks if a given parser name is available and
50## therefore a supported parser.
51#
52## To perform these check, the function is going to lookup if a key in the
53## hash of supported parsers is available
54#
55sub IsSupportedParser ($) {
56 my $parser = $_[0];
57
58 # Check if a key for the given parser exists in the hash of logfile_parsers.
59 if(exists($logfile_parsers{$parser})) {
60 # Found a valid parser, so return nothing.
61 return 1;
62 }
63
64 # Return "False" if we got here, and therefore no parser
65 # is available.
66 return;
67}
68
88d9af2c
SS
69#
70## The Snort message parser.
71#
72## This subfunction is responsible for parsing sort alerts and determine if
73## an action should be performed.
74#
53a02397
SS
75## XXX Currently the parser only supports IPv4. Add support for IPv6 at a
76## later time.
77#
78sub message_parser_snort(@) {
88d9af2c 79 my @message = @_;
8bc363c5
SS
80 my @actions;
81
82 # Temporary array to store single alerts.
83 my @alert;
88d9af2c 84
53a02397
SS
85 # The name of the parser module.
86 my $name = "SNORT";
87
53a02397
SS
88 # Default returned message in case no one could be grabbed
89 # from the snort alert.
90 my $message = "An active snort rule has matched and gained an alert.";
91
8bc363c5
SS
92 # Snort uses a log buffer and a result of this, when detecting multiple
93 # events at once, multiple alerts will be written at one time to the alert
94 # file. They have to be seperated from each, to be able to parse them
95 # individually.
53a02397 96 foreach my $line (@message) {
8bc363c5
SS
97 # Remove any newlines.
98 chomp($line);
99
100 # A single alert contains multiple lines, push all of them
101 # a temporary array.
102 push(@alert, $line);
103
104 # Each alert ends with an empty line, if one is found,
105 # all lines of the current processed alert have been found
106 # and pushed to the temporary array.
107 if($line =~ /^\s*$/) {
108 # Variable to store the grabbed IP-address.
109 my $address;
110
111 # Loop through all lines of the current alert.
112 foreach my $line (@alert) {
113 # Check Priority Level and skip the alert if it is to low.
114 #if ($line =~ /.*\[Priority: (\d+)\].*/) {
115 # return unless($1 < $priority);
116 #}
117
118 # Search for a line like xxx.xxx.xxx.xxx -> xxx.xxx.xxx.xxx
119 if ($line =~ /(\d+\.\d+\.\d+\.\d+)+ -\> (\d+\.\d+\.\d+\.\d+)+/) {
120 # Store the grabbed IP-address.
121 $address = $1;
122 }
123
124 # Search for a line like xxx.xxx.xxx.xxx:xxx -> xxx.xxx.xxx.xxx:xxx
125 elsif ($line =~ /(\d+\.\d+\.\d+\.\d+):\d+ -\> (\d+\.\d+\.\d+\.\d+):\d+/) {
126 # Store the obtained IP-address.
127 $address = $1;
128 }
129
130 # Obtain the reported reason from the headline of the alert.
131 if ($line =~ /.*\] (.*) \[\*\*\]/) {
132 # Store the extracted message.
133 $message = $1;
134 }
135
136 # If the reason could not be determined, try to obtain it from a msg field.
137 elsif ($line =~ /.*msg:\"(.*)\".*/) {
138 # Store the extracted message.
139 $message = $1;
140 }
141 }
142
143 # Check if at least the IP-address information has been extracted.
144 if (defined ($address)) {
145 # Add the extracted values and event message for the computed
146 # event to the actions array.
147 push(@actions, "count $address $name $message");
148 }
149
150 # The alert has been processed, clear the temporary array for storing
151 # the next alert.
152 @alert = ();
53a02397
SS
153 }
154 }
155
8bc363c5
SS
156 # If any actions are required, return the array.
157 if (@actions) {
158 return (@actions);
53a02397
SS
159 }
160
8bc363c5
SS
161 # If we got here, the alert could not be parsed correctly, or did not match any filter.
162 # Therefore it can be skipped - return nothing.
53a02397 163 return;
88d9af2c
SS
164}
165
0b1fe046
SS
166#
167## The SSH message parser.
168#
169## This subfunction is used for parsing and detecting different attacks
170## against the SSH service.
171#
172sub message_parser_ssh (@) {
173 my @message = @_;
43fdb161 174 my @actions;
0b1fe046
SS
175
176 # The name of the parser module.
177 my $name = "SSH";
178
179 # Variable to store the grabbed IP-address.
180 my $address;
181
182 # Variable to store the parsed event.
183 my $message;
184
185 # Loop through all lines, in case multiple one have
186 # been passed.
187 foreach my $line (@message) {
188 # Check for failed password attempts.
189 if ($line =~/.*sshd.*Failed password for (.*) from (.*) port.*/) {
190 # Store the grabbed IP-address.
191 $address = $2;
192
193 # Set event message.
194 $message = "Possible SSH-Bruteforce Attack for user: $1.";
195 }
196
197 # This should catch Bruteforce Attacks with enabled preauth
198 elsif ($line =~ /.*sshd.*Received disconnect from (.*):.*\[preauth\]/) {
199 # Store obtained IP-address.
200 $address = $1;
201
202 # Set event message.
203 $message = "Possible SSH-Bruteforce Attack - failed preauth.";
204 }
43fdb161
SS
205
206 # Check if at least the IP-address information has been extracted.
207 if (defined ($address)) {
208 # Add the extracted values and event message for the computed
209 # event to the actions array.
210 push(@actions, "count $address $name $message");
211 }
0b1fe046
SS
212 }
213
43fdb161
SS
214 # If any actions are required, return the array.
215 if (@actions) {
216 return (@actions);
0b1fe046
SS
217 }
218
219 # If we got here, the provided message is not affected by any filter and
220 # therefore can be skipped. Return nothing (False) in this case.
221 return;
222}
223
55852ce7
SS
224#
225## The HTTPD message parser.
226#
227## This subfunction is used for parsing and detecting different attacks
228## against a running HTTPD service.
229#
230sub message_parser_httpd (@) {
231 my @message = @_;
43fdb161 232 my @actions;
55852ce7
SS
233
234 # The name of the parser module.
235 my $name = "HTTPD";
236
237 # Variable to store the grabbed IP-address.
238 my $address;
239
240 # Variable to store the parsed event.
241 my $message;
242
243 # Loop through all lines, in case multiple one have
244 # been passed.
245 foreach my $line (@message) {
246 # This will catch brute-force attacks against htaccess logins (username).
247 if ($line =~ /.*\[error\] \[client (.*)\] user(.*) not found:.*/) {
248 # Store the grabbed IP-address.
249 $address = $1;
250
251 # Set event message.
252 $message = "Possible WUI brute-force attack, wrong user: $2.";
253 }
254
255 # Detect htaccess password brute-forcing against a username.
256 elsif ($line =~ /.*\[error\] \[client (.*)\] user(.*): authentication failure for.*/) {
257 # Store the extracted IP-address.
258 $address = $1;
259
260 # Set event message.
261 $message = "Possible WUI brute-force attack, wrong password for user: $2.";
262 }
43fdb161
SS
263
264 # Check if at least the IP-address information has been extracted.
265 if (defined ($address)) {
266 # Add the extracted values and event message to the actions array.
267 push(@actions, "count $address $name $message");
268 }
55852ce7
SS
269 }
270
43fdb161
SS
271 # If any actions are required, return the array.
272 if (@actions) {
273 return @actions;
55852ce7
SS
274 }
275
276 # If we got here, the provided message is not affected by any filter and
277 # therefore can be skipped. Return nothing (False) in this case.
278 return;
279}
280
7306aaf8
SS
281#
282## The Owncloud message parser.
283#
284## This subfunction is used for parsing and detecting brute-force login
285## attempts against a local running owncloud instance.
286#
287sub message_parser_owncloud (@) {
288 my @message = @_;
289 my @actions;
290
291 # The name of the parser module.
292 my $name = "Owncloud";
293
294 # Variable to store the grabbed IP-address.
295 my $address;
296
297 # Variable to store the parsed event.
298 my $message;
299
300 # Loop through all lines, in case multiple one have
301 # been passed.
302 foreach my $line (@message) {
303 # This will catch brute-force attacks against the login (username).
304 if ($line =~/.*\"Login failed: \'(.*)\' \(Remote IP: \'(.*)\'\,.*/) {
305 # Store the grabbed user name.
306 my $user = $1;
307
308 # Store the grabbed IP-address.
309 $address = $2;
310
311 # Set event message.
312 $message = "Possible brute-force attack, wrong password for user: $user.";
313 }
314
315 # Check if at least the IP-address information has been extracted.
316 if (defined ($address)) {
317 # Add the extracted values and event message to the actions array.
318 push(@actions, "count $address $name $message");
319 }
320 }
321
322 # If any actions are required, return the array.
323 if (@actions) {
324 return @actions;
325 }
326
327 # If we got here, the provided message is not affected by any filter and
328 # therefore can be skipped. Return nothing (False) in this case.
329 return;
330}
331
88d9af2c 3321;