diff --git a/Makefile.am b/Makefile.am index 980cd02..c346c1c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,7 @@ ACLOCAL_AMFLAGS = -I build +postamb = "modsecurity_rules \"SecAuditEngine On\"" + MAINTAINERCLEANFILES = \ config.log \ Makefile.in \ @@ -39,7 +41,7 @@ all: test: cd t/ && ./TEST -clean cd t/ && ./TEST -configure - cd t/ && ./TEST -httpd_conf conf/httpd.conf -httpd @APACHE@ -apxs @APXS@ + cd t/ && ./TEST -httpd_conf conf/httpd.conf -httpd @APACHE@ -apxs @APXS@ -postamble $(postamb) install-exec-hook: $(pkglib_LTLIBRARIES) diff --git a/t/00-phases.t b/t/00-phases.t new file mode 100644 index 0000000..f2468dd --- /dev/null +++ b/t/00-phases.t @@ -0,0 +1,132 @@ + +use strict; +use Apache::Test; +use Apache::TestRequest; + +require "t/find_string_in_file.pl"; + +plan tests => 5, have_lwp; + +my $audit_log = Apache::Test::config()->{vars}->{t_logs} . "/audit_logs.txt"; + +############################################################################### +# Test phases +# phase 1 (request headers) +############################################################################### +{ + # Remember position in audit log file before our test + my $audit_log_start_size = -s $audit_log or die "Failed to access file: " . $audit_log; + + my $url = "/00-phases/00-phases_01.html"; + my $ct_header_name = "Content-Type"; + my $ct_header_val = "application/x-www-form-urlencoded"; + my $content = "arg1=val1&arg2=val2"; + my $res1 = POST $url, $ct_header_name => $ct_header_val, content => $content; + + # Expected results + my $status_code_expected = 200; + my $audit_log_expected = 'Matched "Operator \`Rx\' with parameter \`\^POST\' against variable \`REQUEST_LINE'; + my $audit_log_missing = 'Matched [\s\S]*(ARGS|RESPONSE)'; + + my $audit_found_expected = find_string_in_file($audit_log, $audit_log_start_size, $audit_log_expected); + my $audit_found_missing = find_string_in_file($audit_log, $audit_log_start_size, $audit_log_missing); + + ok (($res1->code == $status_code_expected) && ($audit_found_expected) && (not $audit_found_missing)); +} + +############################################################################### +# Test phases +# phase 2 (request body) +############################################################################### +{ + # Remember position in audit log file before our test + my $audit_log_start_size = -s $audit_log or die "Failed to access file: " . $audit_log; + + my $url = "/00-phases/00-phases_02.html"; + my $ct_header_name = "Content-Type"; + my $ct_header_val = "application/x-www-form-urlencoded"; + my $content = "arg1=val1&arg2=val2"; + my $res1 = POST $url, $ct_header_name => $ct_header_val, content => $content; + + # Expected results + my $status_code_expected = 200; + my $audit_log_expected = 'Matched "Operator \`Rx\' with parameter \`\^POST\' against variable \`REQUEST_LINE[\s\S]*Matched "Operator \`Rx\' with parameter \`val1\' against variable \`ARGS'; + my $audit_log_missing = 'Matched [\s\S]*RESPONSE'; + + my $audit_found_expected = find_string_in_file($audit_log, $audit_log_start_size, $audit_log_expected); + my $audit_found_missing = find_string_in_file($audit_log, $audit_log_start_size, $audit_log_missing); + + ok (($res1->code == $status_code_expected) && ($audit_found_expected) && (not $audit_found_missing)); +} + +############################################################################### +# Test phases +# phase 3 (response headers) +############################################################################### +{ + # Remember position in audit log file before our test + my $audit_log_start_size = -s $audit_log or die "Failed to access file: " . $audit_log; + + my $url = "/00-phases/00-phases_03.html"; + my $ct_header_name = "Content-Type"; + my $ct_header_val = "application/x-www-form-urlencoded"; + my $content = "arg1=val1&arg2=val2"; + my $res1 = POST $url, $ct_header_name => $ct_header_val, content => $content; + + # Expected results + my $status_code_expected = 200; + my $audit_log_expected = 'Matched "Operator \`Rx\' with parameter \`\^POST\' against variable \`REQUEST_LINE[\s\S]*Matched "Operator \`Rx\' with parameter \`val1\' against variable \`ARGS[\s\S]*Matched "Operator \`Rx\' with parameter \`.\' against variable \`RESPONSE_HEADERS'; + my $audit_log_missing = 'Matched [\s\S]*RESPONSE_BODY'; + + my $audit_found_expected = find_string_in_file($audit_log, $audit_log_start_size, $audit_log_expected); + my $audit_found_missing = find_string_in_file($audit_log, $audit_log_start_size, $audit_log_missing); + + ok (($res1->code == $status_code_expected) && ($audit_found_expected) && (not $audit_found_missing)); +} + +############################################################################### +# Test phases +# phase 4 (response body) +############################################################################### +{ + # Remember position in audit log file before our test + my $audit_log_start_size = -s $audit_log or die "Failed to access file: " . $audit_log; + + my $url = "/00-phases/00-phases_04.html"; + my $ct_header_name = "Content-Type"; + my $ct_header_val = "application/x-www-form-urlencoded"; + my $content = "arg1=val1&arg2=val2"; + my $res1 = POST $url, $ct_header_name => $ct_header_val, content => $content; + + # Expected results + my $status_code_expected = 200; + my $audit_log_expected = 'Matched "Operator \`Rx\' with parameter \`\^POST\' against variable \`REQUEST_LINE[\s\S]*Matched "Operator \`Rx\' with parameter \`val1\' against variable \`ARGS[\s\S]*Matched "Operator \`Rx\' with parameter \`.\' against variable \`RESPONSE_HEADERS[\s\S]*Matched "Operator \`Rx\' with parameter \`TEST\' against variable \`RESPONSE_BODY'; + + my $audit_found_expected = find_string_in_file($audit_log, $audit_log_start_size, $audit_log_expected); + + ok (($res1->code == $status_code_expected) && ($audit_found_expected)); +} + +############################################################################### +# Test phases +# phase 5 (logging) +############################################################################### +{ + # Remember position in audit log file before our test + my $audit_log_start_size = -s $audit_log or die "Failed to access file: " . $audit_log; + + my $url = "/00-phases/00-phases_05.html"; + my $ct_header_name = "Content-Type"; + my $ct_header_val = "application/x-www-form-urlencoded"; + my $content = "arg1=val1&arg2=val2"; + my $res1 = POST $url, $ct_header_name => $ct_header_val, content => $content; + + # Expected results + my $status_code_expected = 200; + my $audit_log_expected = 'Matched "Operator \`Rx\' with parameter \`\^POST\' against variable \`REQUEST_LINE[\s\S]*Matched "Operator \`Rx\' with parameter \`val1\' against variable \`ARGS[\s\S]*Matched "Operator \`Rx\' with parameter \`.\' against variable \`RESPONSE_HEADERS[\s\S]*Matched "Operator \`Rx\' with parameter \`TEST\' against variable \`RESPONSE_BODY'; + + my $audit_found_expected = find_string_in_file($audit_log, $audit_log_start_size, $audit_log_expected); + + ok (($res1->code == $status_code_expected) && ($audit_found_expected)); +} + diff --git a/t/conf/extra.conf.in b/t/conf/extra.conf.in index 6518559..0cab08c 100644 --- a/t/conf/extra.conf.in +++ b/t/conf/extra.conf.in @@ -10,6 +10,10 @@ LoadModule security3_module "@ServerRoot@/.././src/.libs/mod_security3.so" # Lets make sure that the engine is on. modsecurity_rules 'SecRuleEngine On' +# Audit logs -- it is actually turned on via postamble +modsecurity_rules 'SecAuditLog @ServerRoot@/logs/audit_logs.txt' +modsecurity_rules 'SecAuditLogParts ABIJDEFHZ' + # Debug logs modsecurity_rules 'SecDebugLog @ServerRoot@/logs/debug_logs.txt' modsecurity_rules 'SecDebugLogLevel 9' @@ -61,3 +65,44 @@ modsecurity_rules 'SecDebugLogLevel 9' modsecurity_rules 'SecRule ARGS "evil" "phase:5,id:114,log,status:402,block,deny"' + + modsecurity_rules 'SecRequestBodyAccess On' + modsecurity_rules 'SecResponseBodyAccess On' + modsecurity_rules 'SecRule REQUEST_LINE "^POST" "phase:1,pass,log,auditlog,id:500169"' + modsecurity_rules 'SecRule ARGS "val1" "phase:1,pass,log,auditlog,id:500170"' + modsecurity_rules 'SecRule RESPONSE_HEADERS:Last-Modified "." "phase:1,pass,log,auditlog,id:500171"' + modsecurity_rules 'SecRule RESPONSE_BODY "TEST" "phase:1,pass,log,auditlog,id:500172"' + + + modsecurity_rules 'SecRequestBodyAccess On' + modsecurity_rules 'SecResponseBodyAccess On' + modsecurity_rules 'SecRule REQUEST_LINE "^POST" "phase:2,pass,log,auditlog,id:500173"' + modsecurity_rules 'SecRule ARGS "val1" "phase:2,pass,log,auditlog,id:500174"' + modsecurity_rules 'SecRule RESPONSE_HEADERS:Last-Modified "." "phase:2,pass,log,auditlog,id:500175"' + modsecurity_rules 'SecRule RESPONSE_BODY "TEST" "phase:2,pass,log,auditlog,id:500176"' + + + modsecurity_rules 'SecRequestBodyAccess On' + modsecurity_rules 'SecResponseBodyAccess On' + modsecurity_rules 'SecRule REQUEST_LINE "^POST" "phase:3,pass,log,auditlog,id:500177"' + modsecurity_rules 'SecRule ARGS "val1" "phase:3,pass,log,auditlog,id:500178"' + modsecurity_rules 'SecRule RESPONSE_HEADERS:Last-Modified "." "phase:3,pass,log,auditlog,id:500179"' + modsecurity_rules 'SecRule RESPONSE_BODY "TEST" "phase:3,pass,log,auditlog,id:500180"' + + + modsecurity_rules 'SecRequestBodyAccess On' + modsecurity_rules 'SecResponseBodyAccess On' + modsecurity_rules 'SecRule REQUEST_LINE "^POST" "phase:4,pass,log,auditlog,id:500181"' + modsecurity_rules 'SecRule ARGS "val1" "phase:4,pass,log,auditlog,id:500182"' + modsecurity_rules 'SecRule RESPONSE_HEADERS:Last-Modified "." "phase:4,pass,log,auditlog,id:500183"' + modsecurity_rules 'SecRule RESPONSE_BODY "TEST" "phase:4,pass,log,auditlog,id:500184"' + + + modsecurity_rules 'SecRequestBodyAccess On' + modsecurity_rules 'SecResponseBodyAccess On' + modsecurity_rules 'SecRule REQUEST_LINE "^POST" "phase:5,pass,log,auditlog,id:500185"' + modsecurity_rules 'SecRule ARGS "val1" "phase:5,pass,log,auditlog,id:500186"' + modsecurity_rules 'SecRule RESPONSE_HEADERS:Last-Modified "." "phase:5,pass,log,auditlog,id:500187"' + modsecurity_rules 'SecRule RESPONSE_BODY "TEST" "phase:5,pass,log,auditlog,id:500188"' + + diff --git a/t/find_string_in_file.pl b/t/find_string_in_file.pl new file mode 100644 index 0000000..fc386f4 --- /dev/null +++ b/t/find_string_in_file.pl @@ -0,0 +1,22 @@ + +use strict; +use FileHandle; + +# arg0 = filename; arg1 = start position; arg2 = string to find +sub find_string_in_file { + my $file_end_size = -s $_[0] or die "(find_string_in_file.pl) Failed to access file: " . $_[0]; + my $bytes_to_read = $file_end_size - $_[1]; + + my $file_content; + open FILE, $_[0] || die $!; + seek(FILE, $_[1], IO::Seekable::SEEK_SET); + my $bytes_read = read(FILE, $file_content, $bytes_to_read); + + # if (index($file_content, $_[2]) != -1) { + if ($file_content =~ $_[2]) { + return 1; + } + return 0; +} +1; + diff --git a/t/htdocs/00-phases/00-phases_01.html b/t/htdocs/00-phases/00-phases_01.html new file mode 100644 index 0000000..8d571db --- /dev/null +++ b/t/htdocs/00-phases/00-phases_01.html @@ -0,0 +1 @@ +00-phases_01 TEST diff --git a/t/htdocs/00-phases/00-phases_02.html b/t/htdocs/00-phases/00-phases_02.html new file mode 100644 index 0000000..ed919cc --- /dev/null +++ b/t/htdocs/00-phases/00-phases_02.html @@ -0,0 +1 @@ +00-phases_02 TEST diff --git a/t/htdocs/00-phases/00-phases_03.html b/t/htdocs/00-phases/00-phases_03.html new file mode 100644 index 0000000..284d5dd --- /dev/null +++ b/t/htdocs/00-phases/00-phases_03.html @@ -0,0 +1 @@ +00-phases_03 TEST diff --git a/t/htdocs/00-phases/00-phases_04.html b/t/htdocs/00-phases/00-phases_04.html new file mode 100644 index 0000000..34225a8 --- /dev/null +++ b/t/htdocs/00-phases/00-phases_04.html @@ -0,0 +1 @@ +00-phases_04 TEST diff --git a/t/htdocs/00-phases/00-phases_05.html b/t/htdocs/00-phases/00-phases_05.html new file mode 100644 index 0000000..4ffa328 --- /dev/null +++ b/t/htdocs/00-phases/00-phases_05.html @@ -0,0 +1 @@ +00-phases_05 TEST