]> git.ipfire.org Git - people/stevee/guardian.git/blob - modules/Parser.pm
Only log the request to flush the firewall chain in debug mode.
[people/stevee/guardian.git] / modules / Parser.pm
1 package Guardian::Parser;
2 use strict;
3 use warnings;
4
5 use Exporter qw(import);
6
7 our @EXPORT_OK = qw(IsSupportedParser Parser);
8
9 # This hash contains all supported parsers and which function
10 # has to be called to parse messages in the right way.
11 my %logfile_parsers = (
12 "httpd" => \&message_parser_httpd,
13 "owncloud" => \&message_parser_owncloud,
14 "snort" => \&message_parser_snort,
15 "ssh" => \&message_parser_ssh,
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 #
25 sub Parser ($$) {
26 my ($parser, @message) = @_;
27
28 # If no responsible message parser could be found, just return nothing.
29 unless (exists($logfile_parsers{$parser})) {
30 return;
31 }
32
33 # Call responsible message parser.
34 my @actions = $logfile_parsers{$parser}->(@message);
35
36 # In case an action has been returned, return it too.
37 if (@actions) {
38 # Return which actions should be performed.
39 return @actions;
40 }
41
42 # Return undef, if no actions are required.
43 return undef;
44 }
45
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 #
55 sub 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
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 #
75 ## XXX Currently the parser only supports IPv4. Add support for IPv6 at a
76 ## later time.
77 #
78 sub message_parser_snort(@) {
79 my @message = @_;
80 my @actions;
81
82 # Temporary array to store single alerts.
83 my @alert;
84
85 # The name of the parser module.
86 my $name = "SNORT";
87
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
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.
96 foreach my $line (@message) {
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 = ();
153 }
154 }
155
156 # If any actions are required, return the array.
157 if (@actions) {
158 return (@actions);
159 }
160
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.
163 return;
164 }
165
166 #
167 ## The SSH message parser.
168 #
169 ## This subfunction is used for parsing and detecting different attacks
170 ## against the SSH service.
171 #
172 sub message_parser_ssh (@) {
173 my @message = @_;
174 my @actions;
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 }
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 }
212 }
213
214 # If any actions are required, return the array.
215 if (@actions) {
216 return (@actions);
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
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 #
230 sub message_parser_httpd (@) {
231 my @message = @_;
232 my @actions;
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 }
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 }
269 }
270
271 # If any actions are required, return the array.
272 if (@actions) {
273 return @actions;
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
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 #
287 sub 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
332 1;