From 7d501572cc4bca824e54ccdfd84166ae74fb8dd7 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Sat, 19 Aug 2017 00:03:12 -0300 Subject: [PATCH 01/35] Adds `tests` from ModSecurity version 2 Some modifications were made in order to have it workable in the v3 three. --- configure.ac | 39 + tests/.deps/msc_test-msc_test.Po | 1 + .../regression/action/00-disruptive-actions.t | 593 ++++++ tests/regression/action/00-meta.t | 8 + tests/regression/action/00-misc.t | 22 + tests/regression/action/00-transformations.t | 8 + tests/regression/action/10-append-prepend.t | 49 + tests/regression/action/10-ctl.t | 56 + .../action/10-detectiononly-actions.t | 545 +++++ tests/regression/action/10-logging.t | 564 +++++ tests/regression/config/00-load-modsec.t | 29 + tests/regression/config/10-audit-directives.t | 269 +++ tests/regression/config/10-debug-directives.t | 278 +++ tests/regression/config/10-misc-directives.t | 145 ++ .../regression/config/10-request-directives.t | 705 +++++++ .../config/10-response-directives.t | 174 ++ tests/regression/config/20-chroot.t | 35 + tests/regression/misc/00-multipart-parser.t | 1813 +++++++++++++++++ tests/regression/misc/00-phases.t | 151 ++ tests/regression/misc/10-pcre.t | 38 + tests/regression/misc/10-tfn-cache.t | 187 ++ tests/regression/misc/20-status-engine.t | 123 ++ tests/regression/misc/25-libinjection.t | 110 + tests/regression/misc/30-fuzzyHash.t | 180 ++ tests/regression/misc/40-secRemoteRules.t.in | 43 + .../misc/50-ipmatchfromfile-external.t.in | 71 + .../regression/misc/60-pmfromfile-external.t | 84 + .../misc/60-pmfromfile-external.t.in | 84 + tests/regression/rule/00-basics.t | 91 + tests/regression/rule/00-inheritance.t | 4 + tests/regression/rule/00-script.t | 63 + tests/regression/rule/10-xml.t | 428 ++++ tests/regression/rule/15-json.t | 39 + tests/regression/rule/20-exceptions.t | 176 ++ .../server_root/conf/SoapEnvelope-bad.dtd | 8 + .../server_root/conf/SoapEnvelope-bad.xsd | 126 ++ .../server_root/conf/SoapEnvelope.dtd | 8 + .../server_root/conf/SoapEnvelope.xsd | 126 ++ .../regression/server_root/conf/httpd.conf.in | 28 + tests/regression/server_root/conf/match.lua | 14 + tests/regression/server_root/conf/ssdeep.txt | 4 + tests/regression/server_root/conf/test.lua | 14 + tests/regression/server_root/data/.empty | 0 tests/regression/server_root/data/ip.dir | 0 tests/regression/server_root/htdocs/8k.txt | Bin 0 -> 8192 bytes .../regression/server_root/htdocs/index.html | 1 + tests/regression/server_root/htdocs/test.pdf | Bin 0 -> 15406 bytes tests/regression/server_root/htdocs/test.txt | 1 + tests/regression/server_root/htdocs/test2.txt | 1 + .../regression/server_root/logs/audit/.empty | 0 .../regression/server_root/logs/subdir/.empty | 0 tests/regression/server_root/tmp/.empty | 0 tests/regression/server_root/upload/.empty | 0 tests/regression/target/00-targets.t | 685 +++++++ tests/run-regression-tests.pl.in | 807 ++++++++ 55 files changed, 9028 insertions(+) create mode 100644 tests/.deps/msc_test-msc_test.Po create mode 100644 tests/regression/action/00-disruptive-actions.t create mode 100644 tests/regression/action/00-meta.t create mode 100644 tests/regression/action/00-misc.t create mode 100644 tests/regression/action/00-transformations.t create mode 100644 tests/regression/action/10-append-prepend.t create mode 100644 tests/regression/action/10-ctl.t create mode 100644 tests/regression/action/10-detectiononly-actions.t create mode 100644 tests/regression/action/10-logging.t create mode 100644 tests/regression/config/00-load-modsec.t create mode 100644 tests/regression/config/10-audit-directives.t create mode 100644 tests/regression/config/10-debug-directives.t create mode 100644 tests/regression/config/10-misc-directives.t create mode 100644 tests/regression/config/10-request-directives.t create mode 100644 tests/regression/config/10-response-directives.t create mode 100644 tests/regression/config/20-chroot.t create mode 100644 tests/regression/misc/00-multipart-parser.t create mode 100644 tests/regression/misc/00-phases.t create mode 100644 tests/regression/misc/10-pcre.t create mode 100644 tests/regression/misc/10-tfn-cache.t create mode 100644 tests/regression/misc/20-status-engine.t create mode 100644 tests/regression/misc/25-libinjection.t create mode 100644 tests/regression/misc/30-fuzzyHash.t create mode 100644 tests/regression/misc/40-secRemoteRules.t.in create mode 100644 tests/regression/misc/50-ipmatchfromfile-external.t.in create mode 100644 tests/regression/misc/60-pmfromfile-external.t create mode 100644 tests/regression/misc/60-pmfromfile-external.t.in create mode 100644 tests/regression/rule/00-basics.t create mode 100644 tests/regression/rule/00-inheritance.t create mode 100644 tests/regression/rule/00-script.t create mode 100644 tests/regression/rule/10-xml.t create mode 100644 tests/regression/rule/15-json.t create mode 100644 tests/regression/rule/20-exceptions.t create mode 100644 tests/regression/server_root/conf/SoapEnvelope-bad.dtd create mode 100644 tests/regression/server_root/conf/SoapEnvelope-bad.xsd create mode 100644 tests/regression/server_root/conf/SoapEnvelope.dtd create mode 100644 tests/regression/server_root/conf/SoapEnvelope.xsd create mode 100644 tests/regression/server_root/conf/httpd.conf.in create mode 100644 tests/regression/server_root/conf/match.lua create mode 100644 tests/regression/server_root/conf/ssdeep.txt create mode 100644 tests/regression/server_root/conf/test.lua create mode 100644 tests/regression/server_root/data/.empty create mode 100644 tests/regression/server_root/data/ip.dir create mode 100644 tests/regression/server_root/htdocs/8k.txt create mode 100644 tests/regression/server_root/htdocs/index.html create mode 100644 tests/regression/server_root/htdocs/test.pdf create mode 100644 tests/regression/server_root/htdocs/test.txt create mode 100644 tests/regression/server_root/htdocs/test2.txt create mode 100644 tests/regression/server_root/logs/audit/.empty create mode 100644 tests/regression/server_root/logs/subdir/.empty create mode 100644 tests/regression/server_root/tmp/.empty create mode 100644 tests/regression/server_root/upload/.empty create mode 100644 tests/regression/target/00-targets.t create mode 100755 tests/run-regression-tests.pl.in diff --git a/configure.ac b/configure.ac index d5b1de3..fbac276 100644 --- a/configure.ac +++ b/configure.ac @@ -9,12 +9,51 @@ AM_INIT_AUTOMAKE([-Wall -Werror foreign]) FIND_APXS() FIND_LIBMOD() AX_PROG_APACHE() +AC_PATH_PROGS(PERL, [perl perl5], ) AC_SUBST(APXS) AC_SUBST(V3LIB) AC_SUBST(V3INCLUDE) + +# Some directories +MSC_BASE_DIR=`pwd` +MSC_PKGBASE_DIR="$MSC_BASE_DIR/.." +MSC_TEST_DIR="$MSC_BASE_DIR/tests" +MSC_REGRESSION_DIR="$MSC_TEST_DIR/regression" +MSC_REGRESSION_SERVERROOT_DIR="$MSC_REGRESSION_DIR/server_root" +MSC_REGRESSION_CONF_DIR="$MSC_REGRESSION_SERVERROOT_DIR/conf" +MSC_REGRESSION_LOGS_DIR="$MSC_REGRESSION_SERVERROOT_DIR/logs" +MSC_REGRESSION_DOCROOT_DIR="$MSC_REGRESSION_SERVERROOT_DIR/htdocs" + +AC_SUBST(MSC_BASE_DIR) +AC_SUBST(MSC_PKGBASE_DIR) +AC_SUBST(MSC_TEST_DIR) +AC_SUBST(MSC_REGRESSION_DIR) +AC_SUBST(MSC_REGRESSION_SERVERROOT_DIR) +AC_SUBST(MSC_REGRESSION_CONF_DIR) +AC_SUBST(MSC_REGRESSION_LOGS_DIR) +AC_SUBST(MSC_REGRESSION_DOCROOT_DIR) + + + +APXS_SBINDIR="`$APXS -q SBINDIR`" +APXS_PROGNAME="`$APXS -q PROGNAME`" + +APXS_HTTPD="$APXS_SBINDIR/$APXS_PROGNAME" +AC_SUBST(APXS_HTTPD) +APXS_LIBEXECDIR="`$APXS -q LIBEXECDIR`" +if test "xx$APXS_LIBEXECDIR" = "xx"; then APXS_LIBEXECDIR="`$APXS -q LIBDIR`/modules"; fi +AC_SUBST(APXS_LIBEXECDIR) + + + AC_CONFIG_FILES([\ Makefile \ build/apxs-wrapper \ + tests/regression/server_root/conf/httpd.conf \ + tests/regression/misc/40-secRemoteRules.t \ + tests/regression/misc/60-pmfromfile-external.t \ + tests/regression/misc/50-ipmatchfromfile-external.t \ + tests/run-regression-tests.pl ]) #[chmod +x build/apxs-wrapper]) diff --git a/tests/.deps/msc_test-msc_test.Po b/tests/.deps/msc_test-msc_test.Po new file mode 100644 index 0000000..9ce06a8 --- /dev/null +++ b/tests/.deps/msc_test-msc_test.Po @@ -0,0 +1 @@ +# dummy diff --git a/tests/regression/action/00-disruptive-actions.t b/tests/regression/action/00-disruptive-actions.t new file mode 100644 index 0000000..f682396 --- /dev/null +++ b/tests/regression/action/00-disruptive-actions.t @@ -0,0 +1,593 @@ +### Tests all of the actions in each phase + +# Pass +{ + type => "action", + comment => "pass in phase:1", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:1,pass,id:500033" + SecAction "phase:1,deny,id:500034" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "pass in phase:2", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:2,pass,id:500035" + SecAction "phase:2,deny,id:500036" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "pass in phase:3", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 4 + SecAction "phase:3,pass,id:500037" + SecAction "phase:3,deny,id:500038" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "pass in phase:4", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 4 + SecAction "phase:4,pass,id:500039" + SecAction "phase:4,deny,id:500040" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# Allow +{ + type => "action", + comment => "allow in phase:1", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:1,allow,id:500041" + SecAction "phase:1,deny,id:500042" + ), + match_log => { + error => [ qr/ModSecurity: Access allowed \(phase 1\). Unconditional match in SecAction/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "allow in phase:2", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:2,allow,id:500043" + SecAction "phase:2,deny,id:500044" + ), + match_log => { + error => [ qr/ModSecurity: Access allowed \(phase 2\). Unconditional match in SecAction/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "allow in phase:3", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 4 + SecAction "phase:3,allow,id:500045" + SecAction "phase:3,deny,id:500046" + ), + match_log => { + error => [ qr/ModSecurity: Access allowed \(phase 3\). Unconditional match in SecAction/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "allow in phase:4", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 4 + SecAction "phase:4,allow,id:500047" + SecAction "phase:4,deny,id:500048" + ), + match_log => { + error => [ qr/ModSecurity: Access allowed \(phase 4\). Unconditional match in SecAction/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# Deny +{ + type => "action", + comment => "deny in phase:1", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:1,deny,id:500049" + ), + match_log => { + error => [ qr/Access denied with code 403 \(phase 1\). Unconditional match in SecAction./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "deny in phase:2", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:2,deny,id:500050" + ), + match_log => { + error => [ qr/Access denied with code 403 \(phase 2\). Unconditional match in SecAction./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "deny in phase:3", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 4 + SecAction "phase:3,deny,id:500051" + ), + match_log => { + error => [ qr/Access denied with code 403 \(phase 3\). Unconditional match in SecAction./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "deny in phase:4", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 4 + SecAction "phase:4,deny,id:500052" + ), + match_log => { + error => [ qr/Access denied with code 403 \(phase 4\). Unconditional match in SecAction./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# Drop +{ + type => "action", + comment => "drop in phase:1", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:1,drop,id:500053" + ), + match_log => { + error => [ qr/Access denied with connection close \(phase 1\). Unconditional match in SecAction./, 1 ], + }, + match_response => { + status => qr/^500$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "drop in phase:2", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:2,drop,id:500054" + ), + match_log => { + error => [ qr/Access denied with connection close \(phase 2\). Unconditional match in SecAction./, 1 ], + }, + match_response => { + status => qr/^500$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "drop in phase:3", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 4 + SecAction "phase:3,drop,id:500055" + ), + match_log => { + error => [ qr/Access denied with connection close \(phase 3\). Unconditional match in SecAction./, 1 ], + }, + match_response => { + status => qr/^500$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "drop in phase:4", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 4 + SecAction "phase:4,drop,id:500056" + ), + match_log => { + error => [ qr/Access denied with connection close \(phase 4\). Unconditional match in SecAction./, 1 ], + }, + match_response => { + status => qr/^500$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# Redirect +{ + type => "action", + comment => "redirect in phase:1 (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecRule REQUEST_URI "\@streq /test2.txt" "phase:1,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500001" + ), + match_log => { + error => [ qr/ModSecurity: Access denied with redirection to .* using status 302 \(phase 1\)/, 1 ], + }, + match_response => { + status => qr/^200$/, + content => qr/^TEST$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, +{ + type => "action", + comment => "redirect in phase:2 (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecRule REQUEST_URI "\@streq /test2.txt" "phase:2,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500002" + ), + match_log => { + error => [ qr/ModSecurity: Access denied with redirection to .* using status 302 \(phase 2\)/, 1 ], + }, + match_response => { + status => qr/^200$/, + content => qr/^TEST$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, +{ + type => "action", + comment => "redirect in phase:3 (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 4 + SecRule REQUEST_URI "\@streq /test2.txt" "phase:3,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500003" + ), + match_log => { + error => [ qr/ModSecurity: Access denied with redirection to .* using status 302 \(phase 3\)/, 1 ], + }, + match_response => { + status => qr/^200$/, + content => qr/^TEST$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, +{ + type => "action", + comment => "redirect in phase:4 (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 4 + SecRule REQUEST_URI "\@streq /test2.txt" "phase:4,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500004" + ), + match_log => { + error => [ qr/ModSecurity: Access denied with redirection to .* using status 302 \(phase 4\)/, 1 ], + }, + match_response => { + status => qr/^200$/, + content => qr/^TEST$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, + +# Proxy +{ + type => "action", + comment => "proxy in phase:1 (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecRule REQUEST_URI "\@streq /test2.txt" "phase:1,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500005" + ), + match_log => { + error => { + apache => [qr/ModSecurity: Access denied using proxy to \(phase 1\)/, 1], + nginx => [qr/ModSecurity: Access denied with code 500 \(phase 1\) \(Configuration Error: Proxy action to .* requested but proxy is only available in Apache version\)./, 1], + }, + }, + match_response => { + status => { + apache => qr/^200$/, + nginx => qr/^500$/, + }, + content => { + apache => qr/^TEST$/, + nginx => qr/^*$/, + }, + }, + + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, +{ + type => "action", + comment => "nocanon proxy in phase:1 (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecRule REQUEST_URI "\@streq /test2.txt" "phase:1,proxy:'[nocanon]http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500005" + ), + match_log => { + error => { + apache => [qr/ModSecurity: Access denied using proxy to \(phase 1\)/, 1], + nginx => [qr/ModSecurity: Access denied with code 500 \(phase 1\) \(Configuration Error: Proxy action to .* requested but proxy is only available in Apache version\)./, 1], + }, + }, + match_response => { + status => { + apache => qr/^200$/, + nginx => qr/^500$/, + }, + content => { + apache => qr/^TEST$/, + nginx => qr/^*$/, + }, + }, + + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, +{ + type => "action", + comment => "proxy in phase:2 (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecRule REQUEST_URI "\@streq /test2.txt" "phase:2,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500006" + ), + match_log => { + error => { + apache => [qr/ModSecurity: Access denied using proxy to \(phase 2\)/, 1], + nginx => [qr/ModSecurity: Access denied with code 500 \(phase 2\) \(Configuration Error: Proxy action to .* requested but proxy is only available in Apache version\)./, 1], + }, + }, + match_response => { + status => { + apache => qr/^200$/, + nginx => qr/^500$/, + }, + content => { + apache => qr/^TEST$/, + nginx => qr/^*$/, + }, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, +{ + type => "action", + comment => "proxy in phase:3 (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 4 + SecRule REQUEST_URI "\@streq /test2.txt" "phase:3,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500007" + ), + match_log => { + error => { + apache => [qr/ModSecurity: Access denied with code 500 \(phase 3\) \(Configuration Error: Proxy action requested but it does not work in output phases\)./, 1], + nginx => [qr/ModSecurity: Access denied with code 500 \(phase 3\) \(Configuration Error: Proxy action to .* requested but proxy is only available in Apache version\)./, 1], + } + }, + match_response => { + status => { + apache => qr/^500$/, + nginx => qr/^500$/, + }, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, +{ + type => "action", + comment => "proxy in phase:4 (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 4 + SecRule REQUEST_URI "\@streq /test2.txt" "phase:4,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500008" + ), + match_log => { + error => { + apache => [qr/ModSecurity: Access denied with code 500 \(phase 4\) \(Configuration Error: Proxy action requested but it does not work in output phases\)./, 1], + nginx => [qr/ModSecurity: Access denied with code 500 \(phase 4\) \(Configuration Error: Proxy action to .* requested but proxy is only available in Apache version\)./, 1], + } + }, + match_response => { + status => { + apache => qr/^500$/, + nginx => qr/^500$/, + }, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, diff --git a/tests/regression/action/00-meta.t b/tests/regression/action/00-meta.t new file mode 100644 index 0000000..9e7b92a --- /dev/null +++ b/tests/regression/action/00-meta.t @@ -0,0 +1,8 @@ +### Test meta actions + +# TODO: id +# TODO: logdata +# TODO: msg +# TODO: rev +# TODO: severity +# TODO: tag diff --git a/tests/regression/action/00-misc.t b/tests/regression/action/00-misc.t new file mode 100644 index 0000000..1629d69 --- /dev/null +++ b/tests/regression/action/00-misc.t @@ -0,0 +1,22 @@ +### Test misc actions + +# TODO: block +# TODO: capture +# TODO: chain +# TODO: deprecatevar +# TODO: exec +# TODO: expirevar +# TODO: initcol +# TODO: multiMatch +# TODO: pause +# TODO: sanitiseArg +# TODO: sanitiseMatched +# TODO: sanitiseRequestHeader +# TODO: sanitiseResponseHeader +# TODO: setuid +# TODO: setsid +# TODO: setenv +# TODO: setvar +# TODO: skip +# TODO: skipAfter +# TODO: xmlns diff --git a/tests/regression/action/00-transformations.t b/tests/regression/action/00-transformations.t new file mode 100644 index 0000000..e8f7bb2 --- /dev/null +++ b/tests/regression/action/00-transformations.t @@ -0,0 +1,8 @@ +### Transformation tests + +# NOTE: individual tests done in unit tests + +# TODO: t:none to override default +# TODO: t:none inline +# TODO: combined +# TODO: caching diff --git a/tests/regression/action/10-append-prepend.t b/tests/regression/action/10-append-prepend.t new file mode 100644 index 0000000..4e89f62 --- /dev/null +++ b/tests/regression/action/10-append-prepend.t @@ -0,0 +1,49 @@ +# TODO: Need more tests here + +### append +{ + type => "action", + comment => "append content", + conf => qq( + SecRuleEngine On + SecContentInjection On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAction "phase:1,setvar:tx.test=test,id:500002" + SecAction "phase:2,append:'APPEND: \%{tx.test}',id:500003" + ), + match_log => { + debug => [ "Added content to bottom: APPEND: test", 1 ], + }, + match_response => { + status => qr/^200$/, + content => qr/APPEND: test$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +### prepend +{ + type => "action", + comment => "prepend content", + conf => qq( + SecRuleEngine On + SecContentInjection On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAction "phase:1,setvar:tx.test=test,id:500004" + SecAction "phase:2,prepend:'PREPEND: \%{tx.test}',id:500005" + ), + match_log => { + debug => [ "Added content to top: PREPEND: test", 1 ], + }, + match_response => { + status => qr/^200$/, + content => qr/^PREPEND: test/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, diff --git a/tests/regression/action/10-ctl.t b/tests/regression/action/10-ctl.t new file mode 100644 index 0000000..a4940a3 --- /dev/null +++ b/tests/regression/action/10-ctl.t @@ -0,0 +1,56 @@ +### ctl + +### ruleRemoveById +{ + type => "action", + comment => "ruleRemoveById existing rule across phases", + conf => qq( + SecRuleEngine On + SecAction "phase:2,id:666,deny" + SecAction "phase:1,pass,ctl:ruleRemoveById=666,id:500030" + ), + match_log => { + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "ruleRemoveById future rule across phases", + conf => qq( + SecRuleEngine On + SecAction "phase:1,pass,ctl:ruleRemoveById=666,id:500031" + SecAction "phase:2,id:666,deny" + ), + match_log => { + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "ruleRemoveById future rule same phase", + conf => qq( + SecRuleEngine On + SecAction "phase:1,pass,ctl:ruleRemoveById=666,id:500032" + SecAction "phase:1,id:666,deny" + ), + match_log => { + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + + diff --git a/tests/regression/action/10-detectiononly-actions.t b/tests/regression/action/10-detectiononly-actions.t new file mode 100644 index 0000000..42a8dfa --- /dev/null +++ b/tests/regression/action/10-detectiononly-actions.t @@ -0,0 +1,545 @@ +### Tests all of the actions in each phase in detection only mode + +# Pass +{ + type => "action", + comment => "pass in phase:1", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAction "phase:1,pass,msg:'PASSED',id:500057" + SecAction "phase:1,deny,msg:'DENIED',id:500058" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*PASSED/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "pass in phase:2", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:2,pass,msg:'PASSED',id:500059" + SecAction "phase:2,deny,msg:'DENIED',id:500060" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*PASSED/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "pass in phase:3", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 4 + SecAction "phase:3,pass,msg:'PASSED',id:500061" + SecAction "phase:3,deny,msg:'DENIED',id:500062" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*PASSED/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "pass in phase:4", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 4 + SecAction "phase:4,pass,msg:'PASSED',id:500063" + SecAction "phase:4,deny,msg:'DENIED',id:500064" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*PASSED/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# Allow +{ + type => "action", + comment => "allow in phase:1", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:1,allow,msg:'ALLOWED',id:500065" + SecAction "phase:1,deny,msg:'DENIED',id:500066" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*ALLOWED/, 1 ], + -error => [ qr/Access allowed/, 1 ], +# TODO: Allow should probably stop rule execution +# -error => [ qr/DENIED/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "allow in phase:2", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:2,allow,msg:'ALLOWED',id:500067" + SecAction "phase:2,deny,msg:'DENIED',id:500068" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*ALLOWED/, 1 ], + -error => [ qr/Access allowed/, 1 ], +# TODO: Allow should probably stop rule execution +# -error => [ qr/DENIED/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "allow in phase:3", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:3,allow,msg:'ALLOWED',id:500069" + SecAction "phase:3,deny,msg:'DENIED',id:500070" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*ALLOWED/, 1 ], + -error => [ qr/Access allowed/, 1 ], +# TODO: Allow should probably stop rule execution +# -error => [ qr/DENIED/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "allow in phase:4", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:4,allow,msg:'ALLOWED',id:500071" + SecAction "phase:4,deny,msg:'DENIED',id:500072" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*ALLOWED/, 1 ], + -error => [ qr/Access allowed/, 1 ], +# TODO: Allow should probably stop rule execution +# -error => [ qr/DENIED/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# Deny +{ + type => "action", + comment => "deny in phase:1", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:1,deny,msg:'DENIED',id:500073" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DENIED/, 1 ], + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "deny in phase:2", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:2,deny,msg:'DENIED',id:500074" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DENIED/, 1 ], + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "deny in phase:3", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:3,deny,msg:'DENIED',id:500075" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DENIED/, 1 ], + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "deny in phase:4", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:4,deny,msg:'DENIED',id:500076" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DENIED/, 1 ], + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# Drop +{ + type => "action", + comment => "drop in phase:1", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:1,drop,msg:'DROPPED',id:500077" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DROPPED/, 1 ], + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "drop in phase:2", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:2,drop,msg:'DROPPED',id:500078" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DROPPED/, 1 ], + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "drop in phase:3", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:3,drop,msg:'DROPPED',id:500079" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DROPPED/, 1 ], + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "drop in phase:4", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecAction "phase:4,drop,msg:'DROPPED',id:500080" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DROPPED/, 1 ], + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# Redirect +{ + type => "action", + comment => "redirect in phase:1 (get)", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecRule REQUEST_URI "\@streq /test2.txt" "phase:1,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'REDIRECTED',id:500009" + ), + match_log => { + error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*REDIRECTED/, 1 ], + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + content => qr/^TEST 2$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, +{ + type => "action", + comment => "redirect in phase:2 (get)", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecRule REQUEST_URI "\@streq /test2.txt" "phase:2,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'REDIRECTED',id:500010" + ), + match_log => { + error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*REDIRECTED/, 1 ], + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + content => qr/^TEST 2$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, +{ + type => "action", + comment => "redirect in phase:3 (get)", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecRule REQUEST_URI "\@streq /test2.txt" "phase:3,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'REDIRECTED',id:500011" + ), + match_log => { + error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*REDIRECTED/, 1 ], + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + content => qr/^TEST 2$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, +{ + type => "action", + comment => "redirect in phase:4 (get)", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecRule REQUEST_URI "\@streq /test2.txt" "phase:4,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'REDIRECTED',id:500012" + ), + match_log => { + error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*REDIRECTED/, 1 ], + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + content => qr/^TEST 2$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, + +# Proxy +{ + type => "action", + comment => "proxy in phase:1 (get)", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecRule REQUEST_URI "\@streq /test2.txt" "phase:1,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'PROXIED',id:500013" + ), + match_log => { + error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*PROXIED/, 1 ], + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + content => qr/^TEST 2$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, +{ + type => "action", + comment => "proxy in phase:2 (get)", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecRule REQUEST_URI "\@streq /test2.txt" "phase:2,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'PROXIED',id:500014" + ), + match_log => { + error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*PROXIED/, 1 ], + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + content => qr/^TEST 2$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, +{ + type => "action", + comment => "proxy in phase:3 (get)", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 4 + SecRule REQUEST_URI "\@streq /test2.txt" "phase:3,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'PROXIED',id:500015" + ), + match_log => { + error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*PROXIED/, 1 ], + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, +{ + type => "action", + comment => "proxy in phase:4 (get)", + conf => qq( + SecRuleEngine DetectionOnly + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 4 + SecRule REQUEST_URI "\@streq /test2.txt" "phase:4,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'PROXIED',id:500016" + ), + match_log => { + error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*PROXIED/, 1 ], + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", + ), +}, diff --git a/tests/regression/action/10-logging.t b/tests/regression/action/10-logging.t new file mode 100644 index 0000000..b7e8061 --- /dev/null +++ b/tests/regression/action/10-logging.t @@ -0,0 +1,564 @@ +### Logging tests + +# log/nolog (pass) +{ + type => "action", + comment => "log (pass)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,pass,log,id:500006" + ), + match_log => { + error => [ qr/ModSecurity: Warning\. Unconditional match in SecAction\./, 1 ], + audit => [ qr/Message: Warning\. Unconditional match in SecAction\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "nolog (pass)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,pass,nolog,id:500007" + ), + match_log => { + -error => [ qr/500007/, 1 ], + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# log/nolog (deny) +{ + type => "action", + comment => "log (deny)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,deny,status:403,log,id:500008" + ), + match_log => { + error => [ qr/ModSecurity: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], + audit => [ qr/Message: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "nolog (deny)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,deny,status:403,nolog,id:500009" + ), + match_log => { + -error => [ qr/500009/, 1 ], + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# auditlog/noauditlog (pass) +{ + type => "action", + comment => "auditlog (pass)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,pass,auditlog,id:500010" + ), + match_log => { + error => [ qr/ModSecurity: Warning\. Unconditional match in SecAction\./, 1 ], + audit => [ qr/Message: Warning\. Unconditional match in SecAction\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "noauditlog (pass)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,pass,noauditlog,id:500011" + ), + match_log => { + error => [ qr/ModSecurity: Warning\. Unconditional match in SecAction\./, 1 ], + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# auditlog/noauditlog (deny) +{ + type => "action", + comment => "auditlog (deny)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,deny,status:403,auditlog,id:500012" + ), + match_log => { + error => [ qr/ModSecurity: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], + audit => [ qr/Message: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "noauditlog (deny)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,deny,status:403,noauditlog,id:500013" + ), + match_log => { + error => [ qr/ModSecurity: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# All log/nolog auditlog/noauditlog combos (pass) +{ + type => "action", + comment => "log,auditlog (pass)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,pass,log,auditlog,id:500014" + ), + match_log => { + error => [ qr/ModSecurity: Warning\. Unconditional match in SecAction\./, 1 ], + audit => [ qr/Message: Warning\. Unconditional match in SecAction\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "log,noauditlog (pass)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,pass,log,noauditlog,id:500015" + ), + match_log => { + error => [ qr/ModSecurity: Warning\. Unconditional match in SecAction\./, 1 ], + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "nolog,auditlog (pass)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,pass,nolog,auditlog,id:500016" + ), + match_log => { + audit => [ qr/-H--\s+Message: .*Stopwatch: /s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "nolog,noauditlog (pass)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,pass,nolog,noauditlog,id:500017" + ), + match_log => { + -error => [ qr/500017/, 1 ], + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "auditlog,log (pass)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,pass,auditlog,log,id:500018" + ), + match_log => { + error => [ qr/ModSecurity: Warning\. Unconditional match in SecAction\./, 1 ], + audit => [ qr/Message: Warning\. Unconditional match in SecAction\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "auditlog,nolog (pass)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,pass,auditlog,nolog,id:500019" + ), + match_log => { + -error => [ qr/500019/, 1 ], + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "noauditlog,log (pass)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,pass,noauditlog,log,id:500020" + ), + match_log => { + error => [ qr/ModSecurity: Warning\. Unconditional match in SecAction\./, 1 ], + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "noauditlog,nolog (pass)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,pass,noauditlog,nolog,id:500021" + ), + match_log => { + -error => [ qr/500021/, 1 ], + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# All log/nolog auditlog/noauditlog combos (deny) +{ + type => "action", + comment => "log,auditlog (deny)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,deny,status:403,log,auditlog,id:500022" + ), + match_log => { + error => [ qr/ModSecurity: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], + audit => [ qr/Message: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "log,noauditlog (deny)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,deny,status:403,log,noauditlog,id:500023" + ), + match_log => { + error => [ qr/ModSecurity: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "nolog,auditlog (deny)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,deny,status:403,nolog,auditlog,id:500024" + ), + match_log => { + audit => [ qr/-H--\s+Message: .*Stopwatch: /s, 1 ], + }, + match_response => { + -error => [ qr/ModSecurity: /, 1 ], + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "nolog,noauditlog (deny)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,deny,status:403,nolog,noauditlog,id:500025" + ), + match_log => { + -error => [ qr/500025/, 1 ], + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "auditlog,log (deny)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,deny,status:403,auditlog,log,id:500026" + ), + match_log => { + error => [ qr/ModSecurity: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], + audit => [ qr/Message: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "auditlog,nolog (deny)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,deny,status:403,auditlog,nolog,id:500027" + ), + match_log => { + -error => [ qr/500027/, 1 ], + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "noauditlog,log (deny)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,deny,status:403,noauditlog,log,id:500028" + ), + match_log => { + error => [ qr/ModSecurity: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "action", + comment => "noauditlog,nolog (deny)", + conf => qq( + SecRuleEngine On + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecAuditLogRelevantStatus xxx + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecAction "phase:1,deny,status:403,noauditlog,nolog,id:500029" + ), + match_log => { + -error => [ qr/500029/, 1 ], + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, diff --git a/tests/regression/config/00-load-modsec.t b/tests/regression/config/00-load-modsec.t new file mode 100644 index 0000000..222120d --- /dev/null +++ b/tests/regression/config/00-load-modsec.t @@ -0,0 +1,29 @@ +{ + type => "config", + comment => "module loaded", + match_log => { + error => { + apache => [ qr/ModSecurity for Apache.* configured\./, 10 ], + nginx => [ qr/ModSecurity for nginx.* configured\./, 10 ], + }, + }, +}, +{ + type => "config", + comment => "minimal config", + conf => sub { + # Open the minimal conf file, substituting the + # relative log paths with full paths. + open(C, "<$ENV{DIST_ROOT}/modsecurity.conf-minimal") or die "$!\n"; + (my $conf = join('', )) =~ s#Log logs/#Log $ENV{TEST_SERVER_ROOT}/logs/#g; + close C; + + return $conf; + }, + match_log => { + error => { + apache => [ qr/ModSecurity for Apache.* configured\./, 10 ], + nginx => [ qr/ModSecurity for nginx.* configured\./, 10 ], + }, + }, +}, diff --git a/tests/regression/config/10-audit-directives.t b/tests/regression/config/10-audit-directives.t new file mode 100644 index 0000000..7d1f458 --- /dev/null +++ b/tests/regression/config/10-audit-directives.t @@ -0,0 +1,269 @@ +### SecAudit* directive tests + +# SecAuditEngine +{ + type => "config", + comment => "SecAuditEngine On", + conf => qq( + SecAuditEngine On + SecAuditLog $ENV{AUDIT_LOG} + ), + match_log => { + audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "config", + comment => "SecAuditEngine Off", + conf => qq( + SecAuditEngine Off + SecAuditLog $ENV{AUDIT_LOG} + ), + match_log => { + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "config", + comment => "SecAuditEngine RelevantOnly (pos)", + conf => qq( + SecRuleEngine On + SecAuditEngine RelevantOnly + SecAuditLog $ENV{AUDIT_LOG} + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecResponseBodyAccess On + SecDefaultAction "phase:2,log,auditlog,pass" + SecRule REQUEST_URI "." "phase:4,deny,id:500251" + ), + match_log => { + audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "config", + comment => "SecAuditEngine RelevantOnly (neg)", + conf => qq( + SecAuditEngine RelevantOnly + SecAuditLog $ENV{AUDIT_LOG} + SecResponseBodyAccess On + SecDefaultAction "phase:2,log,auditlog,pass" + ), + match_log => { + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# SecAuditLogType & SecAuditLogStorageDir +{ + type => "config", + comment => "SecAuditLogType Serial", + conf => qq( + SecAuditEngine On + SecAuditLog $ENV{AUDIT_LOG} + SecAuditLogType Serial + ), + match_log => { + audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^404$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/bogus", + ), +}, +{ + type => "config", + comment => "SecAuditLogType Concurrent", + conf => qq( + SecAuditEngine On + SecAuditLog $ENV{AUDIT_LOG} + SecAuditLogType Concurrent + SecAuditLogStorageDir "$ENV{LOGS_DIR}/audit" + ), + test => sub { + ### Perl code to parse the audit log entry and verify + ### that the concurrent audit log exists and contains + ### the correct data. + ### + ### TODO: Need some API for this :) + ### + ### FIXME: Just workable with apache, the timing to load auditlog from nginx + ### is not correct, so the test is failing even when it should pass. + ### Disabling it for now until we figure out a way to handle that. + + # Parse log + #my $alogre = qr/^(?:\S+)\ (?:\S+)\ (?:\S+)\ (?:\S+)\ \[(?:[^:]+):(?:\d+:\d+:\d+)\ (?:[^\]]+)\]\ \"(?:.*)\"\ (?:\d+)\ (?:\S+)\ \"(?:.*)\"\ \"(?:.*)\"\ (\S+)\ \"(?:.*)\"\ (\S+)\ (?:\d+)\ (?:\d+)\ (?:\S+)(?:.*)$/m; + #my $alog = match_log("audit", $alogre, 1); + #chomp $alog; + #dbg("Alog: $alog\n"); + #my @log = ($alog =~ m/$alogre/); + #my($id, $fn) = ($log[0], $log[1]); + #if (!$id or !$fn) { + #dbg("LOG ENTRY: $alog"); + #die "Failed to parse audit log: $ENV{AUDIT_LOG}\n"; + #} + + # Verify concurrent log exists + #my $alogdatafn = "$ENV{LOGS_DIR}/audit$fn"; + #if (! -e "$alogdatafn") { + #die "Audit log does not exist: $alogdatafn\n"; + #} + + # Verify concurrent log contents + #if (defined match_file($alogdatafn, qr/^--[^-]+-A--.*$id.*-Z--$/s)) { + #return 0; + #} + + # Error + #dbg("LOGDATA: \"$FILE{$alogdatafn}{buf}\""); + #die "Audit log data did not match.\n"; + return 0; + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# SecAuditLogRelevantStatus +{ + type => "config", + comment => "SecAuditLogRelevantStatus (pos)", + conf => qq( + SecAuditEngine RelevantOnly + SecAuditLog $ENV{AUDIT_LOG} + SecAuditLogRelevantStatus "^4" + ), + match_log => { + audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^404$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/bogus", + ), +}, +{ + type => "config", + comment => "SecAuditLogRelevantStatus (neg)", + conf => qq( + SecAuditEngine RelevantOnly + SecAuditLog $ENV{AUDIT_LOG} + SecAuditLogRelevantStatus "^4" + ), + match_log => { + -audit => [ qr/./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# SecAuditLogParts +{ + type => "config", + comment => "SecAuditLogParts (minimal)", + conf => qq( + SecAuditEngine On + SecAuditLog $ENV{AUDIT_LOG} + SecRequestBodyAccess On + SecResponseBodyAccess On + SecAuditLogParts "AZ" + ), + match_log => { + audit => [ qr/-A--.*-Z--/s, 1 ], + -audit => [ qr/-[B-Y]--/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1r&=2", + ), +}, +{ + type => "config", + comment => "SecAuditLogParts (default)", + conf => qq( + SecAuditEngine On + SecAuditLog $ENV{AUDIT_LOG} + SecRequestBodyAccess On + SecResponseBodyAccess On + ), + match_log => { + audit => [ qr/-A--.*-B--.*-F--.*-H--.*-Z--/s, 1 ], + -audit => [ qr/-[DEGIJK]--/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1r&=2", + ), +}, +{ + type => "config", + comment => "SecAuditLogParts (all)", + conf => qq( + SecRuleEngine On + SecAuditEngine On + SecAuditLog $ENV{AUDIT_LOG} + SecRequestBodyAccess On + SecResponseBodyAccess On + SecAuditLogParts "ABCDEFGHIJKZ" + SecAction "phase:4,log,auditlog,allow,id:500086" + ), + match_log => { + audit => [ qr/-A--.*-B--.*-C--.*-F--.*-E--.*-H--.*-K--.*-Z--/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1r&=2", + ), +}, diff --git a/tests/regression/config/10-debug-directives.t b/tests/regression/config/10-debug-directives.t new file mode 100644 index 0000000..bf78801 --- /dev/null +++ b/tests/regression/config/10-debug-directives.t @@ -0,0 +1,278 @@ +### SecDebug* directive tests +{ + type => "config", + comment => "SecDebugLog (pos)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + ), + match_log => { + debug => [ qr/./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "config", + comment => "SecDebugLog (neg)", + conf => qq( + SecRuleEngine On + ), + match_log => { + -debug => [ qr/./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "config", + comment => "SecDebugLogLevel 0", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 0 + SecRule REQUEST_URI "." "phase:1,deny,id:500241" + ), + match_log => { + -debug => [ qr/./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "config", + comment => "SecDebugLogLevel 1", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 1 + SecRuleScript "test.lua" "phase:1" + SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500242" + ), + match_log => { + debug => [ qr/\]\[[1]\] /, 1 ], + -debug => [ qr/\]\[[2-9]\] /, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1&b=2", + ), +}, +{ + type => "config", + comment => "SecDebugLogLevel 2", + conf => qq( + SecRuleEngine DetectionOnly + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 2 + SecRuleScript "test.lua" "phase:1" + SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500243" + ), + match_log => { + debug => [ qr/\]\[2\] /, 1 ], + -debug => [ qr/\]\[[3-9]\] /, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1&b=2", + ), +}, +{ + type => "config", + comment => "SecDebugLogLevel 3", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 3 + SecRuleScript "test.lua" "phase:1" + SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500244" + ), + match_log => { + debug => [ qr/\]\[3\] /, 1 ], + -debug => [ qr/\]\[[4-9]\] /, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1&b=2", + ), +}, +{ + type => "config", + comment => "SecDebugLogLevel 4", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 4 + SecRuleScript "test.lua" "phase:1" + SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500245" + ), + match_log => { + debug => [ qr/\]\[4\] /, 1 ], + -debug => [ qr/\]\[[5-9]\] /, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1&b=2", + ), +}, +{ + type => "config", + comment => "SecDebugLogLevel 5", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 5 + SecRuleScript "test.lua" "phase:1" + SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500246" + ), + match_log => { + debug => [ qr/\]\[5\] /, 1 ], + -debug => [ qr/\]\[[6-9]\] /, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1&b=2", + ), +}, +{ + type => "config", + comment => "SecDebugLogLevel 6", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 6 + SecRuleScript "test.lua" "phase:1" + SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500247" + ), + match_log => { + debug => [ qr/\]\[6\] /, 1 ], + -debug => [ qr/\]\[[7-9]\] /, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1&b=2", + ), +}, +{ + type => "config", + comment => "SecDebugLogLevel 7", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 7 + SecRuleScript "test.lua" "phase:1" + SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500248" + ), + match_log => { + debug => [ qr/\]\[7\] /, 1 ], + -debug => [ qr/\]\[[8-9]\] /, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1&b=2", + ), +}, +{ + type => "config", + comment => "SecDebugLogLevel 8", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 8 + SecRuleScript "test.lua" "phase:1" + SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500249" + ), + match_log => { + debug => [ qr/\]\[8\] /, 1 ], + -debug => [ qr/\]\[9\] /, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1&b=2", + ), +}, +{ + type => "config", + comment => "SecDebugLogLevel 9", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRuleScript "test.lua" "phase:1" + SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500250" + ), + match_log => { + debug => [ qr/\]\[9\] /, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1&b=2", + ), +}, diff --git a/tests/regression/config/10-misc-directives.t b/tests/regression/config/10-misc-directives.t new file mode 100644 index 0000000..62a5e8e --- /dev/null +++ b/tests/regression/config/10-misc-directives.t @@ -0,0 +1,145 @@ +### Misc directive tests + +### TODO: +# SecTmpDir +# SecUploadKeepFiles +# SecChrootDir +# SecGuardianLog + +# SecDefaultAction +{ + type => "config", + comment => "SecDefaultAction", + conf => qq( + SecRuleEngine on + SecDefaultAction "phase:1,deny,status:500" + SecRule REQUEST_URI "test.txt" "id:500241" + ), + match_log => { + error => [ qr/ModSecurity: Access denied with code 500 \(phase 1\)/, 1 ], + }, + match_response => { + status => qr/^500$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# SecServerSignature +{ + type => "config", + comment => "SecServerSignature On", + conf => qq( + SecServerSignature "NewServerSignature" + ), + match_response => { + status => qr/^200$/, + raw => qr/^Server: +NewServerSignature$/m, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# SecDataDir +{ + type => "config", + comment => "SecDataDir", + conf => qq( + SecRuleEngine On + SecDataDir "$ENV{DATA_DIR}" + SecAction initcol:ip=%{REMOTE_ADDR},setvar:ip.dummy=1,pass,id:500085 + ), + match_log => { + error => [ qr/ModSecurity: Warning. Unconditional match in SecAction\./, 1 ], + }, + match_file => { + "$ENV{DATA_DIR}/ip.pag" => qr/\x00\x06dummy\x00\x00\x021\x00/, + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# SecTmpDir/SecUploadDir/SecUploadKeepFiles +{ + type => "config", + comment => "SecTmpDir/SecUploadDir/SecUploadKeepFiles", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 4 + SecTmpDir "$ENV{TEMP_DIR}" + SecUploadKeepFiles On + SecUploadDir "$ENV{UPLOAD_DIR}" + ), + test => sub { + # Get the filename and make sure the file exists + my $fn = match_log(debug => qr/Moved file from .* to ".*"\./, 5); + die "Failed to determine uploaded filename\n" unless (defined $fn); + + $fn =~ s/Moved file from .* to "(.*)"\..*/$1/; + die "File does not exist: $fn\n" unless (-e $fn); + + # Check the contents of the file + return 0 if (match_file($fn, qr/^TESTFILE$/m)); + + msg("Failed to match contents of uploaded file: $fn"); + return 1; + }, + match_log => { + debug => [ qr/Created temporary file.*$ENV{TEMP_DIR}/, 1 ], + -debug => [ qr/Failed to /, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------19813181771830765643996187206", + ], + q(-----------------------------19813181771830765643996187206 +Content-Disposition: form-data; name="upload-file"; filename="test" +Content-Type: application/octet-stream + +TESTFILE +-----------------------------19813181771830765643996187206 +Content-Disposition: form-data; name="file" + +Upload File +-----------------------------19813181771830765643996187206--), + ), +}, + +# SecWebAppId +{ + type => "config", + comment => "SecWebAppId", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 4 + SecAuditLog "$ENV{AUDIT_LOG}" + SecAuditEngine RelevantOnly + SecWebAppId "app-1" + SecAction "pass,log,auditlog,id:1" + ), + match_log => { + error => [ qr/Warning\. Unconditional match in SecAction\./, 1 ], + debug => [ qr/Warning\. Unconditional match in SecAction\./, 1 ], + audit => [ qr/^WebApp-Info: "app-1"/m, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, diff --git a/tests/regression/config/10-request-directives.t b/tests/regression/config/10-request-directives.t new file mode 100644 index 0000000..42094e1 --- /dev/null +++ b/tests/regression/config/10-request-directives.t @@ -0,0 +1,705 @@ +### Tests for directives altering how a request is handled + +# SecArgumentSeparator +{ + type => "config", + comment => "SecArgumentSeparator (get-pos)", + conf => q( + SecRuleEngine On + SecArgumentSeparator ";" + SecRule ARGS:a "@streq 1" "phase:1,deny,chain,id:500215" + SecRule ARGS:b "@streq 2" "" + ), + match_log => { + error => [ qr/Access denied with code 403 \(phase 1\)\. String match "2" at ARGS:b\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?a=1;b=2", + ), +}, +{ + type => "config", + comment => "SecArgumentSeparator (get-neg)", + conf => q( + SecRuleEngine On + SecRule ARGS:a "@streq 1" "phase:1,deny,chain,id:500217" + SecRule ARGS:b "@streq 2" "" + ), + match_log => { + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?a=1;b=2", + ), +}, +{ + type => "config", + comment => "SecArgumentSeparator (post-pos)", + conf => q( + SecRuleEngine On + SecRequestBodyAccess On + SecArgumentSeparator ";" + SecRule ARGS:a "@streq 1" "phase:2,deny,chain,id:500219" + SecRule ARGS:b "@streq 2" "" + ), + match_log => { + error => [ qr/Access denied with code 403 \(phase 2\)\. String match "2" at ARGS:b\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1;b=2", + ), +}, +{ + type => "config", + comment => "SecArgumentSeparator (post-neg)", + conf => q( + SecRuleEngine On + SecRequestBodyAccess On + SecRule ARGS:a "@streq 1" "phase:2,deny,id:500221" + SecRule ARGS:b "@streq 2" "phase:2,deny,id:500222" + ), + match_log => { + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1;b=2", + ), +}, + +# SecRequestBodyAccess +{ + type => "config", + comment => "SecRequestBodyAccess (pos)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRule ARGS:a "\@streq 1" "phase:2,deny,chain,id:500223" + SecRule ARGS:b "\@streq 2" "" + ), + match_log => { + error => [ qr/Access denied with code 403 \(phase 2\)\. String match "2" at ARGS:b\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1&b=2", + ), +}, +{ + type => "config", + comment => "SecRequestBodyAccess (neg)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess Off + SecRule ARGS:a "\@streq 1" "phase:2,deny,id:500225" + SecRule ARGS:b "\@streq 2" "phase:2,deny,id:500226" + ), + match_log => { + -error => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1&b=2", + ), +}, + +# SecRequestBodyLimit +{ + type => "config", + comment => "SecRequestBodyLimit (equal)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimit 7 + ), + match_log => { + -error => [ qr/Request body is larger than the configured limit/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1&b=2", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimit (greater)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimit 5 + ), + match_log => { + error => [ qr/Request body .*is larger than the configured limit \(5\)\./, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1&b=2", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimit (equal - chunked)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimit 276 + ), + match_log => { + -error => [ qr/Request body is larger than the configured limit/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + 2 + -----------------------------69343412719991675451336310646-- + ) + ), + 1024 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimit (greater - chunked)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimit 256 + ), + match_log => { + error => [ qr/Request body .*is larger than the configured limit \(256\)\./, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + 2 + -----------------------------69343412719991675451336310646-- + ) + ), + 1024 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimit (ctl:ruleEngine=off)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimit 5 + + SecAction "phase:1,pass,nolog,ctl:ruleEngine=off,id:500081" + SecRule REQUEST_BODY "." "phase:2,deny,id:500227" + ), + match_log => { + -error => [ qr/Request body .*is larger than the configured limit/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1&b=2", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimit (ctl:requestBodyAccess=off)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimit 5 + + SecAction "phase:1,pass,nolog,ctl:requestBodyAccess=off,id:500082" + SecRule REQUEST_BODY "." "phase:2,deny,id:500228" + ), + match_log => { + -error => [ qr/Request body .*is larger than the configured limit/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=1&b=2", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimit (ctl:ruleEngine=off - chunked)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimit 256 + + SecAction "phase:1,pass,nolog,ctl:ruleEngine=off,id:500083" + SecRule REQUEST_BODY "." "phase:2,deny,id:500229" + ), + match_log => { + -error => [ qr/Request body .*is larger than the configured limit/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + 2 + -----------------------------69343412719991675451336310646-- + ) + ), + 1024 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimit (ctl:requestBodyAccess=off - chunked)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimit 256 + + SecAction "phase:1,pass,nolog,ctl:requestBodyAccess=off,id:500084" + SecRule REQUEST_BODY "." "phase:2,deny,id:500230" + ), + match_log => { + -error => [ qr/Request body .*is larger than the configured limit \(256\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + 2 + -----------------------------69343412719991675451336310646-- + ) + ), + 1024 + ), +}, + +# SecRequestBodyInMemoryLimit +{ + type => "config", + comment => "SecRequestBodyInMemoryLimit (equal)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimit 1000 + SecRequestBodyInMemoryLimit 276 + ), + match_log => { + -debug => [ qr/Input filter: Request too large to store in memory, switching to disk\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + 2 + -----------------------------69343412719991675451336310646-- + ) + ), + 1024 + ), +}, +{ + type => "config", + comment => "SecRequestBodyInMemoryLimit (greater)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimit 1000 + SecRequestBodyInMemoryLimit 16 + ), + match_log => { + debug => [ qr/Input filter: Request too large to store in memory, switching to disk\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + 2 + -----------------------------69343412719991675451336310646-- + ) + ), + 1024 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (multipart/greater - chunked)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyLimit 20 + ), + match_log => { + debug => [ qr/Request body is larger than the configured limit \(20\).. Deny with code \(413\)/, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + 2 + -----------------------------69343412719991675451336310646-- + ) + ), + 1024 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (plain/greater)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyLimit 131072 + ), + match_log => { + -debug => [ qr/Request body is larger than the configured limit \(131072\).. Deny with code \(413\)/, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + ], + normalize_raw_request_data( + q( + { + ) . "'abcdefghijlmnopq'='abcdefghijlmnopqrstuvxz',\\n" x 99000 . q( + }, + ), + ), + ), +}, + + +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/greater - chunked)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 131072 + ), + match_log => { + -debug => [ qr/Request body is larger than the configured limit/, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1) . "a" x 131072 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + 2) . "b" x 131072 . q( + -----------------------------69343412719991675451336310646-- + ) + ), + 131072*3 + ), +}, +# Known issue on nginx, disable it for now. +#{ +# type => "config", +# comment => "SecRequestBodyLimitAction ProcessPartial (plain/greater)", +# conf => qq( +# SecRuleEngine On +# SecDebugLog $ENV{DEBUG_LOG} +# SecDebugLogLevel 9 +# SecRequestBodyAccess On +# SecRequestBodyLimitAction ProcessPartial +# SecRequestBodyLimit 131072 +# ), +# match_log => { +# -debug => [ qr/Request body is larger than the configured limit/, 1], +# }, +# match_response => { +# status => qr/^200$/, +# }, +# request => new HTTP::Request( +# POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", +# [ +# "Content-Type" => "application/json", +# ], +# normalize_raw_request_data( +# q( +# { +# ) . "'abcdefghijlmnopq'='abcdefghijlmnopqrstuvxz',\\n" x 99000 . q( +# }, +# ), +# ), +# ), +#}, + + + + + +# SecCookieFormat +{ + type => "config", + comment => "SecCookieFormat (pos)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 5 + SecCookieFormat 1 + SecRule REQUEST_COOKIES_NAMES "\@streq SESSIONID" "phase:1,deny,chain,id:500231" + SecRule REQUEST_COOKIES:\$SESSIONID_PATH "\@streq /" "chain" + SecRule REQUEST_COOKIES:SESSIONID "\@streq cookieval" + ), + match_log => { + error => [ qr/Access denied with code 403 \(phase 1\)\. String match "cookieval" at REQUEST_COOKIES:SESSIONID\./, 1 ], + debug => [ qr(Adding request cookie: name "\$SESSIONID_PATH", value "/"), 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Cookie" => q($Version="1"; SESSIONID="cookieval"; $PATH="/"), + ], + undef, + ), +}, +{ + type => "config", + comment => "SecCookieFormat (neg)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 5 + SecCookieFormat 0 + SecRule REQUEST_COOKIES_NAMES "\@streq SESSIONID" "phase:1,deny,chain,id:500234" + SecRule REQUEST_COOKIES:\$SESSIONID_PATH "\@streq /" "chain" + SecRule REQUEST_COOKIES:SESSIONID "\@streq cookieval" + ), + match_log => { + -error => [ qr/Access denied/, 1 ], + -debug => [ qr(Adding request cookie: name "\$SESSIONID_PATH", value "/"), 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Cookie" => q($Version="1"; SESSIONID="cookieval"; $PATH="/"), + ], + undef, + ), +}, + diff --git a/tests/regression/config/10-response-directives.t b/tests/regression/config/10-response-directives.t new file mode 100644 index 0000000..51f10c4 --- /dev/null +++ b/tests/regression/config/10-response-directives.t @@ -0,0 +1,174 @@ +### Tests for directives altering how a response is handled + +# SecResponseBodyMimeTypesClear +{ + type => "config", + comment => "SecResponseBodyMimeTypesClear", + conf => qq( + SecRuleEngine On + SecResponseBodyAccess On + SecResponseBodyMimeTypesClear + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule RESPONSE_BODY "TEST" "phase:4,deny,id:500237" + ), + match_log => { + -error => [ qr/Access denied/, 1 ], + debug => [ qr/Not buffering response body for unconfigured MIME type/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# SecResponseBodyAccess & SecResponseBodyMimeType +{ + type => "config", + comment => "SecResponseBodyAccess On", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecResponseBodyAccess On + SecResponseBodyMimeType text/plain null + SecRule RESPONSE_BODY "TEST" "phase:4,deny,id:500238" + ), + match_log => { + error => [ qr/Access denied with code 403 \(phase 4\)\. Pattern match "TEST" at RESPONSE_BODY\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "config", + comment => "SecResponseBodyAccess Off", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecResponseBodyAccess Off + SecResponseBodyMimeType text/plain null + SecRule RESPONSE_BODY "TEST" "phase:4,deny,id:500239" + ), + match_log => { + -error => [ qr/Access denied/, 1 ], + debug => [ qr/Response body buffering is not enabled\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# SecResponseBodyLimit +{ + type => "config", + comment => "SecResponseBodyLimit (equal)", + conf => qq( + SecRuleEngine On + SecResponseBodyAccess On + SecResponseBodyMimeType text/plain null + SecResponseBodyLimit 8192 + ), + match_log => { + -error => [ qr/Content-Length \(\d+\) over the limit/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/8k.txt", + ), +}, +{ + type => "config", + comment => "SecResponseBodyLimit (less)", + conf => qq( + SecRuleEngine On + SecResponseBodyAccess On + SecResponseBodyMimeType text/plain null + SecResponseBodyLimit 9000 + ), + match_log => { + -error => [ qr/Content-Length \(\d+\) over the limit/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/8k.txt", + ), +}, +{ + type => "config", + comment => "SecResponseBodyLimit (greater)", + conf => qq( + SecRuleEngine On + SecResponseBodyAccess On + SecResponseBodyMimeType text/plain null + SecResponseBodyLimit 8000 + ), + match_log => { + error => [ qr/Content-Length \(\d+\) over the limit \(8000\)\./, 1 ], + }, + match_response => { + status => qr/^500$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/8k.txt", + ), +}, + +# ResponseBodyLimitAction +{ + type => "config", + comment => "SecResponseBodyLimitAction Reject", + conf => qq( + SecRuleEngine On + SecResponseBodyAccess On + SecResponseBodyMimeType text/plain null + SecResponseBodyLimit 5 + SecResponseBodyLimitAction Reject + ), + match_log => { + error => [ qr/Content-Length \(\d+\) over the limit \(5\)\./, 1 ], + }, + match_response => { + status => qr/^500$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/8k.txt", + ), +}, +{ + type => "config", + comment => "SecResponseBodyLimitAction ProcessPartial", + conf => qq( + SecRuleEngine On + SecResponseBodyAccess On + SecResponseBodyMimeType text/plain null + SecResponseBodyLimit 5 + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 4 + SecResponseBodyLimitAction ProcessPartial + ), + match_log => { + -error => [ qr/Content-Length \(\d+\) over the limit/, 1 ], + debug => [ qr/Processing partial response body \(limit 5\)/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/8k.txt", + ), +}, diff --git a/tests/regression/config/20-chroot.t b/tests/regression/config/20-chroot.t new file mode 100644 index 0000000..2147d2b --- /dev/null +++ b/tests/regression/config/20-chroot.t @@ -0,0 +1,35 @@ +### SecChroot tests +# TODO: Will not work as we need root access + +#{ +# type => "config", +# comment => "SecChroot", +# httpd_opts => qw( +# -DCHROOT +# ), +# conf => qq( +# # These will be in the chroot +# PidFile /logs/httpd.pid +# ScoreBoardFile /logs/httpd.scoreboard +# User nobody +# Group nogroup +# +# SecAuditEngine On +# SecDebugLog $ENV{DEBUG_LOG} +# SecDebugLogLevel 9 +# SecAuditLog $ENV{AUDIT_LOG} +# SecAuditLogStorageDir "/logs/audit" +# SecAuditLogType Concurrent +# SecChrootDir "$ENV{TEST_SERVER_ROOT}" +# ), +# match_log => { +# debug => [ qr/./, 1 ], +# audit => [ qr/./, 1 ], +# }, +# match_response => { +# status => qr/^200$/, +# }, +# request => new HTTP::Request( +# GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", +# ), +#}, diff --git a/tests/regression/misc/00-multipart-parser.t b/tests/regression/misc/00-multipart-parser.t new file mode 100644 index 0000000..de39bf0 --- /dev/null +++ b/tests/regression/misc/00-multipart-parser.t @@ -0,0 +1,1813 @@ +### Multipart parser tests + +# Normal +{ + type => "misc", + comment => "multipart parser (normal)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500055" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500056" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500057" + ), + match_log => { + debug => [ qr/Added file part [0-9a-h]+ to the list: name "image" file name "image.jpg" \(offset 258, length 10\).*Adding request argument \(BODY\): name "name", value "Brian Rectanus".*Adding request argument \(BODY\): name "email", value "brian.rectanus\@breach.com"/s, 1 ], + -debug => [ qr/Multipart.*(?i:error|warning)/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary=0000), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, + +# Final CRLF or not, we should still work +{ + type => "misc", + comment => "multipart parser (final CRLF)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500058" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500059" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500060" + ), + match_log => { + debug => [ qr/Adding request argument \(BODY\): name "a", value "1".*Adding request argument \(BODY\): name "b", value "2"/s, 1 ], + -debug => [ qr/Multipart error:/, 1 ], + + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + 2 + -----------------------------69343412719991675451336310646-- + ), + ), + ), +}, + +# No final CRLF +{ + type => "misc", + comment => "multipart parser (no final CRLF)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500061" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500062" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500063" + ), + match_log => { + debug => [ qr/Adding request argument \(BODY\): name "a", value "1".*Adding request argument \(BODY\): name "b", value "2"/s, 1 ], + -debug => [ qr/Multipart error:/, 1 ], + + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + 2 + -----------------------------69343412719991675451336310646--), + ), + ), +}, + +# Should work with a boundary of "boundary" +{ + type => "misc", + comment => "multipart parser (boundary contains \"boundary\")", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500064" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500065" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500066" + ), + match_log => { + debug => [ qr/Adding request argument \(BODY\): name "a", value "1".*Adding request argument \(BODY\): name "b", value "2"/s, 1 ], + -debug => [ qr/Multipart error:/, 1 ], + + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=------------------------------------------------boundary", + ], + normalize_raw_request_data( + q( + --------------------------------------------------boundary + Content-Disposition: form-data; name="a" + + 1 + --------------------------------------------------boundary + Content-Disposition: form-data; name="b" + + 2 + --------------------------------------------------boundary-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (boundary contains \"bOuNdArY\")", + note => q( + KHTML Boundary + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500067" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500068" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500069" + ), + match_log => { + debug => [ qr/Adding request argument \(BODY\): name "a", value "1".*Adding request argument \(BODY\): name "b", value "2"/s, 1 ], + -debug => [ qr/Multipart error:/, 1 ], + + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=--------0xKhTmLbOuNdArY", + ], + normalize_raw_request_data( + q( + ----------0xKhTmLbOuNdArY + Content-Disposition: form-data; name="a" + + 1 + ----------0xKhTmLbOuNdArY + Content-Disposition: form-data; name="b" + + 2 + ----------0xKhTmLbOuNdArY-- + ), + ), + ), +}, + +# We should handle data starting with a "--" +{ + type => "misc", + comment => "multipart parser (data contains \"--\")", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500070" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500071" + ), + match_log => { + debug => [ qr/Adding request argument \(BODY\): name "a", value "--test".*Adding request argument \(BODY\): name "b", value "--"/s, 1 ], + -debug => [ qr/Multipart error:/, 1 ], + + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + --test + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + -- + -----------------------------69343412719991675451336310646--), + ), + ), +}, + +# We should emit warnings for parsing errors +{ + type => "misc", + comment => "multipart parser error (no final boundary)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecAuditLog "$ENV{AUDIT_LOG}" + SecAuditEngine RelevantOnly + ), + match_log => { + debug => [ qr/Final boundary missing/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + 2 + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser error (no disposition)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecAuditLog "$ENV{AUDIT_LOG}" + SecAuditEngine RelevantOnly + ), + match_log => { + -debug => [ qr/Multipart error:/, 1 ], + audit => [ qr/Part missing Content-Disposition header/, 1 ], + debug => [ qr/Part missing Content-Disposition header/, 1 ], + + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + + 1 + -----------------------------69343412719991675451336310646 + + 2 + -----------------------------69343412719991675451336310646-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser error (bad disposition)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecAuditLog "$ENV{AUDIT_LOG}" + SecAuditEngine RelevantOnly + ), + match_log => { + audit => [ qr/Invalid Content-Disposition header/, 1 ], + debug => [ qr/Invalid Content-Disposition header/, 1 ], + + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data name="b" + + 2 + -----------------------------69343412719991675451336310646-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser error (no disposition name)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecAuditLog "$ENV{AUDIT_LOG}" + SecAuditEngine RelevantOnly + ), + match_log => { + -debug => [ qr/Multipart error:/, 1 ], + audit => [ qr/Content-Disposition header missing name field/, 1 ], + debug => [ qr/Content-Disposition header missing name field/, 1 ], + + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; + + 2 + -----------------------------69343412719991675451336310646-- + ), + ), + ), +}, + +# Zero length part name should not crash +{ + type => "misc", + comment => "multipart parser (zero length part name)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "!\@eq 1" "phase:2,deny,id:500072" + SecRule REQBODY_PROCESSOR_ERROR "!\@eq 1" "phase:2,deny,id:500073" + ), + match_log => { + debug => [ qr/name: a.*variable: 1.*Invalid part header \(header name missing\)/s, 1 ], + -debug => [ qr/Adding request argument \(BODY\): name "b"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1 + -----------------------------69343412719991675451336310646 + : + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + 2 + -----------------------------69343412719991675451336310646-- + ), + ), + ), +}, + +# Part header folding +{ + type => "misc", + comment => "multipart parser (part header folding - space)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "!\@eq 1" "phase:2,deny,status:403,id:500074" + SecRule MULTIPART_HEADER_FOLDING "!\@eq 1" "phase:2,deny,status:403,id:500075" + SecRule MULTIPART_INVALID_HEADER_FOLDING "!\@eq 0" "phase:2,deny,status:403,id:500076" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,status:403,id:500077" + ), + match_log => { + debug => [ qr/name: a.*variable: 1.*name: b.*variable: 2/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; + name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; + name="b" + + 2 + -----------------------------69343412719991675451336310646-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (part header folding - tab)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "!\@eq 1" "phase:2,deny,status:403,id:500078" + SecRule MULTIPART_HEADER_FOLDING "!\@eq 1" "phase:2,deny,status:403,id:500079" + SecRule MULTIPART_INVALID_HEADER_FOLDING "!\@eq 0" "phase:2,deny,status:403,id:500080" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,status:403,id:500081" + ), + match_log => { + debug => [ qr/name: a.*variable: 1.*name: b.*variable: 2/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; + name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; + name="b" + + 2 + -----------------------------69343412719991675451336310646-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (part header folding - mixed)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "!\@eq 1" "phase:2,deny,status:403,id:500082" + SecRule MULTIPART_HEADER_FOLDING "!\@eq 1" "phase:2,deny,status:403,id:500083" + SecRule MULTIPART_INVALID_HEADER_FOLDING "!\@eq 0" "phase:2,deny,status:403,id:500084" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,status:403,id:500085" + ), + match_log => { + debug => [ qr/name: a.*variable: 1.*name: b.*variable: 2/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; + name="b" + + 2 + -----------------------------69343412719991675451336310646-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (part header folding - invalid)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "!\@eq 1" "phase:2,deny,status:403,id:500086" + SecRule MULTIPART_HEADER_FOLDING "!\@eq 1" "phase:2,deny,status:403,id:500087" + SecRule MULTIPART_INVALID_HEADER_FOLDING "!\@eq 1" "phase:2,deny,status:403,id:500088" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,status:403,id:500089" + ), + match_log => { + debug => [ qr/name: a.*variable: 1.*name: b.*variable: 2/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; + name="b" + + 2 + -----------------------------69343412719991675451336310646-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (part header folding - mixed invalid)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "!\@eq 1" "phase:2,deny,status:403,id:500090" + SecRule MULTIPART_HEADER_FOLDING "!\@eq 1" "phase:2,deny,status:403,id:500091" + SecRule MULTIPART_INVALID_HEADER_FOLDING "!\@eq 1" "phase:2,deny,status:403,id:500092" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,status:403,id:500093" + ), + match_log => { + debug => [ qr/name: a.*variable: 1.*name: b.*variable: 2/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; + name="b" + + 2 + -----------------------------69343412719991675451336310646-- + ), + ), + ), +}, + +# Data following final boundary should set flag +{ + type => "misc", + comment => "multipart parser (data after final boundary)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_DATA_AFTER "\@eq 1" "phase:2,deny,status:403,id:500094" + ), + match_log => { + debug => [ qr/name: a.*variable: 1.*Ignoring data after last boundary/s, 1 ], + -debug => [ qr/Adding request argument \(BODY\): name "b"/s, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1 + -----------------------------69343412719991675451336310646-- + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + 2 + -----------------------------69343412719991675451336310646-- + ), + ), + ), +}, + +# Single quoted data is invalid +{ + type => "misc", + comment => "multipart parser (C-D uses single quotes)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "!\@eq 1" "phase:2,deny,id:500095" + SecRule MULTIPART_INVALID_QUOTING "!\@eq 1" "phase:2,deny,id:500096" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "chain,phase:2,deny,id:500097" + ), + match_log => { + debug => [ qr/name: a.*variable: 1.*Duplicate Content-Disposition name/s, 1 ], + -debug => [ qr/Adding request argument \(BODY\): name "b/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a" + + 1 + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name=';filename="dummy';name=b;" + + 2 + -----------------------------69343412719991675451336310646-- + ), + ), + ), +}, + +# Invalid boundary separators +{ + type => "misc", + comment => "multipart parser (invalid C-T boundary separator - comma)", + note => q( + PHP 5.2.3 - no effect. + ), + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500098" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500099" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500100" + ), + match_log => { + error => [ qr/Invalid boundary in C-T \(malformed\)/, 1 ], + + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data, boundary=0000), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (invalid C-T boundary separator - space)", + note => q( + PHP 5.2.3 - no effect. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500101" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500102" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500103" + ), + match_log => { + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data boundary=0000), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, + +# Invalid boundary parameter names +{ + type => "misc", + comment => "multipart parser (invalid C-T boundary parameter name - case)", + note => q( + PHP 5.2.3 - does not recognise boundary. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500104" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500105" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500106" + ), + match_log => { + error => [ qr/Invalid boundary in C-T \(case sensitivity\)/, 1 ], + + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; bOundAry=0000), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (invalid C-T boundary parameter name - trailing chars)", + note => q( + PHP 5.2.3 - does not recognise boundary. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500107" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500108" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500109" + ), + match_log => { + error => [ qr/Invalid boundary in C-T \(parameter name\)/, 1 ], + + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary123=0000), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, + +# Invalid boundaries +{ + type => "misc", + comment => "multipart parser (multiple C-T boundaries - first quoted)", + note => q( + PHP 5.2.3 - uses first boundary. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500110" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500111" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500112" + ), + match_log => { + debug => [ qr/Multiple boundary parameters in C-T/, 1 ], + + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary="0000"; boundary=1111), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (multiple C-T boundaries - comma separated)", + note => q( + PHP 5.2.3 - uses first boundary. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500113" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500114" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500115" + ), + match_log => { + debug => [ qr/Multiple boundary parameters in C-T/, 1 ], + + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary=0000, boundary=1111), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (boundary whitespace in C-T - after name)", + note => q( + PHP 5.2.3 - no effect. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500116" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500117" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500118" + ), + match_log => { + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary =0000), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (boundary whitespace in C-T - before value)", + note => q( + PHP 5.2.3 - uses " 0000" as boundary. + We should probably interpret it as "0000" and just flag a strict error. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500119" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500120" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500121" + ), + match_log => { + debug => [ qr/boundary whitespace in C-T header/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary= 0000), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (boundary whitespace in C-T - after value)", + note => q( + PHP 5.2.3 - no effect. + Apache just silently removes the trailing whitespace for us. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500122" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500123" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500124" + ), + match_log => { + debug => [ qr/Added file part [0-9a-h]+ to the list: name "image" file name "image.jpg" \(offset 258, length 10\).*Adding request argument \(BODY\): name "name", value "Brian Rectanus".*Adding request argument \(BODY\): name "email", value "brian.rectanus\@breach.com"/s, 1 ], + -debug => [ qr/Multipart.*(?i:error|warning)/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary=0000 ), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, + +# Special chars +{ + type => "misc", + comment => "multipart parser (boundary special char - trailing whitespace+token)", + note => q( + PHP 5.2.3 - uses "0000 1111" as boundary. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500125" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500126" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500127" + ), + match_log => { + debug => [ qr/No boundaries found in payload/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary=0000 1111), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (boundary special char - trailing comma+token)", + note => q( + PHP 5.2.3 - uses "0000" as boundary. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500128" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500129" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500130" + ), + match_log => { + debug => [ qr/Invalid boundary in C-T \(characters\)/, 1 ], + + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary=0000,1111), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, + +# Quoting +{ + type => "misc", + comment => "multipart parser (quoted boundary - normal)", + note => q( + PHP 5.2.3 - no effect. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500131" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500132" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500133" + ), + match_log => { + debug => [ qr/boundary was quoted/, 1 ], + + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary="0000"), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (quoted boundary value - whitespace before)", + note => q( + PHP 5.2.3 - uses " 0000" as boundary. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500134" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500135" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500136" + ), + match_log => { + debug => [ qr/boundary was quoted.*No boundaries found in payload/s, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary=" 0000"), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (quoted boundary value - whitespace after)", + note => q( + PHP 5.2.3 - uses "0000 " as boundary. + Trailing whitespace violates RFC as well. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500137" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500138" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500139" + ), + match_log => { + debug => [ qr/boundary was quoted.*No boundaries found in payload/s, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary="0000 "), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (quoted boundary value - whitespace between)", + note => q( + PHP 5.2.3 - uses "0000 " as boundary. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500140" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500141" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500142" + ), + match_log => { + debug => [ qr/boundary was quoted/s, 1 ], + -debug => [ qr/No boundaries found in payload/s, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary="0000 1111"), + ], + normalize_raw_request_data( + q( + --0000 1111 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 1111 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 1111 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000 1111-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (quoted boundary value - contained quote)", + note => q( + PHP 5.2.3 - uses "0000 " as boundary. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500143" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500144" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500145" + ), + match_log => { + debug => [ qr/Invalid boundary in C-T \(characters\)/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary="00\"00"), + ], + normalize_raw_request_data( + q( + --00"00 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --00"00 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --00"00 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --00"00-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (quoted boundary value - two quoted values)", + note => q( + PHP 5.2.3 - does not work, uses "00" as boundary. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500146" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500147" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500148" + ), + match_log => { + debug => [ qr/Invalid boundary in C-T \(characters\)/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary="00""00"), + ], + normalize_raw_request_data( + q( + --00"00 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --00"00 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --00"00 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --00"00-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (partial quoted boundary value - only start quote)", + note => q( + PHP 5.2.3 - breaks parsing. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500149" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500150" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500151" + ), + match_log => { + debug => [ qr/Invalid boundary in C-T \(quote\)/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary="0000), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (partial quoted boundary value - only end quote)", + note => q( + PHP 5.2.3 - breaks parsing. + ), + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500152" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500153" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500154" + ), + match_log => { + debug => [ qr/Invalid boundary in C-T \(quote\)/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary=0000"), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image"; filename="image.jpg" + Content-Type: image/jpeg + + BINARYDATA + --0000-- + ), + ), + ), +}, + +# Multipart mixed +{ + type => "misc", + comment => "multipart parser (multipart mixed - normal)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500155" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500156" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500157" + ), + match_log => { + debug => [ qr/Invalid Content-Disposition header/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary=0000), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: attachment + Content-Type: multipart/mixed; boundary=BbC04y + + --BbC04y + Content-Disposition: file; filename="file1.txt" + Content-Type: text/plain + + ... contents of file1.txt ... + --BbC04y + Content-Disposition: file; filename="file2.gif + Content-Type: image/jpeg + Content-Transfer-Encoding: binary + + ...contents of file2.gif... + --0000-- + ), + ), + ), +}, +{ + type => "misc", + comment => "multipart parser (multipart mixed - missing disposition)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny,id:500158" + SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny,id:500159" + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500160" + ), + match_log => { + debug => [ qr/Part missing Content-Disposition header/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary=0000), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Type: multipart/mixed; boundary=BbC04y + + --BbC04y + Content-Disposition: file; filename="file1.txt" + Content-Type: text/plain + + ... contents of file1.txt ... + --BbC04y + Content-Disposition: file; filename="file2.gif + Content-Type: image/jpeg + Content-Transfer-Encoding: binary + + ...contents of file2.gif... + --0000-- + ), + ), + ), +}, + +# File limits +{ + type => "misc", + comment => "multipart parser (normal)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecTmpDir "$ENV{TEMP_DIR}" + SecUploadDir "$ENV{UPLOAD_DIR}" + SecUploadKeepFiles On + SecUploadFileLimit 2 + + # These should be set + SecRule MULTIPART_STRICT_ERROR "!\@eq 1" "phase:2,deny,id:500161" + SecRule MULTIPART_FILE_LIMIT_EXCEEDED "!\@eq 1" "phase:2,deny,id:500162" + + # This should not be set + SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny,id:500163" + + # Theses should still be accurate + SecRule &FILES "!\@eq 3" "phase:2,deny,id:500164" + SecRule &FILES_NAMES "!\@eq 3" "phase:2,deny,id:500165" + SecRule &FILES_SIZES "!\@eq 3" "phase:2,deny,id:500166" + SecRule FILES_SIZES:/^image/ "\@eq 0" "phase:2,deny,id:500167" + + # This should be the SecUploadFileLimit + SecRule &FILES_TMPNAMES "!\@eq 2" "phase:2,deny,id:500168" + ), + match_log => { + debug => [ qr/Multipart: Upload file limit exceeded.*name: test.*variable: This is test data/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary=0000), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image1"; filename="image1.jpg" + Content-Type: image/jpeg + + BINARYDATA1 + --0000 + Content-Disposition: form-data; name="image2"; filename="image2.jpg" + Content-Type: image/jpeg + + BINARYDATA2 + --0000 + Content-Disposition: form-data; name="image3"; filename="image3.jpg" + Content-Type: image/jpeg + + BINARYDATA3 + --0000 + Content-Disposition: form-data; name="test" + + This is test data. + --0000-- + ), + ), + ), +}, + diff --git a/tests/regression/misc/00-phases.t b/tests/regression/misc/00-phases.t new file mode 100644 index 0000000..2f1c4e5 --- /dev/null +++ b/tests/regression/misc/00-phases.t @@ -0,0 +1,151 @@ +### Test the phases + +# Phase 1 (request headers) +{ + type => "misc", + comment => "phase 1", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType text/plain null + SecRule REQUEST_LINE "^POST" "phase:1,pass,log,auditlog,id:500169" + SecRule ARGS "val1" "phase:1,pass,log,auditlog,id:500170" + SecRule RESPONSE_HEADERS:Last-Modified "." "phase:1,pass,log,auditlog,id:500171" + SecRule RESPONSE_BODY "TEST" "phase:1,pass,log,auditlog,id:500172" + ), + match_log => { + error => [ qr/Pattern match "\^POST" at REQUEST_LINE/, 1 ], + -error => [ qr/Pattern match .* (ARGS|RESPONSE)/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, + +# Phase 2 (request body) +{ + type => "misc", + comment => "phase 2", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType text/plain null + SecRule REQUEST_LINE "^POST" "phase:2,pass,log,auditlog,id:500173" + SecRule ARGS "val1" "phase:2,pass,log,auditlog,id:500174" + SecRule RESPONSE_HEADERS:Last-Modified "." "phase:2,pass,log,auditlog,id:500175" + SecRule RESPONSE_BODY "TEST" "phase:2,pass,log,auditlog,id:500176" + ), + match_log => { + error => [ qr/Pattern match "\^POST" at REQUEST_LINE.*Pattern match "val1" at ARGS/s, 1 ], + -error => [ qr/Pattern match .* RESPONSE/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, + +# Phase 3 (response headers) +{ + type => "misc", + comment => "phase 3", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType text/plain null + SecRule REQUEST_LINE "^POST" "phase:3,pass,log,auditlog,id:500177" + SecRule ARGS "val1" "phase:3,pass,log,auditlog,id:500178" + SecRule RESPONSE_HEADERS:Last-Modified "." "phase:3,pass,log,auditlog,id:500179" + SecRule RESPONSE_BODY "TEST" "phase:3,pass,log,auditlog,id:500180" + ), + match_log => { + error => [ qr/Pattern match "\^POST" at REQUEST_LINE.*Pattern match "val1" at ARGS.*Pattern match "\." at RESPONSE_HEADERS/s, 1 ], + -error => [ qr/Pattern match .* RESPONSE_BODY/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, + +# Phase 4 (response body) +{ + type => "misc", + comment => "phase 4", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType text/plain null + SecDebugLog "$ENV{DEBUG_LOG}" + SecDebugLogLevel 9 + SecRule REQUEST_LINE "^POST" "phase:4,pass,log,auditlog,id:500181" + SecRule ARGS "val1" "phase:4,pass,log,auditlog,id:500182" + SecRule RESPONSE_HEADERS:Last-Modified "." "phase:4,pass,log,auditlog,id:500183" + SecRule RESPONSE_BODY "TEST" "phase:4,pass,log,auditlog,id:500184" + ), + match_log => { + error => [ qr/Pattern match "\^POST" at REQUEST_LINE.*Pattern match "val1" at ARGS.*Pattern match "\." at RESPONSE_HEADERS.*Pattern match "TEST" at RESPONSE_BODY/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, + +# Phase 5 (logging) +{ + type => "misc", + comment => "phase 5", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType text/plain null + SecRule REQUEST_LINE "^POST" "phase:5,pass,log,auditlog,id:500185" + SecRule ARGS "val1" "phase:5,pass,log,auditlog,id:500186" + SecRule RESPONSE_HEADERS:Last-Modified "." "phase:5,pass,log,auditlog,id:500187" + SecRule RESPONSE_BODY "TEST" "phase:5,pass,log,auditlog,id:500188" + ), + match_log => { + error => [ qr/Pattern match "\^POST" at REQUEST_LINE.*Pattern match "val1" at ARGS.*Pattern match "\." at RESPONSE_HEADERS.*Pattern match "TEST" at RESPONSE_BODY/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, diff --git a/tests/regression/misc/10-pcre.t b/tests/regression/misc/10-pcre.t new file mode 100644 index 0000000..61c0a30 --- /dev/null +++ b/tests/regression/misc/10-pcre.t @@ -0,0 +1,38 @@ +### PCRE Limits + +{ + type => "misc", + comment => "PCRE limits", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + + SecRequestBodyAccess On + SecRequestBodyLimit 1000000 + SecRequestBodyInMemoryLimit 1000000 + + # Set Limits low + SecPcreMatchLimit 100 + SecPcreMatchLimitRecursion 100 + + # Poor REGEX + SecRule ARGS "(?:(.{2,})\\1{32,})" "phase:2,deny,capture,msg:'REDoS',id:500053" + # Detect PCRE limits exceeded + SecRule TX:MSC_PCRE_LIMITS_EXCEEDED "!\@streq 0" "phase:2,deny,msg:'ModSecurity Internal Error Flagged: %{MATCHED_VAR_NAME}',id:500054" + ), + match_log => { + debug => [ qr/PCRE limits exceeded/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + "test=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad", + ), +}, diff --git a/tests/regression/misc/10-tfn-cache.t b/tests/regression/misc/10-tfn-cache.t new file mode 100644 index 0000000..672b610 --- /dev/null +++ b/tests/regression/misc/10-tfn-cache.t @@ -0,0 +1,187 @@ +### Transformation Caching + +{ + type => "misc", + comment => "tfncache (simple fully cached)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + + # We need to make this work no matter what the defaults may change to + SecCacheTransformations On "minlen:1,maxlen:0" + + # This should cache it + SecRule ARGS_GET "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog,id:500037" + + # This should use the cached value + SecRule ARGS_GET:test "foobar" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,deny,id:500038" + ), + match_log => { + debug => [ qr/removeWhiteSpace,lowercase: "foobar" .*cached/, 1 ], + -debug => [ qr/partially cached/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html?test=Foo+Bar", + ), +}, +{ + type => "misc", + comment => "tfncache (simple partially cached)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + + # We need to make this work no matter what the defaults may change to + SecCacheTransformations On "minlen:1,maxlen:0,incremental:off,maxitems:0" + + # This should cache it + SecRule ARGS_GET "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,pass,nolog,id:500039" + + # This should use the partially cached value + SecRule ARGS_GET:test "foobar" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,deny,id:500040" + ), + match_log => { + debug => [ qr/removeWhiteSpace: "FooBar" .*partially cached/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html?test=Foo+Bar", + ), +}, +{ + type => "misc", + comment => "tfncache (separate phases)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + + # We need to make this work no matter what the defaults may change to + SecCacheTransformations On "minlen:1,maxlen:0" + + # This should cache it + SecRule ARGS_GET "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog,id:500041" + + # This should use the cached value + SecRule ARGS_GET:test "foobar" "phase:2,t:none,t:removeWhiteSpace,t:lowercase,deny,id:500042" + ), + match_log => { + -debug => [ qr/removeWhiteSpace,lowercase: "foobar" .*cached/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html?test=Foo+Bar", + ), +}, +{ + type => "misc", + comment => "tfncache (non-modifying tfns cached)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + + # We need to make this work no matter what the defaults may change to + SecCacheTransformations On "minlen:1,maxlen:0" + + # This should cache it + SecRule ARGS_GET "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog,id:500043" + + # This should use the cached value + SecRule ARGS_GET:test "foobar" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,deny,id:500044" + ), + match_log => { + debug => [ qr/removeWhiteSpace,lowercase: "foobar" .*cached/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html?test=foo+bar", + ), +}, +{ + type => "misc", + comment => "tfncache (unique keys)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + + # We need to make this work no matter what the defaults may change to + SecCacheTransformations On "minlen:1,maxlen:0" + + # This should cache it + SecRule ARGS "WillNotMatch" "phase:2,t:none,t:removeWhiteSpace,t:lowercase,pass,id:500045" + + # This should see cached versions of *both* ARGS_GET + SecRule ARGS:test "queryval" "phase:2,t:none,t:removeWhiteSpace,t:lowercase,deny,chain,id:500046" + SecRule ARGS:test "firstval" "t:none,t:removeWhiteSpace,t:lowercase,chain" + SecRule ARGS:test "secondval" "t:none,t:removeWhiteSpace,t:lowercase" + ), + match_log => { + debug => [ qr/removeWhiteSpace,lowercase: "queryval" .*removeWhiteSpace,lowercase: "firstval" .*cached.*removeWhiteSpace,lowercase: "secondval" .*cached/s, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html?test=Query+Val", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + "test=First+Val&test=Second+Val", + ), +}, +{ + type => "misc", + comment => "tfncache (large cache)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + + SecRequestBodyNoFilesLimit 1048576 + SecRequestBodyInMemoryLimit 131072 + SecResponseBodyLimit 1048576 + + # We need to make this work no matter what the defaults may change to + SecCacheTransformations On "minlen:1,maxlen:0,maxitems:0" + + # This should cache it in all phases + SecRule ARGS "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog,id:500048" + SecRule ARGS "WillNotMatch" "phase:2,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog,id:500049" + SecRule ARGS "WillNotMatch" "phase:3,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog,id:500050" + SecRule ARGS "WillNotMatch" "phase:4,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog,id:500051" + + # This should use the cached value + SecRule ARGS "foobar" "phase:4,t:none,t:removeWhiteSpace,t:lowercase,deny,id:500052" + ), + match_log => { + debug => [ qr/Adding request argument \(BODY\): name "test", value "Foo Bar"/, 60, "Waiting for httpd to process request: "], + -error => [ qr/segmentation fault/i, 60 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # 1000 Args + join("&", map { sprintf "arg%08d=0123456789abcdef+0123456789ABCDEF+0123456789abcdef", $_ } (1 .. 1000))."&test=Foo+Bar", + ), +}, diff --git a/tests/regression/misc/20-status-engine.t b/tests/regression/misc/20-status-engine.t new file mode 100644 index 0000000..a8ec6f3 --- /dev/null +++ b/tests/regression/misc/20-status-engine.t @@ -0,0 +1,123 @@ +### Test the SecStatusEngine + +# On +{ + type => "misc", + comment => "Setting SecStatusEngine to On", + conf => qq( + SecRuleEngine On + SecStatusEngine On + ), + match_log => { + error => [ qr/ModSecurity: StatusEngine call successfully sent/, 1], + -error => [ qr/Status engine is currently disabled, enable it by set SecStatusEngine to On/, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, +# Off +{ + type => "misc", + comment => "Setting SecStatusEngine to Off", + conf => qq( + SecRuleEngine On + SecStatusEngine Off + ), + match_log => { + -error => [ qr/ModSecurity: StatusEngine call successfully sent/, 1], + error => [ qr/Status engine is currently disabled, enable it by set SecStatusEngine to On/, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, +# On and SecServerSignature +{ + type => "misc", + comment => "SecStatusEngine On using SecServerSignature", + conf => qq( + SecRuleEngine On + SecServerSignature "SpiderServer v0.1a" + SecStatusEngine On + ), + match_log => { + error => [ qr/ModSecurity: StatusEngine call successfully sent/, 1], + -error => [ qr/StatusEngine call: .*SpiderServer v0.1a.*/, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, +# On and SecServerSignature +{ + type => "misc", + comment => "SecStatusEngine On/SecServerSignature - checking signature", + conf => qq( + SecRuleEngine On + SecServerSignature "SpiderServer v0.1a" + SecStatusEngine On + ), + match_log => { + error => { + apache => [ qr/StatusEngine call: \"[0-9]+.[0-9]+.[0-9]+[-RC]*[0-9]*\,Apache/, 1], + nginx => [ qr/StatusEngine call: \"[0-9]+.[0-9]+.[0-9]+[-RC]*[0-9]*\,nginx/, 1], + } + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, +# On and SecServerSignature +{ + type => "misc", + comment => "SecStatusEngine On - checking signature", + conf => qq( + SecStatusEngine On + ), + match_log => { + error => { + apache => [ qr/StatusEngine call: \"[0-9]+.[0-9]+.[0-9]+[-RC]*[0-9]*\,Apache/, 1], + nginx => [ qr/StatusEngine call: \"[0-9]+.[0-9]+.[0-9]+[-RC]*[0-9]*\,nginx/, 1], + } + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, + diff --git a/tests/regression/misc/25-libinjection.t b/tests/regression/misc/25-libinjection.t new file mode 100644 index 0000000..28b133f --- /dev/null +++ b/tests/regression/misc/25-libinjection.t @@ -0,0 +1,110 @@ +### libinjection. + +{ + type => "misc", + comment => "libinjection SQLi - with SQLi", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + + SecRule REQUEST_BODY "\@detectSQLi" "id:192372,log,deny" + ), + match_log => { + error => [ qr/detected SQLi using libinjection/, 1], + debug => [ qr/detected SQLi using libinjection/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" + ), +}, +{ + type => "misc", + comment => "libinjection SQLi - without SQLi", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + + SecRule REQUEST_BODY "\@detectSQLi" "id:192372,log,deny" + ), + match_log => { + -error => [ qr/detected SQLi using libinjection/, 1], + -debug => [ qr/detected SQLi using libinjection/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + "some_variable=hello cruel world" + ), +}, +{ + type => "misc", + comment => "libinjection XSS - with XSS", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + + SecRule REQUEST_BODY "\@detectXSS" "id:192372,log,deny" + ), + match_log => { + error => [ qr/detected XSS using libinjection/, 1], + debug => [ qr/detected XSS using libinjection/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + "some_variable=" + ), +}, +{ + type => "misc", + comment => "libinjection XSS - without XSS", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + + SecRule REQUEST_BODY "\@detectXSS" "id:192372,log,deny" + ), + match_log => { + -error => [ qr/detected XSS using libinjection/, 1], + -debug => [ qr/detected XSS using libinjection/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + "some_variable=hello cruel world" + ), +} diff --git a/tests/regression/misc/30-fuzzyHash.t b/tests/regression/misc/30-fuzzyHash.t new file mode 100644 index 0000000..fcd1a89 --- /dev/null +++ b/tests/regression/misc/30-fuzzyHash.t @@ -0,0 +1,180 @@ +### libinjection. + +{ + type => "misc", + comment => "fuzzyHash test", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + + SecRule REQUEST_BODY "\@fuzzyHash $ENV{CONF_DIR}/ssdeep.txt 1" "id:192372,log,deny" + ), + match_log => { + error => [ qr/ModSecurity: Access denied with code 403 \(phase 2\)\. Fuzzy hash of REQUEST_BODY matched with 96:MbQ1L0LDX8GPI8ov3D2D9zd6.*"modsecurity.conf-recommended"|ModSecurity was not compiled with ssdeep support./, 1], + debug => [ qr/Access denied with code 403 \(phase 2\)\. Fuzzy hash of REQUEST_BODY matched with 96:MbQ1L0LDX8GPI8ov3D2D9zd6.*"modsecurity.conf-recommended"|ModSecurity was not compiled with ssdeep support./, 1], + }, + match_response => { + status => qr/^403|200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + " +# -- Rule engine initialization ---------------------------------------------- + +# Enable ModSecurity, attaching it to every transaction. Use detection +# only to start with, because that minimises the chances of post-installation +# disruption. +# +SecRuleEngine DetectionOnly + + +# -- Request body handling --------------------------------------------------- + +# Allow ModSecurity to access request bodies. If you don't, ModSecurity +# won't be able to see any POST parameters, which opens a large security +# hole for attackers to exploit. +# +SecRequestBodyAccess On + + +# Enable XML request body parser. +# Initiate XML Processor in case of xml content-type +# +SecRule REQUEST_HEADERS:Content-Type \"text/xml\" \ + \"id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML\" + +# Enable JSON request body parser. +# Initiate JSON Processor in case of JSON content-type; change accordingly +# if your application does not use 'application/json' +# +SecRule REQUEST_HEADERS:Content-Type \"application/json\" \ + \"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON\" + +# Maximum request body size we will accept for buffering. If you support +# file uploads then the value given on the first line has to be as large +# as the largest file you are willing to accept. The second value refers +# to the size of data, with files excluded. You want to keep that value as +# low as practical. +# +SecRequestBodyLimit 13107200 +SecRequestBodyNoFilesLimit 131072 + +# Store up to 128 KB of request body data in memory. When the multipart +# parser reachers this limit, it will start using your hard disk for +# storage. That is slow, but unavoidable. +# +SecRequestBodyInMemoryLimit 131072 + +# What do do if the request body size is above our configured limit. +# Keep in mind that this setting will automatically be set to ProcessPartial +# when SecRuleEngine is set to DetectionOnly mode in order to minimize +# disruptions when initially deploying ModSecurity. +# +SecRequestBodyLimitAction Reject + +# Verify that we've correctly processed the request body. +# As a rule of thumb, when failing to process a request body +# you should reject the request (when deployed in blocking mode) +# or log a high-severity alert (when deployed in detection-only mode). +# +SecRule REQBODY_ERROR \"!\@eq 0\" \ +\"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2\" + " + ), +}, + +{ + type => "misc", + comment => "fuzzyHash test", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + + SecRule REQUEST_BODY "\@fuzzyHash $ENV{CONF_DIR}/ssdeep.txt 1" "id:192372,log,deny" + ), + match_log => { + -error => [ qr/Fuzzy hash of REQUEST_BODY matched/, 1], + -debug => [ qr/Fuzzy hash of REQUEST_BODY matched/, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + " + wheee + " + ), +}, + +{ + type => "misc", + comment => "fuzzy hash with FILES_TMP_CONTENT", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecUploadKeepFiles On + + SecRule FILES_TMP_CONTENT "\@fuzzyHash $ENV{CONF_DIR}/ssdeep.txt 1" "id:192372,log,deny" + ), + match_log => { + debug => [ qr/operator \"fuzzyHash\" with param \".*ssdeep.txt 1\" against FILES_TMP_CONTENT:image1/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => q(multipart/form-data; boundary=0000), + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name" + + Brian Rectanus + --0000 + Content-Disposition: form-data; name="email" + + brian.rectanus@breach.com + --0000 + Content-Disposition: form-data; name="image1"; filename="image1.jpg" + Content-Type: image/jpeg + + BINARYDATA1 + --0000 + Content-Disposition: form-data; name="image2"; filename="image2.jpg" + Content-Type: image/jpeg + + BINARYDATA2 + --0000 + Content-Disposition: form-data; name="image3"; filename="image3.jpg" + Content-Type: image/jpeg + + BINARYDATA3 + --0000 + Content-Disposition: form-data; name="test" + + This is test data. + --0000-- + ), + ), + ), +}, + + diff --git a/tests/regression/misc/40-secRemoteRules.t.in b/tests/regression/misc/40-secRemoteRules.t.in new file mode 100644 index 0000000..93acd73 --- /dev/null +++ b/tests/regression/misc/40-secRemoteRules.t.in @@ -0,0 +1,43 @@ +### SecRemoteRules + +{ + type => "misc", + comment => "SecRemoteRules load", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRemoteRules 123 "https://www.modsecurity.org/modsecurity-regression-test-secremoterules.txt" + ), + match_log => { + error => [ qr/ModSecurity: Loaded 1 rule/, 1], + }, +}, +{ + type => "misc", + comment => "SecRemoteRules apply some remote rules", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRemoteRules 123 "https://www.modsecurity.org/modsecurity-regression-test-secremoterules.txt" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Matched phrase \"127.0.0.1\" at REQUEST_FILENAME./, 1], + debug => [ qr/Matched phrase \"127.0.0.1\" at REQUEST_FILENAME/, 1 ], + }, + match_response => { + status => qr/^404$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" + ), +}, + diff --git a/tests/regression/misc/50-ipmatchfromfile-external.t.in b/tests/regression/misc/50-ipmatchfromfile-external.t.in new file mode 100644 index 0000000..6b674ca --- /dev/null +++ b/tests/regression/misc/50-ipmatchfromfile-external.t.in @@ -0,0 +1,71 @@ +### ipMatchFromFile external resource + +{ + type => "misc", + comment => "ipMatchFromFile", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule REMOTE_ADDR "\@ipMatchFromFile https://www.modsecurity.org/modsecurity-regression-test.txt" "id:10500,pass" + ), + match_log => { + error => [ qr/ModSecurity: Warning. IPmatchFromFile: \"127.0.0.1\" matched at REMOTE_ADDR./, 1], + debug => [ qr/IPmatchFromFile: \"127.0.0.1\" matched at REMOTE_ADDR./, 1 ], + -error => [ qr/ModSecurity: Problems loading external resources:/, 1], + }, + match_response => { + status => qr/^404$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" + ), +}, +{ + type => "misc", + comment => "ipMatchFromFile - 404 download", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRemoteRulesFailAction Warn + SecRule REMOTE_ADDR "\@ipMatchFromFile https://www.modsecurity.org/modsecurity-regression-test-404.txt" "id:10500,pass" + ), + match_log => { + error => [ qr/ModSecurity: Problems loading external resources: Failed to download: \"https:\/\/www.modsecurity.org\/modsecurity-regression-test-404.txt\" error: HTTP response code said error./, 1], + }, + match_response => { + status => qr/^404$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" + ), +}, +{ + type => "misc", + comment => "ipMatchFromFile - bad certificate name", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRemoteRulesFailAction Warn + SecRule REMOTE_ADDR "\@ipMatchFromFile https://status.modsecurity.org/modsecurity-regression-test-huge-ip-list.txt" "id:10500,pass" + ), + match_log => { + error => [ qr/ModSecurity: Problems loading external resources: Failed to download: \"https:\/\/status.modsecurity.org\/modsecurity-regression-test-huge-ip-list.txt\" error: [SSL peer certificate or SSH remote key was not OK.|Couldn't connect to server.]/, 1], + }, +}, + diff --git a/tests/regression/misc/60-pmfromfile-external.t b/tests/regression/misc/60-pmfromfile-external.t new file mode 100644 index 0000000..2910ed1 --- /dev/null +++ b/tests/regression/misc/60-pmfromfile-external.t @@ -0,0 +1,84 @@ +### pmfromfile external resource + +{ + type => "misc", + comment => "pmfromfile", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule REQUEST_FILENAME "\@pmFromFile https://www.modsecurity.org/modsecurity-regression-test.txt" "id:'123',phase:2,log,pass,t:none" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Matched phrase \"127.0.0.1\" at REQUEST_FILENAME./, 1], + debug => [ qr/Matched phrase \"127.0.0.1\" at REQUEST_FILENAME/, 1 ], + -error => [ qr/ModSecurity: Problems loading external resources:/, 1], + }, + match_response => { + status => qr/^404$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" + ), +}, +{ + type => "misc", + comment => "pmfromfile - 404 download", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRemoteRulesFailAction Warn + SecRule REQUEST_FILENAME "\@pmFromFile https://www.modsecurity.org/modsecurity-regression-test-404.txt" "id:'123',phase:2,log,pass,t:none" + + ), + match_log => { + error => [ qr/ModSecurity: Problems loading external resources: Failed to download: \"https:\/\/www.modsecurity.org\/modsecurity-regression-test-404.txt\" error: HTTP response code said error./, 1], + }, + match_response => { + status => qr/^404$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" + ), +}, +{ + type => "misc", + comment => "pmfromfile - bad certificate name", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRemoteRulesFailAction Warn + SecRule REQUEST_FILENAME "\@pmFromFile https://status.modsecurity.org/modsecurity-regression-test.txt" "id:'123',phase:2,log,pass,t:none" + + ), + match_log => { + error => [ qr/ModSecurity: Problems loading external resources: Failed to download: \"https:\/\/status.modsecurity.org\/modsecurity-regression-test.txt\" error: [SSL peer certificate or SSH remote key was not OK.|Couldn't connect to server.]/, 1], + }, + match_response => { + status => qr/^404$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" + ), +}, + diff --git a/tests/regression/misc/60-pmfromfile-external.t.in b/tests/regression/misc/60-pmfromfile-external.t.in new file mode 100644 index 0000000..2910ed1 --- /dev/null +++ b/tests/regression/misc/60-pmfromfile-external.t.in @@ -0,0 +1,84 @@ +### pmfromfile external resource + +{ + type => "misc", + comment => "pmfromfile", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRule REQUEST_FILENAME "\@pmFromFile https://www.modsecurity.org/modsecurity-regression-test.txt" "id:'123',phase:2,log,pass,t:none" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Matched phrase \"127.0.0.1\" at REQUEST_FILENAME./, 1], + debug => [ qr/Matched phrase \"127.0.0.1\" at REQUEST_FILENAME/, 1 ], + -error => [ qr/ModSecurity: Problems loading external resources:/, 1], + }, + match_response => { + status => qr/^404$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" + ), +}, +{ + type => "misc", + comment => "pmfromfile - 404 download", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRemoteRulesFailAction Warn + SecRule REQUEST_FILENAME "\@pmFromFile https://www.modsecurity.org/modsecurity-regression-test-404.txt" "id:'123',phase:2,log,pass,t:none" + + ), + match_log => { + error => [ qr/ModSecurity: Problems loading external resources: Failed to download: \"https:\/\/www.modsecurity.org\/modsecurity-regression-test-404.txt\" error: HTTP response code said error./, 1], + }, + match_response => { + status => qr/^404$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" + ), +}, +{ + type => "misc", + comment => "pmfromfile - bad certificate name", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRemoteRulesFailAction Warn + SecRule REQUEST_FILENAME "\@pmFromFile https://status.modsecurity.org/modsecurity-regression-test.txt" "id:'123',phase:2,log,pass,t:none" + + ), + match_log => { + error => [ qr/ModSecurity: Problems loading external resources: Failed to download: \"https:\/\/status.modsecurity.org\/modsecurity-regression-test.txt\" error: [SSL peer certificate or SSH remote key was not OK.|Couldn't connect to server.]/, 1], + }, + match_response => { + status => qr/^404$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + # Args + "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" + ), +}, + diff --git a/tests/regression/rule/00-basics.t b/tests/regression/rule/00-basics.t new file mode 100644 index 0000000..36a7b09 --- /dev/null +++ b/tests/regression/rule/00-basics.t @@ -0,0 +1,91 @@ +### Tests for basic rule components + +# SecAction +{ + type => "rule", + comment => "SecAction (override default)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 4 + SecAction "nolog,id:500001" + ), + match_log => { + -error => [ qr/500001/, 1 ], + -audit => [ qr/./, 1 ], + debug => [ qr/Warning\. Unconditional match in SecAction\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# SecRule +{ + type => "rule", + comment => "SecRule (no action)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 5 + SecDefaultAction "phase:2,deny,status:403" + SecRule ARGS:test "value" "id:500032" + ), + match_log => { + error => [ qr/500032/, 1 ], + debug => [ qr/Rule [0-9a-f]+: SecRule "ARGS:test" "\@rx value" "phase:2,deny,status:403,id:500032"$/m, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?test=value", + ), +}, +{ + type => "rule", + comment => "SecRule (action)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 5 + SecDefaultAction "phase:2,pass" + SecRule ARGS:test "value" "deny,status:403,id:500033" + ), + match_log => { + error => [ qr/ModSecurity: /, 1 ], + debug => [ qr/Rule [0-9a-f]+: SecRule "ARGS:test" "\@rx value" "phase:2,deny,status:403,id:500033"$/m, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?test=value", + ), +}, +{ + type => "rule", + comment => "SecRule (chain)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 5 + SecDefaultAction "phase:2,log,noauditlog,pass,tag:foo" + SecRule ARGS:test "value" "chain,phase:2,deny,status:403,id:500034" + SecRule &ARGS "\@eq 1" "chain,setenv:tx.foo=bar" + SecRule REQUEST_METHOD "\@streq GET" + ), + match_log => { + error => [ qr/ModSecurity: /, 1 ], + debug => [ qr/Rule [0-9a-f]+: SecRule "ARGS:test" "\@rx value" "phase:2,log,noauditlog,tag:foo,chain,deny,status:403,id:500034"\r?\n.*Rule [0-9a-f]+: SecRule "&ARGS" "\@eq 1" "chain,setenv:tx.foo=bar"\r?\n.*Rule [0-9a-f]+: SecRule "REQUEST_METHOD" "\@streq GET"\r?\n/s, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?test=value", + ), +}, diff --git a/tests/regression/rule/00-inheritance.t b/tests/regression/rule/00-inheritance.t new file mode 100644 index 0000000..c661c28 --- /dev/null +++ b/tests/regression/rule/00-inheritance.t @@ -0,0 +1,4 @@ +### Tests for rule inheritance + +### TODO: +# SecRuleInheritance diff --git a/tests/regression/rule/00-script.t b/tests/regression/rule/00-script.t new file mode 100644 index 0000000..8af6d7b --- /dev/null +++ b/tests/regression/rule/00-script.t @@ -0,0 +1,63 @@ +### Test for SecRuleScript + +# Lua +{ + type => "rule", + comment => "SecRuleScript (lua absolute nomatch)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 1 + SecRuleScript "$ENV{CONF_DIR}/test.lua" "phase:2,deny" + ), + match_log => { + -error => [ qr/Lua script matched\./, 1 ], + debug => [ qr/Test message\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "rule", + comment => "SecRuleScript (lua relative nomatch)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 1 + SecRuleScript "test.lua" "phase:2,deny" + ), + match_log => { + -error => [ qr/Lua script matched\./, 1 ], + debug => [ qr/Test message\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "rule", + comment => "SecRuleScript (lua relative match)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 1 + SecRuleScript "match.lua" "phase:2,deny" + ), + match_log => { + error => [ qr/ModSecurity: Access denied with code 403 \(phase 2\)\. Lua script matched\./, 1 ], + debug => [ qr/Test message\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, diff --git a/tests/regression/rule/10-xml.t b/tests/regression/rule/10-xml.t new file mode 100644 index 0000000..ea9d6ad --- /dev/null +++ b/tests/regression/rule/10-xml.t @@ -0,0 +1,428 @@ +### Test for XML operator rules + +### Validate Scheme +# OK +{ + type => "rule", + comment => "validateSchema (validate ok)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecXmlExternalEntity On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500005, \\ + phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" + SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500006 + SecRule XML "\@validateSchema $ENV{CONF_DIR}/SoapEnvelope.xsd" "id:500007, \\ + phase:3:,deny" + ), + match_log => { + debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*Successfully validated payload against Schema/s, 1 ], + -debug => [ qr/XML parser error|validation failed|Failed to load/, 1 ], + -error => [ qr/XML parser error|validation failed|Failed to load/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "text/xml", + ], + normalize_raw_request_data( + q( + + + + + 12123 + + + + ), + ), + ), +}, +# Failed attribute value +{ + type => "rule", + comment => "validateSchema (validate attribute value failed)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecXmlExternalEntity On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500008, \\ + phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" + SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500009 + SecRule XML "\@validateSchema $ENV{CONF_DIR}/SoapEnvelope.xsd" "id:500010 \\ + phase:2,deny,log,auditlog,id:12345" + ), + match_log => { + debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*'badval' is not a valid value of the local atomic type.*Schema validation failed/s, 1 ], + -debug => [ qr/Successfully validated payload against Schema|\n\r?\n/, 1 ], + audit => [ qr/^Message: Element.*'badval' is not a valid value of the local atomic type\.\nMessage:/m, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "text/xml", + ], + normalize_raw_request_data( + q( + + + + + 12123 + + + + ), + ), + ), +}, +# Failed validation +{ + type => "rule", + comment => "validateSchema (validate failed)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecXmlExternalEntity On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500011, \\ + phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" + SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500012 + SecRule XML "\@validateSchema $ENV{CONF_DIR}/SoapEnvelope.xsd" "id:500013 \\ + phase:2,deny,id:12345" + ), + match_log => { + debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*element is not expected/s, 1 ], + -debug => [ qr/XML parser error|Failed to load/, 1 ], + -error => [ qr/XML parser error|Failed to load/, 1 ], + audit => [ qr/^Message: Element.*This element is not expected.*\nMessage:/m, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "text/xml", + ], + normalize_raw_request_data( + q( + + + + + 12123 + + + + ), + ), + ), +}, +# Bad XML +{ + type => "rule", + comment => "validateSchema (bad XML)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecXmlExternalEntity On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500014, \\ + phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" + SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500015 + SecRule XML "\@validateSchema $ENV{CONF_DIR}/SoapEnvelope.xsd" "id:500016, \\ + phase:2,deny,id:12345" + ), + match_log => { + debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 0\).*XML parser error.*validation failed because content is not well formed/s, 1 ], + -debug => [ qr/Failed to load|Successfully validated/, 1 ], + -error => [ qr/Failed to load|Successfully validated/, 1 ], + audit => [ qr/^Message: .*Failed parsing document.*\nMessage:/m, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "text/xml", + ], + normalize_raw_request_data( + q( + + + + + 12123 + + + + ), + ), + ), +}, +# Bad schema +{ + type => "rule", + comment => "validateSchema (bad schema)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecXmlExternalEntity On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500017 \\ + phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" + SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500018 + SecRule XML "\@validateSchema $ENV{CONF_DIR}/SoapEnvelope-bad.xsd" "id:500019 \\ + phase:2,deny,id:12345" + ), + match_log => { + debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*Failed to parse the XML resource.*Failed to load Schema/s, 1 ], + audit => [ qr/^Message: .*Failed to parse the XML resource.*\nMessage: Rule processing failed/m, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "text/xml", + ], + normalize_raw_request_data( + q( + + + + + 12123 + + + + ), + ), + ), +}, + +# Validate DTD +# OK +{ + type => "rule", + comment => "validateDTD (validate ok)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecXmlExternalEntity On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500020, \\ + phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" + SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500021 + SecRule XML "\@validateDTD $ENV{CONF_DIR}/SoapEnvelope.dtd" "id:500022, \\ + phase:2,deny,id:12345" + ), + match_log => { + debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*Successfully validated payload against DTD/s, 1 ], + -debug => [ qr/XML parser error|validation failed|Failed to load/, 1 ], + -error => [ qr/XML parser error|validation failed|Failed to load/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "text/xml", + ], + normalize_raw_request_data( + q( + + + + + + 12123 + + + + ), + ), + ), +}, +# Failed validation +{ + type => "rule", + comment => "validateDTD (validate failed)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecXmlExternalEntity On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500023, \\ + phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" + SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500024 + SecRule XML "\@validateDTD $ENV{CONF_DIR}/SoapEnvelope.dtd" "id:500025, \\ + phase:2,deny,id:12345" + ), + match_log => { + debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*content does not follow the DTD/s, 1 ], + -debug => [ qr/XML parser error|Failed to load/, 1 ], + -error => [ qr/XML parser error|Failed to load/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "text/xml", + ], + normalize_raw_request_data( + q( + + + + + + 12123 + + + + ), + ), + ), +}, +# Bad XML +{ + type => "rule", + comment => "validateDTD (bad XML)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecXmlExternalEntity On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500026, \\ + phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" + SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500027 + SecRule XML "\@validateDTD $ENV{CONF_DIR}/SoapEnvelope.dtd" "id:500028, \\ + phase:2,deny,id:12345" + ), + match_log => { + debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 0\).*XML parser error.*validation failed because content is not well formed/s, 1 ], + -debug => [ qr/Failed to load|Successfully validated/, 1 ], + -error => [ qr/Failed to load|Successfully validated/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "text/xml", + ], + normalize_raw_request_data( + q( + + + + + + 12123 + + + + ), + ), + ), +}, +# Bad DTD +{ + type => "rule", + comment => "validateDTD (bad DTD)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecXmlExternalEntity On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500029, \\ + phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" + SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500030 + SecRule XML "\@validateDTD $ENV{CONF_DIR}/SoapEnvelope-bad.dtd" "id:500031 \\ + phase:2,deny,id:12345" + ), + match_log => { + debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*Failed to load DTD/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "text/xml", + ], + normalize_raw_request_data( + q( + + + + + + 12123 + + + + ), + ), + ), +}, diff --git a/tests/regression/rule/15-json.t b/tests/regression/rule/15-json.t new file mode 100644 index 0000000..e75f89c --- /dev/null +++ b/tests/regression/rule/15-json.t @@ -0,0 +1,39 @@ +### Test for JSON parser + +### +# OK +{ + type => "rule", + comment => "json parser", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_HEADERS:Content-Type "application/json" \\ + "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS:foo "bar" "id:'200441',phase:3,log" + ), + match_log => { + error => [ qr/ModSecurity: Warning. Pattern match "bar" at ARGS:foo.|ModSecurity: JSON support was not enabled/s, 1 ], + debug => [ qr/Adding JSON argument 'foo' with value 'bar'|JSON support was not enabled/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + ], + normalize_raw_request_data( + q( + { + "foo":"bar", + "mod":"sec" + } + ), + ), + ), +} + diff --git a/tests/regression/rule/20-exceptions.t b/tests/regression/rule/20-exceptions.t new file mode 100644 index 0000000..675a640 --- /dev/null +++ b/tests/regression/rule/20-exceptions.t @@ -0,0 +1,176 @@ +### Tests for rule exceptions + +# SecRuleRemoveById +{ + type => "rule", + comment => "SecRuleRemoveById (single)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:101010" + SecRuleRemoveById 101010 + ), + match_log => { + -error => [ qr/101010/, 1 ], + -audit => [ qr/./, 1 ], + debug => [ qr/Starting phase REQUEST_HEADERS\..*This phase consists of 0 rule.*Starting phase RESPONSE_HEADERS\./s, 1 ], + -debug => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "rule", + comment => "SecRuleRemoveById (multiple)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:101010" + SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:202020" + SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:303030" + SecRuleRemoveById 101010 202020 303030 + ), + match_log => { + -error => [ qr/101010|202020|303030/, 1 ], + -audit => [ qr/./, 1 ], + debug => [ qr/Starting phase REQUEST_HEADERS\..*This phase consists of 0 rule.*Starting phase RESPONSE_HEADERS\./s, 1 ], + -debug => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "rule", + comment => "SecRuleRemoveById (range)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:101010" + SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:202020" + SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:303030" + SecRuleRemoveById 101010-303030 + ), + match_log => { + -error => [ qr/101010|202020|303030/, 1 ], + -audit => [ qr/./, 1 ], + debug => [ qr/Starting phase REQUEST_HEADERS\..*This phase consists of 0 rule.*Starting phase RESPONSE_HEADERS\./s, 1 ], + -debug => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "rule", + comment => "SecRuleRemoveById (multiple + range)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:101010" + SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:202020" + SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:303030" + SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:404040" + SecRuleRemoveById 101010 202020-404040 + ), + match_log => { + -error => [ qr/101010|202020|303030|404040/, 1 ], + -audit => [ qr/./, 1 ], + debug => [ qr/Starting phase REQUEST_HEADERS\..*This phase consists of 0 rule.*Starting phase RESPONSE_HEADERS\./s, 1 ], + -debug => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# SecRuleRemoveByMsg +{ + type => "rule", + comment => "SecRuleRemoveByMsg", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_URI "test" "phase:1,deny,status:500,msg:'testing rule',id:500001" + SecRuleRemoveByMsg "testing rule" + ), + match_log => { + -error => [ qr/500001/, 1 ], + -audit => [ qr/./, 1 ], + debug => [ qr/Starting phase REQUEST_HEADERS\..*This phase consists of 0 rule.*Starting phase RESPONSE_HEADERS\./s, 1 ], + -debug => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, + +# SecRuleUpdateActionById +{ + type => "rule", + comment => "SecRuleUpdateActionById", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_URI "test" "phase:1,deny,status:500,msg:'testing rule',id:500002" + SecRuleUpdateActionById 500002 "pass,nolog" + ), + match_log => { + -error => [ qr/500002/, 1 ], + -audit => [ qr/./, 1 ], + debug => [ qr/id:500002,pass,nolog/, 1 ], + -debug => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + ), +}, +{ + type => "rule", + comment => "SecRuleUpdateActionById (chain)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_URI "test" "phase:1,deny,status:500,msg:'testing rule',chain,id:500003" + SecRule ARGS "bar" + SecRuleUpdateActionById 500003 "pass,nolog" + ), + match_log => { + -error => [ qr/500003/, 1 ], + -audit => [ qr/./, 1 ], + debug => [ qr/id:500003,pass,nolog/, 1 ], + -debug => [ qr/Access denied/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?foo=bar", + ), +}, diff --git a/tests/regression/server_root/conf/SoapEnvelope-bad.dtd b/tests/regression/server_root/conf/SoapEnvelope-bad.dtd new file mode 100644 index 0000000..7d6c19f --- /dev/null +++ b/tests/regression/server_root/conf/SoapEnvelope-bad.dtd @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/regression/server_root/conf/SoapEnvelope-bad.xsd b/tests/regression/server_root/conf/SoapEnvelope-bad.xsd new file mode 100644 index 0000000..2acfd1d --- /dev/null +++ b/tests/regression/server_root/conf/SoapEnvelope-bad.xsd @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Prose in the spec does not specify that attributes are allowed on the Body element + + + + + + + + + + + + + + + + + + + + 'encodingStyle' indicates any canonicalization conventions followed in the contents of the containing element. For example, the value 'http://schemas.xmlsoap.org/soap/encoding/' indicates the pattern described in SOAP specification + + + + + + + + + + + + + + + Fault reporting structure + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/regression/server_root/conf/SoapEnvelope.dtd b/tests/regression/server_root/conf/SoapEnvelope.dtd new file mode 100644 index 0000000..0ad4a8a --- /dev/null +++ b/tests/regression/server_root/conf/SoapEnvelope.dtd @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/regression/server_root/conf/SoapEnvelope.xsd b/tests/regression/server_root/conf/SoapEnvelope.xsd new file mode 100644 index 0000000..2b4a8c0 --- /dev/null +++ b/tests/regression/server_root/conf/SoapEnvelope.xsd @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Prose in the spec does not specify that attributes are allowed on the Body element + + + + + + + + + + + + + + + + + + + + 'encodingStyle' indicates any canonicalization conventions followed in the contents of the containing element. For example, the value 'http://schemas.xmlsoap.org/soap/encoding/' indicates the pattern described in SOAP specification + + + + + + + + + + + + + + + Fault reporting structure + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/regression/server_root/conf/httpd.conf.in b/tests/regression/server_root/conf/httpd.conf.in new file mode 100644 index 0000000..4cd534e --- /dev/null +++ b/tests/regression/server_root/conf/httpd.conf.in @@ -0,0 +1,28 @@ +### Base configuration for starting Apache httpd + +# TODO: Need to have these configurable +LoadModule security3_module @MSC_BASE_DIR@/src/.libs/mod_security3.so + + +LoadModule unixd_module @APXS_LIBEXECDIR@/mod_unixd.so + +LoadModule access_compat_module @APXS_LIBEXECDIR@/mod_access_compat.so +LoadModule authn_core_module @APXS_LIBEXECDIR@/mod_authn_core.so +LoadModule authz_core_module @APXS_LIBEXECDIR@/mod_authz_core.so + +ServerName localhost +CoreDumpDirectory @MSC_REGRESSION_SERVERROOT_DIR@/tmp +LogLevel debug +ErrorLog @MSC_REGRESSION_LOGS_DIR@/error.log + + +# File locations +PidFile @MSC_REGRESSION_LOGS_DIR@/httpd.pid +ScoreBoardFile @MSC_REGRESSION_LOGS_DIR@/httpd.scoreboard + +DocumentRoot @MSC_REGRESSION_DOCROOT_DIR@ + + Options +Indexes +FollowSymLinks + + + diff --git a/tests/regression/server_root/conf/match.lua b/tests/regression/server_root/conf/match.lua new file mode 100644 index 0000000..fafd39b --- /dev/null +++ b/tests/regression/server_root/conf/match.lua @@ -0,0 +1,14 @@ +-- Test matching Lua Script to just print debug messages +function main() + m.log(1, "Test message."); + m.log(2, "Test message."); + m.log(3, "Test message."); + m.log(4, "Test message."); + m.log(5, "Test message."); + m.log(6, "Test message."); + m.log(7, "Test message."); + m.log(8, "Test message."); + m.log(9, "Test message."); + + return "Lua script matched."; +end diff --git a/tests/regression/server_root/conf/ssdeep.txt b/tests/regression/server_root/conf/ssdeep.txt new file mode 100644 index 0000000..7ba5dc5 --- /dev/null +++ b/tests/regression/server_root/conf/ssdeep.txt @@ -0,0 +1,4 @@ +ssdeep,1.1--blocksize:hash:hash,filename +96:MbQ1L0LDX8GPI8ov3D2D9zd6/gz2wZhFvV0O598La8Kqvfi0znNa8Xi5SM7XRWCK:KvL8Gg8rWIz2ZKqvfjzQ55RpRHjftQ++,"modsecurity.conf-recommended" +192:b8B5UQvywcMIJuavpde/Yyz/U/vF+vGCoCvrQr/dw:afcnrvp8zqUvGrzr6,"README_WINDOWS.TXT" +96:+qK8Z4gA165/hquKNMi68zuEyMM9qNB26x:+RG4z6c1LyZOB26x,"README.TXT" diff --git a/tests/regression/server_root/conf/test.lua b/tests/regression/server_root/conf/test.lua new file mode 100644 index 0000000..1cff076 --- /dev/null +++ b/tests/regression/server_root/conf/test.lua @@ -0,0 +1,14 @@ +-- Test Lua Script to just print debug messages +function main() + m.log(1, "Test message."); + m.log(2, "Test message."); + m.log(3, "Test message."); + m.log(4, "Test message."); + m.log(5, "Test message."); + m.log(6, "Test message."); + m.log(7, "Test message."); + m.log(8, "Test message."); + m.log(9, "Test message."); + + return nil; +end diff --git a/tests/regression/server_root/data/.empty b/tests/regression/server_root/data/.empty new file mode 100644 index 0000000..e69de29 diff --git a/tests/regression/server_root/data/ip.dir b/tests/regression/server_root/data/ip.dir new file mode 100644 index 0000000..e69de29 diff --git a/tests/regression/server_root/htdocs/8k.txt b/tests/regression/server_root/htdocs/8k.txt new file mode 100644 index 0000000000000000000000000000000000000000..6d17cf9d15fb9f4a2358a2d079f3b8c755d005fa GIT binary patch literal 8192 zcmeIu0Sy2E0K%a6Pi+o2h(KY$fB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM GyblZ@00031 literal 0 HcmV?d00001 diff --git a/tests/regression/server_root/htdocs/index.html b/tests/regression/server_root/htdocs/index.html new file mode 100644 index 0000000..16c51c1 --- /dev/null +++ b/tests/regression/server_root/htdocs/index.html @@ -0,0 +1 @@ +INDEX diff --git a/tests/regression/server_root/htdocs/test.pdf b/tests/regression/server_root/htdocs/test.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a6ec2d04c7ff2428885770f29ac8164264ab53c7 GIT binary patch literal 15406 zcmaib1yr2PmM!k?PUBAF?rtGSaF@p2-5r8^2u^T!2=1;ygL|;xP9FUC&b66&^DXGA zI(4MZsa>^N)}~UDkYZwG=0Kq8EA0E;*VLDTzy@Fe*c)3R2nYZbKz8OX76A4)i3(84 z(#8ek1eCHdasf$zOzcfTLP7}6E>0jLTLh0RL4^r~UKXUz13D)hz9bu4Zd&N#t4&*_ z-+=Y!R`|2+<5g1@hz)xTD?I<-l48p?^}i(e_X4m2*f_aZIQ})=Qy*_# z-GSxbZSJZbF=dG;Qzu7jNKUpoEXYj&8u-@2ioImY6y>y;z zWq6wHXJnXXua5JEuSwW&gnY6fs->OgCyK#2Fm;5>OGE2zTR(+A+cIeS1tuIBe+~OG z6S$?*)q2GZH_{av&m+XbvCy^0c}pZl04b*B8=PlIs?o@gOm@J@GMFz~vc$qp9eRnW z>)#GKP*^7(%pJ&K&c9_m$2z@h$#HyJIo$e zh)=R!ssqSk=)m?*FR4Y z_K=#N8NsM;6-^~*ZT>D^qwu@zz4KTHuck8aI%3L{^EgUVFP>v?{TC5gr?6 zQDaGJ4d*<9&6-{B%`4}sye~daw!~aaoGluu1ktE;T3#b9T&018{Wg;B zD2xhpX;JBm(i6)g!;YC>zg!?Hw{~RPbmgI6{NV8+)*~E)Bh@R65Y!^W{l-9l@0S_c zHUghxLO@6OjL9J_^FWaTR}%8TA`rtzAFs=R}uaLMsH_XmV z^$d-4H!{EOx}%Y_lsManY^Bf}h)+c}pelvp`~qHsgpGDZ9_kLmx6232Rholsu4Ys= zJD^Jz`#<&!KBsuqn>j?#nGKJ7Gm5R-I2{nqn9=!)DhrHn~g9(+#tPzS8*LTAE= zR^?<-!C~wB?$!X090XGZZMlJR>SL+WiLSuOr2E@MD}LKl?<$ZlWHUH2rB z6J?*t%X?Lv@lnM)9;?EJci0F^5JBaheG-BM;R1(fs0(tECY@%9Lw#&zS*|JD)`Wiv zSnpmZY~;@z6^RnLj!}&TI}l#jlaY|2p)yfQh!%-~u~`o0sRUgH@pJ@hy@K#8xfPXX zJ?(0N+J=0x=jMV&9OOzW4a_yj-^fQ5g@n@yJYX3YLQ;39=tQo1xo3SQdL{D%?}`?t z#x)5Z8(=j4l@4Qu&))qLZt?3l{J!hH??V4=UHg(_%b7`z@dg%If=n_-T2G_cca7(o zUCC1UHn9~7@1wc7gQ!W@EN^d^iQP#bqy4Ik1GCrom2?8g*RR9TKHWzX-FtpDJ+NC| zSd15R*Ax+JUHRW%vDr0|aO}I?YG6<;Dp7z#3|rd4z;J6w-7mk*T7p_&QF|K^<92{s z#}~-=>d$IkYmnC7X#Rx4(2IA!SzG)fH+s&ugtxoc4EvD~7fwtO(v1gE8Eg$x*_p=q zT42)Qbd{+GZD5fF$BB$$*{bXDE0S*NMBND6b6%LA1fB?%82(XpegBnc%U^N%Hg z(fa5eWRDUy=-iR819y=*X?RrFiO7jeiQbfP^6P0n^eHkrMS`=|h1IgEY%~((qnJVY}0=h5*x2p zLaxXB$S!PJZC?FRFxsmBZp*#@jC$d+lP|>pu z90TY}{azn@+}i5MGx09-)A5bp_&&H2pL4d<$YGtSc806Ku&&=1(XevW1tan(%8DAuIli6`Gfw-LOZ zOFed)YJ3g|5(gSpFxE=={E_k&B>d?$wDYM+0dexCd*loKn))X#RPAs%C7LVpGVqOi zLEY0s>Q7;@fyjRNDJ*xn)+#$KsN1gu5jjYiy*f&)>S7J!FzaqTehC9h035fTia+2N ze~Qs=zFc$_5T`>;!hXG{Q=?sW#)w()nP)^bR9;pQ>Y#Sdb16_BLcqx(?cJ^Z=GHnJmIct9!Jv% z@3!Pe4NYf2Z)v+Sfn&@n?zQYYs zJBB68PeS#nyjj|VQbyrkuzJ~EJfI!~j&y>c;U^?7CHk->_So@lSja$;wZt(i+{OFy zavU}FUnA zuDNV#9rt^_;0O%=Fwv1+dNXT=?+snw136NawMCbwUwOjXoGF}hVT<|Hgzn|$-szQ2 zCFSX10Bd55v({ZUh~Nm$;uObV$61FPm+}E z7b!%}Ro--jXVUNzTrEy76fD~VKwjjj$J^Eve8UT|Z^XDc`-+fDLQ6RWv_BXl`o*f4 zFH+l1G2)VxdbEu50yE1dw92;Fv5MmM4Ant6Lj!tn_9w7amzp#DfMM>d=p12huWxVP z>Eq15*ift!Z|l4ogt4J7xJYf7ZVwhnBdHiaFUOz0-ZpVs2=UrK3A+8}aEIW)xyhO_ zGd)R=fkI*u!$^#9au?N_Ae*NrIf%haHUIkbt&okyP&>F4Bx`$zAatsvS^3AbxO5(W zHrni<9o|}Qp#?V;7f1^<{MZ-{9VYpp;Jmso=#7QAk}6uWQg5E|Ri~-g`=bLr=eo&3 z!>yq7vRjky$A(gG^%vDr6YtL>pk|kDau~Bz%Zg@cXyLD2H?|QieAd{B*sm+$wKvPBWbwOHdq6i{q11 zHRmdi6lGXWB66Hgu8YB0AE6Wx=M2&04gKX4`MIvea$!)+$M?Ta>{og))N|UGYpL5* z);DRMqi@*yNIb_Yk8TP$rw)`fuI%Cz3|gfToDBC`aIzg^u@eu+_q}`q@K-7p2X1L^ zdC-cf#vFUIwKDOKZF7Ar`UYuPh@a88&wM3XKkO6Z*h3=d8*+S1d6IK#V<8+hUV0=8 zq9V)rWJUk;4CRiFsUnyIW|ua%0xG@IA9X=i{g<1`DdGyQoV)|guYUa7=b&ewc0oaQ z{_^j-_LYpl;IE8pc1d!SD&Ap-ejhs7`q-x3ex`4z_?)#SevYkxZPUdrFMQCx_?2<6 zO2ny_leK0^=fua*k}i{y%IX5GFE)8$r8Kp%N#c(FSS{k{np>ixyjFRpTj*$YIaE$l zCOPu!=ojXX3Y{RG^7itALVBgoycv1ZSwaI=5Jz10d98*iACxT?s_JwQCmvL7w7bo# z8GrxKNM|5q)u@5X6Nr;RcrGrs#^!|y_Yn(BLa0!dz=p*FGDt+x?Joz+MOUEY46Ob- zy_M?7cRUYCll5Y~wCb2!$J9?~ZvU01CsFwI>#A`-#^eQWggm0g+ELqzOV^)*xt#5| zpTFstEb$Oc_cQz1BInZWF%HseKBY2Dup7lQz%BZ&xq9dzQLeTkQ-1BVQ1;YPcT3G4 z=8jG~Cnw)TKx_Lz{$Xm@TJ*3!Wfp=)AM-)wWu=6b;)60qx>i1FGM%QpiR@9Tn%AGY zjLfl<44BVU8U|TmuG_1VH@a&L7g5~>>{B+|NYvp40E2cM?sZp?qvTl3kzCHe*09ni zdsE=kV3cm9AcCI~A~PlQ1nt(z)v?HA2&6lIek}Zu=n#H*x^n|xI+!exp_=sQD7}Tg zUf;nwhEOxVCCTr-UG;!LHudHCP&4AgQb2JwyAGimZ&?gHeCj=VU1=dc-#`e_*b5`; ze3)D!G>2=-iF%6M*8(>@2)nMp`90>c(sY(hb;vU=31rN$;4pTWTBBK^2e2PgP3fz? znY4S1yR24|uEi!td|m3(C`%m5352E_ktQvS@;P+1tCtK6IG}O(%+Xdu_32Q_Xk1+E zAIzOx#SMPn@_NjZF-p;7rJJX^r}k+3Xf^%! zQD@Z^irb;F4f755QUp+u*)^ldAdW85U9ReqU}W~qhpnF?UI{%}R~0i^)*}Ney3A=K zRG)dL%o3!&C?3?5r;vXDpXfJxHrZuhrVnK~VNBfD|B_^qZkz_qRv$H?_%+c}HoW!> zbj-gXtfO^|^#xx^inB!r^M`_k<^+~L)-c`h#D$ir=02}dKs#bS^bRzmhLTD*7E$oi zXSQ)-JFXTozTm;dtk|zb%$_L5E2g7S{QS=YHU1yIvE&vjuN|pQ`?bZc4A7k(x#%^K zCP9(s_8K+w&yOlJYI{0oPJL}seF{c_ob~g#bumY>{+J^;v4d4)XX}Km&12gpVI|v~ zSyQNFCYH5eBc_tJ6oS5lePQ?8oazNRIb`=p!yb`uj7_-ww_aziKabJf-MZgy1!m?Up!?pvqsW#y zSWLef)~oI|4lhC%YMB7mu3ySRMY;Tr41~^g;mpT+B4M80rzCto{+v>PH+|nZtLbl( zqx)J8zH~8I)x&2JMd`;Zcnc1ZZx{92oZ|)5kXi7D^DJ=$7GOw6Sn*F+LVzzQ5ne}b z1WVUwXLa?Se;5FrW*>pDu#CZ93VkA!HWT(h%Z0iF3Yu5$y0tbG=0|NW1Lo4JVHJ-y zjX$i~s*5dE9rkUj@796L+N#-AKT4N|%>A5(Qq|V2H1g~{&$->txEY)-E_LkMe{Q(! zNqqL7Yx1t+w1k1PvjF|#XeSh?N75U#fs;u7Cb$=4_d;H=!}xm9CcXQSPV=iyGv+B{ z7yPN(S!MF>QJqzT>y9gVt7W4{V_~FqU1b&g{OM#8-E1LIU1k47RLW>d%7hv+Cj)S` zkVt8rwoBA8SYvfKy^&QC>P?9v{WA<=j4ZTMBCv5D!k2~PIB8us>>=SWWI0r4xy>tjXQ#=`tG;Tr@9No&Y zsAh&2$Hl)zx@{P5)8rs6{S;T|iPP$|>4Yc-DpeS5HD_7aj(u3~2po{P*U;BYt#_;A z_>G(eyw5%r-(GXUbx;3Nyqh~bVTOUd?#sz}oVy$ypl;_WDDzV0q(~UUVv{m%bmWp! zDZO^46$JK$xZo2+uY}m%4j6aAtBuj~=xXK7XEab}nFSY0saTVT!CnPLii+JX<>!-c zhp{@8*t*n*PAe4$dj#;W(Zal5OVBY+aHsI`mKH6mTE5bw;K=KWOV%ZUQ$blO9!J!0op(BqC^$1?!Z#F_uZYz2Sir&94V5A~rke3)h$8K@Ih-H6CRPtzVsv`=Q6U=5jw8f4Fn52l87x z3#`3j>+M3NW;`OQGI*ij&m2o9@ZI?3>hGQR>LID7H4FRtPddI_RFr1jITylS{yx+F znQx`+UfMQKir`(x5cTyB<+;YT|p_$xnCwRID~Hud#No3{h)! z4@n_U@+LmlD7`6aW$IBA0tntVW^r>x?q8EWU$GAysB18jgc%Gkbt2dq2-MYv8IfzE zleW>*%7fJX^M*Byl6Qw!*{jBXjjr3=i&bIOJ4JqCR*k%2plZ+1q2w;VwXh%e4&XKQ zVB6u`|1hDmQa`@2ghcF7tibSNw5@2Zj+STF?!kFB$6``Ojhs1)04j?Se$-8a*Zqe)Su?EZ#|Ws~%5Aeurv*^e^#ihN{( zv>)UoXTRnvd`c{$`EGXj+{XOE`~t3&BU8=YN#C1aIv+Wr&=s*EEJ4XwIsG76B)5kx zW#jTqz6dx{rz_P(jVN8`209%xuAekBiyd=_v=JLj9=;t}PpH+_5`mGArvlM{FqR7} zz0g-aWOLVL2UJlq#V=$I=5yE*qzz`v_F0Bt^1`uK_WbO&f3S<-v!1jq(D9=&EhcNR z7~vu<=SQx>mA}Kqw2wRNS+$fQcO3m_Z&p?wY01$bOeUIc)Z{uHAR)v-NhGO~u3&jr z4x!&gAR^@^nPxC*j**D}keFbPDTmNmxGC?XjZir9;$<5v94>!X&fCV>i@X+umq504 z*KA{)t^f)}IfJ}bWawC}lU?ui_2stjoUxwybDa(Q=Q^#p^R~d5dq=u|C2L*sxY``2 zD_Q1RKc)3aFJ|KzGqv;H;zN4@p9Asm49s1wofqn_9_u)A(i$h}51?B6`FIv;g~gWq z&g#rUldyB%*j7K==f)$ZaLQtlW9Pl6udhJ^RTvxlK@Otx+-It+3rr;3${~A-YqVUe=**B#b@CuFu|;t{}4Zi=$FWGv605igZX*4LTs;1QqR#S zaC{Otgn~&4%`3ULWZ95*MZ@W&F3zwz2%%j+VO0ynSswpsY`Vt?m!t{#D~PaBv4JV< zXpKN3DYUuUA<3{=Lwl?)q&*^N`3e#K_Fm`PGbi>zHuJ{eI0sGz-H^ZMFI>#7S6R!7 zPA9$f%{2x)3kG{oCl5QH-38CrAE{9M5;glH<)nNGO}cq4{@-8D_K~KGiDRcta;F$g zC4sP=xg7LYcC&tJPx6%>?QY)t6D!_qx5ox7o$+^>>Si*Fp#2Q3Qj(3VClLEiqAa77 zX`?KYRO63q+lkti+1Yso6bq;kg+?a67#D5gVul%L7vnX#_T0n(VrMs^M$(3yhI%(^ zt`nWR9j>cp%&1 zOc?8G$6!D!qgXg+U4&r`9)VtDjn0deYVTs2A>tE*n6^2V?S0akF=KGPaxcXE<>4V? z!qGg1a}worTVPU`ytdXRNeZz<}da5s<|78QLJD^Bg7-;#H zt^qk26y!tAprgL&CK!gmO3z4#ge8S28H$M!DH)|xc+Znn{q1)WIJ8`S#?{%?L=r!| z$*yVR=})@#!#^FW-c>j!B*sMkg+c8E!k%uF1;Q1(f)M`Ry`vB9}3*lwsRq?KiMb zkzLfi1M7MV@FOAD0Ve^^LZ`k*vemi_GjbVK`OMrViA91&scUw9AJdCeD;N!Hzo4ea zeZhBb>&y7OR_TDoOh-RoBrl?)y%h27cN@ji=5PK1AA!i|pQ@UKO6>@>ib+)+dIdcw zb)`f{XH~hLACPkve_XT{B<}|@CN&z9BPUgsFfe6ueRU)MkeIr|J1D0~w;k8@d{zr% z$2c|HC(C4d?wY-w!#}@}9sOhBYp{uZ?Ir=Axu}_Xr)tjmxKoCb=(k`2v~5z@0l+xe zI4a97T}VeA(PY$h&9F3HJ_R8W`pur#bNooF=}YSdPFw1}f?|_!E7MPXRijB}15oYm z6T~;vkcH^`4TaXXkNTUgZ-h3tKTUvw@3gRbU;uFwJ5F7q&8SXD{m# zw=TCCHe6pujkTahAUR(Luz9>y)|yK=31RDW>f7rJmNA!M1C`3?Ipj5#9rCnP45t{C z7R}Sy32)$JsSrYltx{N!{LNKcTa3j*b<|pPu+=`4pp;FP^p}lPc%s_g7hAeE9+1v; zqsKMWRbwUIMNZU$_i(r9=ved{SvamCR0Nz!XLC<$h=wr8UJ9t5M_y zByxof(24O2L}fuHuasfv6jz~KqeB$!eyZI@DU#5VGM~$(ipXIZJHzkBPC0)@4wZgoX#EPO}8At5O15cR(5A1;l>b(3EE+>B0far>L~Jld0}9fg&+32l^E2`RKPaF^(jON5Z1 z@SXrmlmZMsFiIR*DGFS17BC-92Mx<9KD_~}Nl}UVN*en~;O!fi1@SR7g0BUUbAN{M z(DxZ6{aWPWHM&PFYz43Jf_#Xt@xe$i8W#ERfGB9HrNY`{FXoat1!W- z53qCUfus^J!a4o=u;Z6z_4%+6btR1MTqiHq;YN6o2DVXxz$l$ll$Yl6AXmWxqql@G zKpf?s@@2$dG4WwRWCgE0={e5J_?5(1v^jrZX(TQ|GIG!Fh{trk_LOS1|1!oJ+6w-uUI6chT8K<%$(Us`5 zv5Ms2lJMB^2}k#JAUr86yLZAg!^!Zz#BeISP>{u}$Wvj4m*;rlbAK%E_{7%Z$r^lMt4{uX0j4A? zsKI^%Vr}T?=Xa3fCt+VK!Xgz!vXIM4BE=mxk&zUD=vIx}trzRgn)PzkOI>HJ*9 zd6mvw?s@(fPWJ~sF$QZYR=nT!r1$R+P7~AmBG9IA<+7Svh01)_+oVfF9ulOERvvhD zjxoA&WC?(f4V9_ zd=7Vz7qCl}Hby3Dl+!0QUF+ajK%TJlkclV|1QSngXEhoq;8?h%IL&1Wc4>gq^%+0G zc40^I_0Y11XcOY=O9vD@Toi`iuF?M|JPIb?|2m-(Bg$_+bB4eFZeo60+I=W*W?# zFsNCC=^`7%;T8W#U803b$NRz)^NXaDu|FBg_NDk$|R)n~>c1 zw@VYOF0aW2tv8t!Y~VF6F7tXHxx-E7AnZ#oYx#Nj6J$4sLzi;@PUR$zAt}@iIm{l_ z!K=MOG2+_(x*B@FAzt9Fxzu{^`I=yAHf72mLY#K5;-Qa}MY`wt@fU8zp7CWA+%Y)@ zB2z?Rz0lM0!()gHMnSePA1|A-H+{R$;0 zN2NGOnx%s}wdiw9f|E}P^j|N}5Aw~5`-W(Hc%|;LE~Gx&=&$Zv?@i`k7d@qe+%k88@vR4DJ0CMWe}3R_ZZRpWnfg*X|84JU$7wSZ?o+AE<=F%71sdzi6UG&nn zP^`Orb268}Nz&bGm1b{|YB69ee0$S|U%jC&+_*m@L)B2=O*a@F)~k_5gqJVOxP>YRGea$!tQj zAl+Iq?&+L%U};K;zV<)kYt^&>5Pb|W&J$u5=%os9>GP`MOy9h0ZQ|6G_wR~BjJnS;OF_U(!Wj1)ZZ18Sp)KMep25W-#djJhg{A4_z{Mhk4hDp4@{r(PI1u)A zM9ReI5XanvN?`yNF6)|l9Ed!~7-7A`#B`0-(_XeT1$v+hW|ZE(hm23kVvIK6sc>{P zEksr4z*s?#gDJ>M4?hmacS8#EhHZ5ST1Ag>_RaI~Fr=J6*l9_Dw?nhqE)cw!Zc^Sf zvWPi`zmW;f)yL;RZ8wP$eavMYiQ>y(APZ!hr}mVqiO15 zh<4o@S|t}EwY_yprfC{sfn8(4D5eRGZX1VWOOts>`dRcGPitD8yj9B=92Iv4PobFu z4^~Th_#Pm?X#{nmYe+=7ICK-#BJGVS!a+bIs3Tr$LCZ=|*fh(BMI;*{I%T9kZkS-A zYg!T)O*1Ic*b&^LiT2oQ5$Rx`b&S7vgL#4|H4+gC$1Spe-d4uV!6AVo0L8U}NrFH# zXt+Idh=z6wM+4IuCnvpgcS z8xlh);y2C;?h)}`noLBn_yru*G2&2xcn@o5oxL}0DqK?IBMfGw#RtcWSYm#kz94|F zA>Tl78 z?xkwu#l&j#e zW1+qC5S~e}R5!tzlULBQiV`ql-xDwwJa*u@p}dWhBgm+%FyWRRWyY@To1EiF?jJ2@_1K3@o!#ed9EEr=pm4QGT}^Jc>z{$}BE~7#!_fN$%uHgqeRygs zxSp_^o0`&=GKMmQ*0NbEds#`@NZrmne0q5~c?NnwC4KdF8IIZ`!;95`@b9)MS1A>G zoTUOkIPNiYuP?H8h{GSY@zncfOc04gg>qrjmYbHt{e7Q<`ERXHjBd4VdAgNv@t&57 za2e9{D|EeTosI?&+OCcFW}{Q&f$Gd1nyvrq;KBcLU^J+h^xy(e)TO!Fj(ZKyQx4W}N5OpP)(y>^cW9y|FHTDImGt2?LU~!NU@h~cgBCH~arADZ=;Q>>yBVBdk+DK^NORxYj z+miyNu9U(PiW3~u;Lhe6cXGj9?`3|ASEgZSvOm=GU&y*hM=6B?$>*V24gMcWHcESoHjC#EOClfh~N*5H@E0^0Z^gUAL> zZcfm?9(T*PtQ^Xm0 zS{IUIG2oNj63;j;jR7d&6Jj@b4d9Mv`0G3k*;g1I(PR4n7l=&&T`j*zY-slnJGZgI zF$gIxhkjDpa_`JjSY_CyR|;NF(9>kvE`o7Hd@+1$*mRhM^%sYb6N-3O?qt*Y1=Q?IL{ zDwHQsqCj7qLX*5*XsYcb=A_~zMZQl-l^9T{Hp>(#H%Nt<7>vRDAcr$o8ACsmQlUOV zC!Y)sDp#>d*bctot`poXM~b%s&j-TWqdcfkD7aIj{fT(FWqXg4LF08ku67%K`2x-d6z8xh%YgOCKqod1&8ujbMb&^kGEjZuU zyS9Ew?KyYYxS5BEc`P{Jx17|#*XL%eTvD%`qTIdGtbF*clzNQ{#L>(v`?_nYXs4C( z?UilcQBx7O5Eg75+%Vy9a3&Uk5*ZgzwC;jz+z4Db&&3H~24lT`M1g0G*AC%Q@egQ1 zk7-~>T+z~eHc8^#W4b9PJ8=Ui0y8u^8OnWOm76e$n|q`zq_Omk7V(c?z@$g)wh_jF z;+}J8#{cQSUIuvx5dje~Gj>9KjRF>fQVI<~_{|d#v4*rr?S^?p+(KePVj>9cTDl%o zS7-P%T{PBkqb&v9lsgzVeNB>i_RW&`194!3+OIf`(ylH!-#L0$a6k+q7Z+z|(zk_x zI{g=kj>1!FEmFodm*@-);iC=&I(%Lrxt7{YxkXHhg$BGr7?0CGqp`968if530~a7 zJ0XcIs{+SN7n}lBv@`4cC&v+42f+xGYKzgVK7tnb)+N4;bfpX5Sm9S@Hq_-KG-}gg z1;T`+eM?R83=Imx2~g7Qu_4w7=A>$B*P_q|oLHgW%#kO?E^nBHX7s2(h}BsFrk;DW z=!=DWpeZajA<#qXi{%(9v8u;TA!vo2DmZK>n+`_Uk{3x#5rQc=r%vDuzs6@sQHQia zoT21Vq;k=z+P5Cmg*lp(dMq%7#x>1S;X%RS?vbjgzDQEX=&WdDfimA~3LaWCNK${F z*XD&#E#Ne)@++OZBDotcJi897C+5LzbTq8YM1d-CdnQ0SGyYbFTMn4UoSG`%p`w^c z!zs%S;>%D^rB)rmV#`n<#~i`KPXs0A2s_Rhz$I85ePoW7q0etgC8HDX(htXDH0gFv z#J6Fe`|d}l_$yP{d3Dw_rQK65Q#-A~jhhBkX<#87b74!YB*$iu-CI&gjFkLo&4N<6 z$rH;+*Jr0vD$&4sr;?bsg_n0NI@7ylcj(xNIBJZWX5h96Cb_X=QmI%c@yuYsBcV`7 z=HO*8$f&2f4(~G2owM5hqfq8z0waYCeRTnEYu2;EfwCDPrlpm@sD$joj31W4gpIYf z^skE_pNXAKhX*ABk$xn`#1o5hiG|tR!%^vc80l89{qQ;5mMip$hn>F=O zT%>#(Htv@vE&!UZLy`Q40(d#aju#w;&=ANJkeO32dd2=XnA(p(v>l3`jq964Qu)&+ z?ohC{B=2Tag2Wv`BKMYa?%wK>efPU-ltckMxz|AwD}~!-!q&c?^=OW}#Qofk3zlED zR@TBl3E?;uVIX^`y=!WBhQ-?nGI7Tnz3r;P^lj;k<*>V z?U@;rk_F=C4jYp;8}wD?RiRbz)g3N)%a;6?Qjt^e)g0RsT1`yF?&8vfNnQlr=LBP8 z4n-k2>Ilf+bFI>A$fq}4^N7XzNHi|MQLq}wWXK9wv;zO&Yqfw4@MmO^kgD)&lmJ+; z*m#14F|RL3TW>#2hkt=0hSYH@EUc^WLbx;(&Z*uQKlot%@V%*X9PGhVuQgx4FVX%Z zY4CM1m_7J2`eqkczYV^Q6fEgpWfMj0wjI(*l7WwGJms0<^oDhn(G`5V=Tm51veWFPK-l6^jT zI)H#u_I553AZHUNO9vPGH#XDTP|?U11QZi}e;7Vl+Jc-FLGCK{wnlbJsvkZfyc3?x zodFzgoi|-EF?$akCQdFM024b47l4h0jT^wq!osBolyxz(u{062Gq(W&SP+1s&L$u` z7XS}0D+2I6-ur-wjg<=lC~o8+1F|%?aQVv+0jTN%vQ-E0yxF`5cwbBi-#J-qJly{WBTFw~!Y%}W6?!KCJ$7VF&c`Pl$(q5(9vS^w*o2zv&`605 zx82(e)cs~V3%%RE?p);{^J{;W_9#)K+!HYtMFk$QUY`+mrH>X{mYtr`{SLuh$Tf^| zeYL7SxKufqSw8Xm!}`gI9JhI%w+CP(8r!gl;Ap?V&UcM@(5DA#R9UJl3IXpqt9Vkt z9h35Kg{!x@NT+%IlE%XTc^c`MgCx`%y#9!1N%6CY+#tR7x*aw+)$7fyfc9gczuSVm zDiBmL%rJ$2`HJU(`}U?qTId7dt4^RZ!#-~Nmf4~M9#L)d`^P%ynxg?Y*hit z14b)~&*wjF+mdHAC>jVEetlQ&i#;2kU~2>*hfw^09}^hvP--V^=zdDefKf6MI+n187Nm7MHNR6#B} zz&Dm4@Ds?xMepC-{?qEO(kL03gMdm#PVZ3Xcy|C)0Xf^dI+=i+0j%#KRDd5qrj|zU zN9a8O7b_cpo0m-wDD7nL>hMN4RDCP6k)5-{y9*Ofpt$PW$#JtZ0jWrf0c8O$Z(ATF z1QdT;;9CS|z&lUzU(d*U$oKRA=I9^GaYp!WYe4wtBHj$%Hi4HDz{A1zURZfcQ)eB( zUn_kdirc%s*WzCb`7hkazha0Ry`6e{^S7+c-;(?*X$0U$2auhpiHoJZozCBU0X4O> z0d}r7Hh+6zdGoDcWM@tbvSU(HrTe#%{uf{IEeY{AX64%zB_Jg!CB?$R$<4yT&GvTF z@V>PiZ|xg+xY>jd-uKAV)dcikdK_=MZ>oRIb4jwWypOrK{{H`F%f4?oUBYN+%zmKG<5$`8xJRt z83GI74d!p2{{8^CI62ul0cL=|$yhnqdEaj7_XlA2ciCH?^$n2!A!B2EtL4ARSUEV^ z{|7ye|E9;z$@+GC{<|$ZC;Qte`41WQ+sgh!#=*k-wqyUU$HC6>pK;&v;P~%)T}t+EryGe(c9(j^bSx}OE1t{semf>_HPLL3&{vTSvxa(0PA05+qZ!PKnEbeDE!6hmx z!Og=hDkUx|Df+hMtP)&2;-V}P;#?A}JW}s~bapXva`|gZSvh#w*bu0wBtJ?a{6Avz B`Q`us literal 0 HcmV?d00001 diff --git a/tests/regression/server_root/htdocs/test.txt b/tests/regression/server_root/htdocs/test.txt new file mode 100644 index 0000000..2a02d41 --- /dev/null +++ b/tests/regression/server_root/htdocs/test.txt @@ -0,0 +1 @@ +TEST diff --git a/tests/regression/server_root/htdocs/test2.txt b/tests/regression/server_root/htdocs/test2.txt new file mode 100644 index 0000000..55d8fa4 --- /dev/null +++ b/tests/regression/server_root/htdocs/test2.txt @@ -0,0 +1 @@ +TEST 2 diff --git a/tests/regression/server_root/logs/audit/.empty b/tests/regression/server_root/logs/audit/.empty new file mode 100644 index 0000000..e69de29 diff --git a/tests/regression/server_root/logs/subdir/.empty b/tests/regression/server_root/logs/subdir/.empty new file mode 100644 index 0000000..e69de29 diff --git a/tests/regression/server_root/tmp/.empty b/tests/regression/server_root/tmp/.empty new file mode 100644 index 0000000..e69de29 diff --git a/tests/regression/server_root/upload/.empty b/tests/regression/server_root/upload/.empty new file mode 100644 index 0000000..e69de29 diff --git a/tests/regression/target/00-targets.t b/tests/regression/target/00-targets.t new file mode 100644 index 0000000..d00b5a5 --- /dev/null +++ b/tests/regression/target/00-targets.t @@ -0,0 +1,685 @@ +### Test basic targets + +# ARGS +{ + type => "target", + comment => "ARGS (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule ARGS "val1" "phase:2,log,pass,id:500189" + SecRule ARGS "val2" "phase:2,log,pass,id:500190" + ), + match_log => { + error => [ qr/Pattern match "val1" at ARGS.*Pattern match "val2" at ARGS/s, 1 ], + debug => [ qr/Adding request argument \(QUERY_STRING\): name "arg1", value "val1".*Adding request argument \(QUERY_STRING\): name "arg2", value "val2"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2", + ), +}, +{ + type => "target", + comment => "ARGS (post)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule ARGS "val1" "phase:2,log,pass,id:500191" + SecRule ARGS "val2" "phase:2,log,pass,id:500192" + ), + match_log => { + error => [ qr/Pattern match "val1" at ARGS.*Pattern match "val2" at ARGS/s, 1 ], + debug => [ qr/Adding request argument \(BODY\): name "arg1", value "val1".*Adding request argument \(BODY\): name "arg2", value "val2"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, + +# ARGS_COMBINED_SIZE +{ + type => "target", + comment => "ARGS_COMBINED_SIZE (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecRule ARGS_COMBINED_SIZE "\@eq 16" "phase:2,log,pass,id:500193" + ), + match_log => { + error => [ qr/Operator EQ matched 16 at ARGS_COMBINED_SIZE\./s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2", + ), +}, +{ + type => "target", + comment => "ARGS_COMBINED_SIZE (post)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecRule ARGS_COMBINED_SIZE "\@eq 16" "phase:2,log,pass,id:500194" + ), + match_log => { + error => [ qr/Operator EQ matched 16 at ARGS_COMBINED_SIZE\./s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, + +# ARGS_NAMES +{ + type => "target", + comment => "ARGS_NAMES (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule ARGS_NAMES "arg1" "phase:2,log,pass,id:500195" + SecRule ARGS_NAMES "arg2" "phase:2,log,pass,id:500196" + ), + match_log => { + error => [ qr/Pattern match "arg1" at ARGS.*Pattern match "arg2" at ARGS/s, 1 ], + debug => [ qr/Adding request argument \(QUERY_STRING\): name "arg1", value "val1".*Adding request argument \(QUERY_STRING\): name "arg2", value "val2"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2", + ), +}, +{ + type => "target", + comment => "ARGS_NAMES (post)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule ARGS_NAMES "arg1" "phase:2,log,pass,id:500197" + SecRule ARGS_NAMES "arg2" "phase:2,log,pass,id:500198" + ), + match_log => { + error => [ qr/Pattern match "arg1" at ARGS_NAMES.*Pattern match "arg2" at ARGS_NAMES/s, 1 ], + debug => [ qr/Adding request argument \(BODY\): name "arg1", value "val1".*Adding request argument \(BODY\): name "arg2", value "val2"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, + +# ARGS_GET +{ + type => "target", + comment => "ARGS_GET (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule ARGS_GET "val1" "phase:2,log,pass,id:500199" + SecRule ARGS_GET "val2" "phase:2,log,pass,id:500200" + ), + match_log => { + error => [ qr/Pattern match "val1" at ARGS_GET.*Pattern match "val2" at ARGS_GET/s, 1 ], + debug => [ qr/Adding request argument \(QUERY_STRING\): name "arg1", value "val1".*Adding request argument \(QUERY_STRING\): name "arg2", value "val2"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2", + ), +}, +{ + type => "target", + comment => "ARGS_GET (post)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule ARGS_GET "val1" "phase:2,log,pass,id:500201" + SecRule ARGS_GET "val2" "phase:2,log,pass,id:500202" + ), + match_log => { + -error => [ qr/Pattern match/, 1 ], + debug => [ qr/Adding request argument \(BODY\): name "arg1", value "val1".*Adding request argument \(BODY\): name "arg2", value "val2"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, + +# ARGS_GET_NAMES +{ + type => "target", + comment => "ARGS_GET_NAMES (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule ARGS_GET_NAMES "arg1" "phase:2,log,pass,id:500203" + SecRule ARGS_GET_NAMES "arg2" "phase:2,log,pass,id:500204" + ), + match_log => { + error => [ qr/Pattern match "arg1" at ARGS_GET.*Pattern match "arg2" at ARGS_GET/s, 1 ], + debug => [ qr/Adding request argument \(QUERY_STRING\): name "arg1", value "val1".*Adding request argument \(QUERY_STRING\): name "arg2", value "val2"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2", + ), +}, +{ + type => "target", + comment => "ARGS_GET_NAMES (post)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule ARGS_GET_NAMES "arg1" "phase:2,log,pass,id:500205" + SecRule ARGS_GET_NAMES "arg2" "phase:2,log,pass,id:500206" + ), + match_log => { + -error => [ qr/Pattern match/, 1 ], + debug => [ qr/Adding request argument \(BODY\): name "arg1", value "val1".*Adding request argument \(BODY\): name "arg2", value "val2"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, + +# ARGS_POST +{ + type => "target", + comment => "ARGS_POST (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule ARGS_POST "val1" "phase:2,log,pass,id:500207" + SecRule ARGS_POST "val2" "phase:2,log,pass,id:500208" + ), + match_log => { + -error => [ qr/Pattern match/, 1 ], + debug => [ qr/Adding request argument \(QUERY_STRING\): name "arg1", value "val1".*Adding request argument \(QUERY_STRING\): name "arg2", value "val2"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "a=b", + ), +}, +{ + type => "target", + comment => "ARGS_POST (post)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule ARGS_POST "val1" "phase:2,log,pass,id:500209" + SecRule ARGS_POST "val2" "phase:2,log,pass,id:500210" + ), + match_log => { + error => [ qr/Pattern match "val1" at ARGS_POST.*Pattern match "val2" at ARGS_POST/s, 1 ], + debug => [ qr/Adding request argument \(BODY\): name "arg1", value "val1".*Adding request argument \(BODY\): name "arg2", value "val2"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, + +# ARGS_POST_NAMES +{ + type => "target", + comment => "ARGS_POST_NAMES (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule ARGS_POST_NAMES "arg1" "phase:2,log,pass,id:500211" + SecRule ARGS_POST_NAMES "arg2" "phase:2,log,pass,id:500212" + ), + match_log => { + -error => [ qr/Pattern match/, 1 ], + debug => [ qr/Adding request argument \(QUERY_STRING\): name "arg1", value "val1".*Adding request argument \(QUERY_STRING\): name "arg2", value "val2"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2", + ), +}, +{ + type => "target", + comment => "ARGS_POST_NAMES (post)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecResponseBodyMimeType null + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule ARGS_POST_NAMES "arg1" "phase:2,log,pass,id:500213" + SecRule ARGS_POST_NAMES "arg2" "phase:2,log,pass,id:500214" + ), + match_log => { + error => [ qr/Pattern match "arg1" at ARGS_POST.*Pattern match "arg2" at ARGS_POST/s, 1 ], + debug => [ qr/Adding request argument \(BODY\): name "arg1", value "val1".*Adding request argument \(BODY\): name "arg2", value "val2"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, +# FULL_REQUEST +{ + type => "target", + comment => "FULL_REQUEST (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule FULL_REQUEST "arg1" "phase:4,log,pass,id:500211" + SecRule FULL_REQUEST "arg2" "phase:4,log,pass,id:500212" + ), + match_log => { + error => [ qr/Pattern match "arg1" at FULL_REQUEST.*Pattern match "arg2" at FULL_REQUEST/s, 1 ], + debug => [ qr/against FULL_REQUEST.*Target value: "GET \/test.txt\?arg1=val1\&arg2=val2 HTTP\/1.1\\n\\nTE: deflate,gzip;q=0.3\\nConnection: TE, close\\nHost: localhost:[0-9]+\\nUser-Agent: ModSecurity Regression Tests\/1.2.3\\n\\n\\x00"/s, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2", + ), +}, +{ + type => "target", + comment => "FULL_REQUEST (post)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule FULL_REQUEST "arg1" "phase:4,log,pass,id:500213" + SecRule FULL_REQUEST "arg2" "phase:4,log,pass,id:500214" + ), + match_log => { + error => [ qr/Pattern match "arg1" at FULL_REQUEST.*Pattern match "arg2" at FULL_REQUEST/s, 1 ], + debug => [ qr/against FULL_REQUEST.*Target value: "POST \/test.txt HTTP\/1.1\\n\\nTE: deflate,gzip;q=0.3\\nConnection: TE, close\\nHost: localhost:[0-9]+\\nUser-Agent: ModSecurity Regression Tests\/1.2.3\\nContent-Type: application\/x-www-form-urlencoded\\nContent-Length: 19\\n\\narg1=val1&arg2=val2\\x00"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, +# FULL_REQUEST_LENGTH +{ + type => "target", + comment => "FULL_REQUEST_LENGTH (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule FULL_REQUEST_LENGTH "\@eq 1" "phase:4,log,pass,id:500211" + SecRule FULL_REQUEST_LENGTH "\@eq 115" "phase:4,log,pass,id:500212" + ), + match_log => { + error => [ qr/Operator EQ matched 115 at FULL_REQUEST_LENGTH./s, 1 ], + debug => [ qr/against FULL_REQUEST_LENGTH.*Target value: "115"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2", + ), +}, +{ + type => "target", + comment => "FULL_REQUEST_LENGTH (post)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule FULL_REQUEST_LENGTH "\@eq 1" "phase:4,log,pass,id:500213" + SecRule FULL_REQUEST_LENGTH "\@eq 201" "phase:4,log,pass,id:500214" + ), + match_log => { + error => [ qr/Operator EQ matched 201 at FULL_REQUEST_LENGTH./s, 1 ], + debug => [ qr/against FULL_REQUEST_LENGTH.*Target value: "201"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, + + + +# AUTH_TYPE +#{ +# type => "target", +# comment => "AUTH_TYPE", +# conf => qq( +# = 2.2> +# +# LoadModule authn_file_module modules/mod_authn_file.so +# +# +## +## +## LoadModule auth_module modules/mod_auth.so +## +## +# +# AuthType Basic +# AuthName Test +# AuthUserFile "$ENV{CONF_DIR}/htpasswd" +# Require user nobody +# +# SecRuleEngine On +# SecRequestBodyAccess On +# SecResponseBodyAccess On +# SecResponseBodyMimeType null +## SecDebugLog $ENV{DEBUG_LOG} +## SecDebugLogLevel 9 +# SecRule REQUEST_HEADERS:Authorization "Basic (.*)" "phase:2,log,pass,capture,chain" +# SecRule TX:1 "nobody:test" "t:none,t:base64Decode,chain" +# SecRule AUTH_TYPE "Basic" +# ), +# match_log => { +# error => [ qr/Pattern match "Basic" at AUTH_TYPE/s, 1 ], +# }, +# match_response => { +# status => qr/^200$/, +# }, +# request => new HTTP::Request( +# GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", +# [ +# "Authorization" => "Basic bm9ib2R5OnRlc3Q=" +# ], +# ), +#}, + +## ENH: We cannot include this test as we cannot distribute the database. +## Instead we should create a simple test DB of our own. +## GEO +#{ +# type => "target", +# comment => "GEO (ip)", +# conf => qq( +# SecRuleEngine On +# SecDebugLog $ENV{DEBUG_LOG} +# SecDebugLogLevel 9 +# SecGeoLookupDB GeoLiteCity.dat +# SecRule ARGS:ip "\@geoLookup" "phase:2,log,pass,t:none" +# SecRule GEO:COUNTRY_CODE "\@streq US" "phase:2,log,pass,t:none" +# SecRule GEO:COUNTRY_CODE3 "\@streq USA" "phase:2,log,pass,t:none" +# SecRule GEO:COUNTRY_NAME "\@streq United States" "phase:2,log,pass,t:none" +# # ENH: Not in this database? +# SecRule GEO:COUNTRY_CONTINENT "\@streq NA" "phase:2,log,pass,t:none" +# SecRule GEO:REGION "\@streq CA" "phase:2,log,pass,t:none" +# SecRule GEO:CITY "\@streq San Diego" "phase:2,log,pass,t:none" +# SecRule GEO:POSTAL_CODE "\@streq 92123" "phase:2,log,pass,t:none" +# SecRule GEO:LATITUDE "\@beginsWith 32.8" "phase:2,log,pass,t:none" +# SecRule GEO:LONGITUDE "\@beginsWith 117.1" "phase:2,log,pass,t:none" +# SecRule GEO:DMA_CODE "\@streq 825" "phase:2,log,pass,t:none" +# SecRule GEO:AREA_CODE "\@streq 858" "phase:2,log,pass,t:none" +# ), +# match_log => { +# debug => [ qr/Geo lookup for "216.75.21.122" succeeded.*match "US" at GEO:COUNTRY_CODE.*match "USA" at GEO:COUNTRY_CODE3.*match "United States" at GEO:COUNTRY_NAME.*match "NA" at GEO:COUNTRY_CONTINENT.*match "CA" at GEO:REGION.*match "San Diego" at GEO:CITY.*match "92123" at GEO:POSTAL_CODE.*match "32.8" at GEO:LATITUDE.*match "825" at GEO:DMA_CODE.*match "858" at GEO:AREA_CODE/si, 1 ], +# }, +# match_response => { +# status => qr/^200$/, +# }, +# request => new HTTP::Request( +# GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?ip=216.75.21.122", +# ), +#}, +#{ +# type => "target", +# comment => "GEO (host)", +# conf => qq( +# SecRuleEngine On +# SecDebugLog $ENV{DEBUG_LOG} +# SecDebugLogLevel 9 +# SecGeoLookupDB GeoLiteCity.dat +# SecRule ARGS:host "\@geoLookup" "phase:2,log,pass,t:none" +# SecRule GEO:COUNTRY_CODE "\@streq US" "phase:2,log,pass,t:none" +# SecRule GEO:COUNTRY_CODE3 "\@streq USA" "phase:2,log,pass,t:none" +# SecRule GEO:COUNTRY_NAME "\@streq United States" "phase:2,log,pass,t:none" +# # ENH: Not in this database? +# SecRule GEO:COUNTRY_CONTINENT "\@streq NA" "phase:2,log,pass,t:none" +# SecRule GEO:REGION "\@streq CA" "phase:2,log,pass,t:none" +# SecRule GEO:CITY "\@streq San Diego" "phase:2,log,pass,t:none" +# SecRule GEO:POSTAL_CODE "\@streq 92123" "phase:2,log,pass,t:none" +# SecRule GEO:LATITUDE "\@beginsWith 32.8" "phase:2,log,pass,t:none" +# SecRule GEO:LONGITUDE "\@beginsWith 117.1" "phase:2,log,pass,t:none" +# SecRule GEO:DMA_CODE "\@streq 825" "phase:2,log,pass,t:none" +# SecRule GEO:AREA_CODE "\@streq 858" "phase:2,log,pass,t:none" +# ), +# match_log => { +# debug => [ qr/Using address "\d+\.\d+\.\d+\.\d+".*Geo lookup for "www\.modsecurity\.org" succeeded.*match "US" at GEO:COUNTRY_CODE.*match "USA" at GEO:COUNTRY_CODE3.*match "United States" at GEO:COUNTRY_NAME.*match "NA" at GEO:COUNTRY_CONTINENT.*match "CA" at GEO:REGION.*match "San Diego" at GEO:CITY.*match "92123" at GEO:POSTAL_CODE.*match "32.8" at GEO:LATITUDE.*match "825" at GEO:DMA_CODE.*match "858" at GEO:AREA_CODE/si, 1 ], +# }, +# match_response => { +# status => qr/^200$/, +# }, +# request => new HTTP::Request( +# GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?host=www.modsecurity.org", +# ), +#}, +#{ +# type => "target", +# comment => "GEO (failed lookup)", +# conf => qq( +# SecRuleEngine On +# SecDebugLog $ENV{DEBUG_LOG} +# SecDebugLogLevel 9 +# SecGeoLookupDB GeoLiteCity.dat +# SecRule ARGS:ip "\@geoLookup" "phase:2,log,pass,t:none" +# SecRule \&GEO "\@eq 0" "phase:2,log,deny,status:403,t:none" +# SecRule ARGS:badip "\@geoLookup" "phase:2,log,pass,t:none" +# SecRule \&GEO "!\@eq 0" "phase:2,log,deny,status:403,t:none" +# ), +# match_log => { +# -debug => [ qr/Geo lookup for "127\.0\.0\.1" succeeded/si, 1 ], +# }, +# match_response => { +# status => qr/^200$/, +# }, +# request => new HTTP::Request( +# GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?ip=216.75.21.122&badip=127.0.0.1", +# ), +#}, + +# TODO: ENV +# TODO: FILES +# TODO: FILES_COMBINED_SIZE +# TODO: FILES_NAMES +# TODO: FILES_SIZES +# TODO: FILES_TMPNAMES +# TODO: HIGHEST_SEVERITY +# TODO: MATCHED_VAR +# TODO: MATCHED_VAR_NAME +# TODO: MODSEC_BUILD +# TODO: MULTIPART_CRLF_LF_LINES +# TODO: MULTIPART_STRICT_ERROR +# TODO: MULTIPART_UNMATCHED_BOUNDARY +# TODO: PATH_INFO +# TODO: QUERY_STRING +# TODO: REMOTE_ADDR +# TODO: REMOTE_HOST +# TODO: REMOTE_PORT +# TODO: REMOTE_USER +# TODO: REQBODY_PROCESSOR +# TODO: REQBODY_PROCESSOR_ERROR +# TODO: REQBODY_PROCESSOR_ERROR_MSG +# TODO: REQUEST_BASENAME +# TODO: REQUEST_BODY +# TODO: REQUEST_COOKIES +# TODO: REQUEST_COOKIES_NAMES +# TODO: REQUEST_FILENAME +# TODO: REQUEST_HEADERS +# TODO: REQUEST_HEADERS_NAMES +# TODO: REQUEST_LINE +# TODO: REQUEST_METHOD +# TODO: REQUEST_PROTOCOL +# TODO: REQUEST_URI +# TODO: REQUEST_URI_RAW +# TODO: RESPONSE_BODY +# TODO: RESPONSE_CONTENT_LENGTH +# TODO: RESPONSE_CONTENT_TYPE +# TODO: RESPONSE_HEADERS +# TODO: RESPONSE_HEADERS_NAMES +# TODO: RESPONSE_PROTOCOL +# TODO: RESPONSE_STATUS +# TODO: RULE +# TODO: SCRIPT_BASENAME +# TODO: SCRIPT_FILENAME +# TODO: SCRIPT_GID +# TODO: SCRIPT_GROUPNAME +# TODO: SCRIPT_MODE +# TODO: SCRIPT_UID +# TODO: SCRIPT_USERNAME +# TODO: SERVER_ADDR +# TODO: SERVER_NAME +# TODO: SERVER_PORT +# TODO: SESSION +# TODO: SESSIONID +# TODO: TIME +# TODO: TIME_DAY +# TODO: TIME_EPOCH +# TODO: TIME_HOUR +# TODO: TIME_MIN +# TODO: TIME_MON +# TODO: TIME_SEC +# TODO: TIME_WDAY +# TODO: TIME_YEAR +# TODO: TX +# TODO: USERID +# TODO: WEBAPPID +# TODO: WEBSERVER_ERROR_LOG +# TODO: XML + diff --git a/tests/run-regression-tests.pl.in b/tests/run-regression-tests.pl.in new file mode 100755 index 0000000..b50e75a --- /dev/null +++ b/tests/run-regression-tests.pl.in @@ -0,0 +1,807 @@ +#!@PERL@ +# +# Run regression tests. +# +# Syntax: run-regression-tests.pl [options] [file [N]] +# +# All: run-regression-tests.pl +# All in file: run-regression-tests.pl file +# Nth in file: run-regression-tests.pl file N +# +use strict; +use Time::HiRes qw(gettimeofday sleep); +use POSIX qw(WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG); +use File::Spec qw(rel2abs); +use File::Basename qw(basename dirname); +use FileHandle; +use IPC::Open2 qw(open2); +use IPC::Open3 qw(open3); +use Getopt::Std; +use Data::Dumper; +use IO::Socket; +use LWP::UserAgent; + +my @TYPES = qw(config misc action target rule); +my $SCRIPT = basename($0); +my $SCRIPT_DIR = File::Spec->rel2abs(dirname($0)); +my $REG_DIR = "$SCRIPT_DIR/regression"; +my $SROOT_DIR = "$REG_DIR/server_root"; +my $DATA_DIR = "$SROOT_DIR/data"; +my $TEMP_DIR = "$SROOT_DIR/tmp"; +my $UPLOAD_DIR = "$SROOT_DIR/upload"; +my $CONF_DIR = "$SROOT_DIR/conf"; +my $MODULES_DIR = q(@APXS_LIBEXECDIR@); +my $FILES_DIR = "$SROOT_DIR/logs"; +my $PID_FILE = "$FILES_DIR/httpd.pid"; +my $HTTPD = q(@APXS_HTTPD@); +my $PASSED = 0; +my $TOTAL = 0; +my $BUFSIZ = 32768; +my %C = (); +my %FILE = (); +my $UA_NAME = "ModSecurity Regression Tests/1.2.3"; +my $UA = LWP::UserAgent->new; +$UA->agent($UA_NAME); + +# Hack for testing the script w/o configure +if ($HTTPD eq "\@APXS_HTTPD\@") { + $HTTPD = "/usr/local/apache2/bin/httpd"; + $MODULES_DIR = "/usr/local/apache2/modules"; +} + +$SIG{TERM} = $SIG{INT} = \&handle_interrupt; + +my $platform = "apache"; + +my %opt; +getopts('A:E:D:C:T:H:a:p:dvh', \%opt); + +if ($opt{d}) { + $Data::Dumper::Indent = 1; + $Data::Dumper::Terse = 1; + $Data::Dumper::Pad = ""; + $Data::Dumper::Quotekeys = 0; +} + +sub usage { + print stderr <<"EOT"; +@_ +Usage: $SCRIPT [options] [file [N]] + + Options: + -A file Specify ModSecurity audit log to read. + -D file Specify ModSecurity debug log to read. + -E file Specify Apache httpd error log to read. + -C file Specify Apache httpd base conf file to generate/reload. + -H path Specify Apache httpd htdocs path. + -S path Specify Apache httpd server root path. + -a file Specify Apache httpd binary (default: httpd) + -p port Specify Apache httpd port (default: 8088) + -v Enable verbose output (details on failure). + -d Enable debugging output. + -h This help. + +EOT + + exit(1); +} + +usage() if ($opt{h}); + +### Check httpd binary +if (defined $opt{a}) { + $HTTPD = $opt{a}; +} +else { + $opt{a} = $HTTPD; +} +usage("Invalid Apache startup script: $HTTPD\n") unless (-e $HTTPD); + +### Defaults +$opt{A} = "$FILES_DIR/modsec_audit.log" unless (defined $opt{A}); +$opt{D} = "$FILES_DIR/modsec_debug.log" unless (defined $opt{D}); +$opt{E} = "$FILES_DIR/error.log" unless (defined $opt{E}); +$opt{C} = "$CONF_DIR/httpd.conf" unless (defined $opt{C}); +$opt{H} = "$SROOT_DIR/htdocs" unless (defined $opt{H}); +$opt{p} = 8088 unless (defined $opt{p}); +$opt{v} = 1 if ($opt{d}); + +unless (defined $opt{S}) { + my $httpd_root = `$HTTPD -V`; + ($opt{S} = $httpd_root) =~ s/.*-D HTTPD_ROOT="([^"]*)".*/$1/sm; +} + +%ENV = ( + %ENV, + SERVER_ROOT => $opt{S}, + SERVER_PORT => $opt{p}, + SERVER_NAME => "localhost", + TEST_SERVER_ROOT => $SROOT_DIR, + DATA_DIR => $DATA_DIR, + TEMP_DIR => $TEMP_DIR, + UPLOAD_DIR => $UPLOAD_DIR, + CONF_DIR => $CONF_DIR, + MODULES_DIR => $MODULES_DIR, + LOGS_DIR => $FILES_DIR, + SCRIPT_DIR => $SCRIPT_DIR, + REGRESSION_DIR => $REG_DIR, + DIST_ROOT => File::Spec->rel2abs(dirname("$SCRIPT_DIR/../../..")), + AUDIT_LOG => $opt{A}, + DEBUG_LOG => $opt{D}, + ERROR_LOG => $opt{E}, + HTTPD_CONF => $opt{C}, + HTDOCS => $opt{H}, + USER_AGENT => $UA_NAME, +); + +#dbg("OPTIONS: ", \%opt); + +if (-e "$PID_FILE") { + msg("Shutting down previous instance: $PID_FILE"); + httpd_stop(); +} + +if (defined $ARGV[0]) { + runfile(dirname($ARGV[0]), basename($ARGV[0]), $ARGV[1]); + done(); +} + +for my $type (@TYPES) { + my $dir = "$SCRIPT_DIR/regression/$type"; + my @cfg = (); + + # Get test names + opendir(DIR, "$dir") or quit(1, "Failed to open \"$dir\": $!"); + @cfg = grep { /\.t$/ && -f "$dir/$_" } readdir(DIR); + closedir(DIR); + + for my $cfg (sort @cfg) { + runfile($dir, $cfg); + } +} +done(); + + +sub runfile { + my($dir, $cfg, $testnum) = @_; + my $fn = "$dir/$cfg"; + my @data = (); + my $edata; + my @C = (); + my @test = (); + my $teststr; + my $n = 0; + my $pass = 0; + + open(CFG, "<$fn") or quit(1, "Failed to open \"$fn\": $!"); + @data = ; + + $edata = q/@C = (/ . join("", @data) . q/)/; + eval $edata; + quit(1, "Failed to read test data \"$cfg\": $@") if ($@); + + unless (@C) { + msg("\nNo tests defined for $fn"); + return; + } + + msg("\nLoaded ".@C." tests from $fn"); + for my $t (@C) { + $n++; + next if (defined $testnum and $n != $testnum); + + my $httpd_up = 0; + my %t = %{$t || {}}; + my $id = sprintf("%3d", $n); + my $out = ""; + my $rc = 0; + my $conf_fn; + + # Startup httpd with optionally included conf. + if (exists $t{conf} and defined $t{conf}) { + $conf_fn = sprintf "%s/%s_%s_%06d.conf", + $CONF_DIR, $t{type}, $cfg, $n; + #dbg("Writing test config to: $conf_fn"); + open(CONF, ">$conf_fn") or die "Failed to open conf \"$conf_fn\": $!\n"; + print CONF (ref $t{conf} eq "CODE" ? eval { &{$t{conf}} } : $t{conf}); + msg("$@") if ($@); + close CONF; + $httpd_up = httpd_start(\%t, "modsecurity_rules_file $conf_fn") ? 0 : 1; + } + else { + $httpd_up = httpd_start(\%t) ? 0 : 1; + } + + # Run any prerun setup + if ($rc == 0 and exists $t{prerun} and defined $t{prerun}) { + vrb("Executing perl prerun..."); + $rc = &{$t{prerun}}; + vrb("Perl prerun returned: $rc"); + } + + if ($httpd_up) { + # Perform the request and check response + if (exists $t{request}) { + my $resp = do_request($t{request}); + if (!$resp) { + msg("invalid response"); + vrb("RESPONSE: ", $resp); + $rc = 1; + } + else { + for my $key (keys %{ $t{match_response} || {}}) { + my($neg,$mtype) = ($key =~ m/^(-?)(.*)$/); + my $m = $t{match_response}{$key}; + if (ref($m) eq "HASH") { + if ($m->{$platform}) { + $m = $m->{$platform}; + } + else { + my $ap = join(", ", keys %{$m}); + msg("Warning: trying to match: $mtype. Nothing " . + "to match in current platform: $platform. " . + "This test only contains cotent for: $ap."); + last; + } + } + my $match = match_response($mtype, $resp, $m); + if ($neg and defined $match) { + $rc = 1; + msg("response $mtype matched: $m"); + vrb($resp); + last; + } + elsif (!$neg and !defined $match) { + $rc = 1; + msg("response $mtype failed to match: $m"); + vrb($resp); + last; + } + } + } + } + + # Run any arbitrary perl tests + if ($rc == 0 and exists $t{test} and defined $t{test}) { + dbg("Executing perl test(s)..."); + $rc = eval { &{$t{test}} }; + if (! defined $rc) { + msg("Error running test: $@"); + $rc = -1; + } + dbg("Perl tests returned: $rc"); + } + + # Search for all log matches + if ($rc == 0 and exists $t{match_log} and defined $t{match_log}) { + for my $key (keys %{ $t{match_log} || {}}) { + my($neg,$mtype) = ($key =~ m/^(-?)(.*)$/); + my $m = $t{match_log}{$key}; + if (ref($m) eq "HASH") { + if ($m->{$platform}) { + $m = $m->{$platform}; + } + else { + my $ap = join(", ", keys %{$m}); + msg("Warning: trying to match: $mtype. Nothing " . + "to match in current platform: $platform. " . + "This test only contains cotent for: $ap."); + last; + } + } + my $match = match_log($mtype, @{$m || []}); + if ($neg and defined $match) { + $rc = 1; + msg("$mtype log matched: $m->[0]"); + last; + } + elsif (!$neg and !defined $match) { + $rc = 1; + msg("$mtype log failed to match: $m->[0]"); + last; + } + } + } + + # Search for all file matches + if ($rc == 0 and exists $t{match_file} and defined $t{match_file}) { + sleep 1; # Make sure the file exists + for my $key (keys %{ $t{match_file} || {}}) { + my($neg,$fn) = ($key =~ m/^(-?)(.*)$/); + my $m = $t{match_file}{$key}; + my $match = match_file($fn, $m); + if ($neg and defined $match) { + $rc = 1; + msg("$fn file matched: $m"); + last; + } + elsif (!$neg and !defined $match) { + $rc = 1; + msg("$fn file failed match: $m"); + last; + } + } + } + } + else { + msg("Failed to start httpd."); + $rc = 1; + } + + if ($rc == 0) { + $pass++; + } + else { + vrb("Test Config: $conf_fn"); + vrb("Debug Log: $FILE{debug}{fn}"); + dbg(escape("$FILE{debug}{buf}")); + vrb("Error Log: $FILE{error}{fn}"); + dbg(escape("$FILE{error}{buf}")); + } + + msg(sprintf("%s) %s%s: %s%s", $id, $t{type}, (exists($t{comment}) ? " - $t{comment}" : ""), ($rc ? "failed" : "passed"), ((defined($out) && $out ne "")? " ($out)" : ""))); + + if ($httpd_up) { + $httpd_up = httpd_stop(\%t) ? 0 : 1; + } + + } + + $TOTAL += $testnum ? 1 : $n; + $PASSED += $pass; + + msg(sprintf("Passed: %2d; Failed: %2d", $pass, $testnum ? (1 - $pass) : ($n - $pass))); +} + +# Take out any indenting and translate LF -> CRLF +sub normalize_raw_request_data { + my $r = $_[0]; + + # Allow for indenting in test file + $r =~ s/^[ \t]*\x0d?\x0a//s; + my($indention) = ($r =~ m/^([ \t]*)/s); # indention taken from first line + $r =~ s/^$indention//mg; + $r =~ s/(\x0d?\x0a)[ \t]+$/$1/s; + + # Translate LF to CRLF + $r =~ s/^\x0a/\x0d\x0a/mg; + $r =~ s/([^\x0d])\x0a/$1\x0d\x0a/mg; + + return $r; +} + +sub do_raw_request { + my $sock = new IO::Socket::INET( + Proto => "tcp", + PeerAddr => "localhost", + PeerPort => $opt{p}, + ) or msg("Failed to connect to localhost:$opt{p}: $@"); + return unless ($sock); + + # Join togeather the request + my $r = join("", @_); + dbg($r); + + # Write to socket + print $sock "$r"; + $sock->shutdown(1); + + # Read from socket + my @resp = <$sock>; + $sock->close(); + + return HTTP::Response->parse(join("", @resp)); +} + +sub do_request { + my $r = $_[0]; + + # Allow test to execute code + if (ref $r eq "CODE") { + $r = eval { &$r }; + msg("$@") unless (defined $r); + } + + if (ref $r eq "HTTP::Request") { + my $resp = $UA->request($r); + dbg($resp->request()->as_string()) if ($opt{d}); + return $resp + } + else { + return do_raw_request($r); + } + + return; +} + + +sub match_response { + my($name, $resp, $re) = @_; + + msg("Warning: Empty regular expression.") if (!defined $re or $re eq ""); + + if ($name eq "status") { + return $& if ($resp->code =~ m/$re/); + } + elsif ($name eq "content") { + return $& if ($resp->content =~ m/$re/m); + } + elsif ($name eq "raw") { + return $& if ($resp->as_string =~ m/$re/m); + } + + return; +} + +sub read_log { + my($name, $timeout, $graph) = @_; + return match_log($name, undef, $timeout, $graph); +} + +sub match_log { + my($name, $re, $timeout, $graph) = @_; + my $t0 = gettimeofday; + my($fh,$rbuf) = ($FILE{$name}{fd}, \$FILE{$name}{buf}); + my $n = length($$rbuf); + my $rc = undef; + + unless (defined $fh) { + msg("Error: File \"$name\" is not opened for matching."); + return; + } + + $timeout = 0 unless (defined $timeout); + + my $i = 0; + my $graphed = 0; + READ: { + do { + my $nbytes = $fh->sysread($$rbuf, $BUFSIZ, $n); + if (!defined($nbytes)) { + msg("Error: Could not read \"$name\" log: $!"); + last; + } + elsif (!defined($re) and $nbytes == 0) { + last; + } + + # Remove APR pool debugging + $$rbuf =~ s/POOL DEBUG:[^\n]+PALLOC[^\n]+\n//sg; + + $n = length($$rbuf); + + #dbg("Match \"$re\" in $name \"$$rbuf\" ($n)"); + if ($$rbuf =~ m/$re/m) { + $rc = $&; + last; + } + # TODO: Use select()/poll() + sleep 0.1 unless ($nbytes == $BUFSIZ); + if ($graph and $opt{d}) { + $i++; + if ($i == 10) { + $graphed++; + $i=0; + print STDERR $graph if ($graphed == 1); + print STDERR "." + } + } + } while (gettimeofday - $t0 < $timeout); + } + print STDERR "\n" if ($graphed); + + return $rc; +} + +sub match_file { + my($neg,$fn) = ($_[0] =~ m/^(-?)(.*)$/); + unless (exists $FILE{$fn}) { + eval { + $FILE{$fn}{fn} = $fn; + $FILE{$fn}{fd} = new FileHandle($fn, O_RDONLY) or die "$!\n"; + $FILE{$fn}{fd}->blocking(0); + $FILE{$fn}{buf} = ""; + }; + if ($@) { + msg("Warning: Failed to open file \"$fn\": $@"); + return; + } + } + return match_log($_[0], $_[1]); # timeout makes no sense +} + +sub quote_shell { + my($s) = @_; + return $s unless ($s =~ m|[^\w!%+,\-./:@^]|); + $s =~ s/(['\\])/\\$1/g; + return "'$s'"; +} + +sub escape { + my @new = (); + for my $c (split(//, $_[0])) { + my $oc = ord($c); + push @new, ((($oc >= 0x20 and $oc <= 0x7e) or $oc == 0x0a or $oc == 0x0d) ? $c : sprintf("\\x%02x", ord($c))); + } + join('', @new); +} + +sub dbg { + return unless(@_ and $opt{d}); + my $out = join "", map { + (ref $_ ne "" ? Dumper($_) : $_) + } @_; + $out =~ s/^/DBG: /mg; + print STDOUT "$out\n"; +} + +sub vrb { + return unless(@_ and $opt{v}); + msg(@_); +} + +sub msg { + return unless(@_); + my $out = join "", map { + (ref $_ ne "" ? Dumper($_) : $_) + } @_; + print STDOUT "$out\n"; +} + +sub handle_interrupt { + $SIG{TERM} = $SIG{INT} = \&handle_interrupt; + + msg("Interrupted via SIG$_[0]. Shutting down tests..."); + httpd_stop(); + + quit(1); +} + +sub quit { + my($ec,$msg) = @_; + $ec = 0 unless (defined $_[0]); + + msg("$msg") if (defined $msg); + + exit $ec; +} + +sub done { + if ($PASSED != $TOTAL) { + quit(1, "\n$PASSED/$TOTAL tests passed."); + } + + quit(0, "\nAll tests passed ($TOTAL)."); +} + +sub httpd_start { + my $t = shift; + httpd_reset_fd($t); + my @p = ( + $HTTPD, + -d => $opt{S}, + -f => $opt{C}, + (map { (-c => $_) } ("Listen localhost:$opt{p}", @_)), + -k => "start", + ); + + my $httpd_out; + my $httpd_pid = open3(undef, $httpd_out, undef, @p) or quit(1); + my $out = join("\\n", grep(!/POOL DEBUG/, (<$httpd_out>))); + close $httpd_out; + waitpid($httpd_pid, 0); + + my $rc = $?; + if ( WIFEXITED($rc) ) { + $rc = WEXITSTATUS($rc); + vrb("Httpd start returned with $rc.") if ($rc); + } + elsif( WIFSIGNALED($rc) ) { + msg("Httpd start failed with signal " . WTERMSIG($rc) . "."); + $rc = -1; + } + else { + msg("Httpd start failed with unknown error."); + $rc = -1; + } + + if (defined $out and $out ne "") { + vrb(join(" ", map { quote_shell($_) } @p)); + msg("Httpd start failed with error messages:\n$out"); + httpd_stop(); + return -1 + } + + # Look for startup msg + unless (defined match_log("error", qr/resuming normal operations/, 60, "Waiting on httpd to start: ")) { + vrb(join(" ", map { quote_shell($_) } @p)); + vrb(match_log("error", qr/(^.*ModSecurity: .*)/sm, 10)); + msg("Httpd server failed to start."); + httpd_stop(); + return -1; + } + + return $rc; +} + +sub httpd_stop { + my $t = shift; + my @p = ( + $HTTPD, + -d => $opt{S}, + -f => $opt{C}, + (map { (-c => $_) } ("Listen localhost:$opt{p}", @_)), + -k => "stop", + ); + + my $httpd_out; + my $httpd_pid = open3(undef, $httpd_out, undef, @p) or quit(1); + my $out = join("\\n", grep(!/POOL DEBUG/, (<$httpd_out>))); + close $httpd_out; + waitpid($httpd_pid, 0); + + if (defined $out and $out ne "") { + msg("Httpd stop failed with error messages:\n$out"); + return -1 + } + + my $rc = $?; + if ( WIFEXITED($rc) ) { + $rc = WEXITSTATUS($rc); + vrb("Httpd stop returned with $rc.") if ($rc); + } + elsif( WIFSIGNALED($rc) ) { + msg("Httpd stop failed with signal " . WTERMSIG($rc) . "."); + $rc = -1; + } + else { + msg("Httpd stop failed with unknown error."); + $rc = -1; + } + + # Look for startup msg + unless (defined match_log("error", qr/caught SIG[A-Z]+, shutting down/, 60, "Waiting on httpd to stop: ")) { + vrb(join(" ", map { quote_shell($_) } @p)); + msg("Httpd server failed to shutdown."); + sleep 0.5; + return -1; + } + + sleep 0.5; + + return $rc; +} + +sub httpd_reload { + my $t = shift; + httpd_reset_fd($t); + my @p = ( + $HTTPD, + -d => $opt{S}, + -f => $opt{C}, + (map { (-c => $_) } ("Listen localhost:$opt{p}", @_)), + -k => "graceful", + ); + + my $httpd_out; + my $httpd_pid = open3(undef, $httpd_out, undef, @p) or quit(1); + my $out = join("\\n", grep(!/POOL DEBUG/, (<$httpd_out>))); + close $httpd_out; + waitpid($httpd_pid, 0); + + if (defined $out and $out ne "") { + msg("Httpd reload failed with error messages:\n$out"); + return -1 + } + + my $rc = $?; + if ( WIFEXITED($rc) ) { + $rc = WEXITSTATUS($rc); + vrb("Httpd reload returned with $rc.") if ($rc); + } + elsif( WIFSIGNALED($rc) ) { + msg("Httpd reload failed with signal " . WTERMSIG($rc) . "."); + $rc = -1; + } + else { + msg("Httpd reload failed with unknown error."); + $rc = -1; + } + + # Look for startup msg + unless (defined match_log("error", qr/resuming normal operations/, 60, "Waiting on httpd to restart: ")) { + vrb(join(" ", map { quote_shell($_) } @p)); + msg("Httpd server failed to reload."); + return -1; + } + + return $rc; +} + +sub httpd_reset_fd { + my($t) = @_; + + # Cleanup + for my $key (keys %FILE) { + if (exists $FILE{$key}{fd} and defined $FILE{$key}{fd}) { + $FILE{$key}{fd}->close(); + } + delete $FILE{$key}; + } + + # Error + eval { + $FILE{error}{fn} = $opt{E}; + $FILE{error}{fd} = new FileHandle($opt{E}, O_RDWR|O_CREAT) or die "$!\n"; + $FILE{error}{fd}->blocking(0); + $FILE{error}{fd}->sysseek(0, 2); + $FILE{error}{buf} = ""; + }; + if ($@) { + msg("Warning: Failed to open file \"$opt{E}\": $@"); + return undef; + } + + # Audit + eval { + $FILE{audit}{fn} = $opt{A}; + $FILE{audit}{fd} = new FileHandle($opt{A}, O_RDWR|O_CREAT) or die "$!\n"; + $FILE{audit}{fd}->blocking(0); + $FILE{audit}{fd}->sysseek(0, 2); + $FILE{audit}{buf} = ""; + }; + if ($@) { + msg("Warning: Failed to open file \"$opt{A}\": $@"); + return undef; + } + + # Debug + eval { + $FILE{debug}{fn} = $opt{D}; + $FILE{debug}{fd} = new FileHandle($opt{D}, O_RDWR|O_CREAT) or die "$!\n"; + $FILE{debug}{fd}->blocking(0); + $FILE{debug}{fd}->sysseek(0, 2); + $FILE{debug}{buf} = ""; + }; + if ($@) { + msg("Warning: Failed to open file \"$opt{D}\": $@"); + return undef; + } + + # Any extras listed in "match_log" + if ($t and exists $t->{match_log}) { + for my $k (keys %{ $t->{match_log} || {} }) { + my($neg,$fn) = ($k =~ m/^(-?)(.*)$/); + next if (!$fn or exists $FILE{$fn}); + eval { + $FILE{$fn}{fn} = $fn; + $FILE{$fn}{fd} = new FileHandle($fn, O_RDWR|O_CREAT) or die "$!\n"; + $FILE{$fn}{fd}->blocking(0); + $FILE{$fn}{fd}->sysseek(0, 2); + $FILE{$fn}{buf} = ""; + }; + if ($@) { + msg("Warning: Failed to open file \"$fn\": $@"); + return undef; + } + } + } +} + +sub encode_chunked { + my($data, $size) = @_; + $size = 128 unless ($size); + my $chunked = ""; + + my $n = 0; + my $bytes = length($data); + while ($bytes >= $size) { + $chunked .= sprintf "%x\x0d\x0a%s\x0d\x0a", $size, substr($data, $n, $size); + $n += $size; + $bytes -= $size; + } + if ($bytes) { + $chunked .= sprintf "%x\x0d\x0a%s\x0d\x0a", $bytes, substr($data, $n, $bytes); + } + $chunked .= "0\x0d\x0a\x0d\x0a" +} From 530fdc9ea507a91a6f5a2f4d2a74f40ec40731c7 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Sun, 20 Aug 2017 14:28:17 -0300 Subject: [PATCH 02/35] test: Not yet supported by version 3 --- .../regression/action/00-disruptive-actions.t | 148 -------------- tests/regression/action/10-append-prepend.t | 49 ----- .../action/10-detectiononly-actions.t | 92 +-------- tests/regression/config/10-debug-directives.t | 27 +-- tests/regression/config/10-misc-directives.t | 42 +--- tests/regression/misc/10-tfn-cache.t | 187 ------------------ tests/regression/misc/20-status-engine.t | 74 ------- tests/regression/misc/30-fuzzyHash.t | 180 ----------------- tests/regression/rule/00-basics.t | 4 +- tests/regression/rule/00-script.t | 63 ------ tests/regression/rule/20-exceptions.t | 49 +---- 11 files changed, 14 insertions(+), 901 deletions(-) delete mode 100644 tests/regression/action/10-append-prepend.t delete mode 100644 tests/regression/misc/10-tfn-cache.t delete mode 100644 tests/regression/misc/30-fuzzyHash.t delete mode 100644 tests/regression/rule/00-script.t diff --git a/tests/regression/action/00-disruptive-actions.t b/tests/regression/action/00-disruptive-actions.t index f682396..64b86a3 100644 --- a/tests/regression/action/00-disruptive-actions.t +++ b/tests/regression/action/00-disruptive-actions.t @@ -442,152 +442,4 @@ ), }, -# Proxy -{ - type => "action", - comment => "proxy in phase:1 (get)", - conf => qq( - SecRuleEngine On - SecRequestBodyAccess On - SecResponseBodyAccess On - SecResponseBodyMimeType null - SecRule REQUEST_URI "\@streq /test2.txt" "phase:1,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500005" - ), - match_log => { - error => { - apache => [qr/ModSecurity: Access denied using proxy to \(phase 1\)/, 1], - nginx => [qr/ModSecurity: Access denied with code 500 \(phase 1\) \(Configuration Error: Proxy action to .* requested but proxy is only available in Apache version\)./, 1], - }, - }, - match_response => { - status => { - apache => qr/^200$/, - nginx => qr/^500$/, - }, - content => { - apache => qr/^TEST$/, - nginx => qr/^*$/, - }, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", - ), -}, -{ - type => "action", - comment => "nocanon proxy in phase:1 (get)", - conf => qq( - SecRuleEngine On - SecRequestBodyAccess On - SecResponseBodyAccess On - SecResponseBodyMimeType null - SecRule REQUEST_URI "\@streq /test2.txt" "phase:1,proxy:'[nocanon]http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500005" - ), - match_log => { - error => { - apache => [qr/ModSecurity: Access denied using proxy to \(phase 1\)/, 1], - nginx => [qr/ModSecurity: Access denied with code 500 \(phase 1\) \(Configuration Error: Proxy action to .* requested but proxy is only available in Apache version\)./, 1], - }, - }, - match_response => { - status => { - apache => qr/^200$/, - nginx => qr/^500$/, - }, - content => { - apache => qr/^TEST$/, - nginx => qr/^*$/, - }, - }, - - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", - ), -}, -{ - type => "action", - comment => "proxy in phase:2 (get)", - conf => qq( - SecRuleEngine On - SecRequestBodyAccess On - SecResponseBodyAccess On - SecResponseBodyMimeType null - SecRule REQUEST_URI "\@streq /test2.txt" "phase:2,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500006" - ), - match_log => { - error => { - apache => [qr/ModSecurity: Access denied using proxy to \(phase 2\)/, 1], - nginx => [qr/ModSecurity: Access denied with code 500 \(phase 2\) \(Configuration Error: Proxy action to .* requested but proxy is only available in Apache version\)./, 1], - }, - }, - match_response => { - status => { - apache => qr/^200$/, - nginx => qr/^500$/, - }, - content => { - apache => qr/^TEST$/, - nginx => qr/^*$/, - }, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", - ), -}, -{ - type => "action", - comment => "proxy in phase:3 (get)", - conf => qq( - SecRuleEngine On - SecRequestBodyAccess On - SecResponseBodyAccess On - SecResponseBodyMimeType null - SecDebugLog "$ENV{DEBUG_LOG}" - SecDebugLogLevel 4 - SecRule REQUEST_URI "\@streq /test2.txt" "phase:3,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500007" - ), - match_log => { - error => { - apache => [qr/ModSecurity: Access denied with code 500 \(phase 3\) \(Configuration Error: Proxy action requested but it does not work in output phases\)./, 1], - nginx => [qr/ModSecurity: Access denied with code 500 \(phase 3\) \(Configuration Error: Proxy action to .* requested but proxy is only available in Apache version\)./, 1], - } - }, - match_response => { - status => { - apache => qr/^500$/, - nginx => qr/^500$/, - }, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", - ), -}, -{ - type => "action", - comment => "proxy in phase:4 (get)", - conf => qq( - SecRuleEngine On - SecRequestBodyAccess On - SecResponseBodyAccess On - SecResponseBodyMimeType null - SecDebugLog "$ENV{DEBUG_LOG}" - SecDebugLogLevel 4 - SecRule REQUEST_URI "\@streq /test2.txt" "phase:4,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500008" - ), - match_log => { - error => { - apache => [qr/ModSecurity: Access denied with code 500 \(phase 4\) \(Configuration Error: Proxy action requested but it does not work in output phases\)./, 1], - nginx => [qr/ModSecurity: Access denied with code 500 \(phase 4\) \(Configuration Error: Proxy action to .* requested but proxy is only available in Apache version\)./, 1], - } - }, - match_response => { - status => { - apache => qr/^500$/, - nginx => qr/^500$/, - }, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", - ), -}, diff --git a/tests/regression/action/10-append-prepend.t b/tests/regression/action/10-append-prepend.t deleted file mode 100644 index 4e89f62..0000000 --- a/tests/regression/action/10-append-prepend.t +++ /dev/null @@ -1,49 +0,0 @@ -# TODO: Need more tests here - -### append -{ - type => "action", - comment => "append content", - conf => qq( - SecRuleEngine On - SecContentInjection On - SecDebugLog "$ENV{DEBUG_LOG}" - SecDebugLogLevel 9 - SecAction "phase:1,setvar:tx.test=test,id:500002" - SecAction "phase:2,append:'APPEND: \%{tx.test}',id:500003" - ), - match_log => { - debug => [ "Added content to bottom: APPEND: test", 1 ], - }, - match_response => { - status => qr/^200$/, - content => qr/APPEND: test$/, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", - ), -}, - -### prepend -{ - type => "action", - comment => "prepend content", - conf => qq( - SecRuleEngine On - SecContentInjection On - SecDebugLog "$ENV{DEBUG_LOG}" - SecDebugLogLevel 9 - SecAction "phase:1,setvar:tx.test=test,id:500004" - SecAction "phase:2,prepend:'PREPEND: \%{tx.test}',id:500005" - ), - match_log => { - debug => [ "Added content to top: PREPEND: test", 1 ], - }, - match_response => { - status => qr/^200$/, - content => qr/^PREPEND: test/, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", - ), -}, diff --git a/tests/regression/action/10-detectiononly-actions.t b/tests/regression/action/10-detectiononly-actions.t index 42a8dfa..77e74e0 100644 --- a/tests/regression/action/10-detectiononly-actions.t +++ b/tests/regression/action/10-detectiononly-actions.t @@ -452,94 +452,4 @@ ), }, -# Proxy -{ - type => "action", - comment => "proxy in phase:1 (get)", - conf => qq( - SecRuleEngine DetectionOnly - SecRequestBodyAccess On - SecResponseBodyAccess On - SecResponseBodyMimeType null - SecRule REQUEST_URI "\@streq /test2.txt" "phase:1,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'PROXIED',id:500013" - ), - match_log => { - error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*PROXIED/, 1 ], - -error => [ qr/Access denied/, 1 ], - }, - match_response => { - status => qr/^200$/, - content => qr/^TEST 2$/, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", - ), -}, -{ - type => "action", - comment => "proxy in phase:2 (get)", - conf => qq( - SecRuleEngine DetectionOnly - SecRequestBodyAccess On - SecResponseBodyAccess On - SecResponseBodyMimeType null - SecRule REQUEST_URI "\@streq /test2.txt" "phase:2,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'PROXIED',id:500014" - ), - match_log => { - error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*PROXIED/, 1 ], - -error => [ qr/Access denied/, 1 ], - }, - match_response => { - status => qr/^200$/, - content => qr/^TEST 2$/, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", - ), -}, -{ - type => "action", - comment => "proxy in phase:3 (get)", - conf => qq( - SecRuleEngine DetectionOnly - SecRequestBodyAccess On - SecResponseBodyAccess On - SecResponseBodyMimeType null - SecDebugLog "$ENV{DEBUG_LOG}" - SecDebugLogLevel 4 - SecRule REQUEST_URI "\@streq /test2.txt" "phase:3,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'PROXIED',id:500015" - ), - match_log => { - error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*PROXIED/, 1 ], - -error => [ qr/Access denied/, 1 ], - }, - match_response => { - status => qr/^200$/, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", - ), -}, -{ - type => "action", - comment => "proxy in phase:4 (get)", - conf => qq( - SecRuleEngine DetectionOnly - SecRequestBodyAccess On - SecResponseBodyAccess On - SecResponseBodyMimeType null - SecDebugLog "$ENV{DEBUG_LOG}" - SecDebugLogLevel 4 - SecRule REQUEST_URI "\@streq /test2.txt" "phase:4,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'PROXIED',id:500016" - ), - match_log => { - error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*PROXIED/, 1 ], - -error => [ qr/Access denied/, 1 ], - }, - match_response => { - status => qr/^200$/, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", - ), -}, + diff --git a/tests/regression/config/10-debug-directives.t b/tests/regression/config/10-debug-directives.t index bf78801..5d78662 100644 --- a/tests/regression/config/10-debug-directives.t +++ b/tests/regression/config/10-debug-directives.t @@ -59,8 +59,7 @@ SecRuleEngine On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 1 - SecRuleScript "test.lua" "phase:1" - SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500242" + SecRule REQUEST_URI "(.)" "phase:4,deny,id:500242" ), match_log => { debug => [ qr/\]\[[1]\] /, 1 ], @@ -84,8 +83,7 @@ SecRuleEngine DetectionOnly SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 2 - SecRuleScript "test.lua" "phase:1" - SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500243" + SecRule REQUEST_URI "(.)" "phase:4,deny,id:500243" ), match_log => { debug => [ qr/\]\[2\] /, 1 ], @@ -109,8 +107,7 @@ SecRuleEngine On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 3 - SecRuleScript "test.lua" "phase:1" - SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500244" + SecRule REQUEST_URI "(.)" "phase:4,deny,id:500244" ), match_log => { debug => [ qr/\]\[3\] /, 1 ], @@ -134,8 +131,7 @@ SecRuleEngine On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 4 - SecRuleScript "test.lua" "phase:1" - SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500245" + SecRule REQUEST_URI "(.)" "phase:4,deny,id:500245" ), match_log => { debug => [ qr/\]\[4\] /, 1 ], @@ -159,8 +155,7 @@ SecRuleEngine On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 5 - SecRuleScript "test.lua" "phase:1" - SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500246" + SecRule REQUEST_URI "(.)" "phase:4,deny,id:500246" ), match_log => { debug => [ qr/\]\[5\] /, 1 ], @@ -184,8 +179,7 @@ SecRuleEngine On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 6 - SecRuleScript "test.lua" "phase:1" - SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500247" + SecRule REQUEST_URI "(.)" "phase:4,deny,id:500247" ), match_log => { debug => [ qr/\]\[6\] /, 1 ], @@ -209,8 +203,7 @@ SecRuleEngine On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 7 - SecRuleScript "test.lua" "phase:1" - SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500248" + SecRule REQUEST_URI "(.)" "phase:4,deny,id:500248" ), match_log => { debug => [ qr/\]\[7\] /, 1 ], @@ -234,8 +227,7 @@ SecRuleEngine On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 8 - SecRuleScript "test.lua" "phase:1" - SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500249" + SecRule REQUEST_URI "(.)" "phase:4,deny,id:500249" ), match_log => { debug => [ qr/\]\[8\] /, 1 ], @@ -259,8 +251,7 @@ SecRuleEngine On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 9 - SecRuleScript "test.lua" "phase:1" - SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus,id:500250" + SecRule REQUEST_URI "(.)" "phase:4,deny,id:500250" ), match_log => { debug => [ qr/\]\[9\] /, 1 ], diff --git a/tests/regression/config/10-misc-directives.t b/tests/regression/config/10-misc-directives.t index 62a5e8e..86c1aed 100644 --- a/tests/regression/config/10-misc-directives.t +++ b/tests/regression/config/10-misc-directives.t @@ -26,21 +26,6 @@ ), }, -# SecServerSignature -{ - type => "config", - comment => "SecServerSignature On", - conf => qq( - SecServerSignature "NewServerSignature" - ), - match_response => { - status => qr/^200$/, - raw => qr/^Server: +NewServerSignature$/m, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", - ), -}, # SecDataDir { @@ -117,29 +102,4 @@ Upload File ), }, -# SecWebAppId -{ - type => "config", - comment => "SecWebAppId", - conf => qq( - SecRuleEngine On - SecRequestBodyAccess On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 4 - SecAuditLog "$ENV{AUDIT_LOG}" - SecAuditEngine RelevantOnly - SecWebAppId "app-1" - SecAction "pass,log,auditlog,id:1" - ), - match_log => { - error => [ qr/Warning\. Unconditional match in SecAction\./, 1 ], - debug => [ qr/Warning\. Unconditional match in SecAction\./, 1 ], - audit => [ qr/^WebApp-Info: "app-1"/m, 1 ], - }, - match_response => { - status => qr/^200$/, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", - ), -}, + diff --git a/tests/regression/misc/10-tfn-cache.t b/tests/regression/misc/10-tfn-cache.t deleted file mode 100644 index 672b610..0000000 --- a/tests/regression/misc/10-tfn-cache.t +++ /dev/null @@ -1,187 +0,0 @@ -### Transformation Caching - -{ - type => "misc", - comment => "tfncache (simple fully cached)", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 9 - - # We need to make this work no matter what the defaults may change to - SecCacheTransformations On "minlen:1,maxlen:0" - - # This should cache it - SecRule ARGS_GET "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog,id:500037" - - # This should use the cached value - SecRule ARGS_GET:test "foobar" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,deny,id:500038" - ), - match_log => { - debug => [ qr/removeWhiteSpace,lowercase: "foobar" .*cached/, 1 ], - -debug => [ qr/partially cached/, 1 ], - }, - match_response => { - status => qr/^403$/, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html?test=Foo+Bar", - ), -}, -{ - type => "misc", - comment => "tfncache (simple partially cached)", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 9 - - # We need to make this work no matter what the defaults may change to - SecCacheTransformations On "minlen:1,maxlen:0,incremental:off,maxitems:0" - - # This should cache it - SecRule ARGS_GET "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,pass,nolog,id:500039" - - # This should use the partially cached value - SecRule ARGS_GET:test "foobar" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,deny,id:500040" - ), - match_log => { - debug => [ qr/removeWhiteSpace: "FooBar" .*partially cached/, 1 ], - }, - match_response => { - status => qr/^403$/, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html?test=Foo+Bar", - ), -}, -{ - type => "misc", - comment => "tfncache (separate phases)", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 9 - - # We need to make this work no matter what the defaults may change to - SecCacheTransformations On "minlen:1,maxlen:0" - - # This should cache it - SecRule ARGS_GET "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog,id:500041" - - # This should use the cached value - SecRule ARGS_GET:test "foobar" "phase:2,t:none,t:removeWhiteSpace,t:lowercase,deny,id:500042" - ), - match_log => { - -debug => [ qr/removeWhiteSpace,lowercase: "foobar" .*cached/, 1 ], - }, - match_response => { - status => qr/^403$/, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html?test=Foo+Bar", - ), -}, -{ - type => "misc", - comment => "tfncache (non-modifying tfns cached)", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 9 - - # We need to make this work no matter what the defaults may change to - SecCacheTransformations On "minlen:1,maxlen:0" - - # This should cache it - SecRule ARGS_GET "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog,id:500043" - - # This should use the cached value - SecRule ARGS_GET:test "foobar" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,deny,id:500044" - ), - match_log => { - debug => [ qr/removeWhiteSpace,lowercase: "foobar" .*cached/, 1 ], - }, - match_response => { - status => qr/^403$/, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html?test=foo+bar", - ), -}, -{ - type => "misc", - comment => "tfncache (unique keys)", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 9 - SecRequestBodyAccess On - - # We need to make this work no matter what the defaults may change to - SecCacheTransformations On "minlen:1,maxlen:0" - - # This should cache it - SecRule ARGS "WillNotMatch" "phase:2,t:none,t:removeWhiteSpace,t:lowercase,pass,id:500045" - - # This should see cached versions of *both* ARGS_GET - SecRule ARGS:test "queryval" "phase:2,t:none,t:removeWhiteSpace,t:lowercase,deny,chain,id:500046" - SecRule ARGS:test "firstval" "t:none,t:removeWhiteSpace,t:lowercase,chain" - SecRule ARGS:test "secondval" "t:none,t:removeWhiteSpace,t:lowercase" - ), - match_log => { - debug => [ qr/removeWhiteSpace,lowercase: "queryval" .*removeWhiteSpace,lowercase: "firstval" .*cached.*removeWhiteSpace,lowercase: "secondval" .*cached/s, 1 ], - }, - match_response => { - status => qr/^403$/, - }, - request => new HTTP::Request( - POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html?test=Query+Val", - [ - "Content-Type" => "application/x-www-form-urlencoded", - ], - # Args - "test=First+Val&test=Second+Val", - ), -}, -{ - type => "misc", - comment => "tfncache (large cache)", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 9 - SecRequestBodyAccess On - - SecRequestBodyNoFilesLimit 1048576 - SecRequestBodyInMemoryLimit 131072 - SecResponseBodyLimit 1048576 - - # We need to make this work no matter what the defaults may change to - SecCacheTransformations On "minlen:1,maxlen:0,maxitems:0" - - # This should cache it in all phases - SecRule ARGS "WillNotMatch" "phase:1,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog,id:500048" - SecRule ARGS "WillNotMatch" "phase:2,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog,id:500049" - SecRule ARGS "WillNotMatch" "phase:3,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog,id:500050" - SecRule ARGS "WillNotMatch" "phase:4,t:none,t:removeWhiteSpace,t:lowercase,pass,nolog,id:500051" - - # This should use the cached value - SecRule ARGS "foobar" "phase:4,t:none,t:removeWhiteSpace,t:lowercase,deny,id:500052" - ), - match_log => { - debug => [ qr/Adding request argument \(BODY\): name "test", value "Foo Bar"/, 60, "Waiting for httpd to process request: "], - -error => [ qr/segmentation fault/i, 60 ], - }, - match_response => { - status => qr/^403$/, - }, - request => new HTTP::Request( - POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html", - [ - "Content-Type" => "application/x-www-form-urlencoded", - ], - # 1000 Args - join("&", map { sprintf "arg%08d=0123456789abcdef+0123456789ABCDEF+0123456789abcdef", $_ } (1 .. 1000))."&test=Foo+Bar", - ), -}, diff --git a/tests/regression/misc/20-status-engine.t b/tests/regression/misc/20-status-engine.t index a8ec6f3..5fffe79 100644 --- a/tests/regression/misc/20-status-engine.t +++ b/tests/regression/misc/20-status-engine.t @@ -46,78 +46,4 @@ "arg1=val1&arg2=val2", ), }, -# On and SecServerSignature -{ - type => "misc", - comment => "SecStatusEngine On using SecServerSignature", - conf => qq( - SecRuleEngine On - SecServerSignature "SpiderServer v0.1a" - SecStatusEngine On - ), - match_log => { - error => [ qr/ModSecurity: StatusEngine call successfully sent/, 1], - -error => [ qr/StatusEngine call: .*SpiderServer v0.1a.*/, 1], - }, - match_response => { - status => qr/^200$/, - }, - request => new HTTP::Request( - POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", - [ - "Content-Type" => "application/x-www-form-urlencoded", - ], - "arg1=val1&arg2=val2", - ), -}, -# On and SecServerSignature -{ - type => "misc", - comment => "SecStatusEngine On/SecServerSignature - checking signature", - conf => qq( - SecRuleEngine On - SecServerSignature "SpiderServer v0.1a" - SecStatusEngine On - ), - match_log => { - error => { - apache => [ qr/StatusEngine call: \"[0-9]+.[0-9]+.[0-9]+[-RC]*[0-9]*\,Apache/, 1], - nginx => [ qr/StatusEngine call: \"[0-9]+.[0-9]+.[0-9]+[-RC]*[0-9]*\,nginx/, 1], - } - }, - match_response => { - status => qr/^200$/, - }, - request => new HTTP::Request( - POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", - [ - "Content-Type" => "application/x-www-form-urlencoded", - ], - "arg1=val1&arg2=val2", - ), -}, -# On and SecServerSignature -{ - type => "misc", - comment => "SecStatusEngine On - checking signature", - conf => qq( - SecStatusEngine On - ), - match_log => { - error => { - apache => [ qr/StatusEngine call: \"[0-9]+.[0-9]+.[0-9]+[-RC]*[0-9]*\,Apache/, 1], - nginx => [ qr/StatusEngine call: \"[0-9]+.[0-9]+.[0-9]+[-RC]*[0-9]*\,nginx/, 1], - } - }, - match_response => { - status => qr/^200$/, - }, - request => new HTTP::Request( - POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", - [ - "Content-Type" => "application/x-www-form-urlencoded", - ], - "arg1=val1&arg2=val2", - ), -}, diff --git a/tests/regression/misc/30-fuzzyHash.t b/tests/regression/misc/30-fuzzyHash.t deleted file mode 100644 index fcd1a89..0000000 --- a/tests/regression/misc/30-fuzzyHash.t +++ /dev/null @@ -1,180 +0,0 @@ -### libinjection. - -{ - type => "misc", - comment => "fuzzyHash test", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 9 - SecRequestBodyAccess On - - SecRule REQUEST_BODY "\@fuzzyHash $ENV{CONF_DIR}/ssdeep.txt 1" "id:192372,log,deny" - ), - match_log => { - error => [ qr/ModSecurity: Access denied with code 403 \(phase 2\)\. Fuzzy hash of REQUEST_BODY matched with 96:MbQ1L0LDX8GPI8ov3D2D9zd6.*"modsecurity.conf-recommended"|ModSecurity was not compiled with ssdeep support./, 1], - debug => [ qr/Access denied with code 403 \(phase 2\)\. Fuzzy hash of REQUEST_BODY matched with 96:MbQ1L0LDX8GPI8ov3D2D9zd6.*"modsecurity.conf-recommended"|ModSecurity was not compiled with ssdeep support./, 1], - }, - match_response => { - status => qr/^403|200$/, - }, - request => new HTTP::Request( - POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html", - [ - "Content-Type" => "application/x-www-form-urlencoded", - ], - # Args - " -# -- Rule engine initialization ---------------------------------------------- - -# Enable ModSecurity, attaching it to every transaction. Use detection -# only to start with, because that minimises the chances of post-installation -# disruption. -# -SecRuleEngine DetectionOnly - - -# -- Request body handling --------------------------------------------------- - -# Allow ModSecurity to access request bodies. If you don't, ModSecurity -# won't be able to see any POST parameters, which opens a large security -# hole for attackers to exploit. -# -SecRequestBodyAccess On - - -# Enable XML request body parser. -# Initiate XML Processor in case of xml content-type -# -SecRule REQUEST_HEADERS:Content-Type \"text/xml\" \ - \"id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML\" - -# Enable JSON request body parser. -# Initiate JSON Processor in case of JSON content-type; change accordingly -# if your application does not use 'application/json' -# -SecRule REQUEST_HEADERS:Content-Type \"application/json\" \ - \"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON\" - -# Maximum request body size we will accept for buffering. If you support -# file uploads then the value given on the first line has to be as large -# as the largest file you are willing to accept. The second value refers -# to the size of data, with files excluded. You want to keep that value as -# low as practical. -# -SecRequestBodyLimit 13107200 -SecRequestBodyNoFilesLimit 131072 - -# Store up to 128 KB of request body data in memory. When the multipart -# parser reachers this limit, it will start using your hard disk for -# storage. That is slow, but unavoidable. -# -SecRequestBodyInMemoryLimit 131072 - -# What do do if the request body size is above our configured limit. -# Keep in mind that this setting will automatically be set to ProcessPartial -# when SecRuleEngine is set to DetectionOnly mode in order to minimize -# disruptions when initially deploying ModSecurity. -# -SecRequestBodyLimitAction Reject - -# Verify that we've correctly processed the request body. -# As a rule of thumb, when failing to process a request body -# you should reject the request (when deployed in blocking mode) -# or log a high-severity alert (when deployed in detection-only mode). -# -SecRule REQBODY_ERROR \"!\@eq 0\" \ -\"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2\" - " - ), -}, - -{ - type => "misc", - comment => "fuzzyHash test", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 9 - SecRequestBodyAccess On - - SecRule REQUEST_BODY "\@fuzzyHash $ENV{CONF_DIR}/ssdeep.txt 1" "id:192372,log,deny" - ), - match_log => { - -error => [ qr/Fuzzy hash of REQUEST_BODY matched/, 1], - -debug => [ qr/Fuzzy hash of REQUEST_BODY matched/, 1], - }, - match_response => { - status => qr/^200$/, - }, - request => new HTTP::Request( - POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html", - [ - "Content-Type" => "application/x-www-form-urlencoded", - ], - # Args - " - wheee - " - ), -}, - -{ - type => "misc", - comment => "fuzzy hash with FILES_TMP_CONTENT", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 9 - SecRequestBodyAccess On - SecUploadKeepFiles On - - SecRule FILES_TMP_CONTENT "\@fuzzyHash $ENV{CONF_DIR}/ssdeep.txt 1" "id:192372,log,deny" - ), - match_log => { - debug => [ qr/operator \"fuzzyHash\" with param \".*ssdeep.txt 1\" against FILES_TMP_CONTENT:image1/s, 1 ], - }, - match_response => { - status => qr/^200$/, - }, - request => new HTTP::Request( - POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", - [ - "Content-Type" => q(multipart/form-data; boundary=0000), - ], - normalize_raw_request_data( - q( - --0000 - Content-Disposition: form-data; name="name" - - Brian Rectanus - --0000 - Content-Disposition: form-data; name="email" - - brian.rectanus@breach.com - --0000 - Content-Disposition: form-data; name="image1"; filename="image1.jpg" - Content-Type: image/jpeg - - BINARYDATA1 - --0000 - Content-Disposition: form-data; name="image2"; filename="image2.jpg" - Content-Type: image/jpeg - - BINARYDATA2 - --0000 - Content-Disposition: form-data; name="image3"; filename="image3.jpg" - Content-Type: image/jpeg - - BINARYDATA3 - --0000 - Content-Disposition: form-data; name="test" - - This is test data. - --0000-- - ), - ), - ), -}, - - diff --git a/tests/regression/rule/00-basics.t b/tests/regression/rule/00-basics.t index 36a7b09..94fca34 100644 --- a/tests/regression/rule/00-basics.t +++ b/tests/regression/rule/00-basics.t @@ -75,12 +75,12 @@ SecDebugLogLevel 5 SecDefaultAction "phase:2,log,noauditlog,pass,tag:foo" SecRule ARGS:test "value" "chain,phase:2,deny,status:403,id:500034" - SecRule &ARGS "\@eq 1" "chain,setenv:tx.foo=bar" + SecRule &ARGS "\@eq 1" "chain," SecRule REQUEST_METHOD "\@streq GET" ), match_log => { error => [ qr/ModSecurity: /, 1 ], - debug => [ qr/Rule [0-9a-f]+: SecRule "ARGS:test" "\@rx value" "phase:2,log,noauditlog,tag:foo,chain,deny,status:403,id:500034"\r?\n.*Rule [0-9a-f]+: SecRule "&ARGS" "\@eq 1" "chain,setenv:tx.foo=bar"\r?\n.*Rule [0-9a-f]+: SecRule "REQUEST_METHOD" "\@streq GET"\r?\n/s, 1 ], + debug => [ qr/Rule [0-9a-f]+: SecRule "ARGS:test" "\@rx value" "phase:2,log,noauditlog,tag:foo,chain,deny,status:403,id:500034"\r?\n.*Rule [0-9a-f]+:/ ] }, match_response => { status => qr/^403$/, diff --git a/tests/regression/rule/00-script.t b/tests/regression/rule/00-script.t deleted file mode 100644 index 8af6d7b..0000000 --- a/tests/regression/rule/00-script.t +++ /dev/null @@ -1,63 +0,0 @@ -### Test for SecRuleScript - -# Lua -{ - type => "rule", - comment => "SecRuleScript (lua absolute nomatch)", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 1 - SecRuleScript "$ENV{CONF_DIR}/test.lua" "phase:2,deny" - ), - match_log => { - -error => [ qr/Lua script matched\./, 1 ], - debug => [ qr/Test message\./, 1 ], - }, - match_response => { - status => qr/^200$/, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", - ), -}, -{ - type => "rule", - comment => "SecRuleScript (lua relative nomatch)", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 1 - SecRuleScript "test.lua" "phase:2,deny" - ), - match_log => { - -error => [ qr/Lua script matched\./, 1 ], - debug => [ qr/Test message\./, 1 ], - }, - match_response => { - status => qr/^200$/, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", - ), -}, -{ - type => "rule", - comment => "SecRuleScript (lua relative match)", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 1 - SecRuleScript "match.lua" "phase:2,deny" - ), - match_log => { - error => [ qr/ModSecurity: Access denied with code 403 \(phase 2\)\. Lua script matched\./, 1 ], - debug => [ qr/Test message\./, 1 ], - }, - match_response => { - status => qr/^403$/, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", - ), -}, diff --git a/tests/regression/rule/20-exceptions.t b/tests/regression/rule/20-exceptions.t index 675a640..71e3c39 100644 --- a/tests/regression/rule/20-exceptions.t +++ b/tests/regression/rule/20-exceptions.t @@ -126,51 +126,4 @@ ), }, -# SecRuleUpdateActionById -{ - type => "rule", - comment => "SecRuleUpdateActionById", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 9 - SecRule REQUEST_URI "test" "phase:1,deny,status:500,msg:'testing rule',id:500002" - SecRuleUpdateActionById 500002 "pass,nolog" - ), - match_log => { - -error => [ qr/500002/, 1 ], - -audit => [ qr/./, 1 ], - debug => [ qr/id:500002,pass,nolog/, 1 ], - -debug => [ qr/Access denied/, 1 ], - }, - match_response => { - status => qr/^200$/, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", - ), -}, -{ - type => "rule", - comment => "SecRuleUpdateActionById (chain)", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 9 - SecRule REQUEST_URI "test" "phase:1,deny,status:500,msg:'testing rule',chain,id:500003" - SecRule ARGS "bar" - SecRuleUpdateActionById 500003 "pass,nolog" - ), - match_log => { - -error => [ qr/500003/, 1 ], - -audit => [ qr/./, 1 ], - debug => [ qr/id:500003,pass,nolog/, 1 ], - -debug => [ qr/Access denied/, 1 ], - }, - match_response => { - status => qr/^200$/, - }, - request => new HTTP::Request( - GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?foo=bar", - ), -}, + From 97ebce6524f9f087d2346c5aaac9f8008dddb7ff Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Sun, 20 Aug 2017 15:15:37 -0300 Subject: [PATCH 03/35] tests: Adds apache-v3 as a new platform run-regression-tests script was changed accordingly. --- tests/regression/config/00-load-modsec.t | 2 ++ tests/run-regression-tests.pl.in | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/regression/config/00-load-modsec.t b/tests/regression/config/00-load-modsec.t index 222120d..49a4bee 100644 --- a/tests/regression/config/00-load-modsec.t +++ b/tests/regression/config/00-load-modsec.t @@ -3,6 +3,7 @@ comment => "module loaded", match_log => { error => { + apache_v3 => [ qr/ModSecurity-Apache/, 10 ], apache => [ qr/ModSecurity for Apache.* configured\./, 10 ], nginx => [ qr/ModSecurity for nginx.* configured\./, 10 ], }, @@ -22,6 +23,7 @@ }, match_log => { error => { + apache_v3 => [ qr/ModSecurity-Apache/, 10 ], apache => [ qr/ModSecurity for Apache.* configured\./, 10 ], nginx => [ qr/ModSecurity for nginx.* configured\./, 10 ], }, diff --git a/tests/run-regression-tests.pl.in b/tests/run-regression-tests.pl.in index b50e75a..4dfb413 100755 --- a/tests/run-regression-tests.pl.in +++ b/tests/run-regression-tests.pl.in @@ -51,7 +51,8 @@ if ($HTTPD eq "\@APXS_HTTPD\@") { $SIG{TERM} = $SIG{INT} = \&handle_interrupt; -my $platform = "apache"; +my $platform = "apache_v3"; +my $platform_alternative = "apache"; my %opt; getopts('A:E:D:C:T:H:a:p:dvh', \%opt); @@ -236,6 +237,9 @@ sub runfile { if ($m->{$platform}) { $m = $m->{$platform}; } + elsif ($m->{$platform_alternative}) { + $m = $m->{$platform_alternative}; + } else { my $ap = join(", ", keys %{$m}); msg("Warning: trying to match: $mtype. Nothing " . From 4e8854c46bd3c216d7834ab94605f45114794791 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Sun, 20 Aug 2017 19:18:33 -0300 Subject: [PATCH 04/35] Updates the status variable bu logging phase --- src/mod_security3.c | 1 + src/msc_filters.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mod_security3.c b/src/mod_security3.c index c347252..587edcb 100644 --- a/src/mod_security3.c +++ b/src/mod_security3.c @@ -414,6 +414,7 @@ static int hook_log_transaction(request_rec *r) return DECLINED; } + msc_update_status_code(msr->t, r->status); msc_process_logging(msr->t); it = process_intervention(msr->t, r); if (it != N_INTERVENTION_STATUS) diff --git a/src/msc_filters.c b/src/msc_filters.c index 8e274c9..ab9b9dd 100644 --- a/src/msc_filters.c +++ b/src/msc_filters.c @@ -108,7 +108,7 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) msc_add_response_header(msr->t, key, val); } - msc_process_response_headers(msr->t, 200, "HTTP 1.1"); + msc_process_response_headers(msr->t, r->status, "HTTP 1.1"); it = process_intervention(msr->t, r); if (it != N_INTERVENTION_STATUS) From 7a410f29389f1eee757fddd782bfcc4ede687e0e Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Fri, 22 Dec 2017 12:46:28 -0300 Subject: [PATCH 05/35] travis: Increases Apache version to 2.4.29 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ba3bb96..5d4aa71 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ addons: env: - VER_APACHE=2.2.34 - - VER_APACHE=2.4.27 + - VER_APACHE=2.4.29 before_script: - curl -L https://cpanmin.us | sudo perl - --sudo App::cpanminus From 150a631387d08c50ad493f851e401b27b3a46ad4 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Fri, 22 Dec 2017 14:13:42 -0300 Subject: [PATCH 06/35] Adds release.sh script --- release.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100755 release.sh diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..beaf9f5 --- /dev/null +++ b/release.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +git clean -xfdi +git submodule foreach --recursive git clean -xfdi + +VERSION=`git describe --tags` +DIR_NAME="modsecurity-apache-$VERSION" +TAR_NAME="modsecurity-apache-$VERSION.tar.gz" + +MY_DIR=${PWD##*/} + +cd .. +tar --transform "s/^$MY_DIR/$DIR_NAME/" -cvzf $TAR_NAME --exclude .git $MY_DIR + +sha256sum $TAR_NAME > $TAR_NAME.sha256 +gpg --detach-sign -a $TAR_NAME + +cd - +echo $TAR_NAME ": done." + From 5431a7a71c68564df61b7500572e659242378da0 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Fri, 22 Dec 2017 14:18:35 -0300 Subject: [PATCH 07/35] Adds AUTHORS file --- AUTHORS | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 AUTHORS diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..f6fa474 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,3 @@ +tahirramzan = Tahir Ramzan +zimmerle = Felipe Zimmerle +csanders-git = Chaim Sanders From 2a760386b8cdecca2b1db9260fe281b87d9a2998 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Fri, 22 Dec 2017 14:19:44 -0300 Subject: [PATCH 08/35] Restores LICENSE to its original content. That is just an example file on how to fill the copyright, not the actual copyright. no point to have something different from the original. --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index d0f53e8..261eeb9 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2016 Chaim sanders + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 2d84005b69dc16255bc6f89c47f2606daae55d67 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Fri, 22 Dec 2017 14:22:07 -0300 Subject: [PATCH 09/35] Adds CHANGES file --- CHANGES | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 CHANGES diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..c79d207 --- /dev/null +++ b/CHANGES @@ -0,0 +1,6 @@ + +v0.0.9-beta1 - 2017-Dec-22 +-------------------------- + + - First version of ModSecurity-apache connector + From 0b83daf025e0e4c972c19bb247ac0d68396531ac Mon Sep 17 00:00:00 2001 From: Victor Hora Date: Mon, 6 Nov 2017 18:02:13 -0500 Subject: [PATCH 10/35] Fix duplicate HTTP protocol strings --- CHANGES | 6 ++++++ src/mod_security3.c | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index c79d207..99c90f1 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +v3.0.x - YYYY-MMM-DD (To be released) +------------------------------------- + + - Fix duplicate HTTP protocol strings + [Issue #21 - @victorhora] + v0.0.9-beta1 - 2017-Dec-22 -------------------------- diff --git a/src/mod_security3.c b/src/mod_security3.c index 587edcb..9d09d06 100644 --- a/src/mod_security3.c +++ b/src/mod_security3.c @@ -387,6 +387,8 @@ static int hook_request_late(request_rec *r) return it; } #endif + + msc_process_request_body(msr->t); it = process_intervention(msr->t, r); if (it != N_INTERVENTION_STATUS) @@ -464,8 +466,9 @@ static int process_request_headers(request_rec *r, msc_t *msr) { /* process uri */ { int it; - msc_process_uri(msr->t, r->unparsed_uri, r->method, r->protocol); + int offset = (r->protocol && strlen(r->protocol) > 5 && r->protocol[0] == 'H') ? 5 : 0; + msc_process_uri(msr->t, r->unparsed_uri, r->method, r->protocol + offset); it = process_intervention(msr->t, r); if (it != N_INTERVENTION_STATUS) { From 8086784004cb6aeaf09c10900b52a74dbdb16ca2 Mon Sep 17 00:00:00 2001 From: Victor Hora Date: Thu, 22 Mar 2018 14:07:35 -0400 Subject: [PATCH 11/35] travis: Update URL for Apache to account for EOL 2.2.x --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5d4aa71..b9953d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ env: before_script: - curl -L https://cpanmin.us | sudo perl - --sudo App::cpanminus - cd .. - - curl https://www.apache.org/dist/httpd/httpd-${VER_APACHE}.tar.gz > httpd-${VER_APACHE}.tar.gz + - curl https://archive.apache.org/dist/httpd/httpd-${VER_APACHE}.tar.gz > httpd-${VER_APACHE}.tar.gz - tar xvzf httpd-${VER_APACHE}.tar.gz - cd httpd-${VER_APACHE} - ./configure --with-mpm=worker From 26a2eab1a65aed772b7b2f17040c8f6a30c858ae Mon Sep 17 00:00:00 2001 From: Athmane Madjoudj Date: Sat, 28 Oct 2017 13:27:57 +0100 Subject: [PATCH 12/35] Add support for 64bit systems when looking for libmodsecurity --- build/find_libmodsec.m4 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build/find_libmodsec.m4 b/build/find_libmodsec.m4 index 2a6c82d..4956195 100644 --- a/build/find_libmodsec.m4 +++ b/build/find_libmodsec.m4 @@ -18,11 +18,15 @@ if test -z "$V3PATH"; then /usr/local/sbin \ /usr/local/bin \ /usr/sbin \ - /usr/bin; + /usr/bin \ + /usr; do if test -f "$i/lib/libmodsecurity.so"; then V3LIB="$i/lib/" fi + if test -f "$i/lib64/libmodsecurity.so"; then + V3LIB="$i/lib64/" + fi if test -f "$i/include/modsecurity/modsecurity.h"; then V3INCLUDE="$i/include/" # TODO: test if V3LIB is set From a755b17f91f8849cbf04d9773ef3d895ffcf2704 Mon Sep 17 00:00:00 2001 From: JRemitz <18032103+JRemitz@users.noreply.github.com> Date: Wed, 24 Jan 2018 17:43:28 -0800 Subject: [PATCH 13/35] Added check for /lib/libmodsecurity.so when --with-libmodsecurity is used --- build/find_libmodsec.m4 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build/find_libmodsec.m4 b/build/find_libmodsec.m4 index 4956195..113f924 100644 --- a/build/find_libmodsec.m4 +++ b/build/find_libmodsec.m4 @@ -10,6 +10,9 @@ AC_ARG_WITH(libmodsecurity, V3PATH=/usr/local/modsecurity/ else V3PATH="$withval" + if test -f "${V3PATH}lib/libmodsecurity.so"; then + V3LIB="${V3PATH}lib/" + fi fi ]) From 967d69af8eee2664da4dc5c88398ec6dd0350728 Mon Sep 17 00:00:00 2001 From: JRemitz <18032103+JRemitz@users.noreply.github.com> Date: Wed, 24 Jan 2018 20:01:18 -0800 Subject: [PATCH 14/35] Updated to also account for included libmodsecurity header files --- build/find_libmodsec.m4 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build/find_libmodsec.m4 b/build/find_libmodsec.m4 index 113f924..9101dc0 100644 --- a/build/find_libmodsec.m4 +++ b/build/find_libmodsec.m4 @@ -13,6 +13,9 @@ AC_ARG_WITH(libmodsecurity, if test -f "${V3PATH}lib/libmodsecurity.so"; then V3LIB="${V3PATH}lib/" fi + if test -f "${V3PATH}include/modsecurity/modsecurity.h"; then + V3INCLUDE="${V3PATH}include/" + fi fi ]) From 8bb515e93ddf0209f196fef2fa5f0a255e34de24 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Tue, 30 Oct 2018 11:42:13 -0300 Subject: [PATCH 15/35] Uses mod_unique if available If mod_unique is available on the platform, the transaction ids will be labeled respecting it. Otherwise, the library will handle it internally. --- CHANGES | 2 ++ src/mod_security3.c | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 99c90f1..7668e7b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ v3.0.x - YYYY-MMM-DD (To be released) ------------------------------------- + - Uses mod_unique if available + [Issue #42 - @zimmerle, @victorhora, @Goron1606] - Fix duplicate HTTP protocol strings [Issue #21 - @victorhora] diff --git a/src/mod_security3.c b/src/mod_security3.c index 9d09d06..703a38e 100644 --- a/src/mod_security3.c +++ b/src/mod_security3.c @@ -1,4 +1,6 @@ +#include + #include "mod_security3.h" #include "msc_utils.h" #include "msc_config.h" @@ -132,6 +134,8 @@ static void store_tx_context(msc_t *msr, request_rec *r) static msc_t *create_tx_context(request_rec *r) { msc_t *msr = NULL; msc_conf_t *z = NULL; + char *unique_id = NULL; + z = (msc_conf_t *)ap_get_module_config(r->per_dir_config, &security3_module); @@ -141,7 +145,14 @@ static msc_t *create_tx_context(request_rec *r) { } msr->r = r; - msr->t = msc_new_transaction(msc_apache->modsec, (Rules *)z->rules_set, (void *)r); + unique_id = getenv("UNIQUE_ID"); + if (unique_id != NULL || strlen(unique_id) > 0) { + msr->t = msc_new_transaction_with_id(msc_apache->modsec, + (Rules *)z->rules_set, unique_id, (void *)r); + } else { + msr->t = msc_new_transaction(msc_apache->modsec, + (Rules *)z->rules_set, (void *)r); + } store_tx_context(msr, r); From 06273d1b40d9b13b5061c6dcc4d15e0a633e03a0 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Tue, 30 Oct 2018 12:33:04 -0300 Subject: [PATCH 16/35] Adds make install to the build system --- CHANGES | 2 ++ Makefile.am | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/CHANGES b/CHANGES index 7668e7b..58342f0 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ v3.0.x - YYYY-MMM-DD (To be released) ------------------------------------- + - Adds make install to the build system + [Issue #36 - @zimmerle, @ROBERT-MCDOWELL] - Uses mod_unique if available [Issue #42 - @zimmerle, @victorhora, @Goron1606] - Fix duplicate HTTP protocol strings diff --git a/Makefile.am b/Makefile.am index e7e6b2f..dec9bc2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -31,4 +31,9 @@ all: test: cd t/ && ./TEST + +install-exec-hook: $(pkglib_LTLIBRARIES) + @APXS@ -i -n mod_security3 ./src/.libs/mod_security3.so + + .PHONY: all From 96fbca43a49c977d12f2a86e2a5ba722b04f0597 Mon Sep 17 00:00:00 2001 From: Victor Hora Date: Mon, 6 Nov 2017 18:57:55 -0500 Subject: [PATCH 17/35] Proposed fix for msc_process_request_body call too early --- CHANGES | 2 ++ src/mod_security3.c | 5 ++--- src/msc_filters.c | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 58342f0..e50ec20 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ v3.0.x - YYYY-MMM-DD (To be released) ------------------------------------- + - Proposed fix for msc_process_request_body call too early + [Issue #22 - @victorhora] - Adds make install to the build system [Issue #36 - @zimmerle, @ROBERT-MCDOWELL] - Uses mod_unique if available diff --git a/src/mod_security3.c b/src/mod_security3.c index 703a38e..6198204 100644 --- a/src/mod_security3.c +++ b/src/mod_security3.c @@ -398,9 +398,8 @@ static int hook_request_late(request_rec *r) return it; } #endif - - - msc_process_request_body(msr->t); +// FIXME: memsc_append_request_body wasn't called yet. Too early? +// msc_process_request_body(msr->t); it = process_intervention(msr->t, r); if (it != N_INTERVENTION_STATUS) { diff --git a/src/msc_filters.c b/src/msc_filters.c index ab9b9dd..6559eb2 100644 --- a/src/msc_filters.c +++ b/src/msc_filters.c @@ -62,6 +62,9 @@ apr_status_t input_filter(ap_filter_t *f, apr_bucket_brigade *pbbOut, return send_error_bucket(msr, f, it); } + // FIXME: Now we should have the body. Is this sane? + msc_process_request_body(msr->t); + pbktOut = apr_bucket_heap_create(data, len, 0, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(pbbOut, pbktOut); apr_bucket_delete(pbktIn); From 60be4aaa03ba7e15bf02f501b3ada78f2d857e04 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Tue, 4 Dec 2018 10:31:45 -0300 Subject: [PATCH 18/35] README: Romoves old info about libModSec. Issue #45 --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 1f7db0a..e9a1a1d 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,6 @@ Notice that this project depends on libmodsecurity rather than ModSecurity (vers The old version of ModSecurity was origionally designed for and contained within an Apache module. This current version abstracts out some of the details allowing ModSecurity to more easily support multiple platforms and features outside beyond the scope of what Apache internals currently support. As a result using the new libmodsecurity engine is no longer reliant on the use of Apache and can be used to power multiple different connectors. As a result of this the current version is more flexible, has wider support, and allows for the support of new functionality that was not previously possible. -NOTICE: libmodsecurity is not feature complete and it is not considerable -stable, so use is undertaken at your own risk. # Compilation From 61f2ff14f690da6c05f0e7053bb41b430ae8b732 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Tue, 4 Dec 2018 12:33:42 -0300 Subject: [PATCH 19/35] Fix: avoids crash if UNIQUE_ID is not set Issue #46 --- CHANGES | 2 ++ src/mod_security3.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index e50ec20..5d64923 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ v3.0.x - YYYY-MMM-DD (To be released) ------------------------------------- + - Fix: avoids crash if UNIQUE_ID is not set + [Issue #46 - @Goron1606, @victorhora, @zimmerle] - Proposed fix for msc_process_request_body call too early [Issue #22 - @victorhora] - Adds make install to the build system diff --git a/src/mod_security3.c b/src/mod_security3.c index 6198204..4ee98c6 100644 --- a/src/mod_security3.c +++ b/src/mod_security3.c @@ -146,7 +146,7 @@ static msc_t *create_tx_context(request_rec *r) { msr->r = r; unique_id = getenv("UNIQUE_ID"); - if (unique_id != NULL || strlen(unique_id) > 0) { + if (unique_id != NULL && strlen(unique_id) > 0) { msr->t = msc_new_transaction_with_id(msc_apache->modsec, (Rules *)z->rules_set, unique_id, (void *)r); } else { From a2e9307c1b9ee40794a14fa4d64984afd1568866 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Wed, 2 Jan 2019 09:13:36 -0300 Subject: [PATCH 20/35] Revert "Proposed fix for msc_process_request_body call too early" This reverts commit 96fbca43a49c977d12f2a86e2a5ba722b04f0597. --- CHANGES | 2 -- src/mod_security3.c | 5 +++-- src/msc_filters.c | 3 --- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 5d64923..eb33556 100644 --- a/CHANGES +++ b/CHANGES @@ -3,8 +3,6 @@ v3.0.x - YYYY-MMM-DD (To be released) - Fix: avoids crash if UNIQUE_ID is not set [Issue #46 - @Goron1606, @victorhora, @zimmerle] - - Proposed fix for msc_process_request_body call too early - [Issue #22 - @victorhora] - Adds make install to the build system [Issue #36 - @zimmerle, @ROBERT-MCDOWELL] - Uses mod_unique if available diff --git a/src/mod_security3.c b/src/mod_security3.c index 4ee98c6..f3ce7b0 100644 --- a/src/mod_security3.c +++ b/src/mod_security3.c @@ -398,8 +398,9 @@ static int hook_request_late(request_rec *r) return it; } #endif -// FIXME: memsc_append_request_body wasn't called yet. Too early? -// msc_process_request_body(msr->t); + + + msc_process_request_body(msr->t); it = process_intervention(msr->t, r); if (it != N_INTERVENTION_STATUS) { diff --git a/src/msc_filters.c b/src/msc_filters.c index 6559eb2..ab9b9dd 100644 --- a/src/msc_filters.c +++ b/src/msc_filters.c @@ -62,9 +62,6 @@ apr_status_t input_filter(ap_filter_t *f, apr_bucket_brigade *pbbOut, return send_error_bucket(msr, f, it); } - // FIXME: Now we should have the body. Is this sane? - msc_process_request_body(msr->t); - pbktOut = apr_bucket_heap_create(data, len, 0, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(pbbOut, pbktOut); apr_bucket_delete(pbktIn); From a7a8c974e710a467f340e67e9a83e1e7070629bc Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Thu, 3 Jan 2019 17:30:03 +0000 Subject: [PATCH 21/35] Fix libmodsecurity search in autoconf file --- build/find_libmodsec.m4 | 49 +++++++++++++---------------------------- 1 file changed, 15 insertions(+), 34 deletions(-) diff --git a/build/find_libmodsec.m4 b/build/find_libmodsec.m4 index 9101dc0..3c2bc9d 100644 --- a/build/find_libmodsec.m4 +++ b/build/find_libmodsec.m4 @@ -7,43 +7,24 @@ AC_ARG_WITH(libmodsecurity, [FILE is the path to libmodsecurity install dir; defaults to "/usr/local/modsecurity/".])], [ if test "$withval" = "yes"; then - V3PATH=/usr/local/modsecurity/ + AC_SUBST(CPPFLAGS, "$CPPFLAGS -I/usr/local/modsecurity/include/ -L/usr/local/modsecurity/lib/") + V3INCLUDE="/usr/local/modsecurity/include/" + V3LIB="/usr/local/modsecurity/lib/" else - V3PATH="$withval" - if test -f "${V3PATH}lib/libmodsecurity.so"; then - V3LIB="${V3PATH}lib/" - fi - if test -f "${V3PATH}include/modsecurity/modsecurity.h"; then - V3INCLUDE="${V3PATH}include/" - fi + AC_SUBST(CPPFLAGS, "$CPPFLAGS -I${withval}/include/ -L${withval}/lib/") + V3INCLUDE="${withval}/include/" + V3LIB="${withval}/lib/" fi ]) -if test -z "$V3PATH"; then - for i in /usr/local/modsecurity/ \ - /usr/local/sbin \ - /usr/local/bin \ - /usr/sbin \ - /usr/bin \ - /usr; - do - if test -f "$i/lib/libmodsecurity.so"; then - V3LIB="$i/lib/" - fi - if test -f "$i/lib64/libmodsecurity.so"; then - V3LIB="$i/lib64/" - fi - if test -f "$i/include/modsecurity/modsecurity.h"; then - V3INCLUDE="$i/include/" - # TODO: test if V3LIB is set - break - fi - done -fi -if test -n "$V3LIB" -a "$V3LIB" != "no" -a -x "$V3LIB" ; then - AC_MSG_NOTICE(found libmodsecurity at $V3LIB) -else - AC_MSG_ERROR(couldn't find libmodsecurity) -fi +dnl Check the ModSecurity libraries (modsecurity) + +AC_CHECK_LIB([modsecurity], [msc_init], [ + AC_DEFINE([HAVE_MODSECURITYLIB], [1], + [Define to 1 if you have the `libmodsecurity' library (-lmodsecurity).])], [ + AC_MSG_ERROR([ModSecurity libraries not found!])]) + +AC_CHECK_HEADERS([modsecurity/modsecurity.h], [], [ + AC_MSG_ERROR([ModSecurity headers not found...])]) ]) From 32ae829e258a4f0a47ffc6ed9635919159cab80e Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Mon, 7 Jan 2019 16:31:07 -0300 Subject: [PATCH 22/35] CHANGES: Adds info about #51 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index eb33556..d5b9125 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ v3.0.x - YYYY-MMM-DD (To be released) ------------------------------------- + - Fix libmodsecurity search in autoconf file. + [Issue #51 - @airween] - Fix: avoids crash if UNIQUE_ID is not set [Issue #46 - @Goron1606, @victorhora, @zimmerle] - Adds make install to the build system From e0e268e0c00be4e119171584b8428dffdb96ef09 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Fri, 4 Jan 2019 10:53:53 -0300 Subject: [PATCH 23/35] travis: Catch up with the changes proposed on #51 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b9953d6..80ca023 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,7 +54,7 @@ before_script: - export PATH="/usr/local/apache2/bin/:$PATH" - ls -la /usr/local/apache2/bin/ - ls -la /usr/local/apache2/include/ - - ./configure --with-apxs=/usr/local/apache2/bin/apxs + - ./configure --with-apxs=/usr/local/apache2/bin/apxs --with-libmodsecurity=/usr/local/modsecurity - make - sudo make install - pwd From de21b1f7babf4f223b2df677f4c2d325da57b4b5 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Wed, 9 Jan 2019 15:41:45 +0000 Subject: [PATCH 24/35] Small modifications in autotool related files: * detect the installed httpd * check used MPM * check its mode (shared, static) * build custom external config * fix the make test chain * added new SecRule to phase:2 step tests --- Makefile.am | 5 ++++- configure.ac | 28 ++++++++++++++++++++++++++++ t/conf/extra.conf.in | 4 ++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index dec9bc2..e4aedc1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,6 +15,7 @@ CLEANFILES = \ t/logs/* \ t/htdocs/index.html \ t/conf/extra.conf \ + t/conf/httpd.conf \ t/conf/apache_test_config.pm \ t/conf/httpd.conf \ src/*.lo \ @@ -29,7 +30,9 @@ all: build/apxs-wrapper test: - cd t/ && ./TEST + cd t/ && ./TEST -clean + cd t/ && ./TEST -configure + cd t/ && ./TEST -httpd_conf conf/httpd.conf -httpd @APACHE@ -apxs @APXS@ install-exec-hook: $(pkglib_LTLIBRARIES) diff --git a/configure.ac b/configure.ac index fbac276..bcb6096 100644 --- a/configure.ac +++ b/configure.ac @@ -13,6 +13,7 @@ AC_PATH_PROGS(PERL, [perl perl5], ) AC_SUBST(APXS) AC_SUBST(V3LIB) AC_SUBST(V3INCLUDE) +AC_SUBST(APACHE) # Some directories MSC_BASE_DIR=`pwd` @@ -24,6 +25,17 @@ MSC_REGRESSION_CONF_DIR="$MSC_REGRESSION_SERVERROOT_DIR/conf" MSC_REGRESSION_LOGS_DIR="$MSC_REGRESSION_SERVERROOT_DIR/logs" MSC_REGRESSION_DOCROOT_DIR="$MSC_REGRESSION_SERVERROOT_DIR/htdocs" +if test -f /etc/apache2/envvars +then + SERVER_MPM=`. /etc/apache2/envvars && $APACHE -V | grep Server\ MPM | awk '{print $3}'` + SERVER_MPM_MODE=`. /etc/apache2/envvars && $APACHE -M | grep mpm_${SERVER_MPM}_module | awk '{print $2}' | sed -e 's/@<:@\@{:@\@:}@@:>@//g'` + SERVER_AUTHZ_MODE=`. /etc/apache2/envvars && $APACHE -M | grep authz_core_module | awk '{print $2}' | sed -e 's/@<:@\@{:@\@:}@@:>@//g'` +else + SERVER_MPM=`$APACHE -V | grep Server\ MPM | awk '{print $3}'` + SERVER_MPM_MODE=`$APACHE -M | grep mpm_${SERVER_MPM}_module | awk '{print $2}' | sed -e 's/@<:@\@{:@\@:}@@:>@//g'` + SERVER_AUTHZ_MODE=`$APACHE -M | grep authz_core_module | awk '{print $2}' | sed -e 's/@<:@\@{:@\@:}@@:>@//g'` +fi + AC_SUBST(MSC_BASE_DIR) AC_SUBST(MSC_PKGBASE_DIR) AC_SUBST(MSC_TEST_DIR) @@ -33,7 +45,11 @@ AC_SUBST(MSC_REGRESSION_CONF_DIR) AC_SUBST(MSC_REGRESSION_LOGS_DIR) AC_SUBST(MSC_REGRESSION_DOCROOT_DIR) +AC_SUBST(SERVER_MPM) +AC_SUBST(SERVER_MPM_MODE) +AC_SUBST(SERVER_AUTHZ_MODE) +echo "Found Apache with MPM ${SERVER_MPM}, ${SERVER_MPM_MODE}." APXS_SBINDIR="`$APXS -q SBINDIR`" APXS_PROGNAME="`$APXS -q PROGNAME`" @@ -44,6 +60,18 @@ APXS_LIBEXECDIR="`$APXS -q LIBEXECDIR`" if test "xx$APXS_LIBEXECDIR" = "xx"; then APXS_LIBEXECDIR="`$APXS -q LIBDIR`/modules"; fi AC_SUBST(APXS_LIBEXECDIR) +# generating apache depends loadable modules +# authz_core required, if not static +# one mpm required, if not static +echo "" > t/conf/modules.conf + +if @<:@ ${SERVER_AUTHZ_MODE} == "shared" @:>@; then + echo "LoadModule authz_core_module ${APXS_LIBEXECDIR}/mod_authz_core.so" >> t/conf/modules.conf +fi +if @<:@ ${SERVER_MPM_MODE} == "shared" @:>@; then + echo "LoadModule mpm_${SERVER_MPM}_module ${APXS_LIBEXECDIR}/mod_mpm_${SERVER_MPM}.so" >> t/conf/modules.conf +fi +echo "" >> t/conf/modules.conf AC_CONFIG_FILES([\ diff --git a/t/conf/extra.conf.in b/t/conf/extra.conf.in index cb68896..eb267a4 100644 --- a/t/conf/extra.conf.in +++ b/t/conf/extra.conf.in @@ -2,6 +2,8 @@ CoreDumpDirectory /tmp/ +Include @ServerRoot@/.././t/conf/modules.conf + LoadModule security3_module "@ServerRoot@/.././src/.libs/mod_security3.so" @@ -26,6 +28,7 @@ modsecurity_rules 'SecDebugLogLevel 9' + modsecurity_rules 'SecResponseBodyAccess On' modsecurity_rules 'SecRule ARGS "evil" "phase:4,id:114,log,status:403,block,deny"' @@ -48,6 +51,7 @@ modsecurity_rules 'SecDebugLogLevel 9' + modsecurity_rules 'SecResponseBodyAccess On' modsecurity_rules 'SecRule ARGS "evil" "phase:4,id:1134,log,status:402,block,deny"' From cff1995a2f50c7368b3bc970d2ef6c12f164a989 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Wed, 9 Jan 2019 13:41:10 -0300 Subject: [PATCH 25/35] CHANGES: Adds more info about #51 --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index d5b9125..a52a99d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,7 @@ v3.0.x - YYYY-MMM-DD (To be released) ------------------------------------- - - Fix libmodsecurity search in autoconf file. + - Asorted fixies on libmodsecurity autoconf. [Issue #51 - @airween] - Fix: avoids crash if UNIQUE_ID is not set [Issue #46 - @Goron1606, @victorhora, @zimmerle] From 83c31de9abf9cd864737c7d473d2a233a44f83ff Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Fri, 11 Jan 2019 08:24:45 +0000 Subject: [PATCH 26/35] Added some other fixes to build workflow --- Makefile.am | 11 ++- build/ax_prog_apache.m4 | 10 ++- .../regression/misc/60-pmfromfile-external.t | 84 ------------------- 3 files changed, 18 insertions(+), 87 deletions(-) delete mode 100644 tests/regression/misc/60-pmfromfile-external.t diff --git a/Makefile.am b/Makefile.am index e4aedc1..980cd02 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,11 +18,18 @@ CLEANFILES = \ t/conf/httpd.conf \ t/conf/apache_test_config.pm \ t/conf/httpd.conf \ + t/conf/mime.types \ + t/conf/modules.conf \ src/*.lo \ - src/*.slo + src/*.slo \ + src/*.o \ + src/*.so \ + src/.libs/*.so + CLEANDIRECTORIES = \ - t/logs + t/logs \ + src/.libs all: diff --git a/build/ax_prog_apache.m4 b/build/ax_prog_apache.m4 index b1dd5c6..aad2f2d 100644 --- a/build/ax_prog_apache.m4 +++ b/build/ax_prog_apache.m4 @@ -114,6 +114,9 @@ AC_DEFUN([AX_PROG_APACHE], if test -z "$APACHE" ; then AC_PATH_PROG(APACHE, httpd, , /usr/local/apache/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/usr/local/apache2/bin) fi + if test -z "$APACHE" ; then + AC_PATH_PROG(APACHE, apache2, , /usr/local/apache/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/usr/local/apache2/bin) + fi AC_SUBST(APACHE) if test -z "$APACHE" ; then AC_MSG_ERROR("apache server executable not found"); @@ -149,7 +152,12 @@ AC_DEFUN([AX_PROG_APACHE], # # Find out if .so modules are in libexec/module.so or modules/module.so # - HTTP_ROOT=`$APACHE -V | grep HTTPD_ROOT | sed -e 's/.*"\(.*\)"/\1/'` + if test -f /etc/apache2/envvars + then + HTTP_ROOT=`. /etc/apache2/envvars && $APACHE -V | grep HTTPD_ROOT | sed -e 's/.*"\(.*\)"/\1/'` + else + HTTP_ROOT=`$APACHE -V | grep HTTPD_ROOT | sed -e 's/.*"\(.*\)"/\1/'` + fi AC_MSG_CHECKING(apache modules) for dir in libexec modules do diff --git a/tests/regression/misc/60-pmfromfile-external.t b/tests/regression/misc/60-pmfromfile-external.t deleted file mode 100644 index 2910ed1..0000000 --- a/tests/regression/misc/60-pmfromfile-external.t +++ /dev/null @@ -1,84 +0,0 @@ -### pmfromfile external resource - -{ - type => "misc", - comment => "pmfromfile", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 9 - SecRequestBodyAccess On - SecRule REQUEST_FILENAME "\@pmFromFile https://www.modsecurity.org/modsecurity-regression-test.txt" "id:'123',phase:2,log,pass,t:none" - ), - match_log => { - error => [ qr/ModSecurity: Warning. Matched phrase \"127.0.0.1\" at REQUEST_FILENAME./, 1], - debug => [ qr/Matched phrase \"127.0.0.1\" at REQUEST_FILENAME/, 1 ], - -error => [ qr/ModSecurity: Problems loading external resources:/, 1], - }, - match_response => { - status => qr/^404$/, - }, - request => new HTTP::Request( - POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", - [ - "Content-Type" => "application/x-www-form-urlencoded", - ], - # Args - "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" - ), -}, -{ - type => "misc", - comment => "pmfromfile - 404 download", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 9 - SecRequestBodyAccess On - SecRemoteRulesFailAction Warn - SecRule REQUEST_FILENAME "\@pmFromFile https://www.modsecurity.org/modsecurity-regression-test-404.txt" "id:'123',phase:2,log,pass,t:none" - - ), - match_log => { - error => [ qr/ModSecurity: Problems loading external resources: Failed to download: \"https:\/\/www.modsecurity.org\/modsecurity-regression-test-404.txt\" error: HTTP response code said error./, 1], - }, - match_response => { - status => qr/^404$/, - }, - request => new HTTP::Request( - POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", - [ - "Content-Type" => "application/x-www-form-urlencoded", - ], - # Args - "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" - ), -}, -{ - type => "misc", - comment => "pmfromfile - bad certificate name", - conf => qq( - SecRuleEngine On - SecDebugLog $ENV{DEBUG_LOG} - SecDebugLogLevel 9 - SecRequestBodyAccess On - SecRemoteRulesFailAction Warn - SecRule REQUEST_FILENAME "\@pmFromFile https://status.modsecurity.org/modsecurity-regression-test.txt" "id:'123',phase:2,log,pass,t:none" - - ), - match_log => { - error => [ qr/ModSecurity: Problems loading external resources: Failed to download: \"https:\/\/status.modsecurity.org\/modsecurity-regression-test.txt\" error: [SSL peer certificate or SSH remote key was not OK.|Couldn't connect to server.]/, 1], - }, - match_response => { - status => qr/^404$/, - }, - request => new HTTP::Request( - POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", - [ - "Content-Type" => "application/x-www-form-urlencoded", - ], - # Args - "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" - ), -}, - From 7a8c78f3d20a4848bdd7e6eb056e808a184b164e Mon Sep 17 00:00:00 2001 From: Victor Hora Date: Sun, 20 Jan 2019 12:54:52 -0500 Subject: [PATCH 27/35] Add basic usage docs Thanks to @porjo for reminding us to do that :) --- README.md | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/README.md b/README.md index e9a1a1d..9031160 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,89 @@ $ make $ sudo make install ``` +# Usage + +libModSecurity for Apache extends your configuration directives. In order to load the module into Apache add the following directive to your Apache configuration: + +``` +LoadModule security3_module modules/mod_security3.so +``` + +The module adds new directives to Apache and some them are documented below: + +modsecurity +----------- +**syntax:** *modsecurity on | off* + +**context:** *http, server, location* + +**default:** *off* + +Turns on or off ModSecurity functionality. +Note that this configuration directive is no longer related to the SecRule state. +Instead, it now serves solely as an Apache flag to enable or disable the module. + +modsecurity_rules_file +---------------------- +**syntax:** *modsecurity_rules_file <path to rules file>* + +**context:** *http, server, location* + +**default:** *no* + +Specifies the location of the ModSecurity configuration file, e.g.: + +``` +modsecurity on +modsecurity_rules_file /etc/my_modsecurity_rules.conf; +``` + +modsecurity_rules_remote +------------------------ +**syntax:** *modsecurity_rules_remote <key> <URL to rules>* + +**context:** *http, server, location* + +**default:** *no* + +Specifies from where (on the internet) a modsecurity configuration file will be downloaded. +It also specifies the key that will be used to authenticate to that server: + +``` +modsecurity on +modsecurity_rules_remote my-server-key https://my-own-server/rules/download; +``` + +modsecurity_rules +----------------- +**syntax:** *modsecurity_rules <modsecurity rule>* + +**context:** *http, server, location* + +**default:** *no* + +Allows for the direct inclusion of a ModSecurity rule into the Apache configuration. +The following example is loading rules from a file and injecting specific configurations per directory/alias: + +``` +TODO +``` + +modsecurity_transaction_id +-------------------------- +**syntax:** *modsecurity_transaction_id string* + +**context:** *http, server, location* + +**default:** *no* + +Allows to pass transaction ID from Apache instead of generating it in the library. +This can be useful for tracing purposes, e.g. consider this configuration: + +``` +TODO +``` + # Contributing As an open source project we invite (and encourage) anyone from the community to contribute to our project. This may take the form of: new From f76aaae5495291202404a0d2f548cfd2c025a9cb Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Fri, 11 Jan 2019 12:20:23 +0000 Subject: [PATCH 28/35] Fix make test --- src/msc_filters.c | 3 +++ t/conf/extra.conf.in | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/msc_filters.c b/src/msc_filters.c index ab9b9dd..3a18e21 100644 --- a/src/msc_filters.c +++ b/src/msc_filters.c @@ -62,6 +62,9 @@ apr_status_t input_filter(ap_filter_t *f, apr_bucket_brigade *pbbOut, return send_error_bucket(msr, f, it); } + // FIXME: Now we should have the body. Is this sane? + msc_process_request_body(msr->t); + pbktOut = apr_bucket_heap_create(data, len, 0, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(pbbOut, pbktOut); apr_bucket_delete(pbktIn); diff --git a/t/conf/extra.conf.in b/t/conf/extra.conf.in index eb267a4..6518559 100644 --- a/t/conf/extra.conf.in +++ b/t/conf/extra.conf.in @@ -20,6 +20,7 @@ modsecurity_rules 'SecDebugLogLevel 9' + modsecurity_rules 'SecRequestBodyAccess On' modsecurity_rules 'SecRule ARGS "evil" "phase:2,id:112,log,status:403,block,deny"' @@ -43,6 +44,7 @@ modsecurity_rules 'SecDebugLogLevel 9' + modsecurity_rules 'SecRequestBodyAccess On' modsecurity_rules 'SecRule ARGS "evil" "phase:2,id:112,log,status:402,block,deny"' From f692ea994e1d4ad0b3fb4cafa4f6a4909d756147 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Wed, 22 Apr 2020 10:01:05 -0300 Subject: [PATCH 29/35] Makes it workable with v3/master --- src/mod_security3.c | 4 ++-- src/mod_security3.h | 12 +++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/mod_security3.c b/src/mod_security3.c index f3ce7b0..76b3fb7 100644 --- a/src/mod_security3.c +++ b/src/mod_security3.c @@ -148,10 +148,10 @@ static msc_t *create_tx_context(request_rec *r) { unique_id = getenv("UNIQUE_ID"); if (unique_id != NULL && strlen(unique_id) > 0) { msr->t = msc_new_transaction_with_id(msc_apache->modsec, - (Rules *)z->rules_set, unique_id, (void *)r); + z->rules_set, unique_id, (void *)r); } else { msr->t = msc_new_transaction(msc_apache->modsec, - (Rules *)z->rules_set, (void *)r); + z->rules_set, (void *)r); } store_tx_context(msr, r); diff --git a/src/mod_security3.h b/src/mod_security3.h index 258d4eb..b1e9b28 100644 --- a/src/mod_security3.h +++ b/src/mod_security3.h @@ -3,7 +3,17 @@ #include #include +#if defined(MODSECURITY_CHECK_VERSION) +#if MODSECURITY_VERSION_NUM >= 304010 +#define MSC_USE_RULES_SET 1 +#endif +#endif + +#if defined(MSC_USE_RULES_SET) +#include +#else #include +#endif #include #include "apr_buckets.h" @@ -46,7 +56,7 @@ typedef struct typedef struct { - Rules *rules_set; + void *rules_set; int msc_state; char *name_for_debug; } msc_conf_t; From 726260d2ce328579c32765ff58038948e661dee6 Mon Sep 17 00:00:00 2001 From: Samy Mahmoudi Date: Tue, 20 Oct 2020 02:26:25 -0400 Subject: [PATCH 30/35] README: Remove four trailing spaces --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9031160..06c6364 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![](https://raw.githubusercontent.com/ZenHubIO/support/master/zenhub-badge.png)](https://zenhub.com) -The ModSecurity-apache connector is the connection point between Apache and libmodsecurity (ModSecurity v3). Said another way, this project provides a communication channel between Apache and libmodsecurity. This connector is required to use LibModSecurity with Apache. +The ModSecurity-apache connector is the connection point between Apache and libmodsecurity (ModSecurity v3). Said another way, this project provides a communication channel between Apache and libmodsecurity. This connector is required to use LibModSecurity with Apache. The ModSecurity-apache connector takes the form of an Apache module. The module simply serves as a layer of communication between Apache and ModSecurity. @@ -132,7 +132,7 @@ Please respect the coding style. Pull requests can include various commits, so p Please respect the coding style in use. Pull requests can include various commits, so provide one fix or one functionality per commit. Do not change anything outside the scope of your target work (e.g. coding style in a function that you have -passed by). +passed by). ### Don’t know where to start? @@ -149,7 +149,7 @@ You may also take a look at recent bug reports and open issues to get an idea of ### Testing your patch Along with the manual testing, we strongly recommend that you to use the Apache test -utility to make sure that you patch does not adversly affect the behavior or performance of Apache. +utility to make sure that you patch does not adversly affect the behavior or performance of Apache. The Apache testing tools are available on: http://httpd.apache.org/test/ @@ -158,7 +158,7 @@ To use those tests .... If you are facing problems getting your added functionality to pass all the Apache tests, feel free to contact us or the Apache mailing list at: http://httpd.apache.org/lists.html -### Debugging +### Debugging Because the ModSecurity Apache Connector runs as part of Apache, one needs to debug the Apache process. Debugging may require several steps. In general debugging can be enabled by compiling the Apache connector with debugging as follows: ```CFLAGS="-g -O0" ./configure ...normal configure parameters...)``` From b907ffa11e1b03f88612c10801950c300416cd02 Mon Sep 17 00:00:00 2001 From: Samy Mahmoudi Date: Tue, 20 Oct 2020 02:28:27 -0400 Subject: [PATCH 31/35] README: Correct four typos --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 06c6364..50902d9 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Notice that this project depends on libmodsecurity rather than ModSecurity (vers ### What is the difference between this project and the old ModSecurity module for Apache? -The old version of ModSecurity was origionally designed for and contained within an Apache module. This current version abstracts out some of the details allowing ModSecurity to more easily support multiple platforms and features outside beyond the scope of what Apache internals currently support. As a result using the new libmodsecurity engine is no longer reliant on the use of Apache and can be used to power multiple different connectors. As a result of this the current version is more flexible, has wider support, and allows for the support of new functionality that was not previously possible. +The old version of ModSecurity was originally designed for and contained within an Apache module. This current version abstracts out some of the details allowing ModSecurity to more easily support multiple platforms and features outside beyond the scope of what Apache internals currently support. As a result using the new libmodsecurity engine is no longer reliant on the use of Apache and can be used to power multiple different connectors. As a result of this the current version is more flexible, has wider support, and allows for the support of new functionality that was not previously possible. # Compilation @@ -122,7 +122,7 @@ are willing to help with. Thank you. ## Providing Patches -We prefer to have your patch within the GtiHub infrastructure to facilitate our +We prefer to have your patch within the GitHub infrastructure to facilitate our review work, and our QA integration. GitHub provides an excellent documentation on how to perform “Pull Requests”. More information available here: https://help.github.com/articles/using-pull-requests/ @@ -186,7 +186,7 @@ security@modsecurity.org to report the issue. Once the problem is fixed we will ## Feature Request -We would love to discuss any ideas that you may have for a new feature. Please keep in mind this is a community driven project so be sure to contact the community via the mailing list to get feedback first. Alternativly, feel free to open GitHub issues requesting for new features. Before opening a new issue, please check if there is an existing feature request for the desired functionalityt. +We would love to discuss any ideas that you may have for a new feature. Please keep in mind this is a community driven project so be sure to contact the community via the mailing list to get feedback first. Alternatively, feel free to open GitHub issues requesting for new features. Before opening a new issue, please check if there is an existing feature request for the desired functionality. ## Packing From 0d9ddcdad93752911eb7556ebd7efcd8b9a18a14 Mon Sep 17 00:00:00 2001 From: Samy Mahmoudi Date: Tue, 20 Oct 2020 02:33:07 -0400 Subject: [PATCH 32/35] README: Add three missing words --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 50902d9..9aa2c59 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ The old version of ModSecurity was originally designed for and contained within # Compilation -Before compile this software make sure that you have libmodsecurity installed. +Before you compile this software make sure that you have libmodsecurity installed. You can download it from the ModSecurity git repository. For information pertaining to the compilation and installation of libmodsecurity please consult the documentation provided along with it. With libmodsecurity installed, you can proceed with the installation of the ModSecurity-apache connector. Run the following commands: @@ -39,7 +39,7 @@ libModSecurity for Apache extends your configuration directives. In order to loa LoadModule security3_module modules/mod_security3.so ``` -The module adds new directives to Apache and some them are documented below: +The module adds new directives to Apache and some of them are documented below: modsecurity ----------- @@ -49,7 +49,7 @@ modsecurity **default:** *off* -Turns on or off ModSecurity functionality. +Turns on or off the ModSecurity functionality. Note that this configuration directive is no longer related to the SecRule state. Instead, it now serves solely as an Apache flag to enable or disable the module. From 97fc190887d0e17d3f26b83e689e46ba913027fc Mon Sep 17 00:00:00 2001 From: Samy Mahmoudi Date: Tue, 20 Oct 2020 02:51:35 -0400 Subject: [PATCH 33/35] README: Remove a duplicate word --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9aa2c59..19be0f5 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Notice that this project depends on libmodsecurity rather than ModSecurity (vers ### What is the difference between this project and the old ModSecurity module for Apache? -The old version of ModSecurity was originally designed for and contained within an Apache module. This current version abstracts out some of the details allowing ModSecurity to more easily support multiple platforms and features outside beyond the scope of what Apache internals currently support. As a result using the new libmodsecurity engine is no longer reliant on the use of Apache and can be used to power multiple different connectors. As a result of this the current version is more flexible, has wider support, and allows for the support of new functionality that was not previously possible. +The old version of ModSecurity was originally designed for and contained within an Apache module. This current version abstracts out some of the details allowing ModSecurity to more easily support multiple platforms and features beyond the scope of what Apache internals currently support. As a result using the new libmodsecurity engine is no longer reliant on the use of Apache and can be used to power multiple different connectors. As a result of this the current version is more flexible, has wider support, and allows for the support of new functionality that was not previously possible. # Compilation From 2368a66a2c773a188749944e0b29e87c08ba1d2a Mon Sep 17 00:00:00 2001 From: Samy Mahmoudi Date: Tue, 20 Oct 2020 02:57:34 -0400 Subject: [PATCH 34/35] README: Remove a duplicate paragraph --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 19be0f5..6fc03f9 100644 --- a/README.md +++ b/README.md @@ -127,12 +127,7 @@ review work, and our QA integration. GitHub provides an excellent documentation on how to perform “Pull Requests”. More information available here: https://help.github.com/articles/using-pull-requests/ -Please respect the coding style. Pull requests can include various commits, so provide one fix or one piece of functionality per commit. Please do not change anything outside the scope of your target work (e.g. coding style in a function that you have passed by). For further information about the coding style used in this project, please check: https://www.chromium.org/blink/coding-style - -Please respect the coding style in use. Pull requests can include various commits, so -provide one fix or one functionality per commit. Do not change anything outside -the scope of your target work (e.g. coding style in a function that you have -passed by). +Please respect the coding style in use. Pull requests can include various commits, so provide one fix or one piece of functionality per commit. Please do not change anything outside the scope of your target work (e.g. coding style in a function that you have passed by). For further information about the coding style used in this project, please check: https://www.chromium.org/blink/coding-style ### Don’t know where to start? From 0488c77f69669584324b70460614a382224b4883 Mon Sep 17 00:00:00 2001 From: martinhsv <55407942+martinhsv@users.noreply.github.com> Date: Thu, 19 Aug 2021 14:15:00 -0400 Subject: [PATCH 35/35] Update README.md Add warning about the project not being production ready. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 6fc03f9..5f604ff 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,11 @@ The ModSecurity-apache connector takes the form of an Apache module. The module Notice that this project depends on libmodsecurity rather than ModSecurity (version 2.9 or less). +### NOTE: This project is not production ready + +This project should be considered under development and not production ready. The functionality is not complete and so should not be used. With Apache HTTP Server, the recommended version of ModSecurity is v2.9.x. + + ### What is the difference between this project and the old ModSecurity module for Apache? The old version of ModSecurity was originally designed for and contained within an Apache module. This current version abstracts out some of the details allowing ModSecurity to more easily support multiple platforms and features beyond the scope of what Apache internals currently support. As a result using the new libmodsecurity engine is no longer reliant on the use of Apache and can be used to power multiple different connectors. As a result of this the current version is more flexible, has wider support, and allows for the support of new functionality that was not previously possible.