diff --git a/.cdtproject b/.cdtproject new file mode 100644 index 00000000..93d4fd4b --- /dev/null +++ b/.cdtproject @@ -0,0 +1,1192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + make + + all + false + true + + + make + + test + false + true + + + gksudo + make + install + false + false + + + make + + clean + false + true + + + make + + dist-clean + false + true + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.jupiter b/.jupiter new file mode 100644 index 00000000..4bd771a5 --- /dev/null +++ b/.jupiter @@ -0,0 +1,200 @@ + + + + property.default.description + + 1970-01-01 :: 00:00:00:000 GMT-10:00 + review + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Pre 2.5 Review + brian + 2008-01-04 :: 10:09:23:000 GMT-08:00 + review + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 00000000..6f0bc9dc --- /dev/null +++ b/.project @@ -0,0 +1,85 @@ + + + ModSecurity + + + + + + org.eclipse.cdt.make.core.makeBuilder + clean,full,incremental, + + + org.eclipse.cdt.make.core.append_environment + true + + + org.eclipse.cdt.make.core.enableCleanBuild + true + + + org.eclipse.cdt.make.core.build.command + make + + + org.eclipse.cdt.make.core.useDefaultBuildCmd + true + + + org.eclipse.cdt.make.core.build.target.auto + all + + + org.eclipse.cdt.make.core.stopOnError + false + + + org.eclipse.cdt.make.core.build.location + + + + org.eclipse.cdt.make.core.build.target.inc + all + + + org.eclipse.cdt.make.core.build.arguments + + + + org.eclipse.cdt.core.errorOutputParser + org.eclipse.cdt.core.MakeErrorParser;org.eclipse.cdt.core.GCCErrorParser;org.eclipse.cdt.core.GASErrorParser;org.eclipse.cdt.core.GLDErrorParser;org.eclipse.cdt.core.VCErrorParser; + + + org.eclipse.cdt.make.core.enableAutoBuild + false + + + org.eclipse.cdt.make.core.environment + + + + org.eclipse.cdt.make.core.enabledIncrementalBuild + true + + + org.eclipse.cdt.make.core.build.target.clean + clean + + + org.eclipse.cdt.make.core.enableFullBuild + true + + + + + org.eclipse.cdt.make.core.ScannerConfigBuilder + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.make.core.makeNature + org.eclipse.cdt.make.core.ScannerConfigNature + + diff --git a/.settings/org.eclipse.cdt.core.prefs b/.settings/org.eclipse.cdt.core.prefs new file mode 100644 index 00000000..2222ff26 --- /dev/null +++ b/.settings/org.eclipse.cdt.core.prefs @@ -0,0 +1,3 @@ +#Fri Jan 04 09:57:26 GMT-08:00 2008 +eclipse.preferences.version=1 +indexerId=org.eclipse.cdt.core.fastIndexer diff --git a/CHANGES b/CHANGES index 7753e5cf..12f1f9d7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,21 +1,140 @@ -?? ???? 2007 - 2.5.0-dev3 -------------------------- +19 Feb 2008 - 2.5.0 +------------------- - * Added Cygwin to the list of platforms not supporting the hidden - visibility attribute. + * Updated included Core Ruleset to version 1.6.0 which uses 2.5 features. -21 June 2007 - 2.5.0-dev2 -------------------------- + * Cleaned up and clarified some documentation. - * Reversioned from 2.2.0 base version to 2.5.0 because of the large changeset. + * Updated code to be more portable so it builds with MS VC++. + + * Added unit tests for most operators and transformations. + + * Fixed crash on startup when ENV is improperly used without a parameter. + + * Allow macro resolution in setenv action. + + * The default action is now a minimal "phase:2,log,pass" with no default + transformations performed. + + * Implemented SecUploadFileMode to allow setting the mode for uploaded files. + + * Implemented "block" action. + + * Implemented SecRuleUpdateActionById. + + * Fixed removal of phase 5 rules via SecRuleRemoveBy* directives. + + * No longer log the query portion of the URI in the error log as + it may contain sensitive data. + + * Build is now 'configure' based: ./configure && make && make install + + * Added support for Lua scripting in the following ways: SecRuleScript + can be used to specify a script to execute as a rule, the exec + action processes Lua scripts internally, as does the @inspectFile + operator. Refer to the documentation for more details. + + * Changed how allow works. Used on its own it now allows phases 1-4. Used + with parameter "phase" (e.g. SecAction allow:phase) it only affects + the current phase. Used with parameter "request" it allows phases + 1-2. + + * Fixed issue where only the first phase 5 rule would run when the + request was intercepted in an earlier phase. + + * Stricter configuration parsing. Disruptive actions, meta actions and + phases are no longer allowed in a chained rule. Disruptive actions, + are no longer allowed in a logging phase (phase 5) rule, including + inheriting from SecDefaultAction. + + * More efficient collection persistance. + + * Fixed t:escapeSeqDecode to better follow ANSI C escapes. + + * Added t:jsDecode to decode JavScript escape sequences. + + * Added IS_NEW built-in collection variables. + + * New audit log part 'K' logs all matching rules. + + * Implemented SecRequestBodyNoFilesLimit. + + * Enhance handling of the case where we run out of disk space while + writing to audit log entry. + + * Added SecComponentSignature to allow other components the ability + to append to the logged signature. + + * Added skipAfter: action to allow skipping all rules until a rule + with a specified ID is reached. Rule execution then continues after + the specified rule. + + * Added SecMarker directive to allow a fixed target for skipAfter. + + * Added ctl:ruleRemoveById action to allow rule removal on a match. + + * Added a @containsWord operator that will match a given string anywhere in + the target value, but only on word boundaries. + + * Added a MATCHED_VAR_NAME variable to store the last matched variable name + so that it can be more easily used by rules. + + * Added a MATCHED_VAR variable to store the last matched variable value + so that it can be more easily used by rules. + + * Fixed expansion of macros when using relative changes with setvar. In + addition, added support for expanding macros in the variable name. + + * Situations where ModSecurity will intercept, generate an error or log + a level 1-3 message to the debug log are now marked as 'relevant' and may + generate an audit log entry. + + * Fixed deprecatevar:var=N/S action so that it decrements N every S seconds + as documented instead of decrementing by a rate. + + * Enable ModSecurity to look at partial response bodies. In previous + versions, ModSecurity would respond with status code 500 when the + response body was too long. Now, if SecResponseBodyLimitAction is + set to "ProcessPartial", it will process the part of the response + body received up until that point but send the rest without buffering. + + * ModSecurity will now process phases 3 and 4 even when request processing + is interrupted (either by Apache - e.g. by responding with 400, 401 + or 403, or by ModSecurity itself). + + * Fixed the base64decode transformation function to not return extra + characters at the end. + + * Return from the output filter with an error in addition to setting + up the HTTP error status in the output data. + + * Used new Apache API calls to get the server version/banner when available. + + * Added "logdata" meta action to allow logging of raw transaction data. + + * Added TX_SEVERITY that keeps track of the highest severity + for any matched rules so far. + + * Added ARGS_GET, ARGS_POST, ARGS_GET_NAMES, ARGS_POST_NAMES variables to + allow seperation of GET and POST arguments. + + * Added an Apache define (MODSEC_2.5) so that you can conditionally include + directives based on the ModSecurity major/minor versions with IfDefine. + + * Added MODSEC_BUILD variable that contains the numeric build value based + on the ModSecurity version. + + * Enhanced debug logging by displaying more data on rule execution. All + invoked rules are now logged in the debug log at level 5. + + * Stricter validation for @validateUtf8Encoding. + + * No longer process Apache internal subrequests. + + * Fixed warnings on Solaris and/or 64bit builds. * Added @within string comparison operator with support for macro expansion. - * Removed experimental variable RESPONSE_CONTENT_ENCODING which was not - working as intended. - - * Update included core rules to latest version. - * Do not trigger "pause" action for internal requests. * Added matching rule filename and line number to audit log. @@ -24,56 +143,14 @@ an alternate set based matching engine (Aho-Corasick) to perform faster phrase type matches such as black/white lists, spam keywords, etc. - * Cache transformations per-request/phase so they are not repeated. + * Allow caching transformations per-request/phase so they are not repeated. - * Fixed issue with requests that use internal requests. These had the - potential to be intercepted incorrectly when other Apache httpd modules - that used internal requests were used with mod_security. - - * Added Solaris to the list of platforms not supporting the hidden - visibility attribute. - - * Removed excessive debug log entries about "capture" action. - - * Fixed decoding full-width unicode in t:urlDecodeUni. - - * Lessen some overhead of debugging messages and calculations - - * Removed strnlen() calls for non-GNU platforms. - - -14 June 2007 - 2.1.2-rc1 ------------------------- - - * Update included core rules to latest version. - - * Do not trigger "pause" action for internal requests. - - * Fixed issue with requests that use internal requests. These had the - potential to be intercepted incorrectly when other Apache httpd modules - that used internal requests were used with mod_security. - - * Added Solaris to the list of platforms not supporting the hidden + * Added Solaris and Cygwin to the list of platforms not supporting the hidden visibility attribute. * Fixed decoding full-width unicode in t:urlDecodeUni. - * Lessen some overhead of debugging messages and calculations. - - * Do not try to intercept a request after a failed rule. This fixes the - issue associated with an "Internal Error: Asked to intercept request - but was_intercepted is zero" error message. - - * Added SecAuditLog2 directive to allow redundent concurrent audit log - index files. This will allow sending audit data to two consoles, etc. - - * Small performance improvement in memory management for rule execution. - - -11 May 2007 - 2.2.0-dev1 -------------------------- - - * Add SecGeoLookupsDb, @geoLookups and GEO collection to support + * Add SecGeoLookupDB, @geoLookups and GEO collection to support geographical lookups by IP/host. * Do not try to intercept a request after a failed rule. This fixes the @@ -86,9 +163,6 @@ * Exported API for registering custom variables. Example in api directory. - * Added experimental variables RESPONSE_CONTENT_LENGTH, RESPONSE_CONTENT_TYPE, - and RESPONSE_CONTENT_ENCODING. - * Added experimental support for content injection. Directive SecContentInjection (On|Off) controls whether injection is taking place. Actions "prepend" and "append" inject content when executed. Do note that @@ -120,15 +194,101 @@ * Do not log 'allow' action as intercepted in the debug log. - * Write debug log messages when "capture" is set, but the regex does not - capture and vice-versa. - - * Small performance improvement in memory management for rule execution. - * Fixed some collection variable names not printing with the parameter and/or counting operator in the debug log. +19 Feb 2008 - 2.1.6 +------------------- + + * Fixed crash on startup when ENV is improperly used without a parameter. + + * Allow macro resolution in setenv action. + + * Implemented SecUploadFileMode to allow setting the mode for uploaded files. + + * No longer log the query portion of the URI in the error log as + it may contain sensitive data. + + +10 Jan 2008 - 2.1.5 +------------------- + + * Updated included Core Ruleset to version 1.5.1. + + * Phase 5 rules can now be removed via SecRuleRemoveBy* directives. + + * Fixed issue where only the first phase 5 rule would run when the + request was intercepted in an earlier phase. + + * Fixed configuration parsing so that disruptive actions, meta actions + and phases are not allowed in a chained rule (as originally intended). + + * Fixed t:escapeSeqDecode to better follow ANSI C escapes. + + +27 Nov 2007 - 2.1.4 +------------------- + + * Updated included Core Ruleset to version 1.5 and noted in the docs that + XML support is required to use the rules without modification. + + * Fixed an evasion FP, mistaking a multipart non-boundary for a boundary. + + * Fixed multiple warnings on Solaris and/or 64bit builds. + + * Do not process subrequests in phase 2-4, but do hand off the request data. + + * Fixed a blocking FP in the multipart parser, which affected Safari. + + +11 Sep 2007 - 2.1.3 +------------------- + + * Updated multipart parsing code adding variables to allow checking + for various parsing issues (request body abnormalities). + + * Allow mod_rpaf and mod_extract_forwarded2 to work before ModSecurity. + + * Quiet some compiler warnings. + + * Do not block internal ErrorDocument requests after blocking request. + + * Added ability to compile without an external API (use -DNO_MODSEC_API). + + +27 Jul 2007 - 2.1.2 +------------------- + + * Cleaned up and clarified some documentation. + + * Update included core rules to latest version (1.4.3). + + * Enhanced ability to alert/audit failed requests. + + * Do not trigger "pause" action for internal requests. + + * Fixed issue with requests that use internal requests. These had the + potential to be intercepted incorrectly when other Apache httpd modules + that used internal requests were used with mod_security. + + * Added Solaris and Cygwin to the list of platforms not supporting the hidden + visibility attribute. + + * Fixed decoding full-width unicode in t:urlDecodeUni. + + * Lessen some overhead of debugging messages and calculations. + + * Do not try to intercept a request after a failed rule. This fixes the + issue associated with an "Internal Error: Asked to intercept request + but was_intercepted is zero" error message. + + * Added SecAuditLog2 directive to allow redundent concurrent audit log + index files. This will allow sending audit data to two consoles, etc. + + * Small performance improvement in memory management for rule execution. + + 11 Apr 2007 - 2.1.1 ------------------- diff --git a/README.TXT b/README.TXT index 5f8a9914..42891749 100644 --- a/README.TXT +++ b/README.TXT @@ -1,5 +1,5 @@ ModSecurity for Apache 2.x, http://www.modsecurity.org/ -Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) +Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) ModSecurity for Apache is an open source product, released under terms of the General Public Licence, Version 2 (GPLv2). Please refer to the diff --git a/apache2/Makefile b/apache2/Makefile deleted file mode 100644 index 9cdbbeca..00000000 --- a/apache2/Makefile +++ /dev/null @@ -1,42 +0,0 @@ -builddir = . - -# To successfully build ModSecurity for Apache you will -# need to configure the "top_dir" variable properly. The -# correct value will differ from system to system. -# -# If you've installed Apache manually simply point to the -# installation directory. Most pre-packaged installations -# consist of two parts. One contains the binaries, and the -# other contains the "development" files. You will typically -# need both. -# -# The list below may help: -# -# Fedora Core - /usr/lib/httpd (the httpd-devel package must be installed) -# -# Debian - /usr/share/apache2 (apache2-prefork-dev or apache2-threaded-dev -# needed, depending on your installation type) -# -top_dir = /apps/apache22 - -top_srcdir = ${top_dir} -top_builddir = ${top_dir} - -include ${top_builddir}/build/special.mk - -APXS = apxs -APACHECTL = apachectl - -INCLUDES = -I /usr/include/libxml2 -DEFS = -DWITH_LIBXML2 -#DEFS = -DWITH_LIBXML2 -DDEBUG_CONF -#DEFS = -DWITH_LIBXML2 -DCACHE_DEBUG -#LIBS = -Lmy/lib/dir -lmylib - -CFLAGS = -O2 -g -Wuninitialized -Wall -Wmissing-prototypes -Wshadow -Wunused-variable -Wunused-value -Wchar-subscripts -Wsign-compare - -all: local-shared-build - -clean: - -rm -f *.o *.lo *.slo *.la *~ .libs - diff --git a/apache2/Makefile.in b/apache2/Makefile.in new file mode 100644 index 00000000..67274191 --- /dev/null +++ b/apache2/Makefile.in @@ -0,0 +1,127 @@ +# Makefile for ModSecurity + +MOD_SECURITY2 = mod_security2 apache2_config apache2_io apache2_util \ + re re_operators re_actions re_tfns re_variables \ + msc_logging msc_xml msc_multipart modsecurity msc_parsers msc_util msc_pcre \ + persist_dbm msc_reqbody pdf_protect msc_geo acmp msc_lua + +MSC_TEST = re re_operators re_actions re_tfns re_variables \ + msc_logging msc_xml msc_multipart modsecurity \ + msc_parsers msc_util msc_pcre persist_dbm \ + msc_reqbody msc_geo acmp msc_lua + +MOD_SECURITY2_H = re.h modsecurity.h msc_logging.h msc_multipart.h msc_parsers.h \ + msc_pcre.h msc_util.h msc_xml.h persist_dbm.h apache2.h pdf_protect.h \ + msc_geo.h acmp.h utf8tables.h msc_lua.h + +CC = @APXS_CC@ +LIBTOOL = @APXS_LIBTOOL@ +PERL = @PERL@ +EXTRA_CFLAGS = @EXTRA_CFLAGS@ +MODSEC_EXTRA_CFLAGS = @MODSEC_EXTRA_CFLAGS@ + +### Note: must be in APXS format: -Wc,-flag +APXS_EXTRA_CFLAGS = @APXS_EXTRA_CFLAGS@ +MODSEC_APXS_EXTRA_CFLAGS = @MODSEC_APXS_EXTRA_CFLAGS@ + +APXS = @APXS@ +APXS_WRAPPER = @APXS_WRAPPER@ +APXS_INCLUDES = @APXS_INCLUDES@ +APXS_CFLAGS = @APXS_CFLAGS@ +APXS_LDFLAGS = @APXS_LDFLAGS@ +APXS_LIBS = @APXS_LIBS@ + +PCRE_CFLAGS = @PCRE_CFLAGS@ +PCRE_LIBS = @PCRE_LIBS@ + +LUA_CFLAGS = @LUA_CFLAGS@ +LUA_LIBS = @LUA_LIBS@ + +LIBXML_CFLAGS = @LIBXML_CFLAGS@ +LIBXML_LIBS = @LIBXML_LIBS@ + +APR_CFLAGS = @APR_CFLAGS@ +APR_LDFLAGS = @APR_LDFLAGS@ +APR_LIBS = @APR_LIBS@ +APR_LINK_LD = @APR_LINK_LD@ + +APU_CFLAGS = @APU_CFLAGS@ +APU_LDFLAGS = @APU_LDFLAGS@ +APU_LIBS = @APU_LIBS@ +APU_LINK_LD = @APU_LINK_LD@ + +CPPFLAGS = @CPPFLAGS@ $(PCRE_CFLAGS) $(LIBXML_CFLAGS) $(LUA_CFLAGS) +LIBS = @LIBS@ $(PCRE_LIBS) $(LIBXML_LIBS) $(LUA_LIBS) +LDFLAGS = @LDFLAGS@ +CFLAGS = @CFLAGS@ + +COMPILE_APACHE_MOD = $(APXS_WRAPPER) -c $(CPPFLAGS) $(LDFLAGS) $(LIBS) + +INSTALL_MOD_SHARED = $(APXS_WRAPPER) -i + +all: mod_security2.la + +install: install-mods + +clean-extras: + @for dir in mlogc-src; do \ + if test -d $$dir; then \ + $(MAKE) -C $$dir clean; \ + fi; \ + done + @rm -rf ../tools/mlogc ../tools/mlogc-static + +clean: clean-extras + @rm -rf *.la *.lo *.o *.slo .libs msc_test msc-test-debug.log + +maintainer-clean: clean + @rm -rf Makefile mlogc-src/Makefile t/run-tests.pl config config.log config.status configure mod_security2_config.h ../tools/*.pl autoscan.log configure.scan build/libtool.m4 build/config.guess build/config.sub build/ltmain.sh build/apxs-wrapper + +distclean: maintainer-clean + +install-mods: mod_security2.la + $(INSTALL_MOD_SHARED) mod_security2.la + +${MOD_SECURITY2:=.slo}: $(MOD_SECURITY2_H) +${MOD_SECURITY2:=.lo}: $(MOD_SECURITY2_H) +${MOD_SECURITY2:=.o}: $(MOD_SECURITY2_H}) + +mod_security2.la: $(MOD_SECURITY2_H) *.c + @src=""; \ + for f in $(MOD_SECURITY2); do \ + src="$$src $$f.c"; \ + done; \ + $(COMPILE_APACHE_MOD) $(APXS_EXTRA_CFLAGS) $(MODSEC_APXS_EXTRA_CFLAGS) $$src + +### MLogC +mlogc: + @$(MAKE) -C mlogc-src mlogc \ + && cp -p mlogc-src/mlogc ../tools \ + && echo \ + && echo "Successfully built \"mlogc\" in ../tools." \ + && echo "See: mlogc-src/INSTALL" \ + && echo + +mlogc-static: + @$(MAKE) -C mlogc-src static \ + && cp -p mlogc-src/mlogc ../tools/mlogc-static \ + && echo \ + && echo "Successfully built \"mlogc-static\" in ../tools." \ + && echo "See: mlogc-src/INSTALL" \ + && echo + +### Experimental Test Framework (*NIX only right now) +msc_test.lo: msc_test.c + $(LIBTOOL) --mode=compile $(CC) $(APXS_CFLAGS) $(EXTRA_CFLAGS) $(MODSEC_EXTRA_CFLAGS) $(CPPFLAGS) $(APR_CFLAGS) $(APU_CFLAGS) -o msc_test.lo -c msc_test.c + +msc_test: $(TESTOBJS) msc_test.lo + @objs=""; \ + for f in $(MSC_TEST); do \ + objs="$$objs $$f.lo"; \ + done; \ + $(LIBTOOL) --mode=link $(CC) $$objs -o msc_test msc_test.lo $(LDFLAGS) $(LIBS) $(APR_LINK_LD) $(APU_LINK_LD) + +test: t/run-tests.pl msc_test + @rm -f msc-test-debug.log; \ + $(PERL) t/run-tests.pl + diff --git a/apache2/Makefile.win b/apache2/Makefile.win index 96284cd7..1b2c4af3 100644 --- a/apache2/Makefile.win +++ b/apache2/Makefile.win @@ -1,43 +1,72 @@ - -# Path to Apache installation -BASE = "C:/Program Files/Apache Group/Apache2/" - -CC = cl - -# Add -DWITH_LIBXML2 below if you want to link against libxml2 -DEFS = /nologo /Od /LD /W3 -DWIN32 -DWINNT -DLL = mod_security2.dll - -# Path to the headers - configure the libxml2 include path -INCLUDES = -I. -I$(BASE)\include -IC:\libxml2\include - -CFLAGS= -O $(INCLUDES) $(DEFS) - -# Paths to the required libraries -# Use the line below if you want to link against libxml2 -# LIBS = $(BASE)\lib\libhttpd.lib $(BASE)\lib\libapr.lib $(BASE)\lib\libaprutil.lib $(BASE)\lib\pcre.lib C:\libxml2\lib\libxml2.lib - -# Use the line belof if you don't want to link against libxml2 -LIBS = $(BASE)\lib\libhttpd.lib $(BASE)\lib\libapr.lib $(BASE)\lib\libaprutil.lib $(BASE)\lib\pcre.lib - -OBJS = mod_security2.obj apache2_config.obj apache2_io.obj apache2_util.obj \ - re.obj re_operators.obj re_actions.obj re_tfns.obj re_variables.obj \ - msc_logging.obj msc_xml.obj msc_multipart.obj modsecurity.obj msc_parsers.obj \ - msc_util.obj msc_pcre.obj persist_dbm.obj msc_reqbody.obj pdf_protect.obj \ - msc_geo.obj acmp.obj - -all: $(DLL) - -dll: $(DLL) - -.c.obj: - $(CC) $(CFLAGS) -c $< -Fo$@ - -.cpp.obj: - $(CC) $(CFLAGS) -c $< -Fo$@ - -$(DLL): $(OBJS) - $(CC) $(CFLAGS) -LD $(OBJS) -Fe$(DLL) $(LIBS) /link /NODEFAULTLIB:MSVCRT - -clean: - del $(OBJS) *.dll *.lib *.pdb *.idb *.ilk *.exp *.res *.rc *.bin +########################################################################### +### You Will need to modify the following variables for your system +########################################################################### +########################################################################### + +# Path to Apache httpd installation +BASE = C:\Apache2 + +# Paths to required libraries +LIBXML2 = C:\work\libxml2-2.6.31 +LUA = C:\work\lua-5.1.3 +PCRE = C:\work\httpd-2.2.8\srclib\pcre + +# Linking libraries +LIBS = $(BASE)\lib\libhttpd.lib \ + $(BASE)\lib\libapr-1.lib \ + $(BASE)\lib\libaprutil-1.lib \ + $(PCRE)\LibR\pcre.lib \ + $(LIBXML2)\win32\bin.msvc\libxml2.lib \ + $(LUA)\lua5.1.lib \ + wsock32.lib + +########################################################################### +########################################################################### + +CC = cL + +MT = mt + +DEFS = /nologo /O2 /LD /W3 /wd4244 -DWIN32 -DWINNT -Dinline=APR_INLINE + +DLL = mod_security2.so + +INCLUDES = -I. \ + -I$(PCRE)\include -I$(PCRE) \ + -I$(LIBXML2)\include \ + -I$(LUA)\include -I$(LUA) \ + -I$(BASE)\include + +CFLAGS= -MD $(INCLUDES) $(DEFS) + +LDFLAGS = + +OBJS = mod_security2.obj apache2_config.obj apache2_io.obj apache2_util.obj \ + re.obj re_operators.obj re_actions.obj re_tfns.obj re_variables.obj \ + msc_logging.obj msc_xml.obj msc_multipart.obj modsecurity.obj \ + msc_parsers.obj msc_util.obj msc_pcre.obj persist_dbm.obj \ + msc_reqbody.obj pdf_protect.obj msc_geo.obj acmp.obj msc_lua.obj + +all: $(DLL) + +dll: $(DLL) + +mod_security2_config.h: mod_security2_config.hw + @echo off + type mod_security2_config.hw > mod_security2_config.h + +.c.obj: + $(CC) $(CFLAGS) -c $< -Fo$@ + +.cpp.obj: + $(CC) $(CFLAGS) -c $< -Fo$@ + +$(DLL): mod_security2_config.h $(OBJS) + $(CC) $(CFLAGS) $(LDFLAGS) -LD $(OBJS) -Fe$(DLL) $(LIBS) /link + IF EXIST $(DLL).manifest $(MT) -manifest $(DLL).manifest -outputresource:$(DLL);2 + +install: $(DLL) + copy $(DLL) $(BASE)\modules + +clean: + del $(OBJS) $(DLL) *.dll *.lib *.pdb *.idb *.ilk *.exp *.res *.rc *.bin mod_security2_config.h *.manifest diff --git a/apache2/acmp.c b/apache2/acmp.c index d5a6dba8..734c6666 100644 --- a/apache2/acmp.c +++ b/apache2/acmp.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -9,7 +9,17 @@ * */ #include "acmp.h" + +#ifdef ACMP_USE_UTF8 +/* UTF support */ #include "utf8tables.h" +#else +/* No UTF support */ +#define acmp_utf8_char_t long +#include +#define utf8_lcase(a) apr_tolower(a) +#endif + #include #include #include @@ -32,17 +42,17 @@ struct acmp_node_t { acmp_callback_t callback; void *callback_data; int depth; - + acmp_node_t *child; acmp_node_t *sibling; acmp_node_t *fail; acmp_node_t *parent; acmp_node_t *o_match; - + acmp_btree_node_t *btree; - + apr_size_t hit_count; - + char *text; char *pattern; }; @@ -58,24 +68,26 @@ struct acmp_btree_node_t { * Data related to parser, not to individual nodes */ struct ACMP { + #ifdef ACMP_USE_UTF8 int is_utf8; + #endif int is_case_sensitive; apr_pool_t *parent_pool; apr_pool_t *pool; - + int dict_count; apr_size_t longest_entry; - + acmp_node_t *root_node; - + const char *data_start; const char *data_end; const char *data_pos; apr_size_t data_len; - + apr_size_t *bp_buffer; apr_size_t bp_buff_len; - + acmp_node_t *active_node; char u8_buff[6]; apr_size_t u8buff_len; @@ -92,11 +104,12 @@ struct ACMP { * Functions for UTF-8 support */ +#ifdef ACMP_USE_UTF8 /** * Returns length of utf-8 sequence based on its first byte */ static int utf8_seq_len(const char *first_byte) { - return utf8_seq_lengths[(unsigned int)(unsigned char)first_byte[0]]; + return utf8_seq_lengths[(unsigned int)(unsigned char)first_byte[0]]; } /** @@ -107,7 +120,7 @@ static size_t utf8_strlen(const char *str) { const char *c = str; while (*c != 0) { c += utf8_seq_len(c); - len++; + len++; } return len; } @@ -131,7 +144,7 @@ static acmp_utf8_char_t utf8_decodechar(const char *str) { } /** - * Returns lowercase for given unicode character. Searches through + * Returns lowercase for given unicode character. Searches through * utf8_lcase_map table, if it doesn't find the code assumes * it doesn't have a lowercase variant and returns code itself. */ @@ -152,6 +165,7 @@ static long utf8_lcase(acmp_utf8_char_t ucs_code) { } return ucs_code; } +#endif /* ******************************************************************************* @@ -163,7 +177,11 @@ static long utf8_lcase(acmp_utf8_char_t ucs_code) { * Returns length of given string for parser's encoding */ static size_t acmp_strlen(ACMP *parser, const char *str) { + #ifdef ACMP_USE_UTF8 return (parser->is_utf8 == 0) ? strlen(str) : utf8_strlen(str); + #else + return strlen(str); + #endif } /** @@ -176,15 +194,18 @@ static void acmp_strtoucs(ACMP *parser, const char *str, acmp_utf8_char_t *ucs_c int i; const char *c = str; - if (parser->is_utf8 == 0) { + #ifdef ACMP_USE_UTF8 + if (parser->is_utf8) { for (i = 0; i < len; i++) { - *ucs_chars++ = *c++; - } - } else { - for (i = 0; i < len; i++) { - *ucs_chars++ = utf8_decodechar(c); + *(ucs_chars++) = utf8_decodechar(c); c += utf8_seq_len(c); } + } else + #endif + { + for (i = 0; i < len; i++) { + *(ucs_chars++) = *(c++); + } } } @@ -205,12 +226,15 @@ static acmp_node_t *acmp_child_for_code(acmp_node_t *parent_node, acmp_utf8_char * Adds node to parent node, if it is not already there */ static void acmp_add_node_to_parent(acmp_node_t *parent, acmp_node_t *child) { + acmp_node_t *node = NULL; + child->parent = parent; if (parent->child == NULL) { parent->child = child; return; } - acmp_node_t *node = parent->child; + + node = parent->child; for (;;) { if (node == child) return; if (node->sibling == NULL) { @@ -234,22 +258,24 @@ static void acmp_clone_node_no_state(acmp_node_t *from, acmp_node_t *to) { } /** - * Copies sibling nodes and child node for from given "from" node to "to" node. + * Copies sibling nodes and child node for from given "from" node to "to" node. * Both nodes must already exist. */ static void acmp_copy_nodes_recursive(acmp_node_t *from, acmp_node_t *to, apr_pool_t *pool) { acmp_node_t *old_node = from->child, *new_node, *nn2; if (old_node == NULL) return; nn2 = apr_pcalloc(pool, sizeof(acmp_node_t)); + /* ENH: Check alloc succeded */ acmp_clone_node_no_state(old_node, nn2); nn2->parent = to; to->child = nn2; acmp_copy_nodes_recursive(from->child, to->child, pool); - + for (;;) { old_node = old_node->sibling; if (old_node == NULL) break; new_node = apr_pcalloc(pool, sizeof(acmp_node_t)); + /* ENH: Check alloc succeded */ acmp_clone_node_no_state(old_node, new_node); new_node->parent = to; nn2->sibling = new_node; @@ -275,7 +301,6 @@ static inline acmp_node_t *acmp_btree_find(acmp_node_t *node, acmp_utf8_char_t l * */ static inline acmp_node_t *acmp_goto(acmp_node_t *node, acmp_utf8_char_t letter) { - //return acmp_child_for_code(node, letter); return acmp_btree_find(node, letter); } @@ -284,7 +309,7 @@ static inline acmp_node_t *acmp_goto(acmp_node_t *node, acmp_utf8_char_t letter) */ static void acmp_connect_other_matches(ACMP *parser, acmp_node_t *node) { acmp_node_t *child, *om; - + for (child = node->child; child != NULL; child = child->sibling) { if (child->fail == NULL) continue; for (om = child->fail; om != parser->root_node; om = om->fail) { @@ -294,7 +319,7 @@ static void acmp_connect_other_matches(ACMP *parser, acmp_node_t *node) { } } } - + /* Go recursively through children of this node that have a child node */ for(child = node->child; child != NULL; child = child->sibling) { if (child->child != NULL) acmp_connect_other_matches(parser, child); @@ -311,16 +336,22 @@ static void acmp_add_btree_leaves(acmp_btree_node_t *node, acmp_node_t *nodes[], if ((pos - lb) > 1) { left = lb + (pos - lb) / 2; node->left = apr_pcalloc(pool, sizeof(acmp_btree_node_t)); + /* ENH: Check alloc succeded */ node->left->node = nodes[left]; node->left->letter = nodes[left]->letter; - /* printf("%c ->left %c \n", node->node->letter, node->left->node->letter); */ + #ifdef DEBUG_ACMP + fprintf(stderr, "%lc ->left %lc\n", (wint_t)node->node->letter, (wint_t)node->left->node->letter); + #endif } if ((rb - pos) > 1) { right = pos + (rb - pos) / 2; node->right = apr_pcalloc(pool, sizeof(acmp_btree_node_t)); + /* ENH: Check alloc succeded */ node->right->node = nodes[right]; node->right->letter = nodes[right]->letter; - /* printf("%c ->right %c \n", node->node->letter, node->right->node->letter); */ + #ifdef DEBUG_ACMP + fprintf(stderr, "%lc ->right %lc\n", (wint_t)node->node->letter, (wint_t)node->right->node->letter); + #endif } if (node->right != NULL) { acmp_add_btree_leaves(node->right, nodes, right, pos, rb, pool); @@ -336,25 +367,36 @@ static void acmp_add_btree_leaves(acmp_btree_node_t *node, acmp_node_t *nodes[], static void acmp_build_binary_tree(ACMP *parser, acmp_node_t *node) { apr_size_t count, i, j; acmp_node_t *child = node->child; - + acmp_node_t **nodes; + apr_size_t pos; + + /* Build an array big enough */ for (count = 0; child != NULL; child = child->sibling) count++; - acmp_node_t *nodes[count]; + nodes = apr_pcalloc(parser->pool, count * sizeof(acmp_node_t *)); + /* ENH: Check alloc succeded */ + + /* ENH: Combine this in the loop below - we do not need two loops */ child = node->child; for (i = 0; i < count; i++) { nodes[i] = child; child = child->sibling; }; + /* We have array with all children of the node and number of those children */ for (i = 0; i < count - 1; i++) for (j = i + 1; j < count; j++) { + acmp_node_t *tmp; + if (nodes[i]->letter < nodes[j]->letter) continue; - acmp_node_t *tmp = nodes[i]; + + tmp = nodes[i]; nodes[i] = nodes[j]; nodes[j] = tmp; } node->btree = apr_pcalloc(parser->pool, sizeof(acmp_btree_node_t)); - apr_size_t pos = count / 2; + /* ENH: Check alloc succeded */ + pos = count / 2; node->btree->node = nodes[pos]; node->btree->letter = nodes[pos]->letter; acmp_add_btree_leaves(node->btree, nodes, pos, -1, count, parser->pool); @@ -368,23 +410,26 @@ static void acmp_build_binary_tree(ACMP *parser, acmp_node_t *node) { */ static apr_status_t acmp_connect_fail_branches(ACMP *parser) { /* Already connected ? */ - if (parser->is_failtree_done != 0) return APR_SUCCESS; acmp_node_t *child, *node, *goto_node; apr_array_header_t *arr, *arr2, *tmp; - + + if (parser->is_failtree_done != 0) return APR_SUCCESS; + parser->root_node->text = ""; arr = apr_array_make(parser->pool, 32, sizeof(acmp_node_t *)); arr2 = apr_array_make(parser->pool, 32, sizeof(acmp_node_t *)); - + parser->root_node->fail = parser->root_node; - + /* All first-level children will fail back to root node */ for (child = parser->root_node->child; child != NULL; child = child->sibling) { child->fail = parser->root_node; *(acmp_node_t **)apr_array_push(arr) = child; - /* printf("fail direction: *%s* => *%s*\n", child->text, child->fail->text); */ + #ifdef DEBUG_ACMP + fprintf(stderr, "fail direction: *%s* => *%s*\n", child->text, child->fail->text); + #endif } - + for (;;) { while (apr_is_empty_array(arr) == 0) { node = *(acmp_node_t **)apr_array_pop(arr); @@ -393,7 +438,9 @@ static apr_status_t acmp_connect_fail_branches(ACMP *parser) { goto_node = acmp_child_for_code(node->parent->fail, node->letter); node->fail = (goto_node != NULL) ? goto_node : parser->root_node; } - /* printf("fail direction: *%s* => *%s*\n", node->text, node->fail->text); */ + #ifdef DEBUG_ACMP + fprintf(stderr, "fail direction: *%s* => *%s*\n", node->text, node->fail->text); + #endif child = node->child; while (child != NULL) { *(acmp_node_t **)apr_array_push(arr2) = child; @@ -401,7 +448,7 @@ static apr_status_t acmp_connect_fail_branches(ACMP *parser) { } } if (apr_is_empty_array(arr2) != 0) break; - + tmp = arr; arr = arr2; arr2 = tmp; @@ -427,11 +474,10 @@ static void acmp_clear_hit_count_recursive(acmp_node_t *node) { */ static void acmp_found(ACMP *parser, acmp_node_t *node) { if (node->callback) { - node->callback(parser, node->callback_data, + node->callback(parser, node->callback_data, parser->bp_buffer[(parser->char_pos - node->depth - 1) % parser->bp_buff_len], parser->char_pos - node->depth - 1); } - /* printf("found: %s at position %d\n", node->pattern, parser->char_pos - node->depth - 1); */ node->hit_count++; parser->hit_count++; } @@ -450,15 +496,21 @@ static void acmp_found(ACMP *parser, acmp_node_t *node) { ACMP *acmp_create(int flags, apr_pool_t *pool) { apr_status_t rc; apr_pool_t *p; + ACMP *parser; + rc = apr_pool_create(&p, pool); if (rc != APR_SUCCESS) return NULL; - - ACMP *parser = apr_pcalloc(p, sizeof(ACMP)); + + parser = apr_pcalloc(p, sizeof(ACMP)); + /* ENH: Check alloc succeded */ parser->pool = p; parser->parent_pool = pool; + #ifdef ACMP_USE_UTF8 parser->is_utf8 = (flags & ACMP_FLAG_UTF8) == 0 ? 0 : 1; + #endif parser->is_case_sensitive = (flags & ACMP_FLAG_CASE_SENSITIVE) == 0 ? 0 : 1; parser->root_node = apr_pcalloc(p, sizeof(acmp_node_t)); + /* ENH: Check alloc succeded */ return parser; } @@ -481,17 +533,22 @@ void acmp_destroy(ACMP *parser) { ACMP *acmp_duplicate(ACMP *parser, apr_pool_t *pool) { apr_status_t rc; apr_pool_t *p; - + ACMP *new_parser; + if (pool == NULL) pool = parser->parent_pool; rc = apr_pool_create(&p, pool); if (rc != APR_SUCCESS) return NULL; - - ACMP *new_parser = apr_pcalloc(p, sizeof(ACMP)); + + new_parser = apr_pcalloc(p, sizeof(ACMP)); + /* ENH: Check alloc succeded */ new_parser->pool = p; new_parser->parent_pool = pool; + #ifdef ACMP_USE_UTF8 new_parser->is_utf8 = parser->is_utf8; + #endif new_parser->is_case_sensitive = parser->is_case_sensitive; new_parser->root_node = apr_pcalloc(p, sizeof(acmp_node_t)); + /* ENH: Check alloc succeded */ new_parser->dict_count = parser->dict_count; new_parser->longest_entry = parser->longest_entry; acmp_copy_nodes_recursive(parser->root_node, new_parser->root_node, new_parser->pool); @@ -503,11 +560,15 @@ ACMP *acmp_duplicate(ACMP *parser, apr_pool_t *pool) { * Creates fail tree and initializes buffer */ apr_status_t acmp_prepare(ACMP *parser) { + apr_status_t st; + if (parser->bp_buff_len < parser->longest_entry) { parser->bp_buff_len = parser->longest_entry * 2; parser->bp_buffer = apr_pcalloc(parser->pool, sizeof(apr_size_t) * parser->bp_buff_len); + /* ENH: Check alloc succeded */ } - apr_status_t st = acmp_connect_fail_branches(parser); + + st = acmp_connect_fail_branches(parser); parser->active_node = parser->root_node; if (st != APR_SUCCESS) return st; parser->is_active = 1; @@ -523,17 +584,22 @@ apr_status_t acmp_prepare(ACMP *parser) { * is supplied * len - Length of pattern in characters, if zero string length is used. */ -apr_status_t acmp_add_pattern(ACMP *parser, const char *pattern, - acmp_callback_t callback, void *data, apr_size_t len) +apr_status_t acmp_add_pattern(ACMP *parser, const char *pattern, + acmp_callback_t callback, void *data, apr_size_t len) { + size_t length, i, j; + acmp_utf8_char_t *ucs_chars; + acmp_node_t *parent, *child; + if (parser->is_active != 0) return APR_EGENERAL; - size_t length = (len == 0) ? acmp_strlen(parser, pattern) : len; - size_t i, j; - acmp_utf8_char_t ucs_chars[length]; - - acmp_node_t *parent = parser->root_node, *child; + + length = (len == 0) ? acmp_strlen(parser, pattern) : len; + ucs_chars = apr_pcalloc(parser->pool, length * sizeof(acmp_utf8_char_t)); + /* ENH: Check alloc succeded */ + + parent = parser->root_node; acmp_strtoucs(parser, pattern, ucs_chars, length); - + for (i = 0; i < length; i++) { acmp_utf8_char_t letter = ucs_chars[i]; if (parser->is_case_sensitive == 0) { @@ -542,10 +608,12 @@ apr_status_t acmp_add_pattern(ACMP *parser, const char *pattern, child = acmp_child_for_code(parent, letter); if (child == NULL) { child = apr_pcalloc(parser->pool, sizeof(acmp_node_t)); + /* ENH: Check alloc succeded */ child->pattern = ""; child->letter = letter; child->depth = i; child->text = apr_pcalloc(parser->pool, strlen(pattern) + 2); + /* ENH: Check alloc succeded */ for (j = 0; j <= i; j++) child->text[j] = pattern[j]; } if (i == length - 1) { @@ -553,6 +621,7 @@ apr_status_t acmp_add_pattern(ACMP *parser, const char *pattern, parser->dict_count++; child->is_last = 1; child->pattern = apr_pcalloc(parser->pool, strlen(pattern) + 2); + /* ENH: Check alloc succeded */ strcpy(child->pattern, pattern); } child->callback = callback; @@ -563,7 +632,7 @@ apr_status_t acmp_add_pattern(ACMP *parser, const char *pattern, } if (length > parser->longest_entry) parser->longest_entry = length; parser->is_failtree_done = 0; - + return APR_SUCCESS; } @@ -573,14 +642,22 @@ apr_status_t acmp_add_pattern(ACMP *parser, const char *pattern, * len - size of data in bytes */ apr_status_t acmp_process(ACMP *parser, const char *data, apr_size_t len) { - if (parser->is_failtree_done == 0) acmp_prepare(parser); - acmp_node_t *node = parser->active_node, *go_to; + acmp_node_t *node, *go_to; + #ifdef ACMP_USE_UTF8 apr_size_t seq_length; - const char *end = (data + len); - + #endif + const char *end; + + if (parser->is_failtree_done == 0) acmp_prepare(parser); + + node = parser->active_node; + end = data + len; + while (data < end) { - parser->bp_buffer[parser->char_pos % parser->bp_buff_len] = parser->byte_pos; acmp_utf8_char_t letter; + + parser->bp_buffer[parser->char_pos % parser->bp_buff_len] = parser->byte_pos; + #ifdef ACMP_USE_UTF8 if (parser->is_utf8) { if (parser->u8buff_len > 0) { /* Resuming partial utf-8 sequence */ @@ -608,7 +685,9 @@ apr_status_t acmp_process(ACMP *parser, const char *data, apr_size_t len) { parser->char_pos++; } } - } else { + } else + #endif + { letter = *data++; parser->byte_pos++; parser->char_pos++; @@ -631,9 +710,9 @@ apr_status_t acmp_process(ACMP *parser, const char *data, apr_size_t len) { if (go_to == NULL) node = node->fail; } if (go_to != NULL) node = go_to; - + /* We need to collect other nodes that are last letters of phrase. These - * will be fail node of current node if it has is_last flag set, and + * will be fail node of current node if it has is_last flag set, and * fail node of that node, recursively down to root node. */ go_to = node; @@ -649,7 +728,7 @@ apr_status_t acmp_process(ACMP *parser, const char *data, apr_size_t len) { /** * Resets the state of parser so you can start using it with new set of data. - * + * * No need to clear buffer since it will be re-initialized at first run of * acmp_process */ @@ -668,6 +747,7 @@ void acmp_reset(ACMP *parser) { ACMPT *acmp_duplicate_quick(ACMP *parser, apr_pool_t *pool) { apr_pool_t *p = (pool != NULL) ? pool : parser->pool; ACMPT *dup = apr_pcalloc(p, sizeof(ACMPT)); + /* ENH: Check alloc succeded */ dup->parser = parser; return dup; } @@ -676,14 +756,19 @@ ACMPT *acmp_duplicate_quick(ACMP *parser, apr_pool_t *pool) { * Process the data using ACMPT to keep state, and ACMPT's parser to keep the tree */ apr_status_t acmp_process_quick(ACMPT *acmpt, const char **match, const char *data, apr_size_t len) { + ACMP *parser; + acmp_node_t *node, *go_to; + const char *end; + if (acmpt->parser->is_failtree_done == 0) { acmp_prepare(acmpt->parser); }; - ACMP *parser = acmpt->parser; + + parser = acmpt->parser; if (acmpt->ptr == NULL) acmpt->ptr = parser->root_node; - acmp_node_t *node = acmpt->ptr, *go_to; - const char *end = (data + len); - + node = acmpt->ptr; + end = data + len; + while (data < end) { acmp_utf8_char_t letter = (unsigned char)*data++; go_to = NULL; @@ -699,7 +784,7 @@ apr_status_t acmp_process_quick(ACMPT *acmpt, const char **match, const char *da if (go_to == NULL) node = node->fail; } if (go_to != NULL) node = go_to; - + /* If node has o_match, then we found a pattern */ if (node->o_match != NULL) { *match = node->text; diff --git a/apache2/acmp.h b/apache2/acmp.h index 40865ac7..f79a44cd 100644 --- a/apache2/acmp.h +++ b/apache2/acmp.h @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -15,9 +15,11 @@ #include #define ACMP_FLAG_BYTE 0 -#define ACMP_FLAG_UTF8 0x100 #define ACMP_FLAG_CASE_SENSITIVE 1 #define ACMP_FLAG_CASE_INSENSITIVE 0 +#ifdef ACMP_USE_UTF8 +#define ACMP_FLAG_UTF8 0x100 +#endif /** * Opaque struct with parser data @@ -25,7 +27,7 @@ typedef struct ACMP ACMP; /** - * Used to separate state from the trie for acmp_process_quick function + * Used to separate state from the trie for acmp_process_quick function */ typedef struct { ACMP *parser; @@ -68,13 +70,13 @@ ACMP *acmp_duplicate(ACMP *parser, apr_pool_t *pool); * is supplied * len - Length of pattern in characters, if zero string length is used. */ -apr_status_t acmp_add_pattern(ACMP *parser, const char *pattern, +apr_status_t acmp_add_pattern(ACMP *parser, const char *pattern, acmp_callback_t callback, void *data, apr_size_t len); /** * Called to process incoming data stream. You must call acmp_done after sending * last data packet - * + * * data - ptr to incoming data * len - size of data in bytes */ diff --git a/apache2/apache2.h b/apache2/apache2.h index 1f3e9571..7dc623b4 100644 --- a/apache2/apache2.h +++ b/apache2/apache2.h @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -13,11 +13,14 @@ #include "http_core.h" #include "http_request.h" +#include "httpd.h" +#include "ap_release.h" #include #include +#if (!defined(NO_MODSEC_API)) /* Optional functions. */ APR_DECLARE_OPTIONAL_FN(void, modsec_register_tfn, (const char *name, void *fn)); @@ -27,6 +30,20 @@ APR_DECLARE_OPTIONAL_FN(void, modsec_register_variable, unsigned int argc_min, unsigned int argc_max, void *fn_validate, void *fn_generate, unsigned int is_cacheable, unsigned int availability)); +#endif + +/* ap_get_server_version() is gone in 2.3.0. + * It was replaced by two calls in 2.2.4 and higher: + * ap_get_server_banner() + * ap_get_server_description() + */ +#if (AP_SERVER_MAJORVERSION_NUMBER > 2) \ + || ((AP_SERVER_MAJORVERSION_NUMBER == 2)&& (AP_SERVER_MINORVERSION_NUMBER > 2)) \ + || ((AP_SERVER_MAJORVERSION_NUMBER == 2) && (AP_SERVER_MINORVERSION_NUMBER == 2) && (AP_SERVER_PATCHLEVEL_NUMBER >= 4)) +#define apache_get_server_version() ap_get_server_banner() +#else +#define apache_get_server_version() ap_get_server_version() +#endif /* Configuration functions. */ @@ -52,6 +69,8 @@ apr_status_t DSOLOCAL read_request_body(modsec_rec *msr, char **error_msg); int DSOLOCAL perform_interception(modsec_rec *msr); +apr_status_t DSOLOCAL send_error_bucket(modsec_rec *msr, ap_filter_t *f, int status); + int DSOLOCAL apache2_exec(modsec_rec *msr, const char *command, const char **argv, char **output); void DSOLOCAL record_time_checkpoint(modsec_rec *msr, int checkpoint_no); @@ -63,7 +82,7 @@ char DSOLOCAL *get_env_var(request_rec *r, char *name); void DSOLOCAL internal_log(request_rec *r, directory_config *dcfg, modsec_rec *msr, int level, const char *text, va_list ap); -void DSOLOCAL msr_log(modsec_rec *msr, int level, const char *text, ...); +void DSOLOCAL msr_log(modsec_rec *msr, int level, const char *text, ...) PRINTF_ATTRIBUTE(3,4); char DSOLOCAL *format_error_log_message(apr_pool_t *mp, error_message *em); diff --git a/apache2/apache2_config.c b/apache2/apache2_config.c index 526934c3..d7bb4857 100644 --- a/apache2/apache2_config.c +++ b/apache2/apache2_config.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -15,7 +15,10 @@ #include "pdf_protect.h" #include "http_log.h" -/* #define DEBUG_CONF 1 */ +#if defined(WITH_LUA) +#include "msc_lua.h" +#endif + /* -- Directory context creation and initialisation -- */ @@ -27,7 +30,7 @@ void *create_directory_config(apr_pool_t *mp, char *path) { if (dcfg == NULL) return NULL; #ifdef DEBUG_CONF - fprintf(stderr, "Created directory config %x path %s\n", dcfg, path); + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Created directory config %pp path %s", dcfg, path); #endif dcfg->mp = mp; @@ -36,14 +39,16 @@ void *create_directory_config(apr_pool_t *mp, char *path) { dcfg->reqbody_access = NOT_SET; dcfg->reqbody_inmemory_limit = NOT_SET; dcfg->reqbody_limit = NOT_SET; + dcfg->reqbody_no_files_limit = NOT_SET; dcfg->resbody_access = NOT_SET; dcfg->debuglog_name = NOT_SET_P; - dcfg->debuglog_level = NOT_SET; + dcfg->debuglog_level = NOT_SET; dcfg->debuglog_fd = NOT_SET_P; dcfg->of_limit = NOT_SET; - dcfg->of_mime_types = NOT_SET_P; + dcfg->of_limit_action = NOT_SET; + dcfg->of_mime_types = NOT_SET_P; dcfg->of_mime_types_cleared = NOT_SET; dcfg->cookie_format = NOT_SET; @@ -70,10 +75,12 @@ void *create_directory_config(apr_pool_t *mp, char *path) { dcfg->upload_dir = NOT_SET_P; dcfg->upload_keep_files = NOT_SET; dcfg->upload_validates_files = NOT_SET; + dcfg->upload_filemode = NOT_SET; /* These are only used during the configuration process. */ dcfg->tmp_chain_starter = NULL; dcfg->tmp_default_actionset = NULL; + dcfg->tmp_rule_placeholders = NULL; /* Misc */ dcfg->data_dir = NOT_SET_P; @@ -93,6 +100,15 @@ void *create_directory_config(apr_pool_t *mp, char *path) { /* Geo Lookups */ dcfg->geo = NOT_SET_P; + /* Cache */ + dcfg->cache_trans = NOT_SET; + dcfg->cache_trans_min = NOT_SET; + dcfg->cache_trans_max = NOT_SET; + + dcfg->component_signatures = apr_array_make(mp, 16, sizeof(char *)); + + dcfg->request_encoding = NOT_SET_P; + return dcfg; } @@ -140,6 +156,10 @@ static void copy_rules_phase(apr_pool_t *mp, apr_array_header_t *parent_phase_ar } if (copy > 0) { + #ifdef DEBUG_CONF + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Copy rule %pp [id \"%s\"]", rule, rule->actionset->id); + #endif + /* Copy the rule. */ *(msre_rule **)apr_array_push(child_phase_arr) = rule; if (rule->actionset->is_chained) mode = 2; @@ -148,6 +168,10 @@ static void copy_rules_phase(apr_pool_t *mp, apr_array_header_t *parent_phase_ar } } else { if (mode == 2) { + #ifdef DEBUG_CONF + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Copy chain %pp for rule %pp [id \"%s\"]", rule, rule->chain_starter, rule->chain_starter->actionset->id); + #endif + /* Copy the rule (it belongs to the chain we want to include. */ *(msre_rule **)apr_array_push(child_phase_arr) = rule; } @@ -174,7 +198,7 @@ static int copy_rules(apr_pool_t *mp, msre_ruleset *parent_ruleset, msre_ruleset child_ruleset->phase_response_body, exceptions_arr); copy_rules_phase(mp, parent_ruleset->phase_logging, child_ruleset->phase_logging, exceptions_arr); - + return 1; } @@ -187,7 +211,7 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) { directory_config *merged = create_directory_config(mp, NULL); #ifdef DEBUG_CONF - fprintf(stderr, "Merge parent %x child %x RESULT %x\n", _parent, _child, merged); + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Merge parent %pp child %pp RESULT %pp", _parent, _child, merged); #endif if (merged == NULL) return NULL; @@ -195,7 +219,7 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) { /* Use values from the child configuration where possible, * otherwise use the parent's. */ - + merged->is_enabled = (child->is_enabled == NOT_SET ? parent->is_enabled : child->is_enabled); @@ -206,11 +230,15 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) { ? parent->reqbody_inmemory_limit : child->reqbody_inmemory_limit); merged->reqbody_limit = (child->reqbody_limit == NOT_SET ? parent->reqbody_limit : child->reqbody_limit); + merged->reqbody_no_files_limit = (child->reqbody_no_files_limit == NOT_SET + ? parent->reqbody_no_files_limit : child->reqbody_no_files_limit); merged->resbody_access = (child->resbody_access == NOT_SET ? parent->resbody_access : child->resbody_access); merged->of_limit = (child->of_limit == NOT_SET - ? parent->of_limit : child->of_limit); + ? parent->of_limit : child->of_limit); + merged->of_limit_action = (child->of_limit_action == NOT_SET + ? parent->of_limit_action : child->of_limit_action); if (child->of_mime_types != NOT_SET_P) { /* Child added to the table */ @@ -268,14 +296,26 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) { if ((child->rule_inheritance == NOT_SET)||(child->rule_inheritance == 1)) { merged->rule_inheritance = parent->rule_inheritance; if ((child->ruleset == NULL)&&(parent->ruleset == NULL)) { + #ifdef DEBUG_CONF + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "No rules in this context."); + #endif + /* Do nothing, there are no rules in either context. */ } else if (child->ruleset == NULL) { + #ifdef DEBUG_CONF + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Using parent rules in this context."); + #endif + /* Copy the rules from the parent context. */ merged->ruleset = msre_ruleset_create(parent->ruleset->engine, mp); copy_rules(mp, parent->ruleset, merged->ruleset, child->rule_exceptions); } else if (parent->ruleset == NULL) { + #ifdef DEBUG_CONF + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Using child rules in this context."); + #endif + /* Copy child rules. */ merged->ruleset = msre_ruleset_create(child->ruleset->engine, mp); merged->ruleset->phase_request_headers = apr_array_copy(mp, @@ -289,6 +329,10 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) { merged->ruleset->phase_logging = apr_array_copy(mp, child->ruleset->phase_logging); } else { + #ifdef DEBUG_CONF + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, "Using parent then child rules in this context."); + #endif + /* Copy parent rules, then add child rules to it. */ merged->ruleset = msre_ruleset_create(parent->ruleset->engine, mp); copy_rules(mp, parent->ruleset, merged->ruleset, child->rule_exceptions); @@ -330,7 +374,7 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) { merged->auditlog_flag = (child->auditlog_flag == NOT_SET ? parent->auditlog_flag : child->auditlog_flag); merged->auditlog_type = (child->auditlog_type == NOT_SET - ? parent->auditlog_type : child->auditlog_type); + ? parent->auditlog_type : child->auditlog_type); if (child->auditlog_fd != NOT_SET_P) { merged->auditlog_fd = child->auditlog_fd; merged->auditlog_name = child->auditlog_name; @@ -350,7 +394,7 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) { merged->auditlog_parts = (child->auditlog_parts == NOT_SET_P ? parent->auditlog_parts : child->auditlog_parts); merged->auditlog_relevant_regex = (child->auditlog_relevant_regex == NOT_SET_P - ? parent->auditlog_relevant_regex : child->auditlog_relevant_regex); + ? parent->auditlog_relevant_regex : child->auditlog_relevant_regex); /* Upload */ merged->tmp_dir = (child->tmp_dir == NOT_SET_P @@ -361,6 +405,8 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) { ? parent->upload_keep_files : child->upload_keep_files); merged->upload_validates_files = (child->upload_validates_files == NOT_SET ? parent->upload_validates_files : child->upload_validates_files); + merged->upload_filemode = (child->upload_filemode == NOT_SET + ? parent->upload_filemode : child->upload_filemode); /* Misc */ merged->data_dir = (child->data_dir == NOT_SET_P @@ -390,6 +436,21 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) { merged->geo = (child->geo == NOT_SET_P ? parent->geo : child->geo); + /* Cache */ + merged->cache_trans = (child->cache_trans == NOT_SET + ? parent->cache_trans : child->cache_trans); + merged->cache_trans_min = (child->cache_trans_min == (apr_size_t)NOT_SET + ? parent->cache_trans_min : child->cache_trans_min); + merged->cache_trans_max = (child->cache_trans_max == (apr_size_t)NOT_SET + ? parent->cache_trans_max : child->cache_trans_max); + + /* Merge component signatures. */ + merged->component_signatures = apr_array_append(mp, parent->component_signatures, + child->component_signatures); + + merged->request_encoding = (child->request_encoding == NOT_SET_P + ? parent->request_encoding : child->request_encoding); + return merged; } @@ -408,8 +469,10 @@ void init_directory_config(directory_config *dcfg) { if (dcfg->reqbody_inmemory_limit == NOT_SET) dcfg->reqbody_inmemory_limit = REQUEST_BODY_DEFAULT_INMEMORY_LIMIT; if (dcfg->reqbody_limit == NOT_SET) dcfg->reqbody_limit = REQUEST_BODY_DEFAULT_LIMIT; + if (dcfg->reqbody_no_files_limit == NOT_SET) dcfg->reqbody_no_files_limit = REQUEST_BODY_NO_FILES_DEFAULT_LIMIT; if (dcfg->resbody_access == NOT_SET) dcfg->resbody_access = 0; if (dcfg->of_limit == NOT_SET) dcfg->of_limit = RESPONSE_BODY_DEFAULT_LIMIT; + if (dcfg->of_limit_action == NOT_SET) dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_REJECT; if (dcfg->of_mime_types == NOT_SET_P) { dcfg->of_mime_types = apr_table_make(dcfg->mp, 3); @@ -444,6 +507,7 @@ void init_directory_config(directory_config *dcfg) { if (dcfg->upload_dir == NOT_SET_P) dcfg->upload_dir = NULL; if (dcfg->upload_keep_files == NOT_SET) dcfg->upload_keep_files = KEEP_FILES_OFF; if (dcfg->upload_validates_files == NOT_SET) dcfg->upload_validates_files = 0; + if (dcfg->upload_filemode == NOT_SET) dcfg->upload_filemode = 0600; /* Misc */ if (dcfg->data_dir == NOT_SET_P) dcfg->data_dir = NULL; @@ -462,13 +526,20 @@ void init_directory_config(directory_config *dcfg) { /* Geo Lookup */ if (dcfg->geo == NOT_SET_P) dcfg->geo = NULL; + + /* Cache */ + if (dcfg->cache_trans == NOT_SET) dcfg->cache_trans = MODSEC_CACHE_ENABLED; + if (dcfg->cache_trans_min == (apr_size_t)NOT_SET) dcfg->cache_trans_min = 15; + if (dcfg->cache_trans_max == (apr_size_t)NOT_SET) dcfg->cache_trans_max = 0; + + if (dcfg->request_encoding == NOT_SET_P) dcfg->request_encoding = NULL; } /** - * TODO + * */ -static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, const char *p1, - const char *p2, const char *p3) +static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, int type, + const char *p1, const char *p2, const char *p3) { char *my_error_msg = NULL; msre_rule *rule = NULL; @@ -481,7 +552,19 @@ static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, const char * } /* Create the rule now. */ - rule = msre_rule_create(dcfg->ruleset, cmd->directive->filename, cmd->directive->line_num, p1, p2, p3, &my_error_msg); + switch(type) { + #if defined(WITH_LUA) + case RULE_TYPE_LUA : + rule = msre_rule_lua_create(dcfg->ruleset, cmd->directive->filename, + cmd->directive->line_num, p1, p2, &my_error_msg); + break; + #endif + default : + rule = msre_rule_create(dcfg->ruleset, type, cmd->directive->filename, + cmd->directive->line_num, p1, p2, p3, &my_error_msg); + break; + } + if (rule == NULL) { return my_error_msg; } @@ -492,31 +575,31 @@ static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, const char * if (dcfg->tmp_default_actionset == NULL) return FATAL_ERROR; } - /* Merge actions with the parent. */ - rule->actionset = msre_actionset_merge(modsecurity->msre, dcfg->tmp_default_actionset, - rule->actionset, 1); - - if (dcfg->tmp_chain_starter != NULL) { - /* This rule is part of a chain. */ + /* Check some cases prior to merging so we know where it came from */ + /* Check syntax for chained rules */ + if ((rule->actionset != NULL) && (dcfg->tmp_chain_starter != NULL)) { /* Must NOT specify a disruptive action. */ - if (rule->actionset->intercept_action == NOT_SET) { + if (rule->actionset->intercept_action != NOT_SET) { return apr_psprintf(cmd->pool, "ModSecurity: Disruptive actions can only " "be specified by chain starter rules."); } /* Must NOT specify a phase. */ - if (rule->actionset->phase == NOT_SET) { + if (rule->actionset->phase != NOT_SET) { return apr_psprintf(cmd->pool, "ModSecurity: Execution phases can only be " "specified by chain starter rules."); } /* Must NOT use metadata actions. */ + /* ENH: loop through to check for tags */ if ((rule->actionset->id != NOT_SET_P) ||(rule->actionset->rev != NOT_SET_P) - ||(rule->actionset->msg != NOT_SET_P)) + ||(rule->actionset->msg != NOT_SET_P) + ||(rule->actionset->severity != NOT_SET) + ||(rule->actionset->logdata != NOT_SET_P)) { - return apr_psprintf(cmd->pool, "ModSecurity: Metadata actions (id, rev, msg) " + return apr_psprintf(cmd->pool, "ModSecurity: Metadata actions (id, rev, msg, tag, severity, logdata) " " can only be specified by chain starter rules."); } @@ -525,7 +608,31 @@ static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, const char * return apr_psprintf(cmd->pool, "ModSecurity: The skip action can only be used " " by chain starter rules. "); } + } + /* Merge actions with the parent. + * + * ENH Probably do not want this done fully for chained rules. + */ + rule->actionset = msre_actionset_merge(modsecurity->msre, dcfg->tmp_default_actionset, + rule->actionset, 1); + + /* Keep track of the parent action for "block" */ + rule->actionset->parent_intercept_action_rec = dcfg->tmp_default_actionset->intercept_action_rec; + rule->actionset->parent_intercept_action = dcfg->tmp_default_actionset->intercept_action; + + /* Must NOT specify a disruptive action in logging phase. */ + if ((rule->actionset != NULL) + && (rule->actionset->phase == PHASE_LOGGING) + && (rule->actionset->intercept_action != ACTION_ALLOW) + && (rule->actionset->intercept_action != ACTION_ALLOW_REQUEST) + && (rule->actionset->intercept_action != ACTION_NONE) + ) { + return apr_psprintf(cmd->pool, "ModSecurity: Disruptive actions " + "cannot be specified in the logging phase."); + } + + if (dcfg->tmp_chain_starter != NULL) { rule->chain_starter = dcfg->tmp_chain_starter; rule->actionset->phase = rule->chain_starter->actionset->phase; } @@ -535,7 +642,7 @@ static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, const char * * not want more rules to follow in the chain * then cut it (the chain). */ - dcfg->tmp_chain_starter = NULL; + dcfg->tmp_chain_starter = NULL; } else { /* On the other hand, if this rule wants other * rules to follow it, then start a new chain @@ -547,22 +654,206 @@ static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, const char * } /* Optimisation */ - if (strcasecmp(rule->op_name, "inspectFile") == 0) { + if ((rule->op_name != NULL)&&(strcasecmp(rule->op_name, "inspectFile") == 0)) { dcfg->upload_validates_files = 1; } + /* Create skip table if one does not already exist. */ + if (dcfg->tmp_rule_placeholders == NULL) { + dcfg->tmp_rule_placeholders = apr_table_make(cmd->pool, 10); + if (dcfg->tmp_rule_placeholders == NULL) return FATAL_ERROR; + } + + /* Keep track of any rule IDs we need to skip after */ + if (rule->actionset->skip_after != NOT_SET_P) { + char *tmp_id = apr_pstrdup(cmd->pool, rule->actionset->skip_after); + apr_table_setn(dcfg->tmp_rule_placeholders, tmp_id, tmp_id); + + #ifdef DEBUG_CONF + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, + "Watching for skipafter target rule id=\"%s\".", tmp_id); + #endif + + } + + #ifdef DEBUG_CONF + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, + "Adding rule %pp id=\"%s\".", rule, (rule->actionset->id == NOT_SET_P + ? "(none)" : rule->actionset->id)); + #endif + /* Add rule to the recipe. */ if (msre_ruleset_rule_add(dcfg->ruleset, rule, rule->actionset->phase) < 0) { return "Internal Error: Failed to add rule to the ruleset."; } - return NULL; + /* Add an additional placeholder if this rule ID is on the list */ + if ((rule->actionset->id != NULL) && apr_table_get(dcfg->tmp_rule_placeholders, rule->actionset->id)) { + msre_rule *phrule = apr_palloc(rule->ruleset->mp, sizeof(msre_rule)); + if (phrule == NULL) { + return FATAL_ERROR; + } + + #ifdef DEBUG_CONF + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, + "Adding placeholder %pp for rule %pp id=\"%s\".", phrule, rule, rule->actionset->id); + #endif + + /* shallow copy of original rule with placeholder marked as target */ + memcpy(phrule, rule, sizeof(msre_rule)); + phrule->placeholder = RULE_PH_SKIPAFTER; + + /* Add placeholder. */ + if (msre_ruleset_rule_add(dcfg->ruleset, phrule, phrule->actionset->phase) < 0) { + return "Internal Error: Failed to add placeholder to the ruleset."; + } + + /* No longer need to search for the ID */ + apr_table_unset(dcfg->tmp_rule_placeholders, rule->actionset->id); + } + + /* Update the unparsed rule */ + rule->unparsed = msre_rule_generate_unparsed(dcfg->ruleset->mp, rule, NULL, NULL, NULL); + + return NULL; +} + +/** + * + */ +static const char *add_marker(cmd_parms *cmd, directory_config *dcfg, const char *p1, + const char *p2, const char *p3) +{ + char *my_error_msg = NULL; + msre_rule *rule = NULL; + extern msc_engine *modsecurity; + int p; + + /* Create a ruleset if one does not exist. */ + if ((dcfg->ruleset == NULL)||(dcfg->ruleset == NOT_SET_P)) { + dcfg->ruleset = msre_ruleset_create(modsecurity->msre, cmd->pool); + if (dcfg->ruleset == NULL) return FATAL_ERROR; + } + + /* Create the rule now. */ + rule = msre_rule_create(dcfg->ruleset, RULE_TYPE_MARKER, cmd->directive->filename, cmd->directive->line_num, p1, p2, p3, &my_error_msg); + if (rule == NULL) { + return my_error_msg; + } + + /* This is a marker */ + rule->placeholder = RULE_PH_MARKER; + + /* Add placeholder to each phase */ + for (p = PHASE_FIRST; p <= PHASE_LAST; p++) { + if (msre_ruleset_rule_add(dcfg->ruleset, rule, p) < 0) { + return "Internal Error: Failed to add marker to the ruleset."; + } + } + + /* No longer need to search for the ID */ + apr_table_unset(dcfg->tmp_rule_placeholders, rule->actionset->id); + + return NULL; +} + +/** + * + */ +static const char *update_rule_action(cmd_parms *cmd, directory_config *dcfg, + const char *p1, const char *p2) +{ + char *my_error_msg = NULL; + msre_rule *rule = NULL; + msre_actionset *new_actionset = NULL; + msre_ruleset *ruleset = dcfg->ruleset; + extern msc_engine *modsecurity; + + /* Get the ruleset if one exists */ + if ((ruleset == NULL)||(ruleset == NOT_SET_P)) { + return NULL; + } + + #ifdef DEBUG_CONF + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, + "Update rule id=\"%s\" with action \"%s\".", p1, p2); + #endif + + /* Fetch the rule */ + rule = msre_ruleset_fetch_rule(ruleset, p1); + if (rule == NULL) { + #ifdef DEBUG_CONF + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, + "Update rule id=\"%s\" with action \"%s\" failed: Rule not found.", p1, p2); + #endif + return NULL; + } + + /* Check the rule actionset */ + /* ENH: Can this happen? */ + if (rule->actionset == NULL) { + return apr_psprintf(cmd->pool, "ModSecurity: Attempt to update action for rule \"%s\" failed: Rule does not have an actionset.", p1); + } + + /* Create a new actionset */ + new_actionset = msre_actionset_create(modsecurity->msre, p2, &my_error_msg); + if (new_actionset == NULL) return FATAL_ERROR; + if (my_error_msg != NULL) return my_error_msg; + + /* Must NOT change an id */ + if ((new_actionset->id != NOT_SET_P) && (rule->actionset->id != NULL) && (strcmp(rule->actionset->id, new_actionset->id) != 0)) { + return apr_psprintf(cmd->pool, "ModSecurity: Rule IDs cannot be updated via SecRuleUpdateActionById."); + } + + /* Must NOT alter the phase */ + if ((new_actionset->phase != NOT_SET) && (rule->actionset->phase != new_actionset->phase)) { + return apr_psprintf(cmd->pool, "ModSecurity: Rule phases cannot be updated via SecRuleUpdateActionById."); + } + + #ifdef DEBUG_CONF + { + char *actions = msre_actionset_generate_action_string(ruleset->mp, rule->actionset); + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, + "Update rule %pp id=\"%s\" old action: \"%s\"", + rule, + (rule->actionset->id == NOT_SET_P ? "(none)" : rule->actionset->id), + actions); + } + #endif + + /* Merge new actions with the rule */ + /* ENH: Will this leak the old actionset? */ + rule->actionset = msre_actionset_merge(modsecurity->msre, rule->actionset, + new_actionset, 1); + msre_actionset_set_defaults(rule->actionset); + + /* Update the unparsed rule */ + rule->unparsed = msre_rule_generate_unparsed(ruleset->mp, rule, NULL, NULL, NULL); + + #ifdef DEBUG_CONF + { + char *actions = msre_actionset_generate_action_string(ruleset->mp, rule->actionset); + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, + "Update rule %pp id=\"%s\" new action: \"%s\"", + rule, + (rule->actionset->id == NOT_SET_P ? "(none)" : rule->actionset->id), + actions); + } + #endif + + return NULL; } /* -- Configuration directives -- */ static const char *cmd_action(cmd_parms *cmd, void *_dcfg, const char *p1) { - return add_rule(cmd, (directory_config *)_dcfg, "REQUEST_URI", "@unconditionalMatch", p1); + return add_rule(cmd, (directory_config *)_dcfg, RULE_TYPE_ACTION, SECACTION_TARGETS, SECACTION_ARGS, p1); +} + +static const char *cmd_marker(cmd_parms *cmd, void *_dcfg, const char *p1) { + directory_config *dcfg = (directory_config *)_dcfg; + const char *action = apr_pstrcat(dcfg->mp, SECMARKER_BASE_ACTIONS, p1, NULL); + return add_marker(cmd, (directory_config *)_dcfg, SECMARKER_TARGETS, SECMARKER_ARGS, action); } static const char *cmd_argument_separator(cmd_parms *cmd, void *_dcfg, const char *p1) { @@ -571,7 +862,7 @@ static const char *cmd_argument_separator(cmd_parms *cmd, void *_dcfg, const cha if (strlen(p1) != 1) { return apr_psprintf(cmd->pool, "ModSecurity: Invalid argument separator: %s", p1); } - + dcfg->argument_separator = p1[0]; return NULL; @@ -744,6 +1035,18 @@ static const char *cmd_chroot_dir(cmd_parms *cmd, void *_dcfg, const char *p1) { return NULL; } +/** + * Adds component signature to the list of signatures kept in configuration. + */ +static const char *cmd_component_signature(cmd_parms *cmd, void *_dcfg, const char *p1) { + directory_config *dcfg = (directory_config *)_dcfg; + + /* ENH Enforce "Name/VersionX.Y.Z (comment)" format. */ + *(char **)apr_array_push(dcfg->component_signatures) = (char *)p1; + + return NULL; +} + static const char *cmd_content_injection(cmd_parms *cmd, void *_dcfg, int flag) { directory_config *dcfg = (directory_config *)_dcfg; if (dcfg == NULL) return NULL; @@ -802,22 +1105,27 @@ static const char *cmd_default_action(cmd_parms *cmd, void *_dcfg, const char *p } /* Must specify a disruptive action. */ + /* ENH: Remove this requirement? */ if (dcfg->tmp_default_actionset->intercept_action == NOT_SET) { return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must specify a disruptive action."); } /* Must specify a phase. */ + /* ENH: Remove this requirement? */ if (dcfg->tmp_default_actionset->phase == NOT_SET) { return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must specify a phase."); } /* Must not use metadata actions. */ + /* ENH: loop through to check for tags */ if ((dcfg->tmp_default_actionset->id != NOT_SET_P) ||(dcfg->tmp_default_actionset->rev != NOT_SET_P) - ||(dcfg->tmp_default_actionset->msg != NOT_SET_P)) + ||(dcfg->tmp_default_actionset->msg != NOT_SET_P) + ||(dcfg->tmp_default_actionset->severity != NOT_SET) + ||(dcfg->tmp_default_actionset->logdata != NOT_SET_P)) { return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must not " - "contain any metadata actions (id, rev, msg)."); + "contain any metadata actions (id, rev, msg, tag, severity, logdata)."); } /* Must not use chain. */ @@ -832,6 +1140,12 @@ static const char *cmd_default_action(cmd_parms *cmd, void *_dcfg, const char *p "contain a skip action."); } + /* Must not use skipAfter. */ + if (dcfg->tmp_default_actionset->skip_after != NOT_SET_P) { + return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must not " + "contain a skipAfter action."); + } + return NULL; } @@ -916,6 +1230,22 @@ static const char *cmd_request_body_limit(cmd_parms *cmd, void *_dcfg, const cha return NULL; } +static const char *cmd_request_body_no_files_limit(cmd_parms *cmd, void *_dcfg, const char *p1) { + directory_config *dcfg = (directory_config *)_dcfg; + long int limit; + + if (dcfg == NULL) return NULL; + + limit = strtol(p1, NULL, 10); + if ((limit == LONG_MAX)||(limit == LONG_MIN)||(limit <= 0)) { + return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRequestBodyNoFilesLimit: %s", p1); + } + + dcfg->reqbody_no_files_limit = limit; + + return NULL; +} + static const char *cmd_request_body_access(cmd_parms *cmd, void *_dcfg, const char *p1) { directory_config *dcfg = (directory_config *)_dcfg; if (dcfg == NULL) return NULL; @@ -929,6 +1259,17 @@ static const char *cmd_request_body_access(cmd_parms *cmd, void *_dcfg, const ch return NULL; } +static const char *cmd_request_encoding(cmd_parms *cmd, void *_dcfg, const char *p1) { + directory_config *dcfg = (directory_config *)_dcfg; + if (dcfg == NULL) return NULL; + + /* ENH Validate encoding */ + + dcfg->request_encoding = p1; + + return NULL; +} + static const char *cmd_response_body_access(cmd_parms *cmd, void *_dcfg, const char *p1) { directory_config *dcfg = (directory_config *)_dcfg; if (dcfg == NULL) return NULL; @@ -960,11 +1301,24 @@ static const char *cmd_response_body_limit(cmd_parms *cmd, void *_dcfg, const ch return NULL; } +static const char *cmd_response_body_limit_action(cmd_parms *cmd, void *_dcfg, const char *p1) { + directory_config *dcfg = (directory_config *)_dcfg; + if (dcfg == NULL) return NULL; + + if (strcasecmp(p1, "ProcessPartial") == 0) dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_PARTIAL; + else + if (strcasecmp(p1, "Reject") == 0) dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_REJECT; + else + return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecResponseBodyLimitAction: %s", p1); + + return NULL; +} + static const char *cmd_response_body_mime_type(cmd_parms *cmd, void *_dcfg, const char *_p1) { directory_config *dcfg = (directory_config *)_dcfg; char *p1 = apr_pstrdup(cmd->pool, _p1); - // TODO check whether the parameter is a valid MIME type of "null" + /* TODO check whether the parameter is a valid MIME type of "???" */ if ((dcfg->of_mime_types == NULL)||(dcfg->of_mime_types == NOT_SET_P)) { dcfg->of_mime_types = apr_table_make(cmd->pool, 10); @@ -992,7 +1346,7 @@ static const char *cmd_response_body_mime_types_clear(cmd_parms *cmd, void *_dcf static const char *cmd_rule(cmd_parms *cmd, void *_dcfg, const char *p1, const char *p2, const char *p3) { - return add_rule(cmd, (directory_config *)_dcfg, p1, p2, p3); + return add_rule(cmd, (directory_config *)_dcfg, RULE_TYPE_NORMAL, p1, p2, p3); } static const char *cmd_rule_engine(cmd_parms *cmd, void *_dcfg, const char *p1) { @@ -1019,8 +1373,8 @@ static const char *cmd_rule_import_by_id(cmd_parms *cmd, void *_dcfg, const char re->type = RULE_EXCEPTION_IMPORT_ID; // TODO verify p1 re->param = p1; - *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re; - + *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re; + return NULL; } @@ -1032,7 +1386,7 @@ static const char *cmd_rule_import_by_msg(cmd_parms *cmd, void *_dcfg, const cha re->type = RULE_EXCEPTION_IMPORT_MSG; // TODO verify p1 re->param = p1; - *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re; + *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re; return NULL; } @@ -1045,11 +1399,23 @@ static const char *cmd_rule_inheritance(cmd_parms *cmd, void *_dcfg, int flag) { return NULL; } +static const char *cmd_rule_script(cmd_parms *cmd, void *_dcfg, const char *p1, + const char *p2) +{ + #if defined(WITH_LUA) + const char *filename = resolve_relative_path(cmd->pool, cmd->directive->filename, p1); + return add_rule(cmd, (directory_config *)_dcfg, RULE_TYPE_LUA, filename, p2, NULL); + #else + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, "Ignoring SecRuleScript \"%s\" directive (%s:%d): No Lua scripting support.", p1, cmd->directive->filename, cmd->directive->line_num); + return NULL; + #endif +} + static const char *cmd_rule_remove_by_id(cmd_parms *cmd, void *_dcfg, const char *p1) { directory_config *dcfg = (directory_config *)_dcfg; rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception)); if (dcfg == NULL) return NULL; - + re->type = RULE_EXCEPTION_REMOVE_ID; re->param = p1; *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re; @@ -1064,7 +1430,7 @@ static const char *cmd_rule_remove_by_msg(cmd_parms *cmd, void *_dcfg, const cha directory_config *dcfg = (directory_config *)_dcfg; rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception)); if (dcfg == NULL) return NULL; - + re->type = RULE_EXCEPTION_REMOVE_MSG; re->param = p1; re->param_data = msc_pregcomp(cmd->pool, p1, 0, NULL, NULL); @@ -1077,12 +1443,18 @@ static const char *cmd_rule_remove_by_msg(cmd_parms *cmd, void *_dcfg, const cha msre_ruleset_rule_remove_with_exception(dcfg->ruleset, re); #ifdef DEBUG_CONF - fprintf(stderr, "Added exception %x (%i %s) to dcfg %x.\n", re, re->type, re->param, dcfg); + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, "Added exception %pp (%d %s) to dcfg %pp.", re, re->type, re->param, dcfg); #endif return NULL; } +static const char *cmd_rule_update_action_by_id(cmd_parms *cmd, void *_dcfg, + const char *p1, const char *p2) +{ + return update_rule_action(cmd, (directory_config *)_dcfg, p1, p2); +} + static const char *cmd_server_signature(cmd_parms *cmd, void *_dcfg, const char *p1) { if (cmd->server->is_virtual) { return "ModSecurity: SecServerSignature not allowed in VirtualHost"; @@ -1113,6 +1485,26 @@ static const char *cmd_upload_dir(cmd_parms *cmd, void *_dcfg, const char *p1) { return NULL; } +static const char *cmd_upload_filemode(cmd_parms *cmd, void *_dcfg, const char *p1) { + directory_config *dcfg = (directory_config *)_dcfg; + + if (dcfg == NULL) return NULL; + + if (strcasecmp(p1, "default") == 0) { + dcfg->upload_filemode = NOT_SET; + } + else { + long int mode = strtol(p1, NULL, 8); /* expects octal mode */ + if ((mode == LONG_MAX)||(mode == LONG_MIN)||(mode <= 0)||(mode > 0777)) { + return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecUploadFileMode: %s", p1); + } + + dcfg->upload_filemode = (int)mode; + } + + return NULL; +} + static const char *cmd_upload_keep_files(cmd_parms *cmd, void *_dcfg, const char *p1) { directory_config *dcfg = (directory_config *)_dcfg; @@ -1120,7 +1512,7 @@ static const char *cmd_upload_keep_files(cmd_parms *cmd, void *_dcfg, const char if (strcasecmp(p1, "on") == 0) { dcfg->upload_keep_files = KEEP_FILES_ON; - } else + } else if (strcasecmp(p1, "off") == 0) { dcfg->upload_keep_files = KEEP_FILES_OFF; } else @@ -1136,7 +1528,7 @@ static const char *cmd_upload_keep_files(cmd_parms *cmd, void *_dcfg, const char static const char *cmd_web_app_id(cmd_parms *cmd, void *_dcfg, const char *p1) { directory_config *dcfg = (directory_config *)_dcfg; - // TODO enforce format (letters, digits, ., _, -) + /* ENH enforce format (letters, digits, ., _, -) */ dcfg->webappid = p1; return NULL; @@ -1147,9 +1539,9 @@ static const char *cmd_web_app_id(cmd_parms *cmd, void *_dcfg, const char *p1) { static const char *cmd_pdf_protect(cmd_parms *cmd, void *_dcfg, int flag) { directory_config *dcfg = (directory_config *)_dcfg; if (dcfg == NULL) return NULL; - + dcfg->pdfp_enabled = flag; - + return NULL; } @@ -1158,9 +1550,9 @@ static const char *cmd_pdf_protect_secret(cmd_parms *cmd, void *_dcfg, { directory_config *dcfg = (directory_config *)_dcfg; if (dcfg == NULL) return NULL; - + dcfg->pdfp_secret = p1; - + return NULL; } @@ -1169,9 +1561,9 @@ static const char *cmd_pdf_protect_timeout(cmd_parms *cmd, void *_dcfg, { directory_config *dcfg = (directory_config *)_dcfg; if (dcfg == NULL) return NULL; - + dcfg->pdfp_timeout = atoi(p1); - + return NULL; } @@ -1180,9 +1572,9 @@ static const char *cmd_pdf_protect_token_name(cmd_parms *cmd, void *_dcfg, { directory_config *dcfg = (directory_config *)_dcfg; if (dcfg == NULL) return NULL; - + dcfg->pdfp_token_name = p1; - + return NULL; } @@ -1191,9 +1583,9 @@ static const char *cmd_pdf_protect_intercept_get_only(cmd_parms *cmd, void *_dcf { directory_config *dcfg = (directory_config *)_dcfg; if (dcfg == NULL) return NULL; - + dcfg->pdfp_only_get = flag; - + return NULL; } @@ -1212,20 +1604,21 @@ static const char *cmd_pdf_protect_method(cmd_parms *cmd, void *_dcfg, return (const char *)apr_psprintf(cmd->pool, "ModSecurity: Unrecognised parameter value for SecPdfProtectMethod: %s", p1); } - + return NULL; } /* -- Geo Lookup configuration -- */ -static const char *cmd_geo_lookups_db(cmd_parms *cmd, void *_dcfg, +static const char *cmd_geo_lookup_db(cmd_parms *cmd, void *_dcfg, const char *p1) { + const char *filename = resolve_relative_path(cmd->pool, cmd->directive->filename, p1); char *error_msg; directory_config *dcfg = (directory_config *)_dcfg; if (dcfg == NULL) return NULL; - - if (geo_init(dcfg, p1, &error_msg) <= 0) { + + if (geo_init(dcfg, filename, &error_msg) <= 0) { return error_msg; } @@ -1233,6 +1626,85 @@ static const char *cmd_geo_lookups_db(cmd_parms *cmd, void *_dcfg, } +/* -- Cache -- */ + +static const char *cmd_cache_transformations(cmd_parms *cmd, void *_dcfg, const char *p1, const char *p2) { + directory_config *dcfg = (directory_config *)_dcfg; + + if (dcfg == NULL) return NULL; + + if (strcasecmp(p1, "on") == 0) + dcfg->cache_trans = MODSEC_CACHE_ENABLED; + else if (strcasecmp(p1, "off") == 0) + dcfg->cache_trans = MODSEC_CACHE_DISABLED; + else + return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecCacheTransformations: %s", p1); + + /* Process options */ + if (p2 != NULL) { + apr_table_t *vartable = apr_table_make(cmd->pool, 10); + apr_status_t rc; + char *error_msg = NULL; + const char *charval = NULL; + apr_int64_t intval = 0; + + if (vartable == NULL) { + return apr_psprintf(cmd->pool, "ModSecurity: Unable to process options for SecCacheTransformations"); + } + rc = msre_parse_generic(cmd->pool, p2, vartable, &error_msg); + if (rc < 0) { + return apr_psprintf(cmd->pool, "ModSecurity: Unable to parse options for SecCacheTransformations: %s", error_msg); + } + + /* minval */ + charval = apr_table_get(vartable, "minlen"); + if (charval != NULL) { + intval = apr_atoi64(charval); + if (errno == ERANGE) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations minlen out of range: %s", charval); + } + if (intval < 0) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations minlen must be positive: %s", charval); + } + + /* The NOT_SET indicator is -1, a signed long, and therfore + * we cannot be >= the unsigned value of NOT_SET. + */ + if ((unsigned long)intval >= (unsigned long)NOT_SET) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations minlen must be less than: %lu", (unsigned long)NOT_SET); + } + dcfg->cache_trans_min = (apr_size_t)intval; + } + + /* maxval */ + charval = apr_table_get(vartable, "maxlen"); + if (charval != NULL) { + intval = apr_atoi64(charval); + if (errno == ERANGE) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen out of range: %s", charval); + } + if (intval < 0) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen must be positive: %s", charval); + } + + /* The NOT_SET indicator is -1, a signed long, and therfore + * we cannot be >= the unsigned value of NOT_SET. + */ + if ((unsigned long)intval >= (unsigned long)NOT_SET) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen must be less than: %lu", (unsigned long)NOT_SET); + } + if ((intval != 0) && ((apr_size_t)intval < dcfg->cache_trans_min)) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen must not be less than minlen: %lu < %" APR_SIZE_T_FMT, (unsigned long)intval, dcfg->cache_trans_min); + } + dcfg->cache_trans_max = (apr_size_t)intval; + + } + } + + return NULL; +} + + /* -- Configuration directives definitions -- */ #define CMD_SCOPE_MAIN (RSRC_CONF) @@ -1245,9 +1717,9 @@ const command_rec module_directives[] = { cmd_action, NULL, CMD_SCOPE_ANY, - "" // TODO + "an action list" ), - + AP_INIT_TAKE1 ( "SecArgumentSeparator", cmd_argument_separator, @@ -1269,7 +1741,7 @@ const command_rec module_directives[] = { cmd_audit_log, NULL, CMD_SCOPE_ANY, - "The filename of the primary audit log file" + "filename of the primary audit log file" ), AP_INIT_TAKE1 ( @@ -1277,7 +1749,7 @@ const command_rec module_directives[] = { cmd_audit_log2, NULL, CMD_SCOPE_ANY, - "The filename of the secondary audit log file" + "filename of the secondary audit log file" ), AP_INIT_TAKE1 ( @@ -1312,12 +1784,28 @@ const command_rec module_directives[] = { "path to the audit log storage area; absolute, or relative to the root of the server" ), + AP_INIT_TAKE12 ( + "SecCacheTransformations", + cmd_cache_transformations, + NULL, + CMD_SCOPE_ANY, + "whether or not to cache transformations. Defaults to true." + ), + AP_INIT_TAKE1 ( "SecChrootDir", cmd_chroot_dir, NULL, CMD_SCOPE_MAIN, - "Path of the directory to which server will be chrooted" + "path of the directory to which server will be chrooted" + ), + + AP_INIT_TAKE1 ( + "SecComponentSignature", + cmd_component_signature, + NULL, + CMD_SCOPE_MAIN, + "component signature to add to ModSecurity signature." ), AP_INIT_FLAG ( @@ -1341,7 +1829,7 @@ const command_rec module_directives[] = { cmd_data_dir, NULL, CMD_SCOPE_MAIN, - "" // TODO + "path to the persistent data storage area" // TODO ), AP_INIT_TAKE1 ( @@ -1366,7 +1854,15 @@ const command_rec module_directives[] = { cmd_default_action, NULL, CMD_SCOPE_ANY, - "" // TODO + "default action list" + ), + + AP_INIT_TAKE1 ( + "SecGeoLookupDB", + cmd_geo_lookup_db, + NULL, + RSRC_CONF, + "database for geographical lookups module." ), AP_INIT_TAKE12 ( @@ -1378,157 +1874,11 @@ const command_rec module_directives[] = { ), AP_INIT_TAKE1 ( - "SecRequestBodyAccess", - cmd_request_body_access, + "SecMarker", + cmd_marker, NULL, CMD_SCOPE_ANY, - "On or Off" - ), - - AP_INIT_TAKE1 ( - "SecRequestBodyInMemoryLimit", - cmd_request_body_inmemory_limit, - NULL, - CMD_SCOPE_ANY, - "maximum request body size that will be placed in memory (except for POST urlencoded requests)." - ), - - AP_INIT_TAKE1 ( - "SecRequestBodyLimit", - cmd_request_body_limit, - NULL, - CMD_SCOPE_ANY, - "maximum request body size ModSecurity is allowed to access." - ), - - AP_INIT_TAKE1 ( - "SecResponseBodyAccess", - cmd_response_body_access, - NULL, - CMD_SCOPE_ANY, - "On or Off" - ), - - AP_INIT_TAKE1 ( - "SecResponseBodyLimit", - cmd_response_body_limit, - NULL, - CMD_SCOPE_ANY, - "" // TODO - ), - - AP_INIT_ITERATE ( - "SecResponseBodyMimeType", - cmd_response_body_mime_type, - NULL, - CMD_SCOPE_ANY, - "adds given MIME types to the list of types that will be buffered on output" - ), - - AP_INIT_NO_ARGS ( - "SecResponseBodyMimeTypesClear", - cmd_response_body_mime_types_clear, - NULL, - CMD_SCOPE_ANY, - "clears the list of MIME types that will be buffered on output" - ), - - AP_INIT_TAKE23 ( - "SecRule", - cmd_rule, - NULL, - CMD_SCOPE_ANY, - "" // TODO - ), - - AP_INIT_TAKE1 ( - "SecRuleEngine", - cmd_rule_engine, - NULL, - CMD_SCOPE_ANY, - "On or Off" - ), - - /* - AP_INIT_TAKE1 ( - "SecRuleImportById", - cmd_rule_import_by_id, - NULL, - CMD_SCOPE_ANY, - "" // TODO - ), - - AP_INIT_TAKE1 ( - "SecRuleImportByMsg", - cmd_rule_import_by_msg, - NULL, - CMD_SCOPE_ANY, - "" // TODO - ), - */ - - AP_INIT_FLAG ( - "SecRuleInheritance", - cmd_rule_inheritance, - NULL, - CMD_SCOPE_ANY, - "On or Off" - ), - - AP_INIT_ITERATE ( - "SecRuleRemoveById", - cmd_rule_remove_by_id, - NULL, - CMD_SCOPE_ANY, - "" // TODO - ), - - AP_INIT_ITERATE ( - "SecRuleRemoveByMsg", - cmd_rule_remove_by_msg, - NULL, - CMD_SCOPE_ANY, - "" // TODO - ), - - AP_INIT_TAKE1 ( - "SecServerSignature", - cmd_server_signature, - NULL, - CMD_SCOPE_MAIN, - "The new signature of the server" - ), - - AP_INIT_TAKE1 ( - "SecTmpDir", - cmd_tmp_dir, - NULL, - CMD_SCOPE_ANY, - "" // TODO - ), - - AP_INIT_TAKE1 ( - "SecUploadDir", - cmd_upload_dir, - NULL, - CMD_SCOPE_ANY, - "" // TODO - ), - - AP_INIT_TAKE1 ( - "SecUploadKeepFiles", - cmd_upload_keep_files, - NULL, - CMD_SCOPE_ANY, - "" // TODO - ), - - AP_INIT_TAKE1 ( - "SecWebAppId", - cmd_web_app_id, - NULL, - CMD_SCOPE_ANY, - "" // TODO + "marker for a skipAfter target" ), AP_INIT_FLAG ( @@ -1580,11 +1930,187 @@ const command_rec module_directives[] = { ), AP_INIT_TAKE1 ( - "SecGeoLookupsDb", - cmd_geo_lookups_db, + "SecRequestBodyAccess", + cmd_request_body_access, NULL, - RSRC_CONF, - "database for geographical lookups module." + CMD_SCOPE_ANY, + "On or Off" + ), + + AP_INIT_TAKE1 ( + "SecRequestBodyInMemoryLimit", + cmd_request_body_inmemory_limit, + NULL, + CMD_SCOPE_ANY, + "maximum request body size that will be placed in memory (except for POST urlencoded requests)." + ), + + AP_INIT_TAKE1 ( + "SecRequestBodyLimit", + cmd_request_body_limit, + NULL, + CMD_SCOPE_ANY, + "maximum request body size ModSecurity will accept." + ), + + AP_INIT_TAKE1 ( + "SecRequestBodyNoFilesLimit", + cmd_request_body_no_files_limit, + NULL, + CMD_SCOPE_ANY, + "maximum request body size ModSecurity will accept, but excluding the size of uploaded files." + ), + + AP_INIT_TAKE1 ( + "SecRequestEncoding", + cmd_request_encoding, + NULL, + CMD_SCOPE_ANY, + "character encoding used in request." + ), + + AP_INIT_TAKE1 ( + "SecResponseBodyAccess", + cmd_response_body_access, + NULL, + CMD_SCOPE_ANY, + "On or Off" + ), + + AP_INIT_TAKE1 ( + "SecResponseBodyLimit", + cmd_response_body_limit, + NULL, + CMD_SCOPE_ANY, + "byte limit for response body" + ), + + AP_INIT_TAKE1 ( + "SecResponseBodyLimitAction", + cmd_response_body_limit_action, + NULL, + CMD_SCOPE_ANY, + "what happens when the response body limit is reached" + ), + + AP_INIT_ITERATE ( + "SecResponseBodyMimeType", + cmd_response_body_mime_type, + NULL, + CMD_SCOPE_ANY, + "adds given MIME types to the list of types that will be buffered on output" + ), + + AP_INIT_NO_ARGS ( + "SecResponseBodyMimeTypesClear", + cmd_response_body_mime_types_clear, + NULL, + CMD_SCOPE_ANY, + "clears the list of MIME types that will be buffered on output" + ), + + AP_INIT_TAKE23 ( + "SecRule", + cmd_rule, + NULL, + CMD_SCOPE_ANY, + "rule target, operator and optional action list" + ), + + AP_INIT_TAKE1 ( + "SecRuleEngine", + cmd_rule_engine, + NULL, + CMD_SCOPE_ANY, + "On or Off" + ), + + AP_INIT_FLAG ( + "SecRuleInheritance", + cmd_rule_inheritance, + NULL, + CMD_SCOPE_ANY, + "On or Off" + ), + + AP_INIT_TAKE12 ( + "SecRuleScript", + cmd_rule_script, + NULL, + CMD_SCOPE_ANY, + "rule script and optional actionlist" + ), + + AP_INIT_ITERATE ( + "SecRuleRemoveById", + cmd_rule_remove_by_id, + NULL, + CMD_SCOPE_ANY, + "rule ID for removal" + ), + + AP_INIT_ITERATE ( + "SecRuleRemoveByMsg", + cmd_rule_remove_by_msg, + NULL, + CMD_SCOPE_ANY, + "rule message for removal" + ), + + AP_INIT_TAKE2 ( + "SecRuleUpdateActionById", + cmd_rule_update_action_by_id, + NULL, + CMD_SCOPE_ANY, + "updated action list" + ), + + AP_INIT_TAKE1 ( + "SecServerSignature", + cmd_server_signature, + NULL, + CMD_SCOPE_MAIN, + "the new signature of the server" + ), + + AP_INIT_TAKE1 ( + "SecTmpDir", + cmd_tmp_dir, + NULL, + CMD_SCOPE_ANY, + "path to the temporary storage area" + ), + + AP_INIT_TAKE1 ( + "SecUploadDir", + cmd_upload_dir, + NULL, + CMD_SCOPE_ANY, + "path to the file upload area" + ), + + AP_INIT_TAKE1 ( + "SecUploadFileMode", + cmd_upload_filemode, + NULL, + CMD_SCOPE_ANY, + "octal permissions mode for uploaded files" + ), + + AP_INIT_TAKE1 ( + "SecUploadKeepFiles", + cmd_upload_keep_files, + NULL, + CMD_SCOPE_ANY, + "On or Off" + ), + + AP_INIT_TAKE1 ( + "SecWebAppId", + cmd_web_app_id, + NULL, + CMD_SCOPE_ANY, + "id" ), { NULL } diff --git a/apache2/apache2_io.c b/apache2/apache2_io.c index 72d05141..63827db1 100644 --- a/apache2/apache2_io.c +++ b/apache2/apache2_io.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -30,6 +30,7 @@ apr_status_t input_filter(ap_filter_t *f, apr_bucket_brigade *bb_out, msc_data_chunk *chunk = NULL; apr_bucket *bucket; apr_status_t rc; + char *my_error_msg = NULL; if (msr == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, f->r->server, @@ -38,31 +39,42 @@ apr_status_t input_filter(ap_filter_t *f, apr_bucket_brigade *bb_out, return APR_EGENERAL; } + msr->r = f->r; + + if (msr->phase < PHASE_REQUEST_BODY) { + msr_log(msr, 1, "Internal error: REQUEST_BODY phase incomplete for input filter in phase %d", msr->phase); + return APR_EGENERAL; + } + if ((msr->if_status == IF_STATUS_COMPLETE)||(msr->if_status == IF_STATUS_NONE)) { if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Input filter: Input forwarding already complete, skipping (f %x, r %x).", f, f->r); + msr_log(msr, 4, "Input filter: Input forwarding already complete, skipping (f %pp, r %pp).", f, f->r); } ap_remove_input_filter(f); return ap_get_brigade(f->next, bb_out, mode, block, nbytes); } if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Input filter: Forwarding input: mode=%i, block=%i, nbytes=%" APR_OFF_T_FMT - " (f %x, r %x).", mode, block, nbytes, f, f->r); + msr_log(msr, 4, "Input filter: Forwarding input: mode=%d, block=%d, nbytes=%" APR_OFF_T_FMT + " (f %pp, r %pp).", mode, block, nbytes, f, f->r); } if (msr->if_started_forwarding == 0) { msr->if_started_forwarding = 1; - rc = modsecurity_request_body_retrieve_start(msr); + rc = modsecurity_request_body_retrieve_start(msr, &my_error_msg); if (rc == -1) { - // TODO err + if (my_error_msg != NULL) { + msr_log(msr, 1, "%s", my_error_msg); + } return APR_EGENERAL; } } - rc = modsecurity_request_body_retrieve(msr, &chunk, (unsigned int)nbytes); + rc = modsecurity_request_body_retrieve(msr, &chunk, (unsigned int)nbytes, &my_error_msg); if (rc == -1) { - // TODO err + if (my_error_msg != NULL) { + msr_log(msr, 1, "%s", my_error_msg); + } return APR_EGENERAL; } @@ -92,7 +104,7 @@ apr_status_t input_filter(ap_filter_t *f, apr_bucket_brigade *bb_out, APR_BRIGADE_INSERT_TAIL(bb_out, bucket); if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Input filter: Forwarded %lu bytes.", chunk->length); + msr_log(msr, 4, "Input filter: Forwarded %" APR_SIZE_T_FMT " bytes.", chunk->length); } } @@ -149,14 +161,13 @@ apr_status_t read_request_body(modsec_rec *msr, char **error_msg) { msr_log(msr, 4, "Input filter: Reading request body."); } - if (modsecurity_request_body_start(msr) < 0) { - // TODO err + if (modsecurity_request_body_start(msr, error_msg) < 0) { return -1; } seen_eos = 0; bb_in = apr_brigade_create(msr->mp, r->connection->bucket_alloc); - if (bb_in == NULL) return -1; + if (bb_in == NULL) return -1; do { apr_status_t rc; @@ -167,25 +178,18 @@ apr_status_t read_request_body(modsec_rec *msr, char **error_msg) { */ switch(rc) { case APR_TIMEUP : + *error_msg = apr_psprintf(msr->mp, "Error reading request body: %s", get_apr_error(msr->mp, rc)); return -4; - break; case -3 : *error_msg = apr_psprintf(msr->mp, "Error reading request body: HTTP Error 413 - Request entity too large. (Most likely.)"); - rc = -3; - break; + return -3; case APR_EGENERAL : *error_msg = apr_psprintf(msr->mp, "Error reading request body: Client went away."); - rc = -2; - break; + return -2; default : *error_msg = apr_psprintf(msr->mp, "Error reading request body: %s", get_apr_error(msr->mp, rc)); - rc = -1; - break; + return -1; } - - if (*error_msg) msr_log(msr, 1, "%s", *error_msg); - - return rc; } /* Loop through the buckets in the brigade in order @@ -199,27 +203,32 @@ apr_status_t read_request_body(modsec_rec *msr, char **error_msg) { apr_size_t buflen; rc = apr_bucket_read(bucket, &buf, &buflen, APR_BLOCK_READ); - if (rc != APR_SUCCESS) { - msr_log(msr, 1, "Input filter: Failed reading input / bucket (%i): %s", - rc, get_apr_error(msr->mp, rc)); + if (rc != APR_SUCCESS) { + *error_msg = apr_psprintf(msr->mp, "Failed reading input / bucket (%d): %s", rc, get_apr_error(msr->mp, rc)); return -1; } if (msr->txcfg->debuglog_level >= 9) { - msr_log(msr, 9, "Input filter: Bucket type %s contains %i bytes.", + msr_log(msr, 9, "Input filter: Bucket type %s contains %" APR_SIZE_T_FMT " bytes.", bucket->type->name, buflen); } /* Check request body limit (should only trigger on chunked requests). */ if (msr->reqbody_length + buflen > (apr_size_t)msr->txcfg->reqbody_limit) { *error_msg = apr_psprintf(msr->mp, "Requests body is larger than the " - "configured limit (%lu).", msr->txcfg->reqbody_limit); + "configured limit (%ld).", msr->txcfg->reqbody_limit); return -5; } if (buflen != 0) { - if (modsecurity_request_body_store(msr, buf, buflen) < 0) { - // TODO err + int rcbs = modsecurity_request_body_store(msr, buf, buflen, error_msg); + if (rcbs < 0) { + if (rcbs == -5) { + *error_msg = apr_psprintf(msr->mp, "Requests body no files data length is larger than the " + "configured limit (%ld).", msr->txcfg->reqbody_no_files_limit); + return -5; + } + return -1; } @@ -234,10 +243,11 @@ apr_status_t read_request_body(modsec_rec *msr, char **error_msg) { apr_brigade_cleanup(bb_in); } while(!seen_eos); - modsecurity_request_body_end(msr); + // TODO: Why ignore the return code here? + modsecurity_request_body_end(msr, error_msg); if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Input filter: Completed receiving request body (length %lu).", + msr_log(msr, 4, "Input filter: Completed receiving request body (length %" APR_SIZE_T_FMT ").", msr->reqbody_length); } @@ -249,25 +259,6 @@ apr_status_t read_request_body(modsec_rec *msr, char **error_msg) { /* -- Output filter -- */ -/** - * Sends a brigade with an error bucket down the filter chain. - */ -static apr_status_t send_error_bucket(ap_filter_t *f, int status) { - apr_bucket_brigade *brigade = NULL; - apr_bucket *bucket = NULL; - - brigade = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc); - if (brigade == NULL) return APR_EGENERAL; - bucket = ap_bucket_error_create(status, NULL, f->r->pool, f->r->connection->bucket_alloc); - if (bucket == NULL) return APR_EGENERAL; - APR_BRIGADE_INSERT_TAIL(brigade, bucket); - bucket = apr_bucket_eos_create(f->r->connection->bucket_alloc); - if (bucket == NULL) return APR_EGENERAL; - APR_BRIGADE_INSERT_TAIL(brigade, bucket); - - return ap_pass_brigade(f->next, brigade); -} - /** * Examines the configuration and the response MIME type * in order to determine whether output buffering should @@ -281,13 +272,14 @@ static int output_filter_should_run(modsec_rec *msr, request_rec *r) { if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Output filter: Response body buffering is not enabled."); } + return 0; } /* Check MIME type. */ if ((msr->txcfg->of_mime_types == NULL)||(msr->txcfg->of_mime_types == NOT_SET_P)) { - msr_log(msr, 1, "Output filter: MIME type structures are corrupted (internal error)."); + msr_log(msr, 1, "Output filter: MIME type structures corrupted (internal error)."); return -1; } @@ -295,7 +287,10 @@ static int output_filter_should_run(modsec_rec *msr, request_rec *r) { char *p = NULL; content_type = apr_pstrdup(msr->mp, r->content_type); - if (content_type == NULL) return -1; + if (content_type == NULL) { + msr_log(msr, 1, "Output filter: Failed to allocate memory for content type."); + return -1; + } /* Hide the character encoding information * if present. Sometimes the content type header @@ -320,6 +315,8 @@ static int output_filter_should_run(modsec_rec *msr, request_rec *r) { if (apr_table_get(msr->txcfg->of_mime_types, content_type) != NULL) return 1; + msr_log(msr, 4, "Output filter: Not buffering response body for unconfigured MIME type \"%s\".", content_type); + return 0; } @@ -334,20 +331,31 @@ static apr_status_t output_filter_init(modsec_rec *msr, ap_filter_t *f, apr_status_t rc; msr->of_brigade = apr_brigade_create(msr->mp, f->c->bucket_alloc); - if (msr->of_brigade == NULL) return -1; + if (msr->of_brigade == NULL) { + msr_log(msr, 1, "Output filter: Failed to create brigade."); + return -1; + } msr->of_status = OF_STATUS_IN_PROGRESS; - + rc = output_filter_should_run(msr, r); - if (rc < 0) return -1; + if (rc < 0) return -1; /* output_filter_should_run() generates error msg */ if (rc == 0) return 0; + /* Do not check the output limit if we are willing to + * process partial response bodies. + */ + + if (msr->txcfg->of_limit_action == RESPONSE_BODY_LIMIT_ACTION_PARTIAL) { + return 1; + } + /* Look up the Content-Length header to see if we know * the amount of data coming our way. If we do and if * it's too much we might want to stop processing right here. */ s_content_length = apr_table_get(r->headers_out, "Content-Length"); if (s_content_length == NULL) { - /* Try this too, mod_cgi seems to put headers there */ + /* Try this too, mod_cgi seems to put headers there. */ s_content_length = apr_table_get(r->err_headers_out, "Content-Length"); } @@ -356,26 +364,111 @@ static apr_status_t output_filter_init(modsec_rec *msr, ap_filter_t *f, len = strtol(s_content_length, NULL, 10); if ((len == LONG_MIN)||(len == LONG_MAX)||(len < 0)||(len >= 1073741824)) { - msr_log(msr, 1, "Output filter: Invalid Content-Length: %s", log_escape_nq(r->pool, (char *)s_content_length)); - return -1; + msr_log(msr, 1, "Output filter: Invalid Content-Length: %s", log_escape_nq(r->pool, + (char *)s_content_length)); + return -1; /* Invalid. */ } if (len == 0) { if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Output filter: Skipping response since Content-Length is zero."); } + return 0; } if (len > msr->txcfg->of_limit) { - msr_log(msr, 1, "Output filter: Content-Length (%s) over the limit (%lu).", log_escape_nq(r->pool, (char *)s_content_length), msr->txcfg->of_limit); - return -2; + msr_log(msr, 1, "Output filter: Content-Length (%s) over the limit (%ld).", + log_escape_nq(r->pool, (char *)s_content_length), msr->txcfg->of_limit); + return -2; /* Over the limit. */ } } return 1; } +/** + * Send the accumulated content down the filter stream + * and to the client. + */ +static apr_status_t send_of_brigade(modsec_rec *msr, ap_filter_t *f) { + apr_status_t rc; + + rc = ap_pass_brigade(f->next, msr->of_brigade); + if (rc != APR_SUCCESS) { + int log_level = 1; + + if (APR_STATUS_IS_ECONNRESET(rc)) { + /* Message "Connection reset by peer" is common and not a sign + * of something unusual. Hence we don't want to make a big deal + * about it, logging at NOTICE level. Everything else we log + * at ERROR level. + */ + log_level = 3; + } + + if (msr->txcfg->debuglog_level >= log_level) { + msr_log(msr, log_level, "Output filter: Error while forwarding response data (%d): %s", + rc, get_apr_error(msr->mp, rc)); + } + + return rc; + } + + return APR_SUCCESS; +} + +/** + * + */ +static void prepend_content_to_of_brigade(modsec_rec *msr, ap_filter_t *f) { + if ((msr->txcfg->content_injection_enabled) && (msr->content_prepend) && (!msr->of_skipping)) { + apr_bucket *bucket_ci = NULL; + + bucket_ci = apr_bucket_heap_create(msr->content_prepend, + msr->content_prepend_len, NULL, f->r->connection->bucket_alloc); + APR_BRIGADE_INSERT_HEAD(msr->of_brigade, bucket_ci); + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Content Injection (b): Added content to top: %s", + log_escape_nq_ex(msr->mp, msr->content_prepend, msr->content_prepend_len)); + } + } +} + +/** + * + */ +static int flatten_response_body(modsec_rec *msr) { + apr_status_t rc; + + msr->resbody_status = RESBODY_STATUS_READ_BRIGADE; + + if (msr->resbody_length + 1 <= 0) { + msr_log(msr, 1, "Output filter: Invalid response length: %" APR_SIZE_T_FMT, msr->resbody_length); + return -1; + } + + msr->resbody_data = apr_palloc(msr->mp, msr->resbody_length + 1); + if (msr->resbody_data == NULL) { + msr_log(msr, 1, "Output filter: Response body data memory allocation failed. Asked for: %" APR_SIZE_T_FMT, + msr->resbody_length + 1); + return -1; + } + + rc = apr_brigade_flatten(msr->of_brigade, msr->resbody_data, &msr->resbody_length); + if (rc != APR_SUCCESS) { + msr_log(msr, 1, "Output filter: Failed to flatten brigade (%d): %s", rc, + get_apr_error(msr->mp, rc)); + return -1; + } + + msr->resbody_data[msr->resbody_length] = '\0'; + msr->resbody_status = RESBODY_STATUS_READ; + + return 1; +} + /** * Output filter. */ @@ -384,39 +477,43 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { modsec_rec *msr = (modsec_rec *)f->ctx; apr_bucket *bucket = NULL, *eos_bucket = NULL; apr_status_t rc; + int start_skipping = 0; + /* Do we have the context? */ if (msr == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, f->r->server, "ModSecurity: Internal Error: msr is null in output filter."); ap_remove_output_filter(f); - return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR); + return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR); } - if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Output filter: Receiving output (f %x, r %x).", f, f->r); + msr->r = r; + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Output filter: Receiving output (f %pp, r %pp).", f, f->r); } - + /* Initialise on first invocation */ if (msr->of_status == OF_STATUS_NOT_STARTED) { /* Update our context from the request structure. */ msr->r = r; msr->response_status = r->status; - msr->status_line = ((r->status_line != NULL) + msr->status_line = ((r->status_line != NULL) ? r->status_line : ap_get_status_line(r->status)); msr->response_protocol = get_response_protocol(r); msr->response_headers = apr_table_overlay(msr->mp, r->err_headers_out, r->headers_out); - /* Process phase RESPONSE_HEADERS */ + /* Process phase RESPONSE_HEADERS */ rc = modsecurity_process_phase(msr, PHASE_RESPONSE_HEADERS); if (rc < 0) { /* error */ ap_remove_output_filter(f); - return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR); + return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR); } if (rc > 0) { /* transaction needs to be interrupted */ int status = perform_interception(msr); if (status != DECLINED) { /* DECLINED means we allow-ed the request. */ ap_remove_output_filter(f); - return send_error_bucket(f, status); + return send_error_bucket(msr, f, status); } } @@ -429,7 +526,7 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { ap_remove_output_filter(f); msr->of_status = OF_STATUS_COMPLETE; msr->resbody_status = RESBODY_STATUS_ERROR; - return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR); + return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR); case 0 : /* We do not want to observe this response body * but we need to remain attached to observe @@ -444,7 +541,7 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { break; } - // If injecting content unset headers now. + /* If injecting content unset headers now. */ if (msr->txcfg->content_injection_enabled == 0) { if (msr->txcfg->debuglog_level >= 9) { msr_log(msr, 9, "Content Injection: Not enabled."); @@ -455,7 +552,7 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { apr_table_unset(msr->r->headers_out, "Last-Modified"); apr_table_unset(msr->r->headers_out, "ETag"); apr_table_unset(msr->r->headers_out, "Expires"); - + if (msr->txcfg->debuglog_level >= 9) { msr_log(msr, 9, "Content Injection: Removing headers (C-L, L-M, Etag, Expires)."); } @@ -465,9 +562,9 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { } } } - - // Content injection (prepend & non-buffering). - if (msr->txcfg->content_injection_enabled && msr->content_prepend && msr->of_skipping) { + + /* Content injection (prepend & non-buffering). */ + if ((msr->txcfg->content_injection_enabled) && (msr->content_prepend) && (msr->of_skipping)) { apr_bucket *bucket_ci = apr_bucket_heap_create(msr->content_prepend, msr->content_prepend_len, NULL, f->r->connection->bucket_alloc); APR_BRIGADE_INSERT_HEAD(bb_in, bucket_ci); @@ -494,39 +591,67 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { const char *buf; apr_size_t buflen; - if (msr->of_skipping == 0) { + /* Look into response data if configured to do so, + * unless we've already processed a partial response. + */ + if ((msr->of_skipping == 0)&&(!msr->of_partial)) { /* Observe the response data. */ + /* Retrieve data from the bucket. */ rc = apr_bucket_read(bucket, &buf, &buflen, APR_BLOCK_READ); - if (rc != APR_SUCCESS) { + if (rc != APR_SUCCESS) { msr->of_status = OF_STATUS_COMPLETE; msr->resbody_status = RESBODY_STATUS_ERROR; - msr_log(msr, 1, "Output filter: Failed to read bucket (rc %i): %s", + + msr_log(msr, 1, "Output filter: Failed to read bucket (rc %d): %s", rc, get_apr_error(r->pool, rc)); + ap_remove_output_filter(f); - return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR); + return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR); } if (msr->txcfg->debuglog_level >= 9) { - msr_log(msr, 9, "Output filter: Bucket type %s contains %i bytes.", + msr_log(msr, 9, "Output filter: Bucket type %s contains %" APR_SIZE_T_FMT " bytes.", bucket->type->name, buflen); } + /* Check the response size. */ if (msr->resbody_length > (apr_size_t)msr->txcfg->of_limit) { - msr_log(msr, 1, "Output filter: Response body too large (over limit of %lu, total length not known).", - msr->txcfg->of_limit); - msr->of_status = OF_STATUS_COMPLETE; - msr->resbody_status = RESBODY_STATUS_PARTIAL; - ap_remove_output_filter(f); - return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR); - } + /* The size of the response is larger than what we're + * ready to accept. We need to decide what we want to do + * about it. + */ + if (msr->txcfg->of_limit_action == RESPONSE_BODY_LIMIT_ACTION_REJECT) { + /* Reject response. */ + msr_log(msr, 1, "Output filter: Response body too large (over limit of %ld, " + "total not specified).", msr->txcfg->of_limit); - msr->resbody_length += buflen; + msr->of_status = OF_STATUS_COMPLETE; + msr->resbody_status = RESBODY_STATUS_PARTIAL; + + ap_remove_output_filter(f); + return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR); + } else { + /* Process partial response. */ + start_skipping = 1; + msr->resbody_length = msr->txcfg->of_limit; + + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Output filter: Processing partial response body (limit %ld)", + msr->txcfg->of_limit); + } + } + } else { + msr->resbody_length += buflen; + } } + /* Have we reached the end of the response? */ if (APR_BUCKET_IS_EOS(bucket)) { eos_bucket = bucket; - // Inject content (append & non-buffering). - if (msr->txcfg->content_injection_enabled && msr->content_append && msr->of_skipping) { + /* Inject content (append & non-buffering). */ + if ((msr->txcfg->content_injection_enabled) && (msr->content_append) + && (msr->of_skipping || msr->of_partial || start_skipping)) + { apr_bucket *bucket_ci = NULL; bucket_ci = apr_bucket_heap_create(msr->content_append, @@ -547,90 +672,94 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { * we have in the context, but only if we actually * want to keep the response body. */ - if (msr->of_skipping == 0) { + if ((msr->of_skipping == 0)&&(msr->of_partial == 0)) { ap_save_brigade(f, &msr->of_brigade, &bb_in, msr->mp); + /* Do we need to process a partial response? */ + if (start_skipping) { + if (flatten_response_body(msr) < 0) { + return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR); + } + + /* Process phase RESPONSE_BODY */ + rc = modsecurity_process_phase(msr, PHASE_RESPONSE_BODY); + if (rc < 0) { + return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR); + } + if (rc > 0) { + int status = perform_interception(msr); + if (status != DECLINED) { /* DECLINED means we allow-ed the request. */ + return send_error_bucket(msr, f, status); + } + } + + /* Prepend content as necessary. */ + prepend_content_to_of_brigade(msr, f); + + if ((rc = send_of_brigade(msr, f)) != APR_SUCCESS) { + return rc; + } + + msr->of_partial = 1; + } + if (msr->of_done_reading == 0) { - /* We are done for now. We will be called again with more data. */ + /* We are done for now. We will be called again with more data. */ return APR_SUCCESS; } if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Output filter: Completed receiving response (length %lu).", - msr->resbody_length); + msr_log(msr, 4, "Output filter: Completed receiving response body (buffered %s - %" APR_SIZE_T_FMT " bytes).", + (msr->of_partial ? "partial" : "full"), msr->resbody_length); } - } else { + } else { /* Not looking at response data. */ if (msr->of_done_reading == 0) { + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Output filter: Sending input brigade directly."); + } + return ap_pass_brigade(f->next, bb_in); } if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Output filter: Completed receiving response."); + msr_log(msr, 4, "Output filter: Completed receiving response body (non-buffering)."); } } - /* We're not coming back here. */ + /* We've done our thing; remove us from the filter list. */ msr->of_status = OF_STATUS_COMPLETE; ap_remove_output_filter(f); - if (msr->of_skipping == 0) { - /* We've done with reading, it's time to inspect the data. */ - msr->resbody_status = RESBODY_STATUS_READ_BRIGADE; - - if (msr->resbody_length + 1 <= 0) { - msr_log(msr, 1, "Output filter: Invalid response length: %lu", msr->resbody_length); - return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR); + /* Process phase RESPONSE_BODY, but + * only if it hasn't been processed already. + */ + if (msr->phase < PHASE_RESPONSE_BODY) { + if (flatten_response_body(msr) < 0) { + return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR); } - msr->resbody_data = apr_palloc(msr->mp, msr->resbody_length + 1); - if (msr->resbody_data == NULL) { - msr_log(msr, 1, "Output filter: Response body data memory allocation failed. Asked for: %li", - msr->resbody_length + 1); - return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR); + rc = modsecurity_process_phase(msr, PHASE_RESPONSE_BODY); + if (rc < 0) { + return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR); } - - // TODO Why does the function below take pointer to length? Will it modify it? - rc = apr_brigade_flatten(msr->of_brigade, msr->resbody_data, &msr->resbody_length); - if (rc != APR_SUCCESS) { - msr_log(msr, 1, "Output filter: Failed to flatten brigade (%i): %s", rc, - get_apr_error(r->pool, rc)); - return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR); - } - msr->resbody_data[msr->resbody_length] = '\0'; - msr->resbody_status = RESBODY_STATUS_READ; - } - - /* Process phase RESPONSE_BODY */ - rc = modsecurity_process_phase(msr, PHASE_RESPONSE_BODY); - if (rc < 0) { - return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR); - } - if (rc > 0) { - int status = perform_interception(msr); - if (status != DECLINED) { /* DECLINED means we allow-ed the request. */ - return send_error_bucket(f, status); - } - } - - if (msr->of_skipping == 0) { - record_time_checkpoint(msr, 3); - - // Inject content into response (prepend & buffering). - if (msr->txcfg->content_injection_enabled && msr->content_prepend && (!msr->of_skipping)) { - apr_bucket *bucket_ci = NULL; - - bucket_ci = apr_bucket_heap_create(msr->content_prepend, - msr->content_prepend_len, NULL, f->r->connection->bucket_alloc); - APR_BRIGADE_INSERT_HEAD(msr->of_brigade, bucket_ci); - - if (msr->txcfg->debuglog_level >= 9) { - msr_log(msr, 9, "Content Injection (b): Added content to top: %s", - log_escape_nq_ex(msr->mp, msr->content_prepend, msr->content_prepend_len)); + if (rc > 0) { + int status = perform_interception(msr); + if (status != DECLINED) { /* DECLINED means we allow-ed the request. */ + return send_error_bucket(msr, f, status); } } + } - // Inject content into response (append & buffering). - if (msr->txcfg->content_injection_enabled && msr->content_append && (!msr->of_skipping)) { + /* Now send data down the filter stream + * (full-buffering only). + */ + if ((msr->of_skipping == 0)&&(!msr->of_partial)) { + record_time_checkpoint(msr, 3); + + prepend_content_to_of_brigade(msr, f); + + /* Inject content into response (append & buffering). */ + if ((msr->txcfg->content_injection_enabled) && (msr->content_append)) { apr_bucket *bucket_ci = NULL; bucket_ci = apr_bucket_heap_create(msr->content_append, @@ -643,34 +772,24 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { } } - rc = ap_pass_brigade(f->next, msr->of_brigade); - if (rc != APR_SUCCESS) { - int log_level = 1; - - if (APR_STATUS_IS_ECONNRESET(rc)) { - /* Message "Connection reset by peer" is common and not a sign - * of something unusual. Hence we don't want to make a big deal - * about it, logging at NOTICE level. Everything else we log - * at ERROR level. - */ - log_level = 3; - } - - msr_log(msr, log_level, "Output filter: Error while forwarding response data (%i): %s", - rc, get_apr_error(msr->mp, rc)); - + /* Send data down the filter stream. */ + if ((rc = send_of_brigade(msr, f)) != APR_SUCCESS) { return rc; } } - + /* Another job well done! */ if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Output filter: Output forwarding complete."); } - if (msr->of_skipping == 0) { + if ((msr->of_skipping == 0)&&(msr->of_partial == 0)) { return APR_SUCCESS; } else { + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Output filter: Sending input brigade directly."); + } + return ap_pass_brigade(f->next, bb_in); } } diff --git a/apache2/apache2_util.c b/apache2/apache2_util.c index f60d1ce8..543b918e 100644 --- a/apache2/apache2_util.c +++ b/apache2/apache2_util.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -13,6 +13,52 @@ #include "http_core.h" #include "util_script.h" +/** + * Sends a brigade with an error bucket down the filter chain. + */ +apr_status_t send_error_bucket(modsec_rec *msr, ap_filter_t *f, int status) { + apr_bucket_brigade *brigade = NULL; + apr_bucket *bucket = NULL; + + /* Set the status line explicitly for the error document */ + f->r->status_line = ap_get_status_line(status); + + /* Force alert log for any errors that are not already marked relevant + * to prevent any missing error messages in the code from going + * unnoticed. To prevent this error, all code should either set + * is_relevant, or just use msr_log with a level <= 3 prior to + * calling this function. + */ + if ((msr != NULL) && (msr->is_relevant == 0)) { + msr_log(msr, 1, "Internal error: Issuing \"%s\" for unspecified error.", + f->r->status_line); + } + + brigade = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc); + if (brigade == NULL) return APR_EGENERAL; + + bucket = ap_bucket_error_create(status, NULL, f->r->pool, f->r->connection->bucket_alloc); + if (bucket == NULL) return APR_EGENERAL; + + APR_BRIGADE_INSERT_TAIL(brigade, bucket); + + bucket = apr_bucket_eos_create(f->r->connection->bucket_alloc); + if (bucket == NULL) return APR_EGENERAL; + + APR_BRIGADE_INSERT_TAIL(brigade, bucket); + + ap_pass_brigade(f->next, brigade); + + /* NOTE: + * It may not matter what we do from the filter as it may be too + * late to even generate an error (already sent to client). Nick Kew + * recommends to return APR_EGENERAL in hopes that the handler in control + * will notice and do The Right Thing. So, that is what we do now. + */ + + return APR_EGENERAL; +} + /** * Execute system command. First line of the output will be returned in * the "output" parameter. @@ -46,7 +92,7 @@ int apache2_exec(modsec_rec *msr, const char *command, const char **argv, char * procnew = apr_pcalloc(r->pool, sizeof(*procnew)); if (procnew == NULL) { - msr_log(msr, 1, "Exec: Unable to allocate %i bytes.", sizeof(*procnew)); + msr_log(msr, 1, "Exec: Unable to allocate %lu bytes.", (unsigned long)sizeof(*procnew)); return -1; } @@ -131,17 +177,17 @@ void record_time_checkpoint(modsec_rec *msr, int checkpoint_no) { msr->time_checkpoint_3 = now; break; default : - msr_log(msr, 1, "Internal Error: Unknown checkpoint: %i", checkpoint_no); + msr_log(msr, 1, "Internal Error: Unknown checkpoint: %d", checkpoint_no); return; break; } /* Apache-specific stuff. */ apr_snprintf(note, 99, "%" APR_TIME_T_FMT, (now - msr->request_time)); - apr_snprintf(note_name, 99, "mod_security-time%i", checkpoint_no); + apr_snprintf(note_name, 99, "mod_security-time%d", checkpoint_no); apr_table_set(msr->r->notes, note_name, note); - msr_log(msr, 4, "Time #%i: %s", checkpoint_no, note); + msr_log(msr, 4, "Time #%d: %s", checkpoint_no, note); } /** @@ -200,9 +246,9 @@ void internal_log(request_rec *r, directory_config *dcfg, modsec_rec *msr, /* Construct the message. */ apr_vsnprintf(str1, sizeof(str1), text, ap); - apr_snprintf(str2, sizeof(str2), "[%s] [%s/sid#%lx][rid#%lx][%s][%i] %s\n", - current_logtime(msr->mp), ap_get_server_name(r), (unsigned long)(r->server), - (unsigned long)r, ((r->uri == NULL) ? "" : log_escape_nq(msr->mp, r->uri)), + apr_snprintf(str2, sizeof(str2), "[%s] [%s/sid#%pp][rid#%pp][%s][%d] %s\n", + current_logtime(msr->mp), ap_get_server_name(r), (r->server), + r, ((r->uri == NULL) ? "" : log_escape_nq(msr->mp, r->uri)), level, str1); /* Write to the debug log. */ @@ -230,10 +276,13 @@ void internal_log(request_rec *r, directory_config *dcfg, modsec_rec *msr, ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r->server, "[client %s] ModSecurity: %s%s [uri \"%s\"]%s", r->connection->remote_ip, str1, - hostname, log_escape(msr->mp, r->unparsed_uri), unique_id); + hostname, log_escape(msr->mp, r->uri), unique_id); /* Add this message to the list. */ if (msr != NULL) { + /* Force relevency if this is an alert */ + msr->is_relevant++; + *(const char **)apr_array_push(msr->alerts) = apr_pstrdup(msr->mp, str1); } } @@ -269,17 +318,17 @@ char *format_error_log_message(apr_pool_t *mp, error_message *em) { log_escape(mp, (char *)em->file)); if (s_file == NULL) return NULL; } - + if (em->line > 0) { - s_line = apr_psprintf(mp, "[line %i] ", em->line); + s_line = apr_psprintf(mp, "[line %d] ", em->line); if (s_line == NULL) return NULL; } - s_level = apr_psprintf(mp, "[level %i] ", em->level); + s_level = apr_psprintf(mp, "[level %d] ", em->level); if (s_level == NULL) return NULL; if (em->status != 0) { - s_status = apr_psprintf(mp, "[status %i] ", em->status); + s_status = apr_psprintf(mp, "[status %d] ", em->status); if (s_status == NULL) return NULL; } diff --git a/apache2/api/README b/apache2/api/README index fcf0bc82..d1bab262 100644 --- a/apache2/api/README +++ b/apache2/api/README @@ -3,6 +3,9 @@ This directory contains two examples how you can extend ModSecurity without having to touch it directly, simply by creating custom Apache modules. +NOTE: ModSecurity must be compiled with API support + to use this feature (do not use -DNO_MODSEC_API). + 1) Module mod_tfn_reverse.c creates a custom transformation diff --git a/apache2/build/PrintPath b/apache2/build/PrintPath new file mode 100755 index 00000000..2a2b48b6 --- /dev/null +++ b/apache2/build/PrintPath @@ -0,0 +1,130 @@ +#!/bin/sh +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +# Look for program[s] somewhere in $PATH. +# +# Options: +# -s +# Do not print out full pathname. (silent) +# -pPATHNAME +# Look in PATHNAME instead of $PATH +# +# Usage: +# PrintPath [-s] [-pPATHNAME] program [program ...] +# +# Initially written by Jim Jagielski for the Apache configuration mechanism +# (with kudos to Kernighan/Pike) + +## +# Some "constants" +## +pathname=$PATH +echo="yes" + +## +# Find out what OS we are running for later on +## +os=`(uname) 2>/dev/null` + +## +# Parse command line +## +for args in $* +do + case $args in + -s ) echo="no" ;; + -p* ) pathname="`echo $args | sed 's/^..//'`" ;; + * ) programs="$programs $args" ;; + esac +done + +## +# Now we make the adjustments required for OS/2 and everyone +# else :) +# +# First of all, all OS/2 programs have the '.exe' extension. +# Next, we adjust PATH (or what was given to us as PATH) to +# be whitespace separated directories. +# Finally, we try to determine the best flag to use for +# test/[] to look for an executable file. OS/2 just has '-r' +# but with other OSs, we do some funny stuff to check to see +# if test/[] knows about -x, which is the prefered flag. +## + +if [ "x$os" = "xOS/2" ] +then + ext=".exe" + pathname=`echo -E $pathname | + sed 's/^;/.;/ + s/;;/;.;/g + s/;$/;./ + s/;/ /g + s/\\\\/\\//g' ` + test_exec_flag="-r" +else + ext="" # No default extensions + pathname=`echo $pathname | + sed 's/^:/.:/ + s/::/:.:/g + s/:$/:./ + s/:/ /g' ` + # Here is how we test to see if test/[] can handle -x + testfile="pp.t.$$" + + cat > $testfile </dev/null`; then + test_exec_flag="-x" + else + test_exec_flag="-r" + fi + rm -f $testfile +fi + +for program in $programs +do + for path in $pathname + do + if [ $test_exec_flag $path/${program}${ext} ] && \ + [ ! -d $path/${program}${ext} ]; then + if [ "x$echo" = "xyes" ]; then + echo $path/${program}${ext} + fi + exit 0 + fi + +# Next try without extension (if one was used above) + if [ "x$ext" != "x" ]; then + if [ $test_exec_flag $path/${program} ] && \ + [ ! -d $path/${program} ]; then + if [ "x$echo" = "xyes" ]; then + echo $path/${program} + fi + exit 0 + fi + fi + done +done +exit 1 + diff --git a/apache2/build/apxs-wrapper.in b/apache2/build/apxs-wrapper.in new file mode 100755 index 00000000..aa53e7d8 --- /dev/null +++ b/apache2/build/apxs-wrapper.in @@ -0,0 +1,12 @@ +#!@SHELL@ + +WRAPPED_OPTS="" +for opt in "$@"; do + case "$opt" in + # Fix for -R not working w/apxs + -R*) WRAPPED_OPTS="$WRAPPED_OPTS -Wl,$opt" ;; + *) WRAPPED_OPTS="$WRAPPED_OPTS $opt" ;; + esac +done + +exec @APXS@ $WRAPPED_OPTS diff --git a/apache2/build/buildcheck.sh b/apache2/build/buildcheck.sh new file mode 100755 index 00000000..3097b0a4 --- /dev/null +++ b/apache2/build/buildcheck.sh @@ -0,0 +1,54 @@ +#! /bin/sh + +echo "buildconf: checking installation..." + +# autoconf 2.50 or newer +ac_version=`${AUTOCONF:-autoconf} --version 2>/dev/null|sed -e 's/^[^0-9]*//;s/[a-z]* *$//;q'` +if test -z "$ac_version"; then +echo "buildconf: autoconf not found." +echo " You need autoconf version 2.50 or newer installed" +echo " to build APR from SVN." +exit 1 +fi +IFS=.; set $ac_version; IFS=' ' +if test "$1" = "2" -a "$2" -lt "50" || test "$1" -lt "2"; then +echo "buildconf: autoconf version $ac_version found." +echo " You need autoconf version 2.50 or newer installed" +echo " to build APR from SVN." +exit 1 +else +echo "buildconf: autoconf version $ac_version (ok)" +fi + +# Sample libtool --version outputs: +# ltmain.sh (GNU libtool) 1.3.3 (1.385.2.181 1999/07/02 15:49:11) +# ltmain.sh (GNU libtool 1.1361 2004/01/02 23:10:52) 1.5a +# output is multiline from 1.5 onwards + +# Require libtool 1.4 or newer +libtool=`build/PrintPath glibtool libtool libtool15 libtool14` +lt_pversion=`$libtool --version 2>/dev/null|sed -e 's/([^)]*)//g;s/^[^0-9]*//;s/[- ].*//g;q'` +if test -z "$lt_pversion"; then +echo "buildconf: libtool not found." +echo " You need libtool version 1.4 or newer installed" +echo " to build APR from SVN." +exit 1 +fi +lt_version=`echo $lt_pversion|sed -e 's/\([a-z]*\)$/.\1/'` +IFS=.; set $lt_version; IFS=' ' +lt_status="good" +if test "$1" = "1"; then + if test "$2" -lt "4"; then + lt_status="bad" + fi +fi +if test $lt_status = "good"; then + echo "buildconf: libtool version $lt_pversion (ok)" + exit 0 +fi + +echo "buildconf: libtool version $lt_pversion found." +echo " You need libtool version 1.4 or newer installed" +echo " to build APR from SVN." + +exit 1 diff --git a/apache2/build/find_apr.m4 b/apache2/build/find_apr.m4 new file mode 100644 index 00000000..c0000c13 --- /dev/null +++ b/apache2/build/find_apr.m4 @@ -0,0 +1,73 @@ +dnl Check for APR Libraries +dnl CHECK_APR(ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]) +dnl Sets: +dnl APR_CFLAGS +dnl APR_LDFLAGS +dnl APR_LIBS +dnl APR_LINK_LD + +APR_CONFIG="" +APR_CFLAGS="" +APR_LDFLAGS="" +APR_LIBS="" +APR_LINK_LD="" + +AC_DEFUN([CHECK_APR], +[dnl + +AC_ARG_WITH( + apr, + [AC_HELP_STRING([--with-apr=PATH],[Path to the apr prefix])], + apr_path="$withval", + :) + +dnl # Determine apr lib directory +if test -z "${apr_path}"; then + test_paths="/usr/local/apr /usr/local /usr" +else + test_paths="${apr_path}" +fi + +AC_MSG_CHECKING([for libapr config script]) +for x in ${test_paths}; do + for APR_CONFIG in apr-1-config apr-config; do + if test -e "${x}/bin/${APR_CONFIG}"; then + with_apr="${x}/bin" + break + elif test -e "${x}/${APR_CONFIG}"; then + with_apr="${x}" + break + else + with_apr="" + fi + done + if test -n "$with_apr"; then + break + fi +done +if test -n "${with_apr}"; then + APR_CONFIG="${with_apr}/${APR_CONFIG}" + AC_MSG_RESULT([${APR_CONFIG}]) + APR_CFLAGS="`${APR_CONFIG} --includes --cppflags --cflags`" + APR_LDFLAGS="`${APR_CONFIG} --ldflags`" + APR_LIBS="`${APR_CONFIG} --libs`" + APR_LINK_LD="`${APR_CONFIG} --link-ld`" + CFLAGS=$save_CFLAGS + LDFLAGS=$save_LDFLAGS +else + AC_MSG_RESULT([no]) +fi + +AC_SUBST(APR_LIBS) +AC_SUBST(APR_CFLAGS) +AC_SUBST(APR_LDFLAGS) +AC_SUBST(APR_LINK_LD) + +if test -z "${APR_LIBS}"; then + AC_MSG_NOTICE([*** apr library not found.]) + ifelse([$2], , AC_MSG_ERROR([apr library is required]), $2) +else + AC_MSG_NOTICE([using '${APR_LIBS}' for apr Library]) + ifelse([$1], , , $1) +fi +]) diff --git a/apache2/build/find_apu.m4 b/apache2/build/find_apu.m4 new file mode 100644 index 00000000..f411c49f --- /dev/null +++ b/apache2/build/find_apu.m4 @@ -0,0 +1,73 @@ +dnl Check for APU Libraries +dnl CHECK_APU(ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]) +dnl Sets: +dnl APU_CFLAGS +dnl APU_LDFLAGS +dnl APU_LIBS +dnl APU_LINK_LD + +APU_CONFIG="" +APU_CFLAGS="" +APU_LDFLAGS="" +APU_LIBS="" +APU_LINK_LD="" + +AC_DEFUN([CHECK_APU], +[dnl + +AC_ARG_WITH( + apu, + [AC_HELP_STRING([--with-apu=PATH],[Path to the apu prefix])], + apu_path="$withval", + :) + +dnl # Determine apu lib directory +if test -z "${apu_path}"; then + test_paths="/usr/local/apr /usr/local /usr" +else + test_paths="${apu_path}" +fi + +AC_MSG_CHECKING([for libapr-util config script]) +for x in ${test_paths}; do + for APU_CONFIG in apu-1-config apu-config; do + if test -e "${x}/bin/${APU_CONFIG}"; then + with_apu="${x}/bin" + break + elif test -e "${x}/${APU_CONFIG}"; then + with_apu="${x}" + break + else + with_apu="" + fi + done + if test -n "$with_apu"; then + break + fi +done +if test -n "${with_apu}"; then + APU_CONFIG="${with_apu}/${APU_CONFIG}" + AC_MSG_RESULT([${APU_CONFIG}]) + APU_CFLAGS="`${APU_CONFIG} --includes`" + APU_LDFLAGS="`${APU_CONFIG} --ldflags`" + APU_LIBS="`${APU_CONFIG} --libs`" + APU_LINK_LD="`${APU_CONFIG} --link-ld`" + CFLAGS=$save_CFLAGS + LDFLAGS=$save_LDFLAGS +else + AC_MSG_RESULT([no]) +fi + +AC_SUBST(APU_LIBS) +AC_SUBST(APU_CFLAGS) +AC_SUBST(APU_LDFLAGS) +AC_SUBST(APU_LINK_LD) + +if test -z "${APU_LIBS}"; then + AC_MSG_NOTICE([*** apu library not found.]) + ifelse([$2], , AC_MSG_ERROR([apu library is required]), $2) +else + AC_MSG_NOTICE([using '${APU_LINK_LD}' for apu Library]) + ifelse([$1], , , $1) +fi +]) diff --git a/apache2/build/find_curl.m4 b/apache2/build/find_curl.m4 new file mode 100644 index 00000000..f551f583 --- /dev/null +++ b/apache2/build/find_curl.m4 @@ -0,0 +1,57 @@ +dnl Check for CURL Libraries +dnl CHECK_CURL(ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]) +dnl Sets: +dnl CURL_CFLAGS +dnl CURL_LIBS + +CURL_CONFIG="curl-config" +CURL_CFLAGS="" +CURL_LIBS="" + +AC_DEFUN([CHECK_CURL], +[dnl + +AC_ARG_WITH( + curl, + [AC_HELP_STRING([--with-curl=PATH],[Path to the curl prefix])], + curl_path="$withval", + :) + +dnl # Determine curl lib directory +if test -z "${curl_path}"; then + test_paths="/usr/local /usr" +else + test_paths="${curl_path}" +fi + +AC_MSG_CHECKING([for libcurl config script]) +for x in ${test_paths}; do + if test -e "${x}/bin/${CURL_CONFIG}"; then + with_curl="${x}/bin" + break + else + with_curl="" + fi +done +if test -n "${with_curl}"; then + CURL_CONFIG="${with_curl}/${CURL_CONFIG}" + AC_MSG_RESULT([${CURL_CONFIG}]) + CURL_CFLAGS="`${CURL_CONFIG} --cflags`" + CURL_LIBS="`${CURL_CONFIG} --libs`" + CFLAGS=$save_CFLAGS + LDFLAGS=$save_LDFLAGS +else + AC_MSG_RESULT([no]) +fi + +AC_SUBST(CURL_LIBS) +AC_SUBST(CURL_CFLAGS) + +if test -z "${CURL_LIBS}"; then + AC_MSG_NOTICE([*** curl library not found.]) + ifelse([$2], , AC_MSG_NOTICE([NOTE: curl library is only required for building mlogc]), $2) +else + AC_MSG_NOTICE([using '${CURL_LIBS}' for curl Library]) + ifelse([$1], , , $1) +fi +]) diff --git a/apache2/build/find_lua.m4 b/apache2/build/find_lua.m4 new file mode 100644 index 00000000..5a1842d6 --- /dev/null +++ b/apache2/build/find_lua.m4 @@ -0,0 +1,123 @@ +dnl Check for LUA Libraries +dnl CHECK_LUA(ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]) +dnl Sets: +dnl LUA_CFLAGS +dnl LUA_LIBS + +LUA_CONFIG="pkg-config" +LUA_PKGNAMES="lua5.1 lua5 lua" +LUA_CFLAGS="" +LUA_LIBS="" + +AC_DEFUN([CHECK_LUA], +[dnl + +AC_ARG_WITH( + lua, + [AC_HELP_STRING([--with-lua=PATH],[Path to the lua prefix])], + lua_path="$withval", + :) + +if test "${lua_path}" != "no"; then + dnl # Determine lua lib directory + if test -z "${lua_path}"; then + test_paths="/usr/local /usr" + else + test_paths="${lua_path}" + fi + + AC_MSG_CHECKING([for pkg-config script for lua library]) + for x in ${test_paths}; do + if test -e "${x}/bin/${LUA_CONFIG}"; then + with_lua="${x}/bin" + break + else + with_lua="" + fi + done + if test -n "${with_lua}"; then + LUA_CONFIG="${with_lua}/${LUA_CONFIG}" + for LUA_PKGNAME in ${LUA_PKGNAMES}; do + if ${LUA_CONFIG} --exists ${LUA_PKGNAME}; then + break + fi + LUA_PKGNAME="" + done + if test -n "$LUA_PKGNAME"; then + AC_MSG_RESULT([${LUA_CONFIG} ${LUA_PKGNAME}]) + LUA_CFLAGS="`${LUA_CONFIG} ${LUA_PKGNAME} --cflags`" + LUA_LIBS="`${LUA_CONFIG} ${LUA_PKGNAME} --libs`" + CFLAGS=$save_CFLAGS + LDFLAGS=$save_LDFLAGS + else + AC_MSG_RESULT([no]) + fi + else + AC_MSG_RESULT([no]) + + dnl Hack to just try to find the lib and include + AC_MSG_CHECKING([for lua install]) + for x in ${test_paths}; do + if test -e "${x}/liblua5.1.a"; then + with_lua_lib="${x}" + lua_lib_name="lua5.1" + break + elif test -e "${x}/lib/liblua5.1.a"; then + with_lua_lib="${x}/lib" + lua_lib_name="lua5.1" + break + elif test -e "${x}/liblua.a"; then + with_lua_lib="${x}" + lua_lib_name="lua" + break + elif test -e "${x}/lib/liblua.a"; then + with_lua_lib="${x}/lib" + lua_lib_name="lua" + break + else + with_lua_lib="" + lua_lib_name="" + fi + done + for x in ${test_paths}; do + if test -e "${x}/lua.h"; then + with_lua_inc="${x}" + break + elif test -e "${x}/include/lua.h"; then + with_lua_inc="${x}/include" + break + else + with_lua_inc="" + fi + done + if test -n "${with_lua_lib}" -a -n "${with_lua_inc}"; then + LUA_CONFIG="" + AC_MSG_RESULT([${with_lua_lib} ${with_lua_inc}]) + LUA_CFLAGS="-I${with_lua_inc}" + LUA_LIBS="-L${with_lua_lib} -l${lua_lib_name}" + CFLAGS=$save_CFLAGS + LDFLAGS=$save_LDFLAGS + else + AC_MSG_RESULT([no]) + fi + fi +else + AC_MSG_NOTICE([not using optional lua library]) +fi + +if test -n "${LUA_LIBS}"; then + LUA_CFLAGS="-DWITH_LUA ${LUA_CFLAGS}" +fi + +AC_SUBST(LUA_LIBS) +AC_SUBST(LUA_CFLAGS) + +if test "${lua_path}" != "no"; then + if test -z "${LUA_LIBS}"; then + ifelse([$2], , AC_MSG_NOTICE([optional lua library not found]), $2) + else + AC_MSG_NOTICE([using '${LUA_LIBS}' for lua Library]) + ifelse([$1], , , $1) + fi +fi +]) diff --git a/apache2/build/find_pcre.m4 b/apache2/build/find_pcre.m4 new file mode 100644 index 00000000..d6d90613 --- /dev/null +++ b/apache2/build/find_pcre.m4 @@ -0,0 +1,60 @@ +dnl Check for PCRE Libraries +dnl CHECK_PCRE(ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]) +dnl Sets: +dnl PCRE_CFLAGS +dnl PCRE_LIBS + +PCRE_CONFIG="pcre-config" +PCRE_CFLAGS="" +PCRE_LIBS="" + +AC_DEFUN([CHECK_PCRE], +[dnl + +AC_ARG_WITH( + pcre, + [AC_HELP_STRING([--with-pcre=PATH],[Path to the pcre prefix])], + pcre_path="$withval", + :) + +dnl # Determine pcre lib directory +if test -z "${pcre_path}"; then + test_paths="/usr/local /usr" +else + test_paths="${pcre_path}" +fi + +AC_MSG_CHECKING([for libpcre config script]) +for x in ${test_paths}; do + if test -e "${x}/bin/${PCRE_CONFIG}"; then + with_pcre="${x}/bin" + break + elif test -e "${x}/${PCRE_CONFIG}"; then + with_pcre="${x}" + break + else + with_pcre="" + fi +done +if test -n "${with_pcre}"; then + PCRE_CONFIG="${with_pcre}/${PCRE_CONFIG}" + AC_MSG_RESULT([${PCRE_CONFIG}]) + PCRE_CFLAGS="`${PCRE_CONFIG} --cflags`" + PCRE_LIBS="`${PCRE_CONFIG} --libs`" + CFLAGS=$save_CFLAGS + LDFLAGS=$save_LDFLAGS +else + AC_MSG_RESULT([no]) +fi + +AC_SUBST(PCRE_LIBS) +AC_SUBST(PCRE_CFLAGS) + +if test -z "${PCRE_LIBS}"; then + AC_MSG_NOTICE([*** pcre library not found.]) + ifelse([$2], , AC_MSG_ERROR([pcre library is required]), $2) +else + AC_MSG_NOTICE([using '${PCRE_LIBS}' for pcre Library]) + ifelse([$1], , , $1) +fi +]) diff --git a/apache2/build/find_xml.m4 b/apache2/build/find_xml.m4 new file mode 100644 index 00000000..4fe0a67c --- /dev/null +++ b/apache2/build/find_xml.m4 @@ -0,0 +1,57 @@ +dnl Check for XML Libraries +dnl CHECK_LIBXML(ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]) +dnl Sets: +dnl LIBXML_CFLAGS +dnl LIBXML_LIBS + +LIBXML_CONFIG="xml2-config" +LIBXML_CFLAGS="" +LIBXML_LIBS="" + +AC_DEFUN([CHECK_LIBXML], +[dnl + +AC_ARG_WITH( + libxml, + [AC_HELP_STRING([--with-libxml=PATH],[Path to the libxml2 prefix])], + libxml_path="$withval", + :) + +dnl # Determine xml lib directory +if test -z "${libxml_path}"; then + test_paths="/usr/local /usr" +else + test_paths="${libxml_path}" +fi + +AC_MSG_CHECKING([for libxml2 config script]) +for x in ${test_paths}; do + if test -e "${x}/bin/${LIBXML_CONFIG}"; then + with_libxml="${x}/bin" + break + else + with_libxml="" + fi +done +if test -n "${with_libxml}"; then + LIBXML_CONFIG="${with_libxml}/${LIBXML_CONFIG}" + AC_MSG_RESULT([${LIBXML_CONFIG}]) + LIBXML_CFLAGS="`${LIBXML_CONFIG} --cflags`" + LIBXML_LIBS="`${LIBXML_CONFIG} --libs`" + CFLAGS=$save_CFLAGS + LDFLAGS=$save_LDFLAGS +else + AC_MSG_RESULT([no]) +fi + +AC_SUBST(LIBXML_LIBS) +AC_SUBST(LIBXML_CFLAGS) + +if test -z "${LIBXML_LIBS}"; then + AC_MSG_NOTICE([*** libxml2 library not found.]) + ifelse([$2], , AC_MSG_ERROR([libxml2 library is required]), $2) +else + AC_MSG_NOTICE([using '${LIBXML_LIBS}' for libxml Library]) + ifelse([$1], , , $1) +fi +]) diff --git a/apache2/build/install-sh b/apache2/build/install-sh new file mode 100755 index 00000000..b6376ec2 --- /dev/null +++ b/apache2/build/install-sh @@ -0,0 +1,224 @@ +#!/bin/sh +## +## install.sh -- install a program, script or datafile +## +## Based on `install-sh' from the X Consortium's X11R5 distribution +## as of 89/12/18 which is freely available. +## Cleaned up for Apache's Autoconf-style Interface (APACI) +## by Ralf S. Engelschall +## +# +# This script falls under the Apache License. +# See http://www.apache.org/docs/LICENSE + + +# +# put in absolute paths if you don't have them in your path; +# or use env. vars. +# +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" + +# +# parse argument line +# +instcmd="$mvprog" +chmodcmd="" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +ext="" +src="" +dst="" +while [ "x$1" != "x" ]; do + case $1 in + -c) instcmd="$cpprog" + shift; continue + ;; + -m) chmodcmd="$chmodprog $2" + shift; shift; continue + ;; + -o) chowncmd="$chownprog $2" + shift; shift; continue + ;; + -g) chgrpcmd="$chgrpprog $2" + shift; shift; continue + ;; + -s) stripcmd="$stripprog" + shift; continue + ;; + -S) stripcmd="$stripprog $2" + shift; shift; continue + ;; + -e) ext="$2" + shift; shift; continue + ;; + *) if [ "x$src" = "x" ]; then + src=$1 + else + dst=$1 + fi + shift; continue + ;; + esac +done +if [ "x$src" = "x" ]; then + echo "install.sh: no input file specified" + exit 1 +fi +if [ "x$dst" = "x" ]; then + echo "install.sh: no destination specified" + exit 1 +fi + +# +# If destination is a directory, append the input filename; if +# your system does not like double slashes in filenames, you may +# need to add some logic +# +if [ -d $dst ]; then + dst="$dst/`basename $src`" +fi + +# Add a possible extension (such as ".exe") to src and dst +src="$src$ext" +dst="$dst$ext" + +# Make a temp file name in the proper directory. +dstdir=`dirname $dst` +dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name +$instcmd $src $dsttmp + +# And set any options; do chmod last to preserve setuid bits +if [ "x$chowncmd" != "x" ]; then $chowncmd $dsttmp; fi +if [ "x$chgrpcmd" != "x" ]; then $chgrpcmd $dsttmp; fi +if [ "x$stripcmd" != "x" ]; then $stripcmd $dsttmp; fi +if [ "x$chmodcmd" != "x" ]; then $chmodcmd $dsttmp; fi + +# Now rename the file to the real destination. +$rmcmd $dst +$mvcmd $dsttmp $dst + +exit 0 + +#!/bin/sh +## +## install.sh -- install a program, script or datafile +## +## Based on `install-sh' from the X Consortium's X11R5 distribution +## as of 89/12/18 which is freely available. +## Cleaned up for Apache's Autoconf-style Interface (APACI) +## by Ralf S. Engelschall +## +# +# This script falls under the Apache License. +# See http://www.apache.org/docs/LICENSE + + +# +# put in absolute paths if you don't have them in your path; +# or use env. vars. +# +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" + +# +# parse argument line +# +instcmd="$mvprog" +chmodcmd="" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +ext="" +src="" +dst="" +while [ "x$1" != "x" ]; do + case $1 in + -c) instcmd="$cpprog" + shift; continue + ;; + -m) chmodcmd="$chmodprog $2" + shift; shift; continue + ;; + -o) chowncmd="$chownprog $2" + shift; shift; continue + ;; + -g) chgrpcmd="$chgrpprog $2" + shift; shift; continue + ;; + -s) stripcmd="$stripprog" + shift; continue + ;; + -S) stripcmd="$stripprog $2" + shift; shift; continue + ;; + -e) ext="$2" + shift; shift; continue + ;; + *) if [ "x$src" = "x" ]; then + src=$1 + else + dst=$1 + fi + shift; continue + ;; + esac +done +if [ "x$src" = "x" ]; then + echo "install.sh: no input file specified" + exit 1 +fi +if [ "x$dst" = "x" ]; then + echo "install.sh: no destination specified" + exit 1 +fi + +# +# If destination is a directory, append the input filename; if +# your system does not like double slashes in filenames, you may +# need to add some logic +# +if [ -d $dst ]; then + dst="$dst/`basename $src`" +fi + +# Add a possible extension (such as ".exe") to src and dst +src="$src$ext" +dst="$dst$ext" + +# Make a temp file name in the proper directory. +dstdir=`dirname $dst` +dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name +$instcmd $src $dsttmp + +# And set any options; do chmod last to preserve setuid bits +if [ "x$chowncmd" != "x" ]; then $chowncmd $dsttmp; fi +if [ "x$chgrpcmd" != "x" ]; then $chgrpcmd $dsttmp; fi +if [ "x$stripcmd" != "x" ]; then $stripcmd $dsttmp; fi +if [ "x$chmodcmd" != "x" ]; then $chmodcmd $dsttmp; fi + +# Now rename the file to the real destination. +$rmcmd $dst +$mvcmd $dsttmp $dst + +exit 0 + diff --git a/apache2/buildconf b/apache2/buildconf new file mode 100755 index 00000000..153f0bf6 --- /dev/null +++ b/apache2/buildconf @@ -0,0 +1,86 @@ +#!/bin/sh +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# + +# buildconf: Build the support scripts needed to compile from a +# checked-out version of the source code. + +# Verify that the builder has the right config tools installed +# +build/buildcheck.sh || exit 1 + +echo "Generating config header ..." +autoheader -Wall || exit 1 + +libtoolize=`build/PrintPath glibtoolize libtoolize15 libtoolize14 libtoolize` +if [ "x$libtoolize" = "x" ]; then + echo "libtoolize not found in path" + exit 1 +fi + +# Create the libtool helper files +# +# Note: we copy (rather than link) them to simplify distribution. +# Note: APR supplies its own config.guess and config.sub -- we do not +# rely on libtool's versions +# +echo "Copying libtool helper files ..." + +# Remove any libtool files so one can switch between libtool 1.3 +# and libtool 1.4 by simply rerunning the buildconf script. +(cd build ; rm -f ltconfig ltmain.sh libtool.m4) + +$libtoolize --copy --automake + +if [ -f libtool.m4 ]; then + ltfile=`pwd`/libtool.m4 +else + ltfindcmd="`sed -n \"/=[^\\\`]/p;/libtool_m4=/{s/.*=/echo /p;q;}\" \ + < $libtoolize`" + ltfile=${LIBTOOL_M4-`eval "$ltfindcmd"`} + # Expecting the code above to be very portable, but just in case... + if [ -z "$ltfile" -o ! -f "$ltfile" ]; then + ltpath=`dirname $libtoolize` + ltfile=`cd $ltpath/../share/aclocal ; pwd`/libtool.m4 + fi +fi + +if [ ! -f $ltfile ]; then + echo "$ltfile not found" + exit 1 +fi + +echo "buildconf: Using libtool.m4 at ${ltfile}." + +cat $ltfile | sed -e 's/LIBTOOL=\(.*\)top_build/LIBTOOL=\1apr_build/' > build/libtool.m4 + +# libtool.m4 from 1.6 requires ltsugar.m4 +if [ -f ltsugar.m4 ]; then + rm -f build/ltsugar.m4 + mv ltsugar.m4 build/ltsugar.m4 +fi + +# Clean up any leftovers +rm -f aclocal.m4 libtool.m4 + +echo "Creating configure ..." +${AUTOCONF:-autoconf} + +# Remove autoconf 2.5x's cache directory +rm -rf autom4te*.cache + +exit 0 diff --git a/apache2/configure.in b/apache2/configure.in new file mode 100644 index 00000000..bcdf5179 --- /dev/null +++ b/apache2/configure.in @@ -0,0 +1,258 @@ +dnl +dnl Autoconf configuration for ModSecurity +dnl +dnl Use ./buildconf to produce a configure script +dnl + +AC_PREREQ(2.50) + +AC_INIT() +dnl AC_INIT(ModSecurity, 2.5, mod-security-users@lists.sourceforge.net, modsecurity-apache) +AC_CONFIG_SRCDIR([mod_security2.c]) +AC_CONFIG_HEADER([mod_security2_config.h]) +AC_CONFIG_AUX_DIR([build]) + +# Checks for programs. +AC_PROG_CXX +AC_PROG_CC +AC_PROG_CPP +AC_PROG_INSTALL +AC_PROG_LN_S +AC_PROG_MAKE_SET +AC_PROG_RANLIB +AC_PATH_PROGS(PERL, [perl perl5], ) + +# Checks for header files. +AC_HEADER_STDC +AC_CHECK_HEADERS([fcntl.h limits.h stdlib.h string.h unistd.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_C_INLINE +AC_C_RESTRICT +AC_TYPE_SIZE_T +AC_STRUCT_TM +AC_TYPE_UINT8_T + +# Checks for library functions. +AC_FUNC_MALLOC +AC_FUNC_MEMCMP +AC_CHECK_FUNCS([atexit fchmod getcwd memset strcasecmp strchr strdup strerror strncasecmp strrchr strstr strtol]) + +# Find apxs +AC_MSG_NOTICE(looking for Apache module support via DSO through APXS) +AC_ARG_WITH(apxs, + [AS_HELP_STRING([[--with-apxs=FILE]], + [FILE is the path to apxs; defaults to "apxs".])], +[ + if test "$withval" = "yes"; then + APXS=apxs + else + APXS="$withval" + fi +]) + +if test -z "$APXS"; then + for i in /usr/local/apache22/bin \ + /usr/local/apache2/bin \ + /usr/local/apache/bin \ + /usr/local/sbin \ + /usr/local/bin \ + /usr/sbin \ + /usr/bin; + do + if test -f "$i/apxs2"; then + APXS="$i/apxs2" + break + elif test -f "$i/apxs"; then + APXS="$i/apxs" + break + fi + done +fi + +# arbitrarily picking the same version subversion looks for, don't know how +# accurate this really is, but at least it'll force us to have apache2... +HTTPD_WANTED_MMN=20020903 + +if test -n "$APXS" -a "$APXS" != "no" -a -x "$APXS" ; then + APXS_INCLUDE="`$APXS -q INCLUDEDIR`" + if test -r $APXS_INCLUDE/httpd.h; then + AC_MSG_NOTICE(found apxs at $APXS) + AC_MSG_NOTICE(checking httpd version) + AC_EGREP_CPP(VERSION_OK, + [ +#include "$APXS_INCLUDE/ap_mmn.h" +#if AP_MODULE_MAGIC_AT_LEAST($HTTPD_WANTED_MMN,0) +VERSION_OK +#endif], + [AC_MSG_NOTICE(httpd is recent enough)], + [AC_MSG_ERROR(apache is too old, mmn must be at least $HTTPD_WANTED_MMN)]) + fi + APXS_INCLUDES="`$APXS -q INCLUDES` `$APXS -q EXTRA_INCLUDES`" + APXS_CFLAGS="`$APXS -q CFLAGS` `$APXS -q EXTRA_CFLAGS`" + APXS_LDFLAGS="`$APXS -q LDFLAGS` `$APXS -q EXTRA_LDFLAGS`" + APXS_LIBS="`$APXS -q LIBS` `$APXS -q EXTRA_LIBS`" + APXS_LIBTOOL="`$APXS -q LIBTOOL`" + APXS_CC="`$APXS -q CC`" +else + AC_MSG_ERROR(couldn't find APXS) +fi + +# Use Apache httpd source srclib as base for pcre, apr and apu config scripts +AC_ARG_WITH(httpd-src, + [AS_HELP_STRING([[--with-httpd-src=PATH]], + [PATH is to the Apache httpd source tree where srclib will be used as a base for pcre, apr and apu config scripts.])], +[ + if test -n "$withval"; then + CPPFLAGS="$CPPFLAGS -I$withval/srclib/pcre" + LDFLAGS="$LDFLAGS -L$withval/srclib/pcre" + pcre_path="$withval/srclib/pcre" + apr_path="$withval/srclib/apr" + apu_path="$withval/srclib/apr-util" + else + AC_MSG_ERROR(--with-httpd-src requires a path) + fi +]) + +# Include M4 macros +sinclude(build/find_pcre.m4) +sinclude(build/find_apr.m4) +sinclude(build/find_apu.m4) +sinclude(build/find_xml.m4) +sinclude(build/find_lua.m4) +sinclude(build/find_curl.m4) + + +### Configure Options + +# DEBUG_CONF +AC_ARG_ENABLE(debug-conf, + AS_HELP_STRING([--enable-debug-conf], + [Enable debug during configuration.]), +[ + if test "$enableval" != "no"; then + debug_conf="-DDEBUG_CONF" + else + debug_conf= + fi +], +[ + debug_conf= +]) + +# CACHE_DEBUG +AC_ARG_ENABLE(debug-cache, + AS_HELP_STRING([--enable-debug-cache], + [Enable debug for transformation caching.]), +[ + if test "$enableval" != "no"; then + debug_cache="-DCACHE_DEBUG" + else + debug_cache= + fi +], +[ + debug_cache= +]) + +# DEBUG_ACMP +AC_ARG_ENABLE(debug-acmp, + AS_HELP_STRING([--enable-debug-acmp], + [Enable debugging acmp code.]), +[ + if test "$enableval" != "no"; then + debug_acmp="-DDEBUG_ACMP" + else + debug_acmp= + fi +], +[ + debug_acmp= +]) + +# PERFORMANCE_MEASUREMENT +AC_ARG_ENABLE(performance-measurement, + AS_HELP_STRING([--enable-performance-measurement], + [Enable performance-measurement stats.]), +[ + if test "$enableval" != "no"; then + perf_meas="-DPERFORMANCE_MEASUREMENT" + else + perf_meas= + fi +], +[ + perf_meas= +]) + +# NO_MODSEC_API +AC_ARG_ENABLE(modsec-api, + AS_HELP_STRING([--disable-modsec-api], + [Disable the API; compiling against some older Apache versions require this.]), +[ + if test "$enableval" != "yes"; then + modsec_api="-DNO_MODSEC_API" + else + modsec_api= + fi +], +[ + modsec_api= +]) + +### Build *EXTRA_CFLAGS vars + +EXTRA_CFLAGS="-O2 -g -Wall -Werror" +MODSEC_EXTRA_CFLAGS="$debug_conf $debug_cache $debug_acmp $perf_meas $modsec_api" + +APXS_WRAPPER=build/apxs-wrapper +APXS_EXTRA_CFLAGS="" +for f in $EXTRA_CFLAGS; do + APXS_EXTRA_CFLAGS="$APXS_EXTRA_CFLAGS -Wc,$f" +done; +MODSEC_APXS_EXTRA_CFLAGS="" +for f in $MODSEC_EXTRA_CFLAGS; do + MODSEC_APXS_EXTRA_CFLAGS="$MODSEC_APXS_EXTRA_CFLAGS -Wc,$f" +done; + +### Substitute the vars + +save_CPPFLAGS=$CPPFLAGS +CPPFLAGS="$APXS_INCLUDES $CPPFLAGS" +save_LDFLAGS=$LDFLAGS +LDFLAGS="$APXS_LDFLAGS $LDFLAGS" + +AC_SUBST(EXTRA_CFLAGS) +AC_SUBST(MODSEC_EXTRA_CFLAGS) +AC_SUBST(APXS) +AC_SUBST(APXS_WRAPPER) +AC_SUBST(APXS_INCLUDES) +AC_SUBST(APXS_EXTRA_CFLAGS) +AC_SUBST(MODSEC_APXS_EXTRA_CFLAGS) +AC_SUBST(APXS_LDFLAGS) +AC_SUBST(APXS_LIBS) +AC_SUBST(APXS_CFLAGS) +AC_SUBST(APXS_LIBTOOL) +AC_SUBST(APXS_CC) + +CHECK_PCRE() +CHECK_APR() +CHECK_APU() +CHECK_LIBXML() +CHECK_LUA() +CHECK_CURL() + +AC_CONFIG_FILES([Makefile]) +AC_CONFIG_FILES([build/apxs-wrapper], [chmod +x build/apxs-wrapper]) +if test -e "$PERL"; then + AC_CONFIG_FILES([t/run-tests.pl], [chmod +x t/run-tests.pl]) + + # Perl based tools + AC_CONFIG_FILES([../tools/rules-updater.pl], [chmod +x ../tools/rules-updater.pl]) +fi +if test -e "mlogc-src/Makefile.in"; then + AC_CONFIG_FILES([mlogc-src/Makefile]) +fi + +AC_OUTPUT diff --git a/apache2/mod_security2.c b/apache2/mod_security2.c index a179cda0..d9513766 100644 --- a/apache2/mod_security2.c +++ b/apache2/mod_security2.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -15,6 +15,7 @@ #include "modsecurity.h" #include "apache2.h" +#include "http_main.h" #include "pdf_protect.h" #include "msc_logging.h" #include "msc_util.h" @@ -24,7 +25,6 @@ msc_engine DSOLOCAL *modsecurity = NULL; - /* Global module variables; these are used for the Apache-specific functionality */ char DSOLOCAL *chroot_dir = NULL; @@ -53,26 +53,9 @@ int perform_interception(modsec_rec *msr) { msre_actionset *actionset = NULL; const char *message = NULL; const char *phase_text = ""; - const char *intreq_text = ""; - int is_initial_req = ap_is_initial_req(msr->r); int status = DECLINED; int log_level = 1; - /* Check for an initial request */ - - if (is_initial_req != 1) { - if (msr->r->main != NULL) { - intreq_text = "Sub-Request: "; - } - else if (msr->r->prev != NULL) { - intreq_text = "Internal Redirect: "; - } - else { - intreq_text = "Internal Request: "; - } - } - - /* Sanity checks first. */ if (msr->was_intercepted == 0) { @@ -81,7 +64,7 @@ int perform_interception(modsec_rec *msr) { } if (msr->phase > 4) { - msr_log(msr, 1, "Internal Error: Asked to intercept request in phase %i.", msr->phase); + msr_log(msr, 1, "Internal Error: Asked to intercept request in phase %d.", msr->phase); msr->was_intercepted = 0; return DECLINED; } @@ -89,18 +72,18 @@ int perform_interception(modsec_rec *msr) { /* OK, we're good to go. */ actionset = msr->intercept_actionset; - phase_text = apr_psprintf(msr->mp, " (phase %i)", msr->phase); + phase_text = apr_psprintf(msr->mp, " (phase %d)", msr->phase); /* By default we log at level 1 but we switch to 4 * if a nolog action was used or this is not the initial request * to hide the message. */ - log_level = ((actionset->log != 1) || (is_initial_req != 1)) ? 4 : 1; + log_level = (actionset->log != 1) ? 4 : 1; /* Pause the request first (if configured and the initial request). */ - if (actionset->intercept_pause && (is_initial_req == 1)) { + if (actionset->intercept_pause) { msr_log(msr, (log_level > 3 ? log_level : log_level + 1), "Pausing transaction for " - "%i msec.", actionset->intercept_pause); + "%d msec.", actionset->intercept_pause); /* apr_sleep accepts microseconds */ apr_sleep((apr_interval_time_t)(actionset->intercept_pause * 1000)); } @@ -110,14 +93,14 @@ int perform_interception(modsec_rec *msr) { case ACTION_DENY : if (actionset->intercept_status != 0) { status = actionset->intercept_status; - message = apr_psprintf(msr->mp, "%sAccess denied with code %i%s.", - intreq_text, status, phase_text); + message = apr_psprintf(msr->mp, "Access denied with code %d%s.", + status, phase_text); } else { log_level = 1; status = HTTP_INTERNAL_SERVER_ERROR; - message = apr_psprintf(msr->mp, "%sAccess denied with code 500%s " - "(Internal Error: Invalid status code requested %i).", - intreq_text, phase_text, actionset->intercept_status); + message = apr_psprintf(msr->mp, "Access denied with code 500%s " + "(Internal Error: Invalid status code requested %d).", + phase_text, actionset->intercept_status); } break; @@ -126,25 +109,25 @@ int perform_interception(modsec_rec *msr) { if (ap_find_linked_module("mod_proxy.c") == NULL) { log_level = 1; status = HTTP_INTERNAL_SERVER_ERROR; - message = apr_psprintf(msr->mp, "%sAccess denied with code 500%s " + message = apr_psprintf(msr->mp, "Access denied with code 500%s " "(Configuration Error: Proxy action to %s requested but mod_proxy not found).", - intreq_text, phase_text, + phase_text, log_escape_nq(msr->mp, actionset->intercept_uri)); } else { msr->r->filename = apr_psprintf(msr->mp, "proxy:%s", actionset->intercept_uri); msr->r->proxyreq = PROXYREQ_REVERSE; msr->r->handler = "proxy-server"; status = OK; - message = apr_psprintf(msr->mp, "%sAccess denied using proxy to%s %s.", - intreq_text, phase_text, + message = apr_psprintf(msr->mp, "Access denied using proxy to%s %s.", + phase_text, log_escape_nq(msr->mp, actionset->intercept_uri)); } } else { log_level = 1; status = HTTP_INTERNAL_SERVER_ERROR; - message = apr_psprintf(msr->mp, "%sAccess denied with code 500%s " + message = apr_psprintf(msr->mp, "Access denied with code 500%s " "(Configuration Error: Proxy action requested but it does not work in output phases).", - intreq_text, phase_text); + phase_text); } break; @@ -157,34 +140,34 @@ int perform_interception(modsec_rec *msr) { extern module core_module; apr_socket_t *csd = ap_get_module_config(msr->r->connection->conn_config, &core_module); - + if (csd) { if (apr_socket_close(csd) == APR_SUCCESS) { status = HTTP_FORBIDDEN; - message = apr_psprintf(msr->mp, "%sAccess denied with connection close%s.", - intreq_text, phase_text); + message = apr_psprintf(msr->mp, "Access denied with connection close%s.", + phase_text); } else { log_level = 1; status = HTTP_INTERNAL_SERVER_ERROR; - message = apr_psprintf(msr->mp, "%sAccess denied with code 500%s " + message = apr_psprintf(msr->mp, "Access denied with code 500%s " "(Error: Connection drop requested but failed to close the " " socket).", - intreq_text, phase_text); + phase_text); } } else { log_level = 1; status = HTTP_INTERNAL_SERVER_ERROR; - message = apr_psprintf(msr->mp, "%sAccess denied with code 500%s " + message = apr_psprintf(msr->mp, "Access denied with code 500%s " "(Error: Connection drop requested but socket not found.", - intreq_text, phase_text); + phase_text); } } #else log_level = 1; status = HTTP_INTERNAL_SERVER_ERROR; - message = apr_psprintf(msr->mp, "%sAccess denied with code 500%s " + message = apr_psprintf(msr->mp, "Access denied with code 500%s " "(Error: Connection drop not implemented on this platform).", - intreq_text, phase_text); + phase_text); #endif break; @@ -197,25 +180,39 @@ int perform_interception(modsec_rec *msr) { } else { status = HTTP_MOVED_TEMPORARILY; } - message = apr_psprintf(msr->mp, "%sAccess denied with redirection to %s using " - "status %i%s.", - intreq_text, + message = apr_psprintf(msr->mp, "Access denied with redirection to %s using " + "status %d%s.", log_escape_nq(msr->mp, actionset->intercept_uri), status, phase_text); break; case ACTION_ALLOW : status = DECLINED; - message = apr_psprintf(msr->mp, "%sAccess allowed%s.", intreq_text, phase_text); + message = apr_psprintf(msr->mp, "Access allowed%s.", phase_text); msr->was_intercepted = 0; + msr->allow_scope = ACTION_ALLOW; + break; + + case ACTION_ALLOW_PHASE : + status = DECLINED; + message = apr_psprintf(msr->mp, "Access to phase allowed%s.", phase_text); + msr->was_intercepted = 0; + msr->allow_scope = ACTION_ALLOW_PHASE; + break; + + case ACTION_ALLOW_REQUEST : + status = DECLINED; + message = apr_psprintf(msr->mp, "Access to request allowed%s.", phase_text); + msr->was_intercepted = 0; + msr->allow_scope = ACTION_ALLOW_REQUEST; break; default : log_level = 1; status = HTTP_INTERNAL_SERVER_ERROR; - message = apr_psprintf(msr->mp, "%sAccess denied with code 500%s " - "(Internal Error: invalid interception action %i).", - intreq_text, phase_text, actionset->intercept_action); + message = apr_psprintf(msr->mp, "Access denied with code 500%s " + "(Internal Error: invalid interception action %d).", + phase_text, actionset->intercept_action); break; } @@ -229,13 +226,14 @@ int perform_interception(modsec_rec *msr) { * Retrieves a previously stored transaction context by * looking at the main request, and the previous requests. */ -static modsec_rec *retrieve_tx_context(const request_rec *r) { +static modsec_rec *retrieve_tx_context(request_rec *r) { modsec_rec *msr = NULL; request_rec *rx = NULL; /* Look in the current request first. */ msr = (modsec_rec *)apr_table_get(r->notes, NOTE_MSR); if (msr != NULL) { + msr->r = r; return msr; } @@ -243,6 +241,7 @@ static modsec_rec *retrieve_tx_context(const request_rec *r) { if (r->main != NULL) { msr = (modsec_rec *)apr_table_get(r->main->notes, NOTE_MSR); if (msr != NULL) { + msr->r = r; return msr; } } @@ -252,6 +251,7 @@ static modsec_rec *retrieve_tx_context(const request_rec *r) { while(rx != NULL) { msr = (modsec_rec *)apr_table_get(rx->notes, NOTE_MSR); if (msr != NULL) { + msr->r = r; return msr; } rx = rx->prev; @@ -344,18 +344,21 @@ static modsec_rec *create_tx_context(request_rec *r) { msr->request_headers = apr_table_copy(msr->mp, r->headers_in); msr->hostname = ap_get_server_name(r); + msr->msc_rule_mptmp = NULL; + /* Invoke the engine to continue with initialisation */ - if (modsecurity_tx_init(msr) < 0) return NULL; + if (modsecurity_tx_init(msr) < 0) { + msr_log(msr, 1, "Failed to initialise transaction (txid %s).", msr->txid); + return NULL; + } store_tx_context(msr, r); if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Transaction context created (dcfg %x).", msr->dcfg1); + msr_log(msr, 4, "Transaction context created (dcfg %pp).", msr->dcfg1); } - msr->msc_rule_mptmp = NULL; - - return msr; + return msr; } @@ -371,7 +374,8 @@ static apr_status_t change_server_signature(server_rec *s) { if (new_server_signature == NULL) return 0; - server_version = (char *)ap_get_server_version(); + server_version = (char *)apache_get_server_version(); + if (server_version == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s, "SecServerSignature: Apache returned null as signature."); @@ -388,6 +392,18 @@ static apr_status_t change_server_signature(server_rec *s) { return -1; } + /* Check that it really changed. This assumes that what we got was + * not a copy and this could change in future versions of Apache. + */ + server_version = (char *)apache_get_server_version(); + if ((server_version == NULL) || (strcmp(server_version, new_server_signature) != 0)) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s, "SecServerSignature: Failed to change server signature to \"%s\".", new_server_signature); + return 0; + } + else { + ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, 0, s, "SecServerSignature: Changed server signature to \"%s\".", server_version); + } + return 1; } @@ -406,12 +422,10 @@ static int hook_pre_config(apr_pool_t *mp, apr_pool_t *mp_log, apr_pool_t *mp_te /* Initialise ModSecurity engine */ modsecurity = modsecurity_create(mp, MODSEC_ONLINE); if (modsecurity == NULL) { - /* ENH Since s not available, how do we log from here? stderr? - * ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s, - * "ModSecurity: Failed to initialise engine."); - */ + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + "ModSecurity: Failed to initialise engine."); return HTTP_INTERNAL_SERVER_ERROR; - } + } return OK; } @@ -423,6 +437,10 @@ static int hook_post_config(apr_pool_t *mp, apr_pool_t *mp_log, apr_pool_t *mp_t void *init_flag = NULL; int first_time = 0; + /* ENH Figure out a way to validate config before we start + * - skipafter: need to make sure we found all of our targets + */ + /* Figure out if we are here for the first time */ apr_pool_userdata_get(&init_flag, "modsecurity-init-flag", s->process->pool); if (init_flag == NULL) { @@ -434,7 +452,7 @@ static int hook_post_config(apr_pool_t *mp, apr_pool_t *mp_log, apr_pool_t *mp_t } /* Store the original server signature */ - real_server_signature = apr_pstrdup(mp, ap_get_server_version()); + real_server_signature = apr_pstrdup(mp, apache_get_server_version()); /* Make some space in the server signature for later */ if (new_server_signature != NULL) { @@ -454,7 +472,7 @@ static int hook_post_config(apr_pool_t *mp, apr_pool_t *mp_log, apr_pool_t *mp_t if (first_time == 0) { ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s, - "ModSecurity: chroot checkpoint #2 (pid=%i ppid=%i)", getpid(), getppid()); + "ModSecurity: chroot checkpoint #2 (pid=%ld ppid=%ld)", (long)getpid(), (long)getppid()); if (chdir(chroot_dir) < 0) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s, @@ -483,7 +501,7 @@ static int hook_post_config(apr_pool_t *mp, apr_pool_t *mp_log, apr_pool_t *mp_t "ModSecurity: chroot successful, path=%s", chroot_dir); } else { ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s, - "ModSecurity: chroot checkpoint #1 (pid=%i ppid=%i)", getpid(), getppid()); + "ModSecurity: chroot checkpoint #1 (pid=%ld ppid=%ld)", (long)getpid(), (long)getppid()); } } #endif @@ -493,13 +511,13 @@ static int hook_post_config(apr_pool_t *mp, apr_pool_t *mp_log, apr_pool_t *mp_t /* Log our presence to the error log. */ if (first_time) { + ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s, + "%s configured.", MODULE_NAME_FULL); + + /* If we've changed the server signature make note of the original. */ if (new_server_signature != NULL) { ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s, - "ModSecurity for Apache %s configured - %s", MODULE_RELEASE, real_server_signature); - } - else { - ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s, - "ModSecurity for Apache %s configured", MODULE_RELEASE); + "Original server signature: %s", real_server_signature); } } @@ -547,7 +565,7 @@ static int hook_request_early(request_rec *r) { /* Check request body limit (should only trigger on non-chunked requests). */ if (msr->request_content_length > msr->txcfg->reqbody_limit) { msr_log(msr, 1, "Request body is larger than the " - "configured limit (%lu).", msr->txcfg->reqbody_limit); + "configured limit (%ld).", msr->txcfg->reqbody_limit); return HTTP_REQUEST_ENTITY_TOO_LARGE; } @@ -569,6 +587,13 @@ static int hook_request_late(request_rec *r) { modsec_rec *msr = NULL; int rc; + /* This function needs to run only once per transaction + * (i.e. subrequests and redirects are excluded). + */ + if ((r->main != NULL)||(r->prev != NULL)) { + return DECLINED; + } + /* Find the transaction context and make sure * we are supposed to proceed. */ @@ -579,25 +604,19 @@ static int hook_request_late(request_rec *r) { */ return DECLINED; } - msr->r = r; - msr->remote_user = r->user; /* Has this phase been completed already? */ if (msr->phase_request_body_complete) { - if (msr->was_intercepted) { - msr_log(msr, 4, "Phase REQUEST_BODY request already intercepted. Intercepting additional request."); - return perform_interception(msr); - } - if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Phase REQUEST_BODY already complete, skipping."); - } + msr_log(msr, 1, "Internal Error: Attempted to process the request body more than once."); return DECLINED; } msr->phase_request_body_complete = 1; + msr->remote_user = r->user; + /* Get the second configuration context. */ msr->dcfg2 = (directory_config *)ap_get_module_config(r->per_dir_config, - &security2_module); + &security2_module); /* Create a transaction context. */ msr->txcfg = create_directory_config(msr->mp, NULL); @@ -629,7 +648,7 @@ static int hook_request_late(request_rec *r) { } if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Second phase starting (dcfg %x).", msr->dcfg2); + msr_log(msr, 4, "Second phase starting (dcfg %pp).", msr->dcfg2); } /* Figure out whether or not to extract multipart files. */ @@ -644,14 +663,22 @@ static int hook_request_late(request_rec *r) { if (rc < 0) { switch(rc) { case -1 : - msr_log(msr, 1, "%s", my_error_msg); + if (my_error_msg != NULL) { + msr_log(msr, 1, "%s", my_error_msg); + } return HTTP_INTERNAL_SERVER_ERROR; break; case -4 : /* Timeout. */ + if (my_error_msg != NULL) { + msr_log(msr, 4, "%s", my_error_msg); + } r->connection->keepalive = AP_CONN_CLOSE; return HTTP_REQUEST_TIME_OUT; break; case -5 : /* Request body limit reached. */ + if (my_error_msg != NULL) { + msr_log(msr, 1, "%s", my_error_msg); + } r->connection->keepalive = AP_CONN_CLOSE; return HTTP_REQUEST_ENTITY_TOO_LARGE; break; @@ -667,8 +694,9 @@ static int hook_request_late(request_rec *r) { /* Update the request headers. They might have changed after * the body was read (trailers). */ - // TODO We still need to keep a copy of the original headers - // to log in the audit log. + /* NOTE We still need to keep a copy of the original headers + * to log in the audit log. + */ msr->request_headers = apr_table_copy(msr->mp, r->headers_in); /* Process phase REQUEST_BODY */ @@ -694,7 +722,24 @@ static void hook_error_log(const char *file, int line, int level, apr_status_t s error_message *em = NULL; if (r == NULL) return; - msr = retrieve_tx_context(r); + msr = retrieve_tx_context((request_rec *)r); + + /* Create a context for requests we never had the chance to process */ + if ((msr == NULL) + && ((level & APLOG_LEVELMASK) < APLOG_DEBUG) + && apr_table_get(r->subprocess_env, "UNIQUE_ID")) + { + msr = create_tx_context((request_rec *)r); + if (msr->txcfg->debuglog_level >= 9) { + if (msr == NULL) { + msr_log(msr, 9, "Failed to create context after request failure."); + } + else { + msr_log(msr, 9, "Context created after request failure."); + } + } + } + if (msr == NULL) return; /* Store the error message for later */ @@ -760,7 +805,7 @@ static void sec_guardian_logger(request_rec *r, request_rec *origr, modsec_rec * * The fields SESSION_ID, MODSEC_MESSAGE, and MODSEC_RATING are not used at the moment. */ - str2 = apr_psprintf(msr->mp, "%" APR_TIME_T_FMT " %" APR_TIME_T_FMT " \"%s\" %i", + str2 = apr_psprintf(msr->mp, "%" APR_TIME_T_FMT " %" APR_TIME_T_FMT " \"%s\" %d", duration, apr_time_sec(duration), log_escape(msr->mp, modsec_message), modsec_rating); if (str2 == NULL) return; @@ -781,7 +826,7 @@ static void sec_guardian_logger(request_rec *r, request_rec *origr, modsec_rec * limit = limit - strlen(str2) - 5; if (limit <= 0) { - msr_log(msr, 1, "Audit Log: Atomic PIPE write buffer too small: %i", PIPE_BUF); + msr_log(msr, 1, "Audit Log: Atomic PIPE write buffer too small: %d", PIPE_BUF); return; } @@ -836,11 +881,11 @@ static int hook_log_transaction(request_rec *r) { while ((arr->nelts == 0)&&(r->prev != NULL)) { r = r->prev; arr = apr_table_elts(r->headers_out); - } + } msr->r = r; msr->response_status = r->status; - msr->status_line = ((r->status_line != NULL) + msr->status_line = ((r->status_line != NULL) ? r->status_line : ap_get_status_line(r->status)); msr->response_protocol = get_response_protocol(origr); msr->response_headers = apr_table_copy(msr->mp, r->headers_out); @@ -871,9 +916,25 @@ static void hook_insert_filter(request_rec *r) { msr = retrieve_tx_context(r); if (msr == NULL) return; + /* Add the input filter, but only if we need it to run. */ + if (msr->if_status == IF_STATUS_WANTS_TO_RUN) { + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Hook insert_filter: Adding input forwarding filter %s(r %pp).", (((r->main != NULL)||(r->prev != NULL)) ? "for subrequest " : ""), r); + } + + ap_add_input_filter("MODSECURITY_IN", msr, r, r->connection); + } + + /* The output filters only need to be added only once per transaction + * (i.e. subrequests and redirects are excluded). + */ + if ((r->main != NULL)||(r->prev != NULL)) { + return; + } + /* We always add the PDF XSS protection filter. */ if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Hook insert_filter: Adding PDF XSS protection output filter (r %x).", r); + msr_log(msr, 4, "Hook insert_filter: Adding PDF XSS protection output filter (r %pp).", r); } ap_add_output_filter("PDFP_OUT", msr, r, r->connection); @@ -883,32 +944,23 @@ static void hook_insert_filter(request_rec *r) { if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Hook insert_filter: Processing disabled, skipping."); } + return; } - /* Add the input filter, but only if we need it to run. */ - if (msr->if_status == IF_STATUS_WANTS_TO_RUN) { - if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Hook insert_filter: Adding input forwarding filter (r %x).", r); - } - - ap_add_input_filter("MODSECURITY_IN", msr, r, r->connection); - } - /* We always add the output filter because that's where we need to * initiate our 3rd and 4th processing phases from. The filter is * smart enough not to buffer the data if it is not supposed to. */ if (msr->of_status != OF_STATUS_COMPLETE) { if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Hook insert_filter: Adding output filter (r %x).", r); + msr_log(msr, 4, "Hook insert_filter: Adding output filter (r %pp).", r); } ap_add_output_filter("MODSECURITY_OUT", msr, r, r->connection); } } -#if 0 /** * Invoked whenever Apache starts processing an error. A chance * to insert ourselves into the output filter chain. @@ -919,25 +971,30 @@ static void hook_insert_error_filter(request_rec *r) { /* Find the transaction context and make sure we are * supposed to proceed. */ - - /* TODO Insert filter but make a note that it's the error - * response the filter would be receiving. - */ - msr = retrieve_tx_context(r); if (msr == NULL) return; + /* Do not run if not enabled. */ if (msr->txcfg->is_enabled == 0) { if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Hook insert_error_filter: Processing disabled, skipping."); } return; } - + + /* Do not run if the output filter already completed. This will + * happen if we intercept in phase 4. + */ if (msr->of_status != OF_STATUS_COMPLETE) { if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Hook insert_error_filter: Adding output filter (r %x).", r); + msr_log(msr, 4, "Hook insert_error_filter: Adding output filter (r %pp).", r); } + + /* Make a note that the output we will be receiving is a + * result of error processing. + */ + msr->of_is_error = 1; + ap_add_output_filter("MODSECURITY_OUT", msr, r, r->connection); } else { if (msr->txcfg->debuglog_level >= 4) { @@ -945,15 +1002,15 @@ static void hook_insert_error_filter(request_rec *r) { } } } -#endif +#if (!defined(NO_MODSEC_API)) /** * This function is exported for other Apache modules to * register new transformation functions. */ static void modsec_register_tfn(const char *name, void *fn) { if (modsecurity != NULL) { - msre_engine_tfn_register(modsecurity->msre, name, fn); + msre_engine_tfn_register(modsecurity->msre, name, (fn_tfn_execute_t)fn); } } @@ -963,7 +1020,7 @@ static void modsec_register_tfn(const char *name, void *fn) { */ static void modsec_register_operator(const char *name, void *fn_init, void *fn_exec) { if (modsecurity != NULL) { - msre_engine_op_register(modsecurity->msre, name, fn_init, fn_exec); + msre_engine_op_register(modsecurity->msre, name, (fn_op_param_init_t)fn_init, (fn_op_execute_t)fn_exec); } } @@ -982,6 +1039,7 @@ static void modsec_register_variable(const char *name, unsigned int type, fprintf(stderr,"modsecurity is NULL\n"); } } +#endif /** * Registers module hooks with Apache. @@ -998,6 +1056,10 @@ static void register_hooks(apr_pool_t *mp) { NULL }; static const char *postread_beforeme_list[] = { + "mod_rpaf.c", + "mod_extract_forwarded2.c", + "mod_breach_realip.c", + "mod_breach_trans.c", "mod_unique_id.c", NULL }; @@ -1006,10 +1068,15 @@ static void register_hooks(apr_pool_t *mp) { NULL }; + /* Add the MODSEC_a.b define */ + *(char **)apr_array_push(ap_server_config_defines) = apr_psprintf(mp, "MODSEC_%s.%s", MODSEC_VERSION_MAJOR, MODSEC_VERSION_MINOR); + +#if (!defined(NO_MODSEC_API)) /* Export optional functions. */ APR_REGISTER_OPTIONAL_FN(modsec_register_tfn); APR_REGISTER_OPTIONAL_FN(modsec_register_operator); APR_REGISTER_OPTIONAL_FN(modsec_register_variable); +#endif /* Main hooks */ ap_hook_pre_config(hook_pre_config, NULL, NULL, APR_HOOK_FIRST); @@ -1020,7 +1087,7 @@ static void register_hooks(apr_pool_t *mp) { /* Our own hook to handle RPC transactions (not used at the moment). * // ap_hook_handler(hook_handler, NULL, NULL, APR_HOOK_MIDDLE); */ - + /* Transaction processing hooks */ ap_hook_post_read_request(hook_request_early, postread_beforeme_list, postread_afterme_list, APR_HOOK_REALLY_FIRST); @@ -1033,7 +1100,7 @@ static void register_hooks(apr_pool_t *mp) { /* Filter hooks */ ap_hook_insert_filter(hook_insert_filter, NULL, NULL, APR_HOOK_FIRST); - /* ap_hook_insert_error_filter(hook_insert_error_filter, NULL, NULL, APR_HOOK_FIRST); */ + ap_hook_insert_error_filter(hook_insert_error_filter, NULL, NULL, APR_HOOK_FIRST); ap_register_input_filter("MODSECURITY_IN", input_filter, NULL, AP_FTYPE_CONTENT_SET); diff --git a/apache2/mod_security2_config.h.in b/apache2/mod_security2_config.h.in new file mode 100644 index 00000000..394a1313 --- /dev/null +++ b/apache2/mod_security2_config.h.in @@ -0,0 +1,123 @@ +/* mod_security2_config.h.in. Generated from configure.in by autoheader. */ + +/* Define to 1 if you have the `atexit' function. */ +#undef HAVE_ATEXIT + +/* Define to 1 if you have the `fchmod' function. */ +#undef HAVE_FCHMOD + +/* Define to 1 if you have the header file. */ +#undef HAVE_FCNTL_H + +/* Define to 1 if you have the `getcwd' function. */ +#undef HAVE_GETCWD + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LIMITS_H + +/* Define to 1 if your system has a GNU libc compatible `malloc' function, and + to 0 otherwise. */ +#undef HAVE_MALLOC + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the `memset' function. */ +#undef HAVE_MEMSET + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the `strcasecmp' function. */ +#undef HAVE_STRCASECMP + +/* Define to 1 if you have the `strchr' function. */ +#undef HAVE_STRCHR + +/* Define to 1 if you have the `strdup' function. */ +#undef HAVE_STRDUP + +/* Define to 1 if you have the `strerror' function. */ +#undef HAVE_STRERROR + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strncasecmp' function. */ +#undef HAVE_STRNCASECMP + +/* Define to 1 if you have the `strrchr' function. */ +#undef HAVE_STRRCHR + +/* Define to 1 if you have the `strstr' function. */ +#undef HAVE_STRSTR + +/* Define to 1 if you have the `strtol' function. */ +#undef HAVE_STRTOL + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define to 1 if your declares `struct tm'. */ +#undef TM_IN_SYS_TIME + +/* Define for Solaris 2.5.1 so the uint8_t typedef from , + , or is not used. If the typedef was allowed, the + #define below would cause a syntax error. */ +#undef _UINT8_T + +/* Define to empty if `const' does not conform to ANSI C. */ +#undef const + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +#undef inline +#endif + +/* Define to rpl_malloc if the replacement function should be used. */ +#undef malloc + +/* Define to equivalent of C99 restrict keyword, or to nothing if this is not + supported. Do not define if restrict is supported directly. */ +#undef restrict + +/* Define to `unsigned int' if does not define. */ +#undef size_t + +/* Define to the type of an unsigned integer type of width exactly 8 bits if + such a type exists and the standard includes do not define it. */ +#undef uint8_t diff --git a/apache2/mod_security2_config.hw b/apache2/mod_security2_config.hw new file mode 100644 index 00000000..c9500446 --- /dev/null +++ b/apache2/mod_security2_config.hw @@ -0,0 +1 @@ +/* This file is left empty for building on Windows. */ diff --git a/apache2/modsecurity.c b/apache2/modsecurity.c index 8eab51f8..c024d21f 100644 --- a/apache2/modsecurity.c +++ b/apache2/modsecurity.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -16,6 +16,15 @@ #include "msc_parsers.h" #include "msc_util.h" +modsec_build_type_rec DSOLOCAL modsec_build_type[] = { + { "dev", 1 }, /* Development build */ + { "rc", 3 }, /* Release Candidate build */ + { "", 9 }, /* Production build */ + { "breach", 9 }, /* Breach build */ + { "trunk", 9 }, /* Trunk build */ + { NULL, -1 } /* terminator */ +}; + /** * Log an alert message to the log, adding the rule metadata at the end. */ @@ -136,7 +145,8 @@ static apr_status_t modsecurity_tx_cleanup(void *data) { apr_table_entry_t *te; int collect_garbage = 0; int i; - + char *my_error_msg = NULL; + if (msr == NULL) return APR_SUCCESS; if (rand() < RAND_MAX/100) { @@ -162,12 +172,14 @@ static apr_status_t modsecurity_tx_cleanup(void *data) { /* Multipart processor cleanup. */ if (msr->mpd != NULL) multipart_cleanup(msr); - #ifdef WITH_LIBXML2 /* XML processor cleanup. */ if (msr->xml != NULL) xml_cleanup(msr); - #endif - modsecurity_request_body_clear(msr); + // TODO: Why do we ignore return code here? + modsecurity_request_body_clear(msr, &my_error_msg); + if (my_error_msg != NULL) { + msr_log(msr, 1, "%s", my_error_msg); + } return APR_SUCCESS; } @@ -230,7 +242,7 @@ apr_status_t modsecurity_tx_init(modsec_rec *msr) { { msr->msc_reqbody_storage = MSC_REQBODY_DISK; } - + /* In all other cases, try using the memory first * but switch over to disk for larger bodies. */ @@ -250,8 +262,8 @@ apr_status_t modsecurity_tx_init(modsec_rec *msr) { if (msr->query_string != NULL) { int invalid_count = 0; - if (parse_arguments(msr, msr->query_string, strlen(msr->query_string), - msr->txcfg->argument_separator, "QUERY_STRING", msr->arguments, + if (parse_arguments(msr, msr->query_string, strlen(msr->query_string), + msr->txcfg->argument_separator, "QUERY_STRING", msr->arguments, &invalid_count) < 0) { msr_log(msr, 1, "Initialisation: Error occurred while parsing QUERY_STRING arguments."); @@ -295,9 +307,21 @@ apr_status_t modsecurity_tx_init(modsec_rec *msr) { msr->collections_dirty = apr_table_make(msr->mp, 8); if (msr->collections_dirty == NULL) return -1; + /* Other */ msr->tcache = apr_hash_make(msr->mp); if (msr->tcache == NULL) return -1; + msr->matched_rules = apr_array_make(msr->mp, 16, sizeof(void *)); + if (msr->matched_rules == NULL) return -1; + + msr->matched_var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + if (msr->matched_var == NULL) return -1; + + msr->highest_severity = 255; /* high, invalid value */ + + msr->removed_rules = apr_array_make(msr->mp, 16, sizeof(char *)); + if (msr->removed_rules == NULL) return -1; + return 1; } @@ -309,19 +333,23 @@ static int is_response_status_relevant(modsec_rec *msr, int status) { apr_status_t rc; char buf[32]; + /* ENH: Setting is_relevant here will cause an audit even if noauditlog + * was set for the last rule that matched. Is this what we want? + */ + if ((msr->txcfg->auditlog_relevant_regex == NULL) ||(msr->txcfg->auditlog_relevant_regex == NOT_SET_P)) { return 0; } - apr_snprintf(buf, sizeof(buf), "%i", status); + apr_snprintf(buf, sizeof(buf), "%d", status); rc = msc_regexec(msr->txcfg->auditlog_relevant_regex, buf, strlen(buf), &my_error_msg); if (rc >= 0) return 1; if (rc == PCRE_ERROR_NOMATCH) return 0; - msr_log(msr, 1, "Regex processing failed (rc %i): %s", rc, my_error_msg); + msr_log(msr, 1, "Regex processing failed (rc %d): %s", rc, my_error_msg); return 0; } @@ -342,7 +370,12 @@ static apr_status_t modsecurity_process_phase_request_headers(modsec_rec *msr) { * */ static apr_status_t modsecurity_process_phase_request_body(modsec_rec *msr) { - msr_log(msr, 4, "Starting phase REQUEST_BODY."); + if ((msr->allow_scope == ACTION_ALLOW_REQUEST)||(msr->allow_scope == ACTION_ALLOW)) { + msr_log(msr, 4, "Skipping phase REQUEST_BODY (allow used)."); + return 0; + } else { + msr_log(msr, 4, "Starting phase REQUEST_BODY."); + } if (msr->txcfg->ruleset != NULL) { return msre_ruleset_process_phase(msr->txcfg->ruleset, msr); @@ -355,7 +388,12 @@ static apr_status_t modsecurity_process_phase_request_body(modsec_rec *msr) { * */ static apr_status_t modsecurity_process_phase_response_headers(modsec_rec *msr) { - msr_log(msr, 4, "Starting phase RESPONSE_HEADERS."); + if (msr->allow_scope == ACTION_ALLOW) { + msr_log(msr, 4, "Skipping phase RESPONSE_HEADERS (allow used)."); + return 0; + } else { + msr_log(msr, 4, "Starting phase RESPONSE_HEADERS."); + } if (msr->txcfg->ruleset != NULL) { return msre_ruleset_process_phase(msr->txcfg->ruleset, msr); @@ -368,7 +406,12 @@ static apr_status_t modsecurity_process_phase_response_headers(modsec_rec *msr) * */ static apr_status_t modsecurity_process_phase_response_body(modsec_rec *msr) { - msr_log(msr, 4, "Starting phase RESPONSE_BODY."); + if (msr->allow_scope == ACTION_ALLOW) { + msr_log(msr, 4, "Skipping phase RESPONSE_BODY (allow used)."); + return 0; + } else { + msr_log(msr, 4, "Starting phase RESPONSE_BODY."); + } if (msr->txcfg->ruleset != NULL) { return msre_ruleset_process_phase(msr->txcfg->ruleset, msr); @@ -401,7 +444,7 @@ static apr_status_t modsecurity_process_phase_logging(modsec_rec *msr) { } /* Figure out if we want to keep the files (if there are any, of course). */ - if ((msr->txcfg->upload_keep_files == KEEP_FILES_ON) + if ((msr->txcfg->upload_keep_files == KEEP_FILES_ON) || ((msr->txcfg->upload_keep_files == KEEP_FILES_RELEVANT_ONLY)&&(msr->is_relevant))) { msr->upload_remove_files = 0; @@ -428,7 +471,7 @@ static apr_status_t modsecurity_process_phase_logging(modsec_rec *msr) { break; default : - return HTTP_INTERNAL_SERVER_ERROR; + msr_log(msr, 1, "Internal error: Could not determine if auditing is needed, so forcing auditing."); break; } @@ -465,10 +508,9 @@ apr_status_t modsecurity_process_phase(modsec_rec *msr, int phase) { return modsecurity_process_phase_logging(msr); break; default : - msr_log(msr, 1, "Invalid processing phase: %i", msr->phase); - return -1; + msr_log(msr, 1, "Invalid processing phase: %d", msr->phase); break; } - return 0; + return -1; } diff --git a/apache2/modsecurity.h b/apache2/modsecurity.h index 2aa69faa..38fcc53c 100644 --- a/apache2/modsecurity.h +++ b/apache2/modsecurity.h @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -30,13 +30,18 @@ typedef struct msc_string msc_string; #define DSOLOCAL #endif +/* For GNU C, tell the compiler to check printf like formatters */ +#if (defined(__GNUC__) && !defined(SOLARIS2)) +#define PRINTF_ATTRIBUTE(a,b) __attribute__((format (printf, a, b))) +#else +#define PRINTF_ATTRIBUTE(a,b) +#endif + #include "msc_logging.h" #include "msc_multipart.h" #include "msc_pcre.h" #include "msc_util.h" -#ifdef WITH_LIBXML2 #include "msc_xml.h" -#endif #include "msc_geo.h" #include "re.h" @@ -49,9 +54,23 @@ typedef struct msc_string msc_string; #include "http_log.h" #include "http_protocol.h" -#define MODULE_NAME "ModSecurity" -#define MODULE_RELEASE "2.5.0-dev2" -#define MODULE_NAME_FULL (MODULE_NAME " v" MODULE_RELEASE " (Apache 2.x)") +typedef struct modsec_build_type_rec { + const char * name; + int val; +} modsec_build_type_rec; +extern DSOLOCAL modsec_build_type_rec modsec_build_type[]; + +#define MODSEC_VERSION_MAJOR "2" +#define MODSEC_VERSION_MINOR "5" +#define MODSEC_VERSION_MAINT "0" +#define MODSEC_VERSION_TYPE "" +#define MODSEC_VERSION_RELEASE "" + +#define MODULE_NAME "ModSecurity for Apache" +#define MODULE_RELEASE \ + MODSEC_VERSION_MAJOR "." MODSEC_VERSION_MINOR "." MODSEC_VERSION_MAINT \ + "-" MODSEC_VERSION_TYPE MODSEC_VERSION_RELEASE +#define MODULE_NAME_FULL MODULE_NAME "/" MODULE_RELEASE " (http://www.modsecurity.org/)" #define PHASE_REQUEST_HEADERS 1 #define PHASE_REQUEST_BODY 2 @@ -61,8 +80,8 @@ typedef struct msc_string msc_string; #define PHASE_FIRST PHASE_REQUEST_HEADERS #define PHASE_LAST PHASE_LOGGING -#define NOT_SET -1 -#define NOT_SET_P (void *)-1 +#define NOT_SET -1l +#define NOT_SET_P ((void *)-1l) #define CREATEMODE ( APR_UREAD | APR_UWRITE | APR_GREAD ) #define CREATEMODE_DIR ( APR_UREAD | APR_UWRITE | APR_UEXECUTE | APR_GREAD | APR_GEXECUTE ) @@ -86,9 +105,20 @@ typedef struct msc_string msc_string; #define REQUEST_BODY_HARD_LIMIT 1073741824L #define REQUEST_BODY_DEFAULT_INMEMORY_LIMIT 131072 #define REQUEST_BODY_DEFAULT_LIMIT 134217728 +#define REQUEST_BODY_NO_FILES_DEFAULT_LIMIT 1048576 #define RESPONSE_BODY_DEFAULT_LIMIT 524288 #define RESPONSE_BODY_HARD_LIMIT 1073741824L +#define RESPONSE_BODY_LIMIT_ACTION_REJECT 0 +#define RESPONSE_BODY_LIMIT_ACTION_PARTIAL 1 + +#define SECACTION_TARGETS "REQUEST_URI" +#define SECACTION_ARGS "@unconditionalMatch" + +#define SECMARKER_TARGETS "REQUEST_URI" +#define SECMARKER_ARGS "@noMatch" +#define SECMARKER_BASE_ACTIONS "t:none,pass,id:" + #if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE) #include "unixd.h" #define __SET_MUTEX_PERMS @@ -140,11 +170,16 @@ extern DSOLOCAL const command_rec module_directives[]; #define ACTION_PROXY 3 #define ACTION_DROP 4 #define ACTION_ALLOW 5 +#define ACTION_ALLOW_REQUEST 6 +#define ACTION_ALLOW_PHASE 7 #define MODSEC_DISABLED 0 #define MODSEC_DETECTION_ONLY 1 #define MODSEC_ENABLED 2 +#define MODSEC_CACHE_DISABLED 0 +#define MODSEC_CACHE_ENABLED 1 + #define MODSEC_OFFLINE 0 #define MODSEC_ONLINE 1 @@ -190,12 +225,13 @@ struct modsec_rec { unsigned int if_started_forwarding; apr_size_t reqbody_length; - unsigned int reqbody_status; apr_bucket_brigade *of_brigade; unsigned int of_status; unsigned int of_done_reading; unsigned int of_skipping; + unsigned int of_partial; + unsigned int of_is_error; unsigned int resbody_status; apr_size_t resbody_length; @@ -229,7 +265,7 @@ struct modsec_rec { const char *request_protocol; const char *hostname; - + apr_table_t *request_headers; apr_off_t request_content_length; @@ -269,7 +305,7 @@ struct modsec_rec { unsigned int msc_reqbody_chunk_offset; /* offset of the chunk currently in use */ msc_data_chunk *msc_reqbody_chunk_current; /* current chunk */ char *msc_reqbody_buffer; - + const char *msc_reqbody_filename; /* when stored on disk */ int msc_reqbody_fd; msc_data_chunk *msc_reqbody_disk_chunk; @@ -278,11 +314,11 @@ struct modsec_rec { int msc_reqbody_error; const char *msc_reqbody_error_msg; + apr_size_t msc_reqbody_no_files_length; + multipart_data *mpd; /* MULTIPART processor data structure */ - #ifdef WITH_LIBXML2 xml_data *xml; /* XML processor data structure */ - #endif /* audit logging */ char *new_auditlog_boundary; @@ -292,6 +328,7 @@ struct modsec_rec { apr_md5_ctx_t new_auditlog_md5ctx; unsigned int was_intercepted; + unsigned int rule_was_intercepted; unsigned int intercept_phase; msre_actionset *intercept_actionset; const char *intercept_message; @@ -302,7 +339,9 @@ struct modsec_rec { apr_time_t time_checkpoint_2; apr_time_t time_checkpoint_3; - const char *matched_var; + apr_array_header_t *matched_rules; + msc_string *matched_var; + int highest_severity; /* upload */ int upload_extract_files; @@ -323,6 +362,17 @@ struct modsec_rec { /* data cache */ apr_hash_t *tcache; + + /* removed rules */ + apr_array_header_t *removed_rules; + + /* When "allow" is executed the variable below is + * updated to contain the scope of the allow action. Set + * at 0 by default, it will have ACTION_ALLOW if we are + * to allow phases 1-4 and ACTION_ALLOW_REQUEST if we + * are to allow phases 1-2 only. + */ + unsigned int allow_scope; }; struct directory_config { @@ -334,11 +384,13 @@ struct directory_config { int reqbody_access; long int reqbody_inmemory_limit; long int reqbody_limit; + long int reqbody_no_files_limit; int resbody_access; long int of_limit; apr_table_t *of_mime_types; int of_mime_types_cleared; + int of_limit_action; const char *debuglog_name; int debuglog_level; @@ -385,17 +437,19 @@ struct directory_config { /* A regular expression that determines if a response * status is treated as relevant. */ - msc_regex_t *auditlog_relevant_regex; + msc_regex_t *auditlog_relevant_regex; /* Upload */ const char *tmp_dir; const char *upload_dir; int upload_keep_files; int upload_validates_files; + int upload_filemode; /* Used only in the configuration phase. */ msre_rule *tmp_chain_starter; msre_actionset *tmp_default_actionset; + apr_table_t *tmp_rule_placeholders; /* Misc */ const char *data_dir; @@ -414,6 +468,19 @@ struct directory_config { /* Geo Lookup */ geo_db *geo; + + /* Cache */ + int cache_trans; + apr_size_t cache_trans_min; + apr_size_t cache_trans_max; + + /* Array to hold signatures of components, which will + * appear in the ModSecurity signature in the audit log. + */ + apr_array_header_t *component_signatures; + + /* Request character encoding. */ + const char *request_encoding; }; struct error_message { @@ -474,14 +541,14 @@ apr_status_t DSOLOCAL modsecurity_process_phase(modsec_rec *msr, int phase); /* Request body functions */ -apr_status_t DSOLOCAL modsecurity_request_body_start(modsec_rec *msr); +apr_status_t DSOLOCAL modsecurity_request_body_start(modsec_rec *msr, char **error_msg); apr_status_t DSOLOCAL modsecurity_request_body_store(modsec_rec *msr, - const char *data, apr_size_t length); + const char *data, apr_size_t length, char **error_msg); -apr_status_t DSOLOCAL modsecurity_request_body_end(modsec_rec *msr); +apr_status_t DSOLOCAL modsecurity_request_body_end(modsec_rec *msr, char **error_msg); -apr_status_t DSOLOCAL modsecurity_request_body_retrieve_start(modsec_rec *msr); +apr_status_t DSOLOCAL modsecurity_request_body_retrieve_start(modsec_rec *msr, char **error_msg); apr_status_t DSOLOCAL modsecurity_request_body_retrieve_end(modsec_rec *msr); @@ -490,7 +557,7 @@ apr_status_t DSOLOCAL modsecurity_request_body_retrieve_end(modsec_rec *msr); * nbytes will contain the number of bytes stored in the buffer. */ apr_status_t DSOLOCAL modsecurity_request_body_retrieve(modsec_rec *msr, msc_data_chunk **chunk, - long int nbytes); + long int nbytes, char **error_msg); void DSOLOCAL msc_add(modsec_rec *msr, int level, msre_actionset *actionset, const char *action_message, const char *rule_message); @@ -498,6 +565,6 @@ void DSOLOCAL msc_add(modsec_rec *msr, int level, msre_actionset *actionset, void DSOLOCAL msc_alert(modsec_rec *msr, int level, msre_actionset *actionset, const char *action_message, const char *rule_message); -apr_status_t DSOLOCAL modsecurity_request_body_clear(modsec_rec *msr); +apr_status_t DSOLOCAL modsecurity_request_body_clear(modsec_rec *msr, char **error_msg); #endif diff --git a/apache2/modules.mk b/apache2/modules.mk index bce0eb7d..d201d566 100644 --- a/apache2/modules.mk +++ b/apache2/modules.mk @@ -1,12 +1,11 @@ - MOD_SECURITY2 = mod_security2 apache2_config apache2_io apache2_util \ re re_operators re_actions re_tfns re_variables \ msc_logging msc_xml msc_multipart modsecurity msc_parsers msc_util msc_pcre \ - persist_dbm msc_reqbody pdf_protect msc_geo acmp + persist_dbm msc_reqbody pdf_protect msc_geo acmp msc_lua H = re.h modsecurity.h msc_logging.h msc_multipart.h msc_parsers.h \ msc_pcre.h msc_util.h msc_xml.h persist_dbm.h apache2.h pdf_protect.h \ - msc_geo.h acmp.h utf8tables.h + msc_geo.h acmp.h utf8tables.h msc_lua.h ${MOD_SECURITY2:=.slo}: ${H} ${MOD_SECURITY2:=.lo}: ${H} diff --git a/apache2/msc_geo.c b/apache2/msc_geo.c index 4f0df69c..3269ba3d 100644 --- a/apache2/msc_geo.c +++ b/apache2/msc_geo.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -165,7 +165,7 @@ static int db_open(directory_config *dcfg, char **error_msg) fprintf(stderr, "GEO: read 0x%02x%02x%02x\n", buf[0], buf[1], buf[2]); #endif if ((rc != APR_SUCCESS) || (nbytes != 3)) { - *error_msg = apr_psprintf(mp, "Could not read from geo database \"%s\" (%d/3 bytes read): %s", geo->dbfn, nbytes, apr_strerror(rc, errstr, 1024)); + *error_msg = apr_psprintf(mp, "Could not read from geo database \"%s\" (%" APR_SIZE_T_FMT "/3 bytes read): %s", geo->dbfn, nbytes, apr_strerror(rc, errstr, 1024)); return -1; } if ((buf[0] == 0xff) && (buf[1] == 0xff) && (buf[2] == 0xff)) { @@ -190,7 +190,7 @@ static int db_open(directory_config *dcfg, char **error_msg) memset(buf, 0, 3); rc = apr_file_read_full(geo->db, &buf, 3, &nbytes); if ((rc != APR_SUCCESS) || (nbytes != 3)) { - *error_msg = apr_psprintf(mp, "Could not read geo database \"%s\" country offset (%d/3 bytes read): %s", geo->dbfn, nbytes, apr_strerror(rc, errstr, 1024)); + *error_msg = apr_psprintf(mp, "Could not read geo database \"%s\" country offset (%" APR_SIZE_T_FMT "/3 bytes read): %s", geo->dbfn, nbytes, apr_strerror(rc, errstr, 1024)); return -1; } #ifdef DEBUG_CONF @@ -300,34 +300,35 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro georec->dma_code = 0; georec->area_code = 0; - msr_log(msr, 9, "GEO: Looking up \"%s\".", target); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "GEO: Looking up \"%s\".", log_escape(msr->mp, target)); + } /* NOTE: This only works with ipv4 */ if ((rc = apr_sockaddr_info_get(&addr, target, APR_INET, 0, 0, msr->mp)) != APR_SUCCESS) { - - *error_msg = apr_psprintf(msr->mp, "Geo lookup of \"%s\" failed: %s", target, apr_strerror(rc, errstr, 1024)); + + *error_msg = apr_psprintf(msr->mp, "Geo lookup of \"%s\" failed: %s", log_escape(msr->mp, target), apr_strerror(rc, errstr, 1024)); return 0; } if ((rc = apr_sockaddr_ip_get(&targetip, addr)) != APR_SUCCESS) { - *error_msg = apr_psprintf(msr->mp, "Geo lookup of \"%s\" failed: %s", target, apr_strerror(rc, errstr, 1024)); + *error_msg = apr_psprintf(msr->mp, "Geo lookup of \"%s\" failed: %s", log_escape(msr->mp, target), apr_strerror(rc, errstr, 1024)); return 0; }; /* Why is this in host byte order? */ ipnum = ntohl(addr->sa.sin.sin_addr.s_addr); - msr_log(msr, 9, "GEO: Using address \"%s\" (0x%08x).", targetip, ipnum); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "GEO: Using address \"%s\" (0x%08lx).", targetip, ipnum); + } for (level = 31; level >= 0; level--) { - /* Read the record */ seekto = 2 * reclen * rec_val; apr_file_seek(geo->db, APR_SET, &seekto); /* TODO: check rc */ rc = apr_file_read_full(geo->db, &buf, (2 * reclen), &nbytes); - - /* NOTE: This is hard-coded for size 3 records */ /* Left */ if ((ipnum & (1 << level)) == 0) { @@ -352,13 +353,11 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro country = rec_val; country -= geo->ctry_offset; if (country <= 0) { - *error_msg = apr_psprintf(msr->mp, "No geo data for \"%s\".", target); + *error_msg = apr_psprintf(msr->mp, "No geo data for \"%s\".", log_escape(msr->mp, target)); return 0; } - msr_log(msr, 9, "GEO: rec=\"%s\"", log_escape_raw(msr->mp, buf, sizeof(buf))); /* Country */ - msr_log(msr, 9, "GEO: country=\"%.*s\"", (1*4), log_escape_raw(msr->mp, (unsigned char *)&rec_val, 1)); georec->country_code = geo_country_code[country]; georec->country_code3 = geo_country_code3[country]; georec->country_name = geo_country_name[country]; @@ -377,13 +376,17 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro country = cbuf[0]; if (country <= 0) { - *error_msg = apr_psprintf(msr->mp, "No geo data for \"%s\".", target); + *error_msg = apr_psprintf(msr->mp, "No geo data for \"%s\".", log_escape(msr->mp, target)); return 0; } - msr_log(msr, 9, "GEO: rec=\"%s\"", log_escape_raw(msr->mp, cbuf, sizeof(cbuf))); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "GEO: rec=\"%s\"", log_escape_raw(msr->mp, cbuf, sizeof(cbuf))); + } /* Country */ - msr_log(msr, 9, "GEO: country=\"%.*s\"", (1*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "GEO: country=\"%.*s\"", (1*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))); + } georec->country_code = geo_country_code[country]; georec->country_code3 = geo_country_code3[country]; georec->country_name = geo_country_name[country]; @@ -393,27 +396,35 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro /* Region */ field_len = field_length((const char *)cbuf+rec_offset, remaining); - msr_log(msr, 9, "GEO: region=\"%.*s\"", ((field_len+1)*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4)); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "GEO: region=\"%.*s\"", ((field_len+1)*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4)); + } georec->region = apr_pstrmemdup(msr->mp, (const char *)cbuf+rec_offset, (remaining)); rec_offset += field_len + 1; remaining -= field_len + 1; - + /* City */ field_len = field_length((const char *)cbuf+rec_offset, remaining); - msr_log(msr, 9, "GEO: city=\"%.*s\"", ((field_len+1)*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4)); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "GEO: city=\"%.*s\"", ((field_len+1)*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4)); + } georec->city = apr_pstrmemdup(msr->mp, (const char *)cbuf+rec_offset, (remaining)); rec_offset += field_len + 1; remaining -= field_len + 1; - + /* Postal Code */ field_len = field_length((const char *)cbuf+rec_offset, remaining); - msr_log(msr, 9, "GEO: postal_code=\"%.*s\"", ((field_len+1)*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4)); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "GEO: postal_code=\"%.*s\"", ((field_len+1)*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4)); + } georec->postal_code = apr_pstrmemdup(msr->mp, (const char *)cbuf+rec_offset, (remaining)); rec_offset += field_len + 1; remaining -= field_len + 1; /* Latitude */ - msr_log(msr, 9, "GEO: latitude=\"%.*s\"", (3*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4)); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "GEO: latitude=\"%.*s\"", (3*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4)); + } dtmp = cbuf[rec_offset] + (cbuf[rec_offset+1] << 8) + (cbuf[rec_offset+2] << 16); @@ -421,9 +432,11 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro rec_offset += 3; remaining -= 3; - + /* Longitude */ - msr_log(msr, 9, "GEO: longitude=\"%.*s\"", (3*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4)); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "GEO: longitude=\"%.*s\"", (3*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4)); + } dtmp = cbuf[rec_offset] + (cbuf[rec_offset+1] << 8) + (cbuf[rec_offset+2] << 16); @@ -432,7 +445,9 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro remaining -= 3; /* dma/area codes are in city rev1 and US only */ - msr_log(msr, 9, "GEO: dma/area=\"%.*s\"", (3*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4)); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "GEO: dma/area=\"%.*s\"", (3*4), log_escape_raw(msr->mp, cbuf, sizeof(cbuf))+(rec_offset*4)); + } if (geo->dbtype == GEO_CITY_DATABASE_1 && georec->country_code[0] == 'U' && georec->country_code[1] == 'S') @@ -446,10 +461,10 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro rec_offset += 6; remaining -= 6; } - + } - *error_msg = apr_psprintf(msr->mp, "Geo lookup of \"%s\" succeeded.", target); + *error_msg = apr_psprintf(msr->mp, "Geo lookup of \"%s\" succeeded.", log_escape(msr->mp, target)); return 1; } diff --git a/apache2/msc_geo.h b/apache2/msc_geo.h index 4c962ffa..9c6b5326 100644 --- a/apache2/msc_geo.h +++ b/apache2/msc_geo.h @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, diff --git a/apache2/msc_logging.c b/apache2/msc_logging.c index 9935ef57..24e940a1 100644 --- a/apache2/msc_logging.c +++ b/apache2/msc_logging.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -8,6 +8,7 @@ * write to Breach Security, Inc. at support@breach.com. * */ +#include "re.h" #include "msc_logging.h" #include "httpd.h" #include "apr_strings.h" @@ -23,22 +24,39 @@ static int sec_auditlog_write(modsec_rec *msr, const char *data, unsigned int le apr_size_t nbytes_written, nbytes = len; apr_status_t rc; - if ((msr->new_auditlog_fd == NULL)||(data == NULL)) return -1; + /* Do nothing if there's no data. */ + if (data == NULL) return -1; + /* Update size counters and the hash calculation. We always do this, + * even in cases where write fails. That will make it easier to detect + * problems with partial writes. + */ + msr->new_auditlog_size += len; + apr_md5_update(&msr->new_auditlog_md5ctx, data, len); + + /* Do not write if we do not have a file descriptor. */ + if (msr->new_auditlog_fd == NULL) return -1; + + /* Write data to file. */ rc = apr_file_write_full(msr->new_auditlog_fd, data, nbytes, &nbytes_written); if (rc != APR_SUCCESS) { msr_log(msr, 1, "Audit log: Failed writing (requested %" APR_SIZE_T_FMT " bytes, written %" APR_SIZE_T_FMT ")", nbytes, nbytes_written); + + /* Set to NULL to prevent more than one error message on + * out-of-disk-space events and to prevent further attempts + * to write to the same file in this request. + * + * Note that, as we opened the file through the pool mechanism of + * the APR, we do not need to close the file here. It will be closed + * automatically at the end of the request. + */ + msr->new_auditlog_fd = NULL; + return -1; } - /* Note the following will only take into account the actual - * amount of bytes we've written. - */ - msr->new_auditlog_size += nbytes_written; - apr_md5_update(&msr->new_auditlog_md5ctx, data, nbytes_written); - - return rc; + return 1; } /** @@ -78,7 +96,7 @@ char *construct_log_vcombinedus(modsec_rec *msr) { /* sessionid */ sessionid = (msr->sessionid == NULL ? "-" : msr->sessionid); - return apr_psprintf(msr->mp, "%s %s %s %s [%s] \"%s\" %i %" APR_OFF_T_FMT " \"%s\" \"%s\" %s \"%s\"", + return apr_psprintf(msr->mp, "%s %s %s %s [%s] \"%s\" %u %" APR_OFF_T_FMT " \"%s\" \"%s\" %s \"%s\"", log_escape_nq(msr->mp, msr->hostname), msr->remote_addr, log_escape_nq(msr->mp, remote_user), log_escape_nq(msr->mp, local_user), current_logtime(msr->mp), ((msr->request_line == NULL) ? "" : log_escape(msr->mp, msr->request_line)), @@ -149,7 +167,7 @@ char *construct_log_vcombinedus_limited(modsec_rec *msr, int _limit, int *was_li limit -= strlen(sessionid); /* session id */ if (limit <= 0) { - msr_log(msr, 1, "GuardianLog: Atomic pipe write size too small: %i", PIPE_BUF); + msr_log(msr, 1, "GuardianLog: Atomic pipe write size too small: %d", PIPE_BUF); return NULL; } @@ -169,19 +187,19 @@ char *construct_log_vcombinedus_limited(modsec_rec *msr, int _limit, int *was_li remote_user[32] = '\0'; } limit -= strlen(remote_user); - + if (strlen(local_user) > 32) { msr_log(msr, 9, "GuardianLog: Reduced local_user to 32."); local_user[32] = '\0'; } limit -= strlen(local_user); - + if (strlen(referer) > 64) { msr_log(msr, 9, "GuardianLog: Reduced referer to 64."); referer[64] = '\0'; } limit -= strlen(referer); - + if (strlen(user_agent) > 64) { msr_log(msr, 9, "GuardianLog: Reduced user_agent to 64."); user_agent[64] = '\0'; @@ -189,21 +207,21 @@ char *construct_log_vcombinedus_limited(modsec_rec *msr, int _limit, int *was_li limit -= strlen(user_agent); if (limit <= 0) { - msr_log(msr, 1, "GuardianLog: Atomic pipe write size too small: %i.", PIPE_BUF); + msr_log(msr, 1, "GuardianLog: Atomic pipe write size too small: %d.", PIPE_BUF); return NULL; } /* use what's left for the request line */ if ((int)strlen(the_request) > limit) { the_request[limit] = '\0'; - msr_log(msr, 9, "GuardianLog: Reduced the_request to %i bytes.", limit); + msr_log(msr, 9, "GuardianLog: Reduced the_request to %d bytes.", limit); } } else { /* Yay! We have enough space! */ *was_limited = 0; } - return apr_psprintf(msr->mp, "%s %s %s %s [%s] \"%s\" %i %s \"%s\" \"%s\" %s \"%s\"", + return apr_psprintf(msr->mp, "%s %s %s %s [%s] \"%s\" %u %s \"%s\" \"%s\" %s \"%s\"", hostname, msr->remote_addr, remote_user, local_user, current_logtime(msr->mp), the_request, msr->response_status, bytes_sent, referer, user_agent, @@ -217,7 +235,7 @@ char *construct_log_vcombinedus_limited(modsec_rec *msr, int _limit, int *was_li int is_valid_parts_specification(char *p) { char c, *t = p; - while((c = *t++) != '\0') { + while((c = *(t++)) != '\0') { if ((c != AUDITLOG_PART_ENDMARKER)&&((c < AUDITLOG_PART_FIRST)||(c > AUDITLOG_PART_LAST))) { return 0; } @@ -284,7 +302,7 @@ static void sanitise_request_line(modsec_rec *msr) { j = arg->value_origin_offset; while((*p != '\0')&&(j--)) p++; if (*p == '\0') { - msr_log(msr, 1, "Unable to sanitise variable \"%s\" at offset %i of QUERY_STRING" + msr_log(msr, 1, "Unable to sanitise variable \"%s\" at offset %u of QUERY_STRING" "because the request line is too short.", log_escape_ex(msr->mp, arg->name, arg->name_len), arg->value_origin_offset); @@ -297,7 +315,7 @@ static void sanitise_request_line(modsec_rec *msr) { *p++ = '*'; } if (*p == '\0') { - msr_log(msr, 1, "Unable to sanitise variable \"%s\" at offset %i (size %i) " + msr_log(msr, 1, "Unable to sanitise variable \"%s\" at offset %u (size %d) " "of QUERY_STRING because the request line is too short.", log_escape_ex(msr->mp, arg->name, arg->name_len), arg->value_origin_offset, arg->value_origin_len); @@ -307,6 +325,37 @@ static void sanitise_request_line(modsec_rec *msr) { } } +/** + * Output the Producer header. + */ +static void sec_auditlog_write_producer_header(modsec_rec *msr) { + char **signatures = NULL; + char *text = NULL; + int i; + + /* Try to write everything in one go. */ + if (msr->txcfg->component_signatures->nelts == 0) { + text = apr_psprintf(msr->mp, "Producer: %s.\n", MODULE_NAME_FULL); + sec_auditlog_write(msr, text, strlen(text)); + + return; + } + + /* Start with the ModSecurity signature. */ + text = apr_psprintf(msr->mp, "Producer: %s", MODULE_NAME_FULL); + sec_auditlog_write(msr, text, strlen(text)); + + + /* Then loop through the components and output individual signatures. */ + signatures = (char **)msr->txcfg->component_signatures->elts; + for(i = 0; i < msr->txcfg->component_signatures->nelts; i++) { + text = apr_psprintf(msr->mp, "; %s", (char *)signatures[i]); + sec_auditlog_write(msr, text, strlen(text)); + } + + sec_auditlog_write(msr, ".\n", 2); +} + /** * Produce an audit log entry. */ @@ -314,6 +363,7 @@ void sec_audit_logger(modsec_rec *msr) { const apr_array_header_t *arr = NULL; apr_table_entry_t *te = NULL; char *str1 = NULL, *str2 = NULL, *text = NULL; + const msre_rule *rule = NULL; apr_size_t nbytes, nbytes_written; unsigned char md5hash[APR_MD5_DIGESTSIZE]; int was_limited = 0; @@ -332,7 +382,7 @@ void sec_audit_logger(modsec_rec *msr) { msr_log(msr, 4, "Audit log: Skipping request whose request_line is null."); return; } - + /* Also return silently if we don't have a file descriptor. */ if (msr->txcfg->auditlog_fd == NULL) { msr_log(msr, 4, "Audit log: Skipping request since there is nowhere to write to."); @@ -361,7 +411,7 @@ void sec_audit_logger(modsec_rec *msr) { * writing to but it's not us that's causing the problem * and there isn't anything we can do about that. * - * TODO Actually there is something we can do! We will make + * ENH Actually there is something we can do! We will make * SecAuditStorageDir mandatory, ask the user to explicitly * define the storage location *and* refuse to work if the * index and the storage location are in the same folder. @@ -413,12 +463,12 @@ void sec_audit_logger(modsec_rec *msr) { /* AUDITLOG_PART_HEADER */ - text = apr_psprintf(msr->mp, "--%s-A--\n", msr->new_auditlog_boundary); + text = apr_psprintf(msr->mp, "--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_HEADER); sec_auditlog_write(msr, text, strlen(text)); /* Format: time transaction_id remote_addr remote_port local_addr local_port */ - text = apr_psprintf(msr->mp, "[%s] %s %s %i %s %i", + text = apr_psprintf(msr->mp, "[%s] %s %s %u %s %u", current_logtime(msr->mp), msr->txid, msr->remote_addr, msr->remote_port, msr->local_addr, msr->local_port); sec_auditlog_write(msr, text, strlen(text)); @@ -427,7 +477,7 @@ void sec_audit_logger(modsec_rec *msr) { /* AUDITLOG_PART_REQUEST_HEADERS */ if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_REQUEST_HEADERS) != NULL) { - text = apr_psprintf(msr->mp, "\n--%s-B--\n", msr->new_auditlog_boundary); + text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_REQUEST_HEADERS); sec_auditlog_write(msr, text, strlen(text)); sanitise_request_line(msr); @@ -465,6 +515,7 @@ void sec_audit_logger(modsec_rec *msr) { unsigned int offset = 0, last_offset = 0; msc_arg *nextarg = NULL; int sanitise = 0; /* IMP1 Use constants for "sanitise" values. */ + char *my_error_msg = NULL; sorted_args = apr_array_make(msr->mp, 25, sizeof(const msc_arg *)); @@ -502,7 +553,7 @@ void sec_audit_logger(modsec_rec *msr) { } } } - + /* If we don't have the next argument that means * we're done here. */ @@ -521,20 +572,20 @@ void sec_audit_logger(modsec_rec *msr) { * sanitise data in pieces. */ - rc = modsecurity_request_body_retrieve_start(msr); + rc = modsecurity_request_body_retrieve_start(msr, &my_error_msg); if (rc < 0) { - msr_log(msr, 1, "Audit log: Failed retrieving request body."); + msr_log(msr, 1, "Audit log: %s", my_error_msg); } else { msc_data_chunk *chunk = NULL; unsigned int chunk_offset = 0; unsigned int sanitise_offset = 0; unsigned int sanitise_length = 0; - text = apr_psprintf(msr->mp, "\n--%s-C--\n", msr->new_auditlog_boundary); + text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_REQUEST_BODY); sec_auditlog_write(msr, text, strlen(text)); for(;;) { - rc = modsecurity_request_body_retrieve(msr, &chunk, -1); + rc = modsecurity_request_body_retrieve(msr, &chunk, -1, &my_error_msg); if (chunk != NULL) { /* Anything greater than 1 means we have more data to sanitise. */ while (sanitise > 1) { @@ -563,7 +614,7 @@ void sec_audit_logger(modsec_rec *msr) { unsigned int len; /* amount in this chunk to sanitise */ soff = sanitise_offset - chunk_offset; - + if (soff + sanitise_length <= chunk->length) { /* The entire argument resides in the current chunk. */ len = sanitise_length; @@ -594,7 +645,13 @@ void sec_audit_logger(modsec_rec *msr) { chunk_offset += chunk->length; } - if (rc <= 0) break; + if (rc <= 0) { + break; + } + } + + if (rc < 0) { + msr_log(msr, 1, "Audit log: %s", my_error_msg); } modsecurity_request_body_retrieve_end(msr); @@ -612,7 +669,7 @@ void sec_audit_logger(modsec_rec *msr) { if (buffer == NULL) { msr_log(msr, 1, "Audit log: Failed to reconstruct request body."); } else { - text = apr_psprintf(msr->mp, "\n--%s-I--\n", msr->new_auditlog_boundary); + text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_FAKE_REQUEST_BODY); sec_auditlog_write(msr, text, strlen(text)); sec_auditlog_write(msr, buffer, strlen(buffer)); } @@ -622,7 +679,7 @@ void sec_audit_logger(modsec_rec *msr) { /* AUDITLOG_PART_A_RESPONSE_HEADERS */ if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_A_RESPONSE_HEADERS) != NULL) { - text = apr_psprintf(msr->mp, "\n--%s-F--\n", msr->new_auditlog_boundary); + text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_A_RESPONSE_HEADERS); sec_auditlog_write(msr, text, strlen(text)); /* There are no response headers (or the status line) in HTTP 0.9 */ @@ -631,7 +688,7 @@ void sec_audit_logger(modsec_rec *msr) { text = apr_psprintf(msr->mp, "%s %s\n", msr->response_protocol, msr->status_line); } else { - text = apr_psprintf(msr->mp, "%s %i\n", msr->response_protocol, + text = apr_psprintf(msr->mp, "%s %u\n", msr->response_protocol, msr->response_status); } sec_auditlog_write(msr, text, strlen(text)); @@ -651,12 +708,12 @@ void sec_audit_logger(modsec_rec *msr) { } } } - + /* AUDITLOG_PART_RESPONSE_BODY */ if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_RESPONSE_BODY) != NULL) { if (msr->resbody_data != NULL) { - text = apr_psprintf(msr->mp, "\n--%s-E--\n", msr->new_auditlog_boundary); + text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_RESPONSE_BODY); sec_auditlog_write(msr, text, strlen(text)); sec_auditlog_write(msr, msr->resbody_data, msr->resbody_length); wrote_response_body = 1; @@ -668,7 +725,7 @@ void sec_audit_logger(modsec_rec *msr) { if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_TRAILER) != NULL) { apr_time_t now = apr_time_now(); - text = apr_psprintf(msr->mp, "\n--%s-H--\n", msr->new_auditlog_boundary); + text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_TRAILER); sec_auditlog_write(msr, text, strlen(text)); /* Messages */ @@ -676,7 +733,7 @@ void sec_audit_logger(modsec_rec *msr) { text = apr_psprintf(msr->mp, "Message: %s\n", ((char **)msr->alerts->elts)[i]); sec_auditlog_write(msr, text, strlen(text)); } - + /* Apache error messages */ for(i = 0; i < msr->error_messages->nelts; i++) { error_message *em = (((error_message**)msr->error_messages->elts)[i]); @@ -684,10 +741,10 @@ void sec_audit_logger(modsec_rec *msr) { format_error_log_message(msr->mp, em)); sec_auditlog_write(msr, text, strlen(text)); } - + /* Action */ if (msr->was_intercepted) { - text = apr_psprintf(msr->mp, "Action: Intercepted (phase %i)\n", msr->intercept_phase); + text = apr_psprintf(msr->mp, "Action: Intercepted (phase %d)\n", msr->intercept_phase); sec_auditlog_write(msr, text, strlen(text)); } @@ -726,7 +783,7 @@ void sec_audit_logger(modsec_rec *msr) { } sec_auditlog_write(msr, text, strlen(text)); - + /* Our response body does not contain chunks */ /* ENH Only write this when the output was chunked. */ /* ENH Add info when request body was decompressed, dechunked too. */ @@ -734,11 +791,9 @@ void sec_audit_logger(modsec_rec *msr) { text = apr_psprintf(msr->mp, "Response-Body-Transformed: Dechunked\n"); sec_auditlog_write(msr, text, strlen(text)); } - - /* Producer */ - text = apr_psprintf(msr->mp, "Producer: %s\n", MODULE_NAME_FULL); - sec_auditlog_write(msr, text, strlen(text)); - + + sec_auditlog_write_producer_header(msr); + /* Server */ if (msr->server_software != NULL) { text = apr_psprintf(msr->mp, "Server: %s\n", msr->server_software); @@ -818,10 +873,28 @@ void sec_audit_logger(modsec_rec *msr) { } } + /* AUDITLOG_PART_UPLOADS */ + /* ENH: Implement */ + + + /* AUDITLOG_PART_MATCHEDRULES */ + + if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_MATCHEDRULES) != NULL) { + text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_MATCHEDRULES); + sec_auditlog_write(msr, text, strlen(text)); + + /* Matched Rules */ + for(i = 0; i < msr->matched_rules->nelts; i++) { + rule = ((msre_rule **)msr->matched_rules->elts)[i]; + text = apr_psprintf(msr->mp, "%s\n", rule->unparsed); + sec_auditlog_write(msr, text, strlen(text)); + } + } + /* AUDITLOG_PART_ENDMARKER */ - text = apr_psprintf(msr->mp, "\n--%s-Z--\n", msr->new_auditlog_boundary); + text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_ENDMARKER); sec_auditlog_write(msr, text, strlen(text)); /* Return here if we were writing to a serial log @@ -841,7 +914,7 @@ void sec_audit_logger(modsec_rec *msr) { } /* From here on only concurrent-style processing. */ - + apr_file_close(msr->new_auditlog_fd); /* Write an entry to the index file */ @@ -849,10 +922,10 @@ void sec_audit_logger(modsec_rec *msr) { /* Calculate hash of the entry. */ apr_md5_final(md5hash, &msr->new_auditlog_md5ctx); - str2 = apr_psprintf(msr->mp, "%s %i %i md5:%s", msr->new_auditlog_filename, 0, + str2 = apr_psprintf(msr->mp, "%s %d %d md5:%s", msr->new_auditlog_filename, 0, msr->new_auditlog_size, bytes2hex(msr->mp, md5hash, 16)); if (str2 == NULL) return; - + /* We do not want the index line to be longer than 3980 bytes. */ limit = 3980; was_limited = 0; @@ -869,7 +942,7 @@ void sec_audit_logger(modsec_rec *msr) { limit = limit - strlen(str2) - 5; if (limit <= 0) { - msr_log(msr, 1, "Audit Log: Atomic PIPE write buffer too small: %i", PIPE_BUF); + msr_log(msr, 1, "Audit Log: Atomic PIPE write buffer too small: %d", PIPE_BUF); return; } @@ -885,14 +958,14 @@ void sec_audit_logger(modsec_rec *msr) { nbytes = strlen(text); if (msr->txcfg->debuglog_level >= 9) { - msr_log(msr, 9, "Audit Log: Writing %d bytes to primary concurrent index", nbytes); + msr_log(msr, 9, "Audit Log: Writing %" APR_SIZE_T_FMT " bytes to primary concurrent index", nbytes); } apr_file_write_full(msr->txcfg->auditlog_fd, text, nbytes, &nbytes_written); /* Write to the secondary audit log if we have one */ if (msr->txcfg->auditlog2_fd != NULL) { if (msr->txcfg->debuglog_level >= 9) { - msr_log(msr, 9, "Audit Log: Writing %d bytes to secondary concurrent index", nbytes); + msr_log(msr, 9, "Audit Log: Writing %" APR_SIZE_T_FMT " bytes to secondary concurrent index", nbytes); } apr_file_write_full(msr->txcfg->auditlog2_fd, text, nbytes, &nbytes_written); } diff --git a/apache2/msc_logging.h b/apache2/msc_logging.h index 6cfaa181..5b5cb92d 100644 --- a/apache2/msc_logging.h +++ b/apache2/msc_logging.h @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -28,7 +28,9 @@ #define AUDITLOG_PART_A_RESPONSE_BODY 'G' #define AUDITLOG_PART_TRAILER 'H' #define AUDITLOG_PART_FAKE_REQUEST_BODY 'I' -#define AUDITLOG_PART_LAST 'I' +#define AUDITLOG_PART_UPLOADS 'J' +#define AUDITLOG_PART_MATCHEDRULES 'K' +#define AUDITLOG_PART_LAST 'K' #define AUDITLOG_PART_ENDMARKER 'Z' #include "modsecurity.h" diff --git a/apache2/msc_lua.c b/apache2/msc_lua.c new file mode 100644 index 00000000..6eaae2bc --- /dev/null +++ b/apache2/msc_lua.c @@ -0,0 +1,413 @@ +/* + * ModSecurity for Apache 2.x, http://www.modsecurity.org/ + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) + * + * You should have received a copy of the licence along with this + * program (stored in the file "LICENSE"). If the file is missing, + * or if you have any other questions related to the licence, please + * write to Breach Security, Inc. at support@breach.com. + * + */ +#if defined(WITH_LUA) + +#include "msc_lua.h" + +#include "apr_strings.h" + +typedef struct { + apr_array_header_t *parts; + apr_pool_t *pool; +} msc_lua_dumpw_t; + +typedef struct { + msc_script *script; + int index; +} msc_lua_dumpr_t; + +/** + * + */ +static const char* dump_reader(lua_State* L, void* user_data, size_t* size) { + msc_lua_dumpr_t *dumpr = (msc_lua_dumpr_t *)user_data; + msc_script_part *part; + + /* Do we have more chunks to return? */ + if (dumpr->index == dumpr->script->parts->nelts) { + return NULL; + } + + /* Get one chunk. */ + part = ((msc_script_part **)dumpr->script->parts->elts)[dumpr->index]; + *size = part->len; + + dumpr->index++; + + return part->data; +} + +/** + * + */ +static int dump_writer(lua_State *L, const void* data, size_t len, void* user_data) { + msc_lua_dumpw_t *dump = (msc_lua_dumpw_t *)user_data; + msc_script_part *part; + + /* Allocate new part, copy the data into it. */ + part = apr_palloc(dump->pool, sizeof(msc_script_part)); + part->data = apr_palloc(dump->pool, len); + part->len = len; + memcpy((void *)part->data, data, len); + + /* Then add it to the list of parsts. */ + *(const msc_script_part **)apr_array_push(dump->parts) = part; + + return 0; +} + +/** + * + */ +static int lua_restore(lua_State *L, msc_script *script) { + msc_lua_dumpr_t dumpr; + + dumpr.script = script; + dumpr.index = 0; + + return lua_load(L, dump_reader, &dumpr, script->name); +} + +/** + * + */ +char *lua_compile(msc_script **script, const char *filename, apr_pool_t *pool) { + lua_State *L = NULL; + msc_lua_dumpw_t dump; + + /* Initialise state. */ + L = lua_open(); + luaL_openlibs(L); + + /* Find script. */ + if (luaL_loadfile(L, filename)) { + return apr_psprintf(pool, "ModSecurity: Failed to compile script %s: %s", + filename, lua_tostring(L, -1)); + } + + /* Dump the script into binary form. */ + dump.pool = pool; + dump.parts = apr_array_make(pool, 128, sizeof(msc_script_part *)); + + lua_dump(L, dump_writer, &dump); + + (*script) = apr_pcalloc(pool, sizeof(msc_script)); + (*script)->name = filename; + (*script)->parts = dump.parts; + + /* Destroy state. */ + lua_close(L); + + return NULL; +} + +/** + * + */ +static int l_log(lua_State *L) { + modsec_rec *msr = NULL; + const char *text; + int level; + + /* Retrieve parameters. */ + level = luaL_checknumber(L, 1); + text = luaL_checkstring(L, 2); + + /* Retrieve msr. */ + lua_getglobal(L, "__msr"); + msr = (modsec_rec *)lua_topointer(L, -1); + + /* Log message. */ + if (msr != NULL) { + msr_log(msr, level, "%s", text); + } + + return 0; +} + +/** + * + */ +static apr_array_header_t *resolve_tfns(lua_State *L, int idx, modsec_rec *msr, apr_pool_t *mp) { + apr_array_header_t *tfn_arr = NULL; + msre_tfn_metadata *tfn = NULL; + char *name = NULL; + + tfn_arr = apr_array_make(mp, 25, sizeof(msre_tfn_metadata *)); + if (tfn_arr == NULL) return NULL; + + /* ENH: Why is this userdata and not none/nil when parameter not given? */ + if (lua_isuserdata(L, idx) || lua_isnoneornil(L, idx)) { /* No second parameter */ + return tfn_arr; + } else if (lua_istable(L, idx)) { /* Is the second parameter an array? */ + int i, n = lua_objlen(L, idx); + + for(i = 1; i <= n; i++) { + lua_rawgeti(L, idx, i); + name = (char *)luaL_checkstring(L, -1); + + /* A "none" means start over */ + if (strcmp("none", name) == 0) { + tfn_arr->nelts = 0; + continue; + } + + tfn = msre_engine_tfn_resolve(msr->modsecurity->msre, name); + if (tfn == NULL) { + msr_log(msr, 1, "SecRuleScript: Invalid transformation function: %s", name); + } else { + *(msre_tfn_metadata **)apr_array_push(tfn_arr) = tfn; + } + } + } else if (lua_isstring(L, idx)) { /* The second parameter may be a simple string? */ + name = (char *)luaL_checkstring(L, idx); + + /* A "none" means start over */ + if (strcmp("none", name) == 0) { + tfn_arr->nelts = 0; + } + else { + tfn = msre_engine_tfn_resolve(msr->modsecurity->msre, name); + if (tfn == NULL) { + msr_log(msr, 1, "SecRuleScript: Invalid transformation function: %s", name); + } else { + *(msre_tfn_metadata **)apr_array_push(tfn_arr) = tfn; + } + } + } else { + msr_log(msr, 1, "SecRuleScript: Transformation parameter must be a transformation name or array of transformation names, but found \"%s\" (type %d).", lua_typename(L, idx), lua_type(L, idx)); + return NULL; + } + + return tfn_arr; +} + +/** + * + */ +static int l_getvar(lua_State *L) { + char *varname = NULL, *param = NULL; + modsec_rec *msr = NULL; + msre_rule *rule = NULL; + char *my_error_msg = NULL; + char *p1 = NULL; + apr_array_header_t *tfn_arr = NULL; + msre_var *vx = NULL; + msre_var *var; + + /* Retrieve parameters. */ + p1 = (char *)luaL_checkstring(L, 1); + + /* Retrieve msr. */ + lua_getglobal(L, "__msr"); + msr = (modsec_rec *)lua_topointer(L, -1); + + /* Retrieve rule. */ + lua_getglobal(L, "__rule"); + rule = (msre_rule *)lua_topointer(L, -1); + + /* Extract the variable name and its parameter from the script. */ + varname = apr_pstrdup(msr->msc_rule_mptmp, p1); + param = strchr(varname, '.'); + if (param != NULL) { + *param = '\0'; + param++; + } + + /* Resolve variable. */ + var = msre_create_var_ex(msr->msc_rule_mptmp, msr->modsecurity->msre, + varname, param, msr, &my_error_msg); + + if (var == NULL) { + msr_log(msr, 1, "%s", my_error_msg); + + lua_pushnil(L); + + return 0; + } + + /* Resolve transformation functions. */ + tfn_arr = resolve_tfns(L, 2, msr, msr->msc_rule_mptmp); + + /* Generate variable. */ + vx = generate_single_var(msr, var, tfn_arr, rule, msr->msc_rule_mptmp); + if (vx == NULL) { + lua_pushnil(L); + + return 0; + } + + /* Return variable value. */ + lua_pushlstring(L, vx->value, vx->value_len); + + return 1; +} + +/** + * + */ +static int l_getvars(lua_State *L) { + const apr_array_header_t *tarr; + const apr_table_entry_t *telts; + apr_table_t *vartable = NULL; + apr_array_header_t *tfn_arr = NULL; + char *varname = NULL, *param = NULL; + modsec_rec *msr = NULL; + msre_rule *rule = NULL; + msre_var *vartemplate = NULL; + char *my_error_msg = NULL; + char *p1 = NULL; + int i; + + /* Retrieve parameters. */ + p1 = (char *)luaL_checkstring(L, 1); + + /* Retrieve msr. */ + lua_getglobal(L, "__msr"); + msr = (modsec_rec *)lua_topointer(L, -1); + + /* Retrieve rule. */ + lua_getglobal(L, "__rule"); + rule = (msre_rule *)lua_topointer(L, -1); + + /* Extract the variable name and its parameter from the script. */ + varname = apr_pstrdup(msr->msc_rule_mptmp, p1); + param = strchr(varname, '.'); + if (param != NULL) { + *param = '\0'; + param++; + } + + /* Resolve transformation functions. */ + tfn_arr = resolve_tfns(L, 2, msr, msr->msc_rule_mptmp); + + lua_newtable(L); + + /* Resolve variable. */ + vartemplate = msre_create_var_ex(msr->msc_rule_mptmp, msr->modsecurity->msre, + varname, param, msr, &my_error_msg); + + if (vartemplate == NULL) { + msr_log(msr, 1, "%s", my_error_msg); + + /* Returning empty table. */ + return 1; + } + + vartable = generate_multi_var(msr, vartemplate, tfn_arr, rule, msr->msc_rule_mptmp); + + tarr = apr_table_elts(vartable); + telts = (const apr_table_entry_t*)tarr->elts; + for (i = 0; i < tarr->nelts; i++) { + msre_var *var = (msre_var *)telts[i].val; + + lua_pushnumber(L, i + 1); /* Index is not zero-based. */ + + lua_newtable(L); /* Per-parameter table. */ + + lua_pushstring(L, "name"); + lua_pushlstring(L, var->name, strlen(var->name)); + lua_settable(L, -3); + + lua_pushstring(L, "value"); + lua_pushlstring(L, var->value, var->value_len); + lua_settable(L, -3); + + lua_settable(L, -3); /* Push one parameter into the results table. */ + } + + return 1; +} + +static const struct luaL_Reg mylib[] = { + { "log", l_log }, + { "getvar", l_getvar }, + { "getvars", l_getvars }, + { NULL, NULL } +}; + +/** + * + */ +int lua_execute(msc_script *script, char *param, modsec_rec *msr, msre_rule *rule, char **error_msg) { + apr_time_t time_before; + lua_State *L = NULL; + int rc; + + if (error_msg == NULL) return -1; + *error_msg = NULL; + + if (msr->txcfg->debuglog_level >= 8) { + msr_log(msr, 8, "Lua: Executing script: %s", script->name); + } + + time_before = apr_time_now(); + + /* Create new state. */ + L = lua_open(); + + luaL_openlibs(L); + + /* Associate msr with the state. */ + lua_pushlightuserdata(L, (void *)msr); + lua_setglobal(L, "__msr"); + + /* Associate rule with the state. */ + if (rule != NULL) { + lua_pushlightuserdata(L, (void *)rule); + lua_setglobal(L, "__rule"); + } + + /* Register functions. */ + luaL_register(L, "m", mylib); + + rc = lua_restore(L, script); + if (rc) { + *error_msg = apr_psprintf(msr->mp, "Lua: Failed to restore script with %i.", rc); + return -1; + } + + /* Execute the chunk so that the functions are defined. */ + lua_pcall(L, 0, 0, 0); + + /* Execute main() */ + lua_getglobal(L, "main"); + + /* Put the parameter on the stack. */ + if (param != NULL) { + lua_pushlstring(L, param, strlen(param)); + } + + if (lua_pcall(L, ((param != NULL) ? 1 : 0), 1, 0)) { + *error_msg = apr_psprintf(msr->mp, "Lua: Script execution failed: %s", lua_tostring(L, -1)); + return -1; + } + + /* Get the response from the script. */ + *error_msg = (char *)lua_tostring(L, -1); + if (*error_msg != NULL) { + *error_msg = apr_pstrdup(msr->mp, *error_msg); + } + + /* Destroy state. */ + lua_pop(L, 1); + lua_close(L); + + /* Returns status code to caller. */ + if (msr->txcfg->debuglog_level >= 8) { + msr_log(msr, 8, "Lua: Script completed in %" APR_TIME_T_FMT " usec, returning: %s.", + (apr_time_now() - time_before), *error_msg); + } + + return ((*error_msg != NULL) ? RULE_MATCH : RULE_NO_MATCH); +} + +#endif /* WITH_LUA */ diff --git a/apache2/msc_lua.h b/apache2/msc_lua.h new file mode 100644 index 00000000..4f9ba937 --- /dev/null +++ b/apache2/msc_lua.h @@ -0,0 +1,43 @@ +/* + * ModSecurity for Apache 2.x, http://www.modsecurity.org/ + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) + * + * You should have received a copy of the licence along with this + * program (stored in the file "LICENSE"). If the file is missing, + * or if you have any other questions related to the licence, please + * write to Breach Security, Inc. at support@breach.com. + * + */ +#if defined(WITH_LUA) + +#ifndef _MSC_LUA_H_ +#define _MSC_LUA_H_ + +typedef struct msc_script msc_script; +typedef struct msc_script_part msc_script_part; + +#include +#include +#include + +#include "apr_general.h" +#include "apr_tables.h" +#include "modsecurity.h" + +struct msc_script { + const char *name; + apr_array_header_t *parts; +}; + +struct msc_script_part { + const void *data; + size_t len; +}; + +char DSOLOCAL *lua_compile(msc_script **script, const char *filename, apr_pool_t *pool); + +int DSOLOCAL lua_execute(msc_script *script, char *param, modsec_rec *msr, msre_rule *rule, char **error_msg); + +#endif + +#endif /* WITH_LUA */ diff --git a/apache2/msc_multipart.c b/apache2/msc_multipart.c index f953bd90..b8f5fe9b 100644 --- a/apache2/msc_multipart.c +++ b/apache2/msc_multipart.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -9,9 +9,12 @@ * */ #include +#include +#include "mod_security2_config.h" #include "msc_multipart.h" #include "msc_util.h" +#include "msc_parsers.h" #if 0 @@ -47,55 +50,55 @@ static char *multipart_construct_filename(modsec_rec *msr) { */ static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value) { char *p = NULL, *t = NULL; - + /* accept only what we understand */ if (strncmp(c_d_value, "form-data", 9) != 0) { return -1; } - + /* see if there are any other parts to parse */ - + p = c_d_value + 9; while((*p == '\t')||(*p == ' ')) p++; if (*p == '\0') return 1; /* this is OK */ - + if (*p != ';') return -2; p++; - + /* parse the appended parts */ - + while(*p != '\0') { char *name = NULL, *value = NULL, *start = NULL; - + /* go over the whitespace */ while((*p == '\t')||(*p == ' ')) p++; if (*p == '\0') return -3; - + start = p; while((*p != '\0')&&(*p != '=')&&(*p != '\t')&&(*p != ' ')) p++; if (*p == '\0') return -4; - + name = apr_pstrmemdup(msr->mp, start, (p - start)); - + while((*p == '\t')||(*p == ' ')) p++; if (*p == '\0') return -5; - if (*p != '=') return -13; + if (*p != '=') return -13; p++; - + while((*p == '\t')||(*p == ' ')) p++; if (*p == '\0') return -6; - + if (*p == '"') { /* quoted */ - + p++; if (*p == '\0') return -7; - + start = p; value = apr_pstrdup(msr->mp, p); t = value; - + while(*p != '\0') { if (*p == '\\') { if (*(p + 1) == '\0') { @@ -108,7 +111,7 @@ static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value) } else { /* improper escaping */ - + /* We allow for now because IE sends * improperly escaped content and there's * nothing we can do about it. @@ -122,49 +125,55 @@ static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value) *t = '\0'; break; } - - *t++ = *p++; + + *(t++) = *(p++); } if (*p == '\0') return -10; - + p++; /* go over the quote at the end */ - + } else { /* not quoted */ - + start = p; while((*p != '\0')&&(is_token_char(*p))) p++; value = apr_pstrmemdup(msr->mp, start, (p - start)); } - + /* evaluate part */ - + if (strcmp(name, "name") == 0) { if (msr->mpd->mpp->name != NULL) return -14; msr->mpd->mpp->name = value; - msr_log(msr, 9, "Multipart: Content-Disposition name: %s", - log_escape_nq(msr->mp, value)); + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Multipart: Content-Disposition name: %s", + log_escape_nq(msr->mp, value)); + } } else if (strcmp(name, "filename") == 0) { if (msr->mpd->mpp->filename != NULL) return -15; msr->mpd->mpp->filename = value; - msr_log(msr, 9, "Multipart: Content-Disposition filename: %s", - log_escape_nq(msr->mp, value)); + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Multipart: Content-Disposition filename: %s", + log_escape_nq(msr->mp, value)); + } } else return -11; - + if (*p != '\0') { while((*p == '\t')||(*p == ' ')) p++; - /* the next character must be a zero or a semi-colon */ + /* the next character must be a zero or a semi-colon */ if (*p == '\0') return 1; /* this is OK */ if (*p != ';') return -12; p++; /* move over the semi-colon */ } - + /* loop will stop when (*p == '\0') */ } - + return 1; } @@ -172,115 +181,153 @@ static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value) * */ static int multipart_process_part_header(modsec_rec *msr, char **error_msg) { - int rc; - + int i, len, rc; + if (error_msg == NULL) return -1; *error_msg = NULL; - if ((msr->mpd->buf[0] == '\r') - &&(msr->mpd->buf[1] == '\n') - &&(msr->mpd->buf[2] == '\0')) - { - char *header_value; - - /* empty line */ + /* Check for nul bytes. */ + len = MULTIPART_BUF_SIZE - msr->mpd->bufleft; + for(i = 0; i < len; i++) { + if (msr->mpd->buf[i] == '\0') { + *error_msg = apr_psprintf(msr->mp, "Multipart: Nul byte in part headers."); + return -1; + } + } + + /* The buffer is data so increase the data length counter. */ + msr->msc_reqbody_no_files_length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft); + + if (len > 1) { + if (msr->mpd->buf[len - 2] == '\r') { + msr->mpd->flag_crlf_line = 1; + } else { + msr->mpd->flag_lf_line = 1; + } + } else { + msr->mpd->flag_lf_line = 1; + } + + /* Is this an empty line? */ + if ( ((msr->mpd->buf[0] == '\r') + &&(msr->mpd->buf[1] == '\n') + &&(msr->mpd->buf[2] == '\0') ) + || ((msr->mpd->buf[0] == '\n') + &&(msr->mpd->buf[1] == '\0') ) ) + { /* Empty line. */ + char *header_value = NULL; header_value = (char *)apr_table_get(msr->mpd->mpp->headers, "Content-Disposition"); if (header_value == NULL) { - *error_msg = apr_psprintf(msr->mp, "Multipart: Part is missing the Content-Disposition header"); + *error_msg = apr_psprintf(msr->mp, "Multipart: Part missing Content-Disposition header."); return -1; } - + rc = multipart_parse_content_disposition(msr, header_value); if (rc < 0) { - *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid Content-Disposition header (%i): %s", + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid Content-Disposition header (%d): %s.", rc, log_escape_nq(msr->mp, header_value)); return -1; } if (msr->mpd->mpp->name == NULL) { - *error_msg = apr_psprintf(msr->mp, "Multipart: Part name missing"); + *error_msg = apr_psprintf(msr->mp, "Multipart: Content-Disposition header missing name field."); return -1; - } - + } + if (msr->mpd->mpp->filename != NULL) { + /* Some parsers use crude methods to extract the name and filename + * values from the C-D header. We need to check for the case where they + * didn't understand C-D but we did. + */ + if (strstr(header_value, "filename=") == NULL) { + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid Content-Disposition header (filename)."); + return -1; + } + msr->mpd->mpp->type = MULTIPART_FILE; } else { msr->mpd->mpp->type = MULTIPART_FORMDATA; } - + msr->mpd->mpp_state = 1; msr->mpd->mpp->last_header_name = NULL; } else { - /* header line */ + /* Header line. */ + if ((msr->mpd->buf[0] == '\t')||(msr->mpd->buf[0] == ' ')) { char *header_value, *new_value, *data; - + /* header folding, add data to the header we are building */ - + msr->mpd->flag_header_folding = 1; + if (msr->mpd->mpp->last_header_name == NULL) { /* we are not building a header at this moment */ - *error_msg = apr_psprintf(msr->mp, "Multipart: invalid part header (invalid folding)"); + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid part header (folding error)."); return -1; } - - /* locate the beginning of the data */ + + /* locate the beginning of data */ data = msr->mpd->buf; while((*data == '\t')||(*data == ' ')) data++; - + new_value = apr_pstrdup(msr->mp, data); remove_lf_crlf_inplace(new_value); - + /* update the header value in the table */ header_value = (char *)apr_table_get(msr->mpd->mpp->headers, msr->mpd->mpp->last_header_name); new_value = apr_pstrcat(msr->mp, header_value, " ", new_value, NULL); apr_table_set(msr->mpd->mpp->headers, msr->mpd->mpp->last_header_name, new_value); - - msr_log(msr, 9, "Multipart: Continued folder header \"%s\" with \"%s\"", - log_escape(msr->mp, msr->mpd->mpp->last_header_name), - log_escape(msr->mp, data)); - - if (strlen(new_value) > 4096) { - *error_msg = apr_psprintf(msr->mp, "Multpart: invalid part header (too long)"); + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Multipart: Continued folder header \"%s\" with \"%s\"", + log_escape(msr->mp, msr->mpd->mpp->last_header_name), + log_escape(msr->mp, data)); + } + + if (strlen(new_value) > MULTIPART_BUF_SIZE) { + *error_msg = apr_psprintf(msr->mp, "Multipart: Part header too long."); return -1; } } else { char *header_name, *header_value, *data; - + /* new header */ - + data = msr->mpd->buf; while((*data != ':')&&(*data != '\0')) data++; if (*data == '\0') { - *error_msg = apr_psprintf(msr->mp, "Multipart: invalid part header (missing colon): %s", + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid part header (colon missing): %s.", log_escape_nq(msr->mp, msr->mpd->buf)); return -1; } header_name = apr_pstrmemdup(msr->mp, msr->mpd->buf, (data - msr->mpd->buf)); - + /* extract the value value */ data++; while((*data == '\t')||(*data == ' ')) data++; header_value = apr_pstrdup(msr->mp, data); remove_lf_crlf_inplace(header_value); - + /* error if the name already exists */ if (apr_table_get(msr->mpd->mpp->headers, header_name) != NULL) { - *error_msg = apr_psprintf(msr->mp, "Multipart: part header already exists: %s", + *error_msg = apr_psprintf(msr->mp, "Multipart: Duplicate part header: %s.", log_escape_nq(msr->mp, header_name)); return -1; } - + apr_table_setn(msr->mpd->mpp->headers, header_name, header_value); msr->mpd->mpp->last_header_name = header_name; - - msr_log(msr, 9, "Multipart: Added part header \"%s\" \"%s\"", - log_escape(msr->mp, header_name), - log_escape(msr->mp, header_value)); + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Multipart: Added part header \"%s\" \"%s\"", + log_escape(msr->mp, header_name), + log_escape(msr->mp, header_value)); + } } } - + return 1; } @@ -288,20 +335,34 @@ static int multipart_process_part_header(modsec_rec *msr, char **error_msg) { * */ static int multipart_process_part_data(modsec_rec *msr, char **error_msg) { - char *p = msr->mpd->buf + (MULTIPART_BUF_SIZE - msr->mpd->bufleft) - 2; + char *p = msr->mpd->buf + (MULTIPART_BUF_SIZE - msr->mpd->bufleft); char localreserve[2] = { '\0', '\0' }; /* initialized to quiet warning */ int bytes_reserved = 0; - + if (error_msg == NULL) return -1; *error_msg = NULL; - - /* preserve the last two bytes for later */ - if (MULTIPART_BUF_SIZE - msr->mpd->bufleft >= 2) { - bytes_reserved = 1; - localreserve[0] = *p; - localreserve[1] = *(p + 1); - msr->mpd->bufleft += 2; - *p = 0; + + /* Preserve some bytes for later. */ + if ( ((MULTIPART_BUF_SIZE - msr->mpd->bufleft) >= 1) + && (*(p - 1) == '\n') ) + { + if ( ((MULTIPART_BUF_SIZE - msr->mpd->bufleft) >= 2) + && (*(p - 2) == '\r') ) + { + /* Two bytes. */ + bytes_reserved = 2; + localreserve[0] = *(p - 2); + localreserve[1] = *(p - 1); + msr->mpd->bufleft += 2; + *(p - 2) = 0; + } else { + /* Only one byte. */ + bytes_reserved = 1; + localreserve[0] = *(p - 1); + localreserve[1] = 0; + msr->mpd->bufleft += 1; + *(p - 1) = 0; + } } /* add data to the part we are building */ @@ -318,8 +379,6 @@ static int multipart_process_part_data(modsec_rec *msr, char **error_msg) { if (msr->upload_extract_files) { /* first create a temporary file if we don't have it already */ if (msr->mpd->mpp->tmp_file_fd == 0) { - // char *filename = multipart_construct_filename(msr); - /* construct temporary file name */ msr->mpd->mpp->tmp_file_name = apr_psprintf(msr->mp, "%s/%s-%s-file-XXXXXX", msr->txcfg->tmp_dir, current_filetime(msr->mp), msr->txid); @@ -332,19 +391,37 @@ static int multipart_process_part_data(modsec_rec *msr, char **error_msg) { return -1; } - msr_log(msr, 4, "Multipart: Created temporary file: %s", - log_escape_nq(msr->mp, msr->mpd->mpp->tmp_file_name)); + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Multipart: Created temporary file: %s", + log_escape_nq(msr->mp, msr->mpd->mpp->tmp_file_name)); + } + + #ifdef HAVE_FCHMOD + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Multipart: Changing file mode to %04o: %s", msr->txcfg->upload_filemode, log_escape_nq(msr->mp, msr->mpd->mpp->tmp_file_name)); + } + if (fchmod(msr->mpd->mpp->tmp_file_fd, msr->txcfg->upload_filemode) < 0) { + + char errbuf[256]; + if (msr->txcfg->debuglog_level >= 3) { + msr_log(msr, 3, "Multipart: Could not change mode on \"%s\" (%d): %s", + log_escape_nq(msr->mp, msr->mpd->mpp->tmp_file_name), + errno, apr_strerror(APR_FROM_OS_ERROR(errno), errbuf, 256)); + } + } + #endif } /* write the reserve first */ - if (msr->mpd->reserve[0] == 1) { - if (write(msr->mpd->mpp->tmp_file_fd, &msr->mpd->reserve[1], 2) != 2) { + if (msr->mpd->reserve[0] != 0) { + if (write(msr->mpd->mpp->tmp_file_fd, &msr->mpd->reserve[1], msr->mpd->reserve[0]) != msr->mpd->reserve[0]) { *error_msg = apr_psprintf(msr->mp, "Multipart: writing to \"%s\" failed", log_escape(msr->mp, msr->mpd->mpp->tmp_file_name)); return -1; } - msr->mpd->mpp->tmp_file_size += 2; - msr->mpd->mpp->length += 2; + + msr->mpd->mpp->tmp_file_size += msr->mpd->reserve[0]; + msr->mpd->mpp->length += msr->mpd->reserve[0]; } /* write data to the file */ @@ -355,47 +432,50 @@ static int multipart_process_part_data(modsec_rec *msr, char **error_msg) { log_escape(msr->mp, msr->mpd->mpp->tmp_file_name)); return -1; } - + msr->mpd->mpp->tmp_file_size += (MULTIPART_BUF_SIZE - msr->mpd->bufleft); msr->mpd->mpp->length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft); } else { /* just keep track of the file size */ - if (msr->mpd->reserve[0] == 1) { - msr->mpd->mpp->tmp_file_size += 2; - } - msr->mpd->mpp->tmp_file_size += (MULTIPART_BUF_SIZE - msr->mpd->bufleft); - msr->mpd->mpp->length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft); + msr->mpd->mpp->tmp_file_size += (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0]; + msr->mpd->mpp->length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0]; } } else if (msr->mpd->mpp->type == MULTIPART_FORMDATA) { value_part_t *value_part = apr_pcalloc(msr->mp, sizeof(value_part_t)); - + + /* The buffer contains data so increase the data length counter. */ + msr->msc_reqbody_no_files_length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0]; + /* add this part to the list of parts */ /* remember where we started */ if (msr->mpd->mpp->length == 0) { msr->mpd->mpp->offset = msr->mpd->buf_offset; } - - if (msr->mpd->reserve[0] == 1) { - value_part->data = apr_palloc(msr->mp, (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + 2); - memcpy(value_part->data, &(msr->mpd->reserve[1]), 2); - memcpy(value_part->data + 2, msr->mpd->buf, (MULTIPART_BUF_SIZE - msr->mpd->bufleft)); - value_part->length = (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + 2; + + if (msr->mpd->reserve[0] != 0) { + value_part->data = apr_palloc(msr->mp, (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0]); + memcpy(value_part->data, &(msr->mpd->reserve[1]), msr->mpd->reserve[0]); + memcpy(value_part->data + msr->mpd->reserve[0], msr->mpd->buf, (MULTIPART_BUF_SIZE - msr->mpd->bufleft)); + + value_part->length = (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0]; msr->mpd->mpp->length += value_part->length; } else { value_part->length = (MULTIPART_BUF_SIZE - msr->mpd->bufleft); value_part->data = apr_pstrmemdup(msr->mp, msr->mpd->buf, value_part->length); msr->mpd->mpp->length += value_part->length; } - + *(value_part_t **)apr_array_push(msr->mpd->mpp->value_parts) = value_part; - msr_log(msr, 9, "Multipart: Added data to variable: %s", - log_escape_nq_ex(msr->mp, value_part->data, value_part->length)); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Multipart: Added data to variable: %s", + log_escape_nq_ex(msr->mp, value_part->data, value_part->length)); + } } else { - *error_msg = apr_psprintf(msr->mp, "Multipart: unknown part type %i", msr->mpd->mpp->type); + *error_msg = apr_psprintf(msr->mp, "Multipart: unknown part type %d", msr->mpd->mpp->type); return -1; } @@ -403,16 +483,16 @@ static int multipart_process_part_data(modsec_rec *msr, char **error_msg) { * context so that they don't get lost */ if (bytes_reserved) { - msr->mpd->reserve[0] = 1; + msr->mpd->reserve[0] = bytes_reserved; msr->mpd->reserve[1] = localreserve[0]; msr->mpd->reserve[2] = localreserve[1]; - msr->mpd->buf_offset += 2; + msr->mpd->buf_offset += bytes_reserved; } else { + msr->mpd->buf_offset -= msr->mpd->reserve[0]; msr->mpd->reserve[0] = 0; - msr->mpd->buf_offset -= 2; } - + return 1; } @@ -458,22 +538,27 @@ static int multipart_process_boundary(modsec_rec *msr, int last_part, char **err /* now construct a single string out of the parts */ msr->mpd->mpp->value = multipart_combine_value_parts(msr, msr->mpd->mpp->value_parts); if (msr->mpd->mpp->value == NULL) return -1; - } + } /* add the part to the list of parts */ *(multipart_part **)apr_array_push(msr->mpd->parts) = msr->mpd->mpp; if (msr->mpd->mpp->type == MULTIPART_FILE) { - msr_log(msr, 9, "Multipart: Added file part %x to the list: name \"%s\" " - "file name \"%s\" (offset %u, length %u)", - msr->mpd->mpp, log_escape(msr->mp, msr->mpd->mpp->name), - log_escape(msr->mp, msr->mpd->mpp->filename), - msr->mpd->mpp->offset, msr->mpd->mpp->length); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Multipart: Added file part %pp to the list: name \"%s\" " + "file name \"%s\" (offset %u, length %u)", + msr->mpd->mpp, log_escape(msr->mp, msr->mpd->mpp->name), + log_escape(msr->mp, msr->mpd->mpp->filename), + msr->mpd->mpp->offset, msr->mpd->mpp->length); + } } else { - msr_log(msr, 9, "Multipart: Added part %x to the list: name \"%s\" " - "(offset %u, length %u)", msr->mpd->mpp, log_escape(msr->mp, msr->mpd->mpp->name), - msr->mpd->mpp->offset, msr->mpd->mpp->length); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Multipart: Added part %pp to the list: name \"%s\" " + "(offset %u, length %u)", msr->mpd->mpp, log_escape(msr->mp, msr->mpd->mpp->name), + msr->mpd->mpp->offset, msr->mpd->mpp->length); + } } + msr->mpd->mpp = NULL; } @@ -483,7 +568,7 @@ static int multipart_process_boundary(modsec_rec *msr, int last_part, char **err if (msr->mpd->mpp == NULL) return -1; msr->mpd->mpp->type = MULTIPART_FORMDATA; msr->mpd->mpp_state = 0; - + msr->mpd->mpp->headers = apr_table_make(msr->mp, 10); if (msr->mpd->mpp->headers == NULL) return -1; msr->mpd->mpp->last_header_name = NULL; @@ -492,13 +577,84 @@ static int multipart_process_boundary(modsec_rec *msr, int last_part, char **err msr->mpd->reserve[1] = 0; msr->mpd->reserve[2] = 0; msr->mpd->reserve[3] = 0; - + msr->mpd->mpp->value_parts = apr_array_make(msr->mp, 10, sizeof(value_part_t *)); } return 1; } +static int multipart_boundary_characters_valid(char *boundary) { + unsigned char *p = (unsigned char *)boundary; + unsigned char c; + + if (p == NULL) return -1; + + while((c = *p) != '\0') { + /* Control characters and space not allowed. */ + if (c < 32) { + return 0; + } + + /* Non-ASCII characters not allowed. */ + if (c > 126) { + return 0; + } + + switch(c) { + /* Special characters not allowed. */ + case '(' : + case ')' : + case '<' : + case '>' : + case '@' : + case ',' : + case ';' : + case ':' : + case '\\' : + case '"' : + case '/' : + case '[' : + case ']' : + case '?' : + case '=' : + return 0; + break; + + default : + /* Do nothing. */ + break; + } + + p++; + } + + return 1; +} + +static int multipart_count_boundary_params(apr_pool_t *mp, const char *header_value) { + char *duplicate = NULL; + char *s = NULL; + int count = 0; + + if (header_value == NULL) return -1; + duplicate = apr_pstrdup(mp, header_value); + if (duplicate == NULL) return -1; + + /* Performing a case-insensitive search. */ + strtolower_inplace((unsigned char *)duplicate); + + s = duplicate; + while((s = strstr(s, "boundary")) != NULL) { + s += 8; + + if (strchr(s, '=') != NULL) { + count++; + } + } + + return count; +} /** * @@ -510,26 +666,156 @@ int multipart_init(modsec_rec *msr, char **error_msg) { msr->mpd = (multipart_data *)apr_pcalloc(msr->mp, sizeof(multipart_data)); if (msr->mpd == NULL) return -1; - if (msr->request_content_type == NULL) { - *error_msg = apr_psprintf(msr->mp, "Multipart: Content-Type header not available."); - return -1; - } - - msr->mpd->boundary = strstr(msr->request_content_type, "boundary="); - if ((msr->mpd->boundary != NULL)&&(*(msr->mpd->boundary + 9) != 0)) { - msr->mpd->boundary = msr->mpd->boundary + 9; - } - else { - *error_msg = apr_psprintf(msr->mp, "Multipart Boundary not found or invalid."); - return -1; - } - msr->mpd->parts = apr_array_make(msr->mp, 10, sizeof(multipart_part *)); msr->mpd->bufleft = MULTIPART_BUF_SIZE; msr->mpd->bufptr = msr->mpd->buf; msr->mpd->buf_contains_line = 1; msr->mpd->mpp = NULL; + if (msr->request_content_type == NULL) { + msr->mpd->flag_error = 1; + *error_msg = apr_psprintf(msr->mp, "Multipart: Content-Type header not available."); + return -1; + } + + if (strlen(msr->request_content_type) > 1024) { + msr->mpd->flag_error = 1; + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (length)."); + return -1; + } + + if (strncasecmp(msr->request_content_type, "multipart/form-data", 19) != 0) { + msr->mpd->flag_error = 1; + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid MIME type."); + return -1; + } + + /* Count how many times the word "boundary" appears in the C-T header. */ + if (multipart_count_boundary_params(msr->mp, msr->request_content_type) > 1) { + msr->mpd->flag_error = 1; + *error_msg = apr_psprintf(msr->mp, "Multipart: Multiple boundary parameters in C-T."); + return -1; + } + + msr->mpd->boundary = strstr(msr->request_content_type, "boundary"); + if (msr->mpd->boundary != NULL) { + char *p = NULL; + char *b = NULL; + int seen_semicolon = 0; + int len = 0; + + /* Check for extra characters before the boundary. */ + for (p = (char *)(msr->request_content_type + 19); p < msr->mpd->boundary; p++) { + if (!isspace(*p)) { + if ((seen_semicolon == 0)&&(*p == ';')) { + seen_semicolon = 1; /* It is OK to have one semicolon. */ + } else { + msr->mpd->flag_error = 1; + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (malformed)."); + return -1; + } + } + } + + /* Have we seen the semicolon in the header? */ + if (seen_semicolon == 0) { + msr->mpd->flag_missing_semicolon = 1; + } + + b = strchr(msr->mpd->boundary + 8, '='); + if (b == NULL) { + msr->mpd->flag_error = 1; + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (malformed)."); + return -1; + } + + /* Check parameter name ends well. */ + if (b != (msr->mpd->boundary + 8)) { + /* Check all characters between the end of the boundary + * and the = character. + */ + for (p = msr->mpd->boundary + 8; p < b; p++) { + if (isspace(*p)) { + /* Flag for whitespace after parameter name. */ + msr->mpd->flag_boundary_whitespace = 1; + } else { + msr->mpd->flag_error = 1; + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (parameter name)."); + return -1; + } + } + } + + b++; /* Go over the = character. */ + len = strlen(b); + + /* Flag for whitespace before parameter value. */ + if (isspace(*b)) { + msr->mpd->flag_boundary_whitespace = 1; + } + + /* Is the boundary quoted? */ + if ((len >= 2)&&(*b == '"')&&(*(b + len - 1) == '"')) { + /* Quoted. */ + msr->mpd->boundary = apr_pstrndup(msr->mp, b + 1, len - 2); + if (msr->mpd->boundary == NULL) return -1; + msr->mpd->flag_boundary_quoted = 1; + } else { + /* Not quoted. */ + + /* Test for partial quoting. */ + if ( (*b == '"') + || ((len >= 2)&&(*(b + len - 1) == '"')) ) + { + msr->mpd->flag_error = 1; + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (quote)."); + return -1; + } + + msr->mpd->boundary = apr_pstrdup(msr->mp, b); + if (msr->mpd->boundary == NULL) return -1; + msr->mpd->flag_boundary_quoted = 0; + } + + /* Case-insensitive test for the string "boundary" in the boundary. */ + if (multipart_count_boundary_params(msr->mp, msr->mpd->boundary) != 0) { + msr->mpd->flag_error = 1; + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (content)."); + return -1; + } + + /* Validate the characters used in the boundary. */ + if (multipart_boundary_characters_valid(msr->mpd->boundary) != 1) { + msr->mpd->flag_error = 1; + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (characters)."); + return -1; + } + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Multipart: Boundary%s: %s", + (msr->mpd->flag_boundary_quoted ? " (quoted)" : ""), + log_escape_nq(msr->mp, msr->mpd->boundary)); + } + + if (strlen(msr->mpd->boundary) == 0) { + msr->mpd->flag_error = 1; + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (empty)."); + return -1; + } + } + else { /* Could not find boundary in the C-T header. */ + msr->mpd->flag_error = 1; + + /* Test for case-insensitive boundary. Allowed by the RFC but highly unusual. */ + if (multipart_count_boundary_params(msr->mp, msr->request_content_type) > 0) { + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (case sensitivity)."); + return -1; + } + + *error_msg = apr_psprintf(msr->mp, "Multipart: Boundary not found in C-T."); + return -1; + } + return 1; } @@ -540,7 +826,11 @@ int multipart_complete(modsec_rec *msr, char **error_log) { if (msr->mpd == NULL) return 1; if ((msr->mpd->seen_data != 0)&&(msr->mpd->is_complete == 0)) { - *error_log = apr_psprintf(msr->mp, "Multipart: final boundary missing"); + if (msr->mpd->boundary_count > 0) { + *error_log = apr_psprintf(msr->mp, "Multipart: Final boundary missing."); + } else { + *error_log = apr_psprintf(msr->mp, "Multipart: No boundaries found in payload."); + } return -1; } @@ -555,32 +845,38 @@ int multipart_process_chunk(modsec_rec *msr, const char *buf, { char *inptr = (char *)buf; unsigned int inleft = size; - + if (error_msg == NULL) return -1; *error_msg = NULL; if (size == 0) return 1; - if (msr->mpd->seen_data == 0) msr->mpd->seen_data = 1; - + msr->mpd->seen_data = 1; + if (msr->mpd->is_complete) { - msr_log(msr, 4, "Multipart: Ignoring data after last boundary (received %i bytes)", size); + msr->mpd->flag_data_before = 1; + + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Multipart: Ignoring data after last boundary (received %u bytes)", size); + } + return 1; } - + if (msr->mpd->bufleft == 0) { + msr->mpd->flag_error = 1; *error_msg = apr_psprintf(msr->mp, "Multipart: Internal error in process_chunk: no space left in the buffer"); return -1; } - /* here we loop through the data available, byte by byte */ + /* here we loop through the available data, one byte at a time */ while(inleft > 0) { char c = *inptr; int process_buffer = 0; - if ((c == 0x0d)&&(msr->mpd->bufleft == 1)) { - /* we don't want to take 0x0d as the last byte in the buffer */ + if ((c == '\r')&&(msr->mpd->bufleft == 1)) { + /* we don't want to take \r as the last byte in the buffer */ process_buffer = 1; } else { inptr++; @@ -594,63 +890,149 @@ int multipart_process_chunk(modsec_rec *msr, const char *buf, /* until we either reach the end of the line * or the end of our internal buffer */ - if ((c == 0x0a)||(msr->mpd->bufleft == 0)||(process_buffer)) { + if ((c == '\n')||(msr->mpd->bufleft == 0)||(process_buffer)) { + int processed_as_boundary = 0; + *(msr->mpd->bufptr) = 0; - /* boundary preconditions: length of the line greater than - * the length of the boundary + the first two characters - * are dashes "-" - */ - if ( msr->mpd->buf_contains_line - && (strlen(msr->mpd->buf) > strlen(msr->mpd->boundary) + 2) - && (((*(msr->mpd->buf) == '-'))&&(*(msr->mpd->buf + 1) == '-')) - && (strncmp(msr->mpd->buf + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) ) { - - char *boundary_end = msr->mpd->buf + 2 + strlen(msr->mpd->boundary); - - if ( (*boundary_end == '\r') - &&(*(boundary_end + 1) == '\n') - &&(*(boundary_end + 2) == '\0') - ) { - /* simple boundary */ - if (multipart_process_boundary(msr, 0, error_msg) < 0) return -1; + /* Do we have something that looks like a boundary? */ + if (msr->mpd->buf_contains_line + && (strlen(msr->mpd->buf) > 3) + && (((*(msr->mpd->buf) == '-'))&&(*(msr->mpd->buf + 1) == '-')) ) + { + /* Does it match our boundary? */ + if ((strlen(msr->mpd->buf) >= strlen(msr->mpd->boundary) + 2) + && (strncmp(msr->mpd->buf + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) ) + { + char *boundary_end = msr->mpd->buf + 2 + strlen(msr->mpd->boundary); + int is_final = 0; + + /* Is this the final boundary? */ + if ((*boundary_end == '-')&&(*(boundary_end + 1)== '-')) { + is_final = 1; + boundary_end += 2; + + if (msr->mpd->is_complete != 0) { + msr->mpd->flag_error = 1; + *error_msg = apr_psprintf(msr->mp, + "Multipart: Invalid boundary (final duplicate)."); + return -1; + } + } + + /* Allow for CRLF and LF line endings. */ + if ( ( (*boundary_end == '\r') + && (*(boundary_end + 1) == '\n') + && (*(boundary_end + 2) == '\0') ) + || ( (*boundary_end == '\n') + && (*(boundary_end + 1) == '\0') ) ) + { + if (*boundary_end == '\n') { + msr->mpd->flag_lf_line = 1; + } else { + msr->mpd->flag_crlf_line = 1; + } + + if (multipart_process_boundary(msr, (is_final ? 1 : 0), error_msg) < 0) { + msr->mpd->flag_error = 1; + return -1; + } + + if (is_final) { + msr->mpd->is_complete = 1; + } + + processed_as_boundary = 1; + msr->mpd->boundary_count++; + } + else { + /* error */ + msr->mpd->flag_error = 1; + *error_msg = apr_psprintf(msr->mp, + "Multipart: Invalid boundary: %s", + log_escape_nq(msr->mp, msr->mpd->buf)); + return -1; + } + } else { /* It looks like a boundary but we couldn't match it. */ + char *p = NULL; + + /* Check if an attempt to use quotes around the boundary was made. */ + if ( (msr->mpd->flag_boundary_quoted) + && (strlen(msr->mpd->buf) >= strlen(msr->mpd->boundary) + 3) + && (*(msr->mpd->buf + 2) == '"') + && (strncmp(msr->mpd->buf + 3, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) + ) { + msr->mpd->flag_error = 1; + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary (quotes)."); + return -1; + } + + /* Check the beginning of the boundary for whitespace. */ + p = msr->mpd->buf + 2; + while(isspace(*p)) { + p++; + } + + if ( (p != msr->mpd->buf + 2) + && (strncmp(p, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) + ) { + /* Found whitespace in front of a boundary. */ + msr->mpd->flag_error = 1; + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary (whitespace)."); + return -1; + } + + msr->mpd->flag_unmatched_boundary = 1; } - else - if ( (*boundary_end == '-') - &&(*(boundary_end + 1) == '-') - &&(*(boundary_end + 2) == '\r') - &&(*(boundary_end + 3) == '\n') - &&(*(boundary_end + 4) == '\0') - ) { - /* final boundary */ - msr->mpd->is_complete = 1; - if (multipart_process_boundary(msr, 1, error_msg) < 0) return -1; - } - else { - /* error */ - *error_msg = apr_psprintf(msr->mp, - "Multipart: Invalid boundary detected: %s", - log_escape_nq(msr->mp, msr->mpd->buf)); - return -1; + } else { /* We do not think the buffer contains a boundary. */ + /* Look into the buffer to see if there's anything + * there that resembles a boundary. + */ + if (msr->mpd->buf_contains_line) { + int i, len = (MULTIPART_BUF_SIZE - msr->mpd->bufleft); + char *p = msr->mpd->buf; + + for(i = 0; i < len; i++) { + if ((p[i] == '-')&&(i + 1 < len)&&(p[i + 1] == '-')) { + if (strncmp(p + i + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) { + msr->mpd->flag_unmatched_boundary = 1; + break; + } + } + } } } - else { + + /* Process as data if it was not a boundary. */ + if (processed_as_boundary == 0) { if (msr->mpd->mpp == NULL) { - msr_log(msr, 4, "Multipart: Ignoring data before first boundary."); + msr->mpd->flag_data_before = 1; + + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Multipart: Ignoring data before first boundary."); + } } else { if (msr->mpd->mpp_state == 0) { if ((msr->mpd->bufleft == 0)||(process_buffer)) { /* part header lines must be shorter than * MULTIPART_BUF_SIZE bytes */ + msr->mpd->flag_error = 1; *error_msg = apr_psprintf(msr->mp, - "Multipart: Part header line over %i bytes long", + "Multipart: Part header line over %d bytes long", MULTIPART_BUF_SIZE); return -1; } - if (multipart_process_part_header(msr, error_msg) < 0) return -1; + + if (multipart_process_part_header(msr, error_msg) < 0) { + msr->mpd->flag_error = 1; + return -1; + } } else { - if (multipart_process_part_data(msr, error_msg) < 0) return -1; + if (multipart_process_part_data(msr, error_msg) < 0) { + msr->mpd->flag_error = 1; + return -1; + } } } } @@ -668,9 +1050,14 @@ int multipart_process_chunk(modsec_rec *msr, const char *buf, msr->mpd->bufleft = MULTIPART_BUF_SIZE; msr->mpd->buf_contains_line = (c == 0x0a) ? 1 : 0; } - + if ((msr->mpd->is_complete)&&(inleft != 0)) { - msr_log(msr, 4, "Multipart: Ignoring data after last boundary (%i bytes left)", inleft); + msr->mpd->flag_data_after = 1; + + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Multipart: Ignoring data after last boundary (%u bytes left)", inleft); + } + return 1; } } @@ -686,7 +1073,9 @@ apr_status_t multipart_cleanup(modsec_rec *msr) { if (msr->mpd == NULL) return -1; - msr_log(msr, 4, "Multipart: Cleanup started (remove files %i).", msr->upload_remove_files); + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Multipart: Cleanup started (remove files %d).", msr->upload_remove_files); + } if (msr->upload_remove_files == 0) { if (msr->txcfg->upload_dir == NULL) { @@ -715,8 +1104,10 @@ apr_status_t multipart_cleanup(modsec_rec *msr) { msr_log(msr, 1, "Multipart: Failed to delete file (part) \"%s\" because %d(%s)", log_escape(msr->mp, parts[i]->tmp_file_name), errno, strerror(errno)); } else { - msr_log(msr, 4, "Multipart: Deleted file (part) \"%s\"", - log_escape(msr->mp, parts[i]->tmp_file_name)); + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Multipart: Deleted file (part) \"%s\"", + log_escape(msr->mp, parts[i]->tmp_file_name)); + } } } } @@ -735,8 +1126,10 @@ apr_status_t multipart_cleanup(modsec_rec *msr) { msr_log(msr, 1, "Multipart: Failed to delete empty file (part) \"%s\" because %d(%s)", log_escape(msr->mp, parts[i]->tmp_file_name), errno, strerror(errno)); } else { - msr_log(msr, 4, "Multipart: Deleted empty file (part) \"%s\"", - log_escape(msr->mp, parts[i]->tmp_file_name)); + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Multipart: Deleted empty file (part) \"%s\"", + log_escape(msr->mp, parts[i]->tmp_file_name)); + } } } } else { @@ -759,9 +1152,11 @@ apr_status_t multipart_cleanup(modsec_rec *msr) { log_escape(msr->mp, new_filename)); return -1; } else { - msr_log(msr, 4, "Input filter: Moved file from \"%s\" to \"%s\".", - log_escape(msr->mp, parts[i]->tmp_file_name), - log_escape(msr->mp, new_filename)); + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Input filter: Moved file from \"%s\" to \"%s\".", + log_escape(msr->mp, parts[i]->tmp_file_name), + log_escape(msr->mp, new_filename)); + } } } } @@ -785,14 +1180,14 @@ int multipart_get_arguments(modsec_rec *msr, char *origin, apr_table_t *argument if (arg == NULL) return -1; arg->name = parts[i]->name; - arg->name_len = strlen(parts[i]->name); // TODO + arg->name_len = strlen(parts[i]->name); arg->value = parts[i]->value; arg->value_len = parts[i]->length; arg->value_origin_offset = parts[i]->offset; arg->value_origin_len = parts[i]->length; arg->origin = origin; - apr_table_addn(arguments, arg->name, (void *)arg); + add_argument(msr, arguments, arg); } } @@ -807,33 +1202,31 @@ char *multipart_reconstruct_urlencoded_body_sanitise(modsec_rec *msr) { char *body; unsigned int body_len; int i; - - if (msr->mpd == NULL) return NULL; - // TODO Cache this data somewhere in the structure + if (msr->mpd == NULL) return NULL; /* calculate the size of the buffer */ body_len = 1; parts = (multipart_part **)msr->mpd->parts->elts; for(i = 0; i < msr->mpd->parts->nelts; i++) { - if (parts[i]->type == MULTIPART_FORMDATA) { + if (parts[i]->type == MULTIPART_FORMDATA) { body_len += 4; body_len += strlen(parts[i]->name) * 3; body_len += strlen(parts[i]->value) * 3; } } - + /* allocate the buffer */ body = apr_palloc(msr->mp, body_len + 1); if ((body == NULL)||(body_len + 1 == 0)) return NULL; *body = 0; - + parts = (multipart_part **)msr->mpd->parts->elts; for(i = 0; i < msr->mpd->parts->nelts; i++) { if (parts[i]->type == MULTIPART_FORMDATA) { if (*body != 0) { strncat(body, "&", body_len - strlen(body)); - } + } strnurlencat(body, parts[i]->name, body_len - strlen(body)); strncat(body, "=", body_len - strlen(body)); @@ -849,6 +1242,6 @@ char *multipart_reconstruct_urlencoded_body_sanitise(modsec_rec *msr) { strnurlencat(body, parts[i]->value, body_len - strlen(body)); } } - + return body; } diff --git a/apache2/msc_multipart.h b/apache2/msc_multipart.h index cfcc35dc..0b06ce89 100644 --- a/apache2/msc_multipart.h +++ b/apache2/msc_multipart.h @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -38,17 +38,17 @@ struct multipart_part { /* variables only, variable value */ char *value; apr_array_header_t *value_parts; - + /* files only, the content type (where available) */ char *content_type; /* files only, the name of the temporary file holding data */ char *tmp_file_name; int tmp_file_fd; - unsigned tmp_file_size; + unsigned int tmp_file_size; /* files only, filename as supplied by the browser */ char *filename; - + char *last_header_name; apr_table_t *headers; @@ -61,9 +61,10 @@ struct multipart_data { apr_array_header_t *parts; /* mime boundary used to detect when - * parts end and new begin + * parts end and begin */ char *boundary; + int boundary_count; /* internal buffer and other variables * used while parsing @@ -84,7 +85,7 @@ struct multipart_data { /* part parsing state; 0 means we are reading * headers, 1 means we are collecting data */ - int mpp_state; + int mpp_state; /* because of the way this parsing algorithm * works we hold back the last two bytes of @@ -93,10 +94,21 @@ struct multipart_data { * a boundary; the first byte is an indicator * 0 - no content, 1 - two data bytes available */ - char reserve[4]; + char reserve[4]; - int seen_data; - int is_complete; + int seen_data; + int is_complete; + + int flag_error; + int flag_data_before; + int flag_data_after; + int flag_header_folding; + int flag_boundary_quoted; + int flag_lf_line; + int flag_crlf_line; + int flag_unmatched_boundary; + int flag_boundary_whitespace; + int flag_missing_semicolon; }; diff --git a/apache2/msc_parsers.c b/apache2/msc_parsers.c index 69be93e4..dfe7dfc8 100644 --- a/apache2/msc_parsers.c +++ b/apache2/msc_parsers.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -206,6 +206,7 @@ int parse_arguments(modsec_rec *msr, const char *s, apr_size_t inputlength, char *value = NULL; char *buf; int status; + int changed; if (s == NULL) return -1; if (inputlength == 0) return 1; @@ -247,7 +248,7 @@ int parse_arguments(modsec_rec *msr, const char *s, apr_size_t inputlength, } if (status == 0) { - arg->name_len = urldecode_nonstrict_inplace_ex((unsigned char *)buf, arg->name_origin_len, invalid_count); + arg->name_len = urldecode_nonstrict_inplace_ex((unsigned char *)buf, arg->name_origin_len, invalid_count, &changed); arg->name = apr_pstrmemdup(msr->mp, buf, arg->name_len); if (s[i] == argument_separator) { @@ -255,14 +256,11 @@ int parse_arguments(modsec_rec *msr, const char *s, apr_size_t inputlength, arg->value_len = 0; arg->value = ""; - apr_table_addn(arguments, arg->name, (void *)arg); - msr_log(msr, 5, "Adding request argument (%s): name \"%s\", value \"%s\"", - arg->origin, log_escape_ex(msr->mp, arg->name, arg->name_len), - log_escape_ex(msr->mp, arg->value, arg->value_len)); + add_argument(msr, arguments, arg); arg = (msc_arg *)apr_pcalloc(msr->mp, sizeof(msc_arg)); arg->origin = origin; - + status = 0; /* unchanged */ j = 0; } else { @@ -271,13 +269,10 @@ int parse_arguments(modsec_rec *msr, const char *s, apr_size_t inputlength, } } else { - arg->value_len = urldecode_nonstrict_inplace_ex((unsigned char *)value, arg->value_origin_len, invalid_count); + arg->value_len = urldecode_nonstrict_inplace_ex((unsigned char *)value, arg->value_origin_len, invalid_count, &changed); arg->value = apr_pstrmemdup(msr->mp, value, arg->value_len); - apr_table_addn(arguments, arg->name, (void *)arg); - msr_log(msr, 5, "Adding request argument (%s): name \"%s\", value \"%s\"", - arg->origin, log_escape_ex(msr->mp, arg->name, arg->name_len), - log_escape_ex(msr->mp, arg->value, arg->value_len)); + add_argument(msr, arguments, arg); arg = (msc_arg *)apr_pcalloc(msr->mp, sizeof(msc_arg)); arg->origin = origin; @@ -294,12 +289,22 @@ int parse_arguments(modsec_rec *msr, const char *s, apr_size_t inputlength, arg->value_len = 0; arg->value = ""; - apr_table_addn(arguments, arg->name, (void *)arg); - msr_log(msr, 5, "Adding request argument (%s): name \"%s\", value \"%s\"", - arg->origin, log_escape_ex(msr->mp, arg->name, arg->name_len), - log_escape_ex(msr->mp, arg->value, arg->value_len)); + add_argument(msr, arguments, arg); } free(buf); + return 1; } + +/** + * + */ +void add_argument(modsec_rec *msr, apr_table_t *arguments, msc_arg *arg) { + msr_log(msr, 5, "Adding request argument (%s): name \"%s\", value \"%s\"", + arg->origin, log_escape_ex(msr->mp, arg->name, arg->name_len), + log_escape_ex(msr->mp, arg->value, arg->value_len)); + + apr_table_addn(arguments, log_escape_nq_ex(msr->mp, arg->name, arg->name_len), (void *)arg); +} + diff --git a/apache2/msc_parsers.h b/apache2/msc_parsers.h index d4370c02..8aad1faa 100644 --- a/apache2/msc_parsers.h +++ b/apache2/msc_parsers.h @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -17,7 +17,9 @@ int DSOLOCAL parse_cookies_v0(modsec_rec *msr, char *_cookie_header, apr_table_t int DSOLOCAL parse_cookies_v1(modsec_rec *msr, char *_cookie_header, apr_table_t *cookies); -int DSOLOCAL parse_arguments(modsec_rec *msr, const char *s, apr_size_t inputlength, +int DSOLOCAL parse_arguments(modsec_rec *msr, const char *s, apr_size_t inputlength, int argument_separator, const char *origin, apr_table_t *arguments, int *invalid_count); +void DSOLOCAL add_argument(modsec_rec *msr, apr_table_t *arguments, msc_arg *arg); + #endif diff --git a/apache2/msc_pcre.c b/apache2/msc_pcre.c index c4f99117..cd007e7a 100644 --- a/apache2/msc_pcre.c +++ b/apache2/msc_pcre.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -20,7 +20,7 @@ apr_status_t msc_pcre_cleanup(msc_regex_t *regex) { free(regex->pe); regex->pe = NULL; } - if (regex->re != NULL) { + if (regex->re != NULL) { free(regex->re); regex->re = NULL; } @@ -34,7 +34,7 @@ apr_status_t msc_pcre_cleanup(msc_regex_t *regex) { * parameters are optional, but if they are provided and an error * occurs they will contain the error message and the offset in * the pattern where the offending part of the pattern begins. - */ + */ void *msc_pregcomp(apr_pool_t *pool, const char *pattern, int options, const char **_errptr, int *_erroffset) { @@ -63,18 +63,32 @@ void *msc_pregcomp(apr_pool_t *pool, const char *pattern, int options, return regex; } +/** + * Executes regular expression with extended options. + * Returns PCRE_ERROR_NOMATCH when there is no match, error code < -1 + * on errors, and a value > 0 when there is a match. + */ +int msc_regexec_ex(msc_regex_t *regex, const char *s, unsigned int slen, + int startoffset, int options, int *ovector, int ovecsize, char **error_msg) +{ + if (error_msg == NULL) return -1000; /* To differentiate from PCRE as it already uses -1. */ + *error_msg = NULL; + + return pcre_exec(regex->re, regex->pe, s, slen, startoffset, options, ovector, ovecsize); +} + /** * Executes regular expression, capturing subexpressions in the given * vector. Returns PCRE_ERROR_NOMATCH when there is no match, error code < -1 * on errors, and a value > 0 when there is a match. - */ + */ int msc_regexec_capture(msc_regex_t *regex, const char *s, unsigned int slen, int *ovector, int ovecsize, char **error_msg) { if (error_msg == NULL) return -1000; /* To differentiate from PCRE as it already uses -1. */ *error_msg = NULL; - return pcre_exec(regex->re, regex->pe, s, slen, 0, 0, ovector, ovecsize); + return msc_regexec_ex(regex, s, slen, 0, 0, ovector, ovecsize, error_msg); } /** @@ -87,7 +101,7 @@ int msc_regexec(msc_regex_t *regex, const char *s, unsigned int slen, if (error_msg == NULL) return -1000; /* To differentiate from PCRE as it already uses -1. */ *error_msg = NULL; - return msc_regexec_capture(regex, s, slen, NULL, 0, error_msg); + return msc_regexec_ex(regex, s, slen, 0, 0, NULL, 0, error_msg); } /** diff --git a/apache2/msc_pcre.h b/apache2/msc_pcre.h index 6067de43..8f41bf5a 100644 --- a/apache2/msc_pcre.h +++ b/apache2/msc_pcre.h @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -24,10 +24,13 @@ struct msc_regex_t { }; apr_status_t DSOLOCAL msc_pcre_cleanup(msc_regex_t *regex); - + void DSOLOCAL *msc_pregcomp(apr_pool_t *pool, const char *pattern, int options, const char **_errptr, int *_erroffset); +int DSOLOCAL msc_regexec_ex(msc_regex_t *regex, const char *s, unsigned int slen, + int startoffset, int options, int *ovector, int ovecsize, char **error_msg); + int DSOLOCAL msc_regexec_capture(msc_regex_t *regex, const char *s, unsigned int slen, int *ovector, int ovecsize, char **error_msg); diff --git a/apache2/msc_reqbody.c b/apache2/msc_reqbody.c index f1586019..90c7f37b 100644 --- a/apache2/msc_reqbody.c +++ b/apache2/msc_reqbody.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -16,28 +16,36 @@ /** * Prepare to accept the request body (part 2). */ -static apr_status_t modsecurity_request_body_start_init(modsec_rec *msr) { +static apr_status_t modsecurity_request_body_start_init(modsec_rec *msr, char **error_msg) { + *error_msg = NULL; + if(msr->msc_reqbody_storage == MSC_REQBODY_MEMORY) { /* Prepare to store request body in memory. */ msr->msc_reqbody_chunks = apr_array_make(msr->msc_reqbody_mp, 32, sizeof(msc_data_chunk *)); - if (msr->msc_reqbody_chunks == NULL) return -1; + if (msr->msc_reqbody_chunks == NULL) { + *error_msg = apr_pstrdup(msr->mp, "Input filter: Failed to prepare in-memory storage."); + return -1; + } } else { /* Prepare to store request body on disk. */ msr->msc_reqbody_filename = apr_psprintf(msr->mp, "%s/%s-%s-request_body-XXXXXX", msr->txcfg->tmp_dir, current_filetime(msr->mp), msr->txid); - if (msr->msc_reqbody_filename == NULL) return -1; + if (msr->msc_reqbody_filename == NULL) { + *error_msg = apr_pstrdup(msr->mp, "Input filter: Failed to generate an on-disk filename."); + return -1; + } msr->msc_reqbody_fd = msc_mkstemp((char *)msr->msc_reqbody_filename); if (msr->msc_reqbody_fd < 0) { - msr_log(msr, 1, "Input filter: Failed to create temporary file: %s", + *error_msg = apr_psprintf(msr->mp, "Input filter: Failed to create temporary file: %s", msr->msc_reqbody_filename); return -1; } - msr_log(msr, 4, "Input filter: Created temporary file to store request body: %s", + msr_log(msr, 4, "Input filter: Created temporary file to store request body: %s", msr->msc_reqbody_filename); } @@ -47,14 +55,15 @@ static apr_status_t modsecurity_request_body_start_init(modsec_rec *msr) { /** * Prepare to accept the request body (part 1). */ -apr_status_t modsecurity_request_body_start(modsec_rec *msr) { +apr_status_t modsecurity_request_body_start(modsec_rec *msr, char **error_msg) { + *error_msg = NULL; msr->msc_reqbody_length = 0; /* Create a separate memory pool that will be used * to allocate structures from (not data, which is allocated * via malloc). */ - apr_pool_create(&msr->msc_reqbody_mp, msr->mp); + apr_pool_create(&msr->msc_reqbody_mp, NULL); /* Initialise request body processors, if any. */ @@ -63,43 +72,47 @@ apr_status_t modsecurity_request_body_start(modsec_rec *msr) { if (strcmp(msr->msc_reqbody_processor, "MULTIPART") == 0) { if (multipart_init(msr, &my_error_msg) < 0) { - msr_log(msr, 1, "Multipart parser init failed: %s", my_error_msg); + *error_msg = apr_psprintf(msr->mp, "Multipart parser init failed: %s", my_error_msg); msr->msc_reqbody_error = 1; msr->msc_reqbody_error_msg = my_error_msg; + msr_log(msr, 2, "Multipart parser init failed: %s", my_error_msg); } } - #ifdef WITH_LIBXML2 else if (strcmp(msr->msc_reqbody_processor, "XML") == 0) { if (xml_init(msr, &my_error_msg) < 0) { - msr_log(msr, 1, "XML parser init failed: %s", my_error_msg); + *error_msg = apr_psprintf(msr->mp, "XML parser init failed: %s", my_error_msg); msr->msc_reqbody_error = 1; msr->msc_reqbody_error_msg = my_error_msg; + msr_log(msr, 2, "Multipart parser init failed: %s", my_error_msg); } } - #endif else if (strcmp(msr->msc_reqbody_processor, "URLENCODED") == 0) { /* Do nothing, URLENCODED processor does not support streaming yet. */ } else { - msr_log(msr, 1, "Unknown request body processor: %s", msr->msc_reqbody_processor); + *error_msg = apr_psprintf(msr->mp, "Unknown request body processor: %s", msr->msc_reqbody_processor); return -1; } } - return modsecurity_request_body_start_init(msr); + return modsecurity_request_body_start_init(msr, error_msg); } /** * Stores a chunk of request body data to disk. */ static apr_status_t modsecurity_request_body_store_disk(modsec_rec *msr, - const char *data, apr_size_t length) + const char *data, apr_size_t length, char **error_msg) { - apr_size_t i = write(msr->msc_reqbody_fd, data, length); + apr_size_t i; + + *error_msg = NULL; + + i = write(msr->msc_reqbody_fd, data, length); if (i != length) { - msr_log(msr, 1, "Input filter: Failed writing %lu bytes to temporary file (rc %lu).", i); + *error_msg = apr_psprintf(msr->mp, "Input filter: Failed writing %" APR_SIZE_T_FMT " bytes to temporary file (rc %" APR_SIZE_T_FMT ").", length, i); return -1; } @@ -110,8 +123,10 @@ static apr_status_t modsecurity_request_body_store_disk(modsec_rec *msr, * Stores one chunk of request body data in memory. */ static apr_status_t modsecurity_request_body_store_memory(modsec_rec *msr, - const char *data, apr_size_t length) + const char *data, apr_size_t length, char **error_msg) { + *error_msg = NULL; + /* Would storing this chunk mean going over the limit? */ if ((msr->msc_reqbody_spilltodisk) && (msr->msc_reqbody_length + length > (apr_size_t)msr->txcfg->reqbody_inmemory_limit)) @@ -129,14 +144,14 @@ static apr_status_t modsecurity_request_body_store_memory(modsec_rec *msr, /* Initialise disk storage */ msr->msc_reqbody_storage = MSC_REQBODY_DISK; - if (modsecurity_request_body_start_init(msr) < 0) return -1; + if (modsecurity_request_body_start_init(msr, error_msg) < 0) return -1; /* Write the data we keep in memory */ chunks = (msc_data_chunk **)msr->msc_reqbody_chunks->elts; for(i = 0; i < msr->msc_reqbody_chunks->nelts; i++) { disklen += chunks[i]->length; - if (modsecurity_request_body_store_disk(msr, chunks[i]->data, chunks[i]->length) < 0) { + if (modsecurity_request_body_store_disk(msr, chunks[i]->data, chunks[i]->length, error_msg) < 0) { return -1; } @@ -152,10 +167,10 @@ static apr_status_t modsecurity_request_body_store_memory(modsec_rec *msr, msr->msc_reqbody_chunks = NULL; apr_pool_clear(msr->msc_reqbody_mp); - msr_log(msr, 4, "Input filter: Wrote %lu bytes from memory to disk.", disklen); + msr_log(msr, 4, "Input filter: Wrote %u bytes from memory to disk.", disklen); /* Continue with disk storage from now on */ - return modsecurity_request_body_store_disk(msr, data, length); + return modsecurity_request_body_store_disk(msr, data, length, error_msg); } /* If we're here that means we are not over the @@ -180,10 +195,16 @@ static apr_status_t modsecurity_request_body_store_memory(modsec_rec *msr, if (msr->msc_reqbody_chunk_current == NULL) { msr->msc_reqbody_chunk_current = (msc_data_chunk *) apr_pcalloc(msr->msc_reqbody_mp, sizeof(msc_data_chunk)); - if (msr->msc_reqbody_chunk_current == NULL) return -1; + if (msr->msc_reqbody_chunk_current == NULL) { + *error_msg = apr_psprintf(msr->mp, "Input filter: Failed to allocate %lu bytes for request body chunk.", (unsigned long)sizeof(msc_data_chunk)); + return -1; + } msr->msc_reqbody_chunk_current->data = malloc(CHUNK_CAPACITY); - if (msr->msc_reqbody_chunk_current->data == NULL) return -1; + if (msr->msc_reqbody_chunk_current->data == NULL) { + *error_msg = apr_psprintf(msr->mp, "Input filter: Failed to allocate %d bytes for request body chunk data.", CHUNK_CAPACITY); + return -1; + } msr->msc_reqbody_chunk_current->length = 0; msr->msc_reqbody_chunk_current->is_permanent = 1; @@ -227,8 +248,10 @@ static apr_status_t modsecurity_request_body_store_memory(modsec_rec *msr, * Stores one chunk of request body data. Returns -1 on error. */ apr_status_t modsecurity_request_body_store(modsec_rec *msr, - const char *data, apr_size_t length) + const char *data, apr_size_t length, char **error_msg) { + *error_msg = NULL; + /* If we have a processor for this request body send * data to it first (but only if it did not report an * error on previous invocations). @@ -237,43 +260,61 @@ apr_status_t modsecurity_request_body_store(modsec_rec *msr, char *my_error_msg = NULL; if (strcmp(msr->msc_reqbody_processor, "MULTIPART") == 0) { + /* The per-request data length counter will + * be updated by the multipart parser. + */ + + /* Process data as multipart/form-data. */ if (multipart_process_chunk(msr, data, length, &my_error_msg) < 0) { + *error_msg = apr_psprintf(msr->mp, "Request body processor error: %s", my_error_msg); msr->msc_reqbody_error = 1; msr->msc_reqbody_error_msg = my_error_msg; - msr_log(msr, 4, "%s", my_error_msg); + msr_log(msr, 2, "Request body processor error: %s", my_error_msg); } } - #ifdef WITH_LIBXML2 else if (strcmp(msr->msc_reqbody_processor, "XML") == 0) { + /* Increase per-request data length counter. */ + msr->msc_reqbody_no_files_length += length; + + /* Process data as XML. */ if (xml_process_chunk(msr, data, length, &my_error_msg) < 0) { + *error_msg = apr_psprintf(msr->mp, "Request body processor error: %s", my_error_msg); msr->msc_reqbody_error = 1; msr->msc_reqbody_error_msg = my_error_msg; - msr_log(msr, 4, "%s", my_error_msg); + msr_log(msr, 2, "Request body processor error: %s", my_error_msg); } } - #endif else if (strcmp(msr->msc_reqbody_processor, "URLENCODED") == 0) { - /* Do nothing, URLENCODED processor does not support streaming. */ + /* Increase per-request data length counter. */ + msr->msc_reqbody_no_files_length += length; + + /* Do nothing else, URLENCODED processor does not support streaming. */ } else { - msr_log(msr, 1, "Unknown request body processor: %s", msr->msc_reqbody_processor); + *error_msg = apr_psprintf(msr->mp, "Unknown request body processor: %s", + msr->msc_reqbody_processor); return -1; } } + /* Check that we are not over the request body no files limit. */ + if (msr->msc_reqbody_no_files_length >= (unsigned long) msr->txcfg->reqbody_no_files_limit) { + return -5; + } + /* Store data. */ if (msr->msc_reqbody_storage == MSC_REQBODY_MEMORY) { - return modsecurity_request_body_store_memory(msr, data, length); + return modsecurity_request_body_store_memory(msr, data, length, error_msg); } else if (msr->msc_reqbody_storage == MSC_REQBODY_DISK) { - return modsecurity_request_body_store_disk(msr, data, length); + return modsecurity_request_body_store_disk(msr, data, length, error_msg); } /* Should never happen. */ - msr_log(msr, 1, "Internal Error: Unknown value for msc_reqbody_storage: %i", + *error_msg = apr_psprintf(msr->mp, "Internal error, unknown value for msc_reqbody_storage: %u", msr->msc_reqbody_storage); return -1; } @@ -281,23 +322,28 @@ apr_status_t modsecurity_request_body_store(modsec_rec *msr, /** * */ -static apr_status_t modsecurity_request_body_end_urlencoded(modsec_rec *msr) { +static apr_status_t modsecurity_request_body_end_urlencoded(modsec_rec *msr, char **error_msg) { msc_data_chunk **chunks, *one_chunk; char *d; int i, sofar; int invalid_count = 0; + *error_msg = NULL; + /* Allocate a buffer large enough to hold the request body. */ - if (msr->msc_reqbody_length + 1 == 0) return -1; + if (msr->msc_reqbody_length + 1 == 0) { + *error_msg = apr_psprintf(msr->mp, "Internal error, request body length will overflow: %u", msr->msc_reqbody_length); + return -1; + } msr->msc_reqbody_buffer = malloc(msr->msc_reqbody_length + 1); if (msr->msc_reqbody_buffer == NULL) { - msr_log(msr, 1, "Unable to allocate memory to hold request body. Asked for %lu bytes.", + *error_msg = apr_psprintf(msr->mp, "Unable to allocate memory to hold request body. Asked for %u bytes.", msr->msc_reqbody_length + 1); return -1; } msr->msc_reqbody_buffer[msr->msc_reqbody_length] = '\0'; - + /* Copy the data we keep in chunks into the new buffer. */ sofar = 0; @@ -309,7 +355,7 @@ static apr_status_t modsecurity_request_body_end_urlencoded(modsec_rec *msr) { d += chunks[i]->length; sofar += chunks[i]->length; } else { - msr_log(msr, 1, "Internal error, request body buffer overflow."); + *error_msg = apr_psprintf(msr->mp, "Internal error, request body buffer overflow."); return -1; } } @@ -325,19 +371,22 @@ static apr_status_t modsecurity_request_body_end_urlencoded(modsec_rec *msr) { /* Create a new array with only one chunk in it. */ msr->msc_reqbody_chunks = apr_array_make(msr->msc_reqbody_mp, 2, sizeof(msc_data_chunk *)); - if (msr->msc_reqbody_chunks == NULL) return -1; + if (msr->msc_reqbody_chunks == NULL) { + *error_msg = apr_pstrdup(msr->mp, "Failed to create structure to hold request body."); + return -1; + } one_chunk = (msc_data_chunk *)apr_pcalloc(msr->msc_reqbody_mp, sizeof(msc_data_chunk)); one_chunk->data = msr->msc_reqbody_buffer; one_chunk->length = msr->msc_reqbody_length; one_chunk->is_permanent = 1; *(const msc_data_chunk **)apr_array_push(msr->msc_reqbody_chunks) = one_chunk; - /* Parse URL-encoded arguments in the request body. */ + /* Parse URL-encoded arguments in the request body. */ if (parse_arguments(msr, msr->msc_reqbody_buffer, msr->msc_reqbody_length, msr->txcfg->argument_separator, "BODY", msr->arguments, &invalid_count) < 0) { - msr_log(msr, 1, "Initialisation: Error occurred while parsing BODY arguments."); + *error_msg = apr_pstrdup(msr->mp, "Initialisation: Error occurred while parsing BODY arguments."); return -1; } @@ -347,7 +396,8 @@ static apr_status_t modsecurity_request_body_end_urlencoded(modsec_rec *msr) { /** * Stops receiving the request body. */ -apr_status_t modsecurity_request_body_end(modsec_rec *msr) { +apr_status_t modsecurity_request_body_end(modsec_rec *msr, char **error_msg) { + *error_msg = NULL; /* Close open file descriptors, if any. */ if (msr->msc_reqbody_storage == MSC_REQBODY_DISK) { @@ -357,31 +407,32 @@ apr_status_t modsecurity_request_body_end(modsec_rec *msr) { } } + /* Note that we've read the body. */ msr->msc_reqbody_read = 1; + /* Finalise body processing. */ if ((msr->msc_reqbody_processor != NULL)&&(msr->msc_reqbody_error == 0)) { char *my_error_msg = NULL; if (strcmp(msr->msc_reqbody_processor, "MULTIPART") == 0) { if (multipart_complete(msr, &my_error_msg) < 0) { + *error_msg = apr_psprintf(msr->mp, "Multipart error: %s", my_error_msg); msr->msc_reqbody_error = 1; msr->msc_reqbody_error_msg = my_error_msg; - msr_log(msr, 1, "Multipart error: %s", my_error_msg); return -1; } if (multipart_get_arguments(msr, "BODY", msr->arguments) < 0) { + *error_msg = apr_psprintf(msr->mp, "Multipart error: %s", my_error_msg); msr->msc_reqbody_error = 1; msr->msc_reqbody_error_msg = "Error retrieving arguments."; - msr_log(msr, 1, "Multipart error: %s", my_error_msg); return -1; } } else if (strcmp(msr->msc_reqbody_processor, "URLENCODED") == 0) { - return modsecurity_request_body_end_urlencoded(msr); + return modsecurity_request_body_end_urlencoded(msr, error_msg); } - #ifdef WITH_LIBXML2 else if (strcmp(msr->msc_reqbody_processor, "XML") == 0) { if (xml_complete(msr, &my_error_msg) < 0) { @@ -391,36 +442,49 @@ apr_status_t modsecurity_request_body_end(modsec_rec *msr) { return -1; } } - #endif } + /* Note the request body no files length. */ + msr_log(msr, 4, "Reqest body no files length: %" APR_SIZE_T_FMT, msr->msc_reqbody_no_files_length); + return 1; } /** * Prepares to forward the request body. */ -apr_status_t modsecurity_request_body_retrieve_start(modsec_rec *msr) { +apr_status_t modsecurity_request_body_retrieve_start(modsec_rec *msr, char **error_msg) { + *error_msg = NULL; + if (msr->msc_reqbody_storage == MSC_REQBODY_MEMORY) { msr->msc_reqbody_chunk_position = 0; msr->msc_reqbody_chunk_offset = 0; - + msr->msc_reqbody_disk_chunk = apr_pcalloc(msr->msc_reqbody_mp, sizeof(msc_data_chunk)); - if (msr->msc_reqbody_disk_chunk == NULL) return -1; + if (msr->msc_reqbody_disk_chunk == NULL) { + *error_msg = apr_psprintf(msr->mp, "Failed to allocate %lu bytes for request body disk chunk.", (unsigned long)sizeof(msc_data_chunk)); + return -1; + } msr->msc_reqbody_disk_chunk->is_permanent = 1; } else if (msr->msc_reqbody_storage == MSC_REQBODY_DISK) { msr->msc_reqbody_disk_chunk = apr_pcalloc(msr->msc_reqbody_mp, sizeof(msc_data_chunk)); - if (msr->msc_reqbody_disk_chunk == NULL) return -1; + if (msr->msc_reqbody_disk_chunk == NULL) { + *error_msg = apr_psprintf(msr->mp, "Failed to allocate %lu bytes for request body disk chunk.", (unsigned long)sizeof(msc_data_chunk)); + return -1; + } msr->msc_reqbody_disk_chunk->is_permanent = 0; msr->msc_reqbody_disk_chunk->data = apr_palloc(msr->msc_reqbody_mp, CHUNK_CAPACITY); - if (msr->msc_reqbody_disk_chunk->data == NULL) return -1; + if (msr->msc_reqbody_disk_chunk->data == NULL) { + *error_msg = apr_psprintf(msr->mp, "Failed to allocate %d bytes for request body disk chunk data.", CHUNK_CAPACITY); + return -1; + } msr->msc_reqbody_fd = open(msr->msc_reqbody_filename, O_RDONLY | O_BINARY); if (msr->msc_reqbody_fd < 0) { - msr_log(msr, 1, "Input filter: Failed to open temporary file for reading: %s", + *error_msg = apr_psprintf(msr->mp, "Failed to open temporary file for reading: %s", msr->msc_reqbody_filename); return -1; } @@ -453,11 +517,16 @@ apr_status_t modsecurity_request_body_retrieve_end(modsec_rec *msr) { * a non-negative value in nbytes. */ apr_status_t modsecurity_request_body_retrieve(modsec_rec *msr, - msc_data_chunk **chunk, long int nbytes) + msc_data_chunk **chunk, long int nbytes, char **error_msg) { msc_data_chunk **chunks; - if (chunk == NULL) return -1; + *error_msg = NULL; + + if (chunk == NULL) { + *error_msg = apr_pstrdup(msr->mp, "Internal error, retrieving request body chunk."); + return -1; + } *chunk = NULL; if (msr->msc_reqbody_storage == MSC_REQBODY_MEMORY) { @@ -527,7 +596,7 @@ apr_status_t modsecurity_request_body_retrieve(modsec_rec *msr, i = read(msr->msc_reqbody_fd, msr->msc_reqbody_disk_chunk->data, my_nbytes); if (i < 0) { - msr_log(msr, 1, "Input filter: Error reading from temporary file: %s", + *error_msg = apr_psprintf(msr->mp, "Input filter: Error reading from temporary file: %s", strerror(errno)); return -1; } @@ -540,7 +609,8 @@ apr_status_t modsecurity_request_body_retrieve(modsec_rec *msr, return 1; /* More data available. */ } - msr_log(msr, 1, "Internal error, invalid msc_reqbody_storage value: %i", + /* Should never happen. */ + *error_msg = apr_psprintf(msr->mp, "Internal error, invalid msc_reqbody_storage value: %u", msr->msc_reqbody_storage); return -1; @@ -549,8 +619,10 @@ apr_status_t modsecurity_request_body_retrieve(modsec_rec *msr, /** * */ -apr_status_t modsecurity_request_body_clear(modsec_rec *msr) { - /* Release memory we used to store request body data. */ +apr_status_t modsecurity_request_body_clear(modsec_rec *msr, char **error_msg) { + *error_msg = NULL; + + /* Release memory we used to store request body data. */ if (msr->msc_reqbody_chunks != NULL) { msc_data_chunk **chunks = (msc_data_chunk **)msr->msc_reqbody_chunks->elts; int i; @@ -574,7 +646,7 @@ apr_status_t modsecurity_request_body_clear(modsec_rec *msr) { if (msr->txcfg->upload_dir != NULL) { keep_body = 1; } else { - msr_log(msr, 1, "Input filter: SecUploadDir is undefined, " + *error_msg = apr_psprintf(msr->mp, "Input filter: SecUploadDir is undefined, " "unable to store PUT file."); } } @@ -589,20 +661,26 @@ apr_status_t modsecurity_request_body_clear(modsec_rec *msr) { /* Construct the new filename. */ put_basename = file_basename(msr->msc_reqbody_mp, msr->msc_reqbody_filename); - if (put_basename == NULL) return -1; + if (put_basename == NULL) { + *error_msg = apr_psprintf(msr->mp, "Input filter: Failed to generate basename to PUT file \"%s\"", log_escape(msr->msc_reqbody_mp, msr->msc_reqbody_filename)); + return -1; + } put_filename = apr_psprintf(msr->msc_reqbody_mp, "%s/%s", - msr->txcfg->upload_dir, put_basename); - if (put_filename == NULL) return -1; + msr->txcfg->upload_dir, put_basename); + if (put_filename == NULL) { + *error_msg = apr_psprintf(msr->mp, "Input filter: Failed to generate filename to PUT file \"%s\"", log_escape(msr->msc_reqbody_mp, msr->msc_reqbody_filename)); + return -1; + } if (apr_file_rename(msr->msc_reqbody_filename, put_filename, msr->msc_reqbody_mp) != APR_SUCCESS) { - msr_log(msr, 1, "Failed to rename file from \"%s\" to \"%s\".", + *error_msg = apr_psprintf(msr->mp, "Input filter: Failed to rename file from \"%s\" to \"%s\".", log_escape(msr->msc_reqbody_mp, msr->msc_reqbody_filename), log_escape(msr->msc_reqbody_mp, put_filename)); return -1; } else { - msr_log(msr, 4, "Moved file from \"%s\" to \"%s\".", + msr_log(msr, 4, "Input filter: Moved file from \"%s\" to \"%s\".", log_escape(msr->msc_reqbody_mp, msr->msc_reqbody_filename), log_escape(msr->msc_reqbody_mp, put_filename)); } @@ -611,8 +689,8 @@ apr_status_t modsecurity_request_body_clear(modsec_rec *msr) { if (apr_file_remove(msr->msc_reqbody_filename, msr->msc_reqbody_mp) != APR_SUCCESS) { - msr_log(msr, 1, "Failed to delete temporary file: %s", - msr->msc_reqbody_filename); + *error_msg = apr_psprintf(msr->mp, "Input filter: Failed to delete temporary file: %s", + log_escape(msr->mp, msr->msc_reqbody_filename)); return -1; } @@ -624,11 +702,10 @@ apr_status_t modsecurity_request_body_clear(modsec_rec *msr) { } } - /* NOTE No need to clear the pool as it has already been destroyed - * if (msr->msc_reqbody_mp != NULL) { - * apr_pool_clear(msr->msc_reqbody_mp); - * } - */ + if (msr->msc_reqbody_mp != NULL) { + apr_pool_destroy(msr->msc_reqbody_mp); + msr->msc_reqbody_mp = NULL; + } return 1; } diff --git a/apache2/msc_test.c b/apache2/msc_test.c new file mode 100644 index 00000000..8adb85c0 --- /dev/null +++ b/apache2/msc_test.c @@ -0,0 +1,475 @@ +/* + * ModSecurity for Apache 2.x, http://www.modsecurity.org/ + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) + * + * You should have received a copy of the licence along with this + * program (stored in the file "LICENSE"). If the file is missing, + * or if you have any other questions related to the licence, please + * write to Breach Security, Inc. at support@breach.com. + * + */ +#include + +#include "modsecurity.h" +#include "re.h" +#include "pdf_protect.h" + +#define ISHEX(X) (((X >= '0')&&(X <= '9')) || ((X >= 'a')&&(X <= 'f')) || ((X >= 'A')&&(X <= 'F'))) + +#define BUFLEN 8192 + +#define RESULT_SUCCESS 0 +#define RESULT_ERROR -1 +#define RESULT_MISMATCHED -2 +#define RESULT_WRONGSIZE -3 +#define RESULT_WRONGRET -4 + +/* Globals */ +static char *test_name = NULL; +static apr_pool_t *g_mp = NULL; +static modsec_rec *g_msr = NULL; +msc_engine *modsecurity = NULL; + + +/* Stubs */ +char *format_error_log_message(apr_pool_t *mp, error_message *em) { + return "FAKE ERROR LOG MESSAGE"; +} + +apr_status_t send_error_bucket(modsec_rec *msr, ap_filter_t *f, int status) { + return APR_SUCCESS; +} + +int apache2_exec(modsec_rec *msr, const char *command, const char **argv, char **output) { + return 0; +} + +char *get_apr_error(apr_pool_t *p, apr_status_t rc) { + return "FAKE APR ERROR"; +} + +void msr_log(modsec_rec *msr, int level, const char *text, ...) { + va_list ap; + char str1[1024] = ""; + char str2[1256] = ""; + + if ((msr == NULL) || (level > msr->txcfg->debuglog_level)) { + return; + } + if (msr->txcfg->debuglog_fd == NOT_SET_P) { + if (apr_file_open(&msr->txcfg->debuglog_fd, msr->txcfg->debuglog_name, APR_READ|APR_WRITE|APR_CREATE|APR_APPEND|APR_BINARY, APR_OS_DEFAULT, g_mp) != APR_SUCCESS) { + fprintf(stderr, "ERROR: failed to create unit test debug log \"%s\".\n", msr->txcfg->debuglog_name); + msr->txcfg->debuglog_fd = NULL; + } + } + + va_start(ap, text); + if (msr->txcfg->debuglog_fd != NULL) { + apr_size_t nbytes_written = 0; + apr_vsnprintf(str1, sizeof(str1), text, ap); + apr_snprintf(str2, sizeof(str2), "[%d] [%s] %s\n", level, test_name, str1); + + apr_file_write_full(msr->txcfg->debuglog_fd, str2, strlen(str2), &nbytes_written); + } + va_end(ap); +} + +const char *ap_get_remote_host(conn_rec *conn, void *dir_config, int type, int *str_is_ip) { + return "FAKE-REMOTE-HOST"; +} + +char *get_env_var(request_rec *r, char *name) { + return "FAKE-ENV-VAR"; +} + +apr_status_t unixd_set_global_mutex_perms(apr_global_mutex_t *gmutex) { + return APR_SUCCESS; +} + +apr_status_t unixd_set_proc_mutex_perms(apr_proc_mutex_t *pmutex) { + return APR_SUCCESS; +} + + +/* Escaping functions */ + +static unsigned char hex2dec(unsigned char *what) { + register unsigned char digit; + + digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0')); + digit *= 16; + digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0')); + + return digit; +} + +static unsigned char *unescape_inplace(unsigned char *str, apr_size_t *len) +{ + apr_size_t i, j; + for (i = j = 0; i < *len; j++) { + if ((str[i] == '\\') && (i + 3 < *len) && (str[i + 1] == 'x') && ISHEX(str[i + 2]) && ISHEX(str[i + 3]) ) { + str[j] = hex2dec(str + i + 2); + i += 4; + } + else { + str[j] = str[i++]; + } + } + *len = j; + + while (j < i) { + str[j++] = '\0'; + } + + return str; +} + +static char *escape(unsigned char *str, apr_size_t *len) +{ + char *new = apr_pcalloc(g_mp, (*len * 4) + 1); + apr_size_t i, j; + for (i = j = 0; i < *len; i++) { + if ((str[i] >= 0x20) && (str[i] <= 0x7e)) { + new[j++] = str[i]; + } + else { + sprintf(new + j, "\\x%02x", str[i]); + j += 4; + } + } + *len = j; + + return new; +} + + +/* Testing functions */ + +static int test_tfn(const char *name, unsigned char *input, apr_size_t input_len, unsigned char **rval, apr_size_t *rval_len, char **errmsg) +{ + int rc = -1; + msre_tfn_metadata *metadata = NULL; + + *errmsg = NULL; + + /* Lookup the tfn */ + metadata = msre_engine_tfn_resolve(modsecurity->msre, name); + + if (metadata == NULL) { + *errmsg = apr_psprintf(g_mp, "Failed to fetch tfn \"%s\".", name); + return -1; + } + + /* Execute the tfn */ + rc = metadata->execute(g_mp, input, (long)input_len, (char **)rval, (long *)rval_len); + if (rc < 0) { + *errmsg = apr_psprintf(g_mp, "Failed to execute tfn \"%s\".", name); + } + + return rc; +} + +static int test_op(const char *name, const char *param, const unsigned char *input, apr_size_t input_len, char **errmsg) +{ + const char *args = apr_psprintf(g_mp, "@%s %s", name, param); + char *conf_fn; + msre_ruleset *ruleset = NULL; + msre_rule *rule = NULL; + msre_var *var = NULL; + msre_op_metadata *metadata = NULL; + int rc = -1; + + *errmsg = NULL; + + if ( apr_filepath_merge(&conf_fn, NULL, "t/unit-test.conf", APR_FILEPATH_TRUENAME, g_mp) != APR_SUCCESS) { + *errmsg = apr_psprintf(g_mp, "Failed to build a conf filename."); + return -1; + } + + /* Register UNIT_TEST variable */ + msre_engine_variable_register(modsecurity->msre, + "UNIT_TEST", + VAR_SIMPLE, + 0, 0, + NULL, + NULL, + VAR_DONT_CACHE, + PHASE_REQUEST_HEADERS + ); + + /* Lookup the operator */ + metadata = msre_engine_op_resolve(modsecurity->msre, name); + if (metadata == NULL) { + *errmsg = apr_psprintf(g_mp, "Failed to fetch op \"%s\".", name); + return -1; + } + + /* Create a ruleset/rule */ + ruleset = msre_ruleset_create(modsecurity->msre, g_mp); + if (ruleset == NULL) { + *errmsg = apr_psprintf(g_mp, "Failed to create ruleset for op \"%s\".", name); + return -1; + } + rule = msre_rule_create(ruleset, RULE_TYPE_NORMAL, conf_fn, 1, "UNIT_TEST", args, "t:none,pass,nolog", errmsg); + if (rule == NULL) { + *errmsg = apr_psprintf(g_mp, "Failed to create rule for op \"%s\": %s", name, *errmsg); + return -1; + } + + /* Create a fake variable */ + var = (msre_var *)apr_pcalloc(g_mp, sizeof(msre_var)); + var->name = "UNIT_TEST"; + var->value = apr_pstrmemdup(g_mp, (char *)input, input_len); + var->value_len = input_len; + var->metadata = msre_resolve_var(modsecurity->msre, var->name); + if (var->metadata == NULL) { + *errmsg = apr_psprintf(g_mp, "Failed to resolve variable for op \"%s\": %s", name, var->name); + return -1; + } + + /* Initialize the operator parameter */ + if (metadata->param_init != NULL) { + rc = metadata->param_init(rule, errmsg); + if (rc <= 0) { + *errmsg = apr_psprintf(g_mp, "Failed to init op \"%s\": %s", name, *errmsg); + return rc; + } + } + + /* Execute the operator */ + if (metadata->execute != NULL) { + rc = metadata->execute(g_msr, rule, var, errmsg); + if (rc < 0) { + *errmsg = apr_psprintf(g_mp, "Failed to execute op \"%s\": %s", name, *errmsg); + } + } + + return rc; +} + + +/* Initialization */ +static void init_msr() { + directory_config *dcfg = NULL; + request_rec *r = NULL; + r = (request_rec *)apr_pcalloc(g_mp, sizeof(request_rec)); + + dcfg = (directory_config *)apr_pcalloc(g_mp, sizeof(directory_config)); + dcfg->is_enabled = 0; + dcfg->reqbody_access = 0; + dcfg->reqbody_inmemory_limit = REQUEST_BODY_DEFAULT_INMEMORY_LIMIT; + dcfg->reqbody_limit = REQUEST_BODY_DEFAULT_LIMIT; + dcfg->reqbody_no_files_limit = REQUEST_BODY_NO_FILES_DEFAULT_LIMIT; + dcfg->resbody_access = 0; + dcfg->of_limit = RESPONSE_BODY_DEFAULT_LIMIT; + dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_REJECT; + dcfg->debuglog_fd = NOT_SET_P; + dcfg->debuglog_name = "msc-test-debug.log"; + dcfg->debuglog_level = 9; + dcfg->cookie_format = 0; + dcfg->argument_separator = '&'; + dcfg->rule_inheritance = 0; + dcfg->auditlog_flag = 0; + dcfg->auditlog_type = AUDITLOG_SERIAL; + dcfg->auditlog_fd = NULL; + dcfg->auditlog2_fd = NULL; + dcfg->auditlog_name = NULL; + dcfg->auditlog2_name = NULL; + dcfg->auditlog_storage_dir = NULL; + dcfg->auditlog_parts = "ABCFHZ"; + dcfg->auditlog_relevant_regex = NULL; + dcfg->tmp_dir = guess_tmp_dir(g_mp); + dcfg->upload_dir = NULL; + dcfg->upload_keep_files = KEEP_FILES_OFF; + dcfg->upload_validates_files = 0; + dcfg->data_dir = NULL; + dcfg->webappid = "default"; + dcfg->content_injection_enabled = 0; + dcfg->pdfp_enabled = 0; + dcfg->pdfp_secret = NULL; + dcfg->pdfp_timeout = 10; + dcfg->pdfp_token_name = "PDFPTOKEN"; + dcfg->pdfp_only_get = 1; + dcfg->pdfp_method = PDF_PROTECT_METHOD_TOKEN_REDIRECTION; + dcfg->geo = NULL; + dcfg->cache_trans = MODSEC_CACHE_ENABLED; + dcfg->cache_trans_min = 15; + dcfg->cache_trans_max = 0; + dcfg->request_encoding = NULL; + + g_msr = (modsec_rec *)apr_pcalloc(g_mp, sizeof(modsec_rec)); + g_msr->modsecurity = modsecurity; + g_msr->mp = g_mp; + g_msr->r = r; + g_msr->r_early = r; + g_msr->request_time = apr_time_now(); + g_msr->dcfg1 = NULL; + g_msr->usercfg = NULL; + g_msr->txcfg = dcfg; + g_msr->txid = "FAKE-TXID"; + g_msr->error_messages = NULL; + g_msr->alerts = NULL; + g_msr->server_software = "FAKE-SERVER-SOFTWARE"; + g_msr->local_addr = "127.0.0.1"; + g_msr->local_port = 80; + g_msr->remote_addr = "127.0.0.1"; + g_msr->remote_port = 1080; + g_msr->request_line = "GET /unit-tests HTTP/1.1"; + g_msr->request_uri = "http://localhost/unit-tests"; + g_msr->request_method = "GET"; + g_msr->query_string = ""; + g_msr->request_protocol = "HTTP/1.1"; + g_msr->request_headers = NULL; + g_msr->hostname = "localhost"; + g_msr->msc_rule_mptmp = g_mp; + g_msr->tx_vars = apr_table_make(g_mp, 10); +} + + +/* Main */ + +int main(int argc, const char * const argv[]) +{ + apr_file_t *fd; + unsigned char buf[BUFLEN]; + apr_size_t nbytes = BUFLEN; + unsigned char input[BUFLEN]; + const char *type = NULL; + const char *name = NULL; + unsigned char *param = NULL; + const char *returnval = NULL; + char *errmsg = NULL; + unsigned char *out = NULL; + apr_size_t input_len = 0; + apr_size_t param_len = 0; + apr_size_t out_len = 0; + int rc = 0; + int result = 0; + int ec = 0; + + apr_app_initialize(&argc, &argv, NULL); + atexit(apr_terminate); + + if (argc < 4) { + fprintf(stderr, "Usage: %s []\n", argv[0]); + exit(1); + } + + apr_pool_create(&g_mp, NULL); + modsecurity = modsecurity_create(g_mp, MODSEC_OFFLINE); + + type = argv[1]; + name = argv[2]; + param_len = strlen(argv[3]); + param = apr_pmemdup(g_mp, argv[3], param_len + 1); + unescape_inplace(param, ¶m_len); + if (argc >= 5) { + returnval = argv[4]; + } + + test_name = apr_psprintf(g_mp, "%s/%s", type, name); + + if (apr_file_open_stdin(&fd, g_mp) != APR_SUCCESS) { + fprintf(stderr, "Failed to open stdin\n"); + exit(1); + } + + memset(buf, 0, BUFLEN); + rc = apr_file_read(fd, buf, &nbytes); + if ((rc != APR_EOF) && (rc != APR_SUCCESS)) { + fprintf(stderr, "Failed to read data\n"); + exit(1); + } + + if (nbytes < 0) { + fprintf(stderr, "Error reading data\n"); + exit(1); + } + + apr_file_close(fd); + + /* Make a copy as transformations are done in-place */ + memcpy(input, buf, BUFLEN); + input_len = nbytes; + + if (strcmp("tfn", type) == 0) { + /* Transformations */ + int ret = returnval ? atoi(returnval) : -8888; + rc = test_tfn(name, input, input_len, &out, &out_len, &errmsg); + if (rc < 0) { + fprintf(stderr, "ERROR: %s\n", errmsg); + result = RESULT_ERROR; + } + else if ((ret != -8888) && (rc != ret)) { + fprintf(stderr, "Returned %d (expected %d)\n", rc, ret); + result = RESULT_WRONGRET; + } + else if (param_len != out_len) { + fprintf(stderr, "Lenth %" APR_SIZE_T_FMT " (expected %" APR_SIZE_T_FMT ")\n", out_len, param_len); + result = RESULT_WRONGSIZE; + } + else { + result = memcmp(param, out, param_len) ? RESULT_MISMATCHED : RESULT_SUCCESS; + } + + if (result != RESULT_SUCCESS) { + apr_size_t s0len = nbytes; + const char *s0 = escape(buf, &s0len); + apr_size_t s1len = out_len; + const char *s1 = escape(out, &s1len); + apr_size_t s2len = param_len; + const char *s2 = escape(param, &s2len); + + fprintf(stderr, " Input: '%s' len=%" APR_SIZE_T_FMT "\n" + "Output: '%s' len=%" APR_SIZE_T_FMT "\n" + "Expect: '%s' len=%" APR_SIZE_T_FMT "\n", + s0, nbytes, s1, out_len, s2, param_len); + ec = 1; + } + } + else if (strcmp("op", type) == 0) { + /* Operators */ + int ret = 0; + + if (!returnval) { + fprintf(stderr, "Return value required for type \"%s\"\n", type); + exit(1); + } + ret = atoi(returnval); + + init_msr(); + + rc = test_op(name, (const char *)param, (const unsigned char *)input, input_len, &errmsg); + if (rc < 0) { + fprintf(stderr, "ERROR: %s\n", errmsg); + result = RESULT_ERROR; + } + else if (rc != ret) { + fprintf(stderr, "Returned %d (expected %d)\n", rc, ret); + result = RESULT_WRONGRET; + } + else { + result = RESULT_SUCCESS; + } + + if (result != RESULT_SUCCESS) { + apr_size_t s0len = nbytes; + const char *s0 = escape(buf, &s0len); + + fprintf(stderr, " Test: '@%s %s'\n" + "Input: '%s' len=%" APR_SIZE_T_FMT "\n", + name, param, s0, nbytes); + ec = 1; + } + } + else { + fprintf(stderr, "Unknown type: \"%s\"\n", type); + exit(1); + } + + fprintf(stdout, "%s\n", errmsg ? errmsg : ""); + + return ec; +} + + diff --git a/apache2/msc_util.c b/apache2/msc_util.c index e4ac75a0..401cf4a3 100644 --- a/apache2/msc_util.c +++ b/apache2/msc_util.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -16,6 +16,15 @@ #include #include +#include + +/* NOTE: Be careful as these can ONLY be used on static values for X. + * (i.e. VALID_HEX(c++) will NOT work) + */ +#define VALID_HEX(X) (((X >= '0')&&(X <= '9')) || ((X >= 'a')&&(X <= 'f')) || ((X >= 'A')&&(X <= 'F'))) +#define ISODIGIT(X) ((X >= '0')&&(X <= '7')) + + /** * */ @@ -28,7 +37,7 @@ int parse_boolean(const char *input) { if (strcasecmp(input, "false") == 0) return 0; if (strcasecmp(input, "0") == 0) return 0; - return -1; + return -1; } /** @@ -58,16 +67,19 @@ int parse_name_eq_value(apr_pool_t *mp, const char *input, char **name, char **v *value = apr_pstrdup(mp, p); if (*value == NULL) return -1; - return 1; + return 1; } /** * + * IMP1 Assumes NUL-terminated */ -char *url_encode(apr_pool_t *mp, char *input, unsigned int input_len) { +char *url_encode(apr_pool_t *mp, char *input, unsigned int input_len, int *changed) { char *rval, *d; unsigned int i, len; + *changed = 0; + len = input_len * 3 + 1; d = rval = apr_palloc(mp, len); if (rval == NULL) return NULL; @@ -79,6 +91,7 @@ char *url_encode(apr_pool_t *mp, char *input, unsigned int input_len) { if (c == ' ') { *d++ = '+'; + *changed = 1; } else if ( (c == 42) || ((c >= 48)&&(c <= 57)) || ((c >= 65)&&(c <= 90)) || ((c >= 97)&&(c <= 122)) @@ -88,11 +101,12 @@ char *url_encode(apr_pool_t *mp, char *input, unsigned int input_len) { *d++ = '%'; c2x(c, (unsigned char *)d); d += 2; + *changed = 1; } } *d = '\0'; - + return rval; } @@ -115,7 +129,7 @@ char *strnurlencat(char *destination, char *source, unsigned int maxlen) { */ while((*s != '\0')&&(maxlen > 0)) { unsigned char c = *s; - + if (c == ' ') { *d++ = '+'; maxlen--; @@ -138,12 +152,12 @@ char *strnurlencat(char *destination, char *source, unsigned int maxlen) { maxlen = 0; } } - + s++; } *d++ = '\0'; - + return destination; } @@ -282,13 +296,13 @@ int remove_lf_crlf_inplace(char *text) { char *p = text; int count = 0; - if (text == NULL) return -1; - + if (text == NULL) return -1; + while(*p != '\0') { count++; p++; } - + if (count > 0) { if (*(p - 1) == '\n') { *(p - 1) = '\0'; @@ -299,7 +313,7 @@ int remove_lf_crlf_inplace(char *text) { } } } - + return 1; } @@ -450,13 +464,43 @@ char *log_escape_raw(apr_pool_t *mp, const unsigned char *text, unsigned long in unsigned long int i, j; for (i = 0, j = 0; i < text_length; i++, j += 4) { - apr_snprintf((char *)ret+j, 5, "\\x%02x", text[i]); + ret[j] = '\\'; + ret[j+1] = 'x'; + c2x(text[i], ret+j+2); } ret[text_length * 4] = '\0'; return (char *)ret; } +/** + * Transform text to ASCII printable or hex escaped + */ +char *log_escape_hex(apr_pool_t *mp, const unsigned char *text, unsigned long int text_length) { + unsigned char *ret = apr_palloc(mp, text_length * 4 + 1); + unsigned long int i, j; + + for (i = 0, j = 0; i < text_length; i++) { + if ( (text[i] == '"') + ||(text[i] == '\\') + ||(text[i] <= 0x1f) + ||(text[i] >= 0x7f)) + { + ret[j] = '\\'; + ret[j+1] = 'x'; + c2x(text[i], ret+j+2); + j += 4; + } + else { + ret[j] = text[i]; + j ++; + } + } + ret[j] = '\0'; + + return (char *)ret; +} + /** * Transform input into a form safe for logging. */ @@ -536,15 +580,136 @@ char *_log_escape(apr_pool_t *mp, const unsigned char *input, unsigned long int return ret; } -#define VALID_HEX(X) (((X >= '0')&&(X <= '9')) || ((X >= 'a')&&(X <= 'f')) || ((X >= 'A')&&(X <= 'F'))) +/** + * JavaScript decoding. + * IMP1 Assumes NUL-terminated + */ + +int js_decode_nonstrict_inplace(unsigned char *input, long int input_len) { + unsigned char *d = (unsigned char *)input; + long int i, count; + + if (input == NULL) return -1; + + i = count = 0; + while (i < input_len) { + if (input[i] == '\\') { + /* Character is an escape. */ + + if ( (i + 5 < input_len) && (input[i + 1] == 'u') + && (VALID_HEX(input[i + 2])) && (VALID_HEX(input[i + 3])) + && (VALID_HEX(input[i + 4])) && (VALID_HEX(input[i + 5])) ) + { + /* \uHHHH */ + + /* Use only the lower byte. */ + *d = x2c(&input[i + 4]); + + /* Full width ASCII (ff01 - ff5e) needs 0x20 added */ + if ( (*d > 0x00) && (*d < 0x5f) + && ((input[i + 2] == 'f') || (input[i + 2] == 'F')) + && ((input[i + 3] == 'f') || (input[i + 3] == 'F'))) + { + (*d) += 0x20; + } + + d++; + count++; + i += 6; + } + else if ( (i + 3 < input_len) && (input[i + 1] == 'x') + && VALID_HEX(input[i + 2]) && VALID_HEX(input[i + 3])) { + /* \xHH */ + *d++ = x2c(&input[i + 2]); + count++; + i += 4; + } + else if ((i + 1 < input_len) && ISODIGIT(input[i + 1])) { + /* \OOO (only one byte, \000 - \377) */ + char buf[4]; + int j = 0; + + while((i + 1 + j < input_len)&&(j < 3)) { + buf[j] = input[i + 1 + j]; + j++; + if (!ISODIGIT(input[i + 1 + j])) break; + } + buf[j] = '\0'; + + if (j > 0) { + /* Do not use 3 characters if we will be > 1 byte */ + if ((j == 3) && (buf[0] > '3')) { + j = 2; + buf[j] = '\0'; + } + *d++ = strtol(buf, NULL, 8); + i += 1 + j; + count++; + } + } + else if (i + 1 < input_len) { + /* \C */ + unsigned char c = input[i + 1]; + switch(input[i + 1]) { + case 'a' : + c = '\a'; + break; + case 'b' : + c = '\b'; + break; + case 'f' : + c = '\f'; + break; + case 'n' : + c = '\n'; + break; + case 'r' : + c = '\r'; + break; + case 't' : + c = '\t'; + break; + case 'v' : + c = '\v'; + break; + /* The remaining (\?,\\,\',\") are just a removal + * of the escape char which is default. + */ + } + + *d++ = c; + i += 2; + count++; + } + else { + /* Not enough bytes */ + while(i < input_len) { + *d++ = input[i++]; + count++; + } + } + } + else { + *d++ = input[i++]; + count++; + } + } + + *d = '\0'; + + return count; +} /** * + * IMP1 Assumes NUL-terminated */ -int urldecode_uni_nonstrict_inplace_ex(unsigned char *input, long int input_len) { +int urldecode_uni_nonstrict_inplace_ex(unsigned char *input, long int input_len, int *changed) { unsigned char *d = input; long int i, count; + *changed = 0; + if (input == NULL) return -1; i = count = 0; @@ -567,27 +732,24 @@ int urldecode_uni_nonstrict_inplace_ex(unsigned char *input, long int input_len) && ((input[i + 2] == 'f') || (input[i + 2] == 'F')) && ((input[i + 3] == 'f') || (input[i + 3] == 'F'))) { - *d += 0x20; + (*d) += 0x20; } d++; count++; i += 6; + *changed = 1; } else { - /* Invalid data. */ - int j; - - for(j = 0; (j < 6)&&(i < input_len); j++) { - *d++ = input[i++]; - count++; - } + /* Invalid data, skip %u. */ + *d++ = input[i++]; + *d++ = input[i++]; + count += 2; } } else { - /* Not enough bytes available (4 data bytes were needed). */ - while(i < input_len) { - *d++ = input[i++]; - count++; - } + /* Not enough bytes (4 data bytes), skip %u. */ + *d++ = input[i++]; + *d++ = input[i++]; + count += 2; } } else { @@ -602,35 +764,20 @@ int urldecode_uni_nonstrict_inplace_ex(unsigned char *input, long int input_len) char c1 = input[i + 1]; char c2 = input[i + 2]; - /* ENH Use VALID_HEX? */ - if ( (((c1 >= '0')&&(c1 <= '9')) || ((c1 >= 'a')&&(c1 <= 'f')) || - ((c1 >= 'A')&&(c1 <= 'F'))) - && (((c2 >= '0')&&(c2 <= '9')) || ((c2 >= 'a')&&(c2 <= 'f')) || - ((c2 >= 'A')&&(c2 <= 'F'))) ) - { + if (VALID_HEX(c1) && VALID_HEX(c2)) { *d++ = x2c(&input[i + 1]); count++; i += 3; + *changed = 1; } else { - /* Not a valid encoding, copy the raw input bytes. */ - *d++ = '%'; - *d++ = c1; - *d++ = c2; - count += 3; - i += 3; + /* Not a valid encoding, skip this % */ + *d++ = input[i++]; + count++; } } else { - /* Not enough bytes available. */ - - *d++ = '%'; + /* Not enough bytes available, skip this % */ + *d++ = input[i++]; count++; - i++; - - if (i + 1 < input_len) { - *d++ = input[i]; - count++; - i++; - } } } } @@ -638,6 +785,7 @@ int urldecode_uni_nonstrict_inplace_ex(unsigned char *input, long int input_len) /* Character is not a percent sign. */ if (input[i] == '+') { *d++ = ' '; + *changed = 1; } else { *d++ = input[i]; } @@ -654,11 +802,14 @@ int urldecode_uni_nonstrict_inplace_ex(unsigned char *input, long int input_len) /** * + * IMP1 Assumes NUL-terminated */ -int urldecode_nonstrict_inplace_ex(unsigned char *input, long int input_len, int *invalid_count) { +int urldecode_nonstrict_inplace_ex(unsigned char *input, long int input_len, int *invalid_count, int *changed) { unsigned char *d = (unsigned char *)input; long int i, count; + *changed = 0; + if (input == NULL) return -1; i = count = 0; @@ -671,41 +822,29 @@ int urldecode_nonstrict_inplace_ex(unsigned char *input, long int input_len, int char c1 = input[i + 1]; char c2 = input[i + 2]; - /* ENH Use VALID_HEX? */ - if ( (((c1 >= '0')&&(c1 <= '9')) || ((c1 >= 'a')&&(c1 <= 'f')) || ((c1 >= 'A')&&(c1 <= 'F'))) - && (((c2 >= '0')&&(c2 <= '9')) || ((c2 >= 'a')&&(c2 <= 'f')) || ((c2 >= 'A')&&(c2 <= 'F'))) ) - { + if (VALID_HEX(c1) && VALID_HEX(c2)) { /* Valid encoding - decode it. */ *d++ = x2c(&input[i + 1]); count++; i += 3; + *changed = 1; } else { - /* Invalid encoding, just copy the raw bytes. */ - *d++ = '%'; - *d++ = c1; - *d++ = c2; - count += 3; - i += 3; - (*invalid_count)++; /* parens quiet compiler warning */ + /* Not a valid encoding, skip this % */ + *d++ = input[i++]; + count ++; + (*invalid_count)++; } } else { /* Not enough bytes available, copy the raw bytes. */ - (*invalid_count)++; /* parens quiet compiler warning */ - - *d++ = '%'; - count++; - i++; - - if (i + 1 < input_len) { - *d++ = input[i]; - count++; - i++; - } + *d++ = input[i++]; + count ++; + (*invalid_count)++; } } else { /* Character is not a percent sign. */ if (input[i] == '+') { *d++ = ' '; + *changed = 1; } else { *d++ = input[i]; } @@ -721,13 +860,14 @@ int urldecode_nonstrict_inplace_ex(unsigned char *input, long int input_len, int /** * + * IMP1 Assumes NUL-terminated */ int html_entities_decode_inplace(apr_pool_t *mp, unsigned char *input, int input_len) { unsigned char *d = input; int i, count; - if ((input == NULL)||(input_len <= 0)) return 0; - + if ((input == NULL)||(input_len <= 0)) return 0; + i = count = 0; while((i < input_len)&&(count < input_len)) { int z, copy = 1; @@ -756,7 +896,7 @@ int html_entities_decode_inplace(apr_pool_t *mp, unsigned char *input, int input while((j < input_len)&&(isxdigit(input[j]))) j++; if (j > k) { /* Do we have at least one digit? */ /* Decode the entity. */ - char *x = apr_pstrmemdup(mp, (const char*)&input[k], j - k); + char *x = apr_pstrmemdup(mp, (const char *)&input[k], j - k); *d++ = (unsigned char)strtol(x, NULL, 16); count++; @@ -774,7 +914,7 @@ int html_entities_decode_inplace(apr_pool_t *mp, unsigned char *input, int input while((j < input_len)&&(isdigit(input[j]))) j++; if (j > k) { /* Do we have at least one digit? */ /* Decode the entity. */ - char *x = apr_pstrmemdup(mp, (const char*)&input[k], j - k); + char *x = apr_pstrmemdup(mp, (const char *)&input[k], j - k); *d++ = (unsigned char)strtol(x, NULL, 10); count++; @@ -793,9 +933,10 @@ int html_entities_decode_inplace(apr_pool_t *mp, unsigned char *input, int input k = j; while((j < input_len)&&(isalnum(input[j]))) j++; if (j > k) { /* Do we have at least one digit? */ - char *x = apr_pstrmemdup(mp, (const char*)&input[k], j - k); + char *x = apr_pstrmemdup(mp, (const char *)&input[k], j - k); /* Decode the entity. */ + /* ENH What about others? */ if (strcasecmp(x, "quot") == 0) *d++ = '"'; else if (strcasecmp(x, "amp") == 0) *d++ = '&'; @@ -835,8 +976,10 @@ int html_entities_decode_inplace(apr_pool_t *mp, unsigned char *input, int input return count; } -#define ISODIGIT(X) ((X >= '0')&&(X <= '7')) - +/** + * + * IMP1 Assumes NUL-terminated + */ int ansi_c_sequences_decode_inplace(unsigned char *input, int input_len) { unsigned char *d = input; int i, count; @@ -845,10 +988,6 @@ int ansi_c_sequences_decode_inplace(unsigned char *input, int input_len) { while(i < input_len) { if ((input[i] == '\\')&&(i + 1 < input_len)) { int c = -1; - - /* ENH Should we handle \c as well? - * See http://www.opengroup.org/onlinepubs/009695399/utilities/printf.html - */ switch(input[i + 1]) { case 'a' : @@ -901,14 +1040,11 @@ int ansi_c_sequences_decode_inplace(unsigned char *input, int input_len) { } } else - if (isdigit(input[i + 1])) { /* Octal. */ - char buf[10]; - int j = 0, l = 3; + if (ISODIGIT(input[i + 1])) { /* Octal. */ + char buf[4]; + int j = 0; - /* Up to 4 digits if the first digit is a zero. */ - if (input[i + 1] == '0') l = 4; - - while((i + 1 + j < input_len)&&(j <= l)) { + while((i + 1 + j < input_len)&&(j < 3)) { buf[j] = input[i + 1 + j]; j++; if (!ISODIGIT(input[i + 1 + j])) break; @@ -944,16 +1080,25 @@ int ansi_c_sequences_decode_inplace(unsigned char *input, int input_len) { return count; } -int normalise_path_inplace(unsigned char *input, int input_len, int win) { +/** + * + * IMP1 Assumes NUL-terminated + */ +int normalise_path_inplace(unsigned char *input, int input_len, int win, int *changed) { unsigned char *d = input; int i, count; + *changed = 0; + i = count = 0; while ((i < input_len)&&(count < input_len)) { char c = input[i]; /* Convert backslash to forward slash on Windows only. */ - if ((win)&&(c == '\\')) c = '/'; + if ((win)&&(c == '\\')) { + c = '/'; + *changed = 1; + } if (c == '/') { /* Is there a directory back-reference? Yes, we @@ -964,6 +1109,8 @@ int normalise_path_inplace(unsigned char *input, int input_len, int win) { unsigned char *cd = d - 4; int ccount = count - 4; + *changed = 1; + /* Go back until we reach the beginning or a forward slash. */ while ((ccount > 0)&&(*cd != '/')) { ccount--; @@ -980,12 +1127,14 @@ int normalise_path_inplace(unsigned char *input, int input_len, int win) { /* Ignore the last two bytes. */ d -= 2; count -= 2; + *changed = 1; } else /* Or are there just multiple occurences of forward slash? */ if ((count >= 1)&&(*(d - 1) == '/')) { /* Ignore the last one byte. */ d--; count--; + *changed = 1; } } @@ -999,3 +1148,46 @@ int normalise_path_inplace(unsigned char *input, int input_len, int win) { return count; } + +char *modsec_build(apr_pool_t *mp) { + int build_type = 0; + int i; + + for (i = 0; modsec_build_type[i].name != NULL; i++) { + if (strcmp(MODSEC_VERSION_TYPE, modsec_build_type[i].name) == 0) { + build_type = modsec_build_type[i].val; + break; + } + } + + return apr_psprintf(mp, "%02i%02i%02i%1i%02i", + atoi(MODSEC_VERSION_MAJOR), + atoi(MODSEC_VERSION_MINOR), + atoi(MODSEC_VERSION_MAINT), + build_type, + atoi(MODSEC_VERSION_RELEASE)); +} + +int is_empty_string(const char *string) { + unsigned int i; + + if (string == NULL) return 1; + + for(i = 0; string[i] != '\0'; i++) { + if (!isspace(string[i])) { + return 0; + } + } + + return 1; +} + +char *resolve_relative_path(apr_pool_t *pool, const char *parent_filename, const char *filename) { + if (filename == NULL) return NULL; + // TODO Support paths on operating systems other than Unix. + if (filename[0] == '/') return (char *)filename; + + return apr_pstrcat(pool, apr_pstrndup(pool, parent_filename, + strlen(parent_filename) - strlen(apr_filepath_name_get(parent_filename))), + filename, NULL); +} diff --git a/apache2/msc_util.h b/apache2/msc_util.h index 9a5548ae..039b97c1 100644 --- a/apache2/msc_util.h +++ b/apache2/msc_util.h @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -13,13 +13,13 @@ #include "modsecurity.h" -int DSOLOCAL normalise_path_inplace(unsigned char *input, int len, int win); +int DSOLOCAL normalise_path_inplace(unsigned char *input, int len, int win, int *changed); int DSOLOCAL parse_boolean(const char *input); int DSOLOCAL parse_name_eq_value(apr_pool_t *mp, const char *input, char **name, char **value); -char DSOLOCAL *url_encode(apr_pool_t *mp, char *input, unsigned int input_len); +char DSOLOCAL *url_encode(apr_pool_t *mp, char *input, unsigned int input_len, int *changed); char DSOLOCAL *strnurlencat(char *destination, char *source, unsigned int maxlen); @@ -35,7 +35,7 @@ int DSOLOCAL is_token_char(unsigned char c); int DSOLOCAL remove_lf_crlf_inplace(char *text); -unsigned DSOLOCAL char x2c(unsigned char *what); +unsigned char DSOLOCAL x2c(unsigned char *what); char DSOLOCAL *guess_tmp_dir(apr_pool_t *p); @@ -59,17 +59,27 @@ char DSOLOCAL *log_escape_nq_ex(apr_pool_t *p, const char *text, unsigned long i char DSOLOCAL *log_escape_header_name(apr_pool_t *p, const char *text); -char *log_escape_raw(apr_pool_t *mp, const unsigned char *text, unsigned long int text_length); +char DSOLOCAL *log_escape_hex(apr_pool_t *mp, const unsigned char *text, unsigned long int text_length); + +char DSOLOCAL *log_escape_raw(apr_pool_t *mp, const unsigned char *text, unsigned long int text_length); char DSOLOCAL *_log_escape(apr_pool_t *p, const unsigned char *input, unsigned long int input_length, int escape_quotes, int escape_colon); -int DSOLOCAL urldecode_uni_nonstrict_inplace_ex(unsigned char *input, long int input_length); +int DSOLOCAL js_decode_nonstrict_inplace(unsigned char *input, long int input_len); -int DSOLOCAL urldecode_nonstrict_inplace_ex(unsigned char *input, long int input_length, int *invalid_count); +int DSOLOCAL urldecode_uni_nonstrict_inplace_ex(unsigned char *input, long int input_length, int * changed); + +int DSOLOCAL urldecode_nonstrict_inplace_ex(unsigned char *input, long int input_length, int *invalid_count, int *changed); int DSOLOCAL html_entities_decode_inplace(apr_pool_t *mp, unsigned char *input, int len); int DSOLOCAL ansi_c_sequences_decode_inplace(unsigned char *input, int len); +char DSOLOCAL *modsec_build(apr_pool_t *mp); + +int DSOLOCAL is_empty_string(const char *string); + +char DSOLOCAL *resolve_relative_path(apr_pool_t *pool, const char *parent_filename, const char *filename); + #endif diff --git a/apache2/msc_xml.c b/apache2/msc_xml.c index df1e62d5..3b5c133a 100644 --- a/apache2/msc_xml.c +++ b/apache2/msc_xml.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -8,8 +8,6 @@ * write to Breach Security, Inc. at support@breach.com. * */ -#ifdef WITH_LIBXML2 - #include "msc_xml.h" @@ -30,14 +28,14 @@ int xml_init(modsec_rec *msr, char **error_msg) { static void xml_receive_sax_error(void *data, const char *msg, ...) { modsec_rec *msr = (modsec_rec *)data; char message[256]; - + if (msr == NULL) return; - apr_snprintf(message, sizeof(message), "%s (line %i pos %i)", + apr_snprintf(message, sizeof(message), "%s (line %d offset %d)", log_escape_nq(msr->mp, msr->xml->parsing_ctx->lastError.message), msr->xml->parsing_ctx->lastError.line, msr->xml->parsing_ctx->lastError.int2); - + msr_log(msr, 5, "XML: Parsing error: %s", message); } #endif @@ -109,7 +107,7 @@ int xml_complete(modsec_rec *msr, char **error_msg) { /* Clean up everything else. */ xmlFreeParserCtxt(msr->xml->parsing_ctx); msr->xml->parsing_ctx = NULL; - msr_log(msr, 4, "XML: Parsing complete (well_formed %i).", msr->xml->well_formed); + msr_log(msr, 4, "XML: Parsing complete (well_formed %u).", msr->xml->well_formed); if (msr->xml->well_formed != 1) { *error_msg = apr_psprintf(msr->mp, "XML: Failed parsing document."); @@ -131,5 +129,3 @@ apr_status_t xml_cleanup(modsec_rec *msr) { return 1; } - -#endif diff --git a/apache2/msc_xml.h b/apache2/msc_xml.h index f8da4adb..389d2996 100644 --- a/apache2/msc_xml.h +++ b/apache2/msc_xml.h @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, diff --git a/apache2/pdf_protect.c b/apache2/pdf_protect.c index 15c2caaf..ed401f33 100644 --- a/apache2/pdf_protect.c +++ b/apache2/pdf_protect.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -9,6 +9,7 @@ * */ #include "modsecurity.h" +#include "apache2.h" #include "pdf_protect.h" #include @@ -48,14 +49,14 @@ static char *create_hash(modsec_rec *msr, msr_log(msr, 1, "PdfProtect: Unable to generate hash. Please configure SecPdfProtectSecret."); return NULL; } - + /* Our protection token is made out of the client's IP * address, the secret key, and the token expiry time. */ content = apr_pstrcat(msr->mp, msr->remote_addr, msr->txcfg->pdfp_secret, time_string, NULL); if (content == NULL) return NULL; - + return encode_sha1_base64(msr->mp, content); } @@ -63,7 +64,7 @@ static char *create_hash(modsec_rec *msr, * */ static char *create_token(modsec_rec *msr) { - unsigned int current_time; + apr_time_t current_time; const char *time_string = NULL; const char *hash = NULL; int timeout = DEFAULT_TIMEOUT; @@ -71,14 +72,14 @@ static char *create_token(modsec_rec *msr) { if (msr->txcfg->pdfp_timeout != -1) { timeout = msr->txcfg->pdfp_timeout; } - + current_time = apr_time_sec(apr_time_now()); - time_string = apr_psprintf(msr->mp, "%i", current_time + timeout); + time_string = apr_psprintf(msr->mp, "%" APR_TIME_T_FMT, (apr_time_t)(current_time + timeout)); if (time_string == NULL) return NULL; - + hash = create_hash(msr, time_string); if (hash == NULL) return NULL; - + return apr_pstrcat(msr->mp, hash, "|", time_string, NULL); } @@ -97,17 +98,17 @@ static char *construct_new_uri(modsec_rec *msr) { if (msr->txcfg->pdfp_token_name != NULL) { token_name = msr->txcfg->pdfp_token_name; } - + token_parameter = apr_pstrcat(msr->mp, token_name, "=", token, NULL); if (token_parameter == NULL) return NULL; - + if (msr->r->args == NULL) { /* No other parameters. */ new_uri = apr_pstrcat(msr->mp, msr->r->uri, "?", token_parameter, "#PDFP", NULL); } else { /* Preserve existing paramters. */ new_uri = apr_pstrcat(msr->mp, msr->r->uri, "?", msr->r->args, "&", token_parameter, "#PDFP", NULL); } - + return (char *)new_uri; } @@ -118,7 +119,7 @@ static char *extract_token(modsec_rec *msr) { char *search_string = NULL; char *p = NULL, *t = NULL; const char *token_name = DEFAULT_TOKEN_NAME; - + if ((msr->r == NULL)||(msr->r->args == NULL)) { return NULL; } @@ -126,18 +127,18 @@ static char *extract_token(modsec_rec *msr) { if (msr->txcfg->pdfp_token_name != NULL) { token_name = msr->txcfg->pdfp_token_name; } - + search_string = apr_pstrcat(msr->mp, msr->txcfg->pdfp_token_name, "=", NULL); if (search_string == NULL) return NULL; - + p = strstr(msr->r->args, search_string); if (p == NULL) return NULL; t = p = p + strlen(search_string); while ((*t != '\0')&&(*t != '&')) t++; - + return apr_pstrmemdup(msr->mp, p, t - p); -} +} /** * @@ -165,11 +166,11 @@ static int verify_token(modsec_rec *msr, const char *token, char **error_msg) { if (error_msg == NULL) return 0; *error_msg = NULL; - + /* Split token into its parts - hash and expiry time. */ p = strstr(token, "|"); if (p == NULL) return 0; - + given_hash = apr_pstrmemdup(msr->mp, token, p - token); time_string = p + 1; if (!validate_time_string(time_string)) { @@ -193,27 +194,10 @@ static int verify_token(modsec_rec *msr, const char *token, char **error_msg) { *error_msg = apr_psprintf(msr->mp, "PdfProtect: Token has expired."); return 0; } - + return 1; } -/** - * - */ -static apr_status_t send_error_bucket(ap_filter_t *f, int status) { - apr_bucket_brigade *brigade = NULL; - apr_bucket *bucket = NULL; - - brigade = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc); - if (brigade == NULL) return APR_EGENERAL; - bucket = ap_bucket_error_create(status, NULL, f->r->pool, f->r->connection->bucket_alloc); - if (bucket == NULL) return APR_EGENERAL; - APR_BRIGADE_INSERT_TAIL(brigade, bucket); - - f->r->connection->keepalive = AP_CONN_CLOSE; - return ap_pass_brigade(f->next, brigade); -} - /** * */ @@ -226,7 +210,7 @@ apr_status_t pdfp_output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { ap_remove_output_filter(f); - return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR); + return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR); } if (msr->txcfg->pdfp_enabled == 1) { @@ -313,7 +297,7 @@ apr_status_t pdfp_output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { /* Locate the protection token. */ token = extract_token(msr); - + if (token == NULL) { /* No token. */ char *new_uri = NULL; @@ -328,13 +312,15 @@ apr_status_t pdfp_output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { apr_table_set(r->headers_out, "Location", new_uri); - return send_error_bucket(f, REDIRECT_STATUS); + ap_remove_output_filter(f); + + return send_error_bucket(msr, f, REDIRECT_STATUS); } } else { /* Token found. */ char *my_error_msg = NULL; /* Verify the token is valid. */ - + if (verify_token(msr, token, &my_error_msg)) { /* Valid. */ /* Do nothing - serve the PDF file. */ if (msr->txcfg->debuglog_level >= 9) { @@ -354,12 +340,12 @@ apr_status_t pdfp_output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { apr_table_set(r->headers_out, "Content-Disposition", DISPOSITION_VALUE); r->content_type = ATTACHMENT_MIME_TYPE; - + /* Fall through. */ } } } - } + } ap_remove_output_filter(f); @@ -436,7 +422,7 @@ int pdfp_check(modsec_rec *msr) { if ((msr->r->method_number != M_GET)&&(cfg->pdfp_only_get != 0)) { if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "PdfProtect: Not intercepting a GET/HEAD request " - "(method=%s/%i).", log_escape_nq(msr->mp, msr->r->method), msr->r->method_number); + "(method=%s/%d).", log_escape_nq(msr->mp, msr->r->method), msr->r->method_number); } return 0; @@ -449,14 +435,14 @@ int pdfp_check(modsec_rec *msr) { /* Locate the protection token. */ token = extract_token(msr); - + if (token == NULL) { /* No token. */ char *new_uri = NULL; /* Create a new URI with the protection token inside. */ new_uri = construct_new_uri(msr); if (new_uri == NULL) return DECLINED; - + /* Redirect user to the new URI. */ if (msr->txcfg->debuglog_level >= 9) { msr_log(msr, 9, "PdfProtect: PDF request without a token - redirecting to %s.", @@ -493,7 +479,7 @@ int pdfp_check(modsec_rec *msr) { apr_table_set(msr->r->headers_out, "Content-Disposition", DISPOSITION_VALUE); msr->r->content_type = ATTACHMENT_MIME_TYPE; apr_table_set(msr->r->notes, NOTE_TWEAK_HEADERS, "1"); - + /* Proceed with response (PDF) generation. */ return 0; } diff --git a/apache2/pdf_protect.h b/apache2/pdf_protect.h index 8936eb55..8613193a 100644 --- a/apache2/pdf_protect.h +++ b/apache2/pdf_protect.h @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, diff --git a/apache2/persist_dbm.c b/apache2/persist_dbm.c index f5c31cef..cf9ac6ec 100644 --- a/apache2/persist_dbm.c +++ b/apache2/persist_dbm.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -46,7 +46,7 @@ static apr_table_t *collection_unpack(modsec_rec *msr, char *blob, unsigned int blob_offset += var->value_len; var->value_len--; - if (log_vars) { + if (log_vars && (msr->txcfg->debuglog_level >= 9)) { msr_log(msr, 9, "Read variable: name \"%s\", value \"%s\".", log_escape_ex(msr->mp, var->name, var->name_len), log_escape_ex(msr->mp, var->value, var->value_len)); @@ -72,40 +72,47 @@ apr_table_t *collection_retrieve(modsec_rec *msr, const char *col_name, apr_table_t *col = NULL; const apr_array_header_t *arr; apr_table_entry_t *te; + int expired = 0; int i; if (msr->txcfg->data_dir == NULL) { msr_log(msr, 1, "Unable to retrieve collection (name \"%s\", key \"%s\"). Use " "SecDataDir to define data directory first.", log_escape(msr->mp, col_name), - log_escape(msr->mp, col_key)); + log_escape_ex(msr->mp, col_key, col_key_len)); return NULL; } dbm_filename = apr_pstrcat(msr->mp, msr->txcfg->data_dir, "/", col_name, NULL); + key.dptr = (char *)col_key; + key.dsize = col_key_len + 1; + rc = apr_sdbm_open(&dbm, dbm_filename, APR_READ | APR_SHARELOCK, CREATEMODE, msr->mp); if (rc != APR_SUCCESS) { return NULL; } - key.dptr = (char *)col_key; - key.dsize = col_key_len + 1; - value = (apr_sdbm_datum_t *)apr_pcalloc(msr->mp, sizeof(apr_sdbm_datum_t)); rc = apr_sdbm_fetch(dbm, value, key); + + apr_sdbm_close(dbm); + if (rc != APR_SUCCESS) { - apr_sdbm_close(dbm); msr_log(msr, 1, "Failed to read from DBM file \"%s\": %s", log_escape(msr->mp, dbm_filename), get_apr_error(msr->mp, rc)); return NULL; } - + if (value->dptr == NULL) { /* Key not found in DBM file. */ - apr_sdbm_close(dbm); return NULL; } + /* ENH Need expiration (and perhaps other metadata) accessible in blob + * form so we can determine if we need to convert to a table. This will + * save some cycles. + */ + /* Transform raw data into a table. */ col = collection_unpack(msr, value->dptr, value->dsize, 1); if (col == NULL) return NULL; @@ -119,22 +126,63 @@ apr_table_t *collection_retrieve(modsec_rec *msr, const char *col_name, msc_string *var = (msc_string *)te[i].val; int expiry_time = atoi(var->value); - /* Do not remove the record itself. */ - if (strcmp(te[i].key, "__expire_KEY") == 0) continue; - if (expiry_time <= apr_time_sec(msr->request_time)) { - char *key_to_expire = apr_pstrdup(msr->mp, te[i].key); - msr_log(msr, 9, "Removing key \"%s\" from collection.", key_to_expire + 9); + char *key_to_expire = te[i].key; + + /* Done early if the col expired */ + if (strcmp(key_to_expire, "__expire_KEY") == 0) { + expired = 1; + } + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Removing key \"%s\" from collection.", key_to_expire + 9); + msr_log(msr, 9, "Removing key \"%s\" from collection.", key_to_expire); + } apr_table_unset(col, key_to_expire + 9); - msr_log(msr, 9, "Removing key \"%s\" from collection.", key_to_expire); apr_table_unset(col, key_to_expire); - msr_log(msr, 4, "Removed expired variable \"%s\".", key_to_expire + 9); + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Removed expired variable \"%s\".", key_to_expire + 9); + } break; } } } - } while(i != arr->nelts); - + } while(!expired && (i != arr->nelts)); + + /* Delete the collection if the variable "KEY" does not exist. + * + * ENH It would probably be more efficient to hold the DBM + * open until we determine if it needs deleted than to open a second + * time. + */ + if (apr_table_get(col, "KEY") == NULL) { + rc = apr_sdbm_open(&dbm, dbm_filename, APR_CREATE | APR_WRITE | APR_SHARELOCK, + CREATEMODE, msr->mp); + if (rc != APR_SUCCESS) { + msr_log(msr, 1, "Failed to access DBM file \"%s\": %s", + log_escape(msr->mp, dbm_filename), get_apr_error(msr->mp, rc)); + return NULL; + } + + rc = apr_sdbm_delete(dbm, key); + + apr_sdbm_close(dbm); + + if (rc != APR_SUCCESS) { + msr_log(msr, 1, "Failed deleting collection (name \"%s\", " + "key \"%s\"): %s", log_escape(msr->mp, col_name), + log_escape_ex(msr->mp, col_key, col_key_len), get_apr_error(msr->mp, rc)); + return NULL; + } + + if (expired && (msr->txcfg->debuglog_level >= 9)) { + msr_log(msr, 9, "Collection expired (name \"%s\", key \"%s\").", col_name, log_escape_ex(msr->mp, col_key, col_key_len)); + } + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Deleted collection (name \"%s\", key \"%s\").", + log_escape(msr->mp, col_name), log_escape_ex(msr->mp, col_key, col_key_len)); + } + return NULL; + } /* Update UPDATE_RATE */ { @@ -150,34 +198,33 @@ apr_table_t *collection_retrieve(modsec_rec *msr, const char *col_name, if (var == NULL) { /* Error. */ } else { - int td; + apr_time_t td; counter = atoi(var->value); - var = (msc_string *)apr_table_get(col, "UPDATE_RATE"); - if (var == NULL) { - var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); - var->name = "UPDATE_RATE"; - var->name_len = strlen(var->name); - apr_table_setn(col, var->name, (void *)var); - } + + /* UPDATE_RATE is removed on store, so we add it back here */ + var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + var->name = "UPDATE_RATE"; + var->name_len = strlen(var->name); + apr_table_setn(col, var->name, (void *)var); /* NOTE: No rate if there has been no time elapsed */ td = (apr_time_sec(apr_time_now()) - create_time); if (td == 0) { - var->value = apr_psprintf(msr->mp, "%i", 0); + var->value = apr_psprintf(msr->mp, "%d", 0); } else { - var->value = apr_psprintf(msr->mp, "%i", - (int)((60 * counter)/td)); + var->value = apr_psprintf(msr->mp, "%" APR_TIME_T_FMT, + (apr_time_t)((60 * counter)/td)); } var->value_len = strlen(var->value); } } } - apr_sdbm_close(dbm); - - msr_log(msr, 4, "Retrieved collection (name \"%s\", key \"%s\").", - log_escape(msr->mp, col_name), log_escape(msr->mp, col_key)); + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Retrieved collection (name \"%s\", key \"%s\").", + log_escape(msr->mp, col_name), log_escape_ex(msr->mp, col_key, col_key_len)); + } return col; } @@ -211,66 +258,17 @@ int collection_store(modsec_rec *msr, apr_table_t *col) { if (msr->txcfg->data_dir == NULL) { msr_log(msr, 1, "Unable to store collection (name \"%s\", key \"%s\"). Use " "SecDataDir to define data directory first.", - log_escape(msr->mp, var_name->value), log_escape(msr->mp, var_key->value)); + log_escape_ex(msr->mp, var_name->value, var_name->value_len), log_escape_ex(msr->mp, var_key->value, var_key->value_len)); return -1; } - dbm_filename = apr_pstrcat(msr->mp, msr->txcfg->data_dir, "/", var_name->value, NULL); + dbm_filename = apr_pstrcat(msr->mp, msr->txcfg->data_dir, "/", var_name->value, NULL); - /* Remove expired variables. */ - do { - arr = apr_table_elts(col); - te = (apr_table_entry_t *)arr->elts; - for (i = 0; i < arr->nelts; i++) { - if (strncmp(te[i].key, "__expire_", 9) == 0) { - msc_string *var = (msc_string *)te[i].val; - int expiry_time = atoi(var->value); + /* Delete IS_NEW on store. */ + apr_table_unset(col, "IS_NEW"); - /* Do not remove the record itself. */ - if (strcmp(te[i].key, "__expire_KEY") == 0) continue; - - if (expiry_time <= apr_time_sec(msr->request_time)) { - char *key_to_expire = apr_pstrdup(msr->mp, te[i].key); - msr_log(msr, 9, "Removing key \"%s\" from collection.", key_to_expire + 9); - apr_table_unset(col, key_to_expire + 9); - msr_log(msr, 9, "Removing key \"%s\" from collection.", key_to_expire); - apr_table_unset(col, key_to_expire); - msr_log(msr, 4, "Removed expired variable \"%s\".", key_to_expire + 9); - break; - } - } - } - } while(i != arr->nelts); - - /* Delete the collection if the variable "KEY" does not exist. */ - if (apr_table_get(col, "KEY") == NULL) { - - rc = apr_sdbm_open(&dbm, dbm_filename, APR_CREATE | APR_WRITE | APR_SHARELOCK, - CREATEMODE, msr->mp); - if (rc != APR_SUCCESS) { - msr_log(msr, 1, "Failed to access DBM file \"%s\": %s", - log_escape(msr->mp, dbm_filename), get_apr_error(msr->mp, rc)); - return -1; - } - - key.dptr = var_key->value; - key.dsize = var_key->value_len + 1; - - rc = apr_sdbm_delete(dbm, key); - if (rc != APR_SUCCESS) { - msr_log(msr, 1, "Failed deleting collection (name \"%s\", " - "key \"%s\"): %s", log_escape(msr->mp, var_name->value), - log_escape(msr->mp, var_key->value), get_apr_error(msr->mp, rc)); - apr_sdbm_close(dbm); - return -1; - } - - msr_log(msr, 4, "Deleted collection (name \"%s\", key \"%s\").", - log_escape(msr->mp, var_name->value), log_escape(msr->mp, var_key->value)); - apr_sdbm_close(dbm); - - return 1; - } + /* Delete UPDATE_RATE on store to save space as it is calculated */ + apr_table_unset(col, "UPDATE_RATE"); /* Update the timeout value. */ { @@ -279,7 +277,7 @@ int collection_store(modsec_rec *msr, apr_table_t *col) { int timeout = atoi(var->value); var = (msc_string *)apr_table_get(col, "__expire_KEY"); if (var != NULL) { - var->value = apr_psprintf(msr->mp, "%i", (int)(apr_time_sec(apr_time_now()) + timeout)); + var->value = apr_psprintf(msr->mp, "%" APR_TIME_T_FMT, (apr_time_t)(apr_time_sec(apr_time_now()) + timeout)); var->value_len = strlen(var->value); } } @@ -294,7 +292,7 @@ int collection_store(modsec_rec *msr, apr_table_t *col) { var->name_len = strlen(var->name); apr_table_setn(col, var->name, (void *)var); } - var->value = apr_psprintf(msr->mp, "%i", (int)(apr_time_sec(apr_time_now()))); + var->value = apr_psprintf(msr->mp, "%" APR_TIME_T_FMT, (apr_time_t)(apr_time_sec(apr_time_now()))); var->value_len = strlen(var->value); } @@ -310,10 +308,15 @@ int collection_store(modsec_rec *msr, apr_table_t *col) { } else { counter = atoi(var->value); } - var->value = apr_psprintf(msr->mp, "%i", counter + 1); + var->value = apr_psprintf(msr->mp, "%d", counter + 1); var->value_len = strlen(var->value); } + /* ENH Make the expiration timestamp accessible in blob form so that + * it is easier/faster to determine expiration without having to + * convert back to table form + */ + /* Calculate the size first. */ blob_size = 3 + 2; arr = apr_table_elts(col); @@ -366,16 +369,24 @@ int collection_store(modsec_rec *msr, apr_table_t *col) { blob[blob_offset + 2 + len - 1] = '\0'; blob_offset += 2 + len; - msr_log(msr, 9, "Wrote variable: name \"%s\", value \"%s\".", - log_escape_ex(msr->mp, var->name, var->name_len), - log_escape_ex(msr->mp, var->value, var->value_len)); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Wrote variable: name \"%s\", value \"%s\".", + log_escape_ex(msr->mp, var->name, var->name_len), + log_escape_ex(msr->mp, var->value, var->value_len)); + } } blob[blob_offset] = 0; blob[blob_offset + 1] = 0; /* And, finally, store it. */ - dbm_filename = apr_pstrcat(msr->mp, msr->txcfg->data_dir, "/", var_name->value, NULL); + dbm_filename = apr_pstrcat(msr->mp, msr->txcfg->data_dir, "/", var_name->value, NULL); + + key.dptr = var_key->value; + key.dsize = var_key->value_len + 1; + + value.dptr = (char *)blob; + value.dsize = blob_size; rc = apr_sdbm_open(&dbm, dbm_filename, APR_CREATE | APR_WRITE | APR_SHARELOCK, CREATEMODE, msr->mp); @@ -385,26 +396,22 @@ int collection_store(modsec_rec *msr, apr_table_t *col) { return -1; } - key.dptr = var_key->value; - key.dsize = var_key->value_len + 1; - - value.dptr = (char *)blob; - value.dsize = blob_size; - rc = apr_sdbm_store(dbm, key, value, APR_SDBM_REPLACE); - if (rc != APR_SUCCESS) { - msr_log(msr, 1, "Failed to write to DBM file \"%s\": %s", dbm_filename, - get_apr_error(msr->mp, rc)); - apr_sdbm_close(dbm); - return -1; - } - - msr_log(msr, 4, "Persisted collection (name \"%s\", key \"%s\").", - log_escape(msr->mp, var_name->value), log_escape(msr->mp, var_key->value)); apr_sdbm_close(dbm); - return 0; + if (rc != APR_SUCCESS) { + msr_log(msr, 1, "Failed to write to DBM file \"%s\": %s", dbm_filename, + get_apr_error(msr->mp, rc)); + return -1; + } + + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Persisted collection (name \"%s\", key \"%s\").", + log_escape_ex(msr->mp, var_name->value, var_name->value_len), log_escape_ex(msr->mp, var_key->value, var_key->value_len)); + } + + return 0; } /** @@ -418,10 +425,10 @@ int collections_remove_stale(modsec_rec *msr, const char *col_name) { apr_array_header_t *keys_arr; char **keys; int i; - unsigned int now = (unsigned int)apr_time_sec(msr->request_time); + apr_time_t now = apr_time_sec(msr->request_time); if (msr->txcfg->data_dir == NULL) { - /* The user has been warned about this problem enough times already by now. + /* The user has been warned about this problem enough times already by now. * msr_log(msr, 1, "Unable to access collection file (name \"%s\"). Use SecDataDir to " * "define data directory first.", log_escape(msr->mp, col_name)); */ @@ -453,14 +460,16 @@ int collections_remove_stale(modsec_rec *msr, const char *col_name) { */ rc = apr_sdbm_firstkey(dbm, &key); while(rc == APR_SUCCESS) { - char *s = apr_pstrmemdup(msr->mp, key.dptr, key.dsize); + char *s = apr_pstrmemdup(msr->mp, key.dptr, key.dsize - 1); *(char **)apr_array_push(keys_arr) = s; rc = apr_sdbm_nextkey(dbm, &key); } apr_sdbm_unlock(dbm); - msr_log(msr, 9, "Found %i record(s) in file \"%s\".", keys_arr->nelts, - log_escape(msr->mp, dbm_filename)); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Found %d record(s) in file \"%s\".", keys_arr->nelts, + log_escape(msr->mp, dbm_filename)); + } /* Now retrieve the entires one by one. */ keys = (char **)keys_arr->elts; @@ -482,6 +491,7 @@ int collections_remove_stale(modsec_rec *msr, const char *col_name) { col = collection_unpack(msr, value.dptr, value.dsize, 0); if (col == NULL) { + apr_sdbm_close(dbm); return -1; } @@ -489,25 +499,30 @@ int collections_remove_stale(modsec_rec *msr, const char *col_name) { if (var == NULL) { msr_log(msr, 1, "Collection cleanup discovered entry with no " "__expire_KEY (name \"%s\", key \"%s\").", - log_escape(msr->mp, col_name), log_escape(msr->mp, key.dptr)); + log_escape(msr->mp, col_name), log_escape_ex(msr->mp, key.dptr, key.dsize - 1)); } else { unsigned int expiry_time = atoi(var->value); - msr_log(msr, 9, "Record (name \"%s\", key \"%s\") set to expire in %i seconds.", - log_escape(msr->mp, col_name), log_escape(msr->mp, key.dptr), - expiry_time - now); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Record (name \"%s\", key \"%s\") set to expire in %" APR_TIME_T_FMT " seconds.", + log_escape(msr->mp, col_name), log_escape_ex(msr->mp, key.dptr, key.dsize - 1), + expiry_time - now); + } if (expiry_time <= now) { rc = apr_sdbm_delete(dbm, key); if (rc != APR_SUCCESS) { msr_log(msr, 1, "Failed deleting collection (name \"%s\", " "key \"%s\"): %s", log_escape(msr->mp, col_name), - log_escape(msr->mp, key.dptr), get_apr_error(msr->mp, rc)); + log_escape_ex(msr->mp, key.dptr, key.dsize - 1), get_apr_error(msr->mp, rc)); + apr_sdbm_close(dbm); return -1; } - msr_log(msr, 4, "Removed stale collection (name \"%s\", " - "key \"%s\").", log_escape(msr->mp, col_name), - log_escape(msr->mp, key.dptr)); + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Removed stale collection (name \"%s\", " + "key \"%s\").", log_escape(msr->mp, col_name), + log_escape_ex(msr->mp, key.dptr, key.dsize - 1)); + } } } } else { diff --git a/apache2/persist_dbm.h b/apache2/persist_dbm.h index 6a5af98f..4bf2d0a9 100644 --- a/apache2/persist_dbm.h +++ b/apache2/persist_dbm.h @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, diff --git a/apache2/re.c b/apache2/re.c index 18e96405..e6af7111 100644 --- a/apache2/re.c +++ b/apache2/re.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -12,11 +12,126 @@ #include "re.h" +#if defined(WITH_LUA) +#include "msc_lua.h" +#endif + +static const char *const severities[] = { + "EMERGENCY", + "ALERT", + "CRITICAL", + "ERROR", + "WARNING", + "NOTICE", + "INFO", + "DEBUG", + NULL, +}; /* -- Actions, variables, functions and operator functions ----------------- */ /** - * Creates msre_var instances (rule variables) out of the + * Remove actions with the same cardinality group from the actionset. + */ +static void msre_actionset_cardinality_fixup(msre_actionset *actionset, msre_action *action) { + const apr_array_header_t *tarr = NULL; + const apr_table_entry_t *telts = NULL; + int i; + + if ((actionset == NULL) || (action == NULL)) return; + + tarr = apr_table_elts(actionset->actions); + telts = (const apr_table_entry_t*)tarr->elts; + + for (i = 0; i < tarr->nelts; i++) { + msre_action *target = (msre_action *)telts[i].val; + if (target->metadata->cardinality_group == action->metadata->cardinality_group) { + + apr_table_unset(actionset->actions, target->metadata->name); + } + } +} + +/** + * Generate an action string from an actionset. + */ +char *msre_actionset_generate_action_string(apr_pool_t *pool, const msre_actionset *actionset) +{ + const apr_array_header_t *tarr = NULL; + const apr_table_entry_t *telts = NULL; + char *actions = NULL; + int i; + + if (actionset == NULL) return apr_pstrdup(pool, ""); + + tarr = apr_table_elts(actionset->actions); + telts = (const apr_table_entry_t*)tarr->elts; + + for (i = 0; i < tarr->nelts; i++) { + msre_action *action = (msre_action *)telts[i].val; + int use_quotes = 0; + + /* Check if we need any quotes */ + if (action->param != NULL) { + int j; + for(j = 0; action->param[j] != '\0'; j++) { + if (isspace(action->param[j])) { + use_quotes = 1; + break; + } + } + if (j == 0) use_quotes = 1; + } + + actions = apr_pstrcat(pool, + (actions == NULL) ? "" : actions, + (actions == NULL) ? "" : ",", + action->metadata->name, + (action->param == NULL) ? "" : ":", + (use_quotes) ? "'" : "", + (action->param == NULL) ? "" : action->param, + (use_quotes) ? "'" : "", + NULL); + } + + return (actions == NULL) ? apr_pstrdup(pool, "") : actions; +} + +/** + * Add an action to an actionset. + */ +static void msre_actionset_action_add(msre_actionset *actionset, msre_action *action) +{ + msre_action *add_action = action; + + if ((actionset == NULL)) return; + + /** + * The "block" action is just a placeholder for the parent action. + */ + if ((actionset->parent_intercept_action_rec != NULL) && (actionset->parent_intercept_action_rec != NOT_SET_P) && (strcmp("block", action->metadata->name) == 0) && (strcmp("block", action->metadata->name) == 0)) { + /* revert back to parent */ + actionset->intercept_action = actionset->parent_intercept_action; + add_action = actionset->parent_intercept_action_rec; + } + + if ((add_action == NULL)) return; + + if (add_action->metadata->cardinality_group != ACTION_CGROUP_NONE) { + msre_actionset_cardinality_fixup(actionset, add_action); + } + + if (add_action->metadata->cardinality == ACTION_CARDINALITY_ONE) { + /* One action per actionlist. */ + apr_table_setn(actionset->actions, add_action->metadata->name, (void *)add_action); + } else { + /* Multiple actions per actionlist. */ + apr_table_addn(actionset->actions, add_action->metadata->name, (void *)add_action); + } +} + +/** + * Creates msre_var instances (rule variables) out of the * given text string and places them into the supplied table. */ apr_status_t msre_parse_targets(msre_ruleset *ruleset, const char *text, @@ -29,14 +144,14 @@ apr_status_t msre_parse_targets(msre_ruleset *ruleset, const char *text, apr_status_t rc; msre_var *var; int i; - + if (text == NULL) return -1; /* Extract name & value pairs first */ vartable = apr_table_make(ruleset->mp, 10); if (vartable == NULL) return -1; rc = msre_parse_generic(ruleset->mp, text, vartable, error_msg); - if (rc < 0) return rc; + if (rc < 0) return rc; /* Loop through the table and create variables */ tarr = apr_table_elts(vartable); @@ -66,13 +181,13 @@ apr_status_t msre_parse_actions(msre_engine *engine, msre_actionset *actionset, msre_action *action; int i; - if (text == NULL) return -1; + if (text == NULL) return -1; /* Extract name & value pairs first */ vartable = apr_table_make(engine->mp, 10); if (vartable == NULL) return -1; rc = msre_parse_generic(engine->mp, text, vartable, error_msg); - if (rc < 0) return rc; + if (rc < 0) return rc; /* Loop through the table and create actions */ tarr = apr_table_elts(vartable); @@ -87,13 +202,7 @@ apr_status_t msre_parse_actions(msre_engine *engine, msre_actionset *actionset, action->metadata->init(engine, actionset, action); } - if (action->metadata->cardinality == ACTION_CARDINALITY_ONE) { - /* One action per actionlist. */ - apr_table_setn(actionset->actions, action->metadata->name, (void *)action); - } else { - /* Multiple actions per actionlist. */ - apr_table_addn(actionset->actions, action->metadata->name, (void *)action); - } + msre_actionset_action_add(actionset, action); count++; } @@ -105,14 +214,14 @@ apr_status_t msre_parse_actions(msre_engine *engine, msre_actionset *actionset, * Locates variable metadata given the variable name. */ msre_var_metadata *msre_resolve_var(msre_engine *engine, const char *name) { - return (msre_var_metadata *)apr_table_get(engine->variables, name); + return (msre_var_metadata *)apr_table_get(engine->variables, name); } /** * Locates action metadata given the action name. */ msre_action_metadata *msre_resolve_action(msre_engine *engine, const char *name) { - return (msre_action_metadata *)apr_table_get(engine->actions, name); + return (msre_action_metadata *)apr_table_get(engine->actions, name); } /** @@ -196,7 +305,6 @@ msre_var *msre_create_var(msre_ruleset *ruleset, const char *name, const char *p if (var->metadata->validate != NULL) { *error_msg = var->metadata->validate(ruleset, var); if (*error_msg != NULL) { - /* ENH Shouldn't we log the problem? */ return NULL; } } @@ -304,7 +412,7 @@ int msre_parse_generic(apr_pool_t *mp, const char *text, apr_table_t *vartable, /* go over any whitespace present */ while(isspace(*p)) p++; - + /* we're done */ if (*p == '\0') { return count; @@ -316,7 +424,7 @@ int msre_parse_generic(apr_pool_t *mp, const char *text, apr_table_t *vartable, continue; } - *error_msg = apr_psprintf(mp, "Unexpected character at position %i: %s", + *error_msg = apr_psprintf(mp, "Unexpected character at position %d: %s", (int)(p - text), text); return -1; } @@ -351,20 +459,20 @@ int msre_parse_generic(apr_pool_t *mp, const char *text, apr_table_t *vartable, for(;;) { if (*p == '\0') { - *error_msg = apr_psprintf(mp, "Missing closing quote at position %i: %s", + *error_msg = apr_psprintf(mp, "Missing closing quote at position %d: %s", (int)(p - text), text); free(value); return -1; } else if (*p == '\\') { if ( (*(p + 1) == '\0') || ((*(p + 1) != '\'')&&(*(p + 1) != '\\')) ) { - *error_msg = apr_psprintf(mp, "Invalid quoted pair at position %i: %s", + *error_msg = apr_psprintf(mp, "Invalid quoted pair at position %d: %s", (int)(p - text), text); free(value); return -1; } p++; - *d++ = *p++; + *(d++) = *(p++); } else if (*p == '\'') { *d = '\0'; @@ -372,7 +480,7 @@ int msre_parse_generic(apr_pool_t *mp, const char *text, apr_table_t *vartable, break; } else { - *d++ = *p++; + *(d++) = *(p++); } } @@ -392,7 +500,7 @@ int msre_parse_generic(apr_pool_t *mp, const char *text, apr_table_t *vartable, /* move to the first character of the next name-value pair */ while(isspace(*p)||(*p == ',')||(*p == '|')) p++; } - + return count; } @@ -417,6 +525,7 @@ msre_actionset *msre_actionset_create(msre_engine *engine, const char *text, actionset->id = NOT_SET_P; actionset->rev = NOT_SET_P; actionset->msg = NOT_SET_P; + actionset->logdata = NOT_SET_P; actionset->phase = NOT_SET; actionset->severity = -1; actionset->rule = NOT_SET_P; @@ -424,8 +533,12 @@ msre_actionset *msre_actionset_create(msre_engine *engine, const char *text, /* Flow */ actionset->is_chained = NOT_SET; actionset->skip_count = NOT_SET; + actionset->skip_after = NOT_SET_P; /* Disruptive */ + actionset->parent_intercept_action_rec = NOT_SET_P; + actionset->intercept_action_rec = NOT_SET_P; + actionset->parent_intercept_action = NOT_SET; actionset->intercept_action = NOT_SET; actionset->intercept_uri = NOT_SET_P; actionset->intercept_status = NOT_SET; @@ -442,7 +555,7 @@ msre_actionset *msre_actionset_create(msre_engine *engine, const char *text, } } - return actionset; + return actionset; } /** @@ -492,6 +605,7 @@ msre_actionset *msre_actionset_merge(msre_engine *engine, msre_actionset *parent if (child->id != NOT_SET_P) merged->id = child->id; if (child->rev != NOT_SET_P) merged->rev = child->rev; if (child->msg != NOT_SET_P) merged->msg = child->msg; + if (child->logdata != NOT_SET_P) merged->logdata = child->logdata; if (child->severity != NOT_SET) merged->severity = child->severity; if (child->phase != NOT_SET) merged->phase = child->phase; if (child->rule != NOT_SET_P) merged->rule = child->rule; @@ -499,9 +613,11 @@ msre_actionset *msre_actionset_merge(msre_engine *engine, msre_actionset *parent /* Flow */ merged->is_chained = child->is_chained; if (child->skip_count != NOT_SET) merged->skip_count = child->skip_count; + if (child->skip_after != NOT_SET_P) merged->skip_after = child->skip_after; /* Disruptive */ if (child->intercept_action != NOT_SET) { + merged->intercept_action_rec = child->intercept_action_rec; merged->intercept_action = child->intercept_action; merged->intercept_uri = child->intercept_uri; } @@ -519,12 +635,7 @@ msre_actionset *msre_actionset_merge(msre_engine *engine, msre_actionset *parent tarr = apr_table_elts(child->actions); telts = (const apr_table_entry_t*)tarr->elts; for (i = 0; i < tarr->nelts; i++) { - msre_action *action = (msre_action *)telts[i].val; - if (action->metadata->cardinality == ACTION_CARDINALITY_ONE) { - apr_table_setn(merged->actions, action->metadata->name, (void *)action); - } else { - apr_table_addn(merged->actions, action->metadata->name, (void *)action); - } + msre_actionset_action_add(merged, (msre_action *)telts[i].val); } return merged; @@ -536,18 +647,19 @@ msre_actionset *msre_actionset_merge(msre_engine *engine, msre_actionset *parent msre_actionset *msre_actionset_create_default(msre_engine *engine) { char *my_error_msg = NULL; return msre_actionset_create(engine, - "log,auditlog,deny,status:403,phase:2,t:lowercase,t:replaceNulls,t:compressWhitespace", + "phase:2,log,pass", &my_error_msg); } /** * Sets the default values for the hard-coded actionset configuration. */ -static void msre_actionset_set_defaults(msre_actionset *actionset) { +void msre_actionset_set_defaults(msre_actionset *actionset) { /* Metadata */ if (actionset->id == NOT_SET_P) actionset->id = NULL; if (actionset->rev == NOT_SET_P) actionset->rev = NULL; if (actionset->msg == NOT_SET_P) actionset->msg = NULL; + if (actionset->logdata == NOT_SET_P) actionset->logdata = NULL; if (actionset->phase == NOT_SET) actionset->phase = 2; if (actionset->severity == -1); /* leave at -1 */ if (actionset->rule == NOT_SET_P) actionset->rule = NULL; @@ -555,15 +667,19 @@ static void msre_actionset_set_defaults(msre_actionset *actionset) { /* Flow */ if (actionset->is_chained == NOT_SET) actionset->is_chained = 0; if (actionset->skip_count == NOT_SET) actionset->skip_count = 0; + if (actionset->skip_after == NOT_SET_P) actionset->skip_after = NULL; /* Disruptive */ + if (actionset->parent_intercept_action_rec == NOT_SET_P) actionset->parent_intercept_action_rec = NULL; + if (actionset->intercept_action_rec == NOT_SET_P) actionset->intercept_action_rec = NULL; + if (actionset->parent_intercept_action == NOT_SET) actionset->parent_intercept_action = ACTION_NONE; if (actionset->intercept_action == NOT_SET) actionset->intercept_action = ACTION_NONE; if (actionset->intercept_uri == NOT_SET_P) actionset->intercept_uri = NULL; if (actionset->intercept_status == NOT_SET) actionset->intercept_status = 403; if (actionset->intercept_pause == NOT_SET) actionset->intercept_pause = 0; /* Other */ - if (actionset->auditlog == NOT_SET) actionset->auditlog = 1; + if (actionset->auditlog == NOT_SET) actionset->auditlog = 0; if (actionset->log == NOT_SET) actionset->log = 1; } @@ -592,7 +708,7 @@ msre_engine *msre_engine_create(apr_pool_t *parent_pool) { engine->actions = apr_table_make(mp, 25); if (engine->actions == NULL) return NULL; - return engine; + return engine; } /** @@ -611,15 +727,77 @@ void msre_engine_destroy(msre_engine *engine) { #define NEXT_RULE 2 #define SKIP_RULES 3 + + /** * Default implementation of the ruleset phase processing; it processes * the rules in the ruleset attached to the currently active * transaction phase. */ +#if defined(PERFORMANCE_MEASUREMENT) apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) { + apr_array_header_t *arr = NULL; + msre_rule **rules = NULL; + apr_status_t rc; + apr_time_t time1; + int i; + + switch (msr->phase) { + case PHASE_REQUEST_HEADERS : + arr = ruleset->phase_request_headers; + break; + case PHASE_REQUEST_BODY : + arr = ruleset->phase_request_body; + break; + case PHASE_RESPONSE_HEADERS : + arr = ruleset->phase_response_headers; + break; + case PHASE_RESPONSE_BODY : + arr = ruleset->phase_response_body; + break; + case PHASE_LOGGING : + arr = ruleset->phase_logging; + break; + default : + msr_log(msr, 1, "Internal Error: Invalid phase %d", msr->phase); + return -1; + } + + rules = (msre_rule **)arr->elts; + for (i = 0; i < arr->nelts; i++) { + msre_rule *rule = rules[i]; + rule->execution_time = 0; + } + + time1 = apr_time_now(); + + for (i = 0; i < 10000; i++) { + rc = msre_ruleset_process_phase_(ruleset, msr); + } + + msr_log(msr, 1, "Phase %d: %" APR_TIME_T_FMT " usec", msr->phase, ((apr_time_now() - time1) / 10000)); + + rules = (msre_rule **)arr->elts; + for (i = 0; i < arr->nelts; i++) { + msre_rule *rule = rules[i]; + msr_log(msr, 1, "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"]: %u usec", rule, + ((rule->actionset != NULL)&&(rule->actionset->id != NULL)) ? rule->actionset->id : "-", + rule->filename != NULL ? rule->filename : "-", + rule->line_num, + (rule->execution_time / 10000)); + } + + return rc; +} + +static apr_status_t msre_ruleset_process_phase_(msre_ruleset *ruleset, modsec_rec *msr) { +#else +apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) { +#endif apr_array_header_t *arr = NULL; msre_rule **rules; apr_status_t rc; + const char *skip_after = NULL; int i, mode, skip; /* First determine which set of rules we need to use. */ @@ -645,7 +823,7 @@ apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) } if (msr->txcfg->debuglog_level >= 9) { - msr_log(msr, 9, "This phase consists of %i rule(s).", arr->nelts); + msr_log(msr, 9, "This phase consists of %d rule(s).", arr->nelts); } /* Loop through the rules in the selected set. */ @@ -654,6 +832,49 @@ apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) rules = (msre_rule **)arr->elts; for (i = 0; i < arr->nelts; i++) { msre_rule *rule = rules[i]; + #if defined(PERFORMANCE_MEASUREMENT) + apr_time_t time1 = 0; + #endif + + /* Reset the rule interception flag */ + msr->rule_was_intercepted = 0; + + /* SKIP_RULES is used to skip all rules until we hit a placeholder + * with the specified rule ID and then resume execution after that. + */ + if (mode == SKIP_RULES) { + /* Go to the next rule if we have not yet hit the skip_after ID */ + if ((rule->placeholder == RULE_PH_NONE) || (rule->actionset->id == NULL) || (strcmp(skip_after, rule->actionset->id) != 0)) { + if (msr->txcfg->debuglog_level >= 9) { + if (rule->chain_starter != NULL) { + msr_log(msr, 9, "Skipping chain rule %pp id=\"%s\" until after id=\"%s\"", rule, (rule->chain_starter->actionset->id ? rule->chain_starter->actionset->id : "(none)"), skip_after); + + } + else { + msr_log(msr, 9, "Skipping rule %pp id=\"%s\" until after id=\"%s\"", rule, (rule->actionset->id ? rule->actionset->id : "(none)"), skip_after); + + } + } + continue; + } + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Found rule %pp id=\"%s\".", rule, skip_after); + } + + /* Go to the rule *after* this one to continue execution. */ + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Continuing execution after rule id=\"%s\".", skip_after); + } + + skip_after = NULL; + mode = NEXT_RULE; + continue; + } + + /* Skip any rule marked as a placeholder */ + if (rule->placeholder != RULE_PH_NONE) { + continue; + } /* NEXT_CHAIN is used when one of the rules in a chain * fails to match and then we need to skip the remaining @@ -691,28 +912,78 @@ apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) continue; } + /* Check if this rule was removed at runtime */ + if ((rule->actionset->id !=NULL) && (! apr_is_empty_array(msr->removed_rules))) { + int j; + int do_process = 1; + const char *range; + + for(j = 0; j < msr->removed_rules->nelts; j++) { + range = ((const char**)msr->removed_rules->elts)[j]; + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Checking removal of rule id=\"%s\" against: %s", rule->actionset->id, range); + } + + if (rule_id_in_range(atoi(rule->actionset->id), range)) { + do_process = 0; + break; + } + } + + /* Go to the next rule if this one has been removed. */ + if (do_process == 0) { + if (msr->txcfg->debuglog_level >= 5) { + msr_log(msr, 5, "Not processing %srule id=\"%s\": " + "removed by ctl action", + rule->actionset->is_chained ? "chained " : "", + rule->actionset->id); + } + + /* Skip the whole chain, if this is a chained rule */ + if (rule->actionset->is_chained) { + mode = NEXT_CHAIN; + } + + continue; + } + } + if (msr->txcfg->debuglog_level >= 4) { apr_pool_t *p = msr->mp; const char *fn = NULL; const char *id = NULL; const char *rev = NULL; + if (rule->filename != NULL) { fn = apr_psprintf(p, " [file \"%s\"] [line \"%d\"]", rule->filename, rule->line_num); } + if (rule->actionset != NULL && rule->actionset->id != NULL) { id = apr_psprintf(p, " [id \"%s\"]", rule->actionset->id); } + if (rule->actionset != NULL && rule->actionset->rev != NULL) { rev = apr_psprintf(p, " [rev \"%s\"]", rule->actionset->rev); } - msr_log(msr, 4, "Recipe: Invoking rule %x%s%s%s.", + + msr_log(msr, 4, "Recipe: Invoking rule %pp;%s%s%s.", rule, (fn ? fn : ""), (id ? id : ""), (rev ? rev : "")); + msr_log(msr, 5, "Rule %pp: %s", rule, rule->unparsed); } + #if defined(PERFORMANCE_MEASUREMENT) + time1 = apr_time_now(); + #endif + rc = msre_rule_process(rule, msr); + #if defined(PERFORMANCE_MEASUREMENT) + rule->execution_time += (apr_time_now() - time1); + #endif + if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Rule returned %i.", rc); + msr_log(msr, 4, "Rule returned %d.", rc); } if (rc == RULE_NO_MATCH) { @@ -736,8 +1007,8 @@ apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) } else if (rc == RULE_MATCH) { - if (msr->was_intercepted) { - /* If the transaction was intercepted we will + if (msr->rule_was_intercepted) { + /* If the transaction was intercepted by this rule we will * go back. Do note that we are relying on the * rule to know if it is a part of a chain and * not intercept if it is. @@ -748,6 +1019,17 @@ apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) return 1; } + if (rule->actionset->skip_after != NULL) { + skip_after = rule->actionset->skip_after; + mode = SKIP_RULES; + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Skipping after rule %pp id=\"%s\" -> mode SKIP_RULES.", rule, skip_after); + } + + continue; + } + /* We had a match but the transaction was not * intercepted. In that case we proceed with the * next rule... @@ -766,14 +1048,14 @@ apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) if (rule->chain_starter->actionset->skip_count > 0) { skip = rule->chain_starter->actionset->skip_count; if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Skipping %i rules/chains (from a chain).", skip); + msr_log(msr, 4, "Skipping %d rules/chains (from a chain).", skip); } } } else if (rule->actionset->skip_count > 0) { skip = rule->actionset->skip_count; if (msr->txcfg->debuglog_level >= 4) { - msr_log(msr, 4, "Skipping %i rules/chains.", skip); + msr_log(msr, 4, "Skipping %d rules/chains.", skip); } } } @@ -783,10 +1065,10 @@ apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) return -1; } else { - msr_log(msr, 1, "Rule processing failed with unknown return code: %i.", rc); + msr_log(msr, 1, "Rule processing failed with unknown return code: %d.", rc); return -1; } - } + } /* ENH warn if chained rules are missing. */ @@ -852,6 +1134,53 @@ int msre_ruleset_rule_add(msre_ruleset *ruleset, msre_rule *rule, int phase) { return 1; } +static msre_rule * msre_ruleset_fetch_phase_rule(const msre_ruleset *ruleset, const char *id, + const apr_array_header_t *phase_arr) +{ + msre_rule **rules = (msre_rule **)phase_arr->elts; + int i; + + for (i = 0; i < phase_arr->nelts; i++) { + msre_rule *rule = (msre_rule *)rules[i]; + + if ( (rule->actionset != NULL) + && !rule->actionset->is_chained + && (rule->actionset->id != NULL) + && (strcmp(rule->actionset->id, id) == 0)) + { + /* Return rule that matched unless it is a placeholder */ + return (rule->placeholder == RULE_PH_NONE) ? rule : NULL; + } + } + + return NULL; +} + +/** + * Fetches rule from the ruleset all rules that match the given exception. + */ +msre_rule * msre_ruleset_fetch_rule(msre_ruleset *ruleset, const char *id) { + msre_rule *rule = NULL; + + if (ruleset == NULL) return NULL; + + rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_request_headers); + if (rule != NULL) return rule; + + rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_request_body); + if (rule != NULL) return rule; + + rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_response_headers); + if (rule != NULL) return rule; + + rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_response_body); + if (rule != NULL) return rule; + + rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_logging); + + return rule; +} + static int msre_ruleset_phase_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re, apr_array_header_t *phase_arr) { @@ -868,31 +1197,34 @@ static int msre_ruleset_phase_rule_remove_with_exception(msre_ruleset *ruleset, if (mode == 0) { /* Looking for next rule. */ int remove_rule = 0; - switch(re->type) { - case RULE_EXCEPTION_REMOVE_ID : - if ((rule->actionset != NULL)&&(rule->actionset->id != NULL)) { - int ruleid = atoi(rule->actionset->id); + /* Only remove non-placeholder rules */ + if (rule->placeholder == RULE_PH_NONE) { + switch(re->type) { + case RULE_EXCEPTION_REMOVE_ID : + if ((rule->actionset != NULL)&&(rule->actionset->id != NULL)) { + int ruleid = atoi(rule->actionset->id); - if (rule_id_in_range(ruleid, re->param)) { - remove_rule = 1; + if (rule_id_in_range(ruleid, re->param)) { + remove_rule = 1; + } } - } - break; + break; - case RULE_EXCEPTION_REMOVE_MSG : - if ((rule->actionset != NULL)&&(rule->actionset->msg != NULL)) { - char *my_error_msg = NULL; + case RULE_EXCEPTION_REMOVE_MSG : + if ((rule->actionset != NULL)&&(rule->actionset->msg != NULL)) { + char *my_error_msg = NULL; - int rc = msc_regexec(re->param_data, - rule->actionset->msg, strlen(rule->actionset->msg), - &my_error_msg); - if (rc >= 0) { - remove_rule = 1; + int rc = msc_regexec(re->param_data, + rule->actionset->msg, strlen(rule->actionset->msg), + &my_error_msg); + if (rc >= 0) { + remove_rule = 1; + } } - } - break; + break; + } } if (remove_rule) { @@ -902,7 +1234,7 @@ static int msre_ruleset_phase_rule_remove_with_exception(msre_ruleset *ruleset, } else { if (rule->actionset->is_chained) mode = 1; /* Keep rules in this chain. */ rules[j++] = rules[i]; - } + } } else { /* Handling rule that is part of a chain. */ if (mode == 2) { /* We want to remove the rule. */ /* Do not increment j. */ @@ -933,8 +1265,9 @@ int msre_ruleset_rule_remove_with_exception(msre_ruleset *ruleset, rule_exceptio count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_request_body); count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_response_headers); count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_response_body); + count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_logging); - return count; + return count; } @@ -944,18 +1277,6 @@ int msre_ruleset_rule_remove_with_exception(msre_ruleset *ruleset, rule_exceptio * Returns the name of the supplied severity level. */ static const char *msre_format_severity(int severity) { - static const char *const severities[] = { - "EMERGENCY", - "ALERT", - "CRITICAL", - "ERROR", - "WARNING", - "NOTICE", - "INFO", - "DEBUG", - NULL, - }; - if ((severity >= 0)&&(severity <= 7)) { return severities[severity]; } @@ -973,6 +1294,7 @@ char *msre_format_metadata(modsec_rec *msr, msre_actionset *actionset) { char *id = ""; char *rev = ""; char *msg = ""; + char *logdata = ""; char *severity = ""; char *tags = ""; char *fn = ""; @@ -1002,6 +1324,28 @@ char *msre_format_metadata(modsec_rec *msr, msre_actionset *actionset) { msg = apr_psprintf(msr->mp, " [msg \"%s\"]", log_escape_ex(msr->mp, var->value, var->value_len)); } + if (actionset->logdata != NULL) { + /* Expand variables in the message string. */ + msc_string *var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + var->value = (char *)actionset->logdata; + var->value_len = strlen(actionset->logdata); + expand_macros(msr, var, NULL, msr->mp); + + logdata = apr_psprintf(msr->mp, " [data \"%s\"]", + log_escape_hex(msr->mp, (unsigned char *)var->value, var->value_len)); + + /* If it is > 512 bytes, then truncate at 512 with ellipsis. + * NOTE: 512 actual data + 9 bytes of label = 521 + */ + if (strlen(logdata) > 521) { + logdata[517] = '.'; + logdata[518] = '.'; + logdata[519] = '.'; + logdata[520] = '"'; + logdata[521] = ']'; + logdata[522] = '\0'; + } + } if ((actionset->severity >= 0)&&(actionset->severity <= 7)) { severity = apr_psprintf(msr->mp, " [severity \"%s\"]", msre_format_severity(actionset->severity)); @@ -1018,15 +1362,69 @@ char *msre_format_metadata(modsec_rec *msr, msre_actionset *actionset) { log_escape(msr->mp, action->param)); } } - - return apr_pstrcat(msr->mp, fn, id, rev, msg, severity, tags, NULL); + + return apr_pstrcat(msr->mp, fn, id, rev, msg, logdata, severity, tags, NULL); +} + +char * msre_rule_generate_unparsed(apr_pool_t *pool, const msre_rule *rule, const char *targets, + const char *args, const char *actions) +{ + char *unparsed = NULL; + const char *r_targets = targets; + const char *r_args = args; + const char *r_actions = actions; + + if (r_targets == NULL) { + r_targets = rule->p1; + } + if (r_args == NULL) { + r_args = apr_pstrcat(pool, (rule->op_negated ? "!" : ""), "@", rule->op_name, " ", rule->op_param, NULL); + } + if (r_actions == NULL) { + r_actions = msre_actionset_generate_action_string(pool, rule->actionset); + } + + switch (rule->type) { + case RULE_TYPE_NORMAL: + if (r_actions == NULL) { + unparsed = apr_psprintf(pool, "SecRule \"%s\" \"%s\"", + log_escape(pool, r_targets), log_escape(pool, r_args)); + } + else { + unparsed = apr_psprintf(pool, "SecRule \"%s\" \"%s\" \"%s\"", + log_escape(pool, r_targets), log_escape(pool, r_args), + log_escape(pool, r_actions)); + } + break; + case RULE_TYPE_ACTION: + unparsed = apr_psprintf(pool, "SecAction \"%s\"", + log_escape(pool, r_actions)); + break; + case RULE_TYPE_MARKER: + unparsed = apr_psprintf(pool, "SecMarker \"%s\"", rule->actionset->id); + break; + #if defined(WITH_LUA) + case RULE_TYPE_LUA: + /* SecRuleScript */ + if (r_actions == NULL) { + unparsed = apr_psprintf(pool, "SecRuleScript \"%s\"", r_args); + } + else { + unparsed = apr_psprintf(pool, "SecRuleScript \"%s\" \"%s\"", + r_args, log_escape(pool, r_actions)); + } + break; + #endif + } + + return unparsed; } /** * Assembles a new rule using the strings that contain a list - * of targets (variables), argumments, and actions. + * of targets (variables), arguments, and actions. */ -msre_rule *msre_rule_create(msre_ruleset *ruleset, +msre_rule *msre_rule_create(msre_ruleset *ruleset, int type, const char *fn, int line, const char *targets, const char *args, const char *actions, char **error_msg) { @@ -1040,8 +1438,11 @@ msre_rule *msre_rule_create(msre_ruleset *ruleset, rule = (msre_rule *)apr_pcalloc(ruleset->mp, sizeof(msre_rule)); if (rule == NULL) return NULL; + + rule->type = type; rule->ruleset = ruleset; rule->targets = apr_array_make(ruleset->mp, 10, sizeof(const msre_var *)); + rule->p1 = apr_pstrdup(ruleset->mp, targets); rule->filename = apr_pstrdup(ruleset->mp, fn); rule->line_num = line; @@ -1055,7 +1456,7 @@ msre_rule *msre_rule_create(msre_ruleset *ruleset, /* Parse args */ argsp = args; - /* Is negation used? */ + /* Is negation used? */ if (*argsp == '!') { rule->op_negated = 1; argsp++; @@ -1102,9 +1503,57 @@ msre_rule *msre_rule_create(msre_ruleset *ruleset, } } + /* Add the unparsed rule */ + rule->unparsed = msre_rule_generate_unparsed(ruleset->mp, rule, targets, args, NULL); + return rule; } +#if defined(WITH_LUA) +/** + * + */ +msre_rule *msre_rule_lua_create(msre_ruleset *ruleset, + const char *fn, int line, const char *script_filename, + const char *actions, char **error_msg) +{ + msre_rule *rule; + char *my_error_msg; + + if (error_msg == NULL) return NULL; + *error_msg = NULL; + + rule = (msre_rule *)apr_pcalloc(ruleset->mp, sizeof(msre_rule)); + if (rule == NULL) return NULL; + + rule->type = RULE_TYPE_LUA; + rule->ruleset = ruleset; + rule->filename = apr_pstrdup(ruleset->mp, fn); + rule->line_num = line; + + /* Compile script. */ + *error_msg = lua_compile(&rule->script, script_filename, ruleset->mp); + if (*error_msg != NULL) { + return NULL; + } + + /* Parse actions */ + if (actions != NULL) { + /* Create per-rule actionset */ + rule->actionset = msre_actionset_create(ruleset->engine, actions, &my_error_msg); + if (rule->actionset == NULL) { + *error_msg = apr_psprintf(ruleset->mp, "Error parsing actions: %s", my_error_msg); + return NULL; + } + } + + /* Add the unparsed rule */ + rule->unparsed = msre_rule_generate_unparsed(ruleset->mp, rule, NULL, script_filename, NULL); + + return rule; +} +#endif + /** * Perform non-disruptive actions associated with the provided actionset. */ @@ -1153,8 +1602,8 @@ static void msre_perform_disruptive_actions(modsec_rec *msr, msre_rule *rule, } } - /* If "noauditlog" was used do not mark the transaction for audit logging. */ - if (actionset->auditlog == 1) { + /* If "noauditlog" used do not mark the transaction relevant. */ + if (actionset->auditlog != 0) { msr->is_relevant++; } @@ -1166,8 +1615,8 @@ static void msre_perform_disruptive_actions(modsec_rec *msr, msre_rule *rule, || (msr->modsecurity->processing_mode == MODSEC_OFFLINE) || (actionset->intercept_action == ACTION_NONE)) { - /* If "nolog" was used log at a higher level. */ - msc_alert(msr, (actionset->log == 0 ? 4 : 2), actionset, + /* If "no(audit)?log" was used log at a higher level. */ + msc_alert(msr, ((actionset->log == 0) || (actionset->auditlog == 0) ? 4 : 2), actionset, "Warning.", message); return; } @@ -1176,6 +1625,7 @@ static void msre_perform_disruptive_actions(modsec_rec *msr, msre_rule *rule, * transaction, and rememer the rule that caused it. */ msr->was_intercepted = 1; + msr->rule_was_intercepted = 1; msr->intercept_phase = msr->phase; msr->intercept_actionset = actionset; msr->intercept_message = message; @@ -1187,25 +1637,25 @@ static void msre_perform_disruptive_actions(modsec_rec *msr, msre_rule *rule, static int execute_operator(msre_var *var, msre_rule *rule, modsec_rec *msr, msre_actionset *acting_actionset, apr_pool_t *mptmp) { - apr_time_t time_before_regex = 0; + apr_time_t time_before_op = 0; char *my_error_msg = NULL; const char *full_varname = NULL; int rc; /* determine the full var name if not already resolved * - * NOTE: this can happen if the var does not match but it is + * NOTE: this can happen if the var does not match but it is * being tested for non-existance as in: * @REQUEST_HEADERS:Foo "@eq 0" * @REQUEST_HEADERS:Foo "!@eq 1" */ if ((var->param != NULL) && (var->name != NULL) && (strchr(var->name,':') == NULL)) { - full_varname = apr_psprintf(mptmp, "%s%s:%s", + full_varname = apr_psprintf(mptmp, "%s%s:%s", (var->is_counting ? "&" : ""), var->name, var->param); } else if ((var->name != NULL) && var->is_counting && (*var->name != '&')) { - full_varname = apr_pstrcat(mptmp, "&", var->name); + full_varname = apr_pstrcat(mptmp, "&", var->name, NULL); } else { full_varname = var->name; @@ -1221,14 +1671,24 @@ static int execute_operator(msre_var *var, msre_rule *rule, modsec_rec *msr, msr_log(msr, 9, "Target value: \"%s\"", log_escape_nq_ex(msr->mp, var->value, var->value_len)); } - - if (msr->txcfg->debuglog_level >= 4) { - time_before_regex = apr_time_now(); - } + + #if !defined(PERFORMANCE_MEASUREMENT) + if (msr->txcfg->debuglog_level >= 4) + #endif + time_before_op = apr_time_now(); + rc = rule->op_metadata->execute(msr, rule, var, &my_error_msg); - if (msr->txcfg->debuglog_level >= 4) { + + #if !defined(PERFORMANCE_MEASUREMENT) + if (msr->txcfg->debuglog_level >= 4) + #endif + { + apr_time_t t1 = apr_time_now(); + #if defined(PERFORMANCE_MEASUREMENT) + rule->op_time += (t1 - time_before_op); + #endif msr_log(msr, 4, "Operator completed in %" APR_TIME_T_FMT " usec.", - (apr_time_now() - time_before_regex)); + (t1 - time_before_op)); } if (rc < 0) { @@ -1242,7 +1702,6 @@ static int execute_operator(msre_var *var, msre_rule *rule, modsec_rec *msr, } else { /* Match. */ - if (rc == 0) { /* Operator did not match so we need to provide a message. */ my_error_msg = apr_psprintf(msr->mp, "Match of \"%s %s\" against \"%s\" required.", @@ -1250,7 +1709,21 @@ static int execute_operator(msre_var *var, msre_rule *rule, modsec_rec *msr, log_escape(msr->mp, full_varname)); } - msr->matched_var = apr_pstrdup(msr->mp, var->name); + /* Save the rules that match */ + *(const msre_rule **)apr_array_push(msr->matched_rules) = rule; + + /* Save the last matched var data */ + msr->matched_var->name = apr_pstrdup(msr->mp, var->name); + msr->matched_var->name_len = strlen(msr->matched_var->name); + msr->matched_var->value = apr_pmemdup(msr->mp, var->value, var->value_len); + msr->matched_var->value_len = var->value_len; + + /* Keep track of the highest severity matched so far */ + if ((acting_actionset->severity > 0) && (acting_actionset->severity < msr->highest_severity)) + { + msr->highest_severity = acting_actionset->severity; + } + /* Perform non-disruptive actions. */ msre_perform_nondisruptive_actions(msr, rule, rule->actionset, mptmp); @@ -1269,17 +1742,17 @@ static int execute_operator(msre_var *var, msre_rule *rule, modsec_rec *msr, /** * Executes rule against the given transaction. */ -apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) { +static apr_status_t msre_rule_process_normal(msre_rule *rule, modsec_rec *msr) { const apr_array_header_t *arr = NULL; const apr_table_entry_t *te = NULL; msre_actionset *acting_actionset = NULL; msre_var **targets = NULL; - apr_pool_t *mptmp = NULL; + apr_pool_t *mptmp = msr->msc_rule_mptmp; apr_table_t *tartab = NULL; apr_table_t *vartab = NULL; int i, rc, match_count = 0; int invocations = 0; - int multi_match = 0; + int multi_match = 0; /* Choose the correct metadata/disruptive action actionset. */ acting_actionset = rule->actionset; @@ -1292,18 +1765,6 @@ apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) { multi_match = 1; } - /* Use a fresh memory sub-pool for processing each rule */ - if (msr->msc_rule_mptmp == NULL) { - if (apr_pool_create(&msr->msc_rule_mptmp, msr->mp) != APR_SUCCESS) { - return -1; - } - mptmp = msr->msc_rule_mptmp; - } - else { - mptmp = msr->msc_rule_mptmp; - apr_pool_clear(mptmp); - } - tartab = apr_table_make(mptmp, 24); if (tartab == NULL) return -1; vartab = apr_table_make(mptmp, 24); @@ -1328,7 +1789,7 @@ apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) { if (targets[i]->is_counting) { /* Count how many there are and just add the score to the target list. */ msre_var *newvar = (msre_var *)apr_pmemdup(mptmp, targets[i], sizeof(msre_var)); - newvar->value = apr_psprintf(mptmp, "%i", list_count); + newvar->value = apr_psprintf(mptmp, "%d", list_count); newvar->value_len = strlen(newvar->value); apr_table_addn(tartab, newvar->name, (void *)newvar); } else { @@ -1345,6 +1806,23 @@ apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) { } } + /* Log the target variable expansion */ + if (msr->txcfg->debuglog_level >= 4) { + const char *expnames = NULL; + + arr = apr_table_elts(tartab); + if (arr->nelts > 1) { + te = (apr_table_entry_t *)arr->elts; + expnames = apr_pstrdup(mptmp, ((msre_var *)te[0].val)->name); + for(i = 1; i < arr->nelts; i++) { + expnames = apr_psprintf(mptmp, "%s|%s", expnames, ((msre_var *)te[i].val)->name); + } + if (strcmp(rule->p1, expnames) != 0) { + msr_log(msr, 4, "Expanded \"%s\" to \"%s\".", rule->p1, expnames); + } + } + } + /* Loop through targets on the final target list, * perform transformations as necessary, and invoke * the operator. @@ -1354,34 +1832,86 @@ apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) { te = (apr_table_entry_t *)arr->elts; for (i = 0; i < arr->nelts; i++) { int changed; - - /* Take one target. */ + int usecache = 0; + apr_table_t **carr = NULL; + apr_table_t *cachetab = NULL; + apr_time_t time_before_trans = 0; + + /* Take one target. */ msre_var *var = (msre_var *)te[i].val; + /* Is this var cacheable? */ + if (msr->txcfg->cache_trans != MODSEC_CACHE_DISABLED) { + usecache = 1; + + /* check the cache options */ + if (var->value_len < msr->txcfg->cache_trans_min) { + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "CACHE: Disabled - %s value length=%u, smaller than minlen=%" APR_SIZE_T_FMT, var->name, var->value_len, msr->txcfg->cache_trans_min); + } + usecache = 0; + } + if ((msr->txcfg->cache_trans_max != 0) && (var->value_len > msr->txcfg->cache_trans_max)) { + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "CACHE: Disabled - %s value length=%u, larger than maxlen=%" APR_SIZE_T_FMT, var->name, var->value_len, msr->txcfg->cache_trans_max); + } + usecache = 0; + } + + /* if cache is still enabled, check the VAR for cacheablity */ + if (usecache) { + if (var->metadata->is_cacheable == VAR_CACHE) { + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "CACHE: Enabled"); + } + + /* Fetch cache table for this target */ + carr = (apr_table_t **)apr_hash_get(msr->tcache, var->name, APR_HASH_KEY_STRING); + if (carr != NULL) { + cachetab = carr[msr->phase]; + } + else { + /* Create an array of cache tables (one table per phase) */ + carr = (apr_table_t **)apr_pcalloc(msr->mp, (sizeof(apr_table_t *) * (PHASE_LAST + 1))); + if (carr == NULL) return -1; + memset(carr, 0, (sizeof(apr_table_t *) * (PHASE_LAST + 1))); + apr_hash_set(msr->tcache, var->name, APR_HASH_KEY_STRING, carr); + } + + /* Create an empty cache table if this is the first time */ + if (cachetab == NULL) { + cachetab = carr[msr->phase] = apr_table_make(msr->mp, 5); + } + } + else { + usecache = 0; + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "CACHE: %s transformations are not cacheable", var->name); + } + } + } + } + + #if !defined(PERFORMANCE_MEASUREMENT) + if (msr->txcfg->debuglog_level >= 4) + #endif + time_before_trans = apr_time_now(); + + /* Transform target. */ { const apr_array_header_t *tarr; const apr_table_entry_t *telts; - msre_cache_rec **carr = NULL; - msre_cache_rec *crec = NULL; - char *tfnsvar = NULL; + const char *tfnspath = NULL; char *tfnskey = NULL; int tfnscount = 0; - int usecache = 0; - apr_table_t *normtab; + int last_cached_tfn = 0; + msre_cache_rec *crec = NULL; + msre_cache_rec *last_crec = NULL; int k; msre_action *action; msre_tfn_metadata *metadata; - - /* Is this var cacheable? */ - if (var->metadata->is_cacheable == VAR_CACHE) { - usecache = 1; - tfnsvar = apr_psprintf(msr->mp, "%lx;%s", (unsigned long)var, var->name); - tfnskey = tfnsvar; - } - else { - msr_log(msr, 9, "CACHE: %s transformations are not cacheable", var->name); - } + apr_table_t *normtab; normtab = apr_table_make(mptmp, 10); if (normtab == NULL) return -1; @@ -1394,64 +1924,82 @@ apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) { if (strcmp(telts[k].key, "t") == 0) { if (strcmp(action->param, "none") == 0) { apr_table_clear(normtab); - tfnskey = tfnsvar; + tfnspath = NULL; tfnscount = 0; + last_cached_tfn = 0; continue; } if (action->param_plusminus == NEGATIVE_VALUE) { apr_table_unset(normtab, action->param); - } else { - apr_table_addn(normtab, action->param, (void *)action); - tfnskey = apr_psprintf(msr->mp, "%s,%s", tfnskey, action->param); + } + else { tfnscount++; - } - } - } - /* Perform transformations. */ + apr_table_addn(normtab, action->param, (void *)action); - /* Try to fetch the full multi-transformation from cache */ - if (usecache && tfnscount > 1 && !multi_match) { - crec = NULL; - msr_log(msr, 9, "CACHE: Fetching %s (multi)", tfnskey); - carr = (msre_cache_rec **)apr_hash_get(msr->tcache, tfnskey, APR_HASH_KEY_STRING); - if (carr != NULL) { - crec = carr[msr->phase]; - } - - /* Cache Miss - Reset the key to perform transformations */ - if (crec == NULL) { - tfnskey = tfnsvar; - } - /* Cache Hit - Use cache value and execute immediatly */ - else { - crec->hits++; - if (crec->changed) { - var->value = apr_pmemdup(msr->mp, crec->val, crec->val_len); - var->value_len = crec->val_len; - } - - msr_log(msr, 9, "T (%i) %s: \"%s\" [cached hits=%d]", crec->changed, (tfnskey + strlen(tfnsvar) + 1), log_escape_nq_ex(mptmp, var->value, var->value_len), crec->hits); - - rc = execute_operator(var, rule, msr, acting_actionset, mptmp); - - if (rc < 0) { - return -1; - } - if (rc == RULE_MATCH) { - /* Return straight away if the transaction - * was intercepted - no need to process the remaining - * targets. - */ - if (msr->was_intercepted) { - return RULE_MATCH; + /* Check the cache, saving the 'most complete' as a + * starting point + */ + if (usecache) { + tfnspath = apr_psprintf(msr->mp, "%s%s%s", (tfnspath?tfnspath:""), (tfnspath?",":""), action->param); + tfnskey = apr_psprintf(msr->mp, "%x;%s", tfnscount, tfnspath); + crec = (msre_cache_rec *)apr_table_get(cachetab, tfnskey); + if (crec != NULL) { + last_crec = crec; + last_cached_tfn = tfnscount; + } } } - continue; /* next target */ } } + /* If the last cached tfn is the last in the list + * then we can stop here and just execute the action immediatly + */ + if (usecache && !multi_match && (crec != NULL) && (crec == last_crec)) { + crec->hits++; + if (crec->changed) { + var->value = apr_pmemdup(msr->mp, crec->val, crec->val_len); + var->value_len = crec->val_len; + } + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "T (%d) %s: \"%s\" [cached hits=%d]", crec->changed, crec->path, log_escape_nq_ex(mptmp, var->value, var->value_len), crec->hits); + } + + #if !defined(PERFORMANCE_MEASUREMENT) + if (msr->txcfg->debuglog_level >= 4) + #endif + { + apr_time_t t1 = apr_time_now(); + #if defined(PERFORMANCE_MEASUREMENT) + rule->trans_time += (t1 - time_before_trans); + #endif + msr_log(msr, 4, "Transformation completed in %" APR_TIME_T_FMT " usec.", + (t1 - time_before_trans)); + } + + rc = execute_operator(var, rule, msr, acting_actionset, mptmp); + + if (rc < 0) { + return -1; + } + if (rc == RULE_MATCH) { + /* Return straight away if the transaction + * was intercepted - no need to process the remaining + * targets. + */ + if (msr->rule_was_intercepted) { + return RULE_MATCH; + } + } + continue; /* next target */ + } + + + /* Perform transformations. */ + tarr = apr_table_elts(normtab); /* Make a copy of the variable value so that @@ -1464,10 +2012,29 @@ apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) { /* Execute transformations in a loop. */ - tfnskey = tfnsvar; - changed = 1; + /* Start after the last known cached transformation if we can */ + if (!multi_match && (last_crec != NULL)) { + k = last_cached_tfn; + tfnspath = last_crec->path; + last_crec->hits++; + + if ((changed = last_crec->changed) == 1) { + var->value = apr_pmemdup(msr->mp, last_crec->val, last_crec->val_len); + var->value_len = last_crec->val_len; + } + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "T (%d) %s: \"%s\" [partially cached hits=%d]", last_crec->changed, tfnspath, log_escape_nq_ex(mptmp, var->value, var->value_len), last_crec->hits); + } + } + else { + changed = 1; + tfnspath = NULL; + k = 0; + } + telts = (const apr_table_entry_t*)tarr->elts; - for (k = 0; k < tarr->nelts; k++) { + for (; k < tarr->nelts; k++) { char *rval = NULL; long int rval_length = -1; @@ -1479,6 +2046,18 @@ apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) { if (multi_match && changed) { invocations++; + #if !defined(PERFORMANCE_MEASUREMENT) + if (msr->txcfg->debuglog_level >= 4) + #endif + { + apr_time_t t1 = apr_time_now(); + #if defined(PERFORMANCE_MEASUREMENT) + rule->trans_time += (t1 - time_before_trans); + #endif + msr_log(msr, 4, "Transformation completed in %" APR_TIME_T_FMT " usec.", + (t1 - time_before_trans)); + } + rc = execute_operator(var, rule, msr, acting_actionset, mptmp); if (rc < 0) { @@ -1487,12 +2066,12 @@ apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) { if (rc == RULE_MATCH) { match_count++; - + /* Return straight away if the transaction * was intercepted - no need to process the remaining * targets. */ - if (msr->was_intercepted) { + if (msr->rule_was_intercepted) { return RULE_MATCH; } } @@ -1505,15 +2084,14 @@ apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) { /* Try to use the cache */ if (usecache) { /* Generate the cache key */ - tfnskey = apr_psprintf(msr->mp, "%s,%s", tfnskey, action->param); + tfnspath = apr_psprintf(msr->mp, "%s%s%s", (tfnspath?tfnspath:""), (tfnspath?",":""), action->param); + tfnskey = apr_psprintf(msr->mp, "%x;%s", (k + 1), tfnspath); /* Try to fetch this transformation from cache */ - msr_log(msr, 9, "CACHE: Fetching %s", tfnskey); - crec = NULL; - carr = (msre_cache_rec **)apr_hash_get(msr->tcache, tfnskey, APR_HASH_KEY_STRING); - if (carr != NULL) { - crec = carr[msr->phase]; - } + #ifdef CACHE_DEBUG + msr_log(msr, 9, "CACHE: Fetching %s %s ", var->name, tfnskey); + #endif + crec = (msre_cache_rec *)apr_table_get(cachetab, tfnskey); if (crec != NULL) { crec->hits++; @@ -1522,7 +2100,9 @@ apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) { var->value_len = crec->val_len; } - msr_log(msr, 9, "T (%i) %s: \"%s\" [cached hits=%i]", crec->changed, metadata->name, log_escape_nq_ex(mptmp, var->value, var->value_len), crec->hits); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "T (%d) %s: \"%s\" [cached hits=%d]", crec->changed, metadata->name, log_escape_nq_ex(mptmp, var->value, var->value_len), crec->hits); + } continue; } } @@ -1541,25 +2121,23 @@ apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) { /* Cache the transformation */ if (usecache) { /* ENH1: Add flag to vars to tell which ones can change across phases store the rest in a global cache */ - if (carr == NULL) { - carr = (msre_cache_rec **)apr_pcalloc(msr->mp, (sizeof(msre_cache_rec *) * (PHASE_LAST + 1))); - if (carr == NULL) return -1; - memset(carr, 0, (sizeof(msre_cache_rec *) * (PHASE_LAST + 1))); - apr_hash_set(msr->tcache, tfnskey, APR_HASH_KEY_STRING, carr); - } - crec = carr[msr->phase] = (msre_cache_rec *)apr_pcalloc(msr->mp, sizeof(msre_cache_rec)); + crec = (msre_cache_rec *)apr_pcalloc(msr->mp, sizeof(msre_cache_rec)); if (crec == NULL) return -1; crec->hits = 0; crec->changed = changed; - crec->key = tfnskey; - crec->val = changed ? apr_pmemdup(msr->mp, rval, rval_length) : NULL; - crec->val_len = changed ? rval_length : -1; - msr_log(msr, 9, "CACHE: Caching %s=\"%.*s\"", tfnskey, crec->val_len, crec->val); + crec->num = k + 1; + crec->path = tfnspath; + crec->val = changed ? apr_pmemdup(msr->mp, var->value, var->value_len) : NULL; + crec->val_len = changed ? var->value_len : 0; + #ifdef CACHE_DEBUG + msr_log(msr, 9, "CACHE: Caching %s=\"%.*s\"", tfnskey, var->value_len, log_escape_nq_ex(mptmp, var->value, var->value_len)); + #endif + apr_table_setn(cachetab, tfnskey, (void *)crec); } if (msr->txcfg->debuglog_level >= 9) { - msr_log(msr, 9, "T (%i) %s: \"%s\"", rc, metadata->name, + msr_log(msr, 9, "T (%d) %s: \"%s\"", rc, metadata->name, log_escape_nq_ex(mptmp, var->value, var->value_len)); } } @@ -1572,6 +2150,18 @@ apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) { if (!multi_match || changed) { invocations++; + #if !defined(PERFORMANCE_MEASUREMENT) + if (msr->txcfg->debuglog_level >= 4) + #endif + { + apr_time_t t1 = apr_time_now(); + #if defined(PERFORMANCE_MEASUREMENT) + rule->trans_time += (t1 - time_before_trans); + #endif + msr_log(msr, 4, "Transformation completed in %" APR_TIME_T_FMT " usec.", + (t1 - time_before_trans)); + } + rc = execute_operator(var, rule, msr, acting_actionset, mptmp); if (rc < 0) { @@ -1580,40 +2170,51 @@ apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) { if (rc == RULE_MATCH) { match_count++; - + /* Return straight away if the transaction * was intercepted - no need to process the remaining * targets. */ - if (msr->was_intercepted) { + if (msr->rule_was_intercepted) { return RULE_MATCH; } } } } - - msr_log(msr, 9, "CACHE: size=%u", apr_hash_count(msr->tcache)); #ifdef CACHE_DEBUG if (msr->txcfg->debuglog_level >= 9) { apr_hash_index_t *hi; void *dummy; - msre_cache_rec **rec; - int hn = 0; - int ri; - for (hi = apr_hash_first(msr->mp, msr->tcache); hi; hi = apr_hash_next(hi)) { - hn++; - apr_hash_this(hi, NULL, NULL, &dummy); - rec = (msre_cache_rec **)dummy; - if (rec == NULL) continue; + apr_table_t **tab; + const apr_array_header_t *ctarr; + const apr_table_entry_t *ctelts; + msre_cache_rec *rec; + int cn = 0; + int ti, ri; - for (ri = PHASE_FIRST; ri <= PHASE_LAST; ri++) { - if (rec[ri] == NULL) continue; - if (rec[ri]->changed) { - msr_log(msr, 9, "CACHE: %5d) phase=%d hits=%d %s=\"%s\"", hn, msr->phase, rec[ri]->hits, rec[ri]->key, log_escape_nq_ex(mptmp, rec[ri]->val, rec[ri]->val_len)); - } - else { - msr_log(msr, 9, "CACHE: %5d) phase=%d hits=%d %s=", hn, msr->phase, rec[ri]->hits, rec[ri]->key); + for (hi = apr_hash_first(msr->mp, msr->tcache); hi; hi = apr_hash_next(hi)) { + apr_hash_this(hi, NULL, NULL, &dummy); + tab = (apr_table_t **)dummy; + if (tab == NULL) continue; + + for (ti = PHASE_FIRST; ti <= PHASE_LAST; ti++) { + if (tab[ti] == NULL) continue; + ctarr = apr_table_elts(tab[ti]); + ctelts = (const apr_table_entry_t*)ctarr->elts; + for (ri = 0; ri < ctarr->nelts; ri++) { + cn++; + rec = (msre_cache_rec *)ctelts[ri].val; + if (rec->changed) { + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "CACHE: %5d) phase=%d hits=%d %x;%s=\"%s\"", cn, msr->phase, rec->hits, rec->num, rec->path, log_escape_nq_ex(mptmp, rec->val, rec->val_len)); + } + } + else { + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "CACHE: %5d) phase=%d hits=%d %x;%s=", cn, msr->phase, rec->hits, rec->num, rec->path); + } + } } } } @@ -1623,6 +2224,66 @@ apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) { return (match_count ? RULE_MATCH : RULE_NO_MATCH); } +#if defined(WITH_LUA) +/** + * + */ +static apr_status_t msre_rule_process_lua(msre_rule *rule, modsec_rec *msr) { + msre_actionset *acting_actionset = NULL; + char *my_error_msg = NULL; + int rc; + + /* Choose the correct metadata/disruptive action actionset. */ + acting_actionset = rule->actionset; + if (rule->chain_starter != NULL) { + acting_actionset = rule->chain_starter->actionset; + } + + rc = lua_execute(rule->script, NULL, msr, rule, &my_error_msg); + if (rc < 0) { + msr_log(msr, 1, "%s", my_error_msg); + return -1; + } + + /* A non-NULL error message means the rule matched. */ + if (my_error_msg != NULL) { + /* Perform non-disruptive actions. */ + msre_perform_nondisruptive_actions(msr, rule, rule->actionset, msr->msc_rule_mptmp); + + /* Perform disruptive actions, but only if + * this rule is not part of a chain. + */ + if (rule->actionset->is_chained == 0) { + msre_perform_disruptive_actions(msr, rule, acting_actionset, msr->msc_rule_mptmp, my_error_msg); + } + } + + return rc; +} +#endif + +/** + * + */ +apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) { + /* Use a fresh memory sub-pool for processing each rule */ + if (msr->msc_rule_mptmp == NULL) { + if (apr_pool_create(&msr->msc_rule_mptmp, msr->mp) != APR_SUCCESS) { + return -1; + } + } else { + apr_pool_clear(msr->msc_rule_mptmp); + } + + #if defined(WITH_LUA) + if (rule->type == RULE_TYPE_LUA) { + return msre_rule_process_lua(rule, msr); + } + #endif + + return msre_rule_process_normal(rule, msr); +} + /** * Checks whether the given rule ID is in the given range. */ @@ -1633,7 +2294,7 @@ int rule_id_in_range(int ruleid, const char *range) { if (range == NULL) return 0; data = strdup(range); if (data == NULL) return 0; - + p = apr_strtok(data, ",", &saveptr); while(p != NULL) { char *s = strstr(p, "-"); diff --git a/apache2/re.h b/apache2/re.h index 09111279..dcc1e966 100644 --- a/apache2/re.h +++ b/apache2/re.h @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -35,6 +35,10 @@ typedef struct msre_cache_rec msre_cache_rec; #include "persist_dbm.h" #include "apache2.h" +#if defined(WITH_LUA) +#include "msc_lua.h" +#endif + /* Actions, variables, functions and operator functions */ int DSOLOCAL expand_macros(modsec_rec *msr, msc_string *var, msre_rule *rule, apr_pool_t *mptmp); @@ -63,6 +67,11 @@ int DSOLOCAL msre_parse_generic(apr_pool_t *pool, const char *text, apr_table_t int DSOLOCAL rule_id_in_range(int ruleid, const char *range); +msre_var DSOLOCAL *generate_single_var(modsec_rec *msr, msre_var *var, apr_array_header_t *tfn_arr, + msre_rule *rule, apr_pool_t *mptmp); + +apr_table_t DSOLOCAL *generate_multi_var(modsec_rec *msr, msre_var *var, apr_array_header_t *tfn_arr, + msre_rule *rule, apr_pool_t *mptmp); /* Structures with the corresponding functions */ @@ -83,7 +92,7 @@ msre_op_metadata DSOLOCAL *msre_engine_op_resolve(msre_engine *engine, const cha struct msre_ruleset { apr_pool_t *mp; msre_engine *engine; - + apr_array_header_t *phase_request_headers; apr_array_header_t *phase_request_body; apr_array_header_t *phase_response_headers; @@ -99,6 +108,8 @@ msre_ruleset DSOLOCAL *msre_ruleset_create(msre_engine *engine, apr_pool_t *mp); int DSOLOCAL msre_ruleset_rule_add(msre_ruleset *ruleset, msre_rule *rule, int phase); +msre_rule DSOLOCAL *msre_ruleset_fetch_rule(msre_ruleset *ruleset, const char *id); + int DSOLOCAL msre_ruleset_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re); /* @@ -109,6 +120,17 @@ int DSOLOCAL msre_ruleset_phase_rule_remove_with_exception(msre_ruleset *ruleset #define RULE_NO_MATCH 0 #define RULE_MATCH 1 +#define RULE_PH_NONE 0 /* Not a placeholder */ +#define RULE_PH_SKIPAFTER 1 /* Implicit placeholder for skipAfter */ +#define RULE_PH_MARKER 2 /* Explicit placeholder for SecMarker */ + +#define RULE_TYPE_NORMAL 0 /* SecRule */ +#define RULE_TYPE_ACTION 1 /* SecAction */ +#define RULE_TYPE_MARKER 2 /* SecMarker */ +#if defined(WITH_LUA) +#define RULE_TYPE_LUA 3 /* SecRuleScript */ +#endif + struct msre_rule { apr_array_header_t *targets; const char *op_name; @@ -117,18 +139,38 @@ struct msre_rule { msre_op_metadata *op_metadata; unsigned int op_negated; msre_actionset *actionset; + const char *p1; + const char *unparsed; const char *filename; int line_num; - + int placeholder; + int type; + msre_ruleset *ruleset; msre_rule *chain_starter; + #if defined(PERFORMANCE_MEASUREMENT) + unsigned int execution_time; + unsigned int trans_time; + unsigned int op_time; + #endif + + #if defined(WITH_LUA) + /* Compiled Lua script. */ + msc_script *script; + #endif }; -msre_rule DSOLOCAL *msre_rule_create(msre_ruleset *ruleset, +char DSOLOCAL *msre_rule_generate_unparsed(apr_pool_t *pool, const msre_rule *rule, const char *targets, const char *args, const char *actions); + +msre_rule DSOLOCAL *msre_rule_create(msre_ruleset *ruleset, int type, const char *fn, int line, const char *targets, const char *args, const char *actions, char **error_msg); -void DSOLOCAL msre_rule_actionset_init(msre_rule *rule); +#if defined(WITH_LUA) +msre_rule DSOLOCAL *msre_rule_lua_create(msre_ruleset *ruleset, + const char *fn, int line, const char *script_filename, + const char *actions, char **error_msg); +#endif apr_status_t DSOLOCAL msre_rule_process(msre_rule *rule, modsec_rec *msr); @@ -141,17 +183,16 @@ apr_status_t DSOLOCAL msre_rule_process(msre_rule *rule, modsec_rec *msr); #define PHASE_RESPONSE_BODY 4 #define PHASE_LOGGING 5 -#define FN_OP_PARAM_INIT(X) int (*X)(msre_rule *rule, char **error_msg) -#define FN_OP_EXECUTE(X) int (*X)(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) - +typedef int (*fn_op_param_init_t)(msre_rule *rule, char **error_msg); +typedef int (*fn_op_execute_t)(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg); struct msre_op_metadata { const char *name; - FN_OP_PARAM_INIT (param_init); - FN_OP_EXECUTE (execute); + fn_op_param_init_t param_init; + fn_op_execute_t execute; }; -#define FN_TFN_EXECUTE(X) int (*X)(apr_pool_t *pool, unsigned char *input, long int input_length, char **rval, long int *rval_length) +typedef int (*fn_tfn_execute_t)(apr_pool_t *pool, unsigned char *input, long int input_length, char **rval, long int *rval_length); struct msre_tfn_metadata { const char *name; @@ -167,14 +208,14 @@ struct msre_tfn_metadata { * * NOTE Strict transformation functions not supported yet. */ - FN_TFN_EXECUTE(execute); + fn_tfn_execute_t execute; }; void DSOLOCAL msre_engine_tfn_register(msre_engine *engine, const char *name, - FN_TFN_EXECUTE(execute)); + fn_tfn_execute_t execute); void DSOLOCAL msre_engine_op_register(msre_engine *engine, const char *name, - FN_OP_PARAM_INIT(fn1), FN_OP_EXECUTE(fn2)); + fn_op_param_init_t fn1, fn_op_execute_t fn2); void DSOLOCAL msre_engine_register_default_tfns(msre_engine *engine); @@ -189,16 +230,16 @@ msre_tfn_metadata DSOLOCAL *msre_engine_tfn_resolve(msre_engine *engine, const c #define VAR_DONT_CACHE 0 #define VAR_CACHE 1 -#define FN_VAR_VALIDATE(X) char *(*X)(msre_ruleset *ruleset, msre_var *var) -#define FN_VAR_GENERATE(X) int (*X)(modsec_rec *msr, msre_var *var, msre_rule *rule, apr_table_t *table, apr_pool_t *mptmp) +typedef char *(*fn_var_validate_t)(msre_ruleset *ruleset, msre_var *var); +typedef int (*fn_var_generate_t)(modsec_rec *msr, msre_var *var, msre_rule *rule, apr_table_t *table, apr_pool_t *mptmp); struct msre_var_metadata { const char *name; unsigned int type; /* VAR_TYPE_ constants */ unsigned int argc_min; unsigned int argc_max; - FN_VAR_VALIDATE (validate); - FN_VAR_GENERATE (generate); + fn_var_validate_t validate; + fn_var_generate_t generate; unsigned int is_cacheable; /* 0 - no, 1 - yes */ unsigned int availability; /* when does this variable become available? */ }; @@ -223,6 +264,7 @@ struct msre_actionset { const char *id; const char *rev; const char *msg; + const char *logdata; int severity; int phase; msre_rule *rule; @@ -230,6 +272,7 @@ struct msre_actionset { /* Flow */ int is_chained; int skip_count; + const char *skip_after; /* Disruptive */ int intercept_action; @@ -237,14 +280,22 @@ struct msre_actionset { int intercept_status; int intercept_pause; + /* "block" needs parent action to reset it */ + msre_action *parent_intercept_action_rec; + msre_action *intercept_action_rec; + int parent_intercept_action; + /* Other */ int log; int auditlog; + int block; }; -void DSOLOCAL msre_engine_variable_register(msre_engine *engine, const char *name, +char DSOLOCAL *msre_actionset_generate_action_string(apr_pool_t *pool, const msre_actionset *actionset); + +void DSOLOCAL msre_engine_variable_register(msre_engine *engine, const char *name, unsigned int type, unsigned int argc_min, unsigned int argc_max, - FN_VAR_VALIDATE(validate), FN_VAR_GENERATE(generate), + fn_var_validate_t validate, fn_var_generate_t generate, unsigned int is_cacheable, unsigned int availability); msre_actionset DSOLOCAL *msre_actionset_create(msre_engine *engine, const char *text, @@ -255,11 +306,13 @@ msre_actionset DSOLOCAL *msre_actionset_merge(msre_engine *engine, msre_actionse msre_actionset DSOLOCAL *msre_actionset_create_default(msre_engine *engine); +void DSOLOCAL msre_actionset_set_defaults(msre_actionset *actionset); + void DSOLOCAL msre_actionset_init(msre_actionset *actionset, msre_rule *rule); -#define FN_ACTION_VALIDATE(X) char *(*X)(msre_engine *engine, msre_action *action) -#define FN_ACTION_INIT(X) apr_status_t (*X)(msre_engine *engine, msre_actionset *actionset, msre_action *action) -#define FN_ACTION_EXECUTE(X) apr_status_t (*X)(modsec_rec *msr, apr_pool_t *mptmp, msre_rule *rule, msre_action *action) +typedef char *(*fn_action_validate_t)(msre_engine *engine, msre_action *action); +typedef apr_status_t (*fn_action_init_t)(msre_engine *engine, msre_actionset *actionset, msre_action *action); +typedef apr_status_t (*fn_action_execute_t)(modsec_rec *msr, apr_pool_t *mptmp, msre_rule *rule, msre_action *action); #define ACTION_DISRUPTIVE 1 #define ACTION_NON_DISRUPTIVE 2 @@ -272,6 +325,11 @@ void DSOLOCAL msre_actionset_init(msre_actionset *actionset, msre_rule *rule); #define ACTION_CARDINALITY_ONE 1 #define ACTION_CARDINALITY_MANY 2 +#define ACTION_CGROUP_NONE 0 +#define ACTION_CGROUP_DISRUPTIVE 1 +#define ACTION_CGROUP_LOG 2 +#define ACTION_CGROUP_AUDITLOG 3 + struct msre_action_metadata { const char *name; unsigned int type; @@ -279,9 +337,10 @@ struct msre_action_metadata { unsigned int argc_max; unsigned int allow_param_plusminus; unsigned int cardinality; - FN_ACTION_VALIDATE (validate); - FN_ACTION_INIT (init); - FN_ACTION_EXECUTE (execute); + unsigned int cardinality_group; + fn_action_validate_t validate; + fn_action_init_t init; + fn_action_execute_t execute; }; struct msre_action { @@ -309,7 +368,8 @@ char DSOLOCAL *msre_format_metadata(modsec_rec *msr, msre_actionset *actionset); struct msre_cache_rec { int hits; int changed; - const char *key; + int num; + const char *path; const char *val; apr_size_t val_len; }; diff --git a/apache2/re_actions.c b/apache2/re_actions.c index fd0453a2..00eada8d 100644 --- a/apache2/re_actions.c +++ b/apache2/re_actions.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -14,10 +14,11 @@ /** * Register action with the engine. */ -static void msre_engine_action_register(msre_engine *engine, const char *name, unsigned int type, - unsigned int argc_min, unsigned int argc_max, unsigned int allow_param_plusminus, - unsigned int cardinality, FN_ACTION_VALIDATE(validate), FN_ACTION_INIT(init), - FN_ACTION_EXECUTE(execute)) +static void msre_engine_action_register(msre_engine *engine, const char *name, + unsigned int type, unsigned int argc_min, unsigned int argc_max, + unsigned int allow_param_plusminus, unsigned int cardinality, + unsigned int cardinality_group, fn_action_validate_t validate, + fn_action_init_t init, fn_action_execute_t execute) { msre_action_metadata *metadata = (msre_action_metadata *)apr_pcalloc(engine->mp, sizeof(msre_action_metadata)); @@ -29,6 +30,7 @@ static void msre_engine_action_register(msre_engine *engine, const char *name, u metadata->argc_max = argc_max; metadata->allow_param_plusminus = allow_param_plusminus; metadata->cardinality = cardinality; + metadata->cardinality_group = cardinality_group; metadata->validate = validate; metadata->init = init; metadata->execute = execute; @@ -39,21 +41,118 @@ static void msre_engine_action_register(msre_engine *engine, const char *name, u /** * Generates a single variable (from the supplied metadata). */ -static msre_var *generate_single_var(modsec_rec *msr, msre_var *var, msre_rule *rule, - apr_pool_t *mptmp) +msre_var *generate_single_var(modsec_rec *msr, msre_var *var, apr_array_header_t *tfn_arr, + msre_rule *rule, apr_pool_t *mptmp) { apr_table_t *vartab = NULL; const apr_table_entry_t *te = NULL; const apr_array_header_t *arr = NULL; + msre_var *rvar = NULL; + int i; + + /* Sanity check. */ + if ((var == NULL)||(var->metadata == NULL)||(var->metadata->generate == NULL)) return NULL; - if (var->metadata->generate == NULL) return NULL; vartab = apr_table_make(mptmp, 16); var->metadata->generate(msr, var, rule, vartab, mptmp); + arr = apr_table_elts(vartab); if (arr->nelts == 0) return NULL; - te = (apr_table_entry_t *)arr->elts; + te = (apr_table_entry_t *)arr->elts; - return (msre_var *)te[0].val; + rvar = (msre_var *)te[0].val; + + /* Return straight away if there were no + * transformation functions supplied. + */ + if ((tfn_arr == NULL)||(tfn_arr->nelts == 0)) { + return rvar; + } + + /* Copy the value so that we can transform it in place. */ + rvar->value = apr_pstrndup(mptmp, rvar->value, rvar->value_len); + + /* Transform rvar in a loop. */ + for (i = 0; i < tfn_arr->nelts; i++) { + msre_tfn_metadata *tfn = ((msre_tfn_metadata **)tfn_arr->elts)[i]; + char *rval; + int rc; + long int rval_len; + + rc = tfn->execute(mptmp, (unsigned char *)rvar->value, + rvar->value_len, &rval, &rval_len); + + rvar->value = rval; + rvar->value_len = rval_len; + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "T (%d) %s: \"%s\"", rc, tfn->name, + log_escape_nq_ex(mptmp, rvar->value, rvar->value_len)); + } + } + + return rvar; +} + +/** + * + */ +apr_table_t *generate_multi_var(modsec_rec *msr, msre_var *var, apr_array_header_t *tfn_arr, + msre_rule *rule, apr_pool_t *mptmp) +{ + const apr_array_header_t *tarr; + const apr_table_entry_t *telts; + apr_table_t *vartab = NULL, *tvartab = NULL; + msre_var *rvar = NULL; + int i, j; + + /* Sanity check. */ + if ((var == NULL)||(var->metadata == NULL)||(var->metadata->generate == NULL)) return NULL; + + /* Generate variables. */ + vartab = apr_table_make(mptmp, 16); + var->metadata->generate(msr, var, rule, vartab, mptmp); + + /* Return straight away if there were no + * transformation functions supplied. + */ + if ((tfn_arr == NULL)||(tfn_arr->nelts == 0)) { + return vartab; + } + + tvartab = apr_table_make(mptmp, 16); + + tarr = apr_table_elts(vartab); + telts = (const apr_table_entry_t*)tarr->elts; + for (j = 0; j < tarr->nelts; j++) { + rvar = (msre_var *)telts[j].val; + + /* Copy the value so that we can transform it in place. */ + rvar->value = apr_pstrndup(mptmp, rvar->value, rvar->value_len); + + /* Transform rvar in a loop. */ + for (i = 0; i < tfn_arr->nelts; i++) { + msre_tfn_metadata *tfn = ((msre_tfn_metadata **)tfn_arr->elts)[i]; + char *rval; + int rc; + long int rval_len; + + rc = tfn->execute(mptmp, (unsigned char *)rvar->value, + rvar->value_len, &rval, &rval_len); + + rvar->value = rval; + rvar->value_len = rval_len; + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "T (%d) %s: \"%s\"", rc, tfn->name, + log_escape_nq_ex(mptmp, rvar->value, rvar->value_len)); + } + } + + apr_table_addn(tvartab, rvar->name, (void *)rvar); + } + + return tvartab; } /** @@ -102,24 +201,11 @@ int expand_macros(modsec_rec *msr, msc_string *var, msre_rule *rule, apr_pool_t *q = '\0'; } - /* ENH Do we want to support %{DIGIT} as well? */ - next_text_start = t + 1; /* *t was '}' */ } else { next_text_start = t; /* *t was '\0' */ } } -/* Removed %0-9 macros as it messes up urlEncoding in the match - * where having '%0a' will be treated as %{TX.0}a, which is incorrect. - * */ -#if 0 - else if ((*(p + 1) >= '0')&&(*(p + 1) <= '9')) { - /* Special case for regex captures. */ - var_name = "TX"; - var_value = apr_pstrmemdup(mptmp, p + 1, 1); - next_text_start = p + 2; - } -#endif if (var_name != NULL) { char *my_error_msg = NULL; @@ -137,18 +223,20 @@ int expand_macros(modsec_rec *msr, msc_string *var, msre_rule *rule, apr_pool_t var_resolved = msre_create_var_ex(mptmp, msr->modsecurity->msre, var_name, var_value, msr, &my_error_msg); if (var_resolved != NULL) { - var_generated = generate_single_var(msr, var_resolved, rule, mptmp); + var_generated = generate_single_var(msr, var_resolved, NULL, rule, mptmp); if (var_generated != NULL) { part = (msc_string *)apr_pcalloc(mptmp, sizeof(msc_string)); if (part == NULL) return -1; part->value_len = var_generated->value_len; part->value = (char *)var_generated->value; *(msc_string **)apr_array_push(arr) = part; - msr_log(msr, 9, "Resolved macro %%{%s%s%s} to \"%s\"", - var_name, - (var_value ? "." : ""), - (var_value ? var_value : ""), - log_escape_ex(mptmp, part->value, part->value_len)); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Resolved macro %%{%s%s%s} to \"%s\"", + var_name, + (var_value ? "." : ""), + (var_value ? var_value : ""), + log_escape_ex(mptmp, part->value, part->value_len)); + } } } else { msr_log(msr, 4, "Failed to resolve macro %%{%s%s%s}: %s", @@ -232,6 +320,15 @@ static apr_status_t msre_action_msg_init(msre_engine *engine, msre_actionset *ac return 1; } +/* logdata */ + +static apr_status_t msre_action_logdata_init(msre_engine *engine, msre_actionset *actionset, + msre_action *action) +{ + actionset->logdata = action->param; + return 1; +} + /* severity */ static apr_status_t msre_action_severity_init(msre_engine *engine, @@ -283,11 +380,21 @@ static apr_status_t msre_action_noauditlog_init(msre_engine *engine, msre_action return 1; } +/* block */ +static apr_status_t msre_action_block_init(msre_engine *engine, msre_actionset *actionset, + msre_action *action) +{ + /* Right now we just set a flag and inherit the real disruptive action */ + actionset->block = 1; + return 1; +} + /* deny */ static apr_status_t msre_action_deny_init(msre_engine *engine, msre_actionset *actionset, msre_action *action) { actionset->intercept_action = ACTION_DENY; + actionset->intercept_action_rec = action; return 1; } @@ -309,6 +416,7 @@ static apr_status_t msre_action_drop_init(msre_engine *engine, msre_actionset *a msre_action *action) { actionset->intercept_action = ACTION_DROP; + actionset->intercept_action_rec = action; return 1; } @@ -337,6 +445,7 @@ static apr_status_t msre_action_redirect_init(msre_engine *engine, msre_actionse { actionset->intercept_action = ACTION_REDIRECT; actionset->intercept_uri = action->param; + actionset->intercept_action_rec = action; return 1; } @@ -352,7 +461,7 @@ static apr_status_t msre_action_redirect_execute(modsec_rec *msr, apr_pool_t *mp expand_macros(msr, var, rule, mptmp); rule->actionset->intercept_uri = apr_pstrmemdup(msr->mp, var->value, var->value_len); - + return 1; } @@ -368,6 +477,7 @@ static apr_status_t msre_action_proxy_init(msre_engine *engine, msre_actionset * { actionset->intercept_action = ACTION_PROXY; actionset->intercept_uri = action->param; + actionset->intercept_action_rec = action; return 1; } @@ -383,7 +493,7 @@ static apr_status_t msre_action_proxy_execute(modsec_rec *msr, apr_pool_t *mptmp expand_macros(msr, var, rule, mptmp); rule->actionset->intercept_uri = apr_pstrmemdup(msr->mp, var->value, var->value_len); - + return 1; } @@ -393,6 +503,7 @@ static apr_status_t msre_action_pass_init(msre_engine *engine, msre_actionset *a msre_action *action) { actionset->intercept_action = ACTION_NONE; + actionset->intercept_action_rec = action; return 1; } @@ -411,15 +522,55 @@ static apr_status_t msre_action_skip_init(msre_engine *engine, msre_actionset *a return 1; } +/* skipAfter */ + +static char *msre_action_skipAfter_validate(msre_engine *engine, msre_action *action) { + /* ENH Add validation. */ + return NULL; +} + +static apr_status_t msre_action_skipAfter_init(msre_engine *engine, msre_actionset *actionset, + msre_action *action) +{ + actionset->skip_after = action->param; + return 1; +} + /* allow */ static apr_status_t msre_action_allow_init(msre_engine *engine, msre_actionset *actionset, msre_action *action) { actionset->intercept_action = ACTION_ALLOW; + actionset->intercept_action_rec = action; + + if (action->param != NULL) { + if (strcasecmp(action->param, "phase") == 0) { + actionset->intercept_action = ACTION_ALLOW_PHASE; + } else + if (strcasecmp(action->param, "request") == 0) { + actionset->intercept_action = ACTION_ALLOW_REQUEST; + } + } + return 1; } +static char *msre_action_allow_validate(msre_engine *engine, msre_action *action) { + if (action->param != NULL) { + if (strcasecmp(action->param, "phase") == 0) { + return NULL; + } else + if (strcasecmp(action->param, "request") == 0) { + return NULL; + } else { + return apr_psprintf(engine->mp, "Invalid parameter for allow: %s", action->param); + } + } + + return NULL; +} + /* phase */ static char *msre_action_phase_validate(msre_engine *engine, msre_action *action) { @@ -467,40 +618,44 @@ static char *msre_action_ctl_validate(msre_engine *engine, msre_action *action) } /* Validate value. */ - if (strcmp(name, "ruleEngine") == 0) { + if (strcasecmp(name, "ruleEngine") == 0) { if (strcasecmp(value, "on") == 0) return NULL; if (strcasecmp(value, "off") == 0) return NULL; if (strcasecmp(value, "detectiononly") == 0) return NULL; return apr_psprintf(engine->mp, "Invalid setting for ctl name ruleEngine: %s", value); } else - if (strcmp(name, "requestBodyAccess") == 0) { + if (strcasecmp(name, "ruleRemoveById") == 0) { + /* ENH nothing yet */ + return NULL; + } else + if (strcasecmp(name, "requestBodyAccess") == 0) { if (parse_boolean(value) == -1) { return apr_psprintf(engine->mp, "Invalid setting for ctl name " " requestBodyAccess: %s", value); } return NULL; } else - if (strcmp(name, "requestBodyProcessor") == 0) { + if (strcasecmp(name, "requestBodyProcessor") == 0) { /* ENH We will accept anything for now but it'd be nice * to add a check here that the processor name is a valid one. */ return NULL; } else - if (strcmp(name, "responseBodyAccess") == 0) { + if (strcasecmp(name, "responseBodyAccess") == 0) { if (parse_boolean(value) == -1) { return apr_psprintf(engine->mp, "Invalid setting for ctl name " " responseBodyAccess: %s", value); } return NULL; } else - if (strcmp(name, "auditEngine") == 0) { + if (strcasecmp(name, "auditEngine") == 0) { if (strcasecmp(value, "on") == 0) return NULL; if (strcasecmp(value, "off") == 0) return NULL; if (strcasecmp(value, "relevantonly") == 0) return NULL; return apr_psprintf(engine->mp, "Invalid setting for ctl name " " auditEngine: %s", value); } else - if (strcmp(name, "auditLogParts") == 0) { + if (strcasecmp(name, "auditLogParts") == 0) { if ((value[0] == '+')||(value[0] == '-')) { if (is_valid_parts_specification(value + 1) != 1) { return apr_psprintf(engine->mp, "Invalid setting for ctl name " @@ -514,12 +669,12 @@ static char *msre_action_ctl_validate(msre_engine *engine, msre_action *action) } return NULL; } else - if (strcmp(name, "debugLogLevel") == 0) { + if (strcasecmp(name, "debugLogLevel") == 0) { if ((atoi(value) >= 0)&&(atoi(value) <= 9)) return NULL; return apr_psprintf(engine->mp, "Invalid setting for ctl name " "debugLogLevel: %s", value); } else - if (strcmp(name, "requestBodyLimit") == 0) { + if (strcasecmp(name, "requestBodyLimit") == 0) { long int limit = strtol(value, NULL, 10); if ((limit == LONG_MAX)||(limit == LONG_MIN)||(limit <= 0)) { @@ -529,12 +684,12 @@ static char *msre_action_ctl_validate(msre_engine *engine, msre_action *action) if (limit > REQUEST_BODY_HARD_LIMIT) { return apr_psprintf(engine->mp, "Request size limit cannot exceed " - "the hard limit: %li", RESPONSE_BODY_HARD_LIMIT); + "the hard limit: %ld", RESPONSE_BODY_HARD_LIMIT); } return NULL; } else - if (strcmp(name, "responseBodyLimit") == 0) { + if (strcasecmp(name, "responseBodyLimit") == 0) { long int limit = strtol(value, NULL, 10); if ((limit == LONG_MAX)||(limit == LONG_MIN)||(limit <= 0)) { @@ -544,7 +699,7 @@ static char *msre_action_ctl_validate(msre_engine *engine, msre_action *action) if (limit > RESPONSE_BODY_HARD_LIMIT) { return apr_psprintf(engine->mp, "Response size limit cannot exceed " - "the hard limit: %li", RESPONSE_BODY_HARD_LIMIT); + "the hard limit: %ld", RESPONSE_BODY_HARD_LIMIT); } return NULL; @@ -572,17 +727,17 @@ static apr_status_t msre_action_ctl_execute(modsec_rec *msr, apr_pool_t *mptmp, if (value == NULL) return -1; /* Validate value. */ - if (strcmp(name, "ruleEngine") == 0) { + if (strcasecmp(name, "ruleEngine") == 0) { if (strcasecmp(value, "on") == 0) { msr->txcfg->is_enabled = MODSEC_ENABLED; msr->usercfg->is_enabled = MODSEC_ENABLED; } - + else if (strcasecmp(value, "off") == 0) { msr->txcfg->is_enabled = MODSEC_DISABLED; msr->usercfg->is_enabled = MODSEC_DISABLED; } - + else if (strcasecmp(value, "detectiononly") == 0) { msr->txcfg->is_enabled = MODSEC_DETECTION_ONLY; msr->usercfg->is_enabled = MODSEC_DETECTION_ONLY; @@ -590,53 +745,57 @@ static apr_status_t msre_action_ctl_execute(modsec_rec *msr, apr_pool_t *mptmp, return 1; } else - if (strcmp(name, "requestBodyAccess") == 0) { + if (strcasecmp(name, "ruleRemoveById") == 0) { + *(const char **)apr_array_push(msr->removed_rules) = (const char *)apr_pstrdup(msr->mp, value); + return 1; + } else + if (strcasecmp(name, "requestBodyAccess") == 0) { int pv = parse_boolean(value); if (pv == -1) return -1; msr->txcfg->reqbody_access = pv; msr->usercfg->reqbody_access = pv; - msr_log(msr, 4, "Ctl: Set requestBodyAccess to %i.", pv); + msr_log(msr, 4, "Ctl: Set requestBodyAccess to %d.", pv); return 1; } else - if (strcmp(name, "requestBodyProcessor") == 0) { + if (strcasecmp(name, "requestBodyProcessor") == 0) { msr->msc_reqbody_processor = value; msr_log(msr, 4, "Ctl: Set requestBodyProcessor to %s.", value); return 1; } else - if (strcmp(name, "responseBodyAccess") == 0) { + if (strcasecmp(name, "responseBodyAccess") == 0) { int pv = parse_boolean(value); if (pv == -1) return -1; msr->txcfg->resbody_access = pv; msr->usercfg->resbody_access = pv; - msr_log(msr, 4, "Ctl: Set responseBodyAccess to %i.", pv); + msr_log(msr, 4, "Ctl: Set responseBodyAccess to %d.", pv); return 1; } else - if (strcmp(name, "auditEngine") == 0) { + if (strcasecmp(name, "auditEngine") == 0) { if (strcasecmp(value, "on") == 0) { msr->txcfg->auditlog_flag = AUDITLOG_ON; msr->usercfg->auditlog_flag = AUDITLOG_ON; } - + else if (strcasecmp(value, "off") == 0) { msr->txcfg->auditlog_flag = AUDITLOG_OFF; msr->usercfg->auditlog_flag = AUDITLOG_OFF; } - + else if (strcasecmp(value, "relevantonly") == 0) { msr->txcfg->auditlog_flag = AUDITLOG_RELEVANT; msr->usercfg->auditlog_flag = AUDITLOG_RELEVANT; } - msr_log(msr, 4, "Ctl: Set auditEngine to %i.", msr->txcfg->auditlog_flag); // TODO + msr_log(msr, 4, "Ctl: Set auditEngine to %d.", msr->txcfg->auditlog_flag); // TODO return 1; } else - if (strcmp(name, "auditLogParts") == 0) { + if (strcasecmp(name, "auditLogParts") == 0) { char *new_value = value; if (value[0] == '+') { @@ -656,9 +815,9 @@ static apr_status_t msre_action_ctl_execute(modsec_rec *msr, apr_pool_t *mptmp, while(*s != '\0') { if (*s != c) { - *d++ = *s++; + *(d++) = *(s++); } else { - (*s)++; /* parens quiet compiler warning */ + s++; } } *d = '\0'; @@ -672,14 +831,14 @@ static apr_status_t msre_action_ctl_execute(modsec_rec *msr, apr_pool_t *mptmp, return 1; } else - if (strcmp(name, "debugLogLevel") == 0) { + if (strcasecmp(name, "debugLogLevel") == 0) { msr->txcfg->debuglog_level = atoi(value); msr->usercfg->debuglog_level = atoi(value); - msr_log(msr, 4, "Ctl: Set debugLogLevel to %i.", msr->txcfg->debuglog_level); + msr_log(msr, 4, "Ctl: Set debugLogLevel to %d.", msr->txcfg->debuglog_level); return 1; } else - if (strcmp(name, "requestBodyLimit") == 0) { + if (strcasecmp(name, "requestBodyLimit") == 0) { long int limit = strtol(value, NULL, 10); /* ENH Accept only in correct phase warn otherwise. */ @@ -688,7 +847,7 @@ static apr_status_t msre_action_ctl_execute(modsec_rec *msr, apr_pool_t *mptmp, return 1; } else - if (strcmp(name, "responseBodyLimit") == 0) { + if (strcasecmp(name, "responseBodyLimit") == 0) { long int limit = strtol(value, NULL, 10); /* ENH Accept only in correct phase warn otherwise. */ @@ -698,9 +857,10 @@ static apr_status_t msre_action_ctl_execute(modsec_rec *msr, apr_pool_t *mptmp, return 1; } else { - /* ENH Should never happen, but log if it does. */ + /* Should never happen, but log if it does. */ + msr_log(msr, 1, "Internal Error: Unknown ctl action \"%s\".", name); return -1; - } + } } /* xmlns */ @@ -760,39 +920,40 @@ static apr_status_t msre_action_sanitiseMatched_execute(modsec_rec *msr, apr_poo const apr_array_header_t *tarr; const apr_table_entry_t *telts; int i, type = 0; + msc_string *mvar = msr->matched_var; - if (msr->matched_var == NULL) return 0; + if (mvar->name_len == 0) return 0; /* IMP1 We need to extract the variable name properly here, * taking into account it may have been escaped. */ - if (strncmp(msr->matched_var, "ARGS:", 5) == 0) { - sargname = apr_pstrdup(msr->mp, msr->matched_var + 5); + if ((mvar->name_len > 5) && (strncmp(mvar->name, "ARGS:", 5) == 0)) { + sargname = apr_pstrdup(msr->mp, mvar->name + 5); type = SANITISE_ARG; } else - if (strncmp(msr->matched_var, "ARGS_NAMES:", 11) == 0) { - sargname = apr_pstrdup(msr->mp, msr->matched_var + 11); + if ((mvar->name_len > 11) && (strncmp(mvar->name, "ARGS_NAMES:", 11) == 0)) { + sargname = apr_pstrdup(msr->mp, mvar->name + 11); type = SANITISE_ARG; } else - if (strncmp(msr->matched_var, "REQUEST_HEADERS:", 16) == 0) { - sargname = apr_pstrdup(msr->mp, msr->matched_var + 16); + if ((mvar->name_len > 16) && (strncmp(mvar->name, "REQUEST_HEADERS:", 16) == 0)) { + sargname = apr_pstrdup(msr->mp, mvar->name + 16); type = SANITISE_REQUEST_HEADER; } else - if (strncmp(msr->matched_var, "REQUEST_HEADERS_NAMES:", 22) == 0) { - sargname = apr_pstrdup(msr->mp, msr->matched_var + 22); + if ((mvar->name_len > 22) && (strncmp(mvar->name, "REQUEST_HEADERS_NAMES:", 22) == 0)) { + sargname = apr_pstrdup(msr->mp, mvar->name + 22); type = SANITISE_REQUEST_HEADER; } else - if (strncmp(msr->matched_var, "RESPONSE_HEADERS:", 17) == 0) { - sargname = apr_pstrdup(msr->mp, msr->matched_var + 17); + if ((mvar->name_len > 17) && (strncmp(mvar->name, "RESPONSE_HEADERS:", 17) == 0)) { + sargname = apr_pstrdup(msr->mp, mvar->name + 17); type = SANITISE_RESPONSE_HEADER; } else - if (strncmp(msr->matched_var, "RESPONSE_HEADERS_NAMES:", 23) == 0) { - sargname = apr_pstrdup(msr->mp, msr->matched_var + 23); + if ((mvar->name_len > 23) && (strncmp(mvar->name, "RESPONSE_HEADERS_NAMES:", 23) == 0)) { + sargname = apr_pstrdup(msr->mp, mvar->name + 23); type = SANITISE_RESPONSE_HEADER; } else { msr_log(msr, 3, "sanitiseMatched: Don't know how to handle variable: %s", - msr->matched_var); + mvar->name); return 0; } @@ -847,6 +1008,7 @@ static apr_status_t msre_action_setenv_execute(modsec_rec *msr, apr_pool_t *mptm char *data = apr_pstrdup(mptmp, action->param); char *env_name = NULL, *env_value = NULL; char *s = NULL; + msc_string *env = NULL; /* Extract the name and the value. */ /* IMP1 We have a function for this now, parse_name_eq_value? */ @@ -860,13 +1022,53 @@ static apr_status_t msre_action_setenv_execute(modsec_rec *msr, apr_pool_t *mptm *s = '\0'; } + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Setting env enviable: %s=%s", env_name, env_value); + } + + /* Expand and escape any macros in the name */ + env = apr_palloc(msr->mp, sizeof(msc_string)); + if (env == NULL) { + msr_log(msr, 1, "Failed to allocate space to expand name macros"); + return -1; + } + env->value = env_name; + env->value_len = strlen(env->value); + expand_macros(msr, env, rule, mptmp); + env_name = log_escape_ex(msr->mp, env->value, env->value_len); + /* Execute the requested action. */ if (env_name[0] == '!') { /* Delete */ apr_table_unset(msr->r->subprocess_env, env_name + 1); + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Unset env variable \"%s\".", env_name); + } } else { /* Set */ - apr_table_set(msr->r->subprocess_env, env_name, env_value); + char * val_value = NULL; + msc_string *val = apr_palloc(msr->mp, sizeof(msc_string)); + if (val == NULL) { + msr_log(msr, 1, "Failed to allocate space to expand value macros"); + return -1; + } + + /* Expand values in value */ + val->value = env_value; + val->value_len = strlen(val->value); + expand_macros(msr, val, rule, mptmp); + + /* To be safe, we escape the value as it goes in subprocess_env. */ + val_value = log_escape_ex(msr->mp, val->value, val->value_len); + + apr_table_set(msr->r->subprocess_env, env_name, val_value); + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Set env variable \"%s\" to \"%s\".", + env_name, + log_escape(mptmp, val_value)); + } } return 1; @@ -881,6 +1083,7 @@ static apr_status_t msre_action_setvar_execute(modsec_rec *msr, apr_pool_t *mptm char *s = NULL; apr_table_t *target_col = NULL; int is_negated = 0; + msc_string *var = NULL; /* Extract the name and the value. */ /* IMP1 We have a function for this now, parse_name_eq_value? */ @@ -896,6 +1099,22 @@ static apr_status_t msre_action_setvar_execute(modsec_rec *msr, apr_pool_t *mptm while ((*var_value != '\0')&&(isspace(*var_value))) var_value++; } + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Setting variable: %s=%s", var_name, var_value); + } + + + /* Expand and escape any macros in the name */ + var = apr_palloc(msr->mp, sizeof(msc_string)); + if (var == NULL) { + msr_log(msr, 1, "Failed to allocate space to expand name macros"); + return -1; + } + var->value = var_name; + var->value_len = strlen(var->value); + expand_macros(msr, var, rule, mptmp); + var_name = log_escape_ex(msr->mp, var->value, var->value_len); + /* Handle the exclamation mark. */ if (var_name[0] == '!') { var_name = var_name + 1; @@ -909,8 +1128,9 @@ static apr_status_t msre_action_setvar_execute(modsec_rec *msr, apr_pool_t *mptm target_col = msr->tx_vars; s = strstr(var_name, "."); if (s == NULL) { - /* ENH Log warning detected variable name but no collection. */ - return 0; + msr_log(msr, 3, "Asked to set variable \"%s\", but no collection name specified. ", + log_escape(msr->mp, var_name)); + return 0; } col_name = var_name; var_name = s + 1; @@ -926,7 +1146,7 @@ static apr_status_t msre_action_setvar_execute(modsec_rec *msr, apr_pool_t *mptm log_escape(msr->mp, col_name), log_escape(msr->mp, var_name)); return 0; } - } + } if (is_negated) { /* Unset variable. */ @@ -934,25 +1154,45 @@ static apr_status_t msre_action_setvar_execute(modsec_rec *msr, apr_pool_t *mptm /* ENH Refuse to remove certain variables, e.g. TIMEOUT, internal variables, etc... */ apr_table_unset(target_col, var_name); - msr_log(msr, 9, "Unset variable \"%s.%s\".", log_escape(mptmp, col_name), - log_escape(mptmp, var_name)); - } else { + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Unset variable \"%s.%s\".", col_name, var_name); + } + } + else { /* Set or change variable. */ if ((var_value[0] == '+')||(var_value[0] == '-')) { /* Relative change. */ - msc_string *var = NULL; + msc_string *rec = NULL; + msc_string *val = apr_palloc(msr->mp, sizeof(msc_string)); int value = 0; + if (val == NULL) { + msr_log(msr, 1, "Failed to allocate space to expand value macros"); + return -1; + } + /* Retrieve variable or generate (if it does not exist). */ - var = (msc_string *)apr_table_get(target_col, var_name); - if (var == NULL) { - var = apr_pcalloc(msr->mp, sizeof(msc_string)); - var->name = apr_pstrdup(msr->mp, var_name); - var->name_len = strlen(var->name); + rec = (msc_string *)apr_table_get(target_col, var_name); + if (rec == NULL) { + rec = var; /* use the already allocated space for var */ + rec->name = apr_pstrdup(msr->mp, var_name); + rec->name_len = strlen(rec->name); value = 0; - } else { - value = atoi(var->value); + } + else { + value = atoi(rec->value); + } + + /* Expand values in value */ + val->value = var_value; + val->value_len = strlen(val->value); + expand_macros(msr, val, rule, mptmp); + var_value = val->value; + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Relative change: %s=%d%s", var_name, value, var_value); } /* Change value. */ @@ -960,20 +1200,19 @@ static apr_status_t msre_action_setvar_execute(modsec_rec *msr, apr_pool_t *mptm if (value < 0) value = 0; /* Counters never go below zero. */ /* Put the variable back. */ - var->value = apr_psprintf(msr->mp, "%i", value); - var->value_len = strlen(var->value); - apr_table_setn(target_col, var->name, (void *)var); + rec->value = apr_psprintf(msr->mp, "%d", value); + rec->value_len = strlen(rec->value); + apr_table_setn(target_col, rec->name, (void *)rec); - msr_log(msr, 9, "Set variable \"%s.%s\" to \"%s\".", - log_escape(mptmp, col_name), - log_escape_ex(mptmp, var->name, var->name_len), - log_escape_ex(mptmp, var->value, var->value_len)); - } else { + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Set variable \"%s.%s\" to \"%s\".", + col_name, rec->name, + log_escape_ex(mptmp, rec->value, rec->value_len)); + } + } + else { /* Absolute change. */ - msc_string *var = NULL; - - var = apr_pcalloc(msr->mp, sizeof(msc_string)); var->name = apr_pstrdup(msr->mp, var_name); var->name_len = strlen(var->name); var->value = apr_pstrdup(msr->mp, var_value); @@ -981,10 +1220,12 @@ static apr_status_t msre_action_setvar_execute(modsec_rec *msr, apr_pool_t *mptm expand_macros(msr, var, rule, mptmp); apr_table_setn(target_col, var->name, (void *)var); - msr_log(msr, 9, "Set variable \"%s.%s\" to \"%s\".", - log_escape(mptmp, col_name), - log_escape_ex(mptmp, var->name, var->name_len), - log_escape_ex(mptmp, var->value, var->value_len)); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Set variable \"%s.%s\" to \"%s\".", + log_escape(mptmp, col_name), + log_escape_ex(mptmp, var->name, var->name_len), + log_escape_ex(mptmp, var->value, var->value_len)); + } } } @@ -1031,12 +1272,13 @@ static apr_status_t msre_action_expirevar_execute(modsec_rec *msr, apr_pool_t *m target_col = (apr_table_t *)apr_table_get(msr->collections, col_name); if (target_col == NULL) { - msr_log(msr, 3, "Could not set variable \"%s.%s\" as the collection does not exist.", + msr_log(msr, 3, "Could not expire variable \"%s.%s\" as the collection does not exist.", log_escape(msr->mp, col_name), log_escape(msr->mp, var_name)); return 0; } } else { - /* ENH Log warning detected variable name but no collection. */ + msr_log(msr, 3, "Asked to expire variable \"%s\", but no collection name specified. ", + log_escape(msr->mp, var_name)); return 0; } @@ -1047,7 +1289,7 @@ static apr_status_t msre_action_expirevar_execute(modsec_rec *msr, apr_pool_t *m var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); var->name = apr_psprintf(msr->mp, "__expire_%s", var_name); var->name_len = strlen(var->name); - var->value = apr_psprintf(msr->mp, "%i", (int)(apr_time_sec(msr->request_time) + var->value = apr_psprintf(msr->mp, "%" APR_TIME_T_FMT, (apr_time_t)(apr_time_sec(msr->request_time) + atoi(var_value))); var->value_len = strlen(var->value); apr_table_setn(target_col, var->name, (void *)var); @@ -1069,8 +1311,8 @@ static apr_status_t msre_action_deprecatevar_execute(modsec_rec *msr, apr_pool_t char *s = NULL; apr_table_t *target_col = NULL; msc_string *var = NULL, *var_last_update_time = NULL; - unsigned int last_update_time, current_time; - long int current_value, new_value; + apr_time_t last_update_time, current_time; + long current_value, new_value; /* Extract the name and the value. */ /* IMP1 We have a function for this now, parse_name_eq_value? */ @@ -1100,15 +1342,18 @@ static apr_status_t msre_action_deprecatevar_execute(modsec_rec *msr, apr_pool_t return 0; } } else { - /* ENH Log warning detected variable name but no collection. */ + msr_log(msr, 3, "Asked to deprecate variable \"%s\", but no collection name specified. ", + log_escape(msr->mp, var_name)); return 0; } /* Find the current value. */ var = (msc_string *)apr_table_get(target_col, var_name); if (var == NULL) { - msr_log(msr, 9, "Asked to deprecate variable \"%s.%s\" but it does not exist.", - log_escape(msr->mp, col_name), log_escape(msr->mp, var_name)); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Asked to deprecate variable \"%s.%s\", but it does not exist.", + log_escape(msr->mp, col_name), log_escape(msr->mp, var_name)); + } return 0; } current_value = atoi(var->value); @@ -1122,7 +1367,7 @@ static apr_status_t msre_action_deprecatevar_execute(modsec_rec *msr, apr_pool_t return 0; } - current_time = (unsigned int)apr_time_sec(apr_time_now()); + current_time = apr_time_sec(apr_time_now()); last_update_time = atoi(var_last_update_time->value); s = strstr(var_value, "/"); @@ -1138,26 +1383,28 @@ static apr_status_t msre_action_deprecatevar_execute(modsec_rec *msr, apr_pool_t * time elapsed since the last update. */ new_value = current_value - - ((current_time - last_update_time) * atoi(var_value) / atoi(s)); + (atol(var_value) * ((current_time - last_update_time) / atol(s))); if (new_value < 0) new_value = 0; /* Only change the value if it differs. */ if (new_value != current_value) { - var->value = apr_psprintf(msr->mp, "%i", (int)new_value); + var->value = apr_psprintf(msr->mp, "%ld", new_value); var->value_len = strlen(var->value); - msr_log(msr, 4, "Deprecated variable \"%s.%s\" from %li to %li (%i seconds since " + msr_log(msr, 4, "Deprecated variable \"%s.%s\" from %ld to %ld (%" APR_TIME_T_FMT " seconds since " "last update).", log_escape(msr->mp, col_name), log_escape(msr->mp, var_name), - current_value, new_value, current_time - last_update_time); + current_value, new_value, (apr_time_t)(current_time - last_update_time)); apr_table_set(msr->collections_dirty, col_name, "1"); } else { - msr_log(msr, 9, "Not deprecating variable \"%s.%s\" because the new value (%li) is " - "the same as the old one (%li) (%i seconds since last update).", - log_escape(msr->mp, col_name), log_escape(msr->mp, var_name), current_value, - new_value, current_time - last_update_time); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Not deprecating variable \"%s.%s\" because the new value (%ld) is " + "the same as the old one (%ld) (%" APR_TIME_T_FMT " seconds since last update).", + log_escape(msr->mp, col_name), log_escape(msr->mp, var_name), current_value, + new_value, (apr_time_t)(current_time - last_update_time)); + } } - + return 1; } @@ -1184,7 +1431,7 @@ static apr_status_t init_collection(modsec_rec *msr, const char *real_col_name, msr_log(msr, 4, "Creating collection (name \"%s\", key \"%s\").", real_col_name, col_key); - table = apr_table_make(msr->mp, 24); + table = apr_table_make(msr->mp, 24); /* IMP1 Is the timeout hard-coded to 3600? */ @@ -1192,7 +1439,7 @@ static apr_status_t init_collection(modsec_rec *msr, const char *real_col_name, var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); var->name = "__expire_KEY"; var->name_len = strlen(var->name); - var->value = apr_psprintf(msr->mp, "%i", (int)(apr_time_sec(msr->request_time) + 3600)); + var->value = apr_psprintf(msr->mp, "%" APR_TIME_T_FMT, (apr_time_t)(apr_time_sec(msr->request_time) + 3600)); var->value_len = strlen(var->value); apr_table_setn(table, var->name, (void *)var); @@ -1208,7 +1455,7 @@ static apr_status_t init_collection(modsec_rec *msr, const char *real_col_name, var = apr_pcalloc(msr->mp, sizeof(msc_string)); var->name = "TIMEOUT"; var->name_len = strlen(var->name); - var->value = apr_psprintf(msr->mp, "%i", 3600); + var->value = apr_psprintf(msr->mp, "%d", 3600); var->value_len = strlen(var->value); apr_table_setn(table, var->name, (void *)var); @@ -1240,7 +1487,7 @@ static apr_status_t init_collection(modsec_rec *msr, const char *real_col_name, var = apr_pcalloc(msr->mp, sizeof(msc_string)); var->name = "CREATE_TIME"; var->name_len = strlen(var->name); - var->value = apr_psprintf(msr->mp, "%i", (int)apr_time_sec(msr->request_time)); + var->value = apr_psprintf(msr->mp, "%" APR_TIME_T_FMT, (apr_time_t)apr_time_sec(msr->request_time)); var->value_len = strlen(var->value); apr_table_setn(table, var->name, (void *)var); @@ -1251,6 +1498,14 @@ static apr_status_t init_collection(modsec_rec *msr, const char *real_col_name, var->value = "0"; var->value_len = strlen(var->value); apr_table_setn(table, var->name, (void *)var); + + /* This is a new collection. */ + var = apr_pcalloc(msr->mp, sizeof(msc_string)); + var->name = "IS_NEW"; + var->name_len = strlen(var->name); + var->value = "1"; + var->value_len = strlen(var->value); + apr_table_setn(table, var->name, (void *)var); } /* Add the collection to the list. */ @@ -1274,7 +1529,7 @@ static apr_status_t msre_action_initcol_execute(modsec_rec *msr, apr_pool_t *mpt char *data = apr_pstrdup(msr->mp, action->param); char *col_name = NULL, *col_key = NULL; unsigned int col_key_len; - + msc_string *var = NULL; char *s = NULL; @@ -1347,15 +1602,53 @@ static apr_status_t msre_action_setuid_execute(modsec_rec *msr, apr_pool_t *mptm } /* exec */ +static char *msre_action_exec_validate(msre_engine *engine, msre_action *action) { + #if defined(WITH_LUA) + char *filename = (char *)action->param; + + /* TODO Support relative filenames. */ + + /* Process Lua scripts internally. */ + if (strlen(filename) > 4) { + char *p = filename + strlen(filename) - 4; + if ((p[0] == '.')&&(p[1] == 'l')&&(p[2] == 'u')&&(p[3] == 'a')) { + /* It's a Lua script. */ + msc_script *script = NULL; + + /* Compile script. */ + char *msg = lua_compile(&script, filename, engine->mp); + if (msg != NULL) return msg; + + action->param_data = script; + } + } + #endif + + return NULL; +} + static apr_status_t msre_action_exec_execute(modsec_rec *msr, apr_pool_t *mptmp, msre_rule *rule, msre_action *action) { - char *script_output = NULL; + #if defined(WITH_LUA) + if (action->param_data != NULL) { /* Lua */ + msc_script *script = (msc_script *)action->param_data; + char *my_error_msg = NULL; - int rc = apache2_exec(msr, action->param, NULL, &script_output); - if (rc != 1) { - msr_log(msr, 1, "Failed to execute: %s", action->param); - return 0; + if (lua_execute(script, NULL, msr, rule, &my_error_msg) < 0) { + msr_log(msr, 1, "%s", my_error_msg); + return 0; + } + } else + #endif + { /* Execute as shell script. */ + char *script_output = NULL; + + int rc = apache2_exec(msr, action->param, NULL, &script_output); + if (rc != 1) { + msr_log(msr, 1, "Failed to execute: %s", action->param); + return 0; + } } return 1; @@ -1395,6 +1688,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, NULL, msre_action_id_init, NULL @@ -1407,6 +1701,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, NULL, msre_action_rev_init, NULL @@ -1419,11 +1714,25 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, NULL, msre_action_msg_init, NULL ); + /* logdata */ + msre_engine_action_register(engine, + "logdata", + ACTION_METADATA, + 1, 1, + NO_PLUS_MINUS, + ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, + NULL, + msre_action_logdata_init, + NULL + ); + /* severity */ msre_engine_action_register(engine, "severity", @@ -1431,6 +1740,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, NULL, msre_action_severity_init, NULL @@ -1443,6 +1753,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 0, 0, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, NULL, msre_action_chain_init, NULL @@ -1455,6 +1766,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 0, 0, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_LOG, NULL, msre_action_log_init, NULL @@ -1467,6 +1779,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 0, 0, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_LOG, NULL, msre_action_nolog_init, NULL @@ -1479,6 +1792,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 0, 0, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_AUDITLOG, NULL, msre_action_auditlog_init, NULL @@ -1491,11 +1805,25 @@ void msre_engine_register_default_actions(msre_engine *engine) { 0, 0, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_AUDITLOG, NULL, msre_action_noauditlog_init, NULL ); + /* deny */ + msre_engine_action_register(engine, + "block", + ACTION_DISRUPTIVE, + 0, 0, + NO_PLUS_MINUS, + ACTION_CARDINALITY_ONE, + ACTION_CGROUP_DISRUPTIVE, + NULL, + msre_action_block_init, + NULL + ); + /* deny */ msre_engine_action_register(engine, "deny", @@ -1503,6 +1831,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 0, 0, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_DISRUPTIVE, NULL, msre_action_deny_init, NULL @@ -1515,6 +1844,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, msre_action_status_validate, msre_action_status_init, NULL @@ -1527,10 +1857,11 @@ void msre_engine_register_default_actions(msre_engine *engine) { 0, 0, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_DISRUPTIVE, NULL, msre_action_drop_init, NULL - ); + ); /* pause */ msre_engine_action_register(engine, @@ -1539,11 +1870,12 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, msre_action_pause_validate, msre_action_pause_init, NULL ); - + /* redirect */ msre_engine_action_register(engine, "redirect", @@ -1551,6 +1883,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_DISRUPTIVE, msre_action_redirect_validate, msre_action_redirect_init, msre_action_redirect_execute @@ -1563,6 +1896,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_DISRUPTIVE, msre_action_proxy_validate, msre_action_proxy_init, msre_action_proxy_execute @@ -1575,6 +1909,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 0, 0, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_DISRUPTIVE, NULL, msre_action_pass_init, NULL @@ -1587,19 +1922,34 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_DISRUPTIVE, msre_action_skip_validate, msre_action_skip_init, NULL ); + /* skipAfter */ + msre_engine_action_register(engine, + "skipAfter", + ACTION_DISRUPTIVE, + 1, 1, + NO_PLUS_MINUS, + ACTION_CARDINALITY_ONE, + ACTION_CGROUP_DISRUPTIVE, + msre_action_skipAfter_validate, + msre_action_skipAfter_init, + NULL + ); + /* allow */ msre_engine_action_register(engine, "allow", ACTION_DISRUPTIVE, - 0, 0, + 0, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, - NULL, + ACTION_CGROUP_DISRUPTIVE, + msre_action_allow_validate, msre_action_allow_init, NULL ); @@ -1611,6 +1961,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, msre_action_phase_validate, msre_action_phase_init, NULL @@ -1623,6 +1974,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, ALLOW_PLUS_MINUS, ACTION_CARDINALITY_MANY, + ACTION_CGROUP_NONE, msre_action_t_validate, msre_action_t_init, NULL @@ -1635,6 +1987,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_MANY, + ACTION_CGROUP_NONE, msre_action_ctl_validate, msre_action_ctl_init, msre_action_ctl_execute @@ -1647,6 +2000,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_MANY, + ACTION_CGROUP_NONE, msre_action_xmlns_validate, NULL, NULL @@ -1659,6 +2013,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 0, 0, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, NULL, NULL, NULL @@ -1671,6 +2026,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_MANY, + ACTION_CGROUP_NONE, NULL, NULL, msre_action_sanitiseArg_execute @@ -1683,6 +2039,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 0, 0, NO_PLUS_MINUS, ACTION_CARDINALITY_MANY, + ACTION_CGROUP_NONE, NULL, NULL, msre_action_sanitiseMatched_execute @@ -1695,6 +2052,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_MANY, + ACTION_CGROUP_NONE, NULL, NULL, msre_action_sanitiseRequestHeader_execute @@ -1707,6 +2065,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_MANY, + ACTION_CGROUP_NONE, NULL, NULL, msre_action_sanitiseResponseHeader_execute @@ -1719,6 +2078,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_MANY, + ACTION_CGROUP_NONE, NULL, NULL, msre_action_setenv_execute @@ -1731,6 +2091,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_MANY, + ACTION_CGROUP_NONE, NULL, NULL, msre_action_setvar_execute @@ -1743,6 +2104,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_MANY, + ACTION_CGROUP_NONE, NULL, NULL, msre_action_expirevar_execute @@ -1755,11 +2117,12 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_MANY, + ACTION_CGROUP_NONE, NULL, NULL, msre_action_deprecatevar_execute ); - + /* initcol */ msre_engine_action_register(engine, "initcol", @@ -1767,6 +2130,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_MANY, + ACTION_CGROUP_NONE, NULL, NULL, msre_action_initcol_execute @@ -1779,6 +2143,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, NULL, NULL, msre_action_setsid_execute @@ -1791,6 +2156,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, NULL, NULL, msre_action_setuid_execute @@ -1803,7 +2169,8 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_MANY, - NULL, + ACTION_CGROUP_NONE, + msre_action_exec_validate, NULL, msre_action_exec_execute ); @@ -1815,6 +2182,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 0, 0, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, NULL, NULL, NULL @@ -1827,6 +2195,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_MANY, + ACTION_CGROUP_NONE, NULL, NULL, NULL @@ -1839,6 +2208,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, NULL, NULL, msre_action_prepend_execute @@ -1851,6 +2221,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { 1, 1, NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, NULL, NULL, msre_action_append_execute diff --git a/apache2/re_operators.c b/apache2/re_operators.c index ea723d81..5eacf863 100644 --- a/apache2/re_operators.c +++ b/apache2/re_operators.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -19,7 +19,7 @@ * */ void msre_engine_op_register(msre_engine *engine, const char *name, - FN_OP_PARAM_INIT(fn1), FN_OP_EXECUTE(fn2)) + fn_op_param_init_t fn1, fn_op_execute_t fn2) { msre_op_metadata *metadata = (msre_op_metadata *)apr_pcalloc(engine->mp, sizeof(msre_op_metadata)); @@ -53,6 +53,17 @@ static int msre_op_unconditionalmatch_execute(modsec_rec *msr, msre_rule *rule, return 1; } +/* noMatch */ + +static int msre_op_nomatch_execute(modsec_rec *msr, msre_rule *rule, + msre_var *var, char **error_msg) +{ + *error_msg = "No match."; + + /* Never match. */ + return 0; +} + /* rx */ static int msre_op_rx_param_init(msre_rule *rule, char **error_msg) { @@ -67,7 +78,7 @@ static int msre_op_rx_param_init(msre_rule *rule, char **error_msg) { /* Compile pattern */ regex = msc_pregcomp(rule->ruleset->mp, pattern, PCRE_DOTALL | PCRE_DOLLAR_ENDONLY, &errptr, &erroffset); if (regex == NULL) { - *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (pos %i): %s", + *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s", erroffset, errptr); return 0; } @@ -135,14 +146,14 @@ static int msre_op_rx_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, c for(i = 0; i < rc; i++) { msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); if (s == NULL) return -1; - s->name = apr_psprintf(msr->mp, "%i", i); + s->name = apr_psprintf(msr->mp, "%d", i); s->value = apr_pstrmemdup(msr->mp, target + ovector[2*i], ovector[2*i + 1] - ovector[2*i]); s->value_len = (ovector[2*i + 1] - ovector[2*i]); if ((s->name == NULL)||(s->value == NULL)) return -1; apr_table_setn(msr->tx_vars, s->name, (void *)s); if (msr->txcfg->debuglog_level >= 9) { - msr_log(msr, 9, "Adding regex subexpression to TXVARS (%i): %s", i, + msr_log(msr, 9, "Added regex subexpression to TX.%d: %s", i, log_escape_nq_ex(msr->mp, s->value, s->value_len)); } } @@ -150,26 +161,22 @@ static int msre_op_rx_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, c /* Unset the remaining ones (from previous invocations). */ for(i = rc; i <= 9; i++) { char buf[24]; - apr_snprintf(buf, sizeof(buf), "%i", i); + apr_snprintf(buf, sizeof(buf), "%d", i); apr_table_unset(msr->tx_vars, buf); } } - /* - if ( ((rc == PCRE_ERROR_NOMATCH)&&(rule->op_negated == 1)) - || ((rc != PCRE_ERROR_NOMATCH)&&(rule->op_negated == 0)) ) - { - */ if (rc != PCRE_ERROR_NOMATCH) { /* Match. */ - char *pattern_escaped = log_escape(msr->mp, regex->pattern); + /* We no longer escape the pattern here as it is done when logging */ + char *pattern = apr_pstrdup(msr->mp, regex->pattern); /* This message will be logged. */ - if (strlen(pattern_escaped) > 252) { + if (strlen(pattern) > 252) { *error_msg = apr_psprintf(msr->mp, "Pattern match \"%.252s ...\" at %s.", - pattern_escaped, var->name); + pattern, var->name); } else { *error_msg = apr_psprintf(msr->mp, "Pattern match \"%s\" at %s.", - pattern_escaped, var->name); + pattern, var->name); } return 1; @@ -182,24 +189,27 @@ static int msre_op_rx_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, c /* pm */ static int msre_op_pm_param_init(msre_rule *rule, char **error_msg) { + ACMP *p; + const char *phrase; + const char *next; + if ((rule->op_param == NULL)||(strlen(rule->op_param) == 0)) { *error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for operator 'pm'."); return 0; /* ERROR */ } - - ACMP *p = acmp_create(0, rule->ruleset->mp); + + p = acmp_create(0, rule->ruleset->mp); if (p == NULL) return 0; - const char *phrase = apr_pstrdup(rule->ruleset->mp, rule->op_param); - const char *next = rule->op_param + strlen(rule->op_param); - + phrase = apr_pstrdup(rule->ruleset->mp, rule->op_param); + /* Loop through phrases */ /* ENH: Need to allow quoted phrases w/space */ for (;;) { - while((isspace(*phrase) != 0) && (*phrase != '\0')) phrase++; + while((apr_isspace(*phrase) != 0) && (*phrase != '\0')) phrase++; if (*phrase == '\0') break; next = phrase; - while((isspace(*next) == 0) && (*next != 0)) next++; + while((apr_isspace(*next) == 0) && (*next != 0)) next++; acmp_add_pattern(p, phrase, NULL, NULL, next - phrase); phrase = next; } @@ -219,18 +229,18 @@ static int msre_op_pmFromFile_param_init(msre_rule *rule, char **error_msg) { const char *rulefile_path; apr_status_t rc; apr_file_t *fd; + ACMP *p; if ((rule->op_param == NULL)||(strlen(rule->op_param) == 0)) { - *error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for operator 'pm'."); + *error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for operator 'pmFromFile'."); return 0; /* ERROR */ } - - ACMP *p = acmp_create(0, rule->ruleset->mp); + + p = acmp_create(0, rule->ruleset->mp); if (p == NULL) return 0; fn = apr_pstrdup(rule->ruleset->mp, rule->op_param); - next = fn + strlen(rule->op_param); - + /* Get the path of the rule filename to use as a base */ rulefile_path = apr_pstrndup(rule->ruleset->mp, rule->filename, strlen(rule->filename) - strlen(apr_filepath_name_get(rule->filename))); @@ -246,11 +256,11 @@ static int msre_op_pmFromFile_param_init(msre_rule *rule, char **error_msg) { int line = 0; /* Trim whitespace */ - while((isspace(*fn) != 0) && (*fn != '\0')) fn++; + while((apr_isspace(*fn) != 0) && (*fn != '\0')) fn++; if (*fn == '\0') break; next = fn; - while((isspace(*next) == 0) && (*next != '\0')) next++; - while((isspace(*next) != 0) && (*next != '\0')) *next++ = '\0'; + while((apr_isspace(*next) == 0) && (*next != '\0')) next++; + while((apr_isspace(*next) != 0) && (*next != '\0')) *(next++) = '\0'; /* Add path of the rule filename for a relative phrase filename */ filepath = fn; @@ -277,7 +287,7 @@ static int msre_op_pmFromFile_param_init(msre_rule *rule, char **error_msg) { rc = apr_file_gets(buf, HUGE_STRING_LEN, fd); if (rc == APR_EOF) break; if (rc != APR_SUCCESS) { - *error_msg = apr_psprintf(rule->ruleset->mp, "Could read \"%s\" line %d: %s", fn, line, apr_strerror(rc, errstr, 1024)); + *error_msg = apr_psprintf(rule->ruleset->mp, "Could not read \"%s\" line %d: %s", fn, line, apr_strerror(rc, errstr, 1024)); return 0; } @@ -308,11 +318,17 @@ static int msre_op_pmFromFile_param_init(msre_rule *rule, char **error_msg) { static int msre_op_pm_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { const char *match = NULL; apr_status_t rc = 0; - + int capture; + ACMPT pt; + /* Nothing to read */ if ((var->value == NULL) || (var->value_len == 0)) return 0; - ACMPT pt = {(ACMP *)rule->op_param_data, NULL}; + /* Are we supposed to capture subexpressions? */ + capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0; + + pt.parser = (ACMP *)rule->op_param_data; + pt.ptr = NULL; rc = acmp_process_quick(&pt, &match, var->value, var->value_len); if (rc) { @@ -326,6 +342,33 @@ static int msre_op_pm_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, c *error_msg = apr_psprintf(msr->mp, "Matched phrase \"%s\" at %s.", match_escaped, var->name); } + + /* Handle capture as tx.0=match */ + if (capture) { + int i; + msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + + if (s == NULL) return -1; + + s->name = "0"; + s->value = apr_pstrdup(msr->mp, match); + if (s->value == NULL) return -1; + s->value_len = strlen(s->value); + apr_table_setn(msr->tx_vars, s->name, (void *)s); + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Added phrase match to TX.0: %s", + log_escape_nq_ex(msr->mp, s->value, s->value_len)); + } + + /* Unset the remaining ones (from previous invocations). */ + for(i = rc; i <= 9; i++) { + char buf[2]; + apr_snprintf(buf, sizeof(buf), "%d", i); + apr_table_unset(msr->tx_vars, buf); + } + } + return 1; } return rc; @@ -366,8 +409,16 @@ static int msre_op_within_execute(modsec_rec *msr, msre_rule *rule, msre_var *va target = var->value; target_length = var->value_len; - /* These are impossible to match */ - if ((match_length == 0) || (target_length > match_length)) { + /* The empty string always matches */ + if (target_length == 0) { + /* Match. */ + *error_msg = apr_psprintf(msr->mp, "String match within \"\" at %s.", + var->name); + return 1; + } + + /* This is impossible to match */ + if (target_length > match_length) { /* No match. */ return 0; } @@ -375,16 +426,14 @@ static int msre_op_within_execute(modsec_rec *msr, msre_rule *rule, msre_var *va /* scan for first character, then compare from there until we * have a match or there is no room left in the target */ - msr_log(msr, 9, "match[%d]='%s' target[%d]='%s'", match_length, match, target_length, target); i_max = match_length - target_length; for (i = 0; i <= i_max; i++) { if (match[i] == target[0]) { - if (strncmp(target, (match + i), target_length) == 0) { + if (memcmp((target + 1), (match + i + 1), (target_length - 1)) == 0) { /* match. */ - *error_msg = apr_psprintf(msr->mp, "String match %s=\"%s\" within \"%s\".", - var->name, - log_escape_ex(msr->mp, target, target_length), - log_escape_ex(msr->mp, match, match_length)); + *error_msg = apr_psprintf(msr->mp, "String match within \"%s\" at %s.", + log_escape_ex(msr->mp, match, match_length), + var->name); return 1; } } @@ -432,8 +481,15 @@ static int msre_op_contains_execute(modsec_rec *msr, msre_rule *rule, msre_var * target_length = var->value_len; } - /* These are impossible to match */ - if ((match_length == 0) || (match_length > target_length)) { + /* The empty string always matches */ + if (match_length == 0) { + /* Match. */ + *error_msg = apr_psprintf(msr->mp, "String match \"\" at %s.", var->name); + return 1; + } + + /* This is impossible to match */ + if (match_length > target_length) { /* No match. */ return 0; } @@ -443,8 +499,12 @@ static int msre_op_contains_execute(modsec_rec *msr, msre_rule *rule, msre_var * */ i_max = target_length - match_length; for (i = 0; i <= i_max; i++) { + /* First character matched - avoid func call */ if (target[i] == match[0]) { - if (strncmp(match, (target + i), match_length) == 0) { + /* See if remaining matches */ + if ( (match_length == 1) + || (memcmp((match + 1), (target + i + 1), (match_length - 1)) == 0)) + { /* Match. */ *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.", log_escape_ex(msr->mp, match, match_length), @@ -458,6 +518,100 @@ static int msre_op_contains_execute(modsec_rec *msr, msre_rule *rule, msre_var * return 0; } +/* containsWord */ + +static int msre_op_containsWord_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { + msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + const char *match = NULL; + const char *target; + unsigned int match_length; + unsigned int target_length = 0; + unsigned int i, i_max; + int rc = 0; + + str->value = (char *)rule->op_param; + str->value_len = strlen(str->value); + + if (error_msg == NULL) return -1; + *error_msg = NULL; + + if (str->value == NULL) { + *error_msg = "Internal Error: match string is null."; + return -1; + } + + expand_macros(msr, str, rule, msr->mp); + + match = (const char *)str->value; + match_length = str->value_len; + + /* If the given target is null run against an empty + * string. This is a behaviour consistent with previous + * releases. + */ + if (var->value == NULL) { + target = ""; + target_length = 0; + } else { + target = var->value; + target_length = var->value_len; + } + + /* The empty string always matches */ + if (match_length == 0) { + /* Match. */ + *error_msg = apr_psprintf(msr->mp, "String match \"\" at %s.", var->name); + return 1; + } + + /* This is impossible to match */ + if (match_length > target_length) { + /* No match. */ + return 0; + } + + /* scan for first character, then compare from there until we + * have a match or there is no room left in the target + */ + i_max = target_length - match_length; + for (i = 0; i <= i_max; i++) { + + /* Previous char must have been a start or non-word */ + if ((i > 0) && (apr_isalnum(target[i-1])||(target[i-1] == '_'))) + continue; + + /* First character matched - avoid func call */ + if (target[i] == match[0]) { + /* See if remaining matches */ + if ( (match_length == 1) + || (memcmp((match + 1), (target + i + 1), (match_length - 1)) == 0)) + { + /* check boundaries */ + if (i == i_max) { + /* exact/end word match */ + rc = 1; + } + else if (!(apr_isalnum(target[i + match_length])||(target[i + match_length] == '_'))) { + /* start/mid word match */ + rc = 1; + } + } + } + } + + if (rc == 1) { + /* Maybe a match. */ + *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.", + log_escape_ex(msr->mp, match, match_length), + var->name); + return 1; + } + + /* No match. */ + *error_msg = NULL; + return 0; +} + /* streq */ static int msre_op_streq_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { @@ -501,7 +655,7 @@ static int msre_op_streq_execute(modsec_rec *msr, msre_rule *rule, msre_var *var return 0; } - if (strncmp(match, target, target_length) == 0) { + if (memcmp(match, target, target_length) == 0) { /* Match. */ *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.", log_escape_ex(msr->mp, match, match_length), @@ -550,13 +704,20 @@ static int msre_op_beginsWith_execute(modsec_rec *msr, msre_rule *rule, msre_var target_length = var->value_len; } - /* These are impossible to match */ - if ((match_length == 0) || (match_length > target_length)) { + /* The empty string always matches */ + if (match_length == 0) { + /* Match. */ + *error_msg = apr_psprintf(msr->mp, "String match \"\" at %s.", var->name); + return 1; + } + + /* This is impossible to match */ + if (match_length > target_length) { /* No match. */ return 0; } - if (strncmp(match, target, match_length) == 0) { + if (memcmp(match, target, match_length) == 0) { /* Match. */ *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.", log_escape_ex(msr->mp, match, match_length), @@ -605,13 +766,20 @@ static int msre_op_endsWith_execute(modsec_rec *msr, msre_rule *rule, msre_var * target_length = var->value_len; } - /* These are impossible to match */ - if ((match_length == 0) || (match_length > target_length)) { + /* The empty string always matches */ + if (match_length == 0) { + /* Match. */ + *error_msg = apr_psprintf(msr->mp, "String match \"\" at %s.", var->name); + return 1; + } + + /* This is impossible to match */ + if (match_length > target_length) { /* No match. */ return 0; } - if (strncmp(match, (target + (target_length - match_length)), match_length) == 0) { + if (memcmp(match, (target + (target_length - match_length)), match_length) == 0) { /* Match. */ *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.", log_escape_ex(msr->mp, match, match_length), @@ -683,8 +851,6 @@ static int msre_op_m_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, ch return 1; } -#ifdef WITH_LIBXML2 - /* validateDTD */ static int msre_op_validateDTD_init(msre_rule *rule, char **error_msg) { @@ -792,7 +958,183 @@ static int msre_op_validateSchema_execute(modsec_rec *msr, msre_rule *rule, msre return 0; } -#endif +/* verifyCC */ + +/** + * Luhn Mod-10 Method (ISO 2894/ANSI 4.13) + */ +static int luhn_verify(const char *ccnumber, int len) { + int sum[2] = { 0, 0 }; + int odd = 0; + int digits = 0; + int i; + + /* Weighted lookup table which is just a precalculated (i = index): + * i*2 + (( (i*2) > 9 ) ? -9 : 0) + */ + static int wtable[10] = {0, 2, 4, 6, 8, 1, 3, 5, 7, 9}; /* weight lookup table */ + + /* Add up only digits (weighted digits via lookup table) + * for both odd and even CC numbers to avoid 2 passes. + */ + for (i = 0; i < len; i++) { + if (apr_isdigit(ccnumber[i])) { + sum[0] += (!odd ? wtable[ccnumber[i] - '0'] : (ccnumber[i] - '0')); + sum[1] += (odd ? wtable[ccnumber[i] - '0'] : (ccnumber[i] - '0')); + odd = 1 - odd; /* alternate weights */ + digits++; + } + } + + /* No digits extracted */ + if (digits == 0) return 0; + + /* Do a mod 10 on the sum */ + sum[odd] %= 10; + + /* If the result is a zero the card is valid. */ + return sum[odd] ? 0 : 1; +} + +static int msre_op_verifyCC_init(msre_rule *rule, char **error_msg) { + const char *errptr = NULL; + int erroffset; + msc_regex_t *regex; + + if (error_msg == NULL) return -1; + *error_msg = NULL; + + /* Compile rule->op_param */ + regex = msc_pregcomp(rule->ruleset->mp, rule->op_param, PCRE_DOTALL | PCRE_MULTILINE, &errptr, &erroffset); + if (regex == NULL) { + *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s", + erroffset, errptr); + return 0; + } + + rule->op_param_data = regex; + + return 1; /* OK */ +} + +static int msre_op_verifyCC_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { + msc_regex_t *regex = (msc_regex_t *)rule->op_param_data; + const char *target; + unsigned int target_length; + char *my_error_msg = NULL; + int ovector[33]; + int rc; + int is_cc = 0; + int offset; + + if (error_msg == NULL) return -1; + *error_msg = NULL; + + if (regex == NULL) { + *error_msg = "Internal Error: regex data is null."; + return -1; + } + + memset(ovector, 0, sizeof(ovector)); + + /* If the given target is null run against an empty + * string. This is a behaviour consistent with previous + * releases. + */ + if (var->value == NULL) { + target = ""; + target_length = 0; + } else { + target = var->value; + target_length = var->value_len; + } + + for (offset = 0; ((unsigned int)offset < target_length) && (is_cc == 0); offset++) { + if (msr->txcfg->debuglog_level >= 9) { + if (offset > 0) { + msr_log(msr, 9, "Continuing CC# search at target offset %d.", offset); + } + } + + rc = msc_regexec_ex(regex, target, target_length, offset, PCRE_NOTEMPTY, ovector, 30, &my_error_msg); + + /* If there was no match, then we are done. */ + if (rc == PCRE_ERROR_NOMATCH) { + break; + } + + if (rc < -1) { + *error_msg = apr_psprintf(msr->mp, "CC# regex execution failed: %s", my_error_msg); + return -1; + } + + /* Verify a match. */ + if (rc > 0) { + const char *match = target + ovector[0]; + int length = ovector[1] - ovector[0]; + int i = 0; + + offset = ovector[2*i]; + + /* Check the Luhn using the match string */ + is_cc = luhn_verify(match, length); + + /* Not a CC number, then try another match where we left off. */ + if (!is_cc) { + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "CC# Luhn check failed at target offset %d: \"%.*s\"", offset, length, match); + } + + continue; + } + + /* We have a potential CC number and need to set any captures + * and we are done. + */ + + if (apr_table_get(rule->actionset->actions, "capture")) { + for(; i < rc; i++) { + msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + if (s == NULL) return -1; + s->name = apr_psprintf(msr->mp, "%d", i); + s->value = apr_pstrmemdup(msr->mp, match, length); + s->value_len = length; + if ((s->name == NULL)||(s->value == NULL)) return -1; + + apr_table_setn(msr->tx_vars, s->name, (void *)s); + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Added regex subexpression to TX.%d: %s", i, + log_escape_nq_ex(msr->mp, s->value, s->value_len)); + } + } + } + + /* Unset the remaining TX vars (from previous invocations). */ + for(; i <= 9; i++) { + char buf[24]; + apr_snprintf(buf, sizeof(buf), "%i", i); + apr_table_unset(msr->tx_vars, buf); + } + + break; + } + } + + if (is_cc) { + /* Match. */ + + /* This message will be logged. */ + *error_msg = apr_psprintf(msr->mp, "CC# match \"%s\" at %s. [offset \"%d\"]", + regex->pattern, var->name, offset); + + return 1; + } + + /* No match. */ + return 0; +} + /** * Perform geograpical lookups on an IP/Host. @@ -806,16 +1148,21 @@ static int msre_op_geoLookup_execute(modsec_rec *msr, msre_rule *rule, msre_var msc_string *s = NULL; int rc; + *error_msg = NULL; + if (geo == NULL) { - msr_log(msr, 1, "Geo lookup for \"%s\" attempted without a database. Set SecGeoLookupDb.", geo_host); + msr_log(msr, 1, "Geo lookup for \"%s\" attempted without a database. Set SecGeoLookupDB.", log_escape(msr->mp, geo_host)); return 0; } rc = geo_lookup(msr, &rec, geo_host, error_msg); if (rc <= 0) { + *error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" failed at %s.", log_escape_nq(msr->mp, geo_host), var->name); return rc; } + *error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" succeeded at %s.", + log_escape_nq(msr->mp, geo_host), var->name); if (msr->txcfg->debuglog_level >= 9) { msr_log(msr, 9, "GEO: %s={country_code=%s, country_code3=%s, country_name=%s, country_continent=%s, region=%s, city=%s, postal_code=%s, latitude=%f, longitude=%f, dma_code=%d, area_code=%d}", @@ -835,54 +1182,63 @@ static int msre_op_geoLookup_execute(modsec_rec *msr, msre_rule *rule, msre_var s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "country_code"); + s->name_len = strlen(s->name); s->value = apr_pstrdup(msr->mp, rec.country_code ? rec.country_code : ""); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "country_code3"); + s->name_len = strlen(s->name); s->value = apr_pstrdup(msr->mp, rec.country_code3 ? rec.country_code3 : ""); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "region"); + s->name_len = strlen(s->name); s->value = apr_pstrdup(msr->mp, rec.region ? rec.region : ""); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "city"); + s->name_len = strlen(s->name); s->value = apr_pstrdup(msr->mp, rec.city ? rec.city : ""); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "postal_code"); + s->name_len = strlen(s->name); s->value = apr_pstrdup(msr->mp, rec.postal_code ? rec.postal_code : ""); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "latitude"); + s->name_len = strlen(s->name); s->value = apr_psprintf(msr->mp, "%f", rec.latitude); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "longitude"); + s->name_len = strlen(s->name); s->value = apr_psprintf(msr->mp, "%f", rec.longitude); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "dma_code"); + s->name_len = strlen(s->name); s->value = apr_psprintf(msr->mp, "%d", rec.dma_code); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "area_code"); + s->name_len = strlen(s->name); s->value = apr_psprintf(msr->mp, "%d", rec.area_code); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); @@ -910,7 +1266,7 @@ static int msre_op_rbl_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, /* Construct the host name we want to resolve. */ if (sscanf(target, "%d.%d.%d.%d", &h0, &h1, &h2, &h3) == 4) { /* IPv4 address */ - name_to_check = apr_psprintf(msr->mp, "%i.%i.%i.%i.%s", h3, h2, h1, h0, rule->op_param); + name_to_check = apr_psprintf(msr->mp, "%d.%d.%d.%d.%s", h3, h2, h1, h0, rule->op_param); } else { /* Assume the input is a domain name. */ name_to_check = apr_psprintf(msr->mp, "%s.%s", target, rule->op_param); @@ -921,53 +1277,112 @@ static int msre_op_rbl_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, rc = apr_sockaddr_info_get(&sa, name_to_check, APR_UNSPEC/*msr->r->connection->remote_addr->family*/, 0, 0, msr->mp); if (rc == APR_SUCCESS) { - *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded.", - log_escape_nq(msr->mp, name_to_check)); + *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s.", + log_escape_nq(msr->mp, name_to_check), var->name); return 1; /* Match. */ } - msr_log(msr, 5, "RBL lookup of %s failed.", log_escape_nq(msr->mp, name_to_check)); + msr_log(msr, 5, "RBL lookup of %s failed at %s.", log_escape_nq(msr->mp, name_to_check), var->name); /* No match. */ return 0; } /* inspectFile */ -static int msre_op_inspectFile_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, - char **error_msg) -{ - char *script_output = NULL; - char const *argv[5]; - const char *approver_script = rule->op_param; - const char *target_file = apr_pstrmemdup(msr->mp, var->value, var->value_len); + +static int msre_op_inspectFile_init(msre_rule *rule, char **error_msg) { + char *filename = (char *)rule->op_param; if (error_msg == NULL) return -1; *error_msg = NULL; - msr_log(msr, 4, "Executing %s to inspect %s.", approver_script, target_file); - - argv[0] = approver_script; - argv[1] = target_file; - argv[2] = NULL; - - if (apache2_exec(msr, approver_script, (const char **)argv, &script_output) <= 0) { - *error_msg = apr_psprintf(msr->mp, "Execution of the approver script \"%s\" failed (invocation failed).", - log_escape(msr->mp, approver_script)); + if ((filename == NULL)||(is_empty_string(filename))) { + *error_msg = apr_psprintf(rule->ruleset->mp, "Operator @inspectFile requires parameter."); return -1; } - if (script_output == NULL) { - *error_msg = apr_psprintf(msr->mp, "Execution of the approver script \"%s\" failed (no output).", - log_escape(msr->mp, approver_script)); - return -1; + filename = resolve_relative_path(rule->ruleset->mp, rule->filename, filename); + + #if defined(WITH_LUA) + /* ENH Write & use string_ends(s, e). */ + if (strlen(rule->op_param) > 4) { + char *p = filename + strlen(filename) - 4; + if ((p[0] == '.')&&(p[1] == 'l')&&(p[2] == 'u')&&(p[3] == 'a')) + { + msc_script *script = NULL; + + /* Compile script. */ + *error_msg = lua_compile(&script, filename, rule->ruleset->mp); + if (*error_msg != NULL) return -1; + + rule->op_param_data = script; + } + } + #endif + + if (rule->op_param_data == NULL) { + /* ENH Verify the script exists and that we have + * the rights to execute it. + */ } - if (script_output[0] != '1') { - *error_msg = apr_psprintf(msr->mp, "File \"%s\" rejected by the approver script \"%s\": %s", - log_escape(msr->mp, target_file), log_escape(msr->mp, approver_script), - log_escape_nq(msr->mp, script_output)); - return 1; /* Match. */ + return 1; +} + +static int msre_op_inspectFile_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, + char **error_msg) +{ + if (error_msg == NULL) return -1; + *error_msg = NULL; + + if (rule->op_param_data == NULL) { + /* Execute externally, as native binary/shell script. */ + char *script_output = NULL; + char const *argv[5]; + const char *approver_script = rule->op_param; + const char *target_file = apr_pstrmemdup(msr->mp, var->value, var->value_len); + + msr_log(msr, 4, "Executing %s to inspect %s.", approver_script, target_file); + + argv[0] = approver_script; + argv[1] = target_file; + argv[2] = NULL; + + if (apache2_exec(msr, approver_script, (const char **)argv, &script_output) <= 0) { + *error_msg = apr_psprintf(msr->mp, "Execution of the approver script \"%s\" failed (invocation failed).", + log_escape(msr->mp, approver_script)); + return -1; + } + + if (script_output == NULL) { + *error_msg = apr_psprintf(msr->mp, "Execution of the approver script \"%s\" failed (no output).", + log_escape(msr->mp, approver_script)); + return -1; + } + + if (script_output[0] != '1') { + *error_msg = apr_psprintf(msr->mp, "File \"%s\" rejected by the approver script \"%s\": %s", + log_escape(msr->mp, target_file), log_escape(msr->mp, approver_script), + log_escape_nq(msr->mp, script_output)); + return 1; /* Match. */ + } } + #if defined(WITH_LUA) + else { + /* Execute internally, as Lua script. */ + char *target = apr_pstrmemdup(msr->mp, var->value, var->value_len); + msc_script *script = (msc_script *)rule->op_param_data; + int rc; + + rc = lua_execute(script, target, msr, rule, error_msg); + if (rc < 0) { + /* Error. */ + return -1; + } + + return rc; + } + #endif /* No match. */ return 0; @@ -1001,7 +1416,7 @@ static int msre_op_validateByteRange_init(msre_rule *rule, char **error_msg) { /* Single value. */ int x = atoi(p); if ((x < 0)||(x > 255)) { - *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range value: %i", x); + *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range value: %d", x); return 0; } table[x>>3] = (table[x>>3] | (1 << (x & 0x7))); @@ -1011,16 +1426,16 @@ static int msre_op_validateByteRange_init(msre_rule *rule, char **error_msg) { int end = atoi(s + 1); if ((start < 0)||(start > 255)) { - *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range start value: %i", + *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range start value: %d", start); return 0; } if ((end < 0)||(end > 255)) { - *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range end value: %i", end); + *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range end value: %d", end); return 0; } if (start > end) { - *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range: %i-%i", start, end); + *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range: %d-%d", start, end); return 0; } @@ -1058,7 +1473,7 @@ static int msre_op_validateByteRange_execute(modsec_rec *msr, msre_rule *rule, m int x = ((unsigned char *)var->value)[i]; if (!(table[x >> 3] & (1 << (x & 0x7)))) { if (msr->txcfg->debuglog_level >= 9) { - msr_log(msr, 9, "Value %i outside range: %s", x, rule->op_param); + msr_log(msr, 9, "Value %d in %s outside range: %s", x, var->name, rule->op_param); } count++; } @@ -1066,8 +1481,8 @@ static int msre_op_validateByteRange_execute(modsec_rec *msr, msre_rule *rule, m if (count == 0) return 0; /* Valid - no match. */ - *error_msg = apr_psprintf(msr->mp, "Found %i byte(s) outside range: %s.", - count, rule->op_param); + *error_msg = apr_psprintf(msr->mp, "Found %d byte(s) in %s outside range: %s.", + count, var->name, rule->op_param); return 1; /* Invalid - match.*/ } @@ -1116,21 +1531,22 @@ static int msre_op_validateUrlEncoding_execute(modsec_rec *msr, msre_rule *rule, int rc = validate_url_encoding(var->value, var->value_len); switch(rc) { case 1 : - return 0; /* Encoding is valid, no match. */ + /* Encoding is valid */ + *error_msg = apr_psprintf(msr->mp, "Valid URL Encoding at %s.", var->name); break; case -2 : *error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Non-hexadecimal " - "digits used."); - return 1; /* Invalid, match. */ + "digits used at %s.", var->name); + return 1; /* Invalid match. */ break; case -3 : *error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Not enough characters " - "at the end of input."); - return 1; /* Invalid, match. */ + "at the end of input at %s.", var->name); + return 1; /* Invalid match. */ break; case -1 : default : - *error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Internal Error (rc = %i)", rc); + *error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Internal Error (rc = %d) at %s", rc, var->name); return -1; break; @@ -1145,89 +1561,91 @@ static int msre_op_validateUrlEncoding_execute(modsec_rec *msr, msre_rule *rule, #define UNICODE_ERROR_CHARACTERS_MISSING -1 #define UNICODE_ERROR_INVALID_ENCODING -2 #define UNICODE_ERROR_OVERLONG_CHARACTER -3 +#define UNICODE_ERROR_RESTRICTED_CHARACTER -4 +#define UNICODE_ERROR_DECODING_ERROR -5 -static int detect_utf8_character(const char *p_read, unsigned int length) { +/* NOTE: This is over-commented for ease of verification */ +static int detect_utf8_character(const unsigned char *p_read, unsigned int length) { int unicode_len = 0; unsigned int d = 0; unsigned char c; - if (p_read == NULL) return 0; + if (p_read == NULL) return UNICODE_ERROR_DECODING_ERROR; c = *p_read; - if (c == 0) return 0; - if ((c & 0xE0) == 0xC0) { - /* two byte unicode */ + /* If first byte begins with binary 0 it is single byte encoding */ + if ((c & 0x80) == 0) { + /* single byte unicode (7 bit ASCII equivilent) has no validation */ + return 1; + } + /* If first byte begins with binary 110 it is two byte encoding*/ + else if ((c & 0xE0) == 0xC0) { + /* check we have at least two bytes */ if (length < 2) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING; - else - if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; + /* check second byte starts with binary 10 */ + else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else { unicode_len = 2; + /* compute character number */ d = ((c & 0x1F) << 6) | (*(p_read + 1) & 0x3F); } } + /* If first byte begins with binary 1110 it is three byte encoding */ else if ((c & 0xF0) == 0xE0) { - /* three byte unicode */ + /* check we have at least three bytes */ if (length < 3) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING; - else - if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; - else - if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; + /* check second byte starts with binary 10 */ + else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; + /* check third byte starts with binary 10 */ + else if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else { unicode_len = 3; + /* compute character number */ d = ((c & 0x0F) << 12) | ((*(p_read + 1) & 0x3F) << 6) | (*(p_read + 2) & 0x3F); } } + /* If first byte begins with binary 11110 it is four byte encoding */ else if ((c & 0xF8) == 0xF0) { - /* four byte unicode */ + /* restrict characters to UTF-8 range (U+0000 - U+10FFFF)*/ + if (c >= 0xF5) { + return UNICODE_ERROR_RESTRICTED_CHARACTER; + } + /* check we have at least four bytes */ if (length < 4) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING; - else - if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; - else - if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; - else - if (((*(p_read + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; + /* check second byte starts with binary 10 */ + else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; + /* check third byte starts with binary 10 */ + else if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; + /* check forth byte starts with binary 10 */ + else if (((*(p_read + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else { - d = ((c & 0x07) << 18) | ((*(p_read + 1) & 0x3F) << 12) | ((*(p_read + 2) & 0x3F) < 6) | (*(p_read + 3) & 0x3F); unicode_len = 4; + /* compute character number */ + d = ((c & 0x07) << 18) | ((*(p_read + 1) & 0x3F) << 12) | ((*(p_read + 2) & 0x3F) < 6) | (*(p_read + 3) & 0x3F); } } - else if ((c & 0xFC) == 0xF8) { - /* five byte unicode */ - if (length < 5) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING; - else - if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; - else - if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; - else - if (((*(p_read + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; - else - if (((*(p_read + 4)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; - else { - d = ((c & 0x03) << 24) | ((*(p_read + 1) & 0x3F) << 18) | ((*(p_read + 2) & 0x3F) << 12) | ((*(p_read + 3) & 0x3F) << 6) | (*(p_read + 4) & 0x3F); - unicode_len = 5; - } - } - else if ((c & 0xFE) == 0xFC) { - /* six byte unicode */ - if (length < 6) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING; - else - if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; - else - if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; - else - if (((*(p_read + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; - else - if (((*(p_read + 4)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; - else - if (((*(p_read + 5)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; - else { - d = ((c & 0x01) << 30) | ((*(p_read + 1) & 0x3F) << 24) | ((*(p_read + 2) & 0x3F) << 18) | ((*(p_read + 3) & 0x3F) << 12) | ((*(p_read + 4) & 0x3F) << 6) | (*(p_read + 5) & 0x3F); - unicode_len = 6; - } + /* any other first byte is invalid (RFC 3629) */ + else { + return UNICODE_ERROR_INVALID_ENCODING; } - if ((unicode_len > 1)&&((d & 0x7F) == d)) { - unicode_len = UNICODE_ERROR_OVERLONG_CHARACTER; + /* invalid UTF-8 character number range (RFC 3629) */ + if ((d >= 0xD800) && (d <= 0xDFFF)) { + return UNICODE_ERROR_RESTRICTED_CHARACTER; + } + + /* check for overlong */ + if ((unicode_len == 4) && (d < 0x010000)) { + /* four byte could be represented with less bytes */ + return UNICODE_ERROR_OVERLONG_CHARACTER; + } + else if ((unicode_len == 3) && (d < 0x0800)) { + /* three byte could be represented with less bytes */ + return UNICODE_ERROR_OVERLONG_CHARACTER; + } + else if ((unicode_len == 2) && (d < 0x80)) { + /* two byte could be represented with less bytes */ + return UNICODE_ERROR_OVERLONG_CHARACTER; } return unicode_len; @@ -1239,27 +1657,50 @@ static int msre_op_validateUtf8Encoding_execute(modsec_rec *msr, msre_rule *rule unsigned int i, bytes_left; bytes_left = var->value_len; - for(i = 0; i < var->value_len; i++) { - int rc = detect_utf8_character(&var->value[i], bytes_left); + + for(i = 0; i < var->value_len;) { + int rc = detect_utf8_character((unsigned char *)&var->value[i], bytes_left); + switch(rc) { case UNICODE_ERROR_CHARACTERS_MISSING : - *error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: not enough bytes in " - "character."); + *error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: " + "not enough bytes in character " + "at %s. [offset \"%d\"]", var->name, i); return 1; break; case UNICODE_ERROR_INVALID_ENCODING : - *error_msg = apr_psprintf(msr->mp, "Invalid Unicode encoding: invalid byte value " - "in character."); + *error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: " + "invalid byte value in character " + "at %s. [offset \"%d\"]", var->name, i); return 1; break; case UNICODE_ERROR_OVERLONG_CHARACTER : - *error_msg = apr_psprintf(msr->mp, "Invalid Unicode encoding: overlong " - "character detected."); + *error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: " + "overlong character detected " + "at %s. [offset \"%d\"]", var->name, i); + return 1; + break; + case UNICODE_ERROR_RESTRICTED_CHARACTER : + *error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: " + "use of restricted character " + "at %s. [offset \"%d\"]", var->name, i); + return 1; + break; + case UNICODE_ERROR_DECODING_ERROR : + *error_msg = apr_psprintf(msr->mp, "Error validating UTF-8 decoding " + "at %s. [offset \"%d\"]", var->name, i); return 1; break; } - bytes_left--; + if (rc <= 0) { + *error_msg = apr_psprintf(msr->mp, "Internal error during UTF-8 validation " + "at %s.", var->name); + return 1; + } + + i += rc; + bytes_left -= rc; } return 0; @@ -1288,7 +1729,7 @@ static int msre_op_eq_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, return 0; } else { - *error_msg = apr_psprintf(msr->mp, "Operator EQ match: %i.", right); + *error_msg = apr_psprintf(msr->mp, "Operator EQ matched %d at %s.", right, var->name); /* Match. */ return 1; } @@ -1317,7 +1758,7 @@ static int msre_op_gt_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, return 0; } else { - *error_msg = apr_psprintf(msr->mp, "Operator GT match: %i.", right); + *error_msg = apr_psprintf(msr->mp, "Operator GT matched %d at %s.", right, var->name); /* Match. */ return 1; } @@ -1335,7 +1776,7 @@ static int msre_op_lt_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, /* NULL values do not match anything. */ return 0; } - + target = apr_pstrmemdup(msr->mp, var->value, var->value_len); if (target == NULL) return -1; left = atoi(target); @@ -1346,7 +1787,7 @@ static int msre_op_lt_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, return 0; } else { - *error_msg = apr_psprintf(msr->mp, "Operator LT match: %i.", right); + *error_msg = apr_psprintf(msr->mp, "Operator LT matched %d at %s.", right, var->name); /* Match. */ return 1; } @@ -1375,7 +1816,7 @@ static int msre_op_ge_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, return 0; } else { - *error_msg = apr_psprintf(msr->mp, "Operator GE match: %i.", right); + *error_msg = apr_psprintf(msr->mp, "Operator GE matched %d at %s.", right, var->name); /* Match. */ return 1; } @@ -1404,7 +1845,7 @@ static int msre_op_le_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, return 0; } else { - *error_msg = apr_psprintf(msr->mp, "Operator LE match: %i.", right); + *error_msg = apr_psprintf(msr->mp, "Operator LE matched %d at %s.", right, var->name); /* Match. */ return 1; } @@ -1423,6 +1864,13 @@ void msre_engine_register_default_operators(msre_engine *engine) { msre_op_unconditionalmatch_execute ); + /* noMatch */ + msre_engine_op_register(engine, + "noMatch", + NULL, + msre_op_nomatch_execute + ); + /* rx */ msre_engine_op_register(engine, "rx", @@ -1458,6 +1906,13 @@ void msre_engine_register_default_operators(msre_engine *engine) { msre_op_contains_execute ); + /* containsWord */ + msre_engine_op_register(engine, + "containsWord", + NULL, /* ENH init function to flag var substitution */ + msre_op_containsWord_execute + ); + /* is */ msre_engine_op_register(engine, "streq", @@ -1486,8 +1941,6 @@ void msre_engine_register_default_operators(msre_engine *engine) { msre_op_m_execute ); - #ifdef WITH_LIBXML2 - /* validateDTD */ msre_engine_op_register(engine, "validateDTD", @@ -1502,7 +1955,12 @@ void msre_engine_register_default_operators(msre_engine *engine) { msre_op_validateSchema_execute ); - #endif + /* verifyCC */ + msre_engine_op_register(engine, + "verifyCC", + msre_op_verifyCC_init, + msre_op_verifyCC_execute + ); /* geoLookup */ msre_engine_op_register(engine, @@ -1521,7 +1979,7 @@ void msre_engine_register_default_operators(msre_engine *engine) { /* inspectFile */ msre_engine_op_register(engine, "inspectFile", - NULL, + msre_op_inspectFile_init, msre_op_inspectFile_execute ); diff --git a/apache2/re_tfns.c b/apache2/re_tfns.c index 97a4664b..c4a0a0af 100644 --- a/apache2/re_tfns.c +++ b/apache2/re_tfns.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -27,7 +27,7 @@ static int msre_fn_lowercase_execute(apr_pool_t *mptmp, unsigned char *input, if (rval == NULL) return -1; *rval = NULL; - + i = 0; while(i < input_len) { int x = input[i]; @@ -158,13 +158,16 @@ static int msre_fn_compressWhitespace_execute(apr_pool_t *mptmp, unsigned char * { long int i, j, count; int changed = 0; + int inwhitespace = 0; i = j = count = 0; while(i < input_len) { if (isspace(input[i])||(input[i] == NBSP)) { - changed = 1; + if (inwhitespace) changed = 1; + inwhitespace = 1; count++; } else { + inwhitespace = 0; if (count) { input[j] = ' '; count = 0; @@ -245,12 +248,30 @@ static int msre_fn_replaceComments_execute(apr_pool_t *mptmp, unsigned char *inp } } + if (incomment) { + input[j++] = ' '; + } + *rval = (char *)input; *rval_len = j; return changed; } +/* jsDecode */ + +static int msre_fn_jsDecode_execute(apr_pool_t *mptmp, unsigned char *input, + long int input_len, char **rval, long int *rval_len) +{ + long int length; + + length = js_decode_nonstrict_inplace(input, input_len); + *rval = (char *)input; + *rval_len = length; + + return (*rval_len == input_len ? 0 : 1); +} + /* urlDecode */ static int msre_fn_urlDecode_execute(apr_pool_t *mptmp, unsigned char *input, @@ -258,12 +279,13 @@ static int msre_fn_urlDecode_execute(apr_pool_t *mptmp, unsigned char *input, { long int length; int invalid_count; + int changed; - length = urldecode_nonstrict_inplace_ex(input, input_len, &invalid_count); + length = urldecode_nonstrict_inplace_ex(input, input_len, &invalid_count, &changed); *rval = (char *)input; *rval_len = length; - - return (*rval_len == input_len ? 0 : 1); + + return changed; } /* urlDecodeUni */ @@ -272,12 +294,13 @@ static int msre_fn_urlDecodeUni_execute(apr_pool_t *mptmp, unsigned char *input, long int input_len, char **rval, long int *rval_len) { long int length; + int changed; - length = urldecode_uni_nonstrict_inplace_ex(input, input_len); + length = urldecode_uni_nonstrict_inplace_ex(input, input_len, &changed); *rval = (char *)input; *rval_len = length; - - return (*rval_len == input_len ? 0 : 1); + + return changed; } /* urlEncode */ @@ -285,10 +308,12 @@ static int msre_fn_urlDecodeUni_execute(apr_pool_t *mptmp, unsigned char *input, static int msre_fn_urlEncode_execute(apr_pool_t *mptmp, unsigned char *input, long int input_len, char **rval, long int *rval_len) { - *rval = url_encode(mptmp, (char *)input, input_len); + int changed; + + *rval = url_encode(mptmp, (char *)input, input_len, &changed); *rval_len = strlen(*rval); - - return (*rval_len == input_len ? 0 : 1); + + return changed; } /* base64Encode */ @@ -301,7 +326,7 @@ static int msre_fn_base64Encode_execute(apr_pool_t *mptmp, unsigned char *input, apr_base64_encode(*rval, (const char *)input, input_len); (*rval_len)--; - return 1; + return *rval_len ? 1 : 0; } /* base64Decode */ @@ -311,10 +336,9 @@ static int msre_fn_base64Decode_execute(apr_pool_t *mptmp, unsigned char *input, { *rval_len = apr_base64_decode_len((const char *)input); /* returns len with NULL byte included */ *rval = apr_palloc(mptmp, *rval_len); - apr_base64_decode(*rval, (const char *)input); - (*rval_len)--; + *rval_len = apr_base64_decode(*rval, (const char *)input); - return 1; + return *rval_len ? 1 : 0; } /* length */ @@ -358,7 +382,7 @@ static int msre_fn_sha1_execute(apr_pool_t *mptmp, unsigned char *input, *rval_len = APR_SHA1_DIGESTSIZE; *rval = apr_pstrmemdup(mptmp, (const char *)digest, APR_SHA1_DIGESTSIZE); - return 1; + return 1; } /* hexDecode */ @@ -410,10 +434,12 @@ static int msre_fn_escapeSeqDecode_execute(apr_pool_t *mptmp, unsigned char *inp static int msre_fn_normalisePath_execute(apr_pool_t *mptmp, unsigned char *input, long int input_len, char **rval, long int *rval_len) { - *rval_len = normalise_path_inplace(input, input_len, 0); + int changed; + + *rval_len = normalise_path_inplace(input, input_len, 0, &changed); *rval = (char *)input; - return (*rval_len == input_len ? 0 : 1); + return changed; } /* normalisePathWin */ @@ -421,10 +447,12 @@ static int msre_fn_normalisePath_execute(apr_pool_t *mptmp, unsigned char *input static int msre_fn_normalisePathWin_execute(apr_pool_t *mptmp, unsigned char *input, long int input_len, char **rval, long int *rval_len) { - *rval_len = normalise_path_inplace(input, input_len, 1); + int changed; + + *rval_len = normalise_path_inplace(input, input_len, 1, &changed); *rval = (char *)input; - return (*rval_len == input_len ? 0 : 1); + return changed; } /* ------------------------------------------------------------------------------ */ @@ -433,7 +461,7 @@ static int msre_fn_normalisePathWin_execute(apr_pool_t *mptmp, unsigned char *in * Registers one transformation function with the engine. */ void msre_engine_tfn_register(msre_engine *engine, const char *name, - FN_TFN_EXECUTE(execute)) + fn_tfn_execute_t execute) { msre_tfn_metadata *metadata = (msre_tfn_metadata *)apr_pcalloc(engine->mp, sizeof(msre_tfn_metadata)); @@ -491,7 +519,7 @@ void msre_engine_register_default_tfns(msre_engine *engine) { msre_engine_tfn_register(engine, "hexDecode", msre_fn_hexDecode_execute - ); + ); /* hexEncode */ msre_engine_tfn_register(engine, @@ -505,6 +533,12 @@ void msre_engine_register_default_tfns(msre_engine *engine) { msre_fn_htmlEntityDecode_execute ); + /* jsDecode */ + msre_engine_tfn_register(engine, + "jsDecode", + msre_fn_jsDecode_execute + ); + /* length */ msre_engine_tfn_register(engine, "length", diff --git a/apache2/re_variables.c b/apache2/re_variables.c index 6dcd2c87..91bf97e6 100644 --- a/apache2/re_variables.c +++ b/apache2/re_variables.c @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -15,9 +15,7 @@ #include "re.h" #include "msc_util.h" -#ifdef WITH_LIBXML2 #include "libxml/xpathInternals.h" -#endif /** * Generates a variable from a string and a length. @@ -69,7 +67,7 @@ static char *var_generic_list_validate(msre_ruleset *ruleset, msre_var *var) { regex = msc_pregcomp(ruleset->mp, pattern, PCRE_DOTALL | PCRE_CASELESS | PCRE_DOLLAR_ENDONLY, &errptr, &erroffset); if (regex == NULL) { - return apr_psprintf(ruleset->mp, "Error compiling pattern (pos %i): %s", + return apr_psprintf(ruleset->mp, "Error compiling pattern (offset %d): %s", erroffset, errptr); } @@ -118,13 +116,13 @@ static int var_args_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, rvar->value = arg->value; rvar->value_len = arg->value_len; - rvar->name = apr_psprintf(mptmp, "ARGS:%s", log_escape_nq(mptmp, arg->name)); + rvar->name = apr_psprintf(mptmp, "ARGS:%s", log_escape_nq_ex(mptmp, arg->name, arg->name_len)); apr_table_addn(vartab, rvar->name, (void *)rvar); count++; } } - + return count; } @@ -146,9 +144,9 @@ static int var_args_combined_size_generate(modsec_rec *msr, msre_var *var, msre_ combined_size += arg->name_len; combined_size += arg->value_len; } - + rvar = apr_pmemdup(mptmp, var, sizeof(msre_var)); - rvar->value = apr_psprintf(mptmp, "%u", combined_size); + rvar->value = apr_psprintf(mptmp, "%u", combined_size); rvar->value_len = strlen(rvar->value); apr_table_addn(vartab, rvar->name, (void *)rvar); @@ -188,13 +186,201 @@ static int var_args_names_generate(modsec_rec *msr, msre_var *var, msre_rule *ru rvar->value = arg->name; rvar->value_len = arg->name_len; - rvar->name = apr_psprintf(mptmp, "ARGS_NAMES:%s", log_escape_nq(mptmp, arg->name)); + rvar->name = apr_psprintf(mptmp, "ARGS_NAMES:%s", log_escape_nq_ex(mptmp, arg->name, arg->name_len)); apr_table_addn(vartab, rvar->name, (void *)rvar); count++; } } - + + return count; +} + +/* ARGS_GET */ + +static int var_args_get_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + const apr_array_header_t *arr = NULL; + const apr_table_entry_t *te = NULL; + int i, count = 0; + + /* Loop through the arguments. */ + arr = apr_table_elts(msr->arguments); + te = (apr_table_entry_t *)arr->elts; + for (i = 0; i < arr->nelts; i++) { + msc_arg *arg = (msc_arg *)te[i].val; + int match = 0; + + /* Only QUERY_STRING arguments */ + if (strcmp("QUERY_STRING", arg->origin) != 0) continue; + + /* Figure out if we want to include this argument. */ + if (var->param == NULL) match = 1; /* Unconditional inclusion. */ + else { + if (var->param_data != NULL) { /* Regex. */ + char *my_error_msg = NULL; + /* Run the regex against the argument name. */ + if (!(msc_regexec((msc_regex_t *)var->param_data, arg->name, + arg->name_len, &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1; + } else { /* Simple comparison. */ + if (strcasecmp(arg->name, var->param) == 0) match = 1; + } + } + + /* If we had a match add this argument to the collection. */ + if (match) { + msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var)); + + rvar->value = arg->value; + rvar->value_len = arg->value_len; + rvar->name = apr_psprintf(mptmp, "ARGS_GET:%s", log_escape_nq_ex(mptmp, arg->name, arg->name_len)); + apr_table_addn(vartab, rvar->name, (void *)rvar); + + count++; + } + } + + return count; +} + +/* ARGS_GET_NAMES */ + +static int var_args_get_names_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + const apr_array_header_t *arr = NULL; + const apr_table_entry_t *te = NULL; + int i, count = 0; + + arr = apr_table_elts(msr->arguments); + te = (apr_table_entry_t *)arr->elts; + for (i = 0; i < arr->nelts; i++) { + msc_arg *arg = (msc_arg *)te[i].val; + int match = 0; + + /* Only QUERY_STRING arguments */ + if (strcmp("QUERY_STRING", arg->origin) != 0) continue; + + /* Figure out if we want to include this variable. */ + if (var->param == NULL) match = 1; /* Unconditional inclusion. */ + else { + if (var->param_data != NULL) { /* Regex. */ + char *my_error_msg = NULL; + if (!(msc_regexec((msc_regex_t *)var->param_data, arg->name, + arg->name_len, &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1; + } else { /* Simple comparison. */ + if (strcasecmp(arg->name, var->param) == 0) match = 1; + } + } + + /* If we had a match add this argument to the collection. */ + if (match) { + msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var)); + + rvar->value = arg->name; + rvar->value_len = arg->name_len; + rvar->name = apr_psprintf(mptmp, "ARGS_GET_NAMES:%s", log_escape_nq_ex(mptmp, arg->name, arg->name_len)); + apr_table_addn(vartab, rvar->name, (void *)rvar); + + count++; + } + } + + return count; +} + +/* ARGS_POST */ + +static int var_args_post_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + const apr_array_header_t *arr = NULL; + const apr_table_entry_t *te = NULL; + int i, count = 0; + + /* Loop through the arguments. */ + arr = apr_table_elts(msr->arguments); + te = (apr_table_entry_t *)arr->elts; + for (i = 0; i < arr->nelts; i++) { + msc_arg *arg = (msc_arg *)te[i].val; + int match = 0; + + /* Only BODY arguments */ + if (strcmp("BODY", arg->origin) != 0) continue; + + /* Figure out if we want to include this argument. */ + if (var->param == NULL) match = 1; /* Unconditional inclusion. */ + else { + if (var->param_data != NULL) { /* Regex. */ + char *my_error_msg = NULL; + /* Run the regex against the argument name. */ + if (!(msc_regexec((msc_regex_t *)var->param_data, arg->name, + arg->name_len, &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1; + } else { /* Simple comparison. */ + if (strcasecmp(arg->name, var->param) == 0) match = 1; + } + } + + /* If we had a match add this argument to the collection. */ + if (match) { + msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var)); + + rvar->value = arg->value; + rvar->value_len = arg->value_len; + rvar->name = apr_psprintf(mptmp, "ARGS_POST:%s", log_escape_nq_ex(mptmp, arg->name, arg->name_len)); + apr_table_addn(vartab, rvar->name, (void *)rvar); + + count++; + } + } + + return count; +} + +/* ARGS_POST_NAMES */ + +static int var_args_post_names_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + const apr_array_header_t *arr = NULL; + const apr_table_entry_t *te = NULL; + int i, count = 0; + + arr = apr_table_elts(msr->arguments); + te = (apr_table_entry_t *)arr->elts; + for (i = 0; i < arr->nelts; i++) { + msc_arg *arg = (msc_arg *)te[i].val; + int match = 0; + + /* Only BODY arguments */ + if (strcmp("BODY", arg->origin) != 0) continue; + + /* Figure out if we want to include this variable. */ + if (var->param == NULL) match = 1; /* Unconditional inclusion. */ + else { + if (var->param_data != NULL) { /* Regex. */ + char *my_error_msg = NULL; + if (!(msc_regexec((msc_regex_t *)var->param_data, arg->name, + arg->name_len, &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1; + } else { /* Simple comparison. */ + if (strcasecmp(arg->name, var->param) == 0) match = 1; + } + } + + /* If we had a match add this argument to the collection. */ + if (match) { + msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var)); + + rvar->value = arg->name; + rvar->value_len = arg->name_len; + rvar->name = apr_psprintf(mptmp, "ARGS_POST_NAMES:%s", log_escape_nq_ex(mptmp, arg->name, arg->name_len)); + apr_table_addn(vartab, rvar->name, (void *)rvar); + + count++; + } + } + return count; } @@ -216,11 +402,14 @@ static int var_rule_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, return var_simple_generate(var, vartab, mptmp, actionset->rev); } else if ((strcasecmp(var->param, "severity") == 0)&&(actionset->severity != -1)) { - char *value = apr_psprintf(mptmp, "%i", actionset->severity); + char *value = apr_psprintf(mptmp, "%d", actionset->severity); return var_simple_generate(var, vartab, mptmp, value); } else if ((strcasecmp(var->param, "msg") == 0)&&(actionset->msg != NULL)) { return var_simple_generate(var, vartab, mptmp, actionset->msg); + } else + if ((strcasecmp(var->param, "logdata") == 0)&&(actionset->logdata != NULL)) { + return var_simple_generate(var, vartab, mptmp, actionset->logdata); } return 0; @@ -229,6 +418,9 @@ static int var_rule_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, /* ENV */ static char *var_env_validate(msre_ruleset *ruleset, msre_var *var) { + if (var->param == NULL) { + return apr_psprintf(ruleset->mp, "Parameter required for ENV."); + } if ((strlen(var->param) > 2)&&(var->param[0] == '/') &&(var->param[strlen(var->param) - 1] == '/')) { @@ -295,7 +487,7 @@ static int var_reqbody_processor_error_generate(modsec_rec *msr, msre_var *var, { msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var)); - rvar->value = apr_psprintf(mptmp, "%i", msr->msc_reqbody_error); + rvar->value = apr_psprintf(mptmp, "%d", msr->msc_reqbody_error); rvar->value_len = strlen(rvar->value); apr_table_addn(vartab, rvar->name, (void *)rvar); @@ -322,8 +514,6 @@ static int var_reqbody_processor_error_msg_generate(modsec_rec *msr, msre_var *v return 1; } -#ifdef WITH_LIBXML2 - /* XML */ static char *var_xml_validate(msre_ruleset *ruleset, msre_var *var) { @@ -405,7 +595,7 @@ static int var_xml_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx); if (xpathObj == NULL) { msr_log(msr, 1, "XML: Unable to evaluate xpath expression."); - xmlXPathFreeContext(xpathCtx); + xmlXPathFreeContext(xpathCtx); return -1; } @@ -435,11 +625,10 @@ static int var_xml_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, } xmlXPathFreeObject(xpathObj); - xmlXPathFreeContext(xpathCtx); + xmlXPathFreeContext(xpathCtx); return count; } -#endif /* WEBSERVER_ERROR_LOG */ @@ -490,7 +679,7 @@ static int var_remote_host_generate(modsec_rec *msr, msre_var *var, msre_rule *r static int var_remote_port_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, apr_table_t *vartab, apr_pool_t *mptmp) { - char *value = apr_psprintf(mptmp, "%i", msr->remote_port); + char *value = apr_psprintf(mptmp, "%u", msr->remote_port); return var_simple_generate(var, vartab, mptmp, value); } @@ -536,7 +725,7 @@ static int var_tx_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, rvar->value = str->value; rvar->value_len = str->value_len; - rvar->name = apr_psprintf(mptmp, "TX:%s", log_escape_nq(mptmp, str->name)); + rvar->name = apr_psprintf(mptmp, "TX:%s", log_escape_nq_ex(mptmp, str->name, str->name_len)); apr_table_addn(vartab, rvar->name, (void *)rvar); count++; @@ -580,7 +769,7 @@ static int var_geo_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, rvar->value = str->value; rvar->value_len = str->value_len; - rvar->name = apr_psprintf(mptmp, "GEO:%s", log_escape_nq(mptmp, str->name)); + rvar->name = apr_psprintf(mptmp, "GEO:%s", log_escape_nq_ex(mptmp, str->name, str->name_len)); apr_table_addn(vartab, rvar->name, (void *)rvar); count++; @@ -590,6 +779,15 @@ static int var_geo_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, return count; } +/* HIGHEST_SEVERITY */ + +static int var_highest_severity_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + return var_simple_generate(var, vartab, mptmp, + apr_psprintf(mptmp, "%d", msr->highest_severity)); +} + /* IP */ static int var_ip_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, @@ -608,7 +806,7 @@ static int var_ip_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, for (i = 0; i < arr->nelts; i++) { msc_string *str = (msc_string *)te[i].val; int match; - + /* Figure out if we want to include this variable. */ match = 0; if (var->param == NULL) match = 1; /* Unconditional inclusion. */ @@ -628,7 +826,7 @@ static int var_ip_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, rvar->value = str->value; rvar->value_len = str->value_len; - rvar->name = apr_psprintf(mptmp, "IP:%s", log_escape_nq(mptmp, str->name)); + rvar->name = apr_psprintf(mptmp, "IP:%s", log_escape_nq_ex(mptmp, str->name, str->name_len)); apr_table_addn(vartab, rvar->name, (void *)rvar); count++; @@ -638,6 +836,30 @@ static int var_ip_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, return count; } +/* MATCHED_VAR */ + +static int var_matched_var_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + return var_simple_generate_ex(var, vartab, mptmp, + apr_pmemdup(mptmp, + msr->matched_var->value, + msr->matched_var->value_len), + msr->matched_var->value_len); +} + +/* MATCHED_VAR_NAME */ + +static int var_matched_var_name_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + return var_simple_generate_ex(var, vartab, mptmp, + apr_pmemdup(mptmp, + msr->matched_var->name, + msr->matched_var->name_len), + msr->matched_var->name_len); +} + /* SESSION */ static int var_session_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, @@ -676,7 +898,7 @@ static int var_session_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, rvar->value = str->value; rvar->value_len = str->value_len; - rvar->name = apr_psprintf(mptmp, "SESSION:%s", log_escape_nq(mptmp, str->name)); + rvar->name = apr_psprintf(mptmp, "SESSION:%s", log_escape_nq_ex(mptmp, str->name, str->name_len)); apr_table_addn(vartab, rvar->name, (void *)rvar); count++; @@ -724,7 +946,7 @@ static int var_user_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, rvar->value = str->value; rvar->value_len = str->value_len; - rvar->name = apr_psprintf(mptmp, "USER:%s", log_escape_nq(mptmp, str->name)); + rvar->name = apr_psprintf(mptmp, "USER:%s", log_escape_nq_ex(mptmp, str->name, str->name_len)); apr_table_addn(vartab, rvar->name, (void *)rvar); count++; @@ -772,7 +994,7 @@ static int var_global_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, rvar->value = str->value; rvar->value_len = str->value_len; - rvar->name = apr_psprintf(mptmp, "GLOBAL:%s", log_escape_nq(mptmp, str->name)); + rvar->name = apr_psprintf(mptmp, "GLOBAL:%s", log_escape_nq_ex(mptmp, str->name, str->name_len)); apr_table_addn(vartab, rvar->name, (void *)rvar); count++; @@ -820,7 +1042,7 @@ static int var_resource_generate(modsec_rec *msr, msre_var *var, msre_rule *rule rvar->value = str->value; rvar->value_len = str->value_len; - rvar->name = apr_psprintf(mptmp, "RESOURCE:%s", log_escape_nq(mptmp, str->name)); + rvar->name = apr_psprintf(mptmp, "RESOURCE:%s", log_escape_nq_ex(mptmp, str->name, str->name_len)); apr_table_addn(vartab, rvar->name, (void *)rvar); count++; @@ -858,7 +1080,7 @@ static int var_files_tmpnames_generate(modsec_rec *msr, msre_var *var, msre_rule } /* If we had a match add this argument to the collection. */ - if (match) { + if (match) { msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var)); rvar->value = parts[i]->tmp_file_name; @@ -903,7 +1125,7 @@ static int var_files_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, } /* If we had a match add this argument to the collection. */ - if (match) { + if (match) { msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var)); rvar->value = parts[i]->filename; @@ -948,7 +1170,7 @@ static int var_files_sizes_generate(modsec_rec *msr, msre_var *var, msre_rule *r } /* If we had a match add this argument to the collection. */ - if (match) { + if (match) { msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var)); rvar->value = apr_psprintf(mptmp, "%u", parts[i]->tmp_file_size); @@ -983,7 +1205,7 @@ static int var_files_names_generate(modsec_rec *msr, msre_var *var, msre_rule *r rvar->value = parts[i]->name; rvar->value_len = strlen(rvar->value); rvar->name = apr_psprintf(mptmp, "FILES_NAMES:%s", - log_escape_nq(mptmp, parts[i]->name)); + log_escape_nq_ex(mptmp, parts[i]->name, rvar->value_len)); apr_table_addn(vartab, rvar->name, (void *)rvar); count++; @@ -1020,6 +1242,157 @@ static int var_files_combined_size_generate(modsec_rec *msr, msre_var *var, msre return 1; } +/* MODSEC_BUILD */ + +static int var_modsec_build_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + return var_simple_generate(var, vartab, mptmp, modsec_build(mptmp)); +} + +/* MULTIPART_BOUNDARY_QUOTED */ + +static int var_multipart_boundary_quoted_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + if ((msr->mpd != NULL)&&(msr->mpd->flag_boundary_quoted != 0)) { + return var_simple_generate(var, vartab, mptmp, "1"); + } else { + return var_simple_generate(var, vartab, mptmp, "0"); + } +} + +/* MULTIPART_BOUNDARY_WHITESPACE */ + +static int var_multipart_boundary_whitespace_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + if ((msr->mpd != NULL)&&(msr->mpd->flag_boundary_whitespace != 0)) { + return var_simple_generate(var, vartab, mptmp, "1"); + } else { + return var_simple_generate(var, vartab, mptmp, "0"); + } +} + +/* MULTIPART_DATA_AFTER */ + +static int var_multipart_data_after_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + if ((msr->mpd != NULL)&&(msr->mpd->flag_data_after != 0)) { + return var_simple_generate(var, vartab, mptmp, "1"); + } else { + return var_simple_generate(var, vartab, mptmp, "0"); + } +} + +/* MULTIPART_DATA_BEFORE */ + +static int var_multipart_data_before_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + if ((msr->mpd != NULL)&&(msr->mpd->flag_data_before != 0)) { + return var_simple_generate(var, vartab, mptmp, "1"); + } else { + return var_simple_generate(var, vartab, mptmp, "0"); + } +} + +/* MULTIPART_HEADER_FOLDING */ + +static int var_multipart_header_folding_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + if ((msr->mpd != NULL)&&(msr->mpd->flag_header_folding != 0)) { + return var_simple_generate(var, vartab, mptmp, "1"); + } else { + return var_simple_generate(var, vartab, mptmp, "0"); + } +} + +/* MULTIPART_CRLF_LINE */ + +static int var_multipart_crlf_line_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + if ((msr->mpd != NULL)&&(msr->mpd->flag_crlf_line != 0)) { + return var_simple_generate(var, vartab, mptmp, "1"); + } else { + return var_simple_generate(var, vartab, mptmp, "0"); + } +} + +/* MULTIPART_CRLF_LF_LINES */ + +static int var_multipart_crlf_lf_lines_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + if ((msr->mpd != NULL)&&(msr->mpd->flag_lf_line != 0)&&(msr->mpd->flag_crlf_line != 0)) { + return var_simple_generate(var, vartab, mptmp, "1"); + } else { + return var_simple_generate(var, vartab, mptmp, "0"); + } +} + +/* MULTIPART_LF_LINE */ + +static int var_multipart_lf_line_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + if ((msr->mpd != NULL)&&(msr->mpd->flag_lf_line != 0)) { + return var_simple_generate(var, vartab, mptmp, "1"); + } else { + return var_simple_generate(var, vartab, mptmp, "0"); + } +} + +/* MULTIPART_MISSING_SEMICOLON */ + +static int var_multipart_missing_semicolon_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + if ((msr->mpd != NULL)&&(msr->mpd->flag_missing_semicolon != 0)) { + return var_simple_generate(var, vartab, mptmp, "1"); + } else { + return var_simple_generate(var, vartab, mptmp, "0"); + } +} + +/* MULTIPART_STRICT_ERROR */ + +static int var_multipart_strict_error_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + if (msr->mpd != NULL) { + /* Respond positive if at least one of the multipart flags is raised. */ + if ( (msr->mpd->flag_error) + ||(msr->mpd->flag_boundary_quoted != 0) + ||(msr->mpd->flag_boundary_whitespace != 0) + ||(msr->mpd->flag_data_before != 0) + ||(msr->mpd->flag_data_after != 0) + ||(msr->mpd->flag_header_folding != 0) + ||(msr->mpd->flag_lf_line != 0) + ||(msr->mpd->flag_missing_semicolon != 0) + ) { + return var_simple_generate(var, vartab, mptmp, "1"); + } + } + + return var_simple_generate(var, vartab, mptmp, "0"); +} + +/* MULTIPART_UNMATCHED_BOUNDARY */ + +static int var_multipart_unmatched_boundary_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + if ((msr->mpd != NULL)&&(msr->mpd->flag_unmatched_boundary != 0)) { + return var_simple_generate(var, vartab, mptmp, "1"); + } else { + return var_simple_generate(var, vartab, mptmp, "0"); + } +} + /* TIME */ static int var_time_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, @@ -1188,7 +1561,7 @@ static int var_time_epoch_generate(modsec_rec *msr, msre_var *var, msre_rule *ru tc = time(NULL); tm = localtime(&tc); rvar = apr_pmemdup(mptmp, var, sizeof(msre_var)); - rvar->value = apr_psprintf(mptmp, "%i", (int)tc); + rvar->value = apr_psprintf(mptmp, "%ld", (long)tc); rvar->value_len = strlen(rvar->value); apr_table_addn(vartab, rvar->name, (void *)rvar); @@ -1263,7 +1636,7 @@ static int var_request_cookies_generate(modsec_rec *msr, msre_var *var, msre_rul count++; } } - + return count; } @@ -1306,7 +1679,7 @@ static int var_request_cookies_names_generate(modsec_rec *msr, msre_var *var, ms count++; } } - + return count; } @@ -1349,7 +1722,7 @@ static int var_request_headers_generate(modsec_rec *msr, msre_var *var, msre_rul count++; } } - + return count; } @@ -1392,7 +1765,7 @@ static int var_request_headers_names_generate(modsec_rec *msr, msre_var *var, ms count++; } } - + return count; } @@ -1449,7 +1822,7 @@ static int var_server_name_generate(modsec_rec *msr, msre_var *var, msre_rule *r static int var_server_port_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, apr_table_t *vartab, apr_pool_t *mptmp) { - char *value = apr_psprintf(mptmp, "%i", msr->local_port); + char *value = apr_psprintf(mptmp, "%u", msr->local_port); return var_simple_generate(var, vartab, mptmp, value); } @@ -1476,7 +1849,7 @@ static int var_script_filename_generate(modsec_rec *msr, msre_var *var, msre_rul static int var_script_gid_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, apr_table_t *vartab, apr_pool_t *mptmp) { - char *value = apr_psprintf(mptmp, "%i", msr->r->finfo.group); + char *value = apr_psprintf(mptmp, "%ld", (long)msr->r->finfo.group); return var_simple_generate(var, vartab, mptmp, value); } @@ -1506,7 +1879,7 @@ static int var_script_mode_generate(modsec_rec *msr, msre_var *var, msre_rule *r static int var_script_uid_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, apr_table_t *vartab, apr_pool_t *mptmp) { - char *value = apr_psprintf(mptmp, "%i", msr->r->finfo.user); + char *value = apr_psprintf(mptmp, "%ld", (long)msr->r->finfo.user); return var_simple_generate(var, vartab, mptmp, value); } @@ -1594,7 +1967,7 @@ static int var_response_headers_generate(modsec_rec *msr, msre_var *var, msre_ru count++; } } - + return count; } @@ -1637,7 +2010,7 @@ static int var_response_headers_names_generate(modsec_rec *msr, msre_var *var, m count++; } } - + return count; } @@ -1664,7 +2037,7 @@ static int var_response_protocol_generate(modsec_rec *msr, msre_var *var, msre_r static int var_response_status_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, apr_table_t *vartab, apr_pool_t *mptmp) { - const char *value = apr_psprintf(mptmp, "%i", msr->response_status); + const char *value = apr_psprintf(mptmp, "%u", msr->response_status); return var_simple_generate(var, vartab, mptmp, value); } @@ -1717,9 +2090,9 @@ static int var_webappid_generate(modsec_rec *msr, msre_var *var, msre_rule *rule /** * */ -void msre_engine_variable_register(msre_engine *engine, const char *name, +void msre_engine_variable_register(msre_engine *engine, const char *name, unsigned int type, unsigned int argc_min, unsigned int argc_max, - FN_VAR_VALIDATE(validate), FN_VAR_GENERATE(generate), + fn_var_validate_t validate, fn_var_generate_t generate, unsigned int is_cacheable, unsigned int availability) { msre_var_metadata *metadata = (msre_var_metadata *)apr_pcalloc(engine->mp, @@ -1765,6 +2138,28 @@ void msre_engine_register_default_variables(msre_engine *engine) { PHASE_REQUEST_HEADERS ); + /* ARGS_GET */ + msre_engine_variable_register(engine, + "ARGS_GET", + VAR_LIST, + 0, 1, + var_generic_list_validate, + var_args_get_generate, + VAR_CACHE, + PHASE_REQUEST_HEADERS + ); + + /* ARGS_GET_NAMES */ + msre_engine_variable_register(engine, + "ARGS_GET_NAMES", + VAR_LIST, + 0, 1, + var_generic_list_validate, + var_args_get_names_generate, + VAR_CACHE, + PHASE_REQUEST_HEADERS + ); + /* ARGS_NAMES */ msre_engine_variable_register(engine, "ARGS_NAMES", @@ -1776,6 +2171,39 @@ void msre_engine_register_default_variables(msre_engine *engine) { PHASE_REQUEST_HEADERS ); + /* ARGS_POST */ + msre_engine_variable_register(engine, + "ARGS_POST", + VAR_LIST, + 0, 1, + var_generic_list_validate, + var_args_post_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + /* ARGS_POST_NAMES */ + msre_engine_variable_register(engine, + "ARGS_POST_NAMES", + VAR_LIST, + 0, 1, + var_generic_list_validate, + var_args_post_names_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + /* AUTH_TYPE */ + msre_engine_variable_register(engine, + "AUTH_TYPE", + VAR_SIMPLE, + 0, 0, + NULL, + var_auth_type_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + /* ENV */ msre_engine_variable_register(engine, "ENV", @@ -1783,188 +2211,10 @@ void msre_engine_register_default_variables(msre_engine *engine) { 0, 1, var_env_validate, var_env_generate, - VAR_CACHE, - PHASE_REQUEST_HEADERS - ); - - /* REQBODY_PROCESSOR */ - msre_engine_variable_register(engine, - "REQBODY_PROCESSOR", - VAR_SIMPLE, - 0, 0, - NULL, - var_reqbody_processor_generate, VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - /* REQBODY_PROCESSOR_ERROR */ - msre_engine_variable_register(engine, - "REQBODY_PROCESSOR_ERROR", - VAR_SIMPLE, - 0, 0, - NULL, - var_reqbody_processor_error_generate, - VAR_DONT_CACHE, - PHASE_REQUEST_BODY - ); - - /* REQBODY_PROCESSOR_ERROR_MSG */ - msre_engine_variable_register(engine, - "REQBODY_PROCESSOR_ERROR_MSG", - VAR_SIMPLE, - 0, 0, - NULL, - var_reqbody_processor_error_msg_generate, - VAR_DONT_CACHE, - PHASE_REQUEST_BODY - ); - - #ifdef WITH_LIBXML2 - /* XML */ - msre_engine_variable_register(engine, - "XML", - VAR_LIST, - 0, 1, - var_xml_validate, - var_xml_generate, - VAR_CACHE, - PHASE_REQUEST_BODY - ); - #endif - - /* WEBSERVER_ERROR_LOG */ - msre_engine_variable_register(engine, - "WEBSERVER_ERROR_LOG", - VAR_LIST, - 0, 0, - NULL, - var_webserver_error_log_generate, - VAR_DONT_CACHE, - PHASE_REQUEST_HEADERS - ); - - /* REMOTE_ADDR */ - msre_engine_variable_register(engine, - "REMOTE_ADDR", - VAR_SIMPLE, - 0, 0, - NULL, - var_remote_addr_generate, - VAR_CACHE, - PHASE_REQUEST_HEADERS - ); - - /* REMOTE_HOST */ - msre_engine_variable_register(engine, - "REMOTE_HOST", - VAR_SIMPLE, - 0, 0, - NULL, - var_remote_host_generate, - VAR_CACHE, - PHASE_REQUEST_BODY - ); - - /* REMOTE_PORT */ - msre_engine_variable_register(engine, - "REMOTE_PORT", - VAR_SIMPLE, - 0, 0, - NULL, - var_remote_port_generate, - VAR_CACHE, - PHASE_REQUEST_BODY - ); - - /* REMOTE_USER */ - msre_engine_variable_register(engine, - "REMOTE_USER", - VAR_SIMPLE, - 0, 0, - NULL, - var_remote_user_generate, - VAR_CACHE, - PHASE_REQUEST_BODY - ); - - /* TX */ - msre_engine_variable_register(engine, - "TX", - VAR_LIST, - 1, 1, - var_generic_list_validate, - var_tx_generate, - VAR_CACHE, - PHASE_REQUEST_HEADERS - ); - - /* GEO */ - msre_engine_variable_register(engine, - "GEO", - VAR_LIST, - 1, 1, - var_generic_list_validate, - var_geo_generate, - VAR_CACHE, - PHASE_REQUEST_HEADERS - ); - - /* IP */ - msre_engine_variable_register(engine, - "IP", - VAR_LIST, - 1, 1, - var_generic_list_validate, - var_ip_generate, - VAR_CACHE, - PHASE_REQUEST_HEADERS - ); - - /* SESSION */ - msre_engine_variable_register(engine, - "SESSION", - VAR_LIST, - 1, 1, - var_generic_list_validate, - var_session_generate, - VAR_CACHE, - PHASE_REQUEST_HEADERS - ); - - /* USER */ - msre_engine_variable_register(engine, - "USER", - VAR_LIST, - 1, 1, - var_generic_list_validate, - var_user_generate, - VAR_CACHE, - PHASE_REQUEST_HEADERS - ); - - /* GLOBAL */ - msre_engine_variable_register(engine, - "GLOBAL", - VAR_LIST, - 1, 1, - var_generic_list_validate, - var_global_generate, - VAR_CACHE, - PHASE_REQUEST_HEADERS - ); - - /* RESOURCE */ - msre_engine_variable_register(engine, - "RESOURCE", - VAR_LIST, - 1, 1, - var_generic_list_validate, - var_resource_generate, - VAR_CACHE, - PHASE_REQUEST_HEADERS - ); - /* FILES */ msre_engine_variable_register(engine, "FILES", @@ -2020,103 +2270,213 @@ void msre_engine_register_default_variables(msre_engine *engine) { PHASE_REQUEST_BODY ); - /* TIME */ + /* GEO */ msre_engine_variable_register(engine, - "TIME", - VAR_SIMPLE, - 0, 0, - NULL, - var_time_generate, + "GEO", + VAR_LIST, + 1, 1, + var_generic_list_validate, + var_geo_generate, VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - /* TIME_DAY */ + /* GLOBAL */ msre_engine_variable_register(engine, - "TIME_DAY", - VAR_SIMPLE, - 0, 0, - NULL, - var_time_day_generate, + "GLOBAL", + VAR_LIST, + 1, 1, + var_generic_list_validate, + var_global_generate, VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - /* TIME_EPOCH */ + /* HIGHEST_SEVERITY */ msre_engine_variable_register(engine, - "TIME_EPOCH", + "HIGHEST_SEVERITY", VAR_SIMPLE, 0, 0, NULL, - var_time_epoch_generate, + var_highest_severity_generate, VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - /* TIME_HOUR */ + /* IP */ msre_engine_variable_register(engine, - "TIME_HOUR", - VAR_SIMPLE, - 0, 0, - NULL, - var_time_hour_generate, + "IP", + VAR_LIST, + 1, 1, + var_generic_list_validate, + var_ip_generate, VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - /* TIME_MIN */ + /* MATCHED_VAR */ msre_engine_variable_register(engine, - "TIME_MIN", + "MATCHED_VAR", VAR_SIMPLE, 0, 0, NULL, - var_time_min_generate, + var_matched_var_generate, VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - /* TIME_MON */ + /* MATCHED_VAR_NAME */ msre_engine_variable_register(engine, - "TIME_MON", + "MATCHED_VAR_NAME", VAR_SIMPLE, 0, 0, NULL, - var_time_mon_generate, + var_matched_var_name_generate, VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - /* TIME_SEC */ + /* MODSEC_BUILD */ msre_engine_variable_register(engine, - "TIME_SEC", + "MODSEC_BUILD", VAR_SIMPLE, 0, 0, NULL, - var_time_sec_generate, - VAR_DONT_CACHE, + var_modsec_build_generate, + VAR_CACHE, PHASE_REQUEST_HEADERS ); - /* TIME_WDAY */ + /* MULTIPART_BOUNDARY_QUOTED */ msre_engine_variable_register(engine, - "TIME_WDAY", + "MULTIPART_BOUNDARY_QUOTED", VAR_SIMPLE, 0, 0, NULL, - var_time_wday_generate, - VAR_DONT_CACHE, - PHASE_REQUEST_HEADERS + var_multipart_boundary_quoted_generate, + VAR_CACHE, + PHASE_REQUEST_BODY ); - /* TIME_YEAR */ + /* MULTIPART_BOUNDARY_WHITESPACE */ msre_engine_variable_register(engine, - "TIME_YEAR", + "MULTIPART_BOUNDARY_WHITESPACE", VAR_SIMPLE, 0, 0, NULL, - var_time_year_generate, - VAR_DONT_CACHE, - PHASE_REQUEST_HEADERS + var_multipart_boundary_whitespace_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + /* MULTIPART_DATA_AFTER */ + msre_engine_variable_register(engine, + "MULTIPART_DATA_AFTER", + VAR_SIMPLE, + 0, 0, + NULL, + var_multipart_data_after_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + /* MULTIPART_DATA_BEFORE */ + msre_engine_variable_register(engine, + "MULTIPART_DATA_BEFORE", + VAR_SIMPLE, + 0, 0, + NULL, + var_multipart_data_before_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + /* MULTIPART_HEADER_FOLDING */ + msre_engine_variable_register(engine, + "MULTIPART_HEADER_FOLDING", + VAR_SIMPLE, + 0, 0, + NULL, + var_multipart_header_folding_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + /* MULTIPART_CRLF_LINE */ + msre_engine_variable_register(engine, + "MULTIPART_CRLF_LINE", + VAR_SIMPLE, + 0, 0, + NULL, + var_multipart_crlf_line_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + /* MULTIPART_CRLF_LF_LINES */ + msre_engine_variable_register(engine, + "MULTIPART_CRLF_LF_LINES", + VAR_SIMPLE, + 0, 0, + NULL, + var_multipart_crlf_lf_lines_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + /* MULTIPART_LF_LINE */ + msre_engine_variable_register(engine, + "MULTIPART_LF_LINE", + VAR_SIMPLE, + 0, 0, + NULL, + var_multipart_lf_line_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + /* MULTIPART_MISSING_SEMICOLON */ + msre_engine_variable_register(engine, + "MULTIPART_MISSING_SEMICOLON", + VAR_SIMPLE, + 0, 0, + NULL, + var_multipart_missing_semicolon_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + /* MULTIPART_STRICT_ERROR */ + msre_engine_variable_register(engine, + "MULTIPART_STRICT_ERROR", + VAR_SIMPLE, + 0, 0, + NULL, + var_multipart_strict_error_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + /* MULTIPART_UNMATCHED_BOUNDARY */ + msre_engine_variable_register(engine, + "MULTIPART_UNMATCHED_BOUNDARY", + VAR_SIMPLE, + 0, 0, + NULL, + var_multipart_unmatched_boundary_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + /* PATH_INFO */ + msre_engine_variable_register(engine, + "PATH_INFO", + VAR_SIMPLE, + 0, 0, + NULL, + var_path_info_generate, + VAR_CACHE, + PHASE_REQUEST_BODY ); /* QUERY_STRING */ @@ -2130,6 +2490,94 @@ void msre_engine_register_default_variables(msre_engine *engine) { PHASE_REQUEST_HEADERS ); + /* REMOTE_ADDR */ + msre_engine_variable_register(engine, + "REMOTE_ADDR", + VAR_SIMPLE, + 0, 0, + NULL, + var_remote_addr_generate, + VAR_CACHE, + PHASE_REQUEST_HEADERS + ); + + /* REMOTE_HOST */ + msre_engine_variable_register(engine, + "REMOTE_HOST", + VAR_SIMPLE, + 0, 0, + NULL, + var_remote_host_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + /* REMOTE_PORT */ + msre_engine_variable_register(engine, + "REMOTE_PORT", + VAR_SIMPLE, + 0, 0, + NULL, + var_remote_port_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + /* REMOTE_USER */ + msre_engine_variable_register(engine, + "REMOTE_USER", + VAR_SIMPLE, + 0, 0, + NULL, + var_remote_user_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + /* RESOURCE */ + msre_engine_variable_register(engine, + "RESOURCE", + VAR_LIST, + 1, 1, + var_generic_list_validate, + var_resource_generate, + VAR_DONT_CACHE, + PHASE_REQUEST_HEADERS + ); + + /* REQBODY_PROCESSOR */ + msre_engine_variable_register(engine, + "REQBODY_PROCESSOR", + VAR_SIMPLE, + 0, 0, + NULL, + var_reqbody_processor_generate, + VAR_DONT_CACHE, + PHASE_REQUEST_HEADERS + ); + + /* REQBODY_PROCESSOR_ERROR */ + msre_engine_variable_register(engine, + "REQBODY_PROCESSOR_ERROR", + VAR_SIMPLE, + 0, 0, + NULL, + var_reqbody_processor_error_generate, + VAR_DONT_CACHE, + PHASE_REQUEST_BODY + ); + + /* REQBODY_PROCESSOR_ERROR_MSG */ + msre_engine_variable_register(engine, + "REQBODY_PROCESSOR_ERROR_MSG", + VAR_SIMPLE, + 0, 0, + NULL, + var_reqbody_processor_error_msg_generate, + VAR_DONT_CACHE, + PHASE_REQUEST_BODY + ); + /* REQUEST_BASENAME */ msre_engine_variable_register(engine, "REQUEST_BASENAME", @@ -2241,7 +2689,7 @@ void msre_engine_register_default_variables(msre_engine *engine) { ); /* REQUEST_URI */ - msre_engine_variable_register(engine, + msre_engine_variable_register(engine, "REQUEST_URI", VAR_SIMPLE, 0, 0, @@ -2252,7 +2700,7 @@ void msre_engine_register_default_variables(msre_engine *engine) { ); /* REQUEST_URI_RAW */ - msre_engine_variable_register(engine, + msre_engine_variable_register(engine, "REQUEST_URI_RAW", VAR_SIMPLE, 0, 0, @@ -2262,37 +2710,92 @@ void msre_engine_register_default_variables(msre_engine *engine) { PHASE_REQUEST_HEADERS ); - /* SERVER_ADDR */ + /* RESPONSE_BODY */ msre_engine_variable_register(engine, - "SERVER_ADDR", + "RESPONSE_BODY", VAR_SIMPLE, 0, 0, NULL, - var_server_addr_generate, + var_response_body_generate, VAR_CACHE, - PHASE_REQUEST_HEADERS + PHASE_RESPONSE_BODY ); - /* SERVER_NAME */ + /* RESPONSE_CONTENT_LENGTH */ msre_engine_variable_register(engine, - "SERVER_NAME", + "RESPONSE_CONTENT_LENGTH", VAR_SIMPLE, 0, 0, NULL, - var_server_name_generate, + var_response_content_length, VAR_CACHE, - PHASE_REQUEST_HEADERS + PHASE_RESPONSE_HEADERS ); - /* SERVER_PORT */ + /* RESPONSE_CONTENT_TYPE */ msre_engine_variable_register(engine, - "SERVER_PORT", + "RESPONSE_CONTENT_TYPE", VAR_SIMPLE, 0, 0, NULL, - var_server_port_generate, + var_response_content_type, VAR_CACHE, - PHASE_REQUEST_HEADERS + PHASE_RESPONSE_HEADERS + ); + + /* RESPONSE_HEADERS */ + msre_engine_variable_register(engine, + "RESPONSE_HEADERS", + VAR_LIST, + 0, 1, + var_generic_list_validate, + var_response_headers_generate, + VAR_CACHE, + PHASE_RESPONSE_HEADERS + ); + + /* RESPONSE_HEADERS_NAMES */ + msre_engine_variable_register(engine, + "RESPONSE_HEADERS_NAMES", + VAR_LIST, + 0, 1, + var_generic_list_validate, + var_response_headers_names_generate, + VAR_CACHE, + PHASE_RESPONSE_HEADERS + ); + + /* RESPONSE_PROTOCOL */ + msre_engine_variable_register(engine, + "RESPONSE_PROTOCOL", + VAR_SIMPLE, + 0, 0, + NULL, + var_response_protocol_generate, + VAR_CACHE, + PHASE_RESPONSE_HEADERS + ); + + /* RESPONSE_STATUS */ + msre_engine_variable_register(engine, + "RESPONSE_STATUS", + VAR_SIMPLE, + 0, 0, + NULL, + var_response_status_generate, + VAR_CACHE, + PHASE_RESPONSE_HEADERS + ); + + /* RULE */ + msre_engine_variable_register(engine, + "RULE", + VAR_LIST, + 1, 1, + NULL, + var_rule_generate, + VAR_DONT_CACHE, + PHASE_RESPONSE_HEADERS ); /* SCRIPT_GID */ @@ -2372,58 +2875,58 @@ void msre_engine_register_default_variables(msre_engine *engine) { PHASE_REQUEST_BODY ); - /* PATH_INFO */ + /* SERVER_ADDR */ msre_engine_variable_register(engine, - "PATH_INFO", + "SERVER_ADDR", VAR_SIMPLE, 0, 0, NULL, - var_path_info_generate, + var_server_addr_generate, VAR_CACHE, - PHASE_REQUEST_BODY + PHASE_REQUEST_HEADERS ); - /* AUTH_TYPE */ + /* SERVER_NAME */ msre_engine_variable_register(engine, - "AUTH_TYPE", + "SERVER_NAME", VAR_SIMPLE, 0, 0, NULL, - var_auth_type_generate, + var_server_name_generate, VAR_CACHE, - PHASE_REQUEST_BODY + PHASE_REQUEST_HEADERS ); - /* RESPONSE_BODY */ + /* SERVER_PORT */ msre_engine_variable_register(engine, - "RESPONSE_BODY", + "SERVER_PORT", VAR_SIMPLE, 0, 0, NULL, - var_response_body_generate, + var_server_port_generate, VAR_CACHE, - PHASE_RESPONSE_BODY + PHASE_REQUEST_HEADERS ); - /* RESPONSE_HEADERS */ + /* SESSION */ msre_engine_variable_register(engine, - "RESPONSE_HEADERS", + "SESSION", VAR_LIST, - 0, 1, + 1, 1, var_generic_list_validate, - var_response_headers_generate, - VAR_CACHE, - PHASE_RESPONSE_HEADERS + var_session_generate, + VAR_DONT_CACHE, + PHASE_REQUEST_HEADERS ); - /* RESPONSE_HEADERS_NAMES */ + /* SESSIONID */ msre_engine_variable_register(engine, - "RESPONSE_HEADERS_NAMES", - VAR_LIST, - 0, 1, - var_generic_list_validate, - var_response_headers_names_generate, - VAR_CACHE, + "SESSIONID", + VAR_SIMPLE, + 0, 0, + NULL, + var_sessionid_generate, + VAR_DONT_CACHE, PHASE_RESPONSE_HEADERS ); @@ -2438,59 +2941,15 @@ void msre_engine_register_default_variables(msre_engine *engine) { PHASE_RESPONSE_HEADERS ); - /* RESPONSE_PROTOCOL */ + /* USER */ msre_engine_variable_register(engine, - "RESPONSE_PROTOCOL", - VAR_SIMPLE, - 0, 0, - NULL, - var_response_protocol_generate, - VAR_CACHE, - PHASE_RESPONSE_HEADERS - ); - - /* RESPONSE_STATUS */ - msre_engine_variable_register(engine, - "RESPONSE_STATUS", - VAR_SIMPLE, - 0, 0, - NULL, - var_response_status_generate, - VAR_CACHE, - PHASE_RESPONSE_HEADERS - ); - - /* RESPONSE_CONTENT_TYPE */ - msre_engine_variable_register(engine, - "RESPONSE_CONTENT_TYPE", - VAR_SIMPLE, - 0, 0, - NULL, - var_response_content_type, - VAR_CACHE, - PHASE_RESPONSE_HEADERS - ); - - /* RESPONSE_CONTENT_LENGTH */ - msre_engine_variable_register(engine, - "RESPONSE_CONTENT_LENGTH", - VAR_SIMPLE, - 0, 0, - NULL, - var_response_content_length, - VAR_CACHE, - PHASE_RESPONSE_HEADERS - ); - - /* RULE */ - msre_engine_variable_register(engine, - "RULE", + "USER", VAR_LIST, 1, 1, - NULL, - var_rule_generate, - VAR_DONT_CACHE, - PHASE_RESPONSE_HEADERS + var_generic_list_validate, + var_user_generate, + VAR_CACHE, + PHASE_REQUEST_HEADERS ); /* USERID */ @@ -2504,15 +2963,114 @@ void msre_engine_register_default_variables(msre_engine *engine) { PHASE_RESPONSE_HEADERS ); - /* SESSIONID */ + /* TIME */ msre_engine_variable_register(engine, - "SESSIONID", + "TIME", VAR_SIMPLE, 0, 0, NULL, - var_sessionid_generate, + var_time_generate, VAR_DONT_CACHE, - PHASE_RESPONSE_HEADERS + PHASE_REQUEST_HEADERS + ); + + /* TIME_DAY */ + msre_engine_variable_register(engine, + "TIME_DAY", + VAR_SIMPLE, + 0, 0, + NULL, + var_time_day_generate, + VAR_DONT_CACHE, + PHASE_REQUEST_HEADERS + ); + + /* TIME_EPOCH */ + msre_engine_variable_register(engine, + "TIME_EPOCH", + VAR_SIMPLE, + 0, 0, + NULL, + var_time_epoch_generate, + VAR_DONT_CACHE, + PHASE_REQUEST_HEADERS + ); + + /* TIME_HOUR */ + msre_engine_variable_register(engine, + "TIME_HOUR", + VAR_SIMPLE, + 0, 0, + NULL, + var_time_hour_generate, + VAR_DONT_CACHE, + PHASE_REQUEST_HEADERS + ); + + /* TIME_MIN */ + msre_engine_variable_register(engine, + "TIME_MIN", + VAR_SIMPLE, + 0, 0, + NULL, + var_time_min_generate, + VAR_DONT_CACHE, + PHASE_REQUEST_HEADERS + ); + + /* TIME_MON */ + msre_engine_variable_register(engine, + "TIME_MON", + VAR_SIMPLE, + 0, 0, + NULL, + var_time_mon_generate, + VAR_DONT_CACHE, + PHASE_REQUEST_HEADERS + ); + + /* TIME_SEC */ + msre_engine_variable_register(engine, + "TIME_SEC", + VAR_SIMPLE, + 0, 0, + NULL, + var_time_sec_generate, + VAR_DONT_CACHE, + PHASE_REQUEST_HEADERS + ); + + /* TIME_WDAY */ + msre_engine_variable_register(engine, + "TIME_WDAY", + VAR_SIMPLE, + 0, 0, + NULL, + var_time_wday_generate, + VAR_DONT_CACHE, + PHASE_REQUEST_HEADERS + ); + + /* TIME_YEAR */ + msre_engine_variable_register(engine, + "TIME_YEAR", + VAR_SIMPLE, + 0, 0, + NULL, + var_time_year_generate, + VAR_DONT_CACHE, + PHASE_REQUEST_HEADERS + ); + + /* TX */ + msre_engine_variable_register(engine, + "TX", + VAR_LIST, + 1, 1, + var_generic_list_validate, + var_tx_generate, + VAR_DONT_CACHE, + PHASE_REQUEST_HEADERS ); /* WEBAPPID */ @@ -2525,4 +3083,26 @@ void msre_engine_register_default_variables(msre_engine *engine) { VAR_DONT_CACHE, PHASE_RESPONSE_HEADERS ); + + /* WEBSERVER_ERROR_LOG */ + msre_engine_variable_register(engine, + "WEBSERVER_ERROR_LOG", + VAR_LIST, + 0, 0, + NULL, + var_webserver_error_log_generate, + VAR_DONT_CACHE, + PHASE_REQUEST_HEADERS + ); + + /* XML */ + msre_engine_variable_register(engine, + "XML", + VAR_LIST, + 0, 1, + var_xml_validate, + var_xml_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); } diff --git a/apache2/t/op/beginsWith.t b/apache2/t/op/beginsWith.t new file mode 100644 index 00000000..831c00b0 --- /dev/null +++ b/apache2/t/op/beginsWith.t @@ -0,0 +1,45 @@ +### Empty +{ + type => "op", + name => "beginsWith", + param => "", + input => "", + ret => 1, +}, +{ + type => "op", + name => "beginsWith", + param => "TestCase", + input => "", + ret => 0, +}, +{ + type => "op", + name => "beginsWith", + param => "", + input => "TestCase", + ret => 1, +}, + +### General +{ + type => "op", + name => "beginsWith", + param => "abcdef", + input => "abcdef", + ret => 1, +}, +{ + type => "op", + name => "beginsWith", + param => "abcdef", + input => "abcdefghi", + ret => 1, +}, +{ + type => "op", + name => "beginsWith", + param => "abcdef", + input => "abc", + ret => 0, +}, diff --git a/apache2/t/op/contains.t b/apache2/t/op/contains.t new file mode 100644 index 00000000..0b406b48 --- /dev/null +++ b/apache2/t/op/contains.t @@ -0,0 +1,73 @@ +### Empty +{ + type => "op", + name => "contains", + param => "", + input => "", + ret => 1, +}, +{ + type => "op", + name => "contains", + param => "TestCase", + input => "", + ret => 0, +}, +{ + type => "op", + name => "contains", + param => "", + input => "TestCase", + ret => 1, +}, + +### General +{ + type => "op", + name => "contains", + param => "abc", + input => "abcdefghi", + ret => 1, +}, +{ + type => "op", + name => "contains", + param => "def", + input => "abcdefghi", + ret => 1, +}, +{ + type => "op", + name => "contains", + param => "ghi", + input => "abcdefghi", + ret => 1, +}, +{ + type => "op", + name => "contains", + param => "ghij", + input => "abcdefghi", + ret => 0, +}, +{ + type => "op", + name => "contains", + param => "x", + input => "x", + ret => 1, +}, +{ + type => "op", + name => "contains", + param => "y", + input => "xyz", + ret => 1, +}, +{ + type => "op", + name => "contains", + param => "hiding", + input => "hidinX<-not quite, but is later on->hiding", + ret => 1, +}, diff --git a/apache2/t/op/containsWord.t b/apache2/t/op/containsWord.t new file mode 100644 index 00000000..5b66bb5a --- /dev/null +++ b/apache2/t/op/containsWord.t @@ -0,0 +1,108 @@ +### Empty +{ + type => "op", + name => "containsWord", + param => "", + input => "", + ret => 1, +}, +{ + type => "op", + name => "containsWord", + param => "TestCase", + input => "", + ret => 0, +}, +{ + type => "op", + name => "containsWord", + param => "", + input => "TestCase", + ret => 1, +}, + +### General +{ + type => "op", + name => "containsWord", + param => "abc", + input => "abcdefghi", + ret => 0, +}, +{ + type => "op", + name => "containsWord", + param => "def", + input => "abcdefghi", + ret => 0, +}, +{ + type => "op", + name => "containsWord", + param => "ghi", + input => "abcdefghi", + ret => 0, +}, +{ + type => "op", + name => "containsWord", + param => "abc", + input => "abc def ghi", + ret => 1, +}, +{ + type => "op", + name => "containsWord", + param => "def", + input => "abc def ghi", + ret => 1, +}, +{ + type => "op", + name => "containsWord", + param => "ghi", + input => "abc def ghi", + ret => 1, +}, +{ + type => "op", + name => "containsWord", + param => "abc", + input => "abc\0def ghi", + ret => 1, +}, +{ + type => "op", + name => "containsWord", + param => "def", + input => "abc\0def ghi", + ret => 1, +}, +{ + type => "op", + name => "containsWord", + param => "x", + input => "x", + ret => 1, +}, +{ + type => "op", + name => "containsWord", + param => "x", + input => " x ", + ret => 1, +}, +{ + type => "op", + name => "containsWord", + param => "y", + input => "xyz", + ret => 0, +}, +{ + type => "op", + name => "containsWord", + param => "hiding", + input => "hidingX<-not on word boundary, but is later on->hiding", + ret => 1, +}, diff --git a/apache2/t/op/endsWith.t b/apache2/t/op/endsWith.t new file mode 100644 index 00000000..d1b37629 --- /dev/null +++ b/apache2/t/op/endsWith.t @@ -0,0 +1,52 @@ +### Empty +{ + type => "op", + name => "endsWith", + param => "", + input => "", + ret => 1, +}, +{ + type => "op", + name => "endsWith", + param => "TestCase", + input => "", + ret => 0, +}, +{ + type => "op", + name => "endsWith", + param => "", + input => "TestCase", + ret => 1, +}, + +### General +{ + type => "op", + name => "endsWith", + param => "abc", + input => "abcdefghi", + ret => 0, +}, +{ + type => "op", + name => "endsWith", + param => "def", + input => "abcdefghi", + ret => 0, +}, +{ + type => "op", + name => "endsWith", + param => "ghi", + input => "abcdefghi", + ret => 1, +}, +{ + type => "op", + name => "endsWith", + param => "ghi", + input => "abcdef\0ghi", + ret => 1, +}, diff --git a/apache2/t/op/eq.t b/apache2/t/op/eq.t new file mode 100644 index 00000000..b8f2068c --- /dev/null +++ b/apache2/t/op/eq.t @@ -0,0 +1,101 @@ +### Empty +{ + type => "op", + name => "eq", + param => "0", + input => "", + ret => 1, +}, +{ + type => "op", + name => "eq", + param => "5", + input => "", + ret => 0, +}, + +### Invalid +# xxx interpreted as 0 +{ + type => "op", + name => "eq", + param => "xxx", + input => "0", + ret => 1, +}, +# xxx interpreted as 0 +{ + type => "op", + name => "eq", + param => "xxx", + input => "5", + ret => 0, +}, +# xxx interpreted as 0 +{ + type => "op", + name => "eq", + param => "xxx", + input => "-1", + ret => 0, +}, +# xxx interpreted as 0 +{ + type => "op", + name => "eq", + param => "0", + input => "xxx", + ret => 1, +}, +# xxx interpreted as 0 +{ + type => "op", + name => "eq", + param => "5", + input => "xxx", + ret => 0, +}, + +### General +{ + type => "op", + name => "eq", + param => "0", + input => "-5", + ret => 0, +}, +{ + type => "op", + name => "eq", + param => "0", + input => "0", + ret => 1, +}, +{ + type => "op", + name => "eq", + param => "0", + input => "5", + ret => 0, +}, +{ + type => "op", + name => "eq", + param => "5", + input => "0", + ret => 0, +}, +{ + type => "op", + name => "eq", + param => "5", + input => "5", + ret => 1, +}, +{ + type => "op", + name => "eq", + param => "5", + input => "10", + ret => 0, +}, diff --git a/apache2/t/op/ge.t b/apache2/t/op/ge.t new file mode 100644 index 00000000..899c82a0 --- /dev/null +++ b/apache2/t/op/ge.t @@ -0,0 +1,93 @@ +### Empty +{ + type => "op", + name => "ge", + param => "0", + input => "", + ret => 1, +}, +{ + type => "op", + name => "ge", + param => "5", + input => "", + ret => 0, +}, + +### Invalid +# xxx interpreted as 0 +{ + type => "op", + name => "ge", + param => "xxx", + input => "5", + ret => 1, +}, +# xxx interpreted as 0 +{ + type => "op", + name => "ge", + param => "xxx", + input => "-1", + ret => 0, +}, +# xxx interpreted as 0 +{ + type => "op", + name => "ge", + param => "0", + input => "xxx", + ret => 1, +}, +# xxx interpreted as 0 +{ + type => "op", + name => "ge", + param => "5", + input => "xxx", + ret => 0, +}, + +### General +{ + type => "op", + name => "ge", + param => "0", + input => "-5", + ret => 0, +}, +{ + type => "op", + name => "ge", + param => "0", + input => "0", + ret => 1, +}, +{ + type => "op", + name => "ge", + param => "0", + input => "5", + ret => 1, +}, +{ + type => "op", + name => "ge", + param => "5", + input => "0", + ret => 0, +}, +{ + type => "op", + name => "ge", + param => "5", + input => "5", + ret => 1, +}, +{ + type => "op", + name => "ge", + param => "5", + input => "10", + ret => 1, +}, diff --git a/apache2/t/op/geoLookup.t b/apache2/t/op/geoLookup.t new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/apache2/t/op/geoLookup.t @@ -0,0 +1 @@ + diff --git a/apache2/t/op/gt.t b/apache2/t/op/gt.t new file mode 100644 index 00000000..168d81f1 --- /dev/null +++ b/apache2/t/op/gt.t @@ -0,0 +1,93 @@ +### Empty +{ + type => "op", + name => "gt", + param => "0", + input => "", + ret => 0, +}, +{ + type => "op", + name => "gt", + param => "5", + input => "", + ret => 0, +}, + +### Invalid +# xxx interpreted as 0 +{ + type => "op", + name => "gt", + param => "xxx", + input => "5", + ret => 1, +}, +# xxx interpreted as 0 +{ + type => "op", + name => "gt", + param => "xxx", + input => "-1", + ret => 0, +}, +# xxx interpreted as 0 +{ + type => "op", + name => "gt", + param => "-1", + input => "xxx", + ret => 1, +}, +# xxx interpreted as 0 +{ + type => "op", + name => "gt", + param => "5", + input => "xxx", + ret => 0, +}, + +### General +{ + type => "op", + name => "gt", + param => "0", + input => "-5", + ret => 0, +}, +{ + type => "op", + name => "gt", + param => "0", + input => "0", + ret => 0, +}, +{ + type => "op", + name => "gt", + param => "0", + input => "5", + ret => 1, +}, +{ + type => "op", + name => "gt", + param => "5", + input => "0", + ret => 0, +}, +{ + type => "op", + name => "gt", + param => "5", + input => "5", + ret => 0, +}, +{ + type => "op", + name => "gt", + param => "5", + input => "10", + ret => 1, +}, diff --git a/apache2/t/op/inspectFile.t b/apache2/t/op/inspectFile.t new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/apache2/t/op/inspectFile.t @@ -0,0 +1 @@ + diff --git a/apache2/t/op/le.t b/apache2/t/op/le.t new file mode 100644 index 00000000..b6a84175 --- /dev/null +++ b/apache2/t/op/le.t @@ -0,0 +1,93 @@ +### Empty +{ + type => "op", + name => "le", + param => "0", + input => "", + ret => 1, +}, +{ + type => "op", + name => "le", + param => "5", + input => "", + ret => 1, +}, + +### Invalid +# xxx interpreted as 0 +{ + type => "op", + name => "le", + param => "xxx", + input => "5", + ret => 0, +}, +# xxx interpreted as 0 +{ + type => "op", + name => "le", + param => "xxx", + input => "-1", + ret => 1, +}, +# xxx interpreted as 0 +{ + type => "op", + name => "le", + param => "0", + input => "xxx", + ret => 1, +}, +# xxx interpreted as 0 +{ + type => "op", + name => "le", + param => "5", + input => "xxx", + ret => 1, +}, + +### General +{ + type => "op", + name => "le", + param => "0", + input => "-5", + ret => 1, +}, +{ + type => "op", + name => "le", + param => "0", + input => "0", + ret => 1, +}, +{ + type => "op", + name => "le", + param => "0", + input => "5", + ret => 0, +}, +{ + type => "op", + name => "le", + param => "5", + input => "0", + ret => 1, +}, +{ + type => "op", + name => "le", + param => "5", + input => "5", + ret => 1, +}, +{ + type => "op", + name => "le", + param => "5", + input => "10", + ret => 0, +}, diff --git a/apache2/t/op/lt.t b/apache2/t/op/lt.t new file mode 100644 index 00000000..59882d46 --- /dev/null +++ b/apache2/t/op/lt.t @@ -0,0 +1,93 @@ +### Empty +{ + type => "op", + name => "lt", + param => "0", + input => "", + ret => 0, +}, +{ + type => "op", + name => "lt", + param => "5", + input => "", + ret => 1, +}, + +### Invalid +# xxx interpreted as 0 +{ + type => "op", + name => "lt", + param => "xxx", + input => "5", + ret => 0, +}, +# xxx interpreted as 0 +{ + type => "op", + name => "lt", + param => "xxx", + input => "-1", + ret => 1, +}, +# xxx interpreted as 0 +{ + type => "op", + name => "lt", + param => "-1", + input => "xxx", + ret => 0, +}, +# xxx interpreted as 0 +{ + type => "op", + name => "lt", + param => "5", + input => "xxx", + ret => 1, +}, + +### General +{ + type => "op", + name => "lt", + param => "0", + input => "-5", + ret => 1, +}, +{ + type => "op", + name => "lt", + param => "0", + input => "0", + ret => 0, +}, +{ + type => "op", + name => "lt", + param => "0", + input => "5", + ret => 0, +}, +{ + type => "op", + name => "lt", + param => "5", + input => "0", + ret => 1, +}, +{ + type => "op", + name => "lt", + param => "5", + input => "5", + ret => 0, +}, +{ + type => "op", + name => "lt", + param => "5", + input => "10", + ret => 0, +}, diff --git a/apache2/t/op/m.t b/apache2/t/op/m.t new file mode 100644 index 00000000..def6d08f --- /dev/null +++ b/apache2/t/op/m.t @@ -0,0 +1,52 @@ +### Empty +{ + type => "op", + name => "m", + param => "", + input => "", + ret => 1, +}, +{ + type => "op", + name => "m", + param => "TestCase", + input => "", + ret => 0, +}, +{ + type => "op", + name => "m", + param => "", + input => "TestCase", + ret => 1, +}, + +### General +{ + type => "op", + name => "m", + param => "abc", + input => "abcdefghi", + ret => 1, +}, +{ + type => "op", + name => "m", + param => "def", + input => "abcdefghi", + ret => 1, +}, +{ + type => "op", + name => "m", + param => "ghi", + input => "abcdefghi", + ret => 1, +}, +{ + type => "op", + name => "m", + param => "ghij", + input => "abcdefghi", + ret => 0, +}, diff --git a/apache2/t/op/noMatch.t b/apache2/t/op/noMatch.t new file mode 100644 index 00000000..8775dd59 --- /dev/null +++ b/apache2/t/op/noMatch.t @@ -0,0 +1,23 @@ +### Empty +{ + type => "op", + name => "noMatch", + param => "", + input => "", + ret => 0, +}, +{ + type => "op", + name => "noMatch", + param => "TestCase", + input => "", + ret => 0, +}, +{ + type => "op", + name => "noMatch", + param => "", + input => "TestCase", + ret => 0, +}, + diff --git a/apache2/t/op/pm.t b/apache2/t/op/pm.t new file mode 100644 index 00000000..2850a811 --- /dev/null +++ b/apache2/t/op/pm.t @@ -0,0 +1,112 @@ +### Empty +{ + type => "op", + name => "pm", + param => "TestCase", + input => "", + ret => 0, +}, + +### General +{ + type => "op", + name => "pm", + param => "abc", + input => "abcdefghi", + ret => 1, +}, +{ + type => "op", + name => "pm", + param => "def", + input => "abcdefghi", + ret => 1, +}, +{ + type => "op", + name => "pm", + param => "ghi", + input => "abcdefghi", + ret => 1, +}, +{ + type => "op", + name => "pm", + param => "ghij", + input => "abcdefghi", + ret => 0, +}, + +### Multiple +{ + type => "op", + name => "pm", + param => "abc def ghi", + input => "abcxxxyyy", + ret => 1, +}, +{ + type => "op", + name => "pm", + param => "abc def ghi", + input => "xxxabcyyy", + ret => 1, +}, +{ + type => "op", + name => "pm", + param => "abc def ghi", + input => "xxxyyyabc", + ret => 1, +}, +{ + type => "op", + name => "pm", + param => "abc def ghi", + input => "defxxxyyy", + ret => 1, +}, +{ + type => "op", + name => "pm", + param => "abc def ghi", + input => "xxxdefyyy", + ret => 1, +}, +{ + type => "op", + name => "pm", + param => "abc def ghi", + input => "xxxyyydef", + ret => 1, +}, +{ + type => "op", + name => "pm", + param => "abc def ghi", + input => "ghixxxyyy", + ret => 1, +}, +{ + type => "op", + name => "pm", + param => "abc def ghi", + input => "xxxghiyyy", + ret => 1, +}, +{ + type => "op", + name => "pm", + param => "abc def ghi", + input => "xxxyyyghi", + ret => 1, +}, + +### Long +{ + type => "op", + name => "pm", + param => "000 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999", + input => "xxxyyy999", + ret => 1, +}, diff --git a/apache2/t/op/pmFromFile-01.dat b/apache2/t/op/pmFromFile-01.dat new file mode 100644 index 00000000..f7b07ea0 --- /dev/null +++ b/apache2/t/op/pmFromFile-01.dat @@ -0,0 +1,4 @@ +abc +def +ghi +xxx yyy zzz diff --git a/apache2/t/op/pmFromFile.t b/apache2/t/op/pmFromFile.t new file mode 100644 index 00000000..b565d86c --- /dev/null +++ b/apache2/t/op/pmFromFile.t @@ -0,0 +1,45 @@ +### No Match +{ + type => "op", + name => "pmFromFile", + param => "op/pmFromFile-01.dat", + input => "xxxyyyzzz", + ret => 0, +}, + +### Multiple +{ + type => "op", + name => "pmFromFile", + param => "op/pmFromFile-01.dat", + input => "defxxxyyy", + ret => 1, +}, +{ + type => "op", + name => "pmFromFile", + param => "op/pmFromFile-01.dat", + input => "xxxdefyyy", + ret => 1, +}, +{ + type => "op", + name => "pmFromFile", + param => "op/pmFromFile-01.dat", + input => "xxxyyydef", + ret => 1, +}, +{ + type => "op", + name => "pmFromFile", + param => "op/pmFromFile-01.dat", + input => "xxx yyy zzz", + ret => 1, +}, +{ + type => "op", + name => "pmFromFile", + param => "op/pmFromFile-01.dat", + input => "xxx yyy", + ret => 0, +}, diff --git a/apache2/t/op/rbl.t b/apache2/t/op/rbl.t new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/apache2/t/op/rbl.t @@ -0,0 +1 @@ + diff --git a/apache2/t/op/rx.t b/apache2/t/op/rx.t new file mode 100644 index 00000000..4be49e73 --- /dev/null +++ b/apache2/t/op/rx.t @@ -0,0 +1,62 @@ +### Empty +{ + type => "op", + name => "rx", + param => "", + input => "", + ret => 1, +}, +{ + type => "op", + name => "rx", + param => "TestCase", + input => "", + ret => 0, +}, +{ + type => "op", + name => "rx", + param => "", + input => "TestCase", + ret => 1, +}, + +### General +{ + type => "op", + name => "rx", + param => "abc", + input => "abcdefghi", + ret => 1, +}, +{ + type => "op", + name => "rx", + param => "def", + input => "abcdefghi", + ret => 1, +}, +{ + type => "op", + name => "rx", + param => "ghi", + input => "abcdefghi", + ret => 1, +}, +{ + type => "op", + name => "rx", + param => "ghij", + input => "abcdefghi", + ret => 0, +}, + +### Complex regex +{ + type => "op", + name => "rx", + param => qr/^([^=])\s*=\s*((?:abc)+(?:def|ghi){2})$/i, + input => "x =AbCDeFgHi", + ret => 1, +}, + diff --git a/apache2/t/op/streq.t b/apache2/t/op/streq.t new file mode 100644 index 00000000..76451fe6 --- /dev/null +++ b/apache2/t/op/streq.t @@ -0,0 +1,52 @@ +### Empty +{ + type => "op", + name => "streq", + param => "", + input => "", + ret => 1, +}, +{ + type => "op", + name => "streq", + param => "TestCase", + input => "", + ret => 0, +}, +{ + type => "op", + name => "streq", + param => "", + input => "TestCase", + ret => 0, +}, + +### General +{ + type => "op", + name => "streq", + param => "abc", + input => "abcdefghi", + ret => 0, +}, +{ + type => "op", + name => "streq", + param => "def", + input => "abcdefghi", + ret => 0, +}, +{ + type => "op", + name => "streq", + param => "ghi", + input => "abcdefghi", + ret => 0, +}, +{ + type => "op", + name => "streq", + param => "abcdefghi", + input => "abcdefghi", + ret => 1, +}, diff --git a/apache2/t/op/unconditionalMatch.t b/apache2/t/op/unconditionalMatch.t new file mode 100644 index 00000000..56458d81 --- /dev/null +++ b/apache2/t/op/unconditionalMatch.t @@ -0,0 +1,23 @@ +### Empty +{ + type => "op", + name => "unconditionalMatch", + param => "", + input => "", + ret => 1, +}, +{ + type => "op", + name => "unconditionalMatch", + param => "TestCase", + input => "", + ret => 1, +}, +{ + type => "op", + name => "unconditionalMatch", + param => "", + input => "TestCase", + ret => 1, +}, + diff --git a/apache2/t/op/validateByteRange.t b/apache2/t/op/validateByteRange.t new file mode 100644 index 00000000..21a535e1 --- /dev/null +++ b/apache2/t/op/validateByteRange.t @@ -0,0 +1,54 @@ +### Empty +{ + type => "op", + name => "validateByteRange", + param => "0-255", + input => "", + ret => 0, +}, +{ + type => "op", + name => "validateByteRange", + param => "", + input => "TestCase", + ret => 1, +}, + +### Invalid +{ + type => "op", + name => "validateByteRange", + param => "xxx", + input => "TestCase", + ret => 1, +}, +{ + type => "op", + name => "validateByteRange", + param => "xxx", + input => "\x00", + ret => 0, +}, + +### General +{ + type => "op", + name => "validateByteRange", + param => "0-255", + input => "abcdefghi", + ret => 0, +}, +{ + type => "op", + name => "validateByteRange", + param => ord("a")."-".ord("i"), + input => "abcdefghi", + ret => 0, +}, +{ + type => "op", + name => "validateByteRange", + param => ord("a")."-".ord("i"), + input => "abcdefghij", + ret => 1, +}, diff --git a/apache2/t/op/validateDTD.t b/apache2/t/op/validateDTD.t new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/apache2/t/op/validateDTD.t @@ -0,0 +1 @@ + diff --git a/apache2/t/op/validateSchema.t b/apache2/t/op/validateSchema.t new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/apache2/t/op/validateSchema.t @@ -0,0 +1 @@ + diff --git a/apache2/t/op/validateUrlEncoding.t b/apache2/t/op/validateUrlEncoding.t new file mode 100644 index 00000000..904b1c36 --- /dev/null +++ b/apache2/t/op/validateUrlEncoding.t @@ -0,0 +1,101 @@ +### Empty +{ + type => "op", + name => "validateUrlEncoding", + param => "", + input => "", + ret => 0, +}, + +### General +{ + type => "op", + name => "validateUrlEncoding", + param => "", + input => "Hello%20World!", + ret => 0, +}, +{ + type => "op", + name => "validateUrlEncoding", + param => "", + input => "Hello+World!", + ret => 0, +}, +{ + type => "op", + name => "validateUrlEncoding", + param => "", + input => "HelloWorld!", + ret => 0, +}, +{ + type => "op", + name => "validateUrlEncoding", + param => "", + input => "%00Hello%20World!", + ret => 0, +}, +{ + type => "op", + name => "validateUrlEncoding", + param => "", + input => "Hello%20World!%00", + ret => 0, +}, +{ + type => "op", + name => "validateUrlEncoding", + param => "", + input => "%00", + ret => 0, +}, +{ + type => "op", + name => "validateUrlEncoding", + param => "", + input => "%ff", + ret => 0, +}, +{ + type => "op", + name => "validateUrlEncoding", + param => "", + input => "%0", + ret => 1, +}, +{ + type => "op", + name => "validateUrlEncoding", + param => "", + input => "%f", + ret => 1, +}, +{ + type => "op", + name => "validateUrlEncoding", + param => "", + input => "%", + ret => 1, +}, +{ + type => "op", + name => "validateUrlEncoding", + param => "", + input => "%0z", + ret => 1, +}, +{ + type => "op", + name => "validateUrlEncoding", + param => "", + input => "%z0", + ret => 1, +}, +{ + type => "op", + name => "validateUrlEncoding", + param => "", + input => "%0%", + ret => 1, +}, diff --git a/apache2/t/op/validateUtf8Encoding.t b/apache2/t/op/validateUtf8Encoding.t new file mode 100644 index 00000000..8c3825a6 --- /dev/null +++ b/apache2/t/op/validateUtf8Encoding.t @@ -0,0 +1,260 @@ +### Empty +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "", + ret => 0, +}, + +### Valid "I can eat glass and it does not hurt me." +# Greek +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "ὕαλον ϕαγεῖν δύναμαι· τοῦτο οὔ με βλάπτει.", + ret => 0, +}, +# French +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "Je peux manger du verre, ça ne me fait pas de mal.", + ret => 0, +}, +# Spanish +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "Puedo comer vidrio, no me hace daño.", + ret => 0, +}, +# Esparanto +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "Mi povas manĝi vitron, ĝi ne damaĝas min.", + ret => 0, +}, +# Latin +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "Ic mæg glæs eotan ond hit ne hearmiað me.", + ret => 0, +}, +# Serbian +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "Могу јести стакло а да ми не шкоди.", + ret => 0, +}, +# Russian +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "Я могу есть стекло, оно мне не вредит.", + ret => 0, +}, +# Armenian +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "Կրնամ ապակի ուտել և ինծի անհանգիստ չըներ։", + ret => 0, +}, +# Turkish +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "جام ييه بلورم بڭا ضررى طوقونمز", + ret => 0, +}, +# Hindi +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "मैं काँच खा सकता हूँ, मुझे उस से कोई पीडा नहीं होती.", + ret => 0, +}, +# Arabic +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "أنا قادر على أكل الزجاج و هذا لا يؤلمني.", + ret => 0, +}, +# Hebrew +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "אני יכול לאכול זכוכית וזה לא מזיק לי.", + ret => 0, +}, +# Japanese +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "私はガラスを食べられます。それは私を傷つけません。", + ret => 0, +}, +# Thai +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "ฉันกินกระจกได้ แต่มันไม่ทำให้ฉันเจ็บ", + ret => 0, +}, +# Korean +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "나는 유리를 먹을 수 있어요. 그래도 아프지 않아요", + ret => 0, +}, +# Navajo +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "Tsésǫʼ yishą́ągo bííníshghah dóó doo shił neezgai da.", + ret => 0, +}, +# Icelandic +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "Ég get etið gler án þess að meiða mig.", + ret => 0, +}, +# Sanskrit +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "काचं शक्नोम्यत्तुम् । नोपहिनस्ति माम् ॥", + ret => 0, +}, +# English Braille +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "⠊⠀⠉⠁⠝⠀⠑⠁⠞⠀⠛⠇⠁⠎⠎⠀⠁⠝⠙⠀⠊⠞⠀⠙⠕⠑⠎⠝⠞⠀⠓⠥⠗⠞⠀⠍⠑", + ret => 0, +}, +# Danish +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "Jeg kan spise glas, det gør ikke ondt på mig.", + ret => 0, +}, +# Hungarian +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "Meg tudom enni az üveget, nem lesz tőle bajom.", + ret => 0, +}, +# Estonian +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "Ma võin klaasi süüa, see ei tee mulle midagi.", + ret => 0, +}, +# Czech +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "Mohu jíst sklo, neublíží mi.", + ret => 0, +}, +# Slovak +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "Môžem jesť sklo. Nezraní ma.", + ret => 0, +}, +# Polish +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "Mogę jeść szkło i mi nie szkodzi.", + ret => 0, +}, +# Symbols +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input=>"∮E⋅da=Qn→∞∑f(i)=∏g(i)∀x∈ℝ:⌈x⌉=−⌊−x⌋α∧¬β=¬(¬α∨β)ℕ⊆ℕ₀⊂ℤ⊂ℚ⊂ℝ⊂ℂ⊥ 0, +}, +### Invalid +# Umlauted a +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "\x00\xe4", + ret => 1, +}, +# Umlauted a +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "\xe4", + ret => 1, +}, +# +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "\x03\xbf", + ret => 1, +}, +# +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "\xc9\x3b", + ret => 1, +}, +### Invalid Full width +# +{ + type => "op", + name => "validateUtf8Encoding", + param => "", + input => "\xFF\x00", + ret => 1, +}, diff --git a/apache2/t/op/verifyCC.t b/apache2/t/op/verifyCC.t new file mode 100644 index 00000000..91162785 --- /dev/null +++ b/apache2/t/op/verifyCC.t @@ -0,0 +1,514 @@ +### Empty +# empty w/re not matching empty +{ + type => "op", + name => "verifyCC", + param => "\d+", + input => "", + ret => 0, +}, +# empty w/re matching empty +{ + type => "op", + name => "verifyCC", + param => '\d*', + input => "", + ret => 0, +}, + +### Non-matching +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "TestCase", + ret => 0, +}, + +### No digits in match +{ + type => "op", + name => "verifyCC", + param => 'TestCase', + input => "TestCase", + ret => 0, +}, + +### Too generic RE w/no matchs (Luhn will be called until all fail) +{ + type => "op", + name => "verifyCC", + param => '.*', + input => "TestCase", + ret => 0, +}, + +### Test Good CC# +# Mastercard +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "5484605089158216", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "5574407071707154", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "5351341509714210", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "5585166974020647", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "5492180332479256", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "5111178142162816", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "5511424748431031", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "5259964281562326", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "5138342589974385", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "5362069587634979", + ret => 1, +}, +# VISA 16 digit +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "4916545704601136", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "4539501231827691", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "4556338049595394", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "4929326438756024", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "4485432027326322", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "4532104980682081", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "4485974616349298", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "4916580487207199", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "4532009746910413", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "4024007144622932", + ret => 1, +}, +# VISA 13 digit +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "4556324125126", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "4067482954141", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "4532402654980", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "4539709679875", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "4024007182237", + ret => 1, +}, +# American Express +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "343918934573386", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "344881778330710", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "345439478558905", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "346465614421111", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "372263817755618", + ret => 1, +}, +# Discover +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "6011402777433576", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "6011890045362751", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "6011439091242416", + ret => 1, +}, +# Diners Club +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "30162519308318", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "30311556856867", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "36850112043985", + ret => 1, +}, +# enRoute +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "201427829075664", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "201434726660424", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "201453368666085", + ret => 1, +}, +# JCB 15 digit +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "210091499965007", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "210072739882947", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "180013970064072", + ret => 1, +}, +# JCB 16 digit +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "3096676276259096", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "3158726040010070", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "3096531217494742", + ret => 1, +}, +# Voyager +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "869974262335041", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "869905005856398", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "869950500085465", + ret => 1, +}, + +### Test Bad CC# +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d+)(?:[^\d]|$)', + input => "1234567890012345", + ret => 0, +}, + +### Test regex + Luhn +# from http://www.merriampark.com/anatomycc.htm +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d{4}\-?\d{4}\-?\d{2}\-?\d{2}\-?\d{1,4})(?:[^\d]|$)', + input => "4417123456789113", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d{4}\-?\d{4}\-?\d{2}\-?\d{2}\-?\d{1,4})(?:[^\d]|$)', + input => "4408041234567893", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d{4}\-?\d{4}\-?\d{2}\-?\d{2}\-?\d{1,4})(?:[^\d]|$)', + input => "4408041234567890", + ret => 0, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d{4}\-?\d{4}\-?\d{2}\-?\d{2}\-?\d{1,4})(?:[^\d]|$)', + input => "4417123456789112", + ret => 0, +}, +# on word boundary +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d{4}\-?\d{4}\-?\d{2}\-?\d{2}\-?\d{1,4})(?:[^\d]|$)', + input => "a5484605089158216", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d{4}\-?\d{4}\-?\d{2}\-?\d{2}\-?\d{1,4})(?:[^\d]|$)', + input => "a5484605089158216b", + ret => 1, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d{4}\-?\d{4}\-?\d{2}\-?\d{2}\-?\d{1,4})(?:[^\d]|$)', + input => "5484605089158216b", + ret => 1, +}, +# valid patterns +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d{4}\-?\d{4}\-?\d{2}\-?\d{2}\-?\d{1,4})(?:[^\d]|$)', + input => "5484-6050-8915-8216", + ret => 1, +}, +# changed digit from table above +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d{4}\-?\d{4}\-?\d{2}\-?\d{2}\-?\d{1,4})(?:[^\d]|$)', + input => "5484605089158217", + ret => 0, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d{4}\-?\d{4}\-?\d{2}\-?\d{2}\-?\d{1,4})(?:[^\d]|$)', + input => "5574407071807154", + ret => 0, +}, +# wrong patterns +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d{4}\-?\d{4}\-?\d{2}\-?\d{2}\-?\d{1,4})(?:[^\d]|$)', + input => "5-484-6050-8915-8216", + ret => 0, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d{4}\-?\d{4}\-?\d{2}\-?\d{2}\-?\d{1,4})(?:[^\d]|$)', + input => "5484 6050 8915 8216", + ret => 0, +}, +# not on digits boundary +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d{4}\-?\d{4}\-?\d{2}\-?\d{2}\-?\d{1,4})(?:[^\d]|$)', + input => "15484605089158216", + ret => 0, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d{4}\-?\d{4}\-?\d{2}\-?\d{2}\-?\d{1,4})(?:[^\d]|$)', + input => "154846050891582162", + ret => 0, +}, +{ + type => "op", + name => "verifyCC", + param => '(?:^|[^\d])(\d{4}\-?\d{4}\-?\d{2}\-?\d{2}\-?\d{1,4})(?:[^\d]|$)', + input => "54846050891582162", + ret => 0, +}, diff --git a/apache2/t/op/within.t b/apache2/t/op/within.t new file mode 100644 index 00000000..9d86dcdd --- /dev/null +++ b/apache2/t/op/within.t @@ -0,0 +1,52 @@ +### Empty +{ + type => "op", + name => "within", + param => "", + input => "", + ret => 1, +}, +{ + type => "op", + name => "within", + param => "TestCase", + input => "", + ret => 1, +}, +{ + type => "op", + name => "within", + param => "", + input => "TestCase", + ret => 0, +}, + +### General +{ + type => "op", + name => "within", + param => "abcdefghi", + input => "abc", + ret => 1, +}, +{ + type => "op", + name => "within", + param => "abcdefghi", + input => "def", + ret => 1, +}, +{ + type => "op", + name => "within", + param => "abcdefghi", + input => "ghi", + ret => 1, +}, +{ + type => "op", + name => "within", + param => "abcdefghi", + input => "ghij", + ret => 0, +}, diff --git a/apache2/t/run-tests.pl.in b/apache2/t/run-tests.pl.in new file mode 100755 index 00000000..238cac29 --- /dev/null +++ b/apache2/t/run-tests.pl.in @@ -0,0 +1,157 @@ +#!@PERL@ +# +# Run unit tests. +# +# Syntax: +# All: run-tests.pl +# All in file: run-tests.pl file +# Nth in file: run-tests.pl file N +# +use strict; +use POSIX qw(WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG); +use File::Basename qw(basename dirname); +use FileHandle; +use IPC::Open2 qw(open2); + +my @TYPES = qw(tfn op); +my $TEST = "./msc_test"; +my $SCRIPT = basename($0); +my $SCRIPTDIR = dirname($0); +my $PASSED = 0; +my $TOTAL = 0; + +if (defined $ARGV[0]) { + runfile(dirname($ARGV[0]), basename($ARGV[0]), $ARGV[1]); + done(); +} + +for my $type (sort @TYPES) { + my $dir = "$SCRIPTDIR/$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 %t = %{$t || {}}; + my $id = sprintf("%6d", $n); + my $in = $t{input}; + my $out; + my $test_in = new FileHandle(); + my $test_out = new FileHandle(); + my $test_pid; + my $rc = 0; + my $param; + + if ($t{type} eq "tfn") { + $param = escape($t{output}); + } + elsif ($t{type} eq "op") { + $param = escape($t{param}); + } + else { + quit(1, "Unknown type \"$t{type}\" - should be one of: " . join(",",@TYPES)); + } + + @test = ($t{type}, $t{name}, $param, (exists($t{ret}) ? ($t{ret}) : ())); + $teststr = "$TEST " . join(" ", map { "\"$_\"" } @test); + $test_pid = open2($test_out, $test_in, $TEST, @test) or quit(1, "Failed to execute test: $teststr\": $!"); + print $test_in "$in"; + close $test_in; + $out = join("\\n", split(/\n/, <$test_out>)); + close $test_out; + waitpid($test_pid, 0); + + $rc = $?; + if ( WIFEXITED($rc) ) { + $rc = WEXITSTATUS($rc); + } + elsif( WIFSIGNALED($rc) ) { + msg("Test exited with signal " . WTERMSIG($rc) . "."); + msg("Executed: $teststr"); + $rc = -1; + } + else { + msg("Test exited with unknown error."); + $rc = -1; + } + + if ($rc == 0) { + $pass++; + } + + msg(sprintf("%s) %s \"%s\": %s%s", $id, $t{type}, $t{name}, ($rc ? "failed" : "passed"), ((defined($out) && $out ne "")? " ($out)" : ""))); + + } + + $TOTAL += $testnum ? 1 : $n; + $PASSED += $pass; + + msg(sprintf("Passed: %2d; Failed: %2d", $pass, $testnum ? (1 - $pass) : ($n - $pass))); +} + +sub escape { + my @new = (); + for my $c (split(//, $_[0])) { + push @new, ((ord($c) >= 0x20 and ord($c) <= 0x7e) ? $c : sprintf("\\x%02x", ord($c))); + } + join('', @new); +} + +sub msg { + print STDOUT "@_\n" if (@_); +} + +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)."); +} diff --git a/apache2/t/tfn/base64Decode.t b/apache2/t/tfn/base64Decode.t new file mode 100644 index 00000000..59d88029 --- /dev/null +++ b/apache2/t/tfn/base64Decode.t @@ -0,0 +1,51 @@ +### Empty +{ + type => "tfn", + name => "base64Decode", + input => "", + output => "", + ret => 0, +}, + +### Test values with varying lengths to check padding +{ + type => "tfn", + name => "base64Decode", + input => "VGVzdENhc2U=", + output => "TestCase", + ret => 1, +}, +{ + type => "tfn", + name => "base64Decode", + input => "VGVzdENhc2Ux", + output => "TestCase1", + ret => 1, +}, +{ + type => "tfn", + name => "base64Decode", + input => "VGVzdENhc2UxMg==", + output => "TestCase12", + ret => 1, +}, + + +### Check with a NUL +{ + type => "tfn", + name => "base64Decode", + input => "VGVzdABDYXNl", + output => "Test\0Case", + ret => 1, +}, + +### Invalid +# What should happen here? Probably just fail and leave alone. +{ + type => "tfn", + name => "base64Decode", + input => "VGVzdENhc2U=\0VGVzdENhc2U=", + output => "TestCase", + ret => 1, +}, diff --git a/apache2/t/tfn/base64Encode.t b/apache2/t/tfn/base64Encode.t new file mode 100644 index 00000000..a5a6f83b --- /dev/null +++ b/apache2/t/tfn/base64Encode.t @@ -0,0 +1,40 @@ +### Empty +{ + type => "tfn", + name => "base64Encode", + input => "", + output => "", + ret => 0, +}, + +### Test values with varying lengths to check padding +{ + type => "tfn", + name => "base64Encode", + input => "TestCase", + output => "VGVzdENhc2U=", + ret => 1, +}, +{ + type => "tfn", + name => "base64Encode", + input => "TestCase1", + output => "VGVzdENhc2Ux", + ret => 1, +}, +{ + type => "tfn", + name => "base64Encode", + input => "TestCase12", + output => "VGVzdENhc2UxMg==", + ret => 1, +}, + +### Check with a NUL +{ + type => "tfn", + name => "base64Encode", + input => "Test\0Case", + output => "VGVzdABDYXNl", + ret => 1, +}, diff --git a/apache2/t/tfn/compressWhitespace.t b/apache2/t/tfn/compressWhitespace.t new file mode 100644 index 00000000..fe6179ed --- /dev/null +++ b/apache2/t/tfn/compressWhitespace.t @@ -0,0 +1,50 @@ +### Empty +{ + type => "tfn", + name => "compressWhitespace", + input => "", + output => "", + ret => 0, +}, + +### Nothing +{ + type => "tfn", + name => "compressWhitespace", + input => "TestCase", + output => "TestCase", + ret => 0, +}, +{ + type => "tfn", + name => "compressWhitespace", + input => "Test\0Case", + output => "Test\0Case", + ret => 0, +}, +{ + type => "tfn", + name => "compressWhitespace", + input => "Test Case", + output => "Test Case", + ret => 0, +}, + + +### Compress space/tab +{ + type => "tfn", + name => "compressWhitespace", + input => " Test \t Case ", + output => " Test Case ", + ret => 1, +}, + +### Pretty much everything in one +{ + type => "tfn", + name => "compressWhitespace", + input => "This is a test case with a tab \t, vtab \x0b, newline \x0a, return \x0d, formfeed \f, and a NUL\0 in it with a CRLF at the end.\x0d\x0a", + output => "This is a test case with a tab , vtab , newline , return , formfeed , and a NUL\0 in it with a CRLF at the end. ", + ret => 1, +}, diff --git a/apache2/t/tfn/escapeSeqDecode.t b/apache2/t/tfn/escapeSeqDecode.t new file mode 100644 index 00000000..7f362e9b --- /dev/null +++ b/apache2/t/tfn/escapeSeqDecode.t @@ -0,0 +1,114 @@ +### Empty +{ + type => "tfn", + name => "escapeSeqDecode", + input => "", + output => "", + ret => 0, +}, + +### Nothing +{ + type => "tfn", + name => "escapeSeqDecode", + input => "TestCase", + output => "TestCase", + ret => 0, +}, +{ + type => "tfn", + name => "escapeSeqDecode", + input => "Test\0Case", + output => "Test\0Case", + ret => 0, +}, + +### Valid Sequences +{ + type => "tfn", + name => "escapeSeqDecode", + input => "\\a\\b\\f\\n\\r\\t\\v\\?\\'\\\"\\0\\12\\123\\x00\\xff", + output => "\a\b\f\x0a\x0d\t\x0b?'\"\x00\x0a\x53\x00\xff", + ret => 1, +}, +{ + type => "tfn", + name => "escapeSeqDecode", + input => "\\a\\b\\f\\n\\r\\t\\v\0\\?\\'\\\"\\0\\12\\123\\x00\\xff", + output => "\a\b\f\x0a\x0d\t\x0b\0?'\"\x00\x0a\x53\x00\xff", + ret => 1, +}, + +### Invalid Sequences +# \8 and \9 are not octal +# \666 is a byte overflow (0x1b6) and should be truncated to a byte as 0xb6 +# \xag and \xga are not hex, +# \0123 is \012 + '3' +{ + type => "tfn", + name => "escapeSeqDecode", + input => "\\8\\9\\666\\xag\\xga\\0123", + output => "89\xb6xagxga\x0a3", + ret => 1, +}, + +# \x, \x0 lack enough hex digits +{ + type => "tfn", + name => "escapeSeqDecode", + input => "\\x", + output => "x", + ret => 1, +}, +{ + type => "tfn", + name => "escapeSeqDecode", + input => "\\x\\x0", + output => "xx0", + ret => 1, +}, +{ + type => "tfn", + name => "escapeSeqDecode", + input => "\\x\\x0\0", + output => "xx0\0", + ret => 1, +}, +# Octal at end +{ + type => "tfn", + name => "escapeSeqDecode", + input => "\\0", + output => "\x00", + ret => 1, +}, +{ + type => "tfn", + name => "escapeSeqDecode", + input => "\\01", + output => "\x01", + ret => 1, +}, +{ + type => "tfn", + name => "escapeSeqDecode", + input => "\\012", + output => "\x0a", + ret => 1, +}, +# A forward slash with nothing after +{ + type => "tfn", + name => "escapeSeqDecode", + input => "\\", + output => "\\", + ret => 0, +}, +# A forward slash with NUL after +{ + type => "tfn", + name => "escapeSeqDecode", + input => "\\\0", + output => "\0", + ret => 1, +}, diff --git a/apache2/t/tfn/hexDecode.t b/apache2/t/tfn/hexDecode.t new file mode 100644 index 00000000..ac6b4236 --- /dev/null +++ b/apache2/t/tfn/hexDecode.t @@ -0,0 +1,50 @@ +### Empty +{ + type => "tfn", + name => "hexDecode", + input => "", + output => "", + ret => 1, +}, + +### Basic +{ + type => "tfn", + name => "hexDecode", + input => "5465737443617365", + output => "TestCase", + ret => 1, +}, + +### Basic w/NULL +{ + type => "tfn", + name => "hexDecode", + input => "546573740043617365", + output => "Test\0Case", + ret => 1, +}, + +### Invalid +# What should happen here? Probably just fail and leave alone. +{ + type => "tfn", + name => "hexDecode", + input => "01234567890a0z01234567890a", + output => "\x01#Eg\x89\x0a#\x01#Eg\x89\x0a", + ret => 1, +}, +{ + type => "tfn", + name => "hexDecode", + input => "01234567890az", + output => "\x01#Eg\x89\x0a", + ret => 1, +}, +{ + type => "tfn", + name => "hexDecode", + input => "01234567890a0", + output => "\x01#Eg\x89\x0a", + ret => 1, +}, diff --git a/apache2/t/tfn/hexEncode.t b/apache2/t/tfn/hexEncode.t new file mode 100644 index 00000000..16dc43d6 --- /dev/null +++ b/apache2/t/tfn/hexEncode.t @@ -0,0 +1,26 @@ +### Empty +{ + type => "tfn", + name => "hexEncode", + input => "", + output => "", + ret => 1, +}, + +### Basic +{ + type => "tfn", + name => "hexEncode", + input => "TestCase", + output => "5465737443617365", + ret => 1, +}, + +### Basic w/NULL +{ + type => "tfn", + name => "hexEncode", + input => "Test\0Case", + output => "546573740043617365", + ret => 1, +}, diff --git a/apache2/t/tfn/htmlEntityDecode.t b/apache2/t/tfn/htmlEntityDecode.t new file mode 100644 index 00000000..201bd6be --- /dev/null +++ b/apache2/t/tfn/htmlEntityDecode.t @@ -0,0 +1,58 @@ +### Empty +{ + type => "tfn", + name => "htmlEntityDecode", + input => "", + output => "", + ret => 0, +}, + +### Nothing +{ + type => "tfn", + name => "htmlEntityDecode", + input => "TestCase", + output => "TestCase", + ret => 0, +}, +{ + type => "tfn", + name => "htmlEntityDecode", + input => "Test\0Case", + output => "Test\0Case", + ret => 0, +}, + +### Valid +# With ; +{ + type => "tfn", + name => "htmlEntityDecode", + input => "�� � \0d"&<> ", + output => "\0\0\x20\x20\0\x20\0\x64\"&<>\xa0", + ret => 1, +}, +# Without ; +{ + type => "tfn", + name => "htmlEntityDecode", + input => "�� � \0d"&<> ", + output => "\0\0\x20\x20\0\x20\0\x64\"&<>\xa0", + ret => 1, +}, + +### Invalid +{ + type => "tfn", + name => "htmlEntityDecode", + input => "&#xg;&#Xg;&#xg0;g;&#a;\0&#a2;a&#a00;a0; a;&foo;", + output => "&#xg;&#Xg;&#xg0;\x02g;&#a;\0&#a2;\x03a&#a00;\x01a0;\x0aa;&foo;", + ret => 1, +}, +{ + type => "tfn", + name => "htmlEntityDecode", + input => "&#xg&#Xg&#xg0g&#a\0&#a2a&#a00a0 a&foo", + output => "&#xg&#Xg&#xg0\x02g&#a\0&#a2\x03a&#a00\x01a0\x0aa&foo", + ret => 1, +}, diff --git a/apache2/t/tfn/jsDecode.t b/apache2/t/tfn/jsDecode.t new file mode 100644 index 00000000..9774b7ff --- /dev/null +++ b/apache2/t/tfn/jsDecode.t @@ -0,0 +1,129 @@ +### Empty +{ + type => "tfn", + name => "jsDecode", + input => "", + output => "", + ret => 0, +}, + +### Nothing +{ + type => "tfn", + name => "jsDecode", + input => "TestCase", + output => "TestCase", + ret => 0, +}, +{ + type => "tfn", + name => "jsDecode", + input => "Test\0Case", + output => "Test\0Case", + ret => 0, +}, + +### Valid Sequences +{ + type => "tfn", + name => "jsDecode", + input => "\\a\\b\\f\\n\\r\\t\\v\\?\\'\\\"\\0\\12\\123\\x00\\xff\\u0021\\uff01", + output => "\a\b\f\x0a\x0d\t\x0b?'\"\x00\x0a\x53\x00\xff\x21\x21", + ret => 1, +}, +{ + type => "tfn", + name => "jsDecode", + input => "\\a\\b\\f\\n\\r\\t\\v\0\\?\\'\\\"\\0\\12\\123\\x00\\xff\\u0021\\uff01", + output => "\a\b\f\x0a\x0d\t\x0b\0?'\"\x00\x0a\x53\x00\xff\x21\x21", + ret => 1, +}, + +### Invalid Sequences +# \8 and \9 are not octal +# \666 is \66 + '6' (JS does not allow the overflow as C does) +# \u00ag, \u00ga, \u0zaa, \uz0aa are not hex +# \xag and \xga are not hex, +# \0123 is \012 + '3' +{ + type => "tfn", + name => "jsDecode", + input => "\\8\\9\\666\\u00ag\\u00ga\\u0zaa\\uz0aa\\xag\\xga\\0123\\u00a", + output => "89\x366u00agu00gau0zaauz0aaxagxga\x0a3u00a", + ret => 1, +}, + +# \x, \x0 lack enough hex digits +{ + type => "tfn", + name => "jsDecode", + input => "\\x", + output => "x", + ret => 1, +}, +{ + type => "tfn", + name => "jsDecode", + input => "\\x\\x0", + output => "xx0", + ret => 1, +}, +{ + type => "tfn", + name => "jsDecode", + input => "\\x\\x0\0", + output => "xx0\0", + ret => 1, +}, +# \u, \u0 \u01, \u012 lack enough hex digits +{ + type => "tfn", + name => "jsDecode", + input => "\\u", + output => "u", + ret => 1, +}, +{ + type => "tfn", + name => "jsDecode", + input => "\\u\\u0", + output => "uu0", + ret => 1, +}, +{ + type => "tfn", + name => "jsDecode", + input => "\\u\\u0\\u01", + output => "uu0u01", + ret => 1, +}, +{ + type => "tfn", + name => "jsDecode", + input => "\\u\\u0\\u01\\u012", + output => "uu0u01u012", + ret => 1, +}, +{ + type => "tfn", + name => "jsDecode", + input => "\\u\\u0\\u01\\u012\0", + output => "uu0u01u012\0", + ret => 1, +}, +# A forward slash with nothing after +{ + type => "tfn", + name => "jsDecode", + input => "\\", + output => "\\", + ret => 0, +}, +# A forward slash with NUL after +{ + type => "tfn", + name => "jsDecode", + input => "\\\0", + output => "\0", + ret => 1, +}, diff --git a/apache2/t/tfn/length.t b/apache2/t/tfn/length.t new file mode 100644 index 00000000..33150d6d --- /dev/null +++ b/apache2/t/tfn/length.t @@ -0,0 +1,43 @@ +### Empty +{ + type => "tfn", + name => "length", + input => "", + output => "0", + ret => 1, +}, + + +### Basic normal and large +{ + type => "tfn", + name => "length", + input => "0123456789abcdef", + output => "16", + ret => 1, +}, +{ + type => "tfn", + name => "length", + input => ('x' x 8192), + output => "8192", + ret => 1, +}, + +### With TAB +{ + type => "tfn", + name => "length", + input => "0123456789\tabcdef", + output => "17", + ret => 1, +}, + +### With NUL +{ + type => "tfn", + name => "length", + input => "Test\0Case", + output => "9", + ret => 1, +}, diff --git a/apache2/t/tfn/lowercase.t b/apache2/t/tfn/lowercase.t new file mode 100644 index 00000000..bd55f4aa --- /dev/null +++ b/apache2/t/tfn/lowercase.t @@ -0,0 +1,40 @@ +### Empty +{ + type => "tfn", + name => "lowercase", + input => "", + output => "", + ret => 0, +}, + +### Nothing +{ + type => "tfn", + name => "lowercase", + input => "testcase", + output => "testcase", + ret => 0, +}, +{ + type => "tfn", + name => "lowercase", + input => "test\0case", + output => "test\0case", + ret => 0, +}, + +### Basic +{ + type => "tfn", + name => "lowercase", + input => "TestCase", + output => "testcase", + ret => 1, +}, +{ + type => "tfn", + name => "lowercase", + input => "Test\0Case", + output => "test\0case", + ret => 1, +}, diff --git a/apache2/t/tfn/md5.t b/apache2/t/tfn/md5.t new file mode 100644 index 00000000..f9956d67 --- /dev/null +++ b/apache2/t/tfn/md5.t @@ -0,0 +1,26 @@ +### Empty +{ + type => "tfn", + name => "md5", + input => "", + output => "\xd4\x1d\x8c\xd9\x8f\x00\xb2\x04\xe9\x80\x09\x98\xec\xf8\x42\x7e", + ret => 1, +}, + +### Basic +{ + type => "tfn", + name => "md5", + input => "TestCase", + output => "\xc9\xab\xa2\xc3\xe6\x01\x26\x16\x9e\x80\xe9\xa2\x6b\xa2\x73\xc1", + ret => 1, +}, + +### Binary w/NUL +{ + type => "tfn", + name => "md5", + input => "\x00\x01\x02\x03\x04\x05\x06\x07\x08", + output => "\xa6\xe7\xd3\xb4\x6f\xdf\xaf\x0b\xde\x2a\x1f\x83\x2a\x00\xd2\xde", + ret => 1, +}, diff --git a/apache2/t/tfn/normalisePath.t b/apache2/t/tfn/normalisePath.t new file mode 100644 index 00000000..851e6679 --- /dev/null +++ b/apache2/t/tfn/normalisePath.t @@ -0,0 +1,98 @@ +### Empty +{ + type => "tfn", + name => "normalisePath", + input => "", + output => "", + ret => 0, +}, + +### Nothing +{ + type => "tfn", + name => "normalisePath", + input => "/foo/bar/baz", + output => "/foo/bar/baz", + ret => 0, +}, +{ + type => "tfn", + name => "normalisePath", + input => "/foo/bar\0/baz", + output => "/foo/bar\0/baz", + ret => 0, +}, + +### Basic +{ + type => "tfn", + name => "normalisePath", + input => "/foo/bar//baz", + output => "/foo/bar/baz", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePath", + input => "/foo/bar baz/././././boo//eek/././../whoa", + output => "/foo/bar baz/boo/whoa", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePath", + input => "./foo/bar baz/././././boo//eek/././../whoa", + output => "./foo/bar baz/boo/whoa", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePath", + input => "/./foo/bar baz/././././boo//eek/././../whoa", + output => "/foo/bar baz/boo/whoa", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePath", + input => "//foo/bar baz/././././boo//eek/././../whoa", + output => "/foo/bar baz/boo/whoa", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePath", + input => "//foo/bar baz/././././boo//eek/././../whoa/./", + output => "/foo/bar baz/boo/whoa/", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePath", + input => "/./foo/bar baz/././././boo//eek/././../whoa//", + output => "/foo/bar baz/boo/whoa/", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePath", + input => "/./../../../../../../../../etc/passwd", + output => "/etc/passwd", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePath", + input => "/./.././../../../../../../../etc/../etc/./passwd", + output => "/etc/passwd", + ret => 1, +}, + +### With NUL +{ + type => "tfn", + name => "normalisePath", + input => "/./.././../../../../../../../\0/../etc/./passwd", + output => "/etc/passwd", + ret => 1, +}, diff --git a/apache2/t/tfn/normalisePathWin.t b/apache2/t/tfn/normalisePathWin.t new file mode 100644 index 00000000..a3845bf4 --- /dev/null +++ b/apache2/t/tfn/normalisePathWin.t @@ -0,0 +1,98 @@ +### Empty +{ + type => "tfn", + name => "normalisePathWin", + input => "", + output => "", + ret => 0, +}, + +### Nothing but switch slashes +{ + type => "tfn", + name => "normalisePathWin", + input => "\\foo\\bar\\baz", + output => "/foo/bar/baz", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePathWin", + input => "\\foo\\bar\0\\baz", + output => "/foo/bar\0/baz", + ret => 1, +}, + +### Basics +{ + type => "tfn", + name => "normalisePathWin", + input => "\\foo\\bar\\\\baz", + output => "/foo/bar/baz", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePathWin", + input => "\\foo\\bar baz\\.\\.\\.\\.\\boo\\\\eek\\.\\.\\..\\whoa", + output => "/foo/bar baz/boo/whoa", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePathWin", + input => ".\\foo\\bar baz\\.\\.\\.\\.\\boo\\\\eek\\.\\.\\..\\whoa", + output => "./foo/bar baz/boo/whoa", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePathWin", + input => "\\.\\foo\\bar baz\\.\\.\\.\\.\\boo\\\\eek\\.\\.\\..\\whoa", + output => "/foo/bar baz/boo/whoa", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePathWin", + input => "\\\\foo\\bar baz\\.\\.\\.\\.\\boo\\\\eek\\.\\.\\..\\whoa", + output => "/foo/bar baz/boo/whoa", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePathWin", + input => "\\\\foo\\bar baz\\.\\.\\.\\.\\boo\\\\eek\\.\\.\\..\\whoa\\.\\", + output => "/foo/bar baz/boo/whoa/", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePathWin", + input => "\\.\\foo\\bar baz\\.\\.\\.\\.\\boo\\\\eek\\.\\.\\..\\whoa\\\\", + output => "/foo/bar baz/boo/whoa/", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePathWin", + input => "\\.\\..\\..\\..\\..\\..\\..\\..\\..\\etc\\passwd", + output => "/etc/passwd", + ret => 1, +}, +{ + type => "tfn", + name => "normalisePathWin", + input => "\\.\\..\\.\\..\\..\\..\\..\\..\\..\\..\\etc\\..\\etc\\.\\passwd", + output => "/etc/passwd", + ret => 1, +}, + +### With NUL +{ + type => "tfn", + name => "normalisePathWin", + input => "\\.\\..\\.\\..\\..\\..\\..\\..\\..\\..\\\0\\..\\etc\\.\\passwd", + output => "/etc/passwd", + ret => 1, +}, diff --git a/apache2/t/tfn/removeNulls.t b/apache2/t/tfn/removeNulls.t new file mode 100644 index 00000000..c538b93d --- /dev/null +++ b/apache2/t/tfn/removeNulls.t @@ -0,0 +1,62 @@ +### Empty +{ + type => "tfn", + name => "removeNulls", + input => "", + output => "", + ret => 0, +}, + +### Nothing +{ + type => "tfn", + name => "removeNulls", + input => "TestCase", + output => "TestCase", + ret => 0, +}, +{ + type => "tfn", + name => "removeNulls", + input => "Test\x01Case", + output => "Test\x01Case", + ret => 0, +}, + + +### Basics +{ + type => "tfn", + name => "removeNulls", + input => "\0TestCase", + output => "TestCase", + ret => 1, +}, +{ + type => "tfn", + name => "removeNulls", + input => "Test\0Case", + output => "TestCase", + ret => 1, +}, +{ + type => "tfn", + name => "removeNulls", + input => "Test\0\0Case", + output => "TestCase", + ret => 1, +}, +{ + type => "tfn", + name => "removeNulls", + input => "TestCase\0", + output => "TestCase", + ret => 1, +}, +{ + type => "tfn", + name => "removeNulls", + input => "\0Test\0Case\0", + output => "TestCase", + ret => 1, +}, diff --git a/apache2/t/tfn/removeWhitespace.t b/apache2/t/tfn/removeWhitespace.t new file mode 100644 index 00000000..69cecae0 --- /dev/null +++ b/apache2/t/tfn/removeWhitespace.t @@ -0,0 +1,43 @@ +### Empty +{ + type => "tfn", + name => "removeWhitespace", + input => "", + output => "", + ret => 0, +}, + +### Nothing +{ + type => "tfn", + name => "removeWhitespace", + input => "TestCase", + output => "TestCase", + ret => 0, +}, +{ + type => "tfn", + name => "removeWhitespace", + input => "Test\0Case", + output => "Test\0Case", + ret => 0, +}, + + +### Remove space/tab +{ + type => "tfn", + name => "removeWhitespace", + input => " Test \t Case ", + output => "TestCase", + ret => 1, +}, + +### Pretty much everything in one +{ + type => "tfn", + name => "removeWhitespace", + input => "This is a test case with a tab \t, vtab \x0b, newline \x0a, return \x0d, formfeed \f, and a NUL\0 in it with a CRLF at the end.\x0d\x0a", + output => "Thisisatestcasewithatab,vtab,newline,return,formfeed,andaNUL\0initwithaCRLFattheend.", + ret => 1, +}, diff --git a/apache2/t/tfn/replaceComments.t b/apache2/t/tfn/replaceComments.t new file mode 100644 index 00000000..332436e4 --- /dev/null +++ b/apache2/t/tfn/replaceComments.t @@ -0,0 +1,155 @@ +### Empty +{ + type => "tfn", + name => "replaceComments", + input => "", + output => "", + ret => 0, +}, + +### Nothing +{ + type => "tfn", + name => "replaceComments", + input => "TestCase", + output => "TestCase", + ret => 0, +}, +{ + type => "tfn", + name => "replaceComments", + input => "Test\0Case", + output => "Test\0Case", + ret => 0, +}, + + +### Basics +{ + type => "tfn", + name => "replaceComments", + input => "/* TestCase */", + output => " ", + ret => 1, +}, +{ + type => "tfn", + name => "replaceComments", + input => "/*TestCase*/", + output => " ", + ret => 1, +}, +{ + type => "tfn", + name => "replaceComments", + input => "/* TestCase*/", + output => " ", + ret => 1, +}, +{ + type => "tfn", + name => "replaceComments", + input => "/*TestCase */", + output => " ", + ret => 1, +}, +{ + type => "tfn", + name => "replaceComments", + input => "Before/* TestCase */After", + output => "Before After", + ret => 1, +}, +{ + type => "tfn", + name => "replaceComments", + input => "Before /* TestCase */ After", + output => "Before After", + ret => 1, +}, +{ + type => "tfn", + name => "replaceComments", + input => "/* Test\nCase */", + output => " ", + ret => 1, +}, +{ + type => "tfn", + name => "replaceComments", + input => "/* Test\x0d\x0aCase */", + output => " ", + ret => 1, +}, +{ + type => "tfn", + name => "replaceComments", + input => "/* Test\x0aCase */", + output => " ", + ret => 1, +}, +{ + type => "tfn", + name => "replaceComments", + input => "/* Test\x0dCase */", + output => " ", + ret => 1, +}, + +### Broken Comments +{ + type => "tfn", + name => "replaceComments", + input => "Before/* Test\x0d\x0aCase ", + output => "Before ", + ret => 1, +}, +{ + type => "tfn", + name => "replaceComments", + input => "Before /* Test\x0aCase ", + output => "Before ", + ret => 1, +}, +{ + type => "tfn", + name => "replaceComments", + input => "Before/* Test\x0d\x0aCase ", + output => "Before ", + ret => 1, +}, +{ + type => "tfn", + name => "replaceComments", + input => "Before /* Test\x0aCase ", + output => "Before ", + ret => 1, +}, +{ + type => "tfn", + name => "replaceComments", + input => "Test\x0d\x0aCase */After", + output => "Test\x0d\x0aCase */After", + ret => 0, +}, +{ + type => "tfn", + name => "replaceComments", + input => "Test\x0aCase */ After", + output => "Test\x0aCase */ After", + ret => 0, +}, +{ + type => "tfn", + name => "replaceComments", + input => "Test\x0d\x0aCase */After", + output => "Test\x0d\x0aCase */After", + ret => 0, +}, +{ + type => "tfn", + name => "replaceComments", + input => "Test\x0aCase */ After", + output => "Test\x0aCase */ After", + ret => 0, +}, diff --git a/apache2/t/tfn/replaceNulls.t b/apache2/t/tfn/replaceNulls.t new file mode 100644 index 00000000..3be063c1 --- /dev/null +++ b/apache2/t/tfn/replaceNulls.t @@ -0,0 +1,55 @@ +### Empty +{ + type => "tfn", + name => "replaceNulls", + input => "", + output => "", + ret => 0, +}, + +### Nothing +{ + type => "tfn", + name => "replaceNulls", + input => "TestCase", + output => "TestCase", + ret => 0, +}, + + +### Basics +{ + type => "tfn", + name => "replaceNulls", + input => "\0TestCase", + output => " TestCase", + ret => 1, +}, +{ + type => "tfn", + name => "replaceNulls", + input => "Test\0Case", + output => "Test Case", + ret => 1, +}, +{ + type => "tfn", + name => "replaceNulls", + input => "Test\0\0Case", + output => "Test Case", + ret => 1, +}, +{ + type => "tfn", + name => "replaceNulls", + input => "TestCase\0", + output => "TestCase ", + ret => 1, +}, +{ + type => "tfn", + name => "replaceNulls", + input => "\0Test\0Case\0", + output => " Test Case ", + ret => 1, +}, diff --git a/apache2/t/tfn/sha1.t b/apache2/t/tfn/sha1.t new file mode 100644 index 00000000..84d38de3 --- /dev/null +++ b/apache2/t/tfn/sha1.t @@ -0,0 +1,26 @@ +### Empty +{ + type => "tfn", + name => "sha1", + input => "", + output => "\xda\x39\xa3\xee\x5e\x6b\x4b\x0d\x32\x55\xbf\xef\x95\x60\x18\x90\xaf\xd8\x07\x09", + ret => 1, +}, + +### Basic +{ + type => "tfn", + name => "sha1", + input => "TestCase", + output => "\xa7\x0c\xe3\x83\x89\xe3\x18\xbd\x2b\xe1\x8a\x01\x11\xc6\xdc\x76\xbd\x2c\xd9\xed", + ret => 1, +}, + +### Binary w/NUL +{ + type => "tfn", + name => "sha1", + input => "\x00\x01\x02\x03\x04\x05\x06\x07\x08", + output => "\x63\xbf\x60\xc7\x10\x5a\x07\xa2\xb1\x25\xbb\xf8\x9e\x61\xab\xda\xbc\x69\x78\xc2", + ret => 1, +}, diff --git a/apache2/t/tfn/trim.t b/apache2/t/tfn/trim.t new file mode 100644 index 00000000..7ac3f7f6 --- /dev/null +++ b/apache2/t/tfn/trim.t @@ -0,0 +1,68 @@ +### Empty +{ + type => "tfn", + name => "trim", + input => "", + output => "", + ret => 0, +}, + +### Nothing +{ + type => "tfn", + name => "trim", + input => "TestCase", + output => "TestCase", + ret => 0, +}, +{ + type => "tfn", + name => "trim", + input => "Test\0Case", + output => "Test\0Case", + ret => 0, +}, + +### Basics +{ + type => "tfn", + name => "trim", + input => " TestCase", + output => "TestCase", + ret => 1, +}, +{ + type => "tfn", + name => "trim", + input => "TestCase ", + output => "TestCase", + ret => 1, +}, +{ + type => "tfn", + name => "trim", + input => " TestCase ", + output => "TestCase", + ret => 1, +}, +{ + type => "tfn", + name => "trim", + input => " Test Case ", + output => "Test Case", + ret => 1, +}, +{ + type => "tfn", + name => "trim", + input => " Test \0 Case ", + output => "Test \0 Case", + ret => 1, +}, +{ + type => "tfn", + name => "trim", + input => " Test \0 Case \r\n ", + output => "Test \0 Case", + ret => 1, +}, diff --git a/apache2/t/tfn/trimLeft.t b/apache2/t/tfn/trimLeft.t new file mode 100644 index 00000000..e2c2951a --- /dev/null +++ b/apache2/t/tfn/trimLeft.t @@ -0,0 +1,69 @@ +### Empty +{ + type => "tfn", + name => "trimLeft", + input => "", + output => "", + ret => 0, +}, + +### Nothing +{ + type => "tfn", + name => "trimLeft", + input => "TestCase", + output => "TestCase", + ret => 0, +}, +{ + type => "tfn", + name => "trimLeft", + input => "Test\0Case", + output => "Test\0Case", + ret => 0, +}, +{ + type => "tfn", + name => "trimLeft", + input => "TestCase ", + output => "TestCase ", + ret => 0, +}, + + +### Basics +{ + type => "tfn", + name => "trimLeft", + input => " TestCase", + output => "TestCase", + ret => 1, +}, +{ + type => "tfn", + name => "trimLeft", + input => " TestCase ", + output => "TestCase ", + ret => 1, +}, +{ + type => "tfn", + name => "trimLeft", + input => " Test Case ", + output => "Test Case ", + ret => 1, +}, +{ + type => "tfn", + name => "trimLeft", + input => " Test \0 Case ", + output => "Test \0 Case ", + ret => 1, +}, +{ + type => "tfn", + name => "trimLeft", + input => " Test \0 Case \r\n ", + output => "Test \0 Case \r\n ", + ret => 1, +}, diff --git a/apache2/t/tfn/trimRight.t b/apache2/t/tfn/trimRight.t new file mode 100644 index 00000000..f356333d --- /dev/null +++ b/apache2/t/tfn/trimRight.t @@ -0,0 +1,68 @@ +### Empty +{ + type => "tfn", + name => "trimRight", + input => "", + output => "", + ret => 0, +}, + +### Nothing +{ + type => "tfn", + name => "trimRight", + input => "TestCase", + output => "TestCase", + ret => 0, +}, +{ + type => "tfn", + name => "trimRight", + input => "Test\0Case", + output => "Test\0Case", + ret => 0, +}, +{ + type => "tfn", + name => "trimRight", + input => " TestCase", + output => " TestCase", + ret => 0, +}, + +### Basics +{ + type => "tfn", + name => "trimRight", + input => "TestCase ", + output => "TestCase", + ret => 1, +}, +{ + type => "tfn", + name => "trimRight", + input => " TestCase ", + output => " TestCase", + ret => 1, +}, +{ + type => "tfn", + name => "trimRight", + input => " Test Case ", + output => " Test Case", + ret => 1, +}, +{ + type => "tfn", + name => "trimRight", + input => " Test \0 Case ", + output => " Test \0 Case", + ret => 1, +}, +{ + type => "tfn", + name => "trimRight", + input => " Test \0 Case \r\n ", + output => " Test \0 Case", + ret => 1, +}, diff --git a/apache2/t/tfn/urlDecode.t b/apache2/t/tfn/urlDecode.t new file mode 100644 index 00000000..b0ea0072 --- /dev/null +++ b/apache2/t/tfn/urlDecode.t @@ -0,0 +1,143 @@ +### Empty +{ + type => "tfn", + name => "urlDecode", + input => "", + output => "", + ret => 0, +}, + +### Nothing +{ + type => "tfn", + name => "urlDecode", + input => "TestCase", + output => "TestCase", + ret => 0, +}, +{ + type => "tfn", + name => "urlDecode", + input => "Test\0Case", + output => "Test\0Case", + ret => 0, +}, + +### Valid +{ + type => "tfn", + name => "urlDecode", + input => "+%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%20%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d%2e%2f%30%31%32%33%34%35%36%37%38%39%3a%3b%3c%3d%3e%3f%40%41%42%43%44%45%46%47%48%49%4a%4b%4c%4d%4e%4f%50%51%52%53%54%55%56%57%58%59%5a%5b%5c%5d%5e%5f%60%61%62%63%64%65%66%67%68%69%6a%6b%6c%6d%6e%6f%70%71%72%73%74%75%76%77%78%79%7a%7b%7c%7d%7e%7f%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff", + output => " \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f \x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecode", + input => "Test+Case", + output => "Test Case", + ret => 1, +}, + + +### Partial Invalid +{ + type => "tfn", + name => "urlDecode", + input => "%+", + output => "% ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecode", + input => "%%20", + output => "% ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecode", + input => "%0g%20", + output => "%0g ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecode", + input => "%0%20", + output => "%0 ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecode", + input => "%g0%20", + output => "%g0 ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecode", + input => "%g%20", + output => "%g ", + ret => 1, +}, + +### Invalid +{ + type => "tfn", + name => "urlDecode", + input => "%0%1%2%3%4%5%6%7%8%9%0%a%b%c%d%e%f", + output => "%0%1%2%3%4%5%6%7%8%9%0%a%b%c%d%e%f", + ret => 0, +}, +{ + type => "tfn", + name => "urlDecode", + input => "%g0%g1%g2%g3%g4%g5%g6%g7%g8%g9%g0%ga%gb%gc%gd%ge%gf", + output => "%g0%g1%g2%g3%g4%g5%g6%g7%g8%g9%g0%ga%gb%gc%gd%ge%gf", + ret => 0, +}, +{ + type => "tfn", + name => "urlDecode", + input => "%0g%1g%2g%3g%4g%5g%6g%7g%8g%9g%0g%ag%bg%cg%dg%eg%fg", + output => "%0g%1g%2g%3g%4g%5g%6g%7g%8g%9g%0g%ag%bg%cg%dg%eg%fg", + ret => 0, +}, +{ + type => "tfn", + name => "urlDecode", + input => "%", + output => "%", + ret => 0, +}, +{ + type => "tfn", + name => "urlDecode", + input => "%0", + output => "%0", + ret => 0, +}, +{ + type => "tfn", + name => "urlDecode", + input => "%%", + output => "%%", + ret => 0, +}, +{ + type => "tfn", + name => "urlDecode", + input => "%0g", + output => "%0g", + ret => 0, +}, +{ + type => "tfn", + name => "urlDecode", + input => "%gg", + output => "%gg", + ret => 0, +}, diff --git a/apache2/t/tfn/urlDecodeUni.t b/apache2/t/tfn/urlDecodeUni.t new file mode 100644 index 00000000..798542ca --- /dev/null +++ b/apache2/t/tfn/urlDecodeUni.t @@ -0,0 +1,235 @@ +### Empty +{ + type => "tfn", + name => "urlDecodeUni", + input => "", + output => "", + ret => 0, +}, + +### Nothing +{ + type => "tfn", + name => "urlDecodeUni", + input => "TestCase", + output => "TestCase", + ret => 0, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "Test\0Case", + output => "Test\0Case", + ret => 0, +}, + +### Valid +{ + type => "tfn", + name => "urlDecodeUni", + input => "+%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%20%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d%2e%2f%30%31%32%33%34%35%36%37%38%39%3a%3b%3c%3d%3e%3f%40%41%42%43%44%45%46%47%48%49%4a%4b%4c%4d%4e%4f%50%51%52%53%54%55%56%57%58%59%5a%5b%5c%5d%5e%5f%60%61%62%63%64%65%66%67%68%69%6a%6b%6c%6d%6e%6f%70%71%72%73%74%75%76%77%78%79%7a%7b%7c%7d%7e%7f%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff", + output => " \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f \x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "Test+Case", + output => "Test Case", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "+%u0000%u0001%u0002%u0003%u0004%u0005%u0006%u0007%u0008%u0009%u000a%u000b%u000c%u000d%u000e%u000f%u0010%u0011%u0012%u0013%u0014%u0015%u0016%u0017%u0018%u0019%u001a%u001b%u001c%u001d%u001e%u001f%u0020%u0021%u0022%u0023%u0024%u0025%u0026%u0027%u0028%u0029%u002a%u002b%u002c%u002d%u002e%u002f%u0030%u0031%u0032%u0033%u0034%u0035%u0036%u0037%u0038%u0039%u003a%u003b%u003c%u003d%u003e%u003f%u0040%u0041%u0042%u0043%u0044%u0045%u0046%u0047%u0048%u0049%u004a%u004b%u004c%u004d%u004e%u004f%u0050%u0051%u0052%u0053%u0054%u0055%u0056%u0057%u0058%u0059%u005a%u005b%u005c%u005d%u005e%u005f%u0060%u0061%u0062%u0063%u0064%u0065%u0066%u0067%u0068%u0069%u006a%u006b%u006c%u006d%u006e%u006f%u0070%u0071%u0072%u0073%u0074%u0075%u0076%u0077%u0078%u0079%u007a%u007b%u007c%u007d%u007e%u007f%u0080%u0081%u0082%u0083%u0084%u0085%u0086%u0087%u0088%u0089%u008a%u008b%u008c%u008d%u008e%u008f%u0090%u0091%u0092%u0093%u0094%u0095%u0096%u0097%u0098%u0099%u009a%u009b%u009c%u009d%u009e%u009f%u00a0%u00a1%u00a2%u00a3%u00a4%u00a5%u00a6%u00a7%u00a8%u00a9%u00aa%u00ab%u00ac%u00ad%u00ae%u00af%u00b0%u00b1%u00b2%u00b3%u00b4%u00b5%u00b6%u00b7%u00b8%u00b9%u00ba%u00bb%u00bc%u00bd%u00be%u00bf%u00c0%u00c1%u00c2%u00c3%u00c4%u00c5%u00c6%u00c7%u00c8%u00c9%u00ca%u00cb%u00cc%u00cd%u00ce%u00cf%u00d0%u00d1%u00d2%u00d3%u00d4%u00d5%u00d6%u00d7%u00d8%u00d9%u00da%u00db%u00dc%u00dd%u00de%u00df%u00e0%u00e1%u00e2%u00e3%u00e4%u00e5%u00e6%u00e7%u00e8%u00e9%u00ea%u00eb%u00ec%u00ed%u00ee%u00ef%u00f0%u00f1%u00f2%u00f3%u00f4%u00f5%u00f6%u00f7%u00f8%u00f9%u00fa%u00fb%u00fc%u00fd%u00fe%u00ff", + output => " \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f \x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "+%u1100%u1101%u1102%u1103%u1104%u1105%u1106%u1107%u1108%u1109%u110a%u110b%u110c%u110d%u110e%u110f%u1110%u1111%u1112%u1113%u1114%u1115%u1116%u1117%u1118%u1119%u111a%u111b%u111c%u111d%u111e%u111f%u1120%u1121%u1122%u1123%u1124%u1125%u1126%u1127%u1128%u1129%u112a%u112b%u112c%u112d%u112e%u112f%u1130%u1131%u1132%u1133%u1134%u1135%u1136%u1137%u1138%u1139%u113a%u113b%u113c%u113d%u113e%u113f%u1140%u1141%u1142%u1143%u1144%u1145%u1146%u1147%u1148%u1149%u114a%u114b%u114c%u114d%u114e%u114f%u1150%u1151%u1152%u1153%u1154%u1155%u1156%u1157%u1158%u1159%u115a%u115b%u115c%u115d%u115e%u115f%u1160%u1161%u1162%u1163%u1164%u1165%u1166%u1167%u1168%u1169%u116a%u116b%u116c%u116d%u116e%u116f%u1170%u1171%u1172%u1173%u1174%u1175%u1176%u1177%u1178%u1179%u117a%u117b%u117c%u117d%u117e%u117f%u1180%u1181%u1182%u1183%u1184%u1185%u1186%u1187%u1188%u1189%u118a%u118b%u118c%u118d%u118e%u118f%u1190%u1191%u1192%u1193%u1194%u1195%u1196%u1197%u1198%u1199%u119a%u119b%u119c%u119d%u119e%u119f%u11a0%u11a1%u11a2%u11a3%u11a4%u11a5%u11a6%u11a7%u11a8%u11a9%u11aa%u11ab%u11ac%u11ad%u11ae%u11af%u11b0%u11b1%u11b2%u11b3%u11b4%u11b5%u11b6%u11b7%u11b8%u11b9%u11ba%u11bb%u11bc%u11bd%u11be%u11bf%u11c0%u11c1%u11c2%u11c3%u11c4%u11c5%u11c6%u11c7%u11c8%u11c9%u11ca%u11cb%u11cc%u11cd%u11ce%u11cf%u11d0%u11d1%u11d2%u11d3%u11d4%u11d5%u11d6%u11d7%u11d8%u11d9%u11da%u11db%u11dc%u11dd%u11de%u11df%u11e0%u11e1%u11e2%u11e3%u11e4%u11e5%u11e6%u11e7%u11e8%u11e9%u11ea%u11eb%u11ec%u11ed%u11ee%u11ef%u11f0%u11f1%u11f2%u11f3%u11f4%u11f5%u11f6%u11f7%u11f8%u11f9%u11fa%u11fb%u11fc%u11fd%u11fe%u11ff", + output => " \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f \x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", + ret => 1, +}, +# Full Width ASCII +{ + type => "tfn", + name => "urlDecodeUni", + input => "%uff01%uff02%uff03%uff04%uff05%uff06%uff07%uff08%uff09%uff0a%uff0b%uff0c%uff0d%uff0e%uff0f%uff10%uff11%uff12%uff13%uff14%uff15%uff16%uff17%uff18%uff19%uff1a%uff1b%uff1c%uff1d%uff1e%uff1f%uff20%uff21%uff22%uff23%uff24%uff25%uff26%uff27%uff28%uff29%uff2a%uff2b%uff2c%uff2d%uff2e%uff2f%uff30%uff31%uff32%uff33%uff34%uff35%uff36%uff37%uff38%uff39%uff3a%uff3b%uff3c%uff3d%uff3e%uff3f%uff40%uff41%uff42%uff43%uff44%uff45%uff46%uff47%uff48%uff49%uff4a%uff4b%uff4c%uff4d%uff4e%uff4f%uff50%uff51%uff52%uff53%uff54%uff55%uff56%uff57%uff58%uff59%uff5a%uff5b%uff5c%uff5d%uff5e", + output => "\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e", + ret => 1, +}, +# Invalid Full Width ASCII +{ + type => "tfn", + name => "urlDecodeUni", + input => "%uff00%uff7f%uff80%uff81%uff82%uff83%uff84%uff85%uff86%uff87%uff88%uff89%uff8a%uff8b%uff8c%uff8d%uff8e%uff8f%uff90%uff91%uff92%uff93%uff94%uff95%uff96%uff97%uff98%uff99%uff9a%uff9b%uff9c%uff9d%uff9e%uff9f%uffa0%uffa1%uffa2%uffa3%uffa4%uffa5%uffa6%uffa7%uffa8%uffa9%uffaa%uffab%uffac%uffad%uffae%uffaf%uffb0%uffb1%uffb2%uffb3%uffb4%uffb5%uffb6%uffb7%uffb8%uffb9%uffba%uffbb%uffbc%uffbd%uffbe%uffbf%uffc0%uffc1%uffc2%uffc3%uffc4%uffc5%uffc6%uffc7%uffc8%uffc9%uffca%uffcb%uffcc%uffcd%uffce%uffcf%uffd0%uffd1%uffd2%uffd3%uffd4%uffd5%uffd6%uffd7%uffd8%uffd9%uffda%uffdb%uffdc%uffdd%uffde%uffdf%uffe0%uffe1%uffe2%uffe3%uffe4%uffe5%uffe6%uffe7%uffe8%uffe9%uffea%uffeb%uffec%uffed%uffee%uffef%ufff0%ufff1%ufff2%ufff3%ufff4%ufff5%ufff6%ufff7%ufff8%ufff9%ufffa%ufffb%ufffc%ufffd%ufffe%uffff", + output => "\x00\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", + ret => 1, +}, + +### Partial Invalid +{ + type => "tfn", + name => "urlDecodeUni", + input => "%+", + output => "% ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%%20", + output => "% ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%0g%20", + output => "%0g ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%0%20", + output => "%0 ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%g0%20", + output => "%g0 ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%g%20", + output => "%g ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%%u0020", + output => "% ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%0g%u0020", + output => "%0g ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%0%u0020", + output => "%0 ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%g0%u0020", + output => "%g0 ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%u%u0020", + output => "%u ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%u0%u0020", + output => "%u0 ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%u00%u0020", + output => "%u00 ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%u000%u0020", + output => "%u000 ", + ret => 1, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%u000g%u0020", + output => "%u000g ", + ret => 1, +}, + +### Invalid +{ + type => "tfn", + name => "urlDecodeUni", + input => "%0%1%2%3%4%5%6%7%8%9%0%a%b%c%d%e%f", + output => "%0%1%2%3%4%5%6%7%8%9%0%a%b%c%d%e%f", + ret => 0, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%g0%g1%g2%g3%g4%g5%g6%g7%g8%g9%g0%ga%gb%gc%gd%ge%gf", + output => "%g0%g1%g2%g3%g4%g5%g6%g7%g8%g9%g0%ga%gb%gc%gd%ge%gf", + ret => 0, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%0g%1g%2g%3g%4g%5g%6g%7g%8g%9g%0g%ag%bg%cg%dg%eg%fg", + output => "%0g%1g%2g%3g%4g%5g%6g%7g%8g%9g%0g%ag%bg%cg%dg%eg%fg", + ret => 0, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%", + output => "%", + ret => 0, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%0", + output => "%0", + ret => 0, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%%", + output => "%%", + ret => 0, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%0g", + output => "%0g", + ret => 0, +}, +{ + type => "tfn", + name => "urlDecodeUni", + input => "%gg", + output => "%gg", + ret => 0, +}, diff --git a/apache2/t/tfn/urlEncode.t b/apache2/t/tfn/urlEncode.t new file mode 100644 index 00000000..10f9bcab --- /dev/null +++ b/apache2/t/tfn/urlEncode.t @@ -0,0 +1,129 @@ +### Empty +{ + type => "tfn", + name => "urlEncode", + input => "", + output => "", + ret => 0, +}, + +### Nothing +{ + type => "tfn", + name => "urlEncode", + input => "TestCase", + output => "TestCase", + ret => 0, +}, + +### Valid +{ + type => "tfn", + name => "urlEncode", + input => " \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", + output => "+%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f+%21%22%23%24%25%26%27%28%29*%2b%2c%2d%2e%2f0123456789%3a%3b%3c%3d%3e%3f%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5b%5c%5d%5e%5f%60abcdefghijklmnopqrstuvwxyz%7b%7c%7d%7e%7f%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff", + ret => 1, +}, +{ + type => "tfn", + name => "urlEncode", + input => "Test Case", + output => "Test+Case", + ret => 1, +}, + + +### Partial Invalid +{ + type => "tfn", + name => "urlEncode", + input => "% ", + output => "%25+", + ret => 1, +}, +{ + type => "tfn", + name => "urlEncode", + input => "%0g ", + output => "%250g+", + ret => 1, +}, +{ + type => "tfn", + name => "urlEncode", + input => "%0 ", + output => "%250+", + ret => 1, +}, +{ + type => "tfn", + name => "urlEncode", + input => "%g0 ", + output => "%25g0+", + ret => 1, +}, +{ + type => "tfn", + name => "urlEncode", + input => "%g ", + output => "%25g+", + ret => 1, +}, + +### Invalid +{ + type => "tfn", + name => "urlEncode", + input => "%0%1%2%3%4%5%6%7%8%9%0%a%b%c%d%e%f", + output => "%250%251%252%253%254%255%256%257%258%259%250%25a%25b%25c%25d%25e%25f", + ret => 1, +}, +{ + type => "tfn", + name => "urlEncode", + input => "%g0%g1%g2%g3%g4%g5%g6%g7%g8%g9%g0%ga%gb%gc%gd%ge%gf", + output => "%25g0%25g1%25g2%25g3%25g4%25g5%25g6%25g7%25g8%25g9%25g0%25ga%25gb%25gc%25gd%25ge%25gf", + ret => 1, +}, +{ + type => "tfn", + name => "urlEncode", + input => "%0g%1g%2g%3g%4g%5g%6g%7g%8g%9g%0g%ag%bg%cg%dg%eg%fg", + output => "%250g%251g%252g%253g%254g%255g%256g%257g%258g%259g%250g%25ag%25bg%25cg%25dg%25eg%25fg", + ret => 1, +}, +{ + type => "tfn", + name => "urlEncode", + input => "%", + output => "%25", + ret => 1, +}, +{ + type => "tfn", + name => "urlEncode", + input => "%0", + output => "%250", + ret => 1, +}, +{ + type => "tfn", + name => "urlEncode", + input => "%%", + output => "%25%25", + ret => 1, +}, +{ + type => "tfn", + name => "urlEncode", + input => "%0g", + output => "%250g", + ret => 1, +}, +{ + type => "tfn", + name => "urlEncode", + input => "%gg", + output => "%25gg", + ret => 1, +}, diff --git a/apache2/utf8tables.h b/apache2/utf8tables.h index f12dc19b..5bd74253 100644 --- a/apache2/utf8tables.h +++ b/apache2/utf8tables.h @@ -1,6 +1,6 @@ /* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ - * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) + * Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, @@ -47,7 +47,7 @@ static const acmp_utf8_char_t utf8_offsets[6] = { #define UTF8_LCASEMAP_LEN 759 /** - * Table mapping is from PHP's mbstring extension, maps uppercase + * Table mapping is from PHP's mbstring extension, maps uppercase */ static const acmp_utf8_char_t utf8_lcase_map[UTF8_LCASEMAP_LEN * 2] = { 0x00000061, 0x00000041, diff --git a/doc/html-chunked.xsl b/doc/html-chunked.xsl index 5158fbca..5f022655 100644 --- a/doc/html-chunked.xsl +++ b/doc/html-chunked.xsl @@ -12,7 +12,7 @@ - + @@ -289,4 +289,4 @@ --> - \ No newline at end of file + diff --git a/doc/html.xsl b/doc/html.xsl index 5c774adc..44ce60c3 100644 --- a/doc/html.xsl +++ b/doc/html.xsl @@ -8,7 +8,7 @@ - + @@ -17,4 +17,4 @@ modsecurity-reference.css - \ No newline at end of file + diff --git a/doc/migration-matrix.html b/doc/migration-matrix.html index 4ecb47ed..70d908a5 100644 --- a/doc/migration-matrix.html +++ b/doc/migration-matrix.html @@ -71,4 +71,4 @@ FILES_TMPNAMESThe main difference here is that now @inspectFile is an O Also, the return codes are now reversed – In 1.x, a return code of “1” means that the file would be allowed. In 2.x, a return code of “1” means that the file would be denied. In order to scan/inspect uploaded files in 2.x, you need to create specific rules that use the FILES_TMPNAMES variable (as these are the names of the files that are temporarily stored on disk) and then use the @inspectFile Operator on each rule. -Also, make sure to swap your return codes in existing scripts as mentioned in the notes column.Memory limits for uploaded filesSecUploadInMemoryLimitSecRequestBodyInMemoryLimitThese two directives function the same.Change the SecUploadInMemoryLimit directive to SecRequestBodyInMemoryLimit. \ No newline at end of file +Also, make sure to swap your return codes in existing scripts as mentioned in the notes column.Memory limits for uploaded filesSecUploadInMemoryLimitSecRequestBodyInMemoryLimitThese two directives function the same.Change the SecUploadInMemoryLimit directive to SecRequestBodyInMemoryLimit. diff --git a/doc/migration-matrix.xml b/doc/migration-matrix.xml index 95f02aa4..33afdf26 100644 --- a/doc/migration-matrix.xml +++ b/doc/migration-matrix.xml @@ -6,7 +6,7 @@ Version 1.0 / (April 10, 2007) - 2004-2007 + 2004-2008 Breach Security, Inc. (http://www.breach.com) @@ -811,4 +811,4 @@ - \ No newline at end of file + diff --git a/doc/modsecurity2-apache-reference.xml b/doc/modsecurity2-apache-reference.xml index f202f01d..a59dbe01 100644 --- a/doc/modsecurity2-apache-reference.xml +++ b/doc/modsecurity2-apache-reference.xml @@ -1,66 +1,71 @@
- ModSecurity Reference Manual + <trademark class="trade">ModSecurity</trademark> Reference + Manual - Version 2.5.0-dev2 / (June 21, 2007) + Version 2.5.0 (February 19, 2008) - 2004-2007 + 2004-2008 Breach Security, Inc. (http://www.breach.com) -
+
Introduction - ModSecurityis a web application - firewall (WAF). With over 70% of all attacks now carried out over the web - application level, organisations need every help they can get in making - their systems secure. WAFs are deployed to establish an external security - layer that increases security, detects, and prevents attacks before they - reach web applications. It provides protection from a range of attacks - against web applications and allows for HTTP traffic monitoring and - real-time analysis with little or no changes to existing - infrastructure. + ModSecurity is a web + application firewall (WAF). With over 70% of attacks now carried out over + the web application level, organisations need all the help they can get in + making their systems secure. WAFs are deployed to establish an increased + external security layer to detect and/or prevent attacks before they reach + web applications. ModSecurity + provides protection from a range of attacks against web applications and + allows for HTTP traffic monitoring and real-time analysis with little or + no changes to existing infrastructure.
HTTP Traffic Logging Web servers are typically well-equipped to log traffic in a form - useful for marketing analyses, but fall short when it comes to logging - of traffic to web applications. In particular, most are not capable of - logging the request bodies. Your adversaries know this, and that is why - most attacks are now carried out via POST requests, rendering your - systems blind. ModSecurity makes full HTTP transaction logging possible, - allowing complete requests and responses to be logged. Its logging - facilities also allow fine-grained decisions to be made about exactly - what is logged and when, ensure only the relevant data is - recorded. + useful for marketing analyses, but fall short logging traffic to web + applications. In particular, most are not capable of logging the request + bodies. Your adversaries know this, and that is why most attacks are now + carried out via POST requests, rendering your systems blind. ModSecurity makes full HTTP transaction + logging possible, allowing complete requests and responses to be logged. + Its logging facilities also allow fine-grained decisions to be made + about exactly what is logged and when, ensuring only the relevant data + is recorded. As some of the request and/or response may contain + sensitive data in certain fields, ModSecurity can be configured to mask these + fields before they are written to the audit log.
Real-Time Monitoring and Attack Detection - In addition to providing logging facilities, ModSecurity can - monitor the HTTP traffic in real time in order to detect attacks. In - this case ModSecurity operates as a web intrusion detection tool, - allowing you to react to suspicious events that take place at your web - systems. + In addition to providing logging facilities, ModSecurity can monitor the HTTP traffic in + real time in order to detect attacks. In this case, ModSecurity operates as a web intrusion + detection tool, allowing you to react to suspicious events that take + place at your web systems.
Attack Prevention and Just-in-time Patching - ModSecurity can also act immediately to prevent attacks from - reaching your web applications. There are three commonly used - approaches: + ModSecurity can also act + immediately to prevent attacks from reaching your web applications. + There are three commonly used approaches: - Negative security model. Negative security model monitors + Negative security model. A negative security model monitors requests for anomalies, unusual behaviour, and common web application attacks. It keeps anomaly scores for each request, IP addresses, application sessions, and user accounts. Requests with @@ -68,21 +73,25 @@ - Positive security model. When positive security model is + Positive security model. When a positive security model is deployed, only requests that are known to be valid are accepted, - with everything else rejected. This approach works best with - applications that are heavily used but rarely updated. + with everything else rejected. This model requires knownledge of the + web applications you are protecting. Therefore a positive security + model works best with applications that are heavily used but rarely + updated so that maintenance of the model is minimized. Known weaknesses and vulnerabilities. Its rule language makes - ModSecurity an ideal external patching tool. External patching is - all about reducing the window of opportunity. Time needed to patch - application vulnerabilities often runs to weeks in many - organisations. With ModSecurity, applications can be patched from - the outside, without touching the application source code (and even - without any access to it), making your systems secure until a proper - patch is produced. + ModSecurity an ideal external + patching tool. External patching (sometimes referred to as Virtual + Patching) is about reducing the window of opportunity. Time needed + to patch application vulnerabilities often runs to weeks in many + organisations. With ModSecurity, applications can be patched + from the outside, without touching the application source code (and + even without any access to it), making your systems secure until a + proper patch is applied to the application.
@@ -90,33 +99,36 @@
Flexible Rule Engine - A flexible rule engine sits in the heart of ModSecurity. It - implements the ModSecurity Rule Language, which is a specialised - programming language designed to work with HTTP transaction data. The - ModSecurity Rule Language was designed to be easy to use, yet flexible: - common operations are simple while complex operations are possible. - Certified ModSecurity Rules, included with subscription to ModSecurity, - contain a comprehensive set of rules that implement general-purpose - hardening, common web application security issues. Heavily commented, + A flexible rule engine sits in the heart of ModSecurity. It implements the ModSecurity Rule Language, which is a + specialised programming language designed to work with HTTP transaction + data. The ModSecurity Rule Language + is designed to be easy to use, yet flexible: common operations are + simple while complex operations are possible. Certified ModSecurity Rules, included with ModSecurity, contain a comprehensive set of + rules that implement general-purpose hardening, protocol validation and + detection of common web application security issues. Heavily commented, these rules can be used as a learning tool.
Embedded-mode Deployment - ModSecurity is an embeddable web application firewall, which means - it can be deployed as part of your existing web server infrastructure - provided your web servers are Apache-based. This deployment method has - certain advantages: + ModSecurity is an embeddable + web application firewall, which means it can be deployed as part of your + existing web server infrastructure provided your web servers are + Apache-based. This deployment method has certain advantages: No changes to existing network. It only takes a few minutes to - add ModSecurity to your existing web servers. And because it was - designed to be completely passive by default, you are free to deploy - it incrementally and only use the features you need. It is equally - easy to remove or deactivate it should decide you don't want it any - more. + add ModSecurity to your + existing web servers. And because it was designed to be completely + passive by default, you are free to deploy it incrementally and only + use the features you need. It is equally easy to remove or + deactivate it if required. @@ -127,10 +139,10 @@ Implicit load balancing and scaling. Because it works embedded - in web servers, ModSecurity will automatically take advantage of the - additional load balancing and scalability features. You will not - need to think of load balancing and scaling unless your existing - system needs them. + in web servers, ModSecurity + will automatically take advantage of the additional load balancing + and scalability features. You will not need to think of load + balancing and scaling unless your existing system needs them. @@ -142,37 +154,42 @@ No problem with encrypted or compressed content. Many IDS systems have difficulties analysing SSL traffic. This is not a - problem for ModSecurity because it is positioned to work when the - traffic is decrypted and decompressed. + problem for ModSecurity because + it is positioned to work when the traffic is decrypted and + decompressed. - - ModSecurity is known to work well on a wide range of operating - systems. Our customers are successfully running it on Linux, Windows, - Solaris, FreeBSD, OpenBSD, NetBSD, AIX, Mac OS X, and HP-UX.
Network-based Deployment - ModSecurity works equally well when deployed as part of an - Apache-based reverse proxy server, and many of our customers choose to - do so. In this scenario, one installation of ModSecurity can protect any - number of web servers (even the non-Apache ones). + ModSecurity works equally + well when deployed as part of an Apache-based reverse proxy server, and + many of our customers choose to do so. In this scenario, one + installation of ModSecurity can + protect any number of web servers (even the non-Apache ones).
-
+
+ Portability + + ModSecurity is known to work + well on a wide range of operating systems. Our customers are + successfully running it on Linux, Windows, Solaris, FreeBSD, OpenBSD, + NetBSD, AIX, Mac OS X, and HP-UX. +
+ +
Licensing - ModSecurity is available under two licenses. Users can choose to - use the software under the terms of the GNU General Public License - (http://www.gnu.org/licenses/gpl.html),as - an Open Source / Free Software product. A range of commercial licenses - is also available, together with a range of commercial support - contracts. For more information on commercial licensing please contact - Breach Security. + ModSecurity is available + under two licenses. Users can choose to use the software under the terms + of the GNU General Public License version 2 (licence text is included + with the distribution), as an Open Source / Free Software product. A + range of commercial licenses is also available, together with a range of + commercial support contracts. For more information on commercial + licensing please contact Breach Security. ModSecurity, mod_security, and ModSecurity Pro are trademarks or @@ -182,70 +199,28 @@
- ModSecurity Core Rules + <trademark class="trade">ModSecurity</trademark> Core Rules
Overview - ModSecurity is a web application firewall engine that provides - very little protection on its own. In order to become useful, - ModSecurity must be configured with rules. In order to enable users to - take full advantage of ModSecurity out of the box, Breach Security Inc. - is providing a free certified rule set for ModSecurity 2.0. Unlike - intrusion detection and prevention systems, which rely on signature - specific to known vulnerabilities, the Core Rules provide generic - protection from unknown vulnerabilities often found in web applications, - which are in most cases custom coded. The Core Rules are heavily - commented to allow it to be used as a step-by-step deployment guide for - ModSecurity. The latest Core Rules can be found at the ModSecurity - website - http://www.modsecurity.org/projects/rules/. -
- -
- Core Rules Structure - - If you expect a single pack of Apache configuration files, you are - right, and wrong. A ModSecurity rule set includes information about - different areas: - - - - The logic required to detect attacks. - - - - A policy setting the actions to perform if an attack is - detected. - - - - Information regarding attacks. - - - - In order to allow separate management of the different parts, the - Core Rules are based on templates that are generated into a run-time - rule set by inserting policy, patterns and event information. The Core - Rules package includes these templates, the generation script (written - in Perl) and data files required to generate a useful rule set. It also - includes a bunch of pre-generated rule sets for different policies. The - generation script also allows two optimizations: - - - - Optimal use of regular expressions. Since regular expressions - are much more efficient if assembled into a single expression and - optimized, the generation script takes the list of patterns that are - required for a rule and optimize them into a most efficient regular - expression. - - - - Removal of rules that are not utilized by a specific - policy. - - + ModSecurity is a web + application firewall engine that provides very little protection on its + own. In order to become useful, ModSecurity must be configured with rules. In + order to enable users to take full advantage of ModSecurity out of the box, Breach Security, + Inc. is providing a free certified rule set for ModSecurity 2.x. Unlike intrusion detection + and prevention systems, which rely on signatures specific to known + vulnerabilities, the Core Rules provide generic protection from unknown + vulnerabilities often found in web applications, which are in most cases + custom coded. The Core Rules are heavily commented to allow it to be + used as a step-by-step deployment guide for ModSecurity. The latest Core Rules can be + found at the ModSecurity website - + http://www.modsecurity.org/projects/rules/.
@@ -282,123 +257,209 @@
-
+
Installation - ModSecurity installation consists of the following steps: + ModSecurity installation + consists of the following steps: - ModSecurity 2.x works with Apache 2.0.x or better. + ModSecurity 2.x works with + Apache 2.0.x or better. Make sure you have mod_unique_id installed. + + mod_unique_id is packaged with Apache httpd. - (Optional) Install the latest version of libxml2, if it isn't - already installed on the server. + Install the latest version of libxml2, if it isn't already + installed on the server. + + http://xmlsoft.org/downloads.html - Unpack the ModSecurity archive + Install the latest version of Lua in the 5.1.x branch, if it + isn't already installed on the server. + + http://www.lua.org/download.html - Edit Makefile to configure the path to the Apache ServerRoot - directory. You can check this by identifying the ServerRoot directive - setting in your httpd.conf file. This is the path that was specified - with the "--install-path=" configuration flag during compilation (for - example, in Fedora Core4: top_dir = - /etc/httpd). + Stop Apache httpd - (Optional) Edit Makefile to enable ModSecurity to use libxml2 - (uncomment line DEFS = - -DWITH_LIBXML2) and configure the include path (for example: - INCLUDES=-I/usr/include/libxml2) + Unpack the ModSecurity + archive - Compile with make + Building differs for UNIX (or UNIX-like) operating systems and + Windows. + + + + UNIX + + + + Run the configure script to generate a Makefile. + Typically no options are needed. + + ./configure + + Options are available for more customization (use + ./configure --help for a full list), but + typically you will only need to specify the location of the + apxs command installed by Apache httpd with + the --with-apxs option. + + ./configure + --with-apxs=/path/to/httpd-2.x.y/bin/apxs + + + + Compile with: make + + + + Optionally test with: make + test + + + + Optionally build the ModSecurity Log Collector with: + make mlogc + + + + Optionally install mlogc: Review the + INSTALL file included in the + apache2/mlogc-src directory in the distribution. + + + + Install the ModSecurity module with: + make install + + + + + + Windows (MS VC++ 8) + + + + Edit Makefile.win to configure the + Apache base and library paths. + + + + Compile with: nmake -f + Makefile.win + + + + Install the ModSecurity module with: + nmake -f Makefile.win install + + + + Copy the libxml2.dll and + lua5.1.dll to the Apache + bin directory. Alternatively you can follow + the step below for using LoadFile to load these + libraries. + + + + - Stop Apache + Edit the main Apache httpd config file (usually + httpd.conf) + + On UNIX (and Windows if you did not copy the DLLs as stated + above) you must load libxml2 and lua5.1 before ModSecurity with something like this: + + LoadFile /usr/lib/libxml2.so +LoadFile /usr/lib/liblua5.1.so + + Load the ModSecurity module + with:LoadModule security2_module modules/mod_security2.so - Install with make - install + Configure ModSecurity - (Optional) Add one line to your configuration to load libxml2: - LoadFile - /usr/lib/libxml2.so + Start Apache httpd - Add one line to your configuration to load ModSecurity: LoadModule security2_module - modules/mod_security2.so - - - - Configure ModSecurity - - - - Start Apache - - - - You now have ModSecurity 2.x up and running. + You should now have ModSecurity 2.x up and running. If you have compiled Apache yourself you might experience problems - compiling ModSecurity against PCRE. This is because Apache bundles PCRE - but this library is also typically provided by the operating system. I - would expect most (all) vendor-packaged Apache distributions to be - configured to use an external PCRE library (so this should not be a - problem). + compiling ModSecurity against PCRE. + This is because Apache bundles PCRE but this library is also typically + provided by the operating system. I would expect most (all) + vendor-packaged Apache distributions to be configured to use an external + PCRE library (so this should not be a problem). You want to avoid Apache using the bundled PCRE library and - ModSecurity linking against the one provided by the operating system. - The easiest way to do this is to compile Apache against the PCRE library - provided by the operating system (or you can compile it against the - latest PCRE version you downloaded from the main PCRE distribution - site). You can do this at configure time using the --with-pcre switch. If you are not in a - position to recompile Apache then, to compile ModSecurity successfully, - you'd still need to have access to the bundled PCRE headers (they are - available only in the Apache source code) and change the include path - for ModSecurity (as you did in step 7 above) to point to them. + ModSecurity linking against the one + provided by the operating system. The easiest way to do this is to + compile Apache against the PCRE library provided by the operating system + (or you can compile it against the latest PCRE version you downloaded + from the main PCRE distribution site). You can do this at configure time + using the --with-pcre switch. If you + are not in a position to recompile Apache, then, to compile ModSecurity successfully, you'd still need to + have access to the bundled PCRE headers (they are available only in the + Apache source code) and change the include path for ModSecurity (as you did in step 7 above) to + point to them (via the --with-pcre ModSecurity configure option). Do note that if your Apache is using an external PCRE library you - can compile ModSecurity with WITH_PCRE_STUDY defined,which would possibly - give you a slight performance edge in regular expression + can compile ModSecurity with + WITH_PCRE_STUDY defined,which would + possibly give you a slight performance edge in regular expression processing.
-
+
Configuration Directives - The following section outlines all of the ModSecurity directives. - Most of the ModSecurity directives can be used inside the various Apache - Scope Directives such as VirtualHost, + The following section outlines all of the ModSecurity directives. Most of the ModSecurity directives can be used inside the + various Apache Scope Directives such as VirtualHost, Location, LocationMatch, Directory, etc... There are others, however, that can only be used once in the main configuration file. This information is - specified in the Scope sections below. + specified in the Scope sections below. The first version to use a given + directive is given in the Version sections below. These rules, along with the Core rules files, should be contained is files outside of the httpd.conf file and called up with Apache "Include" @@ -407,115 +468,120 @@ rules, you should create a file called - modsecurity_crs_15_customrules.conf and place it in the same directory as the Core rules files. By using this file name, your - custom rules will be called up after the standard ModSecurity Core rules - configuration file but before the other Core rules. This allows your rules - to be evaluate first which can be useful if you need to implement specific - "allow" rules or to correct any false positives in the Core rules as they - are applied to your site. + custom rules will be called up after the standard ModSecurity Core rules configuration file but + before the other Core rules. This allows your rules to be evaluated first + which can be useful if you need to implement specific "allow" rules or to + correct any false positives in the Core rules as they are applied to your + site. - Note - - It is highly encouraged that you do not edit the Core rules files - themselves but rather place all changes (such as - SecRuleRemoveByID, etc...) in your custom rules file. - This will allow for easier upgrading as newer Core rules are released by - Breach Security on the ModSecurity website. + + It is highly encouraged that you do not edit the Core rules files + themselves but rather place all changes (such as + SecRuleRemoveByID, etc...) in your custom rules file. + This will allow for easier upgrading as newer Core rules are released by + Breach Security on the ModSecurity + website. +
<literal>SecAction</literal> - Description: Unconditionally - processes the action list it receives as the first and only parameter. - It accepts one parameter, the syntax of which is identical to the third - parameter of SecRule. + Description: Unconditionally processes the + action list it receives as the first and only parameter. It accepts one + parameter, the syntax of which is identical to the third parameter + of SecRule. - Syntax: SecAction action1,action2,action2 + Syntax: SecAction + action1,action2,action3 - Example Usage: Example Usage: SecAction - nolog,redirect:http://www.hostname.com + nolog,initcol:RESOURCE=%{REQUEST_FILENAME} - ProcessingPhase: Any + Processing Phase: Any - Scope: - Any + Scope: Any - Dependencies/Notes: None + Version: 2.0.0 - SecAction is best used when you uncondiationally execute an - action. This is explicit triggering whereas the normal Actions are - conditional based on data inspection of the request/response. This is a - useful directive when you want to run certian actions such as initcol to - initialize collections. + Dependencies/Notes: None + + SecAction is best used when you unconditionally execute an action. + This is explicit triggering whereas the normal Actions are conditional + based on data inspection of the request/response. This is a useful + directive when you want to run certain actions such as + initcol to initialize collections.
<literal>SecArgumentSeparator</literal> - Description: Specifies which - character to use as separator for + Description: Specifies which character to use + as separator for application/x-www-form-urlencoded content. Defaults to &. Applications are sometimes (very rarely) written to use a semicolon (;). - Syntax: Syntax: SecArgumentSeparator character - Example Usage: Example Usage: SecArgumentSeparator ; - Processing Phase: Any + Processing Phase: Any - Scope: - Main + Scope: Main - Dependencies/Notes: None + Version: 2.0.0 - This directive is needed if a backend web appliaction is using a + Dependencies/Notes: None + + This directive is needed if a backend web application is using a non-standard argument separator. If this directive is not set properly - for each web app, then ModSecurity will not be able to parse the - arguements appropriately and the effectiveness of the rule matching will + for each web application, then ModSecurity will not be able to parse the + arguments appropriately and the effectiveness of the rule matching will be significantly decreased.
<literal>SecAuditEngine</literal> - Description: Configures the audit - logging engine. + Description: Configures the audit logging + engine. - Syntax: Syntax: SecAuditEngine On|Off|RelevantOnly - Example Usage: Example Usage: SecAuditEngine On - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: Can be - set/changed with the "ctl" action for the current transaction. + Version: 2.0.0 + + Dependencies/Notes: Can be set/changed with + the "ctl" action for the current transaction. Example: The following example shows the various audit directives used together. - SecAuditEngine RelevantOnly -SecAuditLog logs/audit/audit.log + SecAuditEngine RelevantOnly +SecAuditLog logs/audit/audit.log SecAuditLogParts ABCFHZ SecAuditLogType concurrent SecAuditLogStorageDir logs/audit -SecAuditLogRelevantStatus ^[45] +SecAuditLogRelevantStatus ^(?:5|4\d[^4]) Possible values are: - On - log all transactions + On - log all transactions by default. @@ -525,7 +591,7 @@ SecAuditLogStorageDir logs/audit - RelevantOnly - by default + RelevantOnly - by default only log transactions that have triggered a warning or an error, or have a status code that is considered to be relevant (see SecAuditLogRelevantStatus). @@ -536,23 +602,24 @@ SecAuditLogStorageDir logs/audit
<literal>SecAuditLog</literal> - Description: Defines the path to - the main audit log file. + Description: Defines the path to the main + audit log file. - Syntax: SecAuditLog /path/to/auditlog + Syntax: SecAuditLog + /path/to/auditlog - Example Usage: Example Usage: SecAuditLog /usr/local/apache/logs/audit.log - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: This file is - open on startup when the server typically still runs as + Version: 2.0.0 + + Dependencies/Notes: This file is open on + startup when the server typically still runs as root. You should not allow non-root users to have write privileges for this file or for the directory it is stored in.. @@ -560,63 +627,65 @@ SecAuditLogStorageDir logs/audit audit logging format is used. If concurrent audit logging format is used this file will be used as an index, and contain a record of all audit log files created. If you are planning to use Concurrent audit logging - and sending your audit log data off to a remote Console host, then you - will need to use the modsec-auditlog-collector.pl script and use the - following format: + and sending your audit log data off to a remote Console host or + commercial ModSecurity Management + Appliance, then you will need to configure and use the ModSecurity Log Collector (mlogc) and use the + following format for the audit log: - SecAuditLog \ - "|/path/modsec-auditlog-collector.pl /path/SecAuditLogDataDir /path/SecAuditLog" + SecAuditLog "|/path/to/mlogc /path/to/mlogc.conf"
<literal>SecAuditLog2</literal> - Description: Defines the path to - the secondary audit log index file when concurrent logging is enabled. - See SecAuditLog2 for more - details. + Description: Defines the path to the + secondary audit log index file when concurrent logging is enabled. See + SecAuditLog2 for more details. - Syntax: SecAuditLog2 /path/to/auditlog2 + Syntax: SecAuditLog2 + /path/to/auditlog2 - Example Usage: Example Usage: SecAuditLog2 /usr/local/apache/logs/audit2.log - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: A main audit - log must be defined via SecAuditLog - before this directive may be used. Additionally, this log is only used - for replicating the main audit log index file when concurrent audit - logging is used. It will not be used - for non-concurrent audit logging. + Version: 2.1.2 + + Dependencies/Notes: A main audit log must be + defined via SecAuditLog before this + directive may be used. Additionally, this log is only used for + replicating the main audit log index file when concurrent audit logging + is used. It will not be used for non-concurrent + audit logging.
<literal>SecAuditLogParts</literal> - Description: Defines the path to - the main audit log file. + Description: Defines the path to the main + audit log file. - Syntax: Syntax: SecAuditLogParts PARTS - Example Usage: Example Usage: SecAuditLogParts ABCFHZ - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: At this time - ModSecurity does not log response bodies of stock Apache responses (e.g. - 404), or the Server and Version: 2.0.0 + + Dependencies/Notes: At this time ModSecurity does not log response bodies of + stock Apache responses (e.g. 404), or + the Server and Date response headers. Default: ABCFHZ. @@ -625,18 +694,19 @@ SecAuditLogStorageDir logs/audit - A – audit log header + A - audit log header (mandatory) - B – request headers + B - request headers - C – request body (present - only if the request body exists and ModSecurity is configured to - intercept it) + C - request body (present + only if the request body exists and ModSecurity is configured to intercept + it) @@ -645,24 +715,25 @@ SecAuditLogStorageDir logs/audit - E – intermediary response - body (present only if ModSecurity is configured to intercept + E - intermediary response + body (present only if ModSecurity is configured to intercept response bodies, and if the audit log engine is configured to record it). Intermediary response body is the same as the actual response - body unless ModSecurity intercepts the intermediary response body, - in which case the actual response body will contain the error - message (either the Apache default error message, or the - ErrorDocument page). + body unless ModSecurity + intercepts the intermediary response body, in which case the actual + response body will contain the error message (either the Apache + default error message, or the ErrorDocument page). - F – final response headers + F - final response headers (excluding the Date and Server headers, which are always added by Apache in the late stage of content delivery). - G – RESERVED for the actual + G - RESERVED for the actual response body, not implemented yet. @@ -674,23 +745,30 @@ SecAuditLogStorageDir logs/audit I - This part is a replacement for part C. It will log the same data as C in all cases - except whenmultipart/form-dataencoding in used. In - this case it will log a fake - application/x-www-form-urlencoded body that contains the - information about parameters but not about the files. This is handy - if you don't want to have (often large) files stored in your audit - logs. + except when multipart/form-data + encoding in used. In this case it will log a fake application/x-www-form-urlencoded body + that contains the information about parameters but not about the + files. This is handy if you don't want to have (often large) files + stored in your audit logs. J - RESERVED. This part, when implemented, will contain information about the files uploaded - using multipart/form-data encoding. + using multipart/form-data encoding. - Z – final boundary, + K - This part contains a + full list of every rule that matched (one per line) in the order + they were matched. The rules are fully qualified and will thus show + inherited actions and default operators. Supported as of + v2.5.0 + + + + Z - final boundary, signifies the end of the entry (mandatory) @@ -699,23 +777,26 @@ SecAuditLogStorageDir logs/audit
<literal>SecAuditLogRelevantStatus</literal> - Description: Configures which - response status code is to be considered relevant for the purpose of - audit logging. + Description: Configures which response status + code is to be considered relevant for the purpose of audit + logging. - Syntax: Syntax: SecAuditLogRelevantStatus REGEX - Example Usage: SecAuditLogRelevantStatus ^[45] + Example Usage: SecAuditLogRelevantStatus + ^(?:5|4\d[^4]) - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: Must have the - SecAuditEngine set to RelevantOnly. The parameter is a regular + Version: 2.0.0 + + Dependencies/Notes: Must have the + SecAuditEngine set to + RelevantOnly. The parameter is a regular expression. The main purpose of this directive is to allow you to configure @@ -729,27 +810,27 @@ SecAuditLogStorageDir logs/audit
<literal>SecAuditLogStorageDir</literal> - Description: Configures the - storage directory where concurrent audit log entries are to be - stored. + Description: Configures the storage directory + where concurrent audit log entries are to be stored. - Syntax: Syntax: SecAuditLogStorageDir /path/to/storage/dir - Example Usage: Example Usage: SecAuditLogStorageDir /usr/local/apache/logs/audit - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: - SecAuditLogType must be set to Concurrent. The directory must already be - created before starting Apache and it must be writable by the web server - user as new files are generated at runtime. + Version: 2.0.0 + + Dependencies/Notes: SecAuditLogType must be + set to Concurrent. The directory must already be created before starting + Apache and it must be writable by the web server user as new files are + generated at runtime. As with all logging mechanisms, ensure that you specify a file system location that has adequate disk space and is not on the root @@ -759,22 +840,24 @@ SecAuditLogStorageDir logs/audit
<literal>SecAuditLogType</literal> - Description: Configures the type - of audit logging mechanism to be used. + Description: Configures the type of audit + logging mechanism to be used. - Syntax: Syntax: SecAuditLogType Serial|Concurrent - Example Usage: Example Usage: SecAuditLogType Serial - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: Must specify - SecAuditLogStorageDir if you use concurrent logging. + Version: 2.0.0 + + Dependencies/Notes: Must specify + SecAuditLogStorageDir if you use concurrent + logging. Possible values are: @@ -790,76 +873,189 @@ SecAuditLogStorageDir logs/audit Concurrent - audit log entries will be stored in separate files, one for each transaction. Concurrent logging is the mode to use if you are going to send the - audit log data off to a remote ModSecurity Console host. + audit log data off to a remote ModSecurity Console host.
- <literal>SecChrootDir</literal> + <literal>SecCacheTransformations</literal> - Description: Configures the - directory path that will be used to jail the web server process. + Description: Controls caching of + transformations. - Syntax: SecChrootDir /path/to/chroot/dir + Syntax: SecCacheTransformations On|Off + [options] - Example Usage: SecChrootDir /chroot + Example Usage: SecCacheTransformations On + "minlen:64,maxlen:0" - Processing Phase: N/A + Processing Phase: N/A - Scope: - Main + Scope: Any - Dependencies/Notes: The internal - chroot functionality provided by ModSecurity works great for simple - setups. One example of a simple setup is Apache serving static files - only, or running scripts using modules. For more complex setups you - should consider building a jail the old-fashioned way. The internal - chroot feature should be treated as somewhat experimental. Due to the - large number of default and third-party modules available for the Apache - web server, it is not possible to verify the internal chroot works - reliably with all of them. You are advised to think about your option - and make your own decision. In particular, if you are using any of the - modules that fork in the module initialisation phase (e.g. mod_fastcgi, - mod_fcgid, mod_cgid), you are advised to examine each Apache process and - observe its current working directory, process root, and the list of - open files. + Version: 2.5.0 + + Dependencies/Notes: N/A + + First parameter: + + + + On - cache transformations + (per transaction, per phase) allowing identical transformations to + be performed only once. (default) + + + + Off - do not cache any + transformations, forcing all transformations to be performed for + each rule executed. + + + + The following options are allowed (comma separated): + + + + minlen:N - do not cache the + transformation if the value's length is less than N bytes. (default: + 15) + + + + maxlen:N - do not cache the + transformation if the value's length is more than N bytes. A zero + value is interpreted as "unlimited". (default: 0) + +
- <literal>SecContentInjection (Experimental)</literal> + <literal>SecChrootDir</literal> - Description: Enables content - injection using actions append and - prepend. + Description: Configures the directory path + that will be used to jail the web server process. - Syntax: - SecContentInjection (On|Off) + Syntax: SecChrootDir + /path/to/chroot/dir - Example Usage: - SecContentInjection On + Example Usage: SecChrootDir /chroot + + Processing Phase: N/A + + Scope: Main + + Version: 2.0.0 + + Dependencies/Notes: The internal chroot + functionality provided by ModSecurity works great for simple setups. One + example of a simple setup is Apache serving static files only, or + running scripts using modules. Some problems you might encounter with + more complex setups: + + + + DNS lookups do not work (this is because this feature requires + a shared library that is loaded on demand, after chroot takes + place). + + + + You cannot send email from PHP because it uses sendmail and + sendmail is outside the jail. + + + + In some cases Apache graceful (reload) no longer works. + + + + You should be aware that the internal chroot feature might not be + 100% reliable. Due to the large number of default and third-party + modules available for the Apache web server, it is not possible to + verify the internal chroot works reliably with all of them. A module, + working from within Apache, can do things that make it easy to break out + of the jail. In particular, if you are using any of the modules that + fork in the module initialisation phase (e.g. + mod_fastcgi, mod_fcgid, + mod_cgid), you are advised to examine each Apache + process and observe its current working directory, process root, and the + list of open files. Consider what your options are and make your own + decision. +
+ +
+ <literal>SecComponentSignature</literal> + + Description: Appends component signature to + the ModSecurity signature. + + Syntax: SecComponentSignature + "COMPONENT_NAME/X.Y.Z (COMMENT)" + + Example usage: SecComponentSignature + "Core Rules/1.2.3" + + Processing Phase: N/A + + Scope: Main + + Version: 2.5.0 + + Dependencies/Notes: This directive should be + used to make the presence of significant ModSecurity components known. The entire + signature will be recorded in transaction audit log. It should be used + by ModSecurity module and rule set + writers to make debugging easier. +
+ +
+ <literal>SecContentInjection</literal> + + Description: Enables content injection using + actions append and prepend. + + Syntax: SecContentInjection + (On|Off) + + Example Usage: SecContentInjection + On + + Processing Phase: N/A + + Scope: Any + + Version: 2.5.0 + + Dependencies/Notes: N/A
<literal>SecCookieFormat</literal> - Description: Selects the cookie - format that will be used in the current configuration context. + Description: Selects the cookie format that + will be used in the current configuration context. - Syntax: Syntax: SecCookieFormat 0|1 - Example Usage: Example Usage: SecCookieFormat 0 - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: None + Version: 2.0.0 + + Dependencies/Notes: None Possible values are: @@ -871,7 +1067,7 @@ SecAuditLogStorageDir logs/audit - 1 - use version 1 + 1 - use version 1 cookies. @@ -880,105 +1076,104 @@ SecAuditLogStorageDir logs/audit
<literal>SecDataDir</literal> - Description: Path where - persistent data (e.g. IP address data, session data, etc) is to be - stored. + Description: Path where persistent data (e.g. + IP address data, session data, etc) is to be stored. - Syntax: SecDataDir /path/to/dir + Syntax: SecDataDir + /path/to/dir - Example Usage: Example Usage: SecDataDir /usr/local/apache/logs/data - Processing Phase: N/A + Processing Phase: N/A - Scope: - Main + Scope: Main - Dependencies/Notes: This - directive is needed when initcol, setsid an setuid are used. Must be - writable by the web server user. + Dependencies/Notes: This directive is needed + when initcol, setsid an setuid are used. Must be writable by the web + server user.
<literal>SecDebugLog</literal> - Description: Path to the - ModSecurity debug log file. + Description: Path to the ModSecurity debug log file. - Syntax: SecDebugLog /path/to/modsec-debug.log + Syntax: SecDebugLog + /path/to/modsec-debug.log - Example Usage: Example Usage: SecDebugLog /usr/local/apache/logs/modsec-debug.log - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: None + Version: 2.0.0 + + Dependencies/Notes: None
<literal>SecDebugLogLevel</literal> - Description: Configures the - verboseness of the debug log data. + Description: Configures the verboseness of + the debug log data. - Syntax: Syntax: SecDebugLogLevel 0|1|2|3|4|5|6|7|8|9 - Example Usage: Example Usage: SecDebugLogLevel 4 - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: Levels - 1-3 - are always sent to the Apache error log. Therefore you can - always use level 0 as the default - logging level in production. Level 5 - is useful when debugging. It is not advisable to use higher - logging levels in production as excessive logging can slow down server - significantly. + Version: 2.0.0 + + Dependencies/Notes: Levels 1 - 3 are always sent to the Apache error log. + Therefore you can always use level 0 + as the default logging level in production. Level 5 is useful when debugging. It is not + advisable to use higher logging levels in production as excessive + logging can slow down server significantly. Possible values are: - 0 - no logging. + 0 - no logging. - 1 - errors (intercepted + 1 - errors (intercepted requests) only. - 2 - warnings. + 2 - warnings. - 3 - notices. + 3 - notices. - 4 - details of how + 4 - details of how transactions are handled. - 5 - as above, but including + 5 - as above, but including information about each piece of information handled. - 9 - log everything, + 9 - log everything, including very detailed debugging information. @@ -987,125 +1182,178 @@ SecAuditLogStorageDir logs/audit
<literal>SecDefaultAction</literal> - Description: Defines the default - action to take on a rule match. + Description: Defines the default action to + take on a rule match. - Syntax: Syntax: SecDefaultAction action1,action2,action3 - Example Usage: Example Usage: SecDefaultAction - log,auditlog,deny,status:403,phase:2,t:lowercase + log,auditlog,deny,status:403,phase:2 - Processing Phase: Any + Processing Phase: Any - Scope: - Any + Scope: Any - Dependencies/Notes: Rules - following a SecDefaultAction directive will inherit this setting unless - a specific action is specified for an indivdual rule or until another - SecDefaultAction is specified. + Version: 2.0.0 - The default value is: + Dependencies/Notes: Rules following a + SecDefaultAction directive will inherit this setting unless a specific + action is specified for an individual rule or until another + SecDefaultAction is specified. Take special note that in the logging + disruptive actions are not allowed, but this can inadvertently be + inherited using a disruptive action in SecDefaultAction. - SecDefaultAction log,auditlog,deny,status:403,phase:2,t:none + The default value is minimal (differing from previous + versions): - Note + SecDefaultAction phase:2,log,pass + + Note SecDefaultAction must specify a disruptive action and a processing - phase. + phase and cannot contain metadata actions.
- <literal>SecGeoLookupsDb</literal> + <literal>SecGeoLookupDb</literal> - Description: Defines the path to - the geograpical database file. + Description: Defines the path to the + geographical database file. - Syntax: SecGeoLookupsDb /path/to/db + Syntax: SecGeoLookupDb /path/to/db - Example Usage: SecGeoLookupsDb + Example Usage: SecGeoLookupDb /usr/local/geo/data/GeoLiteCity.dat - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: Check out - www.maxmind.com for free database files. + Version: 2.5.0 + + Dependencies/Notes: Check out + maxmind.com for free database files.
<literal>SecGuardianLog</literal> - Description: Configuration - directive to use the httpd-guardian script to monitor for Denial of - Service (DoS) attacks. + Description: Configuration directive to use + the httpd-guardian script to monitor for Denial of Service (DoS) + attacks. - Syntax: Syntax: SecGuardianLog |/path/to/httpd-guardian - Example Usage: Example Usage: SecGuardianLog |/usr/local/apache/bin/httpd-guardian - Processing Phase: N/A + Processing Phase: N/A - Scope: - Main + Scope: Main - Dependencies/Notes: By default - httpd-guardian will defend against clients that send more 120 requests - in a minute, or more than 360 requests in five minutes. + Version: 2.0.0 - Since 1.9, ModSecurity supports a new directive, SecGuardianLog, - that is designed to send all access data to another program using the - piped logging feature. Since Apache is typically deployed in a - multi-process fashion, making information sharing difficult, the idea is - to deploy a single external process to observe all requests in a - stateful manner, providing additional protection. + Dependencies/Notes: By default httpd-guardian + will defend against clients that send more than 120 requests in a + minute, or more than 360 requests in five minutes. + + Since 1.9, ModSecurity + supports a new directive, SecGuardianLog, that is designed to send all + access data to another program using the piped logging feature. Since + Apache is typically deployed in a multi-process fashion, making + information sharing difficult, the idea is to deploy a single external + process to observe all requests in a stateful manner, providing + additional protection. Development of a state of the art external protection tool will be - a focus of subsequent ModSecurity releases. However, a fully functional - tool is already available as part of the Apache httpd tools - project. The tool is called httpd-guardian and can be used to - defend against Denial of Service attacks. It uses the blacklist tool - (from the same project) to interact with an iptables-based (Linux) or - pf-based (*BSD) firewall, dynamically blacklisting the offending IP - addresses. It can also interact with SnortSam (http://www.snortsam.net). - Assuming httpd-guardian is already configured (look into the source code - for the detailed instructions) you only need to add one line to your - Apache configuration to deploy it: + a focus of subsequent ModSecurity + releases. However, a fully functional tool is already available as part + of the Apache + httpd tools project. The tool is called httpd-guardian and can + be used to defend against Denial of Service attacks. It uses the + blacklist tool (from the same project) to interact with an + iptables-based (Linux) or pf-based (*BSD) firewall, dynamically + blacklisting the offending IP addresses. It can also interact with + SnortSam (http://www.snortsam.net). Assuming httpd-guardian is already + configured (look into the source code for the detailed instructions) you + only need to add one line to your Apache configuration to deploy + it: SecGuardianLog |/path/to/httpd-guardian
- <literal>SecPdfProtect</literal> (Experimental) + <literal>SecMarker</literal> - Description: Enables the PDF XSS - protection functionality. Once enabled access to PDF files is tracked. - Direct access attempts are redirected to links that contain one-time - tokens. Requests with valid tokens are allowed through unmodified. - Requests with invalid tokens are also allowed through but with forced - download of the PDF files. This implementation uses response headers to - detect PDF files and thus can be used with dynamically generated PDF - files that do not have the .pdf extension in the - request URI. + Description: Adds a fixed rule marker in the + ruleset to be used as a target in a skipAfter + action. + + Syntax: SecMarker + id + + Example Usage: SecMarker 9999 + + Processing Phase: Any + + Scope: Any + + Version: 2.5.0 + + Dependencies/Notes: None + + SecRule REQUEST_URI "^/$" "chain,skipAfter:99" +SecRule REMOTE_ADDR "^127\.0\.0\.1$" "chain" +SecRule REQUEST_HEADERS:User-Agent \ + "^Apache \(internal dummy connection\)$" "t:none" +SecRule &REQUEST_HEADERS:Host "@eq 0" \ + "deny,log,status:400,id:08,severity:4,msg:'Missing a Host Header'" +SecRule &REQUEST_HEADERS:Accept "@eq 0" \ + "log,deny,log,status:400,id:15,msg:'Request Missing an Accept Header'" +SecMarker 99
- <literal>SecPdfProtectMethod</literal> (Experimental) + <literal>SecPdfProtect</literal> - Description: Configure desired - protection method to be used when requests for PDF files are detected. - Possible values are TokenRedirection and + Description: Enables the PDF XSS protection + functionality. Once enabled access to PDF files is tracked. Direct + access attempts are redirected to links that contain one-time tokens. + Requests with valid tokens are allowed through unmodified. Requests with + invalid tokens are also allowed through but with forced download of the + PDF files. This implementation uses response headers to detect PDF files + and thus can be used with dynamically generated PDF files that do not + have the .pdf extension in the request URI. + + Syntax: SecPdfProtect On|Off + + Example Usage: SecPdfProtect On + + Processing Phase: N/A + + Scope: Any + + Version: 2.5.0 + + Dependencies/Notes: None +
+ +
+ <literal>SecPdfProtectMethod</literal> + + Description: Configure desired protection + method to be used when requests for PDF files are detected. Possible + values are TokenRedirection and ForcedDownload. The token redirection approach will attempt to redirect with tokens where possible. This allows PDF files to continue to be opened inline but only works for GET requests. Forced @@ -1114,72 +1362,128 @@ SecAuditLogStorageDir logs/audit download is considered to be more secure but may cause usability problems for users ("This PDF won't open anymore!"). - Default: + Syntax: SecPdfProtectMethod method + + Example Usage: SecPdfProtectMethod TokenRedirection + + Processing Phase: N/A + + Scope: Any + + Version: 2.5.0 + + Dependencies/Notes: None + + Default: TokenRedirection
- <literal>SecPdfProtectSecret</literal> (Experimental) + <literal>SecPdfProtectSecret</literal> - Description: Defines the secret - that will be used to construct one-time tokens. You should use a - reasonably long value for the secret (e.g. 16 characters is good). Once - selected the secret should not be changed as as it will break the the - tokens that were sent prior to change. But it's not a big deal even if - you change it. It will just force dowload of PDF files with tokens that - were issued in the last few seconds. + Description: Defines the secret that will be + used to construct one-time tokens. You should use a reasonably long + value for the secret (e.g. 16 characters is good). Once selected the + secret should not be changed as it will break the tokens that were sent + prior to change. But it's not a big deal even if you change it. It will + just force download of PDF files with tokens that were issued in the + last few seconds. + + Syntax: SecPdfProtectSecret secret + + Example Usage: SecPdfProtectSecret + MyRandomSecretString + + Processing Phase: N/A + + Scope: Any + + Version: 2.5.0 + + Dependencies/Notes: None
- <literal>SecPdfProtectTimeout</literal> (Experimental) + <literal>SecPdfProtectTimeout</literal> - Description: Defines the token - timeout. After token expires it can no longer be used to allow access to - PDF file. Request will be allowed through but the PDF will be delivered - as attachment. + Description: Defines the token timeout. After + token expires it can no longer be used to allow access to PDF file. + Request will be allowed through but the PDF will be delivered as + attachment. - Default: - 10 + Syntax: SecPdfProtectTimeout timeout + + Example Usage: SecPdfProtectTimeout 10 + + Processing Phase: N/A + + Scope: Any + + Version: 2.5.0 + + Dependencies/Notes: None + + Default: 10
- <literal>SecPdfProtectTokenName</literal> (Experimental) + <literal>SecPdfProtectTokenName</literal> - Description: Defines the name of - the token. The only reason you would want to change the name of the - token is if you wanted to hide the fact you are running ModSecurity. - It's a good reason but it won't really help as the adversary can look - into the algorithm used for PDF protection and figure it out anyway. It - does raise the bar slightly so go ahead if you want to. + Description: Defines the name of the token. + The only reason you would want to change the name of the token is if you + wanted to hide the fact you are running ModSecurity. It's a good reason but it won't + really help as the adversary can look into the algorithm used for PDF + protection and figure it out anyway. It does raise the bar slightly so + go ahead if you want to. - Default: - PDFTOKEN + Syntax: SecPdfProtectTokenName name + + Example Usage: SecPdfProtectTokenName PDFTOKEN + + Processing Phase: N/A + + Scope: Any + + Version: 2.5.0 + + Dependencies/Notes: None + + Default: PDFTOKEN
<literal>SecRequestBodyAccess</literal> - Description: Configures whether - request bodies will be buffered and processed by ModSecurity by - default. + Description: Configures whether request + bodies will be buffered and processed by ModSecurity by default. - Syntax: Syntax: SecRequestBodyAccess On|Off - Example Usage: Example Usage: SecRequestBodyAccess On - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: This - directive is required if you plan to inspect POST_PAYLOADS of requests. - This directive must be used along with the "phase:2" processing phase - action and REQUEST_BODY variable/location. If any of these 3 parts are - not configured, you will not be able to inspect the request - bodies. + Version: 2.0.0 + + Dependencies/Notes: This directive is + required if you plan to inspect POST_PAYLOADS of requests. This + directive must be used along with the "phase:2" processing phase action + and REQUEST_BODY variable/location. If any of these 3 parts are not + configured, you will not be able to inspect the request bodies. Possible values are: @@ -1190,7 +1494,7 @@ SecAuditLogStorageDir logs/audit - Off - do not attempt to + Off - do not attempt to access request bodies. @@ -1199,45 +1503,81 @@ SecAuditLogStorageDir logs/audit
<literal>SecRequestBodyLimit</literal> - Description: Configures the - maximum request body size ModSecurity will accept for buffering. + Description: Configures the maximum request + body size ModSecurity will accept + for buffering. - Syntax: Syntax: SecRequestBodyLimit NUMBER_IN_BYTES - Example Usage: Example Usage: SecRequestBodyLimit 134217728 - Processing Phase: N/A + Scope: Any - Scope: - Any + Version: 2.0.0 - Dependencies/Notes: 131072 KB - (134217728 bytes) is the default setting. Anything over this limit will - be rejected with status code 413 Request Entity Too Large. There is a - hard limit of 1 GB. + Dependencies/Notes: 131072 KB (134217728 + bytes) is the default setting. Anything over this limit will be rejected + with status code 413 Request Entity Too Large. There is a hard limit of + 1 GB. +
+ +
+ <literal>SecRequestBodyNoFilesLimit</literal> + + Description: Configures the maximum request + body size ModSecurity will accept + for buffering, excluding the size of files being transported in the + request. This directive comes handy to further reduce susceptibility to + DoS attacks when someone is sending request bodies of very large sizes. + Web applications that require file uploads must configure + SecRequestBodyLimit to a high value. Since large + files are streamed to disk file uploads will not increase memory + consumption. However, it's still possible for someone to take advantage + of a large request body limit and send non-upload requests with large + body sizes. This directive eliminates that loophole. + + Syntax: SecRequestBodyNoFilesLimit + NUMBER_IN_BYTES + + Example Usage: SecRequestBodyLimit 131072 + + Scope: Any + + Version: 2.5.0 + + Dependencies/Notes: 1 MB (1048576 bytes) is + the default setting. This value is very conservative. For most + applications you should be able to reduce it down to 128 KB or lower. + Anything over the limit will be rejected with status code 413 + Request Entity Too Large. There is a hard limit of 1 + GB.
<literal>SecRequestBodyInMemoryLimit</literal> - Description: Configures the - maximum request body size ModSecurity will store in memory. + Description: Configures the maximum request + body size ModSecurity will store in + memory. - Syntax: Syntax: SecRequestBodyInMemoryLimit NUMBER_IN_BYTES - Example Usage: Example Usage: SecRequestBodyInMemoryLimit 131072 - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: None + Version: 2.0.0 + + Dependencies/Notes: None By default the limit is 128 KB: @@ -1248,23 +1588,24 @@ SecRequestBodyInMemoryLimit 131072
<literal>SecResponseBodyLimit</literal> - Description: Configures the - maximum response body size that will be accepted for buffering. + Description: Configures the maximum response + body size that will be accepted for buffering. - Syntax: Syntax: SecResponseBodyLimit NUMBER_IN_BYTES - Example Usage: Example Usage: SecResponseBodyLimit 524228 - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: Anything over - this limit will be rejected with status code 500 Internal Server Error. - This setting will not affect the responses with MIME types that are not + Version: 2.0.0 + + Dependencies/Notes: Anything over this limit + will be rejected with status code 500 Internal Server Error. This + setting will not affect the responses with MIME types that are not marked for buffering. There is a hard limit of 1 GB. By default this limit is configured to 512 KB: @@ -1273,29 +1614,66 @@ SecRequestBodyInMemoryLimit 131072 SecResponseBodyLimit 524288
+
+ <literal>SecResponseBodyLimitAction</literal> + + Description: Controls what happens once a + response body limit, configured with + SecResponseBodyLimit, is encountered. By default + ModSecurity will reject a response + body that is longer than specified. Some web sites, however, will + produce very long responses making it difficult to come up with a + reasonable limit. Such sites would have to raise the limit significantly + to function properly defying the purpose of having the limit in the + first place (to control memory consumption). With the ability to choose + what happens once a limit is reached site administrators can choose to + inspect only the first part of the response, the part that can fit into + the desired limit, and let the rest through. Some could argue that + allowing parts of responses to go uninspected is a weakness. This is + true in theory but only applies to cases where the attacker controls the + output (e.g. can make it arbitrary long). In such cases, however, it is + not possible to prevent leakage anyway. The attacker could compress, + obfuscate, or even encrypt data before it is sent back, and therefore + bypass any monitoring device. + + Syntax: SecResponseBodyLimitAction + Reject|ProcessPartial + + Example Usage: + SecResponseBodyLimitAction ProcessPartial + + Processing Phase: N/A + + Scope: Any + + Version: 2.5.0 + + Dependencies/Notes: None +
+
<literal>SecResponseBodyMimeType</literal> - Description: Configures - which MIME types are to be considered - for response body buffering. + Description: Configures which MIME types are to be considered for response + body buffering. - Syntax: Syntax: SecResponseBodyMimeType mime/type - Example Usage: Example Usage: SecResponseBodyMimeType text/plain text/html - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: - Multiple SecResponseBodyMimeType - directives can be used to add MIME - types. + Version: 2.0.0 + + Dependencies/Notes: Multiple SecResponseBodyMimeType directives can be + used to add MIME types. The default value is text/plaintext/html: @@ -1306,58 +1684,60 @@ SecResponseBodyLimit 524288
<literal>SecResponseBodyMimeTypesClear</literal> - Description: Clears the list of - MIME types considered for response - body buffering, allowing you to start populating the list from + Description: Clears the list of MIME types considered for response body + buffering, allowing you to start populating the list from scratch. - Syntax: Syntax: SecResponseBodyMimeTypesClear - Example Usage: Example Usage: SecResponseBodyMimeTypesClear - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: None + Version: 2.0.0 + + Dependencies/Notes: None
<literal>SecResponseBodyAccess</literal> - Description: Configures whether - response bodies are to be buffer and analysed or not. + Description: Configures whether response + bodies are to be buffer and analysed or not. - Syntax: Syntax: SecResponseBodyAccess On|Off - Example Usage: Example Usage: SecResponseBodyAccess On - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: This - directive is required if you plan to inspect html responses. This - directive must be used along with the "phase:4" processing phase action - and RESPONSE_BODY variable/location. If any of these 3 parts are not - configured, you will not be able to inspect the response bodies. + Version: 2.0.0 + + Dependencies/Notes: This directive is + required if you plan to inspect HTML responses. This directive must be + used along with the "phase:4" processing phase action and RESPONSE_BODY + variable/location. If any of these 3 parts are not configured, you will + not be able to inspect the response bodies. Possible values are: - On - access response bodies + On - access response bodies (but only if the MIME type matches, see above). - Off - do not attempt to + Off - do not attempt to access response bodies. @@ -1366,22 +1746,24 @@ SecResponseBodyLimit 524288
<literal>SecRule</literal> - Description: SecRuleis the main ModSecurity directive. It - is used to analyse data and perform actions based on the results. + Description: SecRule is the main ModSecurity directive. It is used to analyse + data and perform actions based on the results. - Syntax: SecRule VARIABLES OPERATOR [ACTIONS] + Syntax: SecRule + VARIABLES OPERATOR [ACTIONS] - Example Usage: Example Usage: SecRule REQUEST_URI "attack" - Processing Phase: Any + Processing Phase: Any - Scope: - Any + Scope: Any - Dependencies/Notes: None + Version: 2.0.0 + + Dependencies/Notes: None In general, the format of this rule is as follows: @@ -1415,10 +1797,9 @@ SecResponseBodyLimit 524288 SecRule XML:/xPath/Expression dirty - As you have just seen, not all collections support all - selection operator format types. You should refer to the - documentation of each collection to determine what is and isn't - supported. + Not all collections support all selection operator format + types. You should refer to the documentation of each collection to + determine what is and isn't supported.
@@ -1427,10 +1808,11 @@ SecResponseBodyLimit 524288 In the simplest possible case you will use a regular expression pattern as the second rule parameter. This is what we've done in the - examples above. If you do this ModSecurity assumes you want to use - the rx operator. You can explicitly + examples above. If you do this ModSecurity assumes you want to use the + rx operator. You can explicitly specify the operator you want to use by using @ as the first character in the second rule + moreinfo="none">@ as the first character in the second rule parameter: SecRule REQUEST_URI "@rx dirty" @@ -1464,37 +1846,38 @@ SecResponseBodyLimit 524288
<literal>SecRuleInheritance</literal> - Description: Configures whether - the current context will inherit rules from the parent context - (configuration options are inherited in most cases - you should look up - the documentation for every directive to determine if it is inherited or + Description: Configures whether the current + context will inherit rules from the parent context (configuration + options are inherited in most cases - you should look up the + documentation for every directive to determine if it is inherited or not). - Syntax: Syntax: SecRuleInheritance On|Off - Example Usage: Example Usage: SecRuleInheritance Off - Processing Phase: Any + Processing Phase: Any - Scope: - Any + Scope: Any - Dependencies/Notes: - Resource-specific contexts (e.g. - Location, Directory, etc) - cannot override phase1 rules configured in the main - server or in the virtual server. This is because phase 1 is run early in - the request processing process, before Apache maps request to resource. - Virtual host context can override phase 1 rules configured in the main - server. + Version: 2.0.0 - Example: The following example shows where ModSecurity may be - enabled in the main Apache configuration scope, however you might want - to configure your VirtualHosts differently. In the first example, the - first virtualhost is not inheriting the ModSecurity main config - directives and in the second one it is. + Dependencies/Notes: Resource-specific + contexts (e.g. Location, Directory, etc) cannot override + phase1 rules configured in the main server or in + the virtual server. This is because phase 1 is run early in the request + processing process, before Apache maps request to resource. Virtual host + context can override phase 1 rules configured in the main server. + + Example: The following example shows where ModSecurity may be enabled in the main Apache + configuration scope, however you might want to configure your + VirtualHosts differently. In the first example, the first VirtualHost is + not inheriting the ModSecurity main + config directives and in the second one it is. SecRuleEnine On SecDefaultAction log,pass,phase:2 @@ -1502,7 +1885,7 @@ SecDefaultAction log,pass,phase:2 <VirtualHost *:80> ServerName app1.com -ServerAlias www.app1.com +ServerAlias www.app1.com SecRuleInheritance Off SecDefaultAction log,deny,phase:1,redirect:http://www.site2.com ... @@ -1511,8 +1894,7 @@ SecDefaultAction log,deny,phase:1,redirect:http://www.site2.com <VirtualHost *:80> ServerName app2.com ServerAlias www.app2.com -SecRuleInheritance On -SecRule ARGS "attack" +SecRuleInheritance On SecRule ARGS "attack" ... </VirtualHost> @@ -1520,12 +1902,12 @@ ServerAlias www.app2.com - On - inherit rules from the + On - inherit rules from the parent context. - Off - do not inherit rules + Off - do not inherit rules from the parent context. @@ -1534,23 +1916,24 @@ ServerAlias www.app2.com
<literal>SecRuleEngine</literal> - Description: Configures the rules + Description: Configures the rules engine. - Syntax: Syntax: SecRuleEngine On|Off|DetectionOnly - Example Usage: Example Usage: SecRuleEngine On - Processing Phase: Any + Processing Phase: Any - Scope: - Any + Scope: Any - Dependencies/Notes: Thisdirective - can also be controled by the ctl action (ctl:ruleEngine=off) for per - rule processing. + Version: 2.0.0 + + Dependencies/Notes: This directive can also + be controlled by the ctl action (ctl:ruleEngine=off) for per rule + processing. Possible values are: @@ -1575,24 +1958,25 @@ ServerAlias www.app2.com
<literal>SecRuleRemoveById</literal> - Description: Removes matching - rules from the parent contexts. + Description: Removes matching rules from the + parent contexts. - Syntax: Syntax: SecRuleRemoveById RULEID - Example Usage: Example Usage: SecRuleRemoveByID 1 2 "9000-9010" - Processing Phase: Any + Processing Phase: Any - Scope: - Any + Scope: Any - Dependencies/Notes: This - directive supports multiple parameters, where each parameter can either - be a rule ID, or a range. Parameters that contain spaces must be - delimited using double quotes. + Version: 2.0.0 + + Dependencies/Notes: This directive supports + multiple parameters, where each parameter can either be a rule ID, or a + range. Parameters that contain spaces must be delimited using double + quotes. SecRuleRemoveById 1 2 5 10-20 "400-556" 673
@@ -1600,121 +1984,296 @@ ServerAlias www.app2.com
<literal>SecRuleRemoveByMsg</literal> - Description: Removes matching - rules from the parent contexts. + Description: Removes matching rules from the + parent contexts. - Syntax: Syntax: SecRuleRemoveByMsg REGEX - Example Usage: Example Usage: SecRuleRemoveByMsg "FAIL" - Processing Phase: Any + Processing Phase: Any - Scope: - Any + Scope: Any - Dependencies/Notes: This - directive supports multiple parameters. Each parameter is a regular - expression that will be applied to the message (specified using the - msg action). + Version: 2.0.0 + + Dependencies/Notes: This directive supports + multiple parameters. Each parameter is a regular expression that will be + applied to the message (specified using the msg action). +
+ +
+ <literal>SecRuleScript</literal> (Experimental) + + Description: This directive creates a special + rule that executes a Lua script to decide whether to match or not. The + main difference from SecRule is that there are no + targets nor operators. The script can fetch any variable from the + ModSecurity context and use any + (Lua) operator to test them. The second optional parameter is the list + of actions whose meaning is identical to that of + SecRule. + + Syntax: SecRuleScript + /path/to/script.lua [ACTIONS] + + Example Usage: SecRuleScript "/path/to/file.lua" + "block" + + Processing Phase: Any + + Scope: Any + + Version: 2.5.0 + + Dependencies/Notes: None + + + All Lua scripts are compiled at configuration time and cached in + memory. To reload scripts you must reload the entire ModSecurity configuration by restarting + Apache. + + + Example script: + + -- Your script must define the main entry +-- point, as below. +function main() + -- Log something at level 1. Normally you shouldn't be + -- logging anything, especially not at level 1, but this is + -- just to show you can. Useful for debugging. + m.log(1, "Hello world!"); + + -- Retrieve one variable. + local var1 = m.getvar("REMOTE_ADDR"); + + -- Retrieve one variable, applying one transformation function. + -- The second parameter is a string. + local var2 = m.getvar("REQUEST_URI", "normalisePath"); + + -- Retrieve one variable, applying several transformation functions. + -- The second parameter is now a list. You should note that m.getvar() + -- requires the use of comma to separate collection names from + -- variable names. This is because only one variable is returned. + local var3 = m.getvar("ARGS.p", { "lowercase", "compressWhitespace" } ); + + -- If you want this rule to match return a string + -- containing the error message. The message must contain the name + -- of the variable where the problem is located. + -- return "Variable ARGS:p looks suspicious!" + + -- Otherwise, simply return nil. + return nil; +end + + In this first example we were only retrieving one variable at the + time. In this case the name of the variable is known to you. In many + cases, however, you will want to examine variables whose names you won't + know in advance, for example script parameters. + + Example showing use of m.getvars() to retrieve + many variables at once: + + function main() + -- Retrieve script parameters. + local d = m.getvars("ARGS", { "lowercase", "htmlEntityDecode" } ); + + -- Loop through the paramters. + for i = 1, #d do + -- Examine parameter value. + if (string.find(d[i].value, "<script")) then + -- Always specify the name of the variable where the + -- problem is located in the error message. + return ("Suspected XSS in variable " .. d[i].name .. "."); + end + end + + -- Nothing wrong found. + return nil; +end + + + Go to http://www.lua.org/ to find more + about the Lua programming language. The reference manual too is + available online, at http://www.lua.org/manual/5.1/. + + + + Lua support is marked as experimental as + the way the progamming interface may continue to evolve while we are + working for the best implementation style. Any user input into the + programming interface is appreciated. + +
+ +
+ <literal>SecRuleUpdateActionById</literal> + + Description: Updates the action list of the + specified rule. + + Syntax: SecRuleRemoveById RULEID ACTIONLIST + + Example Usage: SecRuleUpdateActionById 12345 + deny,status:403 + + Processing Phase: Any + + Scope: Any + + Version: 2.5.0 + + Dependencies/Notes: This directive merges the + specified action list with the rule's action list. There are two + limitations. The rule ID cannot be changed, nor can the phase. Further + note that actions that may be specified multiple times are appended to + the original. + + SecAction \ + "t:lowercase,phase:2,id:12345,pass,msg:'The Message',log,auditlog" +SecRuleUpdateActionById 12345 "t:compressWhitespace,deny,status:403,msg:'A new message' + + The example above will cause the rule to be executed as if it was + specified as follows: + + SecAction \ + "t:lowercase,phase:2,id:12345,log,auditlog,t:compressWhitespace,deny,status:403,msg:'A new message'"
<literal>SecServerSignature</literal> - Description: Instructs - ModSecurity to change the data presented in the "Server:" response - header token. + Description: Instructs ModSecurity to change the data presented in + the "Server:" response header token. - Syntax: Syntax: SecServerSignature "WEB SERVER SOFTWARE" - Example Usage: Example Usage: SecServerSignature "Netscape-Enterprise/6.0" - Processing Phase: N/A + Processing Phase: N/A - Scope: - Main + Scope: Main - Dependencies/Notes: In order for - this directive to work, you must set the Apache ServerTokens directive - to Full. ModSecurity will overwrite the server signature data held in - this memory space with the data set in this directive. If ServerTokens - is not set to Full, then the memory space is most likely not large - enough to hold the new data we are looking to insert. + Version: 2.0.0 + + Dependencies/Notes: In order for this + directive to work, you must set the Apache ServerTokens directive to + Full. ModSecurity will overwrite + the server signature data held in this memory space with the data set in + this directive. If ServerTokens is not set to Full, then the memory + space is most likely not large enough to hold the new data we are + looking to insert.
<literal>SecTmpDir</literal> - Description: Configures the - directory where temporary files will be created. + Description: Configures the directory where + temporary files will be created. - Syntax: SecTmpDir /path/to/dir + Syntax: SecTmpDir + /path/to/dir - Example Usage: Example Usage: SecTmpDir /tmp - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: Needs to be - writable by the Apache user process. This is the directory location - where Apache will swap data to disk if it runs out of memory (more data - than what was specified in the SecRequestBodyInMemoryLimit directive) - during inspection. + Version: 2.0.0 + + Dependencies/Notes: Needs to be writable by + the Apache user process. This is the directory location where Apache + will swap data to disk if it runs out of memory (more data than what was + specified in the SecRequestBodyInMemoryLimit directive) during + inspection.
<literal>SecUploadDir</literal> - Description: Configures the - directory where intercepted files will be stored. + Description: Configures the directory where + intercepted files will be stored. - Syntax: SecUploadDir /path/to/dir + Syntax: SecUploadDir + /path/to/dir - Example Usage: Example Usage: SecUploadDir /tmp - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: This - directory must be on the same filesystem as the temporary directory - defined with SecTmpDir. This - directive is used with SecUploadKeepFiles. + Version: 2.0.0 + + Dependencies/Notes: This directory must be on + the same filesystem as the temporary directory defined with SecTmpDir. This directive is used with + SecUploadKeepFiles. +
+ +
+ <literal>SecUploadFileMode</literal> + + Description: Configures the mode + (permissions) of any uploaded files using an octal number. + + Syntax: SecUploadFileMode octal_mode|"default" + + Example Usage: SecUploadFileMode 0640 + + Processing Phase: N/A + + Scope: Any + + Version: 2.1.6 + + Dependencies/Notes: The mode is an octal + number (as used in chmod). The default mode is for only the account + writing the file to have read/write access (0600). Use this directive + with caution to avoid exposing potentially sensitive data to + unauthorized users. Using the value "default" will revert back to the + default setting.
<literal>SecUploadKeepFiles</literal> - Description: Configures whether - or not the intercepted files will be kept after transaction is - processed. + Description: Configures whether or not the + intercepted files will be kept after transaction is processed. - Syntax: Syntax: SecUploadKeepFiles On|Off|RelevantOnly - Example Usage: Example Usage: SecUploadKeepFiles On - Processing Phase: N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: This - directive requires the storage directory to be defined (using Version: 2.0.0 + + Dependencies/Notes: This directive requires + the storage directory to be defined (using SecUploadDir). Possible values are: @@ -1741,31 +2300,32 @@ ServerAlias www.app2.com
<literal>SecWebAppId</literal> - Description: Creates a partition - on the server that belongs to one web application. + Description: Creates a partition on the + server that belongs to one web application. - Syntax: SecWebAppId "NAME" + Syntax: SecWebAppId + "NAME" - Example Usage: Example Usage: SecWebAppId "WebApp1" - Processing Phase:N/A + Processing Phase: N/A - Scope: - Any + Scope: Any - Dependencies/Notes: Partitions - are used to avoid collisions between session IDs and user IDs. This - directive must be used if there are multiple applications deployed on - the same server. If it isn't used, a collision between session IDs might - occur. The default value is default. + Version: 2.0.0 + + Dependencies/Notes: Partitions are used to + avoid collisions between session IDs and user IDs. This directive must + be used if there are multiple applications deployed on the same server. + If it isn't used, a collision between session IDs might occur. The + default value is default. Example: <VirtualHost *:80> ServerName app1.com ServerAlias www.app1.com -SecWebAppId "App1" +SecWebAppId "App1" SecRule REQUEST_COOKIES:PHPSESSID !^$ chain,nolog,pass SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} ... @@ -1773,7 +2333,7 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} <VirtualHost *:80> ServerName app2.com -ServerAlias www.app2.com +ServerAlias www.app2.com SecWebAppId "App2" SecRule REQUEST_COOKIES:PHPSESSID !^$ chain,nolog,pass SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} @@ -1781,20 +2341,22 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} </VirtualHost> In the two examples configurations shown, SecWebAppId is being - used in conjuction with the Apache VirtualHost directives. What this + used in conjunction with the Apache VirtualHost directives. What this achieves is to create more unique collection names when being hosted on - one server. Normally, when setsid is used, ModSecurity will create a - collection with the name "SESSION" and it will hold the value specified. - With using SecWebAppId as shown in the examples, however, the name of - the collection would become "App1_SESSION" and "App2_SESSION". + one server. Normally, when setsid is used, ModSecurity will create a collection with the + name "SESSION" and it will hold the value specified. With using + SecWebAppId as shown in the examples, however, the name of the + collection would become "App1_SESSION" and "App2_SESSION". SecWebAppId is relevant in two cases: - You are logging transactions/alerts to the ModSecurity Console - and you want to use the web application ID to search only the - transactions belonging to that application. + You are logging transactions/alerts to the ModSecurity Console and you want to use + the web application ID to search only the transactions belonging to + that application. @@ -1806,40 +2368,37 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID}
-
+
Processing Phases - ModSecurity 2.x allows rules to be placed in one of the following - five phases: + ModSecurity 2.x allows rules to + be placed in one of the following five phases: - Request headers + Request headers (REQUEST_HEADERS) - Request body + Request body (REQUEST_BODY) - Response headers + Response headers (RESPONSE_HEADERS) - Response body + Response body (RESPONSE_BODY) - Logging + Logging (LOGGING) - ModSecurity Processing Phases - Diagram - - Below is a diagram of the standard Apache - Request Cycle. In the diagram, the 5 ModSecurity processing phases are - shown. + Below is a diagram of the standard Apache Request Cycle. In the + diagram, the 5 ModSecurity processing + phases are shown. @@ -1848,18 +2407,25 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} action either directly in the rule or in using the SecDefaultAction directive: - SecDefaultAction "log,pass,phase:2" -SecRule REQUEST_HEADERS:Host "!^$" "deny,phase:1" + SecDefaultAction "log,pass,phase:2" +SecRule REQUEST_HEADERS:Host "!^$" "deny,phase:1" - Note on Rule and Phases + + Keep in mind that rules are executed according to phases, so even + if two rules are adjacent in a configuration file, but are set to + execute in different phases, they would not happen one after the other. + The order of rules in the configuration file is important only within + the rules of each phase. This is especially important when using the + skip and skipAfter actions. + - Keep in mind that rules are executed according to phases, so even if - two rules are adjacent in a configuration file, but are set to execute in - different phases, they would not happen one after the other. The order of - rules in the configuration file is important only within the rules of each - phase. This is especially important when using the skip - action. + + The LOGGING phase is special. It is executed at + the end of each transaction no matter what happened in the previous + phases. This means it will be processed even if the request was + intercepted or the allow action was used to pass the + transaction through. +
Phase Request Headers @@ -1874,14 +2440,16 @@ SecRule REQUEST_HEADERS:Host "!^$" "deny,phase:1 - Note + Note Rules in this phase can not leverage Apache scope directives (Directory, Location, LocationMatch, etc...) as the post-read-request hook does not have this information yet. The exception here is the - VirtualHost directive. If you want to use ModSecurity rules inside - Apache locations, then they should run in Phase 2. Refer to the Apache - Request Cycle/ModSecurity Processing Phases diagram. + VirtualHost directive. If you want to use ModSecurity rules inside Apache locations, + then they should run in Phase 2. Refer to the Apache Request + Cycle/ModSecurity Processing Phases + diagram.
@@ -1889,22 +2457,23 @@ SecRule REQUEST_HEADERS:Host "!^$" "deny,phase:1This is the general-purpose input analysis phase. Most of the application-oriented rules should go here. In this phase you are - guaranteed to have received the request argument (provided the request - body has been read). ModSecurity supports three encoding types for the - request body phase: + guaranteed to have received the request arguments (provided the request + body has been read). ModSecurity + supports three encoding types for the request body phase: - application/x-www-form-urlencoded - used to transfer form - data + application/x-www-form-urlencoded - used to + transfer form data - multipart/form-data – used for file transfers + multipart/form-data - used for file + transfers - text/xml - used for passing XML data + text/xml - used for passing XML data @@ -1922,7 +2491,7 @@ SecRule REQUEST_HEADERS:Host "!^$" "deny,phase:1
@@ -1932,7 +2501,7 @@ SecRule REQUEST_HEADERS:Host "!^$" "deny,phase:1This is the general-purpose output analysis phase. At this point you can run rules against the response body (provided it was buffered, of course). This is the phase where you would want to inspect the - outbound html for information discloure, error messages or failed + outbound HTML for information disclosure, error messages or failed authentication text.
@@ -1942,16 +2511,20 @@ SecRule REQUEST_HEADERS:Host "!^$" "deny,phase:1This phase is run just before logging takes place. The rules placed into this phase can only affect how the logging is performed. This phase can be used to inspect the error messages logged by Apache. - You can not deny/block connections in this phase as it is too late. This + You cannot deny/block connections in this phase as it is too late. This phase also allows for inspection of other response headers that weren't - available during phase:3 or phase:4. + available during phase:3 or phase:4. Note that you must be careful not + to inherit a disruptive action into a rule in this phase as this is a + configuration error in ModSecurity + 2.5.0 and later versions.
-
+
Variables - The following variables are supported in ModSecurity 2.x: + The following variables are supported in ModSecurity 2.x:
<literal moreinfo="none">ARGS</literal> @@ -1960,36 +2533,42 @@ SecRule REQUEST_HEADERS:Host "!^$" "deny,phase:1ARGS:p will not result in any - invocations against the operator if argument p does not exist. Some - variables are actually collections, which are expanded into more - variables at runtime. The following example will examine all request - arguments:SecRule ARGS dirtySometimes, - however, you will want to look only at parts of a collection. This can - be achieved with the help of the selection + expression). To look at only the query string or body arguments, see the + ARGS_GET and ARGS_POST + collections. + + Some variables are actually collections, which are expanded into + more variables at runtime. The following example will examine all + request arguments:SecRule ARGS dirty + Sometimes, however, you will want to look only at parts of a collection. + This can be achieved with the help of the selection operator(colon). The following example will only look at the arguments named p (do note that, in general, requests can contain multiple arguments with the same name): - SecRule ARGS:p dirtyIt - is also possible to specify exclusions. The following will examine all - request arguments for the word dirty, except the - ones named z (again, there can be + SecRule ARGS:p dirty + It is also possible to specify exclusions. The following will examine + all request arguments for the word dirty, except + the ones named z (again, there can be zero or more arguments named z): - SecRule ARGS|!ARGS:z dirtyThere - is a special operator that allows you to count how many variables there - are in a collection. The following rule will trigger if there is more - than zero arguments in the request (ignore the second parameter for the - time being): SecRule &ARGS !^0$And - sometimes you need to look at an array of parameters, each with a + SecRule ARGS|!ARGS:z dirty + There is a special operator that allows you to count how many variables + there are in a collection. The following rule will trigger if there is + more than zero arguments in the request (ignore the second parameter for + the time being): SecRule &ARGS !^0$ + And sometimes you need to look at an array of parameters, each with a slightly different name. In this case you can specify a regular expression in the selection operator itself. The following rule will look into all arguments whose names begin with id_: SecRule ARGS:/^id_/ dirty - In ModSecurity 1.X, the ARGS variable stood - for QUERY_STRING + POST_PAYLOAD, - whereas now it expands to to individual variables. + Using ARGS:p will not result in any + invocations against the operator if argument p does not exist. + + In ModSecurity 1.X, the + ARGS variable stood for + QUERY_STRING + POST_PAYLOAD, + whereas now it expands to individual variables.
@@ -2004,7 +2583,7 @@ SecRule REQUEST_HEADERS:Host "!^$" "deny,phase:1 SecRule REQUEST_FILENAME "^/cgi-bin/login\.php$" "chain,log,deny,phase:2" -SecRule ARGS_COMBINED_SIZE "@gt 25" +SecRule ARGS_COMBINED_SIZE "@gt 25"
@@ -2018,7 +2597,38 @@ SecRule ARGS_COMBINED_SIZE "@gt 25" SecRule REQUEST_FILENAME "/index.php" "chain,log,deny,status:403,phase:2" -SecRule ARGS_NAMES "!^(p|a)$" +SecRule ARGS_NAMES "!^(p|a)$" +
+ +
+ <literal moreinfo="none">ARGS_GET</literal> + + ARGS_GET is similar to ARGS, + but only contains arguments from the query string. +
+ +
+ <literal moreinfo="none">ARGS_GET_NAMES</literal> + + ARGS_GET_NAMES is similar to + ARGS_NAMES, but only contains argument names from the + query string. +
+ +
+ <literal moreinfo="none">ARGS_POST</literal> + + ARGS_POST is similar to + ARGS, but only contains arguments from the POST + body. +
+ +
+ <literal moreinfo="none">ARGS_POST_NAMES</literal> + + ARGS_POST_NAMES is similar to + ARGS_NAMES, but only contains argument names from the + POST body.
@@ -2027,13 +2637,13 @@ SecRule ARGS_NAMES "!^(p|a)$" This variable holds the authentication method used to validate a user. Example: - SecRule AUTH_TYPE "basic" log,deny,status:403,phase:1,t:lowercase + SecRule AUTH_TYPE "basic" log,deny,status:403,phase:1,t:lowercase - Note + Note This data will not be available in a proxy-mode deployment as the authentication is not local. In a proxy-mode deployment, you would need - to inpect the REQUEST_HEADERS:Authorization + to inspect the REQUEST_HEADERS:Authorization header.
@@ -2044,9 +2654,8 @@ SecRule ARGS_NAMES "!^(p|a)$" The ENV variable is set with setenv and does not give access to the CGI environment variables. Example: - SecRule REQUEST_FILENAME "printenv" pass,setenv:tag=suspicious -SecRule ENV:tag "suspicious" + SecRule REQUEST_FILENAME "printenv" pass,setenv:tag=suspicious +SecRule ENV:tag "suspicious"
@@ -2056,7 +2665,7 @@ SecRule ENV:tag "suspicious" were called on the remote user's file system). Note: only available if files were extracted from the request body. Example: - SecRule FILES "\.conf$" log,deny,status:403,phase:2 + SecRule FILES "\.conf$" log,deny,status:403,phase:2
@@ -2065,7 +2674,7 @@ SecRule ENV:tag "suspicious" Single value. Total size of the uploaded files. Note: only available if files were extracted from the request body. Example: - SecRule FILES_COMBINED_SIZE "@gt 1000" log,deny,status:403,phase:2 + SecRule FILES_COMBINED_SIZE "@gt 1000" log,deny,status:403,phase:2
@@ -2075,7 +2684,7 @@ SecRule ENV:tag "suspicious" used for file upload. Note: only available if files were extracted from the request body. Example: - SecRule FILES_NAMES "^upfile$" log,deny,status:403,phase:2 + SecRule FILES_NAMES "^upfile$" log,deny,status:403,phase:2
@@ -2085,7 +2694,7 @@ SecRule ENV:tag "suspicious" a size limitation on individual uploaded files. Note: only available if files were extracted from the request body. Example: - SecRule FILES_SIZES "@gt 100" log,deny,status:403,phase:2 + SecRule FILES_SIZES "@gt 100" log,deny,status:403,phase:2
@@ -2093,10 +2702,10 @@ SecRule ENV:tag "suspicious" Collection. Contains a collection of temporary files' names on the disk. Useful when used together with @inspectFile. Note: only available if files + moreinfo="none">@inspectFile. Note: only available if files were extracted from the request body. Example: - SecRule FILES_TMPNAMES "@inspectFile /path/to/inspect_script.pl" + SecRule FILES_TMPNAMES "@inspectFile /path/to/inspect_script.pl"
@@ -2112,67 +2721,201 @@ SecRule ENV:tag "suspicious" - COUNTRY_CODE: Two character - country code. EX: US, UK, etc. + COUNTRY_CODE: Two character country code. + EX: US, UK, etc. - COUNTRY_CODE3: Up to three - character country code. + COUNTRY_CODE3: Up to three character + country code. - COUNTRY_NAME: The full - country name. + COUNTRY_NAME: The full country + name. - COUNTRY_CONTINENT: The teo - character continent that the country is located. EX: EU + COUNTRY_CONTINENT: The two character + continent that the country is located. EX: EU - REGION: The two character - region. For US, this is state. For Canada, providence, etc. + REGION: The two character region. For US, + this is state. For Canada, providence, etc. - CITY: The city name. + CITY: The city name. - POSTAL_CODE: The postal - code. + POSTAL_CODE: The postal code. - LATITUDE: The - latitude. + LATITUDE: The latitude. - LONGITUDE: The - longitude. + LONGITUDE: The longitude. - DMA_CODE: The metropoliton - area code. (US only) + DMA_CODE: The metropolitan area code. (US + only) - AREA_CODE: The phone system - area code. (US only) + AREA_CODE: The phone system area code. + (US only) Example: - SecRule REMOTE_ADDR "@geoLookup" chain,drop,msg:'Non-UK IP address' + SecRule REMOTE_ADDR "@geoLookup" "chain,drop,msg:'Non-UK IP address'" SecRule GEO:COUNTRY_CODE "!@streq UK"
+
+ <literal moreinfo="none">HIGHEST_SEVERITY</literal> + + This variable holds the highest severity of any rules that have + matched so far. Severities are numeric values and thus can be used with + comparison operators such as @lt, + etc. + + + Higher severities have a lower numeric value. + + A value of 255 indicates no severity has been set. + + + SecRule HIGHEST_SEVERITY "@le 2" "phase:2,deny,status:500,msg:'severity %{HIGHEST_SEVERITY}'" +
+ +
+ <literal moreinfo="none">MATCHED_VAR</literal> + + This variable holds the value of the variable that was matched + against. It is similar to the TX:0, except it can be used for all + operators and does not require that the capture action be specified. + + SecRule ARGS pattern chain,deny +... +SecRule MATCHED_VAR "further scrutiny" +
+ +
+ <literal moreinfo="none">MATCHED_VAR_NAME</literal> + + This variable holds the full name of the variable that was matched + against. + + SecRule ARGS pattern setvar:tx.mymatch=%{MATCHED_VAR_NAME} +... +SecRule TX:MYMATCH "@eq ARGS:param" deny +
+ +
+ <literal moreinfo="none">MODSEC_BUILD</literal> + + This variable holds the ModSecurity build number. This variable is + intended to be used to check the build number prior to using a feature + that is available only in a certain build. Example: + + SecRule MODSEC_BUILD "!@ge 02050102" skipAfter:12345 +SecRule ARGS "@pm some key words" id:12345,deny,status:500 +
+ +
+ <literal>MULTIPART_CRLF_LF_LINES</literal> + + This flag variable will be set to 1 whenever a + multi-part request uses mixed line terminators. The + multipart/form-data RFC requires + CRLF sequence to be used to terminate lines. Since + some client implementations use only LF to terminate + lines you might want to allow them to proceed under certain + circumstances (if you want to do this you will need to stop using + MULTIPART_STRICT_ERROR and check each multi-part flag + variable individually, avoiding MULTIPART_LF_LINE). + However, mixing CRLF and LF line + terminators is dangerous as it can allow for evasion. Therefore, in such + cases, you will have to add a check for + MULTIPART_CRLF_LF_LINES. +
+ +
+ <literal>MULTIPART_STRICT_ERROR</literal> + + MULTIPART_STRICT_ERROR will be set to + 1 when any of the following variables is also set to + 1: REQBODY_PROCESSOR_ERROR, + MULTIPART_BOUNDARY_QUOTED, + MULTIPART_BOUNDARY_WHITESPACE, + MULTIPART_DATA_BEFORE, + MULTIPART_DATA_AFTER, + MULTIPART_HEADER_FOLDING, + MULTIPART_LF_LINE, + MULTIPART_SEMICOLON_MISSING. Each of these variables + covers one unusual (although sometimes legal) aspect of the request body + in multipart/form-data format. Your policies should + always contain a rule to check either this variable + (easier) or one or more individual variables (if you know exactly what + you want to accomplish). Depending on the rate of false positives and + your default policy you should decide whether to block or just warn when + the rule is triggered. + + The best way to use this variable is as in the example + below: + + SecRule MULTIPART_STRICT_ERROR "!@eq 0" \ +"phase:2,t:none,log,deny,msg:'Multipart request body \ +failed strict validation: \ +PE %{REQBODY_PROCESSOR_ERROR}, \ +BQ %{MULTIPART_BOUNDARY_QUOTED}, \ +BW %{MULTIPART_BOUNDARY_WHITESPACE}, \ +DB %{MULTIPART_DATA_BEFORE}, \ +DA %{MULTIPART_DATA_AFTER}, \ +HF %{MULTIPART_HEADER_FOLDING}, \ +LF %{MULTIPART_LF_LINE}, \ +SM %{MULTIPART_SEMICOLON_MISSING}'" + + The multipart/form-data parser was upgraded in + ModSecurity v2.1.3 to actively look + for signs of evasion. Many variables (as listed above) were added to + expose various facts discovered during the parsing process. The + MULTIPART_STRICT_ERROR variable is handy to check on + all abnormalities at once. The individual variables allow detection to + be fine-tuned according to your circumstances in order to reduce the + number of false positives. Detailed analysis of various evasion + techniques covered will be released as a separated document at a later + date. +
+ +
+ <literal>MULTIPART_UNMATCHED_BOUNDARY</literal> + + Set to 1 when, during the parsing phase of a + multipart/request-body, ModSecurity encounters what feels like a + boundary but it is not. Such an event may occur when evasion of + ModSecurity is attempted. + + The best way to use this variable is as in the example + below: + + SecRule MULTIPART_UNMATCHED_BOUNDARY "!@eq 0" \ +"phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'" + + Change the rule from blocking to logging-only if many false + positives are encountered. +
+
<literal moreinfo="none">PATH_INFO</literal> @@ -2180,16 +2923,17 @@ SecRule GEO:COUNTRY_CODE "!@streq UK" also pass additional data, known as extra path information, as part of the URL. Example: - SecRule PATH_INFO "^/(bin|etc|sbin|opt|usr)" + SecRule PATH_INFO "^/(bin|etc|sbin|opt|usr)"
<literal moreinfo="none">QUERY_STRING</literal> This variable holds form data passed to the script/handler by - appending data after a question mark. Example: + appending data after a question mark. Warning: Not URL-decoded. + Example: - SecRule QUERY_STRING "attack" + SecRule QUERY_STRING "attack"
@@ -2198,7 +2942,7 @@ SecRule GEO:COUNTRY_CODE "!@streq UK" This variable holds the IP address of the remote client. Example: - SecRule REMOTE_ADDR "^192\.168\.1\.101$" + SecRule REMOTE_ADDR "^192\.168\.1\.101$"
@@ -2210,7 +2954,7 @@ SecRule GEO:COUNTRY_CODE "!@streq UK" known bad client hosts or network blocks, or conversely, to allow in authorized hosts. Example: - SecRule REMOTE_HOST "\.evil\.network\org$" + SecRule REMOTE_HOST "\.evil\.network\org$"
@@ -2222,7 +2966,7 @@ SecRule GEO:COUNTRY_CODE "!@streq UK" is less than 1024, which would indicate that the user is a privileged user (root). - SecRule REMOTE_PORT "@lt 1024" phase:1,log,pass,setenv:remote_port=privileged + SecRule REMOTE_PORT "@lt 1024" phase:1,log,pass,setenv:remote_port=privileged
@@ -2232,9 +2976,9 @@ SecRule GEO:COUNTRY_CODE "!@streq UK" there are no password (basic|digest) access controls in place, then this variable will be empty. Example: - SecRule REMOTE_USER "admin" + SecRule REMOTE_USER "admin" - Note + Note This data will not be available in a proxy-mode deployment as the authentication is not local. @@ -2248,7 +2992,7 @@ SecRule GEO:COUNTRY_CODE "!@streq UK" MULTIPART, and XML. Example: - SecRule REQBODY_PROCESSOR "^XML$ chain + SecRule REQBODY_PROCESSOR "^XML$ chain SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd"
@@ -2256,11 +3000,26 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" <literal moreinfo="none">REQBODY_PROCESSOR_ERROR</literal> - 0 (no error) or 1 (error). If you want to stop processing on an - error you must have an explicit rule in phase 2 to do so. - Example: + Possible values are 0 (no error) or 1 (error). This variable will + be set by request body processors (typically the + multipart/request-data parser or the XML parser) + when they fail to properly parse a request payload. - SecRule REQBODY_PROCESSOR_ERROR "@eq 1" deny,phase:2 + Example: + + SecRule REQBODY_PROCESSOR_ERROR "@eq 1" deny,phase:2 + + + Your policies must have a rule to check + REQBODY_PROCESSOR_ERROR at the beginning of phase 2. Failure to do so + will leave the door open for impedance mismatch attacks. It is + possible, for example, that a payload that cannot be parsed by + ModSecurity can be successfully + parsed by more tolerant parser operating in the application. If your + policy dictates blocking then you should reject the request if error + is detected. When operating in detection-only mode your rule should + alert with high severity when request body processing fails. +
@@ -2270,17 +3029,17 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" Empty, or contains the error message from the processor. Example: - SecRule REQBODY_PROCESSOR_ERROR_MSG "failed to parse" t:lowercase + SecRule REQBODY_PROCESSOR_ERROR_MSG "failed to parse" t:lowercase
<literal moreinfo="none">REQUEST_BASENAME</literal> This variable holds just the filename part of - REQUEST_FILENAME (e.g. index.php). Warning: not - urlDecoded. Example: + REQUEST_FILENAME (e.g. index.php). Warning: not URL + decoded. Example: - SecRule REQUEST_BASENAME "^login\.php$" + SecRule REQUEST_BASENAME "^login\.php$"
@@ -2288,12 +3047,12 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" This variable holds the data in the request body (including POST_PAYLOAD data). REQUEST_BODY should be used if the original order of - the arguements is important (ARGS should be used in all other cases). + the arguments is important (ARGS should be used in all other cases). Example: - SecRule REQUEST_BODY "^username=\w{25,}\&password=\w{25,}\&Submit\=login$" + SecRule REQUEST_BODY "^username=\w{25,}\&password=\w{25,}\&Submit\=login$" - Note + Note This variable is only available if the content type is application/x-www-form-urlencoded. @@ -2307,7 +3066,7 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" how many variables are in the collection. In this rule, it would trigger if the request does not include any Cookie headers. - SecRule &REQUEST_COOKIES "@eq 0" + SecRule &REQUEST_COOKIES "@eq 0"
@@ -2317,34 +3076,33 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" headers. Example: the following rule will trigger if the JSESSIONID cookie is not present. - SecRule &REQUEST_COOKIES_NAMES:JSESSIONID "@eq 0" + SecRule &REQUEST_COOKIES_NAMES:JSESSIONID "@eq 0"
<literal moreinfo="none">REQUEST_FILENAME</literal> This variable holds the relative REQUEST_URI minus the - QUERY_STRING part (e.g. /index.php). Warning: not urlDecoded. - Example: + QUERY_STRING part (e.g. /index.php). Example: - SecRule REQUEST_FILENAME "^/cgi-bin/login\.php$" + SecRule REQUEST_FILENAME "^/cgi-bin/login\.php$"
<literal moreinfo="none">REQUEST_HEADERS</literal> This variable can be used as either a collection of all of the - Request Headers or can be used to specify indivudual headers (by using + Request Headers or can be used to specify individual headers (by using REQUEST_HEADERS:Header-Name). Example: the first example uses REQUEST_HEADERS as a collection and is applying the validateUrlEncoding operator against all headers. - SecRule REQUEST_HEADERS "@validateUrlEncoding" + SecRule REQUEST_HEADERS "@validateUrlEncoding" Example: the second example is targeting only the Host header. - SecRule REQUEST_HEADERS:Host "^[\d\.]+$" \ + SecRule REQUEST_HEADERS:Host "^[\d\.]+$" \ "deny,log,status:400,msg:'Host header is a numeric IP address'"
@@ -2354,7 +3112,7 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" This variable is a collection of the names of all of the Request Headers. Example: - SecRule REQUEST_HEADERS_NAMES "^x-forwarded-for" \ + SecRule REQUEST_HEADERS_NAMES "^x-forwarded-for" \ "log,deny,status:403,t:lowercase,msg:'Proxy Server Used'"
@@ -2367,13 +3125,7 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" GET, HEAD, POST or if the HTTP is something other than HTTP/0.9, 1.0 or 1.1. - SecRule REQUEST_LINE "!(^((?:(?:pos|ge)t|head))|http/(0\.9|1\.0|1\.1)$)" - - Note - - Due to the default action transformation function lowercase, the - regex strings should be in lowercase as well unless the t:none - transformation function is specified for this particular rule. + SecRule REQUEST_LINE "!(^((?:(?:pos|ge)t|head))|http/(0\.9|1\.0|1\.1)$)" t:none,t:lowercase
@@ -2383,13 +3135,7 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" Example: the following example will trigger if the Request Method is either CONNECT or TRACE. - SecRule REQUEST_METHOD "^((?:connect|trace))$" - - Note - - Due to the default action transformation function lowercase, the - regex strings should be in lowercase as well unless the t:none - transformation function is specified for this particular rule. + SecRule REQUEST_METHOD "^((?:connect|trace))$" t:none,t:lowercase
@@ -2398,13 +3144,7 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" This variable holds the Request Protocol Version information. Example: - SecRule REQUEST_PROTOCOL "!^http/(0\.9|1\.0|1\.1)$" - - Note - - Due to the default action transformation function lowercase, the - regex strings should be in lowercase as well unless the t:none - transformation function is specified for this particular rule. + SecRule REQUEST_PROTOCOL "!^http/(0\.9|1\.0|1\.1)$" t:none,t:lowercase
@@ -2412,11 +3152,11 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" This variable holds the full URL including the QUERY_STRING data (e.g. /index.php?p=X), however it will never contain a domain name, even - if it was provided on the request line. Warning: not urlDecoded. It also - does not include either the REQUEST_METHOD or the HTTP version info. - Example: + if it was provided on the request line. Warning: not URL decoded. It + also does not include either the REQUEST_METHOD or the HTTP version + info. Example: - SecRule REQUEST_URI "attack" + SecRule REQUEST_URI "attack"
@@ -2424,10 +3164,10 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" Same as REQUEST_URI but will contain the domain name if it was provided on the request line (e.g. - http://www.example.com/index.php?p=X). Warning: not urlDecoded. + http://www.example.com/index.php?p=X). Warning: not URL decoded. Example: - SecRule REQUEST_URI_RAW "http:/" + SecRule REQUEST_URI_RAW "http:/"
@@ -2436,7 +3176,7 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" This variable holds the data for the response payload. Example: - SecRule RESPONSE_BODY "ODBC Error Code" + SecRule RESPONSE_BODY "ODBC Error Code"
@@ -2468,16 +3208,16 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" This variable is similar to the REQUEST_HEADERS variable and can be used in the same manner. Example: - SecRule RESPONSE_HEADERS:X-Cache "MISS" + SecRule RESPONSE_HEADERS:X-Cache "MISS" - Note + Note This variable may not have access to some headers when running in embedded-mode. Headers such as Server, Date, Connection and Content-Type are added during a later Apache hook just prior to sending the data to the client. This data should be available, however, either during - ModSecurity phase:5 (logging) or when running in proxy-mode. + ModSecurity phase:5 (logging) or + when running in proxy-mode.
@@ -2486,9 +3226,9 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" This variable is a collection of the response header names. Example: - SecRule RESPONSE_HEADERS_NAMES "Set-Cookie" + SecRule RESPONSE_HEADERS_NAMES "Set-Cookie" - Note + Note Same limitations as RESPONSE_HEADERS with regards to access to some headers in embedded-mode. @@ -2500,7 +3240,7 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" This variable holds the HTTP Response Protocol information. Example: - SecRule RESPONSE_PROTOCOL "^HTTP\/0\.9" + SecRule RESPONSE_PROTOCOL "^HTTP\/0\.9"
@@ -2509,9 +3249,9 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" This variable holds the HTTP Response Status Code generated by Apache. Example: - SecRule RESPONSE_STATUS "^[45]" + SecRule RESPONSE_STATUS "^[45]" - Note + Note This directive may not work as expected in embedded-mode as Apache handles many of the stock response codes (404, 401, etc...) earlier in @@ -2523,15 +3263,14 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" <literal moreinfo="none">RULE</literal> This variable provides access to the id,rev,severity, and id, rev, + severity, logdata, and msg fields of the rule that triggered the action. Only available for expansion in action strings (e.g.setvar:tx.varname=%{rule.id}). Example: - SecRule &REQUEST_HEADERS:Host "@eq 0" "log,deny,setvar:tx.varname=%{rule.id}" + SecRule &REQUEST_HEADERS:Host "@eq 0" "log,deny,setvar:tx.varname=%{rule.id}"
@@ -2540,9 +3279,9 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" This variable holds just the local filename part of SCRIPT_FILENAME. Example: - SecRule SCRIPT_BASENAME "^login\.php$" + SecRule SCRIPT_BASENAME "^login\.php$" - Note + Note This variable is not available in proxy mode.
@@ -2553,9 +3292,9 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" This variable holds the full path on the server to the requested script. (e.g. SCRIPT_NAME plus the server path). Example: - SecRule SCRIPT_FILENAME "^/usr/local/apache/cgi-bin/login\.php$" + SecRule SCRIPT_FILENAME "^/usr/local/apache/cgi-bin/login\.php$" - Note + Note This variable is not available in proxy mode.
@@ -2563,12 +3302,12 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd"
<literal moreinfo="none">SCRIPT_GID</literal> - This variable holds the groupid (numerical value) of the group + This variable holds the group id (numerical value) of the group owner of the script. Example: - SecRule SCRIPT_GID "!^46$" + SecRule SCRIPT_GID "!^46$" - Note + Note This variable is not available in proxy mode.
@@ -2579,9 +3318,9 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" This variable holds the group name of the group owner of the script. Example: - SecRule SCRIPT_GROUPNAME "!^apache$" + SecRule SCRIPT_GROUPNAME "!^apache$" - Note + Note This variable is not available in proxy mode.
@@ -2593,9 +3332,9 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" - 1=execute, 2=write, 4=read and 7=read/write/execute). Example: will trigger if the script has the WRITE permissions set. - SecRule SCRIPT_MODE "^(2|3|6|7)$" + SecRule SCRIPT_MODE "^(2|3|6|7)$" - Note + Note This variable is not available in proxy mode.
@@ -2603,13 +3342,13 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd"
<literal moreinfo="none">SCRIPT_UID</literal> - This variable holds the userid (numerical value) of the owner of + This variable holds the user id (numerical value) of the owner of the script. Example: the example rule below will trigger if the UID is not 46 (the Apache user). - SecRule SCRIPT_UID "!^46$" + SecRule SCRIPT_UID "!^46$" - Note + Note This variable is not available in proxy mode.
@@ -2620,9 +3359,9 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" This variable holds the username of the owner of the script. Example: - SecRule SCRIPT_USERNAME "!^apache$" + SecRule SCRIPT_USERNAME "!^apache$" - Note + Note This variable is not available in proxy mode.
@@ -2633,7 +3372,7 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" This variable contains the IP address of the server. Example: - SecRule SERVER_ADDR "^192\.168\.1\.100$" + SecRule SERVER_ADDR "^192\.168\.1\.100$"
@@ -2642,9 +3381,9 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" This variable contains the server's hostname or IP address. Example: - SecRule SERVER_NAME "hostname\.com$" + SecRule SERVER_NAME "hostname\.com$" - Note + Note This data is taken from the Host header submitted in the client request. @@ -2656,7 +3395,7 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" This variable contains the local port that the web server is listening on. Example: - SecRule SERVER_PORT "^80$" + SecRule SERVER_PORT "^80$"
@@ -2671,10 +3410,9 @@ SecRule XML "@validateDTD /opt/apache-frontend/conf/xml.dtd" SecRule REQUEST_COOKIES:PHPSESSID !^$ chain,nolog,pass SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} -SecRule REQUEST_URI "^/cgi-bin/finger$" "pass,log,setvar:session.score=+10" -SecRule SESSION:SCORE "@gt 50" "pass,log,setvar:session.blocked=1" -SecRule SESSION:BLOCKED "@eq 1" "log,deny,status:403" +SecRule REQUEST_URI "^/cgi-bin/finger$" "pass,log,setvar:session.score=+10" +SecRule SESSION:SCORE "@gt 50" "pass,log,setvar:session.blocked=1" +SecRule SESSION:BLOCKED "@eq 1" "log,deny,status:403"
@@ -2683,7 +3421,7 @@ SecRule SESSION:BLOCKED "@eq 1" "log,deny,statu This variable is the value set with setsid. Example: - SecRule SESSIONID !^$ chain,nolog,pass + SecRule SESSIONID !^$ chain,nolog,pass SecRule REQUEST_COOKIES:PHPSESSID !^$ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID}
@@ -2694,7 +3432,7 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} This variable holds a formatted string representing the time (hour:minute:second). Example: - SecRule TIME "^(([1](8|9))|([2](0|1|2|3))):\d{2}:\d{2}$" + SecRule TIME "^(([1](8|9))|([2](0|1|2|3))):\d{2}:\d{2}$"
@@ -2704,7 +3442,7 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} would trigger anytime between the 10th and 20th days of the month. - SecRule TIME_DAY "^(([1](0|1|2|3|4|5|6|7|8|9))|20)$" + SecRule TIME_DAY "^(([1](0|1|2|3|4|5|6|7|8|9))|20)$"
@@ -2713,7 +3451,7 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} This variable holds the time in seconds since 1970. Example: - SecRule TIME_EPOCH "@gt 1000" + SecRule TIME_EPOCH "@gt 1000"
@@ -2722,7 +3460,7 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} This variable holds the current hour (0-23). Example: this rule would trigger during "off hours". - SecRule TIME_HOUR "^(0|1|2|3|4|5|6|[1](8|9)|[2](0|1|2|3))$" + SecRule TIME_HOUR "^(0|1|2|3|4|5|6|[1](8|9)|[2](0|1|2|3))$"
@@ -2731,7 +3469,7 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} This variable holds the current minute (0-59). Example: this rule would trigger during the last half hour of every hour. - SecRule TIME_MIN "^(3|4|5)" + SecRule TIME_MIN "^(3|4|5)"
@@ -2741,7 +3479,7 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} would match if the month was either November (10) or December (11). - SecRule TIME_MON "^1" + SecRule TIME_MON "^1"
@@ -2750,7 +3488,7 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} This variable holds the current second count (0-59). Example: - SecRule TIME_SEC "@gt 30" + SecRule TIME_SEC "@gt 30"
@@ -2759,7 +3497,7 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} This variable holds the current weekday (0-6). Example: this rule would trigger only on week-ends (Saturday and Sunday). - SecRule TIME_WDAY "^(0|6)$" + SecRule TIME_WDAY "^(0|6)$"
@@ -2768,7 +3506,7 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} This variable holds the current four-digit year data. Example: - SecRule TIME_YEAR "^2006$" + SecRule TIME_YEAR "^2006$"
@@ -2780,12 +3518,30 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} last past the current request/response process. Example: In this example, we are using setvar to increase the tx.score value by 5 points. We then have a follow-up run that will evaluate the transactional score - this this request and then it will decided whether or not to allow/deny - the request through. + this request and then it will decided whether or not to allow/deny the + request through. - SecRule WEBSERVER_ERROR_LOG "does not exist" "phase:5,pass,setvar:tx.score=+5" -SecRule TX:SCORE "@gt 20" deny,log + The following is a list of reserved names in the TX + collection: + + + + TX:0 - The matching value + when using the @rx or @pm operator with the capture action. + + + + TX:1-TX:9 - The captured + subexpression value when using the @rx operator with capturing parens and the + capture action. + + + + SecRule WEBSERVER_ERROR_LOG "does not exist" "phase:5,pass,setvar:tx.score=+5" +SecRule TX:SCORE "@gt 20" deny,log
@@ -2795,7 +3551,7 @@ SecRule TX:SCORE "@gt 20" deny,logsetuid. Example: SecAction setuid:%{REMOTE_USER},nolog -SecRule USERID "Admin" +SecRule USERID "Admin"
@@ -2805,7 +3561,7 @@ SecRule USERID "Admin" moreinfo="none">SecWebAppId. Example: SecWebAppId "WebApp1" -SecRule WEBAPPID "WebApp1" "chain,log,deny,status:403" +SecRule WEBAPPID "WebApp1" "chain,log,deny,status:403" SecRule REQUEST_HEADERS:Transfer-Encoding "!^$"
@@ -2815,7 +3571,7 @@ SecRule REQUEST_HEADERS:Transfer-Encoding "!^$" Contains zero or more error messages produced by the web server. Access to this variable is in phase:5 (logging). Example: - SecRule WEBSERVER_ERROR_LOG "File does not exist" "phase:5,setvar:tx.score=+5" + SecRule WEBSERVER_ERROR_LOG "File does not exist" "phase:5,setvar:tx.score=+5"
@@ -2828,12 +3584,11 @@ SecRule REQUEST_HEADERS:Transfer-Encoding "!^$" SecDefaultAction log,deny,status:403,phase:2 SecRule REQUEST_HEADERS:Content-Type ^text/xml$ \ - phase:1,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML -SecRule REQBODY_PROCESSOR "!^XML$" skip:2 -SecRule XML:/employees/employee/name/text() Fred -SecRule XML:/xq:employees/employee/name/text() Fred \ - xmlns:xq=http://www.example.com/employees + phase:1,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML +SecRule REQBODY_PROCESSOR "!^XML$" skipAfter:12345 +SecRule XML:/employees/employee/name/text() Fred +SecRule XML:/xq:employees/employee/name/text() Fred \ + id:12345,xmlns:xq=http://www.example.com/employees The first XPath expression does not use namespaces. It would match against payload such as this one: @@ -2903,48 +3658,48 @@ SecRule XML:/xq:employees/employee/name/text()
-
+
Transformation functions - When ModSecurity receives request or response information, it makes - a copy of this data and places it into memory. It is on this data in - memory that transformation functions are applied. The raw request/response - data is never altered. Transformation functions are used to transform a - variable before testing it in a rule. + When ModSecurity receives + request or response information, it makes a copy of this data and places + it into memory. It is on this data in memory that transformation functions + are applied. The raw request/response data is never altered. + Transformation functions are used to transform a variable before testing + it in a rule. - Note + Note - The default transformation function setting is - lowercase, - replaceNulls and compressWhitespace (in this order). + There are no default transformation functions as there were in + previous versions of ModSecurity. The following rule will ensure that an attacker does not use mixed - case in order to evade the ModSecurity rule: + case in order to evade the ModSecurity rule: - SecRule ARG:p "xp_cmdshell" "t:lowercase"multiple - tranformation actions can be used in the same rule, for example the - following rule also ensures that an attacker does not use URL encoding + SecRule ARG:p "xp_cmdshell" "t:lowercase" + multiple transformation actions can be used in the same rule, for example + the following rule also ensures that an attacker does not use URL encoding (%xx encoding) for evasion. Note the order of the transformation functions, which ensures that a URL encoded letter is first decoded and than translated to lower case. - SecRule ARG:p "xp_cmdshell" "t:urlDecode,t:lowercase" + SecRule ARG:p "xp_cmdshell" "t:urlDecode,t:lowercase" - One can use the SetDefaultAction command to ensure the translation - occurs for every rule until the next. Note that translation actions are + One can use the SecDefaultAction command to ensure the translation + occurs for every rule until the next. Note that transformation actions are additive, so if a rule explicitly list actions, the translation actions - set by SetDefaultAction are still performed. + set by SecDefaultAction are still performed. - SecDefaultAction t:urlDecode,t:lowercase + SecDefaultAction t:urlDecode,t:lowercase The following transformation functions are supported:
<literal>base64Decode</literal> - This function decoes a base64-encoded string. + This function decodes a base64-encoded string.
@@ -2972,8 +3727,8 @@ SecRule XML:/xq:employees/employee/name/text() moreinfo="none">\v, \\, \?, \', \", - \xHH(hexadecimal), \0OOO(octal). Invalid encodings are left in + \xHH (hexadecimal), \0OOO (octal). Invalid encodings are left in the output.
@@ -3014,7 +3769,7 @@ SecRule XML:/xq:employees/employee/name/text() - &nbsp and &nbsp and &nbsp; @@ -3030,6 +3785,15 @@ SecRule XML:/xq:employees/employee/name/text()
+
+ <literal>jsDecode</literal> + + Decodes JavaScript escape sequences. If a \uHHHH code is in the + range of FF01-FF5E (the full width ASCII codes), then the higher byte is + used to detect and adjust the lower byte. Otherwise, only the lower byte + will be used and the higher byte zeroed. +
+
<literal>length</literal> @@ -3040,22 +3804,25 @@ SecRule XML:/xq:employees/employee/name/text()
<literal>lowercase</literal> - This function is enabled by default. It converts all charactes to + This function is enabled by default. It converts all characters to lowercase using the current C locale.
<literal>md5</literal> - This function calculates an MD5 hash from input. + This function calculates an MD5 hash from input. Note that the + computed hash is in a raw binary form and may need encoded to be usable + (EX: t:md5,t:hexEncode).
<literal><literal>none</literal></literal> This not an actual transformation function but an instruction to - ModSecurity to remove all transformation functions associated with the - current rule and start from scratch. + ModSecurity to remove all + transformation functions associated with the current rule and start from + scratch.
@@ -3088,9 +3855,9 @@ SecRule XML:/xq:employees/employee/name/text()
<literal>replaceComments</literal> - This function replaces each occurence of a C-style comments + This function replaces each occurrence of a C-style comments (/* ... */) with a single space - (multiple consecutive occurences of a space will not be compressed). + (multiple consecutive occurrences of a space will not be compressed). Unterminated comments will too be replaced with a space (ASCII 32). However, a standalone termination of a comment (*/) will not be acted upon. @@ -3111,7 +3878,7 @@ SecRule XML:/xq:employees/employee/name/text() ones that are at the end of string and have one or two characters missing) will not be converted. If you want to detect invalid encodings use the @validateUrlEncoding - operator. The transformational function should not be used against + operator. The transformation function should not be used against variables that have already been URL-decoded unless it is your intention to perform URL decoding twice!
@@ -3120,11 +3887,11 @@ SecRule XML:/xq:employees/employee/name/text() <literal>urlDecodeUni</literal> In addition to decoding %xx like urlDecode, urlDecodeUni also decodes %uXXXX encoding. If the - code is in the range of FF01-FF5E (the full width ASCII codes), then the - higher byte is used to detect and adjust the lower byte. Otherwise, only - the lower byte will be used and the higher byte zeroed. + moreinfo="none">urlDecode, urlDecodeUni also decodes %uXXXX encoding. If the code is in the range + of FF01-FF5E (the full width ASCII codes), then the higher byte is used + to detect and adjust the lower byte. Otherwise, only the lower byte will + be used and the higher byte zeroed.
@@ -3136,7 +3903,9 @@ SecRule XML:/xq:employees/employee/name/text()
<literal>sha1</literal> - This function calculates a SHA1 hash from input. + This function calculates a SHA1 hash from input. Note that the + computed hash is in a raw binary form and may need encoded to be usable + (EX: t:sha1,t:hexEncode).
@@ -3161,25 +3930,25 @@ SecRule XML:/xq:employees/employee/name/text()
-
+
Actions Each action belongs to one of five groups: - Disruptive actions- are those actions where - ModSecurity will intercept the data. They can only appear in the first - rule in a chain. + Disruptive actions - are those actions + where ModSecurity will intercept + the data. They can only appear in the first rule in a chain. - Non-disruptive actions; can appear + Non-disruptive actions - can appear anywhere. - Flow actions; can appear only in the first + Flow actions - can appear only in the first rule in a chain. @@ -3187,12 +3956,12 @@ SecRule XML:/xq:employees/employee/name/text() Meta-data actions(id, rev, severity, msg); can only appear in the first rule in + moreinfo="none"> msg) - can only appear in the first rule in a chain. - Data actions- can appear anywhere; these + Data actions - can appear anywhere; these actions are completely passive and only serve to carry data used by other actions. @@ -3201,137 +3970,220 @@ SecRule XML:/xq:employees/employee/name/text()
<literal>allow</literal> - Description: Stops processing on - a successful match and allows transaction to proceed. + Description: Stops rule processing on a + successful match and allows the transaction to proceed. - Action Group: Disruptive + Action Group: Disruptive Example: - SecRule REMOTE_ADDR "^192\.168\.1\.100$" nolog,phase:1,allow + SecRule REMOTE_ADDR "^192\.168\.1\.100$" nolog,phase:1,allow - Note + Prior to ModSecurity 2.5 the + allow action would only affect the current phase. An + allow in phase 1 would skip processing the remaining + rules in phase 1 but the rules from phase 2 would execute. Starting with + v2.5.0 allow was enhanced to allow for fine-grained + control of what is done. The following rules now apply: - The allow action only applies to the current processing phase. If - your intent is to explicitly allow a request, then you should use the - "ctl" action to turn the ruleEngine off - - ctl:ruleEngine=Off. + + + If used one its own, like in the example above, + allow will affect the entire transaction, + stopping processing of the current phase but also skipping over all + other phases apart from the logging phase. (The logging phase is + special; it is designed to always execute.) + + + + If used with parameter "phase", allow will + cause the engine to stop processing the current phase. Other phases + will continue as normal. + + + + If used with parameter "request", allow + will cause the engine to stop processing the current phase. The next + phase to be processed will be phase + RESPONSE_HEADERS. + + + + Examples: + + # Do not process request but process response. +SecAction phase:1,allow:request + +# Do not process transaction (request and response). +SecAction phase:1,allow + + + If you want to allow a response through, put a rule in phase + RESPONSE_HEADERS and simply use + allow on its own: + + # Allow response through. +SecAction phase:3,allow
- append (Experimental) + append - Description: Appends text given - as parameter to the end of response body. For this action to work - content injection must be enabled by setting - SecContentInjection to On. Also - make sure you check the content type of the response before you make - changes to it (e.g. you don't want to inject stuff into images). + Description: Appends text given as parameter + to the end of response body. For this action to work content injection + must be enabled by setting SecContentInjection to + On. Also make sure you check the content type of the + response before you make changes to it (e.g. you don't want to inject + stuff into images). - Action Group: - Non-Disruptive + Action Group: Non-Disruptive - Processing Phases: 3 and - 4. + Processing Phases: 3 and 4. Example: - SecRule RESPONSE_CONTENT_TYPE "^text/html" "nolog,pass,append:'<hr>Footer'" + SecRule RESPONSE_CONTENT_TYPE "^text/html" "nolog,pass,append:'<hr>Footer'"
<literal>auditlog</literal> - Description: Marks the - transaction for logging in the audit log. + Description: Marks the transaction for + logging in the audit log. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: - SecRule REMOTE_ADDR "^192\.168\.1\.100$" auditlog,phase:1,allow + SecRule REMOTE_ADDR "^192\.168\.1\.100$" auditlog,phase:1,allow - Note + Note The auditlog action is now explicit if log is already specified.
+
+ <literal>block</literal> + + Description: Performs the default disruptive + action. + + Action Group: Disruptive + + It is intended to be used by ruleset writers to signify that the + rule was intended to block and leaves the "how" up to the administrator. + This action is currently a placeholder which will just be replaced by + the action from the last SecDefaultAction in the same + context. Using the block action with the + SecRuleUpdateActionById directive allows a rule to be + reverted back to the previous SecDefaultAction + disruptive action. + + In future versions of ModSecurity, more control and functionality + will be added to define "how" to block. + + Examples: + + In the following example, the second rule will "deny" because of + the SecDefaultAction disruptive action. The intent being that the + administrator could easily change this to another disruptive action + without editing the actual rules. + + ### Administrator defines "how" to block (deny,status:403)... +SecDefaultAction phase:2,deny,status:403,log,auditlog + +### Included from a rulest... +# Intent is to warn for this User Agent +SecRule REQUEST_HEADERS:User-Agent "perl" "phase:2,pass,msg:'Perl based user agent identified'" +# Intent is to block for this User Agent, "how" described in SecDefaultAction +SecRule REQUEST_HEADERS:User-Agent "nikto" "phase:2,block,msg:'Nikto Scanners Identified'" + + In the following example, The rule is reverted back to the + pass action defined in the SecDefaultAction directive + by using the SecRuleUpdateActionById directive in + conjuction with the block action. This allows an + administrator to override an action in a 3rd party rule without + modifying the rule itself. + + ### Administrator defines "how" to block (deny,status:403)... +SecDefaultAction phase:2,pass,log,auditlog + +### Included from a rulest... +SecRule REQUEST_HEADERS:User-Agent "nikto" "id:1,phase:2,deny,msg:'Nikto Scanners Identified'" + +### Added by the administrator +SecRuleUpdateActionById 1 "block" +
+
<literal>capture</literal> - Description: When used together - with the regular expression operator, capture action will create copies - of regular expression captures and place them into the transaction - variable collection. Up to ten captures will be copied on a successful - pattern match, each with a name consisting of a digit from 0 to - 9. + Description: When used together with the + regular expression operator, capture action will create copies of + regular expression captures and place them into the transaction variable + collection. Up to ten captures will be copied on a successful pattern + match, each with a name consisting of a digit from 0 to 9. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: - SecRule REQUEST_BODY "^username=(\w{25,})" phase:2,capture,t:none,chain + SecRule REQUEST_BODY "^username=(\w{25,})" phase:2,capture,t:none,chain SecRule TX:1 "(?:(?:a(dmin|nonymous)))" - Note + Note The 0 data captures the entire REGEX match and 1 captures the data - in the first parantheses, etc... + in the first parens, etc...
<literal>chain</literal> - Description: Chains the rule - where the action is placed with the rule that immediately follows it. - The result is called a rule chain. Chained rules - allow for more complex rule matches where you want to use a number of - different VARIABLES to create a better rule and to help prevent false + Description: Chains the rule where the action + is placed with the rule that immediately follows it. The result is + called a rule chain. Chained rules allow for more + complex rule matches where you want to use a number of different + VARIABLES to create a better rule and to help prevent false positives. - Action Group: Flow + Action Group: Flow Example: # Refuse to accept POST requests that do # not specify request body length -SecRule REQUEST_METHOD ^POST$ chain +SecRule REQUEST_METHOD ^POST$ chain SecRule REQUEST_HEADER:Content-Length ^$ - Note + Note In programming language concepts, think of chained rules somewhat similar to AND conditional statements. The actions specified in the first portion of the chained rule will only be triggered if all of the variable checks return positive hits. If one aspect of the chained rule is negative, then the entire rule chain is negative. Also note that - disruptive actions, execution phases, metadata actions (id, rev, msg) - and skip actions can only be specified on by the chain starter + disruptive actions, execution phases, metadata actions (id, rev, msg), + skip and skipAfter actions can only be specified on by the chain starter rule.
<literal>ctl</literal> - Description: The ctl action - allows configuration options to be updated for the transaction. + Description: The ctl action allows + configuration options to be updated for the transaction. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: # Parse requests with Content-Type "text/xml" as XML -SecRule REQUEST_CONTENT_TYPE ^text/xml nolog,pass,ctl:requestBodyProcessor=XML +SecRule REQUEST_CONTENT_TYPE ^text/xml nolog,pass,ctl:requestBodyProcessor=XML - Note + Note The following configuration options are supported: @@ -3348,6 +4200,11 @@ SecRule REQUEST_CONTENT_TYPE ^text/xml nolog,pass,ctl:requ debugLogLevel + + ruleRemoveById (single rule + ID, or a single rule ID range accepted as parameter) + + requestBodyAccess @@ -3378,16 +4235,18 @@ SecRule REQUEST_CONTENT_TYPE ^text/xml nolog,pass,ctl:requ one configuration directive and the usage is identical. The requestBodyProcessor option allows you to configure the - request body processor. By default ModSecurity will use the ModSecurity will use the URLENCODED and MULTIPART processors to process an application/x-www-form-urlencoded and a - multipart/form-data body, + multipart/form-data body, respectively. A third processor, XML, is also supported, but it is never - used implicitly. Instead you must tell ModSecurity to use it by placing - a few rules in the REQUEST_HEADERS - processing phase. After the request body was processed as XML you will - be able to use the XML-related features to inspect it. + used implicitly. Instead you must tell ModSecurity to use it by placing a few rules + in the REQUEST_HEADERS processing + phase. After the request body was processed as XML you will be able to + use the XML-related features to inspect it. Request body processors will not interrupt a transaction if an error occurs during parsing. Instead they will set variablesctl:requ
<literal>deny</literal> - Description: Stops rule - processing and intercepts transaction. + Description: Stops rule processing and + intercepts transaction. - Action Group: Disruptive + Action Group: Disruptive Example: - SecRule REQUEST_HEADERS:User-Agent "nikto" log,deny,msg:'Nikto Scanners Identified" + SecRule REQUEST_HEADERS:User-Agent "nikto" "log,deny,msg:'Nikto Scanners Identified'"
<literal>deprecatevar</literal> - Description: Decrement counter - based on its age. + Description: Decrement counter based on its + age. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: The following example will decrement the counter by 60 every 300 seconds. SecAction deprecatevar:session.score=60/300 - Note + Note Counter values are always positive, meaning the value will never go below zero. @@ -3435,11 +4292,11 @@ SecRule REQUEST_CONTENT_TYPE ^text/xml nolog,pass,ctl:requ
<literal>drop</literal> - Description: Immediately initiate - a "connection close" action to tear down the TCP connection by sending a + Description: Immediately initiate a + "connection close" action to tear down the TCP connection by sending a FIN packet. - Action Group: Disruptive + Action Group: Disruptive Example: The following example initiates an IP collection for tracking Basic Authentication attempts. If the client goes over the @@ -3450,9 +4307,9 @@ SecRule REQUEST_CONTENT_TYPE ^text/xml nolog,pass,ctl:requ SecRule ARGS:login "!^$" \ nolog,phase:1,setvar:ip.auth_attempt=+1,deprecatevar:ip.auth_attempt=20/120 SecRule IP:AUTH_ATTEMPT "@gt 25" \ - log,drop,phase:1,msg:'Possible Brute Force Attack" + log,drop,phase:1,msg:'Possible Brute Force Attack" - Note + Note This action is extremely useful when responding to both Brute Force and Denial of Service attacks in that, in both cases, you want to @@ -3464,51 +4321,62 @@ SecRule IP:AUTH_ATTEMPT "@gt 25" \
<literal>exec</literal> - Description: Executes an external - script/binary supplied as parameter. + Description: Executes an external + script/binary supplied as parameter. As of v2.5.0, if the parameter + supplied to exec is a Lua script (detected by the + .lua extension) the script will be processed + internally. This means you will get direct access + to the internal request context from the script. Please read the + SecRuleScript documentation for more details on how + to write Lua scripts. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: - SecRule REQUEST_URI "^/cgi-bin/script\.pl" \ - "log,exec:/usr/local/apache/bin/test.sh,phase:1" + # The following is going to execute /usr/local/apache/bin/test.sh +# as a shell script on rule match. +SecRule REQUEST_URI "^/cgi-bin/script\.pl" \ + "log,exec:/usr/local/apache/bin/test.sh" - Note +# The following is going to process /usr/local/apache/conf/exec.lua +# internally as a Lua script on rule match. +SecRule ARGS:p attack log,exec:/usr/local/apache/conf/exec.lua - This directive does not effect a primary action if it exists. This - action will always call script with no parameters, but providing all - information in the environment. All the usual CGI environment variables - will be there. You can have one binary executed per filter match. - Execution will add the header mod_security-executed to the list of - request headers. You should be aware that forking a threaded process - results in all threads being replicated in the new process. Forking can - therefore incur larger overhead in multithreaded operation. The script - you execute must write something (anything) to stdout. If it doesn't - ModSecurity will assume execution didn't work. + + This directive does not effect a primary action if it exists. + This action will always call script with no parameters, but providing + all information in the environment. All the usual CGI environment + variables will be there. You can have one binary executed per filter + match. Execution will add the header mod_security-executed to the list + of request headers. You should be aware that forking a threaded + process results in all threads being replicated in the new process. + Forking can therefore incur larger overhead in multi-threaded + operation. The script you execute must write something (anything) to + stdout. If it doesn't ModSecurity + will assume execution didn't work. +
<literal>expirevar</literal> - Description: Configurescollection - variable to expire after the given time in seconds. + Description: Configures a collection variable + to expire after the given time in seconds. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: SecRule REQUEST_COOKIES:JSESSIONID "!^$" nolog,phase:1,pass,chain SecAction setsid:%{REQUEST_COOKIES:JSESSIONID} SecRule REQUEST_URI "^/cgi-bin/script\.pl" \ - "log,allow,setvar:session.suspicious=1,expirevar:session.suspicious=3600,phase:1" + "log,allow,setvar:session.suspicious=1,expirevar:session.suspicious=3600,phase:1" - Note + Note You should use expirevar actions at the same time that you use - setvar actions in order to keep the indended expiration time. If they + setvar actions in order to keep the indented expiration time. If they are used on their own (perhaps in a SecAction directive) the expire time could get re-set. When variables are removed from collections, and there are no other changes, collections are not written to disk at the end of @@ -3519,43 +4387,64 @@ SecRule REQUEST_URI "^/cgi-bin/script\.pl" \
<literal>id</literal> - Description: Assigns a unique ID - to the rule or chain. + Description: Assigns a unique ID to the rule + or chain. - Action Group: Metadata + Action Group: Metadata Example: SecRule &REQUEST_HEADERS:Host "@eq 0" \ - "log,id:60008,severity:2,msg:'Request Missing a Host Header'" + "log,id:60008,severity:2,msg:'Request Missing a Host Header'" - Note + Note These are the reserved ranges: - 1 – 99999; reserved for your internal needs, use as you see - fit but don't publish them to others + 1-99,999; reserved for local (internal) use. Use as you see + fit but do not use this range for rules that are distributed to + others. 100,000-199,999; reserved for internal use of the engine, to - assign to rules that do not have explicit IDs + assign to rules that do not have explicit IDs. 200,000-299,999; reserved for rules published at - modsecurity.org + modsecurity.org. 300,000-399,999; reserved for rules published at - gotroot.com + gotroot.com. - 400,000 and above; unreserved range. + 400,000-419,999; unused (available for reservation). + + + + 420,000-429,999; reserved for ScallyWhack. + + + + 430,000-899,999; unused (available for reservation). + + + + 900,000-999,999; reserved for the Core Rules + project. + + + + 1,000,000 and above; unused (available for + reservation).
@@ -3563,19 +4452,18 @@ SecRule REQUEST_URI "^/cgi-bin/script\.pl" \
<literal>initcol</literal> - Description: Initialises a named - persistent collection, either by loading data from storage or by - creating a new collection in memory. + Description: Initialises a named persistent + collection, either by loading data from storage or by creating a new + collection in memory. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: The following example initiates IP address tracking. - SecAction initcol:ip=%{REMOTE_ADDR},nolog + SecAction initcol:ip=%{REMOTE_ADDR},nolog - Note + Note Every collection contains several built-in variables that are read-only: @@ -3586,6 +4474,11 @@ SecRule REQUEST_URI "^/cgi-bin/script\.pl" \ the creation of the collection. + + IS_NEW - set to 1 if the + collection is new (not yet persisted) otherwise set to 0. + + KEY - the value of the initcol variable (the client's IP address in the example). @@ -3597,7 +4490,7 @@ SecRule REQUEST_URI "^/cgi-bin/script\.pl" \ - TIMEOUT- date/time in + TIMEOUT - date/time in seconds when the collection will be updated on disk from memory (if no other updates occur). @@ -3634,12 +4527,13 @@ SecRule REQUEST_URI "^/cgi-bin/script\.pl" \ - Please note that ModSecurity does not implement atomic updates - of persistent variables at this time. Variables are read from storage - whenever initcol is encountered in the rules and - persisted at the end of request processing. On busy servers requests - often run in parallel, leading to situations where one request - overwrites the changes made by another request. We anticipate + Please note that ModSecurity does not implement atomic + updates of persistent variables at this time. Variables are read from + storage whenever initcol is encountered in the + rules and persisted at the end of request processing. On busy servers + requests often run in parallel, leading to situations where one + request overwrites the changes made by another request. We anticipate implementing atomic updates of counter values in a future version. @@ -3648,38 +4542,56 @@ SecRule REQUEST_URI "^/cgi-bin/script\.pl" \
<literal>log</literal> - Description: Indicates that a - successful match of the rule needs to be logged. + Description: Indicates that a successful + match of the rule needs to be logged. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: - SecAction initcol:ip=%{REMOTE_ADDR},log + SecAction initcol:ip=%{REMOTE_ADDR},log - Note + Note This action will log matches to the Apache error log file and the - ModSecurity audit log. + ModSecurity audit log. +
+ +
+ <literal>logdata</literal> + + Description: Allows logging a data + fragment. + + Action Group: Metadata + + Example: + + SecRule &ARGS:p "@eq 0" "log,logdata:'%{TX.0}'" + + Note + + The logdata information appears in the error and/or audit log + files and is not sent back to the client in response headers. Macro + expansion is preformed so you may use variable names such as %{TX.0}, + etc. The information is properly escaped for use with logging binary + data.
<literal>msg</literal> - Description: Assigns a custom - message to the rule or chain. + Description: Assigns a custom message to the + rule or chain. - Action Group: Metadata + Action Group: Metadata Example: SecRule &REQUEST_HEADERS:Host "@eq 0" \ - "log,id:60008,severity:2,msg:'Request Missing a Host Header'" + "log,id:60008,severity:2,msg:'Request Missing a Host Header'" - Note + Note The msg information appears in the error and/or audit log files and is not sent back to the client in response headers. @@ -3688,19 +4600,19 @@ SecRule REQUEST_URI "^/cgi-bin/script\.pl" \
<literal>multiMatch</literal> - Description: If enabled - ModSecurity will perform multiple operator invocations for every target, - before and after every anti-evasion transformation is performed. + Description: If enabled ModSecurity will perform multiple operator + invocations for every target, before and after every anti-evasion + transformation is performed. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: SecDefaultAction log,deny,phase:1,t:removeNulls,t:lowercase -SecRule ARGS "attack" multiMatch +SecRule ARGS "attack" multiMatch - Note + Note Normally, variables are evaluated once, only after all transformation functions have completed. With multiMatch, variables are @@ -3711,25 +4623,23 @@ SecRule ARGS "attack" multiMatch <literal>noauditlog</literal> - Description: Indicates that a - successful match of the rule should not be used as criteria whether the - transaction should be logged to the audit log. + Description: Indicates that a successful + match of the rule should not be used as criteria whether the transaction + should be logged to the audit log. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: - SecRule REQUEST_HEADERS:User-Agent "Test" allow,noauditlog + SecRule REQUEST_HEADERS:User-Agent "Test" allow,noauditlog - Note + Note If the SecAuditEngine is set to On, all of the transactions will be logged. If it is set to RelevantOnly, then you can control it with the noauditlog action. Even if the noauditlog action is applied to a specific rule and a rule either before or after triggered an audit - event, then the tranaction will be logged to the audit log. The correct + event, then the transaction will be logged to the audit log. The correct way to disable audit logging for the entire transaction is to use "ctl:auditEngine=Off"
@@ -3737,18 +4647,16 @@ SecRule ARGS "attack" multiMatch <literal>nolog</literal> - Description: Prevents rule - matches from appearing in both the error and audit logs. + Description: Prevents rule matches from + appearing in both the error and audit logs. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: - SecRule REQUEST_HEADERS:User-Agent "Test" allow,nolog + SecRule REQUEST_HEADERS:User-Agent "Test" allow,nolog - Note + Note The nolog action also implies noauditlog.
@@ -3756,17 +4664,16 @@ SecRule ARGS "attack" multiMatch <literal>pass</literal> - Description: Continues processing - with the next rule in spite of a successful match. + Description: Continues processing with the + next rule in spite of a successful match. - Action Group: Disruptive + Action Group: Disruptive Example: - SecRule REQUEST_HEADERS:User-Agent "Test" log,pass + SecRule REQUEST_HEADERS:User-Agent "Test" log,pass - Note + Note Transaction will not be interrupted but it will be logged (unless logging has been suppressed). @@ -3775,17 +4682,16 @@ SecRule ARGS "attack" multiMatch <literal>pause</literal> - Description: Pauses transaction - processing for the specified number of milliseconds. + Description: Pauses transaction processing + for the specified number of milliseconds. - Action Group: Disruptive + Action Group: Disruptive Example: - SecRule REQUEST_HEADERS:User-Agent "Test" log,deny,status:403,pause:5000 + SecRule REQUEST_HEADERS:User-Agent "Test" log,deny,status:403,pause:5000 - Note + Note This feature can be of limited benefit for slowing down Brute Force Scanners, however use with care. If you are under a Denial of @@ -3797,18 +4703,17 @@ SecRule ARGS "attack" multiMatch <literal>phase</literal> - Description: Places the rule (or - the rule chain) into one of five available processing phases. + Description: Places the rule (or the rule + chain) into one of five available processing phases. - Action Group: Disruptive + Action Group: Disruptive Example: - SecDefaultAction log,deny,phase:1,t:removeNulls,t:lowercase + SecDefaultAction log,deny,phase:1,t:removeNulls,t:lowercase SecRule REQUEST_HEADERS:User-Agent "Test" log,deny,status:403 - Note + Note Keep in mind that is you specify the incorrect phase, the target variable that you specify may be empty. This could lead to a false @@ -3818,42 +4723,37 @@ SecRule REQUEST_HEADERS:User-Agent "Test" log,deny,status:403
- prepend (Experimental) + prepend - Description: Prepends text given - as parameter to the response body. For this action to work content - injection must be enabled by setting - SecContentInjection to On. Also - make sure you check the content type of the response before you make - changes to it (e.g. you don't want to inject stuff into images). + Description: Prepends text given as parameter + to the response body. For this action to work content injection must be + enabled by setting SecContentInjection to + On. Also make sure you check the content type of the + response before you make changes to it (e.g. you don't want to inject + stuff into images). - Action Group: - Non-Disruptive + Action Group: Non-Disruptive - Processing Phases: 3 and - 4. + Processing Phases: 3 and 4. Example: - SecRule RESPONSE_CONTENT_TYPE ^text/html "phase:3,nolog,pass,prepend:'Header<br>'" + SecRule RESPONSE_CONTENT_TYPE ^text/html "phase:3,nolog,pass,prepend:'Header<br>'"
<literal>proxy</literal> - Description: Intercepts - transaction by forwarding request to another web server using the proxy - backend. + Description: Intercepts transaction by + forwarding request to another web server using the proxy backend. - Action Group: Disruptive + Action Group: Disruptive Example: - SecRule REQUEST_HEADERS:User-Agent "Test" log,proxy:http://www.honeypothost.com/ + SecRule REQUEST_HEADERS:User-Agent "Test" log,proxy:http://www.honeypothost.com/ - Note + Note For this action to work, mod_proxy must also be installed. This action is useful if you would like to proxy matching requests onto a @@ -3863,17 +4763,17 @@ SecRule REQUEST_HEADERS:User-Agent "Test" log,deny,status:403
<literal>redirect</literal> - Description: Intercepts - transaction by issuing a redirect to the given location. + Description: Intercepts transaction by + issuing a redirect to the given location. - Action Group: Disruptive + Action Group: Disruptive Example: SecRule REQUEST_HEADERS:User-Agent "Test" \ - log,redirect:http://www.hostname.com/failed.html + log,redirect:http://www.hostname.com/failed.html - Note + Note If the status action is present and its value is acceptable (301, 302, 303, or 307) it will be used for @@ -3883,17 +4783,15 @@ SecRule REQUEST_HEADERS:User-Agent "Test" log,deny,status:403
<literal>rev</literal> - Description: Specifies rule - revision. + Description: Specifies rule revision. - Action Group: Metadata + Action Group: Metadata Example: - SecRule REQUEST_METHOD "^PUT$" "id:340002,rev:1,severity:2,msg:'Restricted HTTP function'" + SecRule REQUEST_METHOD "^PUT$" "id:340002,rev:1,severity:2,msg:'Restricted HTTP function'" - Note + Note This action is used in combination with the id action to allow the same rule ID to be used @@ -3904,19 +4802,17 @@ SecRule REQUEST_HEADERS:User-Agent "Test" log,deny,status:403
<literal>sanitiseArg</literal> - Description: Sanitises (replaces - each byte with an asterisk) a named request argument prior to audit + Description: Sanitises (replaces each byte + with an asterisk) a named request argument prior to audit logging. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: - SecAction nolog,phase:2,sanitiseArg:password + SecAction nolog,phase:2,sanitiseArg:password - Note + Note The sanitize actions do not sanitize any data within the actual raw requests but only on the copy of data within memory that is set to @@ -3928,22 +4824,20 @@ SecRule REQUEST_HEADERS:User-Agent "Test" log,deny,status:403
<literal>sanitiseMatched</literal> - Description: Sanitises the - variable (request argument, request header, or response header) that - caused a rule match. + Description: Sanitises the variable (request + argument, request header, or response header) that caused a rule + match. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: This action can be used to sanitise arbitrary transaction elements when they match a condition. For example, the example below will sanitise any argument that contains the word password in the name. - SecRule ARGS_NAMES password nolog,pass,sanitiseMatched + SecRule ARGS_NAMES password nolog,pass,sanitiseMatched - Note + Note Same note as sanitiseArg.
@@ -3951,19 +4845,17 @@ SecRule REQUEST_HEADERS:User-Agent "Test" log,deny,status:403
<literal>sanitiseRequestHeader</literal> - Description: Sanitises a named - request header. + Description: Sanitises a named request + header. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: This will sanitise the data in the Authorization header. - SecAction log,phase:1,sanitiseRequestHeader:Authorization + SecAction log,phase:1,sanitiseRequestHeader:Authorization - Note + Note Same note as sanitiseArg.
@@ -3971,19 +4863,17 @@ SecRule REQUEST_HEADERS:User-Agent "Test" log,deny,status:403
<literal>sanitiseResponseHeader</literal> - Description: Sanitises a named - response header. + Description: Sanitises a named response + header. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: This will sanitise the Set-Cookie data sent to the client. - SecAction log,phase:3,sanitiseResponseHeader:Set-Cookie + SecAction log,phase:3,sanitiseResponseHeader:Set-Cookie - Note + Note Same note as sanitiseArg.
@@ -3991,70 +4881,76 @@ SecRule REQUEST_HEADERS:User-Agent "Test" log,deny,status:403
<literal>severity</literal> - Description: Assigns severity to - the rule it is placed with. + Description: Assigns severity to the rule it + is placed with. - Action Group: Metadata + Action Group: Metadata Example: - SecRule REQUEST_METHOD "^PUT$" "id:340002,rev:1,severity:2,msg:'Restricted HTTP function'" + SecRule REQUEST_METHOD "^PUT$" "id:340002,rev:1,severity:CRITICAL,msg:'Restricted HTTP function'" - Note + Note - The severity numbers follow the Syslog convention: + Severity values in ModSecurity follow those of syslog, as + below: - 0 = EMERGENCY + 0 - EMERGENCY - 1 = ALERT + 1 - ALERT - 2 = CRITICAL + 2 - CRITICAL - 3 = ERROR + 3 - ERROR - 4 = WARNING + 4 - WARNING - 5 = NOTICE + 5 - NOTICE - 6 = INFO + 6 - INFO - 7 = DEBUG + 7 - DEBUG + + It is possible to specify severity levels using either the + numerical values or the text values. You should always specify severity + levels using the text values. The use of the numerical values is + deprecated (as of v2.5.0) and may be removed in one of the susequent + major updates.
<literal>setuid</literal> - Description: Special-purpose - action that initialises the USER + Description: Special-purpose action that + initialises the USER collection. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: - SecAction setuid:%{REMOTE_USER},nolog + SecAction setuid:%{REMOTE_USER},nolog - Note + Note After initialisation takes place the variable USERID will be available for use in the @@ -4064,23 +4960,22 @@ SecRule REQUEST_HEADERS:User-Agent "Test" log,deny,status:403
<literal>setsid</literal> - Description: - Special-purposeaction that initialises the SESSION collection. + Description: Special-purpose action that + initialises the SESSION + collection. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: # Initialise session variables using the session cookie value SecRule REQUEST_COOKIES:PHPSESSID !^$ chain,nolog,pass -SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} +SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} - Note + Note On first invocation of this action the collection will be empty - (not taking the pre-defined variables into account - see initcol for more information). On subsequent invocations the contents of the collection (session, in this case) will be retrieved from storage. After initialisation takes place the @@ -4093,11 +4988,10 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} <literal>setenv</literal> - Description: Creates, removes, or - updates an environment variable. + Description: Creates, removes, or updates an + environment variable. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Examples: @@ -4110,7 +5004,7 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID}setenv:!name - Note + Note This action can be used to establish communication with other Apache modules. @@ -4119,11 +5013,10 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} <literal>setvar</literal> - Description: Creates, removes, or - updates a variable in the specified collection. + Description: Creates, removes, or updates a + variable in the specified collection. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Examples: @@ -4135,10 +5028,9 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID}setvar:!tx.score - To increase or decrease variable value use+and-characters in front of a numerical - value: + To increase or decrease variable value use + and - + characters in front of a numerical value: setvar:tx.score=+5
@@ -4146,16 +5038,14 @@ SecAction setsid:%{REQUEST_COOKIES.PHPSESSID} <literal>skip</literal> - Description: Skips one or more - rules (or chains) on successful match. + Description: Skips one or more rules (or + chains) on successful match. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: - SecRule REQUEST_URI "^/$" "chain,skip:2" + SecRule REQUEST_URI "^/$" "chain,skip:2" SecRule REMOTE_ADDR "^127\.0\.0\.1$" "chain" SecRule REQUEST_HEADERS:User-Agent "^Apache \(internal dummy connection\)$" "t:none" SecRule &REQUEST_HEADERS:Host "@eq 0" \ @@ -4163,34 +5053,61 @@ SecRule &REQUEST_HEADERS:Host "@eq 0" \ SecRule &REQUEST_HEADERS:Accept "@eq 0" \ "log,deny,log,status:400,id:960015,msg:'Request Missing an Accept Header'" - Note + Note Skip only applies to the current processing phase and not necessarily the order in which the rules appear in the configuration file. If you group rules by processing phases, then skip should work as expected. This action can not be used to skip rules within one chain. - Accepts a single paramater denoting the number of rules (or chains) to + Accepts a single parameter denoting the number of rules (or chains) to skip.
+
+ <literal>skipAfter</literal> + + Description: Skips rules (or chains) on + successful match resuming rule execution after the specified rule id or + marker (see SecMarker) is found. + + Action Group: Non-Disruptive + + Example: + + SecRule REQUEST_URI "^/$" "chain,skipAfter:960015" +SecRule REMOTE_ADDR "^127\.0\.0\.1$" "chain" +SecRule REQUEST_HEADERS:User-Agent "^Apache \(internal dummy connection\)$" "t:none" +SecRule &REQUEST_HEADERS:Host "@eq 0" \ + "deny,log,status:400,id:960008,severity:4,msg:'Request Missing a Host Header'" +SecRule &REQUEST_HEADERS:Accept "@eq 0" \ + "log,deny,log,status:400,id:960015,msg:'Request Missing an Accept Header'" + + Note + + SkipAfter only applies to the current processing phase and not + necessarily the order in which the rules appear in the configuration + file. If you group rules by processing phases, then skip should work as + expected. This action can not be used to skip rules within one chain. + Accepts a single parameter denoting the last rule ID to skip. +
+
<literal>status</literal> - Description: Specifies the - response status code to use with actions - deny and redirect. + Description: Specifies the response status + code to use with actions deny + and redirect. - Action Group: Disruptive + Action Group: Disruptive Example: - SecDefaultAction log,deny,status:403,phase:1 + SecDefaultAction log,deny,status:403,phase:1 - Note + Note - Staus actions defined in Apache scope locations (such as - Directory, Location, etc...) may be superceded by phase:1 action + Status actions defined in Apache scope locations (such as + Directory, Location, etc...) may be superseded by phase:1 action settings. The Apache ErrorDocument directive will be triggered if present in the configuration. Therefore if you have previously defined a custom error page for a given status then it will be executed and its @@ -4200,48 +5117,65 @@ SecRule &REQUEST_HEADERS:Accept "@eq 0" \
<literal>t</literal> - Description: This action can be - used which transformation function should be used against the specified - variables before they (or the results, rather) are run against the - operator specified in the rule. + Description: This action can be used which + transformation function should be used against the specified variables + before they (or the results, rather) are run against the operator + specified in the rule. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: SecDefaultAction log,deny,phase:1,t:removeNulls,t:lowercase SecRule REQUEST_COOKIES:SESSIONID "47414e81cbbef3cf8366e84eeacba091" \ - log,deny,status:403,t:md5 + log,deny,status:403,t:md5,t:hexEncode - Note + Note Any transformation functions that you specify in a SecRule will be - in addtion to previous ones specified in SecDefaultAction. Use of + in addition to previous ones specified in SecDefaultAction. Use of "t:none" will remove all transformation functions for the specified rule.
+
+ <literal>tag</literal> + + Description: Assigns custom text to a rule or + chain. + + Action Group: Metadata + + Example: + + SecRule REQUEST_FILENAME "\b(?:n(?:map|et|c)|w(?:guest|sh)|cmd(?:32)?|telnet|rcmd|ftp)\.exe\b" \ + "deny,msg:'System Command Access',id:'950002',tag:'WEB_ATTACK/FILE_INJECTION',tag:'OWASP/A2',severity:'2'" + + Note + + The tag information appears in the error and/or audit log files. + Its intent is to be used to automate classification of rules and the + alerts generated by rules. Multiple tags can be used per + rule/chain. +
+
<literal>xmlns</literal> - Description: This action should - be used together with an XPath expression to register a - namespace. + Description: This action should be used + together with an XPath expression to register a namespace. - Action Group: - Non-Disruptive + Action Group: Non-Disruptive Example: SecRule REQUEST_HEADERS:Content-Type "text/xml" \ - phase:1,pass,ctl:requestBodyProcessor=XML,ctl:requestBodyAccess=On,xmlns:xsd="http://www.w3.org/2001/XMLSchema" + phase:1,pass,ctl:requestBodyProcessor=XML,ctl:requestBodyAccess=On,xmlns:xsd="http://www.w3.org/2001/XMLSchema" SecRule XML:/soap:Envelope/soap:Body/q1:getInput/id() "123" phase:2,deny
-
+
Operators A number of operators can be used in rules, as documented below. The @@ -4251,84 +5185,78 @@ SecRule XML:/soap:Envelope/soap:Body/q1:getInput/id() "123" phase:2,deny <literal>beginsWith</literal> - Description: This operator is a - string comparison and returns true if the parameter value is found at - the beginning of the input. Macro expansion is performed so you may use + Description: This operator is a string + comparison and returns true if the parameter value is found at the + beginning of the input. Macro expansion is performed so you may use variable names such as %{TX.1}, etc. Example: - SecRule REQUEST_LINE "!@beginsWith GET" t:none,deny,status:403 + SecRule REQUEST_LINE "!@beginsWith GET" t:none,deny,status:403 SecRule REQUEST_ADDR "^(.*)\.\d+$" deny,status:403,capture,chain -SecRule ARGS:gw "!@beginsWith %{TX.1}" +SecRule ARGS:gw "!@beginsWith %{TX.1}"
<literal>contains</literal> - Description: This operator is a - string comparison and returns true if the parameter value is found - anywhere in the input. Macro expansion is performed so you may use - variable names such as %{TX.1}, etc. + Description: This operator is a string + comparison and returns true if the parameter value is found anywhere in + the input. Macro expansion is performed so you may use variable names + such as %{TX.1}, etc. Example: - SecRule REQUEST_LINE "!@contains .php " t:none,deny,status:403 + SecRule REQUEST_LINE "!@contains .php" t:none,deny,status:403 SecRule REQUEST_ADDR "^(.*)$" deny,status:403,capture,chain -SecRule ARGS:ip "!@contains %{TX.1}" +SecRule ARGS:ip "!@contains %{TX.1}"
<literal>endsWith</literal> - Description: This operator is a - string comparison and returns true if the parameter value is found at - the end of the input. Macro expansion is performed so you may use - variable names such as %{TX.1}, etc. + Description: This operator is a string + comparison and returns true if the parameter value is found at the end + of the input. Macro expansion is performed so you may use variable names + such as %{TX.1}, etc. Example: - SecRule REQUEST_LINE "!@endsWith HTTP/1.1" t:none,deny,status:403 -SecRule ARGS:route "!@endsWith %{REQUEST_ADDR}" t:none,deny,status:403 + SecRule REQUEST_LINE "!@endsWith HTTP/1.1" t:none,deny,status:403 +SecRule ARGS:route "!@endsWith %{REQUEST_ADDR}" t:none,deny,status:403
<literal>eq</literal> - Description: This operator is a - numerical comparison and stands for "equal to." + Description: This operator is a numerical + comparison and stands for "equal to." Example: - SecRule &REQUEST_HEADERS_NAMES "@eq 15" + SecRule &REQUEST_HEADERS_NAMES "@eq 15"
<literal>ge</literal> - Description: This operator is a - numerical comparison and stands for "greater than or equal to." + Description: This operator is a numerical + comparison and stands for "greater than or equal to." Example: - SecRule &REQUEST_HEADERS_NAMES "@ge 15" + SecRule &REQUEST_HEADERS_NAMES "@ge 15"
<literal>geoLookup</literal> - Description: This operator looks - up various data fields from an IP address or hostname. The results will - be captured in the GEO - collection. + Description: This operator looks up various + data fields from an IP address or hostname. The results will be captured + in the GEO collection. You must provide a database via SecGeoLookupsDb before this operator can be + moreinfo="none">SecGeoLookupDb before this operator can be used. See the GEO variable for an @@ -4338,64 +5266,84 @@ SecRule ARGS:route "!@endsWith %{REQUEST_ADDR}"
<literal>gt</literal> - Description: This operator is a - numerical comparison and stands for "greater than." + Description: This operator is a numerical + comparison and stands for "greater than." Example: - SecRule &REQUEST_HEADERS_NAMES "@gt 15" + SecRule &REQUEST_HEADERS_NAMES "@gt 15"
<literal>inspectFile</literal> - Description: Executes the - external script/binary given as parameter to the operator against every - file extracted from the request. + Description: Executes the external + script/binary given as parameter to the operator against every file + extracted from the request. As of v2.5.0, if the supplied filename is + not absolute it is treated as relative to the directory in which the + configuration file resides. Also as of v2.5.0, if the filename is + determined to be a Lua script (based on its extension) the script will + be processed by the internal engine. As such it will have full access to + the ModSecurity context. - Example: + Example of using an external binary/script: - SecRule FILES_TMPNAMES "@inspectFile /opt/apache/bin/inspect_script.pl" + # Execute external script to validate uploaded files. +SecRule FILES_TMPNAMES "@inspectFile /opt/apache/bin/inspect_script.pl" + + Example of using Lua script: + + SecRule FILES_TMPNANMES "@inspectFile inspect.lua" + + Script inspect.lua: + + function main(filename) + -- Do something to the file to verify it. In this example, we + -- read up to 10 characters from the beginning of the file. + local f = io.open(filename, "rb"); + local d = f:read(10); + f:close(); + + -- Return null if there is no reason to believe there is ansything + -- wrong with the file (no match). Returning any text will be taken + -- to mean a match should be trigerred. + return null; +end
<literal>le</literal> - Description: This operator is a - numerical comparison and stands for "less than or equal to." + Description: This operator is a numerical + comparison and stands for "less than or equal to." Example: - SecRule &REQUEST_HEADERS_NAMES "@le 15" + SecRule &REQUEST_HEADERS_NAMES "@le 15"
<literal>lt</literal> - Description: This operator is a - numerical comparison and stands for "less than." + Description: This operator is a numerical + comparison and stands for "less than." Example: - SecRule &REQUEST_HEADERS_NAMES "@lt 15" + SecRule &REQUEST_HEADERS_NAMES "@lt 15"
<literal>pm</literal> - Description: Phrase Match - operator. This operator uses a set based matching engine (Aho-Corasick) - for faster matches of keyword lists. It will match any one of its - arguments anywhere in the target value. + Description: Phrase Match operator. This + operator uses a set based matching engine (Aho-Corasick) for faster + matches of keyword lists. It will match any one of its arguments + anywhere in the target value. Example: - SecRule REQUEST_HEADERS:User-Agent "@pm WebZIP WebCopier Webster WebStripper SiteSnagger ProWebWalker CheeseBot" "deny,status:403 + SecRule REQUEST_HEADERS:User-Agent "@pm WebZIP WebCopier Webster WebStripper SiteSnagger ProWebWalker CheeseBot" "deny,status:403 The above would deny access with 403 if any of the words matched within the User-Agent HTTP header value. @@ -4404,9 +5352,9 @@ SecRule ARGS:route "!@endsWith %{REQUEST_ADDR}"
<literal>pmFromFile</literal> - Description: Phrase Match - operator. This operator uses a set based matching engine (Aho-Corasick) - for faster matches of keyword lists. This operator is the same as + Description: Phrase Match operator. This + operator uses a set based matching engine (Aho-Corasick) for faster + matches of keyword lists. This operator is the same as @pm except that it takes a list of files as arguments. It will match any one of the phrases listed in the file(s) anywhere in the target value. @@ -4431,8 +5379,7 @@ SecRule ARGS:route "!@endsWith %{REQUEST_ADDR}" Example: - SecRule REQUEST_HEADERS:User-Agent "@pm /path/to/blacklist1 blacklist2" "deny,status:403 + SecRule REQUEST_HEADERS:User-Agent "@pm /path/to/blacklist1 blacklist2" "deny,status:403 The above would deny access with 403 if any of the patterns in the two files matched within the User-Agent HTTP header value. The @@ -4443,33 +5390,32 @@ SecRule ARGS:route "!@endsWith %{REQUEST_ADDR}"
<literal>rbl</literal> - Description: Look up the - parameter in the RBL given as parameter. Parameter can be an IPv4 - address, or a hostname. + Description: Look up the parameter in the RBL + given as parameter. Parameter can be an IPv4 address, or a + hostname. Example: - SecRule REMOTE_ADDR "@rbl sc.surbl.org" + SecRule REMOTE_ADDR "@rbl sc.surbl.org"
<literal>rx</literal> - Description: Regular expression - operator. This is the default operator, so if the "@" operator is not - defined, it is assumed to be rx. + Description: Regular expression operator. + This is the default operator, so if the "@" operator is not defined, it + is assumed to be rx. Example: - SecRule REQUEST_HEADERS:User-Agent "@rx nikto" + SecRule REQUEST_HEADERS:User-Agent "@rx nikto" - Note + Note Regular expressions are handled by the PCRE library (http://www.pcre.org). ModSecurity - compiles its regular expressions with the following settings: + url="http://www.pcre.org">http://www.pcre.org). ModSecurity compiles its regular expressions + with the following settings: @@ -4480,10 +5426,9 @@ SecRule ARGS:route "!@endsWith %{REQUEST_ADDR}" All matches are case-sensitive. If you do not care about case sensitivity you either need to implement the lowercase transformational function, or - use the per-pattern(?i)modificator, as allowed by - PCRE. + moreinfo="none">lowercase transformation function, or use + the per-pattern(?i)modifier, as + allowed by PCRE. @@ -4491,7 +5436,7 @@ SecRule ARGS:route "!@endsWith %{REQUEST_ADDR}" PCRE_DOLLAR_ENDONLY flags are set during compilation, meaning a single dot will match any character, including the newlines and a $ - end anchor will not match a trailing newline charater. + end anchor will not match a trailing newline character.
@@ -4499,44 +5444,44 @@ SecRule ARGS:route "!@endsWith %{REQUEST_ADDR}"
<literal>streq</literal> - Description: This operator is a - string comparison and returns true if the parameter value matches the - input exactly. Macro expansion is performed so you may use variable - names such as %{TX.1}, etc. + Description: This operator is a string + comparison and returns true if the parameter value matches the input + exactly. Macro expansion is performed so you may use variable names such + as %{TX.1}, etc. Example: - SecRule ARGS:foo "!@streq bar" t:none,deny,status:403 + SecRule ARGS:foo "!@streq bar" t:none,deny,status:403 SecRule REQUEST_ADDR "^(.*)$" deny,status:403,capture,chain -SecRule REQUEST_HEADERS:Ip-Address "!@streq %{TX.1}" +SecRule REQUEST_HEADERS:Ip-Address "!@streq %{TX.1}"
<literal>validateByteRange</literal> - Description: Validates the byte - range used in the variable falls into the specified range. + Description: Validates the byte range used in + the variable falls into the specified range. Example: - SecRule ARG:text "@validateByteRange 10, 13, 32-126" + SecRule ARG:text "@validateByteRange 10, 13, 32-126" - Note + Note You can force requests to consist only of bytes from a certain byte range. This can be useful to avoid stack overflow attacks (since they usually contain "random" binary content). Default range values are 0 and 255, i.e. all byte values are allowed. This directive does not - check byte range in a POST payload when multipart/form-data encoding - (file upload) is used. Doing so would prevent binary files from being - uploaded. However, after the parameters are extracted from such request - they are checked for a valid range. + check byte range in a POST payload when + multipart/form-data encoding (file upload) is used. + Doing so would prevent binary files from being uploaded. However, after + the parameters are extracted from such request they are checked for a + valid range. - validateByteRange is similar to the ModSecurity 1.X - SecFilterForceByteRange Directive however since it works in a rule - context, it has the following differences: + validateByteRange is similar to the ModSecurity 1.X SecFilterForceByteRange + Directive however since it works in a rule context, it has the following + differences: @@ -4558,31 +5503,31 @@ SecRule REQUEST_HEADERS:Ip-Address "!@streq %{TX.1} <literal>validateDTD</literal> - Description: This operator - requires the request body to be processed as XML. + Description: This operator requires the + request body to be processed as XML. Example: SecDefaultAction log,deny,status:403,phase:2 SecRule REQUEST_HEADERS:Content-Type ^text/xml$ \ phase:1,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML -SecRule REQBODY_PROCESSOR "!^XML$" nolog,pass,skip:1 -SecRule XML "@validateDTD /path/to/apache2/conf/xml.dtd" +SecRule REQBODY_PROCESSOR "!^XML$" nolog,pass,skipAfter:12345 +SecRule XML "@validateDTD /path/to/apache2/conf/xml.dtd,id:12345"
<literal>validateSchema</literal> - Description: This operator - requires the request body to be processed as XML. + Description: This operator requires the + request body to be processed as XML. Example: SecDefaultAction log,deny,status:403,phase:2 SecRule REQUEST_HEADERS:Content-Type ^text/xml$ \ phase:1,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML -SecRule REQBODY_PROCESSOR "!^XML$" nolog,pass,skip:1 -SecRule XML "@validateSchema /path/to/apache2/conf/xml.xsd" +SecRule REQBODY_PROCESSOR "!^XML$" nolog,pass,skipAfter:12345 +SecRule XML "@validateSchema /path/to/apache2/conf/xml.xsd,id:12345" This operator requires request body to be processed as XML.
@@ -4590,35 +5535,34 @@ SecRule XML "@validateSchema /path/to/apache2/conf/xml.xsd
<literal>validateUrlEncoding</literal> - Description: Verifies the - encodings used in the variable (if any) are valid. + Description: Verifies the encodings used in + the variable (if any) are valid. Example: - SecRule ARGS "@validateUrlEncoding" + SecRule ARGS "@validateUrlEncoding" - Note + Note URL encoding is an HTTP standard for encoding byte values within a URL. The byte is escaped with a % followed by two hexadecimal values (0-F). This directive does not check encoding in a POST payload when the - multipart/form-data encoding (file upload) is used. It is not necessary - to do so because URL encoding is not used for this encoding. + multipart/form-data encoding (file upload) is used. + It is not necessary to do so because URL encoding is not used for this + encoding.
<literal>validateUtf8Encoding</literal> - Description: Verifies the - variable is a valid UTF-8 encoded string. + Description: Verifies the variable is a valid + UTF-8 encoded string. Example: - SecRule ARGS "@validateUtf8Encoding" + SecRule ARGS "@validateUtf8Encoding" - Note + Note UTF-8 encoding is valid on most web servers. Integer values between 0-65535 are encoded in a UTF-8 byte sequence that is escaped by @@ -4629,8 +5573,8 @@ SecRule XML "@validateSchema /path/to/apache2/conf/xml.xsd Not enough bytes. UTF-8 supports two, three, four, five, and - six byte encodings. ModSecurity will locate cases when a byte or - more is missing. + six byte encodings. ModSecurity + will locate cases when a byte or more is missing. @@ -4650,23 +5594,536 @@ SecRule XML "@validateSchema /path/to/apache2/conf/xml.xsd
+
+ <literal>verifyCC</literal> + + Description: This operator verifies a given + regular expression as a potential credit card number. It first matches + with a single generic regular expression then runs the resulting match + through a Luhn checksum algorithm to further verify it as a potential + credit card number. + + Example: + + SecRule ARGS "@verifyCC \d{13,16}" \ + "phase:2,sanitiseMatched,log,auditlog,pass,msg:'Potential credit card number'" +
+
<literal>within</literal> - Description: This operator is a - string comparison and returns true if the input value is found anywhere - within the parameter value. Note that this is similar to + Description: This operator is a string + comparison and returns true if the input value is found anywhere within + the parameter value. Note that this is similar to @contains, except that the target and match values are reversed. Macro expansion is performed so you may use variable names such as %{TX.1}, etc. Example: - SecRule REQUEST_METHOD "!@within get,post,head" t:lowercase,deny,status:403 + SecRule REQUEST_METHOD "!@within get,post,head" t:lowercase,deny,status:403 SecAction "pass,setvar:'tx.allowed_methods=get,post,head'" -SecRule REQUEST_METHOD "!@within %{tx.allowed_methods}" t:lowercase,deny,status:403 +SecRule REQUEST_METHOD "!@within %{tx.allowed_methods}" t:lowercase,deny,status:403
-
+ +
+ Data Formats + + This section documents the various data formats used by ModSecurity. + +
+ Alerts + + Below is an example of a ModSecurity alert entry. It is always + contained on a single line but we've broken it here into multiple lines + for readability. + + Access denied with code 505 (phase 1). Match of "rx ^HTTP/(0\\\\.9|1\\\\.[01])$" +against "REQUEST_PROTOCOL" required. [id "960034"] [msg "HTTP protocol version +is not allowed by policy"] [severity "CRITICAL"] [uri "/"] [unique_id +"PQaTTVBEUOkAAFwKXrYAAAAM"] + + Each alert entry begins with the engine message: + + Access denied with code 505 (phase 1). Match of "rx ^HTTP/(0\\\\.9|1\\\\.[01])$" +against "REQUEST_PROTOCOL" required. + + The engine message consists of two parts. The first part tells you + whether ModSecurity acted to + interrupt transaction or rule processing. If it did nothing the first + part of the message will simply say "Warning". If an action was taken + then one of the following messages will be used: + + + + Access denied with code %0 - a response + with status code %0 was sent. + + + + Access denied with connection close - + connection was abruptly closed. + + + + Access denied with redirection to %0 using status + %1 - a redirection to URI %0 was issued using status + %1. + + + + Access allowed - rule engine stopped + processing rules (transaction was unaffected). + + + + Access to phase allowed - rule engine + stopped processing rules in the current phase only. Subsequent + phases will be processed normally. Transaction was not affected by + this rule but it may be affected by any of the rules in the + subsequent phase. + + + + Access to request allowed - rule engine + stopped processing rules in the current phase. Phases prior to + request execution in the backend (currently phases 1 and 2) will not + be processed. The response phases (currently phases 3 and 4) and + others (currently phase 5) will be processed as normal. Transaction + was not affected by this rule but it may be affected by any of the + rules in the subsequent phase. + + + + The second part of the engine message explains + why the event was generated. Since it is + automatically generated from the rules it will be very technical in + nature talking about operators and their parameters and give you insight + into what the rule looked like. But this message cannot give you insight + into the reasoning behind the rule. A well-written rule will always + specify a human-readable message (using the msg + action) to provide further clarification. + + The format of the second part of the engine message depends on + whether it was generated by the operator (which happens on a match) or + by the rule processor (which happens where there is not a match, but the + negation was used): + + + + @beginsWith s- String match %0 at %1. + + + + @contains - String match %0 at %1. + + + + @containsWord - String match %0 at %1. + + + + @endsWith - String match %0 at %1. + + + + @eq - Operator EQ matched %0 at %1. + + + + @ge - Operator GE matched %0 at %1. + + + + @geoLookup - Geo lookup for %0 succeeded at %1. + + + + @inspectFile - File %0 rejected by the approver script %1: + %2 + + + + @le - Operator LE matched %0 at %1. + + + + @lt - Operator LT matched %0 at %1. + + + + @rbl - RBL lookup of %0 succeeded at %1. + + + + @rx - Pattern match %0 at %1. + + + + @streq - String match %0 at %1. + + + + @validateByteRange - Found %0 byte(s) in %1 outside range: + %2. + + + + @validateDTD - XML: DTD validation failed. + + + + @validateSchema - XML: Schema validation failed. + + + + @validateUrlEncoding + + + + Invalid URL Encoding: Non-hexadecimal digits used at + %0. + + + + Invalid URL Encoding: Not enough characters at the end of + input at %0. + + + + + + @validateUtf8Encoding + + + + Invalid UTF-8 encoding: not enough bytes in character at + %0. + + + + Invalid UTF-8 encoding: invalid byte value in character at + %0. + + + + Invalid UTF-8 encoding: overlong character detected at + %0. + + + + Invalid UTF-8 encoding: use of restricted character at + %0. + + + + Invalid UTF-8 encoding: decoding error at %0. + + + + + + @verifyCC - CC# match %0 at %1. + + + + Messages not related to operators: + + + + When SecAction directive is processed - + Unconditional match in SecAction. + + + + When SecRule does not match but negation is + used - Match of %0 against %1 required. + + + + The metadata fields are always placed at the end of the alert + entry. Each metadata field is a text fragment that consists of an open + bracket followed by the metadata field name, followed by the value and + the closing bracket. What follows is the text fragment that makes up the + id metadata field. + + [id "960034"] + + The following metadata fields are currently used: + + + + offset - The byte offset where a match + occured within the target data. This is not always available. + + + + id - Unique rule ID, as specified by the + id action. + + + + rev - Rule revision, as specified by the + rev action. + + + + msg - Human-readable message, as specified + by the msg action. + + + + severity - Event severity, as specified by + the severity action. + + + + unique_id - Unique event ID, generated + automatically. + + + + uri - Request URI. + + + + logdata - contains transaction data + fragment, as specified by the logdata + action. + + + +
+ Alerts in Apache + + Every ModSecurity alert + conforms to the following format when it appears in the Apache error + log: + + [Sun Jun 24 10:19:58 2007] [error] [client 192.168.0.1] ModSecurity: ALERT_MESSAGE + + The above is a standard Apache error log format. The "ModSecurity:" prefix is specific to + ModSecurity. It is used to allow + quick identification of ModSecurity alert messages when they appear + in the same file next to other Apache messages. + + The actual message (ALERT_MESSAGE in the + example above) is in the same format as described in the + Alerts section. +
+ +
+ Alerts in Audit Log + + Alerts are transported in the H section of + the ModSecurity Audit Log. Alerts + will appear each on a separate line and in the order they were + generated by ModSecurity. Each + line will be in the following format: + + Message: ALERT_MESSAGE + + Below is an example of an entire H section + (followed by the Z section terminator): + + --c7036611-H-- +Message: Warning. Match of "rx ^apache.*perl" against "REQUEST_HEADERS:User-Agent" required. [id "990011"] + [msg "Request Indicates an automated program explored the site"] [severity "NOTICE"] +Message: Warning. Pattern match "(?:\\b(?:(?:s(?:elect\\b(?:.{1,100}?\\b(?:(?:length|count|top)\\b.{1,100} + ?\\bfrom|from\\b.{1,100}?\\bwhere)|.*?\\b(?:d(?:ump\\b.*\\bfrom|ata_type)|(?:to_(?:numbe|cha)|inst)r))|p_ + (?:(?:addextendedpro|sqlexe)c|(?:oacreat|prepar)e|execute(?:sql)?|makewebt ..." at ARGS:c. [id "950001"] + [msg "SQL Injection Attack. Matched signature: union select"] [severity "CRITICAL"] +Stopwatch: 1199881676978327 2514 (396 2224 -) +Producer: ModSecurity v2.x.x (Apache 2.x) +Server: Apache/2.x.x + +--c7036611-Z-- +
+
+ +
+ Audit Log + + ModSecurity records one + transaction in a single audit log file. Below is an example: + + --c7036611-A-- +[09/Jan/2008:12:27:56 +0000] OSD4l1BEUOkAAHZ8Y3QAAAAH 209.90.77.54 64995 80.68.80.233 80 +--c7036611-B-- +GET //EvilBoard_0.1a/index.php?c='/**/union/**/select/**/1,concat(username,char(77), + password,char(77),email_address,char(77),info,char(77),user_level,char(77))/**/from + /**/eb_members/**/where/**/userid=1/*http://kamloopstutor.com/images/banners/on.txt? + HTTP/1.1 +TE: deflate,gzip;q=0.3 +Connection: TE, cslose +Host: www.example.com +User-Agent: libwww-perl/5.808 + +--c7036611-F-- +HTTP/1.1 404 Not Found +Content-Length: 223 +Connection: close +Content-Type: text/html; charset=iso-8859-1 + +--c7036611-H-- +Message: Warning. Match of "rx ^apache.*perl" against "REQUEST_HEADERS:User-Agent" required. [id "990011"] + [msg "Request Indicates an automated program explored the site"] [severity "NOTICE"] +Message: Warning. Pattern match "(?:\\b(?:(?:s(?:elect\\b(?:.{1,100}?\\b(?:(?:length|count|top)\\b.{1,100} + ?\\bfrom|from\\b.{1,100}?\\bwhere)|.*?\\b(?:d(?:ump\\b.*\\bfrom|ata_type)|(?:to_(?:numbe|cha)|inst)r))|p_ + (?:(?:addextendedpro|sqlexe)c|(?:oacreat|prepar)e|execute(?:sql)?|makewebt ..." at ARGS:c. [id "950001"] + [msg "SQL Injection Attack. Matched signature: union select"] [severity "CRITICAL"] +Apache-Error: [file "/tmp/buildd/apache2-2.x.x/build-tree/apache2/server/core.c"] [line 3505] [level 3] + File does not exist: /var/www/EvilBoard_0.1a +Stopwatch: 1199881676978327 2514 (396 2224 -) +Producer: ModSecurity v2.x.x (Apache 2.x) +Server: Apache/2.x.x + +--c7036611-Z-- + + + The file consist of multiple sections, each in different format. + Separators are used to define sections: + + --c7036611-A-- + + A separator always begins on a new line and conforms to the + following format: + + + + Two dashes + + + + Unique boundary, which consists from several hexadecimal + characters. + + + + One dash character. + + + + Section identifier, currently a single uppercase + letter. + + + + Two trailing dashes. + + + + Refer to the documentation for SecAuditLogParts + for the explanation of each part. +
+
+ +
+ Miscellaneous Topics + + + +
+ Impedance Mismatch + + Web application fireballs have a difficult job trying to make + sense of data that passes by, without any knowledge of the application + and its business logic. The protection they provide comes from having an + independent layer of security on the outside. Because data validation is + done twice, security can be increased without having to touch the + application. In some cases, however, the fact that everything is done + twice brings problems. Problems can arise in the areas where the + communication protocols are not well specified, or where either the + device or the application do things that are not in the specification. + In such cases it may be possible to design payload that will be + interpreted in one way by one device and in another by the other device. + This problem is better known as Impedance Mismatch. It can be exploited + to evade the security devices. + + While we will continue to enhance ModSecurity to deal with various evasion + techniques the problem can only be minimized, but never solved. With so + many different application backend chances are some will always do + something completely unexpected. The only solution is to be aware of the + technologies in the backend when writing rules, adapting the rules to + remove the mismatch. See the next section for some examples. + +
+ PHP Peculiarities for <trademark + class="trade">ModSecurity</trademark> Users + + When writing rules to protect PHP applications you need to pay + attention to the following facts: + + + + When "register_globals" is set to "On" request parameters + are automatically converted to script variables. In some PHP + versions it is even possible to override the $GLOBALS + array. + + + + Whitespace at the beginning of parameter names is ignored. + (This is very dangerous if you are writing rules to target + specific named variables.) + + + + The remaining whitespace (in parameter names) is converted + to underscores. The same applies to dots and to a "[" if the + variable name does not contain a matching closing bracket. + (Meaning that if you want to exploit a script through a variable + that contains an underscore in the name you can send a parameter + with a whitespace or a dot instead.) + + + + Cookies can be treated as request parameters. + + + + The discussion about variable names applies equally to the + cookie names. + + + + The order in which parameters are taken from the request and + the environment is EGPCS (environment, GET, POST, Cookies, + built-in variables). This means that a POST parameter will + overwrite the parameters transported on the request line (in + QUERY_STRING). + + + + When "magic_quotes_gpc" is set to "On" PHP will use + backslash to escape the following characters: single quote, double + quote, backslash, and the nul byte. + + + + If "magic_quotes_sybase" is set to "On" only the single + quote will be escaped using another single quote. In this case the + "magic_quotes_gpc" setting becomes irrelevant. The + "magic_quotes_sybase" setting completely overrides the + "magic_quotes_gpc" behaviour but "magic_quotes_gpc" still must be + set to "On" for the Sybase-specific quoting to be work. + + + + PHP will also automatically create nested arrays for you. + For example "p[x][y]=1" results in a total of three + variables. + + +
+
+
+ \ No newline at end of file diff --git a/modsecurity.conf-minimal b/modsecurity.conf-minimal index 0a17e992..ab9935f2 100644 --- a/modsecurity.conf-minimal +++ b/modsecurity.conf-minimal @@ -38,3 +38,22 @@ SecResponseBodyLimit 524288 SecRule REQBODY_PROCESSOR_ERROR "!@eq 0" \ "phase:2,t:none,log,deny,msg:'Failed to parse request body.',severity:2" +# By default be strict with what we accept in the multipart/form-data +# request body. If the rule below proves to be too strict for your +# environment consider changing it to detection-only. You are encouraged +# _not_ to remove it altogether. +SecRule MULTIPART_STRICT_ERROR "!@eq 0" \ +"phase:2,t:none,log,deny,msg:'Multipart request body \ +failed strict validation: \ +PE %{REQBODY_PROCESSOR_ERROR}, \ +BQ %{MULTIPART_BOUNDARY_QUOTED}, \ +BW %{MULTIPART_BOUNDARY_WHITESPACE}, \ +DB %{MULTIPART_DATA_BEFORE}, \ +DA %{MULTIPART_DATA_AFTER}, \ +HF %{MULTIPART_HEADER_FOLDING}, \ +LF %{MULTIPART_LF_LINE}, \ +SM %{MULTIPART_SEMICOLON_MISSING}'" + +# Did we see anything that might be a boundary? +SecRule MULTIPART_UNMATCHED_BOUNDARY "!@eq 0" \ +"phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'" diff --git a/review/pre-2.5-brian.review b/review/pre-2.5-brian.review new file mode 100644 index 00000000..0d2e9b5a --- /dev/null +++ b/review/pre-2.5-brian.review @@ -0,0 +1,3436 @@ + + + + + 2008-01-04 :: 10:55:40:361 GMT-08:00 + 2008-01-11 :: 16:47:15:701 GMT-08:00 + + brian + brian + apache2/re_variables.c + item.type.label.suggestion + item.severity.label.normal + Is ENV really cacheable? It could change via setenv. + /* ENV */ + msre_engine_variable_register(engine, + "ENV", + VAR_LIST, + 0, 1, + var_env_validate, + var_env_generate, + VAR_CACHE, + PHASE_REQUEST_HEADERS + ); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 10:57:54:279 GMT-08:00 + 2008-01-11 :: 16:43:38:825 GMT-08:00 + + brian + brian + apache2/re_variables.c + item.type.label.suggestion + item.severity.label.trivial + GEO is probably not cacheable as it changes with every @geoLookup operator. + /* GEO */ + msre_engine_variable_register(engine, + "GEO", + VAR_LIST, + 1, 1, + var_generic_list_validate, + var_geo_generate, + VAR_CACHE, + PHASE_REQUEST_HEADERS + ); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 11:02:30:450 GMT-08:00 + 2008-01-11 :: 16:43:20:652 GMT-08:00 + + brian + brian + apache2/re_variables.c + item.type.label.suggestion + item.severity.label.trivial + GLOBAL is not documented. Is it cacheable? + /* GLOBAL */ + msre_engine_variable_register(engine, + "GLOBAL", + VAR_LIST, + 1, 1, + var_generic_list_validate, + var_global_generate, + VAR_CACHE, + PHASE_REQUEST_HEADERS + ); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 11:06:50:690 GMT-08:00 + 2008-01-11 :: 16:43:02:916 GMT-08:00 + + brian + brian + apache2/re_variables.c + item.type.label.suggestion + item.severity.label.trivial + IP undocumented. Probably not cacheable as it can change via setvar, etc. + /* IP */ + msre_engine_variable_register(engine, + "IP", + VAR_LIST, + 1, 1, + var_generic_list_validate, + var_ip_generate, + VAR_CACHE, + PHASE_REQUEST_HEADERS + ); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 11:08:36:733 GMT-08:00 + 2008-01-11 :: 16:42:44:589 GMT-08:00 + + brian + brian + apache2/re_variables.c + item.type.label.suggestion + item.severity.label.trivial + RESOURCE is undocumented. Probably not cacheable as it is easily changed. + /* RESOURCE */ + msre_engine_variable_register(engine, + "RESOURCE", + VAR_LIST, + 1, 1, + var_generic_list_validate, + var_resource_generate, + VAR_CACHE, + PHASE_REQUEST_HEADERS + ); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 11:14:32:043 GMT-08:00 + 2008-01-11 :: 16:41:31:558 GMT-08:00 + + brian + brian + apache2/re_variables.c + item.type.label.suggestion + item.severity.label.trivial + SESSION is probably not cacheable since it is modifyable via setvar. + /* SESSION */ + msre_engine_variable_register(engine, + "SESSION", + VAR_LIST, + 1, 1, + var_generic_list_validate, + var_session_generate, + VAR_CACHE, + PHASE_REQUEST_HEADERS + ); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 11:25:25:879 GMT-08:00 + 2008-01-11 :: 17:00:23:571 GMT-08:00 + + brian + brian + apache2/apache2_util.c + item.type.label.suggestion + item.severity.label.trivial + Portable way to format sizeof()? + msr_log(msr, 1, "Exec: Unable to allocate %lu bytes.", (unsigned long)sizeof(*procnew)); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 11:48:52:563 GMT-08:00 + 2008-01-04 :: 11:50:30:473 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.irrelevant + item.severity.label.trivial + This #if 0'd out code should be removed. + /* Removed %0-9 macros as it messes up urlEncoding in the match + * where having '%0a' will be treated as %{TX.0}a, which is incorrect. + * */ +#if 0 + else if ((*(p + 1) >= '0')&&(*(p + 1) <= '9')) { + /* Special case for regex captures. */ + var_name = "TX"; + var_value = apr_pstrmemdup(mptmp, p + 1, 1); + next_text_start = p + 2; + } +#endif + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 11:53:45:291 GMT-08:00 + 2008-01-04 :: 14:51:42:573 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.optimization + item.severity.label.trivial + Use apr_array_pstrcat(msr->mp, arr, NULL) instead? + /* If there's more than one member of the array that + * means there was at least one macro present. Combine + * text parts into a single string now. + */ + if (arr->nelts > 1) { + /* Figure out the required size for the string. */ + var->value_len = 0; + for(i = 0; i < arr->nelts; i++) { + part = ((msc_string **)arr->elts)[i]; + var->value_len += part->value_len; + } + + /* Allocate the string. */ + var->value = apr_palloc(msr->mp, var->value_len + 1); + if (var->value == NULL) return -1; + + /* Combine the parts. */ + offset = 0; + for(i = 0; i < arr->nelts; i++) { + part = ((msc_string **)arr->elts)[i]; + memcpy((char *)(var->value + offset), part->value, part->value_len); + offset += part->value_len; + } + var->value[offset] = '\0'; + } + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 12:00:50:877 GMT-08:00 + 2008-01-04 :: 12:03:50:156 GMT-08:00 + + brian + brian + apache2/re_operators.c + Suggestion + item.severity.label.trivial + Use resolve_relative_path() instead? Maybe a config_relative_path() to just get the path? + /* Get the path of the rule filename to use as a base */ + rulefile_path = apr_pstrndup(rule->ruleset->mp, rule->filename, strlen(rule->filename) - strlen(apr_filepath_name_get(rule->filename))); + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 13:23:49:834 GMT-08:00 + 2008-01-11 :: 16:26:24:257 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.clarity + item.severity.label.trivial + Add parens for clarity. + *next++ = '\0'; + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 14:54:09:456 GMT-08:00 + 2008-01-04 :: 14:55:05:945 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.missing + item.severity.label.trivial + Need to check return code and log an error on failure. + acmp_add_pattern(p, buf, NULL, NULL, strlen(buf)); + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 14:55:53:308 GMT-08:00 + 2008-01-04 :: 14:56:17:267 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.missing + item.severity.label.trivial + Need to check return code and log an error on failure. + acmp_prepare(p); + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 14:58:08:006 GMT-08:00 + 2008-01-04 :: 15:00:17:190 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.optimization + item.severity.label.minor + See if apr_strmatch is faster. + msre_op_within_execute + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 14:59:32:341 GMT-08:00 + 2008-01-04 :: 14:59:59:858 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.optimization + item.severity.label.minor + See if apr_strmatch is faster. + msre_op_contains_execute + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 15:01:39:208 GMT-08:00 + 2008-01-04 :: 15:02:04:258 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.optimization + item.severity.label.minor + See if apr_strmatch is faster. + msre_op_containsWord_execute + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 15:03:12:792 GMT-08:00 + 2008-01-04 :: 15:04:40:965 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.optimization + item.severity.label.minor + This implementation comment needs to be coded as many string operators now attempt to resolve macros. + /* IMP1 Duplicate the string and create the array on + * demand, thus not having to do it if there are + * no macros in the input data. + */ + + data = apr_pstrdup(mptmp, var->value); /* IMP1 Are we modifying data anywhere? */ + arr = apr_array_make(mptmp, 16, sizeof(msc_string *)); + if ((data == NULL)||(arr == NULL)) return -1; + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 15:12:55:300 GMT-08:00 + 2008-01-04 :: 15:13:19:677 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.missing + item.severity.label.minor + Need more unit tests for operators. Start with new operators. + + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 15:36:54:292 GMT-08:00 + 2008-01-04 :: 15:37:50:111 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.missing + item.severity.label.trivial + @m operator is not documented. This does the same as @contains, so it was suggested earlier to use the @m algorithm for contains (if faster) and drop @m. + /* m */ + msre_engine_op_register(engine, + "m", + msre_op_m_param_init, + msre_op_m_execute + ); + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 15:43:40:931 GMT-08:00 + 2008-01-11 :: 16:38:59:940 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.missing + item.severity.label.trivial + @geoLookup should set error_msg on success to something like "Successful geograpical lookup of \"%s\" at %s." + msre_op_geoLookup_execute + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 15:47:09:574 GMT-08:00 + 2008-01-11 :: 16:40:07:483 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.missing + item.severity.label.trivial + @rbl fails to set the var name in error_msg. Should append "at %s". + rc = apr_sockaddr_info_get(&sa, name_to_check, + APR_UNSPEC/*msr->r->connection->remote_addr->family*/, 0, 0, msr->mp); + if (rc == APR_SUCCESS) { + *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded.", + log_escape_nq(msr->mp, name_to_check)); + return 1; /* Match. */ + } + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 15:49:05:878 GMT-08:00 + 2008-01-11 :: 14:56:55:081 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.suggestion + item.severity.label.major + Change from TODO to ENH. + + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 15:54:24:055 GMT-08:00 + 2008-01-11 :: 14:57:08:520 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.suggestion + item.severity.label.trivial + Need to remove the LUA #ifdef's + #ifdef WITH_LUA + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 15:56:23:415 GMT-08:00 + 2008-01-11 :: 14:57:21:641 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.suggestion + item.severity.label.trivial + The LUA #ifdef's should be removed, but if it is decided not to, then this lua call needs to be #ifdef'd. + } else { + /* Execute internally, as Lua script. */ + char *target = apr_pstrmemdup(msr->mp, var->value, var->value_len); + msc_script *script = (msc_script *)rule->op_param_data; + int rc; + + rc = lua_execute(script, target, msr, rule, error_msg); + if (rc < 0) { + /* Error. */ + return -1; + } + + return rc; + } + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 15:58:01:918 GMT-08:00 + 2008-01-04 :: 15:58:58:621 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.missing + item.severity.label.major + Need an error_msg set for lua execution error. + rc = lua_execute(script, target, msr, rule, error_msg); + if (rc < 0) { + /* Error. */ + return -1; + } + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 16:00:09:204 GMT-08:00 + 2008-01-11 :: 16:38:08:350 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.missing + item.severity.label.trivial + @validateByteRange does not output the VAR name on match. Need to append " at %s." + msre_op_validateByteRange_execute + + + item.resolution.label.invalidWontfix + item.status.label.closed + + + + 2008-01-04 :: 16:02:02:403 GMT-08:00 + 2008-01-04 :: 16:06:22:410 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.missing + item.severity.label.trivial + @validateurlEncoding does not output VAR name nor offset in error_msg on match. + msre_op_validateUrlEncoding_execute + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 16:03:51:127 GMT-08:00 + 2008-01-11 :: 16:30:07:550 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.missing + item.severity.label.trivial + Numeric operators (@eq, etc) do not output VAR name on match. + msre_op_eq_execute +msre_op_gt_execute +msre_op_lt_execute +msre_op_ge_execute +msre_op_le_execute + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 16:12:30:943 GMT-08:00 + 2008-01-11 :: 14:57:45:711 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.suggestion + item.severity.label.trivial + No. + /* ENH Do we want to support %{DIGIT} as well? */ + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 16:14:51:515 GMT-08:00 + 2008-01-07 :: 14:22:10:119 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.missing + item.severity.label.trivial + Implement. Need to check if Apache will return an invalid status code + /* status */ +static char *msre_action_status_validate(msre_engine *engine, msre_action *action) { + /* ENH action->param must be a valid HTTP status code. */ + return NULL; +} + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 16:15:55:149 GMT-08:00 + 2008-01-04 :: 16:16:52:467 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.missing + item.severity.label.trivial + Implement. + /* pause */ +static char *msre_action_pause_validate(msre_engine *engine, msre_action *action) { + /* ENH Validate a positive number. */ + return NULL; +} + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 16:17:34:464 GMT-08:00 + 2008-01-04 :: 16:22:35:501 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.missing + item.severity.label.trivial + Implement as a valid URI check with apr_uri_parse()? + /* redirect */ + +static char *msre_action_redirect_validate(msre_engine *engine, msre_action *action) { + /* ENH Add validation. */ + return NULL; +} + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 16:22:43:274 GMT-08:00 + 2008-01-04 :: 16:22:54:679 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.missing + item.severity.label.trivial + Implement as a valid URI check with apr_uri_parse()? + /* proxy */ + +static char *msre_action_proxy_validate(msre_engine *engine, msre_action *action) { + /* ENH Add validation. */ + return NULL; +} + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 16:24:58:322 GMT-08:00 + 2008-01-11 :: 16:13:48:178 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.irrelevant + item.severity.label.trivial + I believe this is already done and comment needs removed. + // TODO: Need to keep track of skipAfter IDs so we can insert placeholders after + // we get to the real rule with that ID. + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 16:26:46:732 GMT-08:00 + 2008-01-04 :: 16:27:54:881 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.irrelevant + item.severity.label.trivial + I do not see a need to validate beyound what is already done in the init function. + msre_action_skip_validate +msre_action_skipAfter_validate + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 16:28:48:332 GMT-08:00 + 2008-01-04 :: 16:29:03:587 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.missing + item.severity.label.trivial + Implement. + /* phase */ + +static char *msre_action_phase_validate(msre_engine *engine, msre_action *action) { + /* ENH Add validation. */ + return NULL; +} + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 16:51:52:029 GMT-08:00 + 2008-01-04 :: 17:03:32:872 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.suggestion + item.severity.label.trivial + Probably should also calc length and validate a length > 0 instead of just checking NULL. Other checks would benefit from checking a length as well, so no harm in calculating that. + if (value == NULL) { + return apr_psprintf(engine->mp, "Missing ctl value for name: %s", name); + } + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 16:56:10:995 GMT-08:00 + 2008-01-04 :: 16:59:47:800 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.irrelevant + item.severity.label.trivial + Why register init() if we do not use it? + static apr_status_t msre_action_ctl_init(msre_engine *engine, msre_actionset *actionset, + msre_action *action) +{ + /* Do nothing. */ + return 1; +} + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 17:00:49:340 GMT-08:00 + 2008-01-04 :: 17:02:15:141 GMT-08:00 + + brian + brian + apache2/msc_logging.c + item.type.label.programLogic + item.severity.label.trivial + This allows an empty string as a valid part. This misvalidates "ctl:auditLogParts=+", etc. + is_valid_parts_specification + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 17:04:51:155 GMT-08:00 + 2008-01-11 :: 16:12:33:664 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.optimization + item.severity.label.trivial + Inner if's should be else if's. + if (strcasecmp(name, "ruleEngine") == 0) { + if (strcasecmp(value, "on") == 0) { + msr->txcfg->is_enabled = MODSEC_ENABLED; + msr->usercfg->is_enabled = MODSEC_ENABLED; + } + + if (strcasecmp(value, "off") == 0) { + msr->txcfg->is_enabled = MODSEC_DISABLED; + msr->usercfg->is_enabled = MODSEC_DISABLED; + } + + if (strcasecmp(value, "detectiononly") == 0) { + msr->txcfg->is_enabled = MODSEC_DETECTION_ONLY; + msr->usercfg->is_enabled = MODSEC_DETECTION_ONLY; + } + + return 1; + } else + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 17:05:44:757 GMT-08:00 + 2008-01-11 :: 16:12:12:876 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.optimization + item.severity.label.trivial + TODO needs looked into. + if (strcasecmp(name, "auditEngine") == 0) { + if (strcasecmp(value, "on") == 0) { + msr->txcfg->auditlog_flag = AUDITLOG_ON; + msr->usercfg->auditlog_flag = AUDITLOG_ON; + } + + if (strcasecmp(value, "off") == 0) { + msr->txcfg->auditlog_flag = AUDITLOG_OFF; + msr->usercfg->auditlog_flag = AUDITLOG_OFF; + } + + if (strcasecmp(value, "relevantonly") == 0) { + msr->txcfg->auditlog_flag = AUDITLOG_RELEVANT; + msr->usercfg->auditlog_flag = AUDITLOG_RELEVANT; + } + + msr_log(msr, 4, "Ctl: Set auditEngine to %d.", msr->txcfg->auditlog_flag); // TODO + + return 1; + } else + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-04 :: 17:08:00:396 GMT-08:00 + 2008-01-11 :: 14:59:05:782 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.suggestion + item.severity.label.trivial + That warning quieter should be s++. An evil typo that was fixed in 2.1.x, but not trunk! + while(*s != '\0') { + if (*s != c) { + *d++ = *s++; + } else { + (*s)++; /* parens quiet compiler warning */ + } + } + *d = '\0'; + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-04 :: 17:13:11:886 GMT-08:00 + 2008-01-04 :: 17:13:40:681 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.missing + item.severity.label.trivial + Should log an internal error here. + else { + /* ENH Should never happen, but log if it does. */ + return -1; + } + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 11:14:40:912 GMT-08:00 + 2008-01-07 :: 11:15:54:834 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.suggestion + item.severity.label.trivial + Should log a level 9 msg here. + } else { + /* We could not identify a valid macro so add it as text. */ + part = (msc_string *)apr_pcalloc(mptmp, sizeof(msc_string)); + if (part == NULL) return -1; + part->value_len = p - text_start + 1; /* len(text)+len("%") */ + part->value = apr_pstrmemdup(mptmp, text_start, part->value_len); + *(msc_string **)apr_array_push(arr) = part; + + next_text_start = p + 1; + } + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 11:37:09:009 GMT-08:00 + 2008-01-07 :: 11:41:55:614 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.suggestion + item.severity.label.trivial + Probably should use apr_strtoi64 where we can tell if there was an error in conversion since we are potentially taking a value from a macro expansion. Also may want to look for overflow. + value += atoi(var_value); + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 11:43:13:789 GMT-08:00 + 2008-01-11 :: 16:10:24:140 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.missing + item.severity.label.trivial + Missing error log needs implemented. + } else { + /* ENH Log warning detected variable name but no collection. */ + return 0; + } + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-07 :: 11:52:38:857 GMT-08:00 + 2008-01-07 :: 11:53:56:791 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.suggestion + item.severity.label.trivial + Not sure why we would not want to deprecate a TX var. Further rules could use this even if TX is not persisted. + /* IMP1 Add message TX variables cannot deprecate in value. */ + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 11:54:14:673 GMT-08:00 + 2008-01-11 :: 15:04:13:383 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.suggestion + item.severity.label.trivial + Missing error log needs implemented. + } else { + /* ENH Log warning detected variable name but no collection. */ + return 0; + } + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-07 :: 12:03:17:626 GMT-08:00 + 2008-01-07 :: 23:10:15:221 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.suggestion + item.severity.label.trivial + The timeout is hardcoded to 3600. The docs state TIMEOUT is read-only, but this is not true. So, you can modify TIMEOUT. + /* IMP1 Is the timeout hard-coded to 3600? */ + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 14:12:19:250 GMT-08:00 + 2008-01-07 :: 14:14:28:669 GMT-08:00 + + brian + brian + apache2/msc_logging.c + item.type.label.suggestion + item.severity.label.trivial + apr_dir_make_recursive will attempt to create the dir straight away and if that fails keep backing off a dir until it can start creating, so I see no need to cache. Besides, what happens if you cache, then someone deletes the path from outside apache? + /* IMP1 Surely it would be more efficient to check the folders for + * the audit log repository base path in the configuration phase, to reduce + * the work we do on every request. Also, since our path depends on time, + * we could cache the time we last checked and don't check if we know + * the folder is there. + */ + rc = apr_dir_make_recursive(entry_basename, CREATEMODE_DIR, msr->mp); + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 14:24:02:378 GMT-08:00 + 2008-01-07 :: 14:50:55:762 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.suggestion + item.severity.label.trivial + We already have support for relative filenames, but cannot get to this data from here. This needs solved by passing more data to the validate function (cmd_parms rec). Maybe need a warning here stating we do not support them yet, or it might be confusing to users that we do not here but do elsewhere. + /* TODO Support relative filenames. */ + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 14:33:59:432 GMT-08:00 + 2008-01-07 :: 15:59:35:056 GMT-08:00 + + brian + brian + apache2/re.h + item.type.label.suggestion + item.severity.label.trivial + Why not stored in op_param_data like @rx, etc. The param_data is used w/exec action for lua. + /* Compiled Lua script. */ + msc_script *script; + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 15:55:08:220 GMT-08:00 + 2008-01-07 :: 16:02:36:938 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.suggestion + item.severity.label.trivial + This assumes lua is the only type (which it is now), but should be re-writen with a script_rec stored in param_data. + if (action->param_data != NULL) { /* Lua */ + msc_script *script = (msc_script *)action->param_data; + char *my_error_msg = NULL; + + if (lua_execute(script, NULL, msr, rule, &my_error_msg) < 0) { + msr_log(msr, 1, "%s", my_error_msg); + return 0; + } + } else { /* Execute as shell script. */ + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 16:00:51:901 GMT-08:00 + 2008-01-07 :: 16:04:13:185 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.suggestion + item.severity.label.trivial + Not sure using an extension is a good idea here. Better I think would be to specify a type: "exec:[type=]/path/to/file" as in "exec:lua=/path/to/script" and make param_data a script_rec with a type and value. Also we use the abstract param_data here vs using a specific field as in SecRuleScript. + /* Process Lua scripts internally. */ + if (strlen(filename) > 4) { + char *p = filename + strlen(filename) - 4; + if ((p[0] == '.')&&(p[1] == 'l')&&(p[2] == 'u')&&(p[3] == 'a')) { + /* It's a Lua script. */ + msc_script *script = NULL; + + /* Compile script. */ + char *msg = lua_compile(&script, filename, engine->mp); + if (msg != NULL) return msg; + + action->param_data = script; + } + } + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 16:08:53:365 GMT-08:00 + 2008-01-11 :: 15:25:58:269 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.suggestion + item.severity.label.trivial + Should not log_escape the actions as they will get double escaped (once now and again when logged). + } else { + rule->unparsed = apr_psprintf(ruleset->mp, "SecRuleScript \"%s\" \"%s\"", + script_filename, log_escape(ruleset->mp, actions)); + } + + + item.resolution.label.invalidWontfix + item.status.label.closed + + + + 2008-01-07 :: 16:17:58:895 GMT-08:00 + 2008-01-11 :: 15:26:18:631 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.suggestion + item.severity.label.trivial + Should not log_escape the actions as they will get double escaped (once now and again when logged). + /* Add the unparsed rule */ + if ((strcmp(SECACTION_TARGETS, targets) == 0) && (strcmp(SECACTION_ARGS, args) == 0)) { + rule->unparsed = apr_psprintf(ruleset->mp, "SecAction \"%s\"", + log_escape(ruleset->mp, actions)); + } + else + if ((strcmp(SECMARKER_TARGETS, targets) == 0) + && (strcmp(SECMARKER_ARGS, args) == 0) + && (strncmp(SECMARKER_BASE_ACTIONS, actions, strlen(SECMARKER_BASE_ACTIONS)) == 0)) + { + rule->unparsed = apr_psprintf(ruleset->mp, "SecMarker \"%s\"", + log_escape(ruleset->mp, actions + strlen(SECMARKER_BASE_ACTIONS))); + } + else { + if (actions == NULL) { + rule->unparsed = apr_psprintf(ruleset->mp, "SecRule \"%s\" \"%s\"", + log_escape(ruleset->mp, targets), log_escape(ruleset->mp, args)); + } else { + rule->unparsed = apr_psprintf(ruleset->mp, "SecRule \"%s\" \"%s\" \"%s\"", + log_escape(ruleset->mp, targets), log_escape(ruleset->mp, args), + log_escape(ruleset->mp, actions)); + } + } + + + item.resolution.label.invalidWontfix + item.status.label.closed + + + + 2008-01-07 :: 16:22:43:295 GMT-08:00 + 2008-01-11 :: 15:26:48:665 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.suggestion + item.severity.label.trivial + No logging should be done here as we are passing the error_msg back to the parent and they are responsible for this. + if (*error_msg != NULL) { + /* ENH Shouldn't we log the problem? */ + return NULL; + } + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-07 :: 16:30:41:403 GMT-08:00 + 2008-01-07 :: 16:31:00:930 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.missing + item.severity.label.trivial + Need to log on failure. + var = msre_create_var(ruleset, telts[i].key, telts[i].val, NULL, error_msg); + if (var == NULL) return -1; + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 16:36:31:466 GMT-08:00 + 2008-01-07 :: 16:36:54:189 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.suggestion + item.severity.label.trivial + Should replace with isvarnamechar() if possible. + while((*p != '\0')&&(*p != '|')&&(*p != ':')&&(*p != ',')&&(!isspace(*p))) p++; /* ENH replace with isvarnamechar() */ + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 16:39:21:316 GMT-08:00 + 2008-01-11 :: 15:28:02:997 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.suggestion + item.severity.label.trivial + Fix or remove TODO. + // TODO better 64-bit support here + *error_msg = apr_psprintf(mp, "Missing closing quote at position %d: %s", + (int)(p - text), text); + free(value); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-07 :: 16:39:47:318 GMT-08:00 + 2008-01-11 :: 15:28:24:355 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.suggestion + item.severity.label.trivial + Fix or remove TODO. + // TODO better 64-bit support here + *error_msg = apr_psprintf(mp, "Invalid quoted pair at position %d: %s", + (int)(p - text), text); + free(value); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-07 :: 16:40:15:634 GMT-08:00 + 2008-01-11 :: 16:07:58:168 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.clarity + item.severity.label.trivial + Add parens for clarity. + *d++ = *p++; + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-07 :: 16:40:43:464 GMT-08:00 + 2008-01-11 :: 16:07:34:306 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.clarity + item.severity.label.trivial + Add parens for clarity. + *d++ = *p++; + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-07 :: 20:46:50:832 GMT-08:00 + 2008-01-11 :: 16:06:54:324 GMT-08:00 + + brian + brian + apache2/acmp.c + item.type.label.clarity + item.severity.label.trivial + Add parens for clarity. + *ucs_chars++ = *c++; + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-07 :: 20:47:54:093 GMT-08:00 + 2008-01-11 :: 16:06:02:231 GMT-08:00 + + brian + brian + apache2/msc_multipart.c + item.type.label.clarity + item.severity.label.trivial + Add parens for clarity. + *t++ = *p++; + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-07 :: 20:48:42:400 GMT-08:00 + 2008-01-11 :: 16:05:23:686 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.clarity + item.severity.label.trivial + Add parens for clarity. + *d++ = *s++; + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-07 :: 21:42:51:192 GMT-08:00 + 2008-01-07 :: 21:44:12:097 GMT-08:00 + + brian + brian + apache2/apache2_io.c + item.type.label.programLogic + item.severity.label.major + Returning here may fail to free chunks data due to modsecurity_request_body_end() not being called. + int rcbs = modsecurity_request_body_store(msr, buf, buflen, error_msg); + if (rcbs < 0) { + if (rcbs == -5) { + *error_msg = apr_psprintf(msr->mp, "Requests body no files data length is larger than the " + "configured limit (%lu).", msr->txcfg->reqbody_no_files_limit); + return -5; + } + + return -1; + } + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 21:50:32:890 GMT-08:00 + 2008-01-07 :: 21:52:29:239 GMT-08:00 + + brian + brian + apache2/modsecurity.c + item.type.label.suggestion + item.severity.label.major + Good. This looks to solve the other issues noted as possible memory leaks in body chunk data due to modsecurity_request_body_end() not being called. Need to verify, though. + /* Register TX cleanup */ + apr_pool_cleanup_register(msr->mp, msr, modsecurity_tx_cleanup, apr_pool_cleanup_null); + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 21:59:33:579 GMT-08:00 + 2008-01-11 :: 15:29:21:240 GMT-08:00 + + brian + brian + apache2/msc_util.c + item.type.label.suggestion + item.severity.label.trivial + Actually, the parens are *required* for correctness, so remove the comments. + (*invalid_count)++; /* parens quiet compiler warning */ + } + } else { + /* Not enough bytes available, copy the raw bytes. */ + *d++ = input[i++]; + count ++; + (*invalid_count)++; /* parens quiet compiler warning */ + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-07 :: 22:03:31:138 GMT-08:00 + 2008-01-07 :: 22:04:07:108 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.irrelevant + item.severity.label.trivial + Does not appear to be used anywhere. + /** + * Destroys an engine instance, releasing the consumed memory. + */ +void msre_engine_destroy(msre_engine *engine) { + /* Destroyed automatically by the parent pool. + * apr_pool_destroy(engine->mp); + */ +} + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 22:04:22:398 GMT-08:00 + 2008-01-07 :: 22:04:35:724 GMT-08:00 + + brian + brian + apache2/re.h + item.type.label.irrelevant + item.severity.label.trivial + Does not appear to be used anywhere. + void DSOLOCAL msre_engine_destroy(msre_engine *engine); + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 22:23:48:909 GMT-08:00 + 2008-01-11 :: 16:04:30:061 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.clarity + item.severity.label.trivial + This version should be moved up next to the normal version. + #if defined(PERFORMANCE_MEASUREMENT) +apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) { + ... +} + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-07 :: 22:30:42:215 GMT-08:00 + 2008-01-11 :: 16:02:09:916 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.missing + item.severity.label.major + Hmm, I thought this had already been fixed in trunk. Missing logging phase. Need to fix in 2.1.5 as well. + /** + * Removes from the ruleset all rules that match the given exception. + */ +int msre_ruleset_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re) { + int count = 0; + + if (ruleset == NULL) return 0; + + count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_request_headers); + count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_request_body); + count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_response_headers); + count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_response_body); + + return count; +} + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-07 :: 22:46:51:423 GMT-08:00 + 2008-01-11 :: 16:01:31:111 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.optimization + item.severity.label.trivial + Should move this to a static global for performance. + static const char *const severities[] = { + "EMERGENCY", + "ALERT", + "CRITICAL", + "ERROR", + "WARNING", + "NOTICE", + "INFO", + "DEBUG", + NULL, + }; + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-07 :: 22:49:04:822 GMT-08:00 + 2008-01-07 :: 22:49:16:740 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.suggestion + item.severity.label.trivial + Implement TODO. + //TODO: restrict to 512 bytes + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-07 :: 22:51:40:727 GMT-08:00 + 2008-01-07 :: 22:53:13:118 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.optimization + item.severity.label.trivial + tags set to NULL would be a bit better as it would stop apr_pstrcat() earlier, but tags *must* remain last or wierd results. + char *tags = ""; + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-08 :: 11:44:13:484 GMT-08:00 + 2008-01-08 :: 11:45:29:864 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.optimization + item.severity.label.trivial + This causes two loops through the action list. Perhaps there is a more performant way to do these at the same time? Maybe split into two lists? + /* Perform non-disruptive actions. */ + msre_perform_nondisruptive_actions(msr, rule, rule->actionset, mptmp); + + /* Perform disruptive actions, but only if + * this rule is not part of a chain. + */ + if (rule->actionset->is_chained == 0) { + msre_perform_disruptive_actions(msr, rule, acting_actionset, mptmp, my_error_msg); + } + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-08 :: 12:16:20:280 GMT-08:00 + 2008-01-11 :: 15:59:48:300 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.irrelevant + item.severity.label.trivial + These do not appear to be needed. + tfnspath = NULL; + tfnskey = NULL; + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-08 :: 12:20:29:491 GMT-08:00 + 2008-01-11 :: 15:59:23:210 GMT-08:00 + + brian + brian + apache2/re.c + item.type.label.programLogic + item.severity.label.major + This does not appear to work as the tfnskey is not being built here. Need to build the tfnskey in this loop for this to work. + /* check cache, saving the 'most complete' */ + crec = (msre_cache_rec *)apr_table_get(cachetab, tfnskey); + if (crec != NULL) { + last_crec = crec; + last_cached_tfn = tfnscount; + } + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-08 :: 21:29:14:889 GMT-08:00 + 2008-01-08 :: 21:30:41:759 GMT-08:00 + + brian + brian + apache2/re_tfns.c + item.type.label.optimization + item.severity.label.trivial + No need to set this on all. Only set it once when we find the first non-space char. + (*rval)[i] = '\0'; + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-08 :: 22:09:10:463 GMT-08:00 + 2008-01-11 :: 14:20:53:411 GMT-08:00 + + brian + brian + apache2/re_variables.c + item.type.label.suggestion + item.severity.label.trivial + Indention off. + return var_simple_generate_ex(var, vartab, mptmp, + apr_pmemdup(mptmp, + msr->matched_var->value, + msr->matched_var->value_len), + msr->matched_var->value_len); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-08 :: 22:09:35:664 GMT-08:00 + 2008-01-11 :: 14:21:28:242 GMT-08:00 + + brian + brian + apache2/re_variables.c + item.type.label.suggestion + item.severity.label.trivial + Indention off. + return var_simple_generate_ex(var, vartab, mptmp, + apr_pmemdup(mptmp, + msr->matched_var->name, + msr->matched_var->name_len), + msr->matched_var->name_len); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 10:55:38:639 GMT-08:00 + 2008-01-11 :: 14:25:15:860 GMT-08:00 + + brian + brian + apache2/acmp.c + item.type.label.suggestion + item.severity.label.trivial + Remove comment. + //return acmp_child_for_code(node, letter) + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 11:01:01:815 GMT-08:00 + 2008-01-11 :: 15:55:50:363 GMT-08:00 + + brian + brian + apache2/acmp.c + item.type.label.suggestion + item.severity.label.trivial + Change to #idef DEBUG_ACMP or similar. + /* printf("%c ->left %c \n", node->node->letter, node->left->node->letter); */ + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 11:01:41:424 GMT-08:00 + 2008-01-11 :: 15:56:10:175 GMT-08:00 + + brian + brian + apache2/acmp.c + item.type.label.suggestion + item.severity.label.trivial + Change to #idef DEBUG_ACMP or similar. + /* printf("%c ->right %c \n", node->node->letter, node->right->node->letter); */ + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 11:02:22:279 GMT-08:00 + 2008-01-11 :: 15:56:41:045 GMT-08:00 + + brian + brian + apache2/acmp.c + item.type.label.suggestion + item.severity.label.trivial + Change to #idef DEBUG_ACMP or similar. + /* printf("fail direction: *%s* => *%s*\n", child->text, child->fail->text); */ + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 11:02:42:897 GMT-08:00 + 2008-01-11 :: 15:54:16:807 GMT-08:00 + + brian + brian + apache2/acmp.c + item.type.label.suggestion + item.severity.label.trivial + Change to #idef DEBUG_ACMP or similar. + /* printf("fail direction: *%s* => *%s*\n", node->text, node->fail->text); */ + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 11:10:25:157 GMT-08:00 + 2008-01-11 :: 14:24:57:212 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + The 'tag' and 'severity' metadata actions should be included. Some actions are missing from the log msg. + /* Must NOT use metadata actions. */ + if ((rule->actionset->id != NOT_SET_P) + ||(rule->actionset->rev != NOT_SET_P) + ||(rule->actionset->msg != NOT_SET_P) + ||(rule->actionset->logdata != NOT_SET_P)) + { + return apr_psprintf(cmd->pool, "ModSecurity: Metadata actions (id, rev, msg) " + " can only be specified by chain starter rules."); + } + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 11:18:38:672 GMT-08:00 + 2008-01-11 :: 14:29:49:134 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Probably should check we were able to allocate. + msre_rule *phrule = apr_palloc(rule->ruleset->mp, sizeof(msre_rule)); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 12:05:40:949 GMT-08:00 + 2008-01-11 :: 14:30:01:771 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Comment or remove. + TODO + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 12:06:48:653 GMT-08:00 + 2008-01-11 :: 14:30:40:514 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + ENH instead of TODO + TODO + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 12:09:17:114 GMT-08:00 + 2008-01-11 :: 14:31:50:812 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + The 'tag' and 'severity' metadata actions should be included. Some actions are missing from the log msg. + /* Must not use metadata actions. */ + if ((dcfg->tmp_default_actionset->id != NOT_SET_P) + ||(dcfg->tmp_default_actionset->rev != NOT_SET_P) + ||(dcfg->tmp_default_actionset->msg != NOT_SET_P) + ||(dcfg->tmp_default_actionset->logdata != NOT_SET_P)) + { + return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must not " + "contain any metadata actions (id, rev, msg)."); + } + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 12:17:06:712 GMT-08:00 + 2008-01-11 :: 14:33:23:560 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Fix TODO or make ENH + // TODO Validate encoding + // return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRequestBodyAccess: %s", p1); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 12:17:55:545 GMT-08:00 + 2008-01-09 :: 12:25:33:617 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.irrelevant + item.severity.label.trivial + Remove code? + /* +static const char *cmd_rule_import_by_id(cmd_parms *cmd, void *_dcfg, const char *p1) { + directory_config *dcfg = (directory_config *)_dcfg; + rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception)); + if (dcfg == NULL) return NULL; + + re->type = RULE_EXCEPTION_IMPORT_ID; + // TODO verify p1 + re->param = p1; + *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re; + + return NULL; +} + +static const char *cmd_rule_import_by_msg(cmd_parms *cmd, void *_dcfg, const char *p1) { + directory_config *dcfg = (directory_config *)_dcfg; + rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception)); + if (dcfg == NULL) return NULL; + + re->type = RULE_EXCEPTION_IMPORT_MSG; + // TODO verify p1 + re->param = p1; + *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re; + + return NULL; +} +*/ + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 12:18:34:399 GMT-08:00 + 2008-01-11 :: 14:34:07:673 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Fix or TODO->ENH + // TODO enforce format (letters, digits, ., _, -) + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 12:20:16:234 GMT-08:00 + 2008-01-11 :: 14:40:53:633 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Need to allow for relative filename based of rule file. + /* -- Geo Lookup configuration -- */ + +static const char *cmd_geo_lookup_db(cmd_parms *cmd, void *_dcfg, + const char *p1) +{ + char *error_msg; + directory_config *dcfg = (directory_config *)_dcfg; + if (dcfg == NULL) return NULL; + + if (geo_init(dcfg, p1, &error_msg) <= 0) { + return error_msg; + } + + return NULL; +} + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 12:22:58:798 GMT-08:00 + 2008-01-09 :: 12:23:11:462 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Portable? + /* The NOT_SET indicator is -1, a signed long, and therfore + * we cannot be >= the unsigned value of NOT_SET. + */ + if ((unsigned long)intval >= (unsigned long)NOT_SET) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations minlen must be less than: %lu", (unsigned long)NOT_SET); + } + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 12:23:33:676 GMT-08:00 + 2008-01-09 :: 12:23:39:437 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Portable? + /* The NOT_SET indicator is -1, a signed long, and therfore + * we cannot be >= the unsigned value of NOT_SET. + */ + if ((unsigned long)intval >= (unsigned long)NOT_SET) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen must be less than: %lu", (unsigned long)NOT_SET); + } + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 12:24:06:001 GMT-08:00 + 2008-01-09 :: 12:24:23:994 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Could use strtol as Ivan has as well. + intval = apr_atoi64(charval); + if (errno == ERANGE) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations minlen out of range: %s", charval); + } + if (intval < 0) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations minlen must be positive: %s", charval); + } + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 12:24:37:453 GMT-08:00 + 2008-01-09 :: 12:24:42:462 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Could use strtol as Ivan has as well. + intval = apr_atoi64(charval); + if (errno == ERANGE) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen out of range: %s", charval); + } + if (intval < 0) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen must be positive: %s", charval); + } + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 12:25:36:799 GMT-08:00 + 2008-01-09 :: 12:31:35:264 GMT-08:00 + + brian + brian + apache2/apache2_io.c + item.type.label.programLogic + item.severity.label.major + Remove code? It is actually used below, so need to verify. + #if 0 +static void dummy_free_func(void *data) {} +#endif + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 12:27:10:252 GMT-08:00 + 2008-01-09 :: 12:29:31:617 GMT-08:00 + + brian + brian + apache2/apache2_io.c + item.type.label.programLogic + item.severity.label.major + dummy_free_func() is defined where? It is ifdef'd out at the top of source, so need to verify it is valid. + /* Do not make a copy of the data we received in the chunk. */ + bucket = apr_bucket_heap_create(chunk->data, chunk->length, dummy_free_func, + f->r->connection->bucket_alloc); + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 12:35:21:613 GMT-08:00 + 2008-01-09 :: 13:02:40:668 GMT-08:00 + + brian + brian + apache2/apache2_io.c + item.type.label.suggestion + item.severity.label.trivial + Yes, why do we ignore the rc - why have one at all? + // TODO: Why ignore the return code here? + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 12:55:19:633 GMT-08:00 + 2008-01-11 :: 17:00:04:416 GMT-08:00 + + brian + brian + apache2/msc_reqbody.c + item.type.label.suggestion + item.severity.label.trivial + Portable way to format sizeof()? + *error_msg = apr_psprintf(msr->mp, "Input filter: Failed to allocate %lu bytes for request body chunk.", (unsigned long)sizeof(msc_data_chunk)); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 12:55:39:585 GMT-08:00 + 2008-01-11 :: 16:58:16:438 GMT-08:00 + + brian + brian + apache2/msc_reqbody.c + item.type.label.suggestion + item.severity.label.trivial + Portable way to format sizeof()? + *error_msg = apr_psprintf(msr->mp, "Failed to allocate %lu bytes for request body disk chunk.", (unsigned long)sizeof(msc_data_chunk)); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 12:56:05:302 GMT-08:00 + 2008-01-11 :: 16:57:58:049 GMT-08:00 + + brian + brian + apache2/msc_reqbody.c + item.type.label.suggestion + item.severity.label.trivial + Portable way to format sizeof()? + *error_msg = apr_psprintf(msr->mp, "Failed to allocate %lu bytes for request body disk chunk.", (unsigned long)sizeof(msc_data_chunk)); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 12:56:51:734 GMT-08:00 + 2008-01-11 :: 14:41:50:773 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Fix TODO or change to ENH. + // TODO check whether the parameter is a valid MIME type of "null" + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 12:58:11:331 GMT-08:00 + 2008-01-11 :: 14:42:59:645 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Fix TODO or change to ENH. + AP_INIT_TAKE1 ( + "SecAction", + cmd_action, + NULL, + CMD_SCOPE_ANY, + "" // TODO + ), + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 12:58:29:461 GMT-08:00 + 2008-01-11 :: 14:46:15:813 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Fix TODO or change to ENH. + AP_INIT_TAKE1 ( + "SecDataDir", + cmd_data_dir, + NULL, + CMD_SCOPE_MAIN, + "" // TODO + ), + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 12:58:45:647 GMT-08:00 + 2008-01-11 :: 14:46:51:173 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Fix TODO or change to ENH. + AP_INIT_TAKE1 ( + "SecDefaultAction", + cmd_default_action, + NULL, + CMD_SCOPE_ANY, + "" // TODO + ), + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 12:59:03:959 GMT-08:00 + 2008-01-11 :: 14:47:05:294 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Fix TODO or change to ENH. + AP_INIT_TAKE1 ( + "SecResponseBodyLimit", + cmd_response_body_limit, + NULL, + CMD_SCOPE_ANY, + "" // TODO + ), + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 12:59:17:580 GMT-08:00 + 2008-01-11 :: 14:49:00:349 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Fix TODO or change to ENH. + AP_INIT_TAKE1 ( + "SecResponseBodyLimitAction", + cmd_response_body_limit_action, + NULL, + CMD_SCOPE_ANY, + "" // TODO + ), + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 13:00:04:205 GMT-08:00 + 2008-01-11 :: 14:50:22:367 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Fix TODO or change to ENH. + AP_INIT_TAKE23 ( + "SecRule", + cmd_rule, + NULL, + CMD_SCOPE_ANY, + "" // TODO + ), + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 13:00:30:606 GMT-08:00 + 2008-01-11 :: 14:51:16:772 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Fix TODO or change to ENH. + AP_INIT_TAKE12 ( + "SecRuleScript", + cmd_rule_script, + NULL, + CMD_SCOPE_ANY, + "" // TODO + ), + + AP_INIT_ITERATE ( + "SecRuleRemoveById", + cmd_rule_remove_by_id, + NULL, + CMD_SCOPE_ANY, + "" // TODO + ), + + AP_INIT_ITERATE ( + "SecRuleRemoveByMsg", + cmd_rule_remove_by_msg, + NULL, + CMD_SCOPE_ANY, + "" // TODO + ), + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 13:00:56:319 GMT-08:00 + 2008-01-11 :: 14:52:48:192 GMT-08:00 + + brian + brian + apache2/apache2_config.c + item.type.label.suggestion + item.severity.label.trivial + Fix TODO or change to ENH. + AP_INIT_TAKE1 ( + "SecTmpDir", + cmd_tmp_dir, + NULL, + CMD_SCOPE_ANY, + "" // TODO + ), + + AP_INIT_TAKE1 ( + "SecUploadDir", + cmd_upload_dir, + NULL, + CMD_SCOPE_ANY, + "" // TODO + ), + + AP_INIT_TAKE1 ( + "SecUploadKeepFiles", + cmd_upload_keep_files, + NULL, + CMD_SCOPE_ANY, + "" // TODO + ), + + AP_INIT_TAKE1 ( + "SecWebAppId", + cmd_web_app_id, + NULL, + CMD_SCOPE_ANY, + "" // TODO + ), + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 13:01:32:996 GMT-08:00 + 2008-01-11 :: 14:54:32:209 GMT-08:00 + + brian + brian + apache2/mod_security2.c + item.type.label.suggestion + item.severity.label.trivial + Fix TODO or change to ENH. + /* Update the request headers. They might have changed after + * the body was read (trailers). + */ + // TODO We still need to keep a copy of the original headers + // to log in the audit log. + msr->request_headers = apr_table_copy(msr->mp, r->headers_in); + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 13:02:05:035 GMT-08:00 + 2008-01-09 :: 13:02:47:482 GMT-08:00 + + brian + brian + apache2/modsecurity.c + item.type.label.suggestion + item.severity.label.trivial + Yes, why do we ignore the rc - why have one at all? + // TODO: Why do we ignore return code here? + modsecurity_request_body_clear(msr, &my_error_msg); + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 13:03:24:928 GMT-08:00 + 2008-01-09 :: 13:04:49:633 GMT-08:00 + + brian + brian + apache2/msc_geo.c + item.type.label.suggestion + item.severity.label.trivial + Fix TODO. + offset = -3; + apr_file_seek(geo->db, APR_END, &offset); + /* TODO check offset */ + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 13:04:20:937 GMT-08:00 + 2008-01-09 :: 13:04:39:295 GMT-08:00 + + brian + brian + apache2/msc_geo.c + item.type.label.suggestion + item.severity.label.trivial + Fix TODO. + rc = apr_file_read_full(geo->db, &buf, 1, &nbytes); + /* TODO: check rc */ + geo->dbtype = (int)buf[0]; + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 13:05:16:204 GMT-08:00 + 2008-01-09 :: 13:05:27:720 GMT-08:00 + + brian + brian + apache2/msc_geo.c + item.type.label.suggestion + item.severity.label.trivial + Fix TODO. + apr_file_seek(geo->db, APR_SET, &seekto); + /* TODO: check rc */ + rc = apr_file_read_full(geo->db, &buf, (2 * reclen), &nbytes); + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 13:05:58:953 GMT-08:00 + 2008-01-09 :: 13:06:03:494 GMT-08:00 + + brian + brian + apache2/msc_geo.c + item.type.label.suggestion + item.severity.label.trivial + Fix TODO. + apr_file_seek(geo->db, APR_SET, &seekto); + /* TODO: check rc */ + rc = apr_file_read_full(geo->db, &cbuf, sizeof(cbuf), &nbytes); + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 13:06:45:422 GMT-08:00 + 2008-01-11 :: 14:55:10:355 GMT-08:00 + + brian + brian + apache2/msc_logging.c + item.type.label.suggestion + item.severity.label.trivial + Fix TODO or change to ENH. + /* The audit log storage directory should be explicitly + * defined. But if it isn't try to write to the same + * directory where the index file is placed. Of course, + * it is *very* bad practice to allow the Apache user + * to write to the same directory where a root user is + * writing to but it's not us that's causing the problem + * and there isn't anything we can do about that. + * + * TODO Actually there is something we can do! We will make + * SecAuditStorageDir mandatory, ask the user to explicitly + * define the storage location *and* refuse to work if the + * index and the storage location are in the same folder. + */ + if (msr->txcfg->auditlog_storage_dir == NULL) { + entry_filename = file_dirname(msr->mp, msr->txcfg->auditlog_name); + } + else { + entry_filename = msr->txcfg->auditlog_storage_dir; + } + if (entry_filename == NULL) return; + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 13:07:22:398 GMT-08:00 + 2008-01-11 :: 14:55:42:896 GMT-08:00 + + brian + brian + apache2/msc_logging.c + item.type.label.suggestion + item.severity.label.trivial + Change TODO to ENH. + /* AUDITLOG_PART_UPLOADS */ + // TODO: Implement + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 13:08:02:613 GMT-08:00 + 2008-01-11 :: 15:51:28:546 GMT-08:00 + + brian + brian + apache2/msc_lua.c + item.type.label.missing + item.severity.label.trivial + Log an error. + } else { + // TODO Error + return NULL; + } + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 13:08:51:978 GMT-08:00 + 2008-01-09 :: 13:09:44:680 GMT-08:00 + + brian + brian + apache2/msc_util.c + item.type.label.suggestion + item.severity.label.normal + Fix TODO. + char *resolve_relative_path(apr_pool_t *pool, const char *parent_filename, const char *filename) { + if (filename == NULL) return NULL; + // TODO Support paths on operating systems other than Unix. + if (filename[0] == '/') return (char *)filename; + + return apr_pstrcat(pool, apr_pstrndup(pool, parent_filename, + strlen(parent_filename) - strlen(apr_filepath_name_get(parent_filename))), + filename, NULL); +} + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 13:10:22:786 GMT-08:00 + 2008-01-09 :: 13:10:31:982 GMT-08:00 + + brian + brian + apache2/pdf_protect.c + item.type.label.suggestion + item.severity.label.trivial + Change from TODO to ENH. + // TODO We need ID and REV values for the PDF XSS alert. + +// TODO It would be nice if the user could choose the ID/REV/SEVERITY/MESSAGE, etc. + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 13:10:53:181 GMT-08:00 + 2008-01-09 :: 13:11:06:931 GMT-08:00 + + brian + brian + apache2/pdf_protect.c + item.type.label.suggestion + item.severity.label.trivial + Change from TODO to ENH. + // TODO Should we look at err_headers_out too? + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 13:11:23:251 GMT-08:00 + 2008-01-09 :: 13:11:28:132 GMT-08:00 + + brian + brian + apache2/pdf_protect.c + item.type.label.suggestion + item.severity.label.trivial + Change from TODO to ENH. + // TODO application/x-pdf, application/vnd.fdf, application/vnd.adobe.xfdf, + // application/vnd.adobe.xdp+xml, application/vnd.adobe.xfd+xml, application/vnd.pdf + // application/acrobat, text/pdf, text/x-pdf ??? + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 13:11:54:942 GMT-08:00 + 2008-01-09 :: 13:12:24:617 GMT-08:00 + + brian + brian + apache2/pdf_protect.c + item.type.label.missing + item.severity.label.trivial + Add the missing alert. + // TODO Log alert + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 13:13:04:159 GMT-08:00 + 2008-01-09 :: 13:13:38:064 GMT-08:00 + + brian + brian + apache2/re_actions.c + item.type.label.suggestion + item.severity.label.trivial + Not positive why the TODO here. Perhaps for a decision as to log and/or at what level? + msr_log(msr, 4, "Ctl: Set auditEngine to %d.", msr->txcfg->auditlog_flag); // TODO + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 13:14:02:379 GMT-08:00 + 2008-01-11 :: 15:42:21:749 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.suggestion + item.severity.label.trivial + Change from TODO to ENH. + // TODO Write & use string_ends(s, e). + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 13:14:29:571 GMT-08:00 + 2008-01-11 :: 15:43:53:997 GMT-08:00 + + brian + brian + apache2/re_operators.c + item.type.label.suggestion + item.severity.label.trivial + Remove the ifdef as lua is required? + #ifdef WITH_LUA + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 13:17:47:759 GMT-08:00 + 2008-01-11 :: 15:41:12:538 GMT-08:00 + + brian + brian + CHANGES + item.type.label.suggestion + item.severity.label.trivial + Remove TODO. + TODO: more to come + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 13:35:00:891 GMT-08:00 + 2008-01-09 :: 13:35:40:975 GMT-08:00 + + brian + brian + apache2/modsecurity.h + item.type.label.optimization + item.severity.label.trivial + Need to re-test implementing this as just a table. + /* data cache */ + apr_hash_t *tcache; + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 13:37:04:264 GMT-08:00 + 2008-01-09 :: 13:37:25:196 GMT-08:00 + + brian + brian + apache2/modsecurity.h + item.type.label.suggestion + item.severity.label.trivial + Should probably use STRINGIFY and define the numeric value. + #define MODSEC_VERSION_MAJOR "2" +#define MODSEC_VERSION_MINOR "5" +#define MODSEC_VERSION_MAINT "0" +#define MODSEC_VERSION_TYPE "rc" +#define MODSEC_VERSION_RELEASE "1" + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 14:08:14:025 GMT-08:00 + 2008-01-11 :: 15:40:12:054 GMT-08:00 + + brian + brian + apache2/msc_logging.c + item.type.label.suggestion + item.severity.label.trivial + Spelling. + throught + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-09 :: 14:21:32:782 GMT-08:00 + 2008-01-09 :: 14:22:32:803 GMT-08:00 + + brian + brian + apache2/msc_geo.h + item.type.label.suggestion + item.severity.label.trivial + This value may not be portable based on endianness. The algorithm compares it to the IP address as int in host order. + #define GEO_COUNTRY_OFFSET 0xffff00 + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 14:31:25:720 GMT-08:00 + 2008-01-09 :: 14:45:20:612 GMT-08:00 + + brian + brian + apache2/msc_geo.c + item.type.label.suggestion + item.severity.label.trivial + Check portability due to endianness. It seems that the DB values are assumed to be in host order. Perhaps the compiled DB is little-endian and we need to compensate? + + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 15:28:14:563 GMT-08:00 + 2008-01-09 :: 15:28:24:203 GMT-08:00 + + brian + brian + apache2/msc_multipart.c + item.type.label.irrelevant + item.severity.label.trivial + Remove code? + #if 0 +static char *multipart_construct_filename(modsec_rec *msr) { + char c, *p, *q = msr->mpd->mpp->filename; + char *filename; + + /* find the last backward slash and consider the + * filename to be only what's right from it + */ + p = strrchr(q, '\\'); + if (p != NULL) q = p + 1; + + /* do the same for the forward slash */ + p = strrchr(q, '/'); + if (p != NULL) q = p + 1; + + /* allow letters, digits and dots, replace + * everything else with underscores + */ + p = filename = apr_pstrdup(msr->mp, q); + while((c = *p) != 0) { + if (!( isalnum(c)||(c == '.') )) *p = '_'; + p++; + } + + return filename; +} +#endif + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 15:35:50:978 GMT-08:00 + 2008-01-09 :: 15:49:54:128 GMT-08:00 + + brian + brian + apache2/msc_multipart.c + item.type.label.programLogic + item.severity.label.trivial + The multipart C-D header is case insensitive (rfc 2183), so we should probably use strncasecmp() here. + /* accept only what we understand */ + if (strncmp(c_d_value, "form-data", 9) != 0) { + return -1; + } + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 16:05:05:601 GMT-08:00 + 2008-01-09 :: 16:07:17:436 GMT-08:00 + + brian + brian + apache2/msc_multipart.c + item.type.label.suggestion + item.severity.label.trivial + This allows for a name of "", so maybe check for name[0] == '\0' and return an error. Although we catch this later on as an unrecognized name and return -10. + start = p; + while((*p != '\0')&&(*p != '=')&&(*p != '\t')&&(*p != ' ')) p++; + if (*p == '\0') return -4; + + name = apr_pstrmemdup(msr->mp, start, (p - start)); + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 16:14:36:307 GMT-08:00 + 2008-01-09 :: 17:02:47:324 GMT-08:00 + + brian + brian + apache2/msc_multipart.c + item.type.label.suggestion + item.severity.label.trivial + For a quoted value we just include everything until the end quote. The field values should be US-ASCII 'qtext' or 'quoted-pair' (RFC 822) or escaped using RFC 2047. + if (*p == '"') { + *t = '\0'; + break; + } + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 16:59:12:520 GMT-08:00 + 2008-01-09 :: 17:00:55:566 GMT-08:00 + + brian + brian + apache2/msc_multipart.c + item.type.label.suggestion + item.severity.label.trivial + I think this is wrong. RFC 822 defines a quoted-string as <"> *(qtext/quoted-pair) <"> and quoted-par being able to quote any CHAR. + /* only " and \ can be escaped */ + if ((*(p + 1) == '"')||(*(p + 1) == '\\')) { + p++; + } + else { + /* improper escaping */ + + /* We allow for now because IE sends + * improperly escaped content and there's + * nothing we can do about it. + * + * return -9; + */ + } + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-09 :: 17:04:30:357 GMT-08:00 + 2008-01-09 :: 17:05:19:369 GMT-08:00 + + brian + brian + apache2/msc_multipart.c + item.type.label.suggestion + item.severity.label.trivial + RFC 2045 defines an 'attribute' as "ALWAYS case-insensitive", so these should be strncasecmp() + if (strcmp(name, "name") == 0) { + if (msr->mpd->mpp->name != NULL) return -14; + msr->mpd->mpp->name = value; + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Multipart: Content-Disposition name: %s", + log_escape_nq(msr->mp, value)); + } + } + else + if (strcmp(name, "filename") == 0) { + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-10 :: 12:24:59:317 GMT-08:00 + 2008-01-11 :: 16:58:44:275 GMT-08:00 + + brian + brian + apache2/msc_multipart.c + item.type.label.suggestion + item.severity.label.trivial + len is always assumed to be at least 1. What is preventing a len of 0? + if (len > 1) { + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-10 :: 12:27:34:472 GMT-08:00 + 2008-01-11 :: 15:39:17:001 GMT-08:00 + + brian + brian + apache2/msc_multipart.c + item.type.label.suggestion + item.severity.label.trivial + Use MULTIPART_BUF_SIZE for the constant. + if (strlen(new_value) > 4096) { + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-10 :: 13:04:27:048 GMT-08:00 + 2008-01-10 :: 13:04:57:229 GMT-08:00 + + brian + brian + apache2/msc_multipart.c + item.type.label.suggestion + item.severity.label.trivial + Cannot find anything that supports that this is or is not allowed. + /* Flag for whitespace after parameter name. */ + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-10 :: 13:05:06:226 GMT-08:00 + 2008-01-10 :: 13:05:11:552 GMT-08:00 + + brian + brian + apache2/msc_multipart.c + item.type.label.suggestion + item.severity.label.trivial + Cannot find anything that supports that this is or is not allowed. + /* Flag for whitespace before parameter value. */ + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-10 :: 13:12:20:299 GMT-08:00 + 2008-01-11 :: 15:38:39:261 GMT-08:00 + + brian + brian + apache2/msc_multipart.c + item.type.label.suggestion + item.severity.label.trivial + Use '\r' vs 0x0d as is done elsewhere. + if ((c == 0x0d)&&(msr->mpd->bufleft == 1)) { + /* we don't want to take 0x0d as the last byte in the buffer */ + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-10 :: 13:12:54:295 GMT-08:00 + 2008-01-11 :: 15:36:43:726 GMT-08:00 + + brian + brian + apache2/msc_multipart.c + item.type.label.suggestion + item.severity.label.trivial + Use '\n' vs 0x0a as is done elsewhere. + if ((c == 0x0a)||(msr->mpd->bufleft == 0)||(process_buffer)) { + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-10 :: 13:19:16:539 GMT-08:00 + 2008-01-11 :: 15:35:53:104 GMT-08:00 + + brian + brian + apache2/msc_parsers.c + item.type.label.suggestion + item.severity.label.trivial + Headers are not used. I think they were added for iconv support, but Ivan removed it. + #include "iconv.h" +#include <errno.h> + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-10 :: 13:26:56:609 GMT-08:00 + 2008-01-10 :: 13:27:21:817 GMT-08:00 + + brian + brian + apache2/msc_util.c + item.type.label.suggestion + item.severity.label.trivial + Be more consistent in naming. + #define VALID_HEX(X) (((X >= '0')&&(X <= '9')) || ((X >= 'a')&&(X <= 'f')) || ((X >= 'A')&&(X <= 'F'))) +#define ISODIGIT(X) ((X >= '0')&&(X <= '7')) + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-10 :: 13:32:29:278 GMT-08:00 + 2008-01-11 :: 16:51:13:950 GMT-08:00 + + brian + brian + apache2/msc_util.c + item.type.label.optimization + item.severity.label.trivial + No need to do a strlen twice *and* loop through the string. Just loop and exit on non-space. + if (strlen(string) == 0) return 1; + + for(i = 0; i < strlen(string); i++) { + if (!isspace(string[i])) { + return 0; + } + } + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + + 2008-01-10 :: 13:35:40:307 GMT-08:00 + 2008-01-10 :: 13:36:03:933 GMT-08:00 + + brian + brian + apache2/modsecurity.c + item.type.label.irrelevant + item.severity.label.trivial + Remove commented code? + /* Serial audit log mutext */ + rc = apr_global_mutex_create(&msce->auditlog_lock, NULL, APR_LOCK_DEFAULT, mp); + if (rc != APR_SUCCESS) { + //ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "mod_security: Could not create modsec_auditlog_lock"); + //return HTTP_INTERNAL_SERVER_ERROR; + return -1; + } + + #ifdef __SET_MUTEX_PERMS + rc = unixd_set_global_mutex_perms(msce->auditlog_lock); + if (rc != APR_SUCCESS) { + // ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, "mod_security: Could not set permissions on modsec_auditlog_lock; check User and Group directives"); + // return HTTP_INTERNAL_SERVER_ERROR; + return -1; + } + #endif + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-10 :: 13:36:28:229 GMT-08:00 + 2008-01-10 :: 13:36:52:702 GMT-08:00 + + brian + brian + apache2/modsecurity.c + item.type.label.suggestion + item.severity.label.trivial + What *should* we do on error here? + if (rc != APR_SUCCESS) { + // ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, "Failed to child-init auditlog mutex"); + } + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-10 :: 13:37:17:326 GMT-08:00 + 2008-01-10 :: 13:37:25:682 GMT-08:00 + + brian + brian + apache2/modsecurity.c + item.type.label.suggestion + item.severity.label.trivial + This does nothing. + /** + * Releases resources held by engine instance. + */ +void modsecurity_shutdown(msc_engine *msce) { + if (msce == NULL) return; +} + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-10 :: 13:39:01:265 GMT-08:00 + 2008-01-10 :: 13:39:22:070 GMT-08:00 + + brian + brian + apache2/modsecurity.c + item.type.label.suggestion + item.severity.label.trivial + Figure out the optimal initial size for the arrays/tables. + /* Collections. */ + msr->tx_vars = apr_table_make(msr->mp, 32); + if (msr->tx_vars == NULL) return -1; + + msr->geo_vars = apr_table_make(msr->mp, 8); + if (msr->geo_vars == NULL) return -1; + + msr->collections = apr_table_make(msr->mp, 8); + if (msr->collections == NULL) return -1; + msr->collections_dirty = apr_table_make(msr->mp, 8); + if (msr->collections_dirty == NULL) return -1; + + /* Other */ + msr->tcache = apr_hash_make(msr->mp); + if (msr->tcache == NULL) return -1; + + msr->matched_rules = apr_array_make(msr->mp, 16, sizeof(void *)); + if (msr->matched_rules == NULL) return -1; + + msr->matched_var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + if (msr->matched_var == NULL) return -1; + + msr->highest_severity = 255; /* high, invalid value */ + + msr->removed_rules = apr_array_make(msr->mp, 16, sizeof(char *)); + if (msr->removed_rules == NULL) return -1; + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-10 :: 13:42:33:518 GMT-08:00 + 2008-01-10 :: 13:42:50:351 GMT-08:00 + + brian + brian + apache2/msc_xml.c + item.type.label.suggestion + item.severity.label.trivial + Remove #if 0'd code? + #if 0 +static void xml_receive_sax_error(void *data, const char *msg, ...) { + modsec_rec *msr = (modsec_rec *)data; + char message[256]; + + if (msr == NULL) return; + + apr_snprintf(message, sizeof(message), "%s (line %d offset %d)", + log_escape_nq(msr->mp, msr->xml->parsing_ctx->lastError.message), + msr->xml->parsing_ctx->lastError.line, + msr->xml->parsing_ctx->lastError.int2); + + msr_log(msr, 5, "XML: Parsing error: %s", message); +} +#endif + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-10 :: 13:54:16:193 GMT-08:00 + 2008-01-10 :: 13:54:45:610 GMT-08:00 + + brian + brian + apache2/mod_security2.c + item.type.label.suggestion + item.severity.label.trivial + What is the history of this? + /* Our own hook to handle RPC transactions (not used at the moment). + * // ap_hook_handler(hook_handler, NULL, NULL, APR_HOOK_MIDDLE); + */ + + + item.resolution.label.validNeedsfixing + item.status.label.open + + + + 2008-01-10 :: 13:56:58:413 GMT-08:00 + 2008-01-11 :: 15:34:14:676 GMT-08:00 + + brian + brian + apache2/msc_lua.c + item.type.label.suggestion + item.severity.label.trivial + Change C++ to C style comment. + // Get the response from the script. + + + item.resolution.label.validNeedsfixing + item.status.label.resolved + + + diff --git a/review/pre-2.5-brian.txt b/review/pre-2.5-brian.txt new file mode 100644 index 00000000..3f2f49f9 --- /dev/null +++ b/review/pre-2.5-brian.txt @@ -0,0 +1,982 @@ +File: apache2/apache2_config.c +=================================================================== +[brian] Irrelevant @ 1251 + +Remove code? + +/* +static const char *cmd_rule_import_by_id(cmd_parms *cmd, void *_dcfg, const char *p1) { + directory_config *dcfg = (directory_config *)_dcfg; + rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception)); + if (dcfg == NULL) return NULL; + + re->type = RULE_EXCEPTION_IMPORT_ID; + // TODO verify p1 + re->param = p1; + *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re; + + return NULL; +} + +static const char *cmd_rule_import_by_msg(cmd_parms *cmd, void *_dcfg, const char *p1) { + directory_config *dcfg = (directory_config *)_dcfg; + rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception)); + if (dcfg == NULL) return NULL; + + re->type = RULE_EXCEPTION_IMPORT_MSG; + // TODO verify p1 + re->param = p1; + *(rule_exception **)apr_array_push(dcfg->rule_exceptions) = re; + + return NULL; +} +*/ + + +[brian] Suggestion @ 1514 + +Could use strtol as Ivan has as well. + +intval = apr_atoi64(charval); + if (errno == ERANGE) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations minlen out of range: %s", charval); + } + if (intval < 0) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations minlen must be positive: %s", charval); + } + + +[brian] Suggestion @ 1522 + +Portable? + +/* The NOT_SET indicator is -1, a signed long, and therfore + * we cannot be >= the unsigned value of NOT_SET. + */ + if ((unsigned long)intval >= (unsigned long)NOT_SET) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations minlen must be less than: %lu", (unsigned long)NOT_SET); + } + + +[brian] Suggestion @ 1534 + +Could use strtol as Ivan has as well. + +intval = apr_atoi64(charval); + if (errno == ERANGE) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen out of range: %s", charval); + } + if (intval < 0) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen must be positive: %s", charval); + } + + +[brian] Suggestion @ 1542 + +Portable? + +/* The NOT_SET indicator is -1, a signed long, and therfore + * we cannot be >= the unsigned value of NOT_SET. + */ + if ((unsigned long)intval >= (unsigned long)NOT_SET) { + return apr_psprintf(cmd->pool, "ModSecurity: SecCacheTransformations maxlen must be less than: %lu", (unsigned long)NOT_SET); + } + + + +File: apache2/apache2_io.c +=================================================================== +[brian] ProgramLogic @ 17 + +Remove code? It is actually used below, so need to verify. + +#if 0 +static void dummy_free_func(void *data) {} +#endif + + +[brian] ProgramLogic @ 92 + +dummy_free_func() is defined where? It is ifdef'd out at the top of source, so need to verify it is valid. + +/* Do not make a copy of the data we received in the chunk. */ + bucket = apr_bucket_heap_create(chunk->data, chunk->length, dummy_free_func, + f->r->connection->bucket_alloc); + + +[brian] ProgramLogic @ 224 + +Returning here may fail to free chunks data due to modsecurity_request_body_end() not being called. + +int rcbs = modsecurity_request_body_store(msr, buf, buflen, error_msg); + if (rcbs < 0) { + if (rcbs == -5) { + *error_msg = apr_psprintf(msr->mp, "Requests body no files data length is larger than the " + "configured limit (%lu).", msr->txcfg->reqbody_no_files_limit); + return -5; + } + + return -1; + } + + +[brian] Suggestion @ 246 + +Yes, why do we ignore the rc - why have one at all? + +// TODO: Why ignore the return code here? + + + +File: apache2/mod_security2.c +=================================================================== +[brian] Suggestion @ 1074 + +What is the history of this? + +/* Our own hook to handle RPC transactions (not used at the moment). + * // ap_hook_handler(hook_handler, NULL, NULL, APR_HOOK_MIDDLE); + */ + + + +File: apache2/modsecurity.c +=================================================================== +[brian] Irrelevant @ 100 + +Remove commented code? + +/* Serial audit log mutext */ + rc = apr_global_mutex_create(&msce->auditlog_lock, NULL, APR_LOCK_DEFAULT, mp); + if (rc != APR_SUCCESS) { + //ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "mod_security: Could not create modsec_auditlog_lock"); + //return HTTP_INTERNAL_SERVER_ERROR; + return -1; + } + + #ifdef __SET_MUTEX_PERMS + rc = unixd_set_global_mutex_perms(msce->auditlog_lock); + if (rc != APR_SUCCESS) { + // ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, "mod_security: Could not set permissions on modsec_auditlog_lock; check User and Group directives"); + // return HTTP_INTERNAL_SERVER_ERROR; + return -1; + } + #endif + + +[brian] Suggestion @ 126 + +What *should* we do on error here? + +if (rc != APR_SUCCESS) { + // ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, "Failed to child-init auditlog mutex"); + } + + +[brian] Suggestion @ 132 + +This does nothing. + +/** + * Releases resources held by engine instance. + */ +void modsecurity_shutdown(msc_engine *msce) { + if (msce == NULL) return; +} + + +[brian] Suggestion @ 178 + +Yes, why do we ignore the rc - why have one at all? + +// TODO: Why do we ignore return code here? + modsecurity_request_body_clear(msr, &my_error_msg); + + +[brian] Suggestion @ 196 + +Good. This looks to solve the other issues noted as possible memory leaks in body chunk data due to modsecurity_request_body_end() not being called. Need to verify, though. + +/* Register TX cleanup */ + apr_pool_cleanup_register(msr->mp, msr, modsecurity_tx_cleanup, apr_pool_cleanup_null); + + +[brian] Suggestion @ 298 + +Figure out the optimal initial size for the arrays/tables. + +/* Collections. */ + msr->tx_vars = apr_table_make(msr->mp, 32); + if (msr->tx_vars == NULL) return -1; + + msr->geo_vars = apr_table_make(msr->mp, 8); + if (msr->geo_vars == NULL) return -1; + + msr->collections = apr_table_make(msr->mp, 8); + if (msr->collections == NULL) return -1; + msr->collections_dirty = apr_table_make(msr->mp, 8); + if (msr->collections_dirty == NULL) return -1; + + /* Other */ + msr->tcache = apr_hash_make(msr->mp); + if (msr->tcache == NULL) return -1; + + msr->matched_rules = apr_array_make(msr->mp, 16, sizeof(void *)); + if (msr->matched_rules == NULL) return -1; + + msr->matched_var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + if (msr->matched_var == NULL) return -1; + + msr->highest_severity = 255; /* high, invalid value */ + + msr->removed_rules = apr_array_make(msr->mp, 16, sizeof(char *)); + if (msr->removed_rules == NULL) return -1; + + + +File: apache2/modsecurity.h +=================================================================== +[brian] Suggestion @ 63 + +Should probably use STRINGIFY and define the numeric value. + +#define MODSEC_VERSION_MAJOR "2" +#define MODSEC_VERSION_MINOR "5" +#define MODSEC_VERSION_MAINT "0" +#define MODSEC_VERSION_TYPE "rc" +#define MODSEC_VERSION_RELEASE "1" + + +[brian] Optimization @ 363 + +Need to re-test implementing this as just a table. + +/* data cache */ + apr_hash_t *tcache; + + + +File: apache2/msc_geo.c +=================================================================== +[brian] Suggestion + +Check portability due to endianness. It seems that the DB values are assumed to be in host order. Perhaps the compiled DB is little-endian and we need to compensate? + + +[brian] Suggestion @ 153 + +Fix TODO. + +offset = -3; + apr_file_seek(geo->db, APR_END, &offset); + /* TODO check offset */ + + +[brian] Suggestion @ 176 + +Fix TODO. + +rc = apr_file_read_full(geo->db, &buf, 1, &nbytes); + /* TODO: check rc */ + geo->dbtype = (int)buf[0]; + + +[brian] Suggestion @ 325 + +Fix TODO. + +apr_file_seek(geo->db, APR_SET, &seekto); + /* TODO: check rc */ + rc = apr_file_read_full(geo->db, &buf, (2 * reclen), &nbytes); + + +[brian] Suggestion @ 374 + +Fix TODO. + +apr_file_seek(geo->db, APR_SET, &seekto); + /* TODO: check rc */ + rc = apr_file_read_full(geo->db, &cbuf, sizeof(cbuf), &nbytes); + + + +File: apache2/msc_geo.h +=================================================================== +[brian] Suggestion @ 16 + +This value may not be portable based on endianness. The algorithm compares it to the IP address as int in host order. + +#define GEO_COUNTRY_OFFSET 0xffff00 + + + +File: apache2/msc_logging.c +=================================================================== +[brian] ProgramLogic @ 235 + +This allows an empty string as a valid part. This misvalidates "ctl:auditLogParts=+", etc. + +is_valid_parts_specification + + +[brian] Suggestion @ 432 + +apr_dir_make_recursive will attempt to create the dir straight away and if that fails keep backing off a dir until it can start creating, so I see no need to cache. Besides, what happens if you cache, then someone deletes the path from outside apache? + +/* IMP1 Surely it would be more efficient to check the folders for + * the audit log repository base path in the configuration phase, to reduce + * the work we do on every request. Also, since our path depends on time, + * we could cache the time we last checked and don't check if we know + * the folder is there. + */ + rc = apr_dir_make_recursive(entry_basename, CREATEMODE_DIR, msr->mp); + + + +File: apache2/msc_multipart.c +=================================================================== +[brian] Irrelevant @ 18 + +Remove code? + +#if 0 +static char *multipart_construct_filename(modsec_rec *msr) { + char c, *p, *q = msr->mpd->mpp->filename; + char *filename; + + /* find the last backward slash and consider the + * filename to be only what's right from it + */ + p = strrchr(q, '\\'); + if (p != NULL) q = p + 1; + + /* do the same for the forward slash */ + p = strrchr(q, '/'); + if (p != NULL) q = p + 1; + + /* allow letters, digits and dots, replace + * everything else with underscores + */ + p = filename = apr_pstrdup(msr->mp, q); + while((c = *p) != 0) { + if (!( isalnum(c)||(c == '.') )) *p = '_'; + p++; + } + + return filename; +} +#endif + + +[brian] ProgramLogic @ 52 + +The multipart C-D header is case insensitive (rfc 2183), so we should probably use strncasecmp() here. + +/* accept only what we understand */ + if (strncmp(c_d_value, "form-data", 9) != 0) { + return -1; + } + + +[brian] Suggestion @ 75 + +This allows for a name of "", so maybe check for name[0] == '\0' and return an error. Although we catch this later on as an unrecognized name and return -10. + +start = p; + while((*p != '\0')&&(*p != '=')&&(*p != '\t')&&(*p != ' ')) p++; + if (*p == '\0') return -4; + + name = apr_pstrmemdup(msr->mp, start, (p - start)); + + +[brian] Suggestion @ 106 + +I think this is wrong. RFC 822 defines a quoted-string as <"> *(qtext/quoted-pair) <"> and quoted-par being able to quote any CHAR. + +/* only " and \ can be escaped */ + if ((*(p + 1) == '"')||(*(p + 1) == '\\')) { + p++; + } + else { + /* improper escaping */ + + /* We allow for now because IE sends + * improperly escaped content and there's + * nothing we can do about it. + * + * return -9; + */ + } + + +[brian] Suggestion @ 122 + +For a quoted value we just include everything until the end quote. The field values should be US-ASCII 'qtext' or 'quoted-pair' (RFC 822) or escaped using RFC 2047. + +if (*p == '"') { + *t = '\0'; + break; + } + + +[brian] Suggestion @ 143 + +RFC 2045 defines an 'attribute' as "ALWAYS case-insensitive", so these should be strncasecmp() + +if (strcmp(name, "name") == 0) { + if (msr->mpd->mpp->name != NULL) return -14; + msr->mpd->mpp->name = value; + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Multipart: Content-Disposition name: %s", + log_escape_nq(msr->mp, value)); + } + } + else + if (strcmp(name, "filename") == 0) { + + +[brian] Suggestion @ 722 + +Cannot find anything that supports that this is or is not allowed. + +/* Flag for whitespace after parameter name. */ + + +[brian] Suggestion @ 735 + +Cannot find anything that supports that this is or is not allowed. + +/* Flag for whitespace before parameter value. */ + + + +File: apache2/msc_util.c +=================================================================== +[brian] Suggestion @ 24 + +Be more consistent in naming. + +#define VALID_HEX(X) (((X >= '0')&&(X <= '9')) || ((X >= 'a')&&(X <= 'f')) || ((X >= 'A')&&(X <= 'F'))) +#define ISODIGIT(X) ((X >= '0')&&(X <= '7')) + + +[brian] Suggestion @ 1186 + +Fix TODO. + +char *resolve_relative_path(apr_pool_t *pool, const char *parent_filename, const char *filename) { + if (filename == NULL) return NULL; + // TODO Support paths on operating systems other than Unix. + if (filename[0] == '/') return (char *)filename; + + return apr_pstrcat(pool, apr_pstrndup(pool, parent_filename, + strlen(parent_filename) - strlen(apr_filepath_name_get(parent_filename))), + filename, NULL); +} + + + +File: apache2/msc_xml.c +=================================================================== +[brian] Suggestion @ 27 + +Remove #if 0'd code? + +#if 0 +static void xml_receive_sax_error(void *data, const char *msg, ...) { + modsec_rec *msr = (modsec_rec *)data; + char message[256]; + + if (msr == NULL) return; + + apr_snprintf(message, sizeof(message), "%s (line %d offset %d)", + log_escape_nq(msr->mp, msr->xml->parsing_ctx->lastError.message), + msr->xml->parsing_ctx->lastError.line, + msr->xml->parsing_ctx->lastError.int2); + + msr_log(msr, 5, "XML: Parsing error: %s", message); +} +#endif + + + +File: apache2/pdf_protect.c +=================================================================== +[brian] Suggestion @ 26 + +Change from TODO to ENH. + +// TODO We need ID and REV values for the PDF XSS alert. + +// TODO It would be nice if the user could choose the ID/REV/SEVERITY/MESSAGE, etc. + + +[brian] Suggestion @ 217 + +Change from TODO to ENH. + +// TODO Should we look at err_headers_out too? + + +[brian] Suggestion @ 244 + +Change from TODO to ENH. + +// TODO application/x-pdf, application/vnd.fdf, application/vnd.adobe.xfdf, + // application/vnd.adobe.xdp+xml, application/vnd.adobe.xfd+xml, application/vnd.pdf + // application/acrobat, text/pdf, text/x-pdf ??? + + +[brian] Missing @ 472 + +Add the missing alert. + +// TODO Log alert + + + +File: apache2/re.c +=================================================================== +[brian] Missing @ 47 + +Need to log on failure. + +var = msre_create_var(ruleset, telts[i].key, telts[i].val, NULL, error_msg); + if (var == NULL) return -1; + + +[brian] Suggestion @ 297 + +Should replace with isvarnamechar() if possible. + +while((*p != '\0')&&(*p != '|')&&(*p != ':')&&(*p != ',')&&(!isspace(*p))) p++; /* ENH replace with isvarnamechar() */ + + +[brian] Irrelevant @ 608 + +Does not appear to be used anywhere. + +/** + * Destroys an engine instance, releasing the consumed memory. + */ +void msre_engine_destroy(msre_engine *engine) { + /* Destroyed automatically by the parent pool. + * apr_pool_destroy(engine->mp); + */ +} + + +[brian] Optimization @ 1150 + +tags set to NULL would be a bit better as it would stop apr_pstrcat() earlier, but tags *must* remain last or wierd results. + +char *tags = ""; + + +[brian] Suggestion @ 1179 + +Implement TODO. + +//TODO: restrict to 512 bytes + + +[brian] Optimization @ 1528 + +This causes two loops through the action list. Perhaps there is a more performant way to do these at the same time? Maybe split into two lists? + +/* Perform non-disruptive actions. */ + msre_perform_nondisruptive_actions(msr, rule, rule->actionset, mptmp); + + /* Perform disruptive actions, but only if + * this rule is not part of a chain. + */ + if (rule->actionset->is_chained == 0) { + msre_perform_disruptive_actions(msr, rule, acting_actionset, mptmp, my_error_msg); + } + + + +File: apache2/re.h +=================================================================== +[brian] Irrelevant @ 86 + +Does not appear to be used anywhere. + +void DSOLOCAL msre_engine_destroy(msre_engine *engine); + + +[brian] Suggestion @ 148 + +Why not stored in op_param_data like @rx, etc. The param_data is used w/exec action for lua. + +/* Compiled Lua script. */ + msc_script *script; + + + +File: apache2/re_actions.c +=================================================================== +[brian] Optimization @ 170 + +This implementation comment needs to be coded as many string operators now attempt to resolve macros. + +/* IMP1 Duplicate the string and create the array on + * demand, thus not having to do it if there are + * no macros in the input data. + */ + + data = apr_pstrdup(mptmp, var->value); /* IMP1 Are we modifying data anywhere? */ + arr = apr_array_make(mptmp, 16, sizeof(msc_string *)); + if ((data == NULL)||(arr == NULL)) return -1; + + +[brian] Irrelevant @ 209 + +This #if 0'd out code should be removed. + +/* Removed %0-9 macros as it messes up urlEncoding in the match + * where having '%0a' will be treated as %{TX.0}a, which is incorrect. + * */ +#if 0 + else if ((*(p + 1) >= '0')&&(*(p + 1) <= '9')) { + /* Special case for regex captures. */ + var_name = "TX"; + var_value = apr_pstrmemdup(mptmp, p + 1, 1); + next_text_start = p + 2; + } +#endif + + +[brian] Suggestion @ 257 + +Should log a level 9 msg here. + +} else { + /* We could not identify a valid macro so add it as text. */ + part = (msc_string *)apr_pcalloc(mptmp, sizeof(msc_string)); + if (part == NULL) return -1; + part->value_len = p - text_start + 1; /* len(text)+len("%") */ + part->value = apr_pstrmemdup(mptmp, text_start, part->value_len); + *(msc_string **)apr_array_push(arr) = part; + + next_text_start = p + 1; + } + + +[brian] Optimization @ 276 + +Use apr_array_pstrcat(msr->mp, arr, NULL) instead? + +/* If there's more than one member of the array that + * means there was at least one macro present. Combine + * text parts into a single string now. + */ + if (arr->nelts > 1) { + /* Figure out the required size for the string. */ + var->value_len = 0; + for(i = 0; i < arr->nelts; i++) { + part = ((msc_string **)arr->elts)[i]; + var->value_len += part->value_len; + } + + /* Allocate the string. */ + var->value = apr_palloc(msr->mp, var->value_len + 1); + if (var->value == NULL) return -1; + + /* Combine the parts. */ + offset = 0; + for(i = 0; i < arr->nelts; i++) { + part = ((msc_string **)arr->elts)[i]; + memcpy((char *)(var->value + offset), part->value, part->value_len); + offset += part->value_len; + } + var->value[offset] = '\0'; + } + + +[brian] Missing @ 402 + +Implement. Need to check if Apache will return an invalid status code + +/* status */ +static char *msre_action_status_validate(msre_engine *engine, msre_action *action) { + /* ENH action->param must be a valid HTTP status code. */ + return NULL; +} + + +[brian] Missing @ 422 + +Implement. + +/* pause */ +static char *msre_action_pause_validate(msre_engine *engine, msre_action *action) { + /* ENH Validate a positive number. */ + return NULL; +} + + +[brian] Missing @ 434 + +Implement as a valid URI check with apr_uri_parse()? + +/* redirect */ + +static char *msre_action_redirect_validate(msre_engine *engine, msre_action *action) { + /* ENH Add validation. */ + return NULL; +} + + +[brian] Missing @ 465 + +Implement as a valid URI check with apr_uri_parse()? + +/* proxy */ + +static char *msre_action_proxy_validate(msre_engine *engine, msre_action *action) { + /* ENH Add validation. */ + return NULL; +} + + +[brian] Irrelevant @ 507 + +I do not see a need to validate beyound what is already done in the init function. + +msre_action_skip_validate +msre_action_skipAfter_validate + + +[brian] Missing @ 570 + +Implement. + +/* phase */ + +static char *msre_action_phase_validate(msre_engine *engine, msre_action *action) { + /* ENH Add validation. */ + return NULL; +} + + +[brian] Suggestion @ 612 + +Probably should also calc length and validate a length > 0 instead of just checking NULL. Other checks would benefit from checking a length as well, so no harm in calculating that. + +if (value == NULL) { + return apr_psprintf(engine->mp, "Missing ctl value for name: %s", name); + } + + +[brian] Irrelevant @ 708 + +Why register init() if we do not use it? + +static apr_status_t msre_action_ctl_init(msre_engine *engine, msre_actionset *actionset, + msre_action *action) +{ + /* Do nothing. */ + return 1; +} + + +[brian] Optimization @ 774 + +TODO needs looked into. + +if (strcasecmp(name, "auditEngine") == 0) { + if (strcasecmp(value, "on") == 0) { + msr->txcfg->auditlog_flag = AUDITLOG_ON; + msr->usercfg->auditlog_flag = AUDITLOG_ON; + } + + if (strcasecmp(value, "off") == 0) { + msr->txcfg->auditlog_flag = AUDITLOG_OFF; + msr->usercfg->auditlog_flag = AUDITLOG_OFF; + } + + if (strcasecmp(value, "relevantonly") == 0) { + msr->txcfg->auditlog_flag = AUDITLOG_RELEVANT; + msr->usercfg->auditlog_flag = AUDITLOG_RELEVANT; + } + + msr_log(msr, 4, "Ctl: Set auditEngine to %d.", msr->txcfg->auditlog_flag); // TODO + + return 1; + } else + + +[brian] Suggestion @ 790 + +Not positive why the TODO here. Perhaps for a decision as to log and/or at what level? + +msr_log(msr, 4, "Ctl: Set auditEngine to %d.", msr->txcfg->auditlog_flag); // TODO + + +[brian] Missing @ 855 + +Should log an internal error here. + +else { + /* ENH Should never happen, but log if it does. */ + return -1; + } + + +[brian] Suggestion @ 1152 + +Probably should use apr_strtoi64 where we can tell if there was an error in conversion since we are potentially taking a value from a macro expansion. Also may want to look for overflow. + +value += atoi(var_value); + + +[brian] Suggestion @ 1288 + +Not sure why we would not want to deprecate a TX var. Further rules could use this even if TX is not persisted. + +/* IMP1 Add message TX variables cannot deprecate in value. */ + + +[brian] Suggestion @ 1383 + +The timeout is hardcoded to 3600. The docs state TIMEOUT is read-only, but this is not true. So, you can modify TIMEOUT. + +/* IMP1 Is the timeout hard-coded to 3600? */ + + +[brian] Suggestion @ 1555 + +We already have support for relative filenames, but cannot get to this data from here. This needs solved by passing more data to the validate function (cmd_parms rec). Maybe need a warning here stating we do not support them yet, or it might be confusing to users that we do not here but do elsewhere. + +/* TODO Support relative filenames. */ + + +[brian] Suggestion @ 1557 + +Not sure using an extension is a good idea here. Better I think would be to specify a type: "exec:[type=]/path/to/file" as in "exec:lua=/path/to/script" and make param_data a script_rec with a type and value. Also we use the abstract param_data here vs using a specific field as in SecRuleScript. + +/* Process Lua scripts internally. */ + if (strlen(filename) > 4) { + char *p = filename + strlen(filename) - 4; + if ((p[0] == '.')&&(p[1] == 'l')&&(p[2] == 'u')&&(p[3] == 'a')) { + /* It's a Lua script. */ + msc_script *script = NULL; + + /* Compile script. */ + char *msg = lua_compile(&script, filename, engine->mp); + if (msg != NULL) return msg; + + action->param_data = script; + } + } + + +[brian] Suggestion @ 1578 + +This assumes lua is the only type (which it is now), but should be re-writen with a script_rec stored in param_data. + +if (action->param_data != NULL) { /* Lua */ + msc_script *script = (msc_script *)action->param_data; + char *my_error_msg = NULL; + + if (lua_execute(script, NULL, msr, rule, &my_error_msg) < 0) { + msr_log(msr, 1, "%s", my_error_msg); + return 0; + } + } else { /* Execute as shell script. */ + + + +File: apache2/re_operators.c +=================================================================== +[brian] Missing + +Need more unit tests for operators. Start with new operators. + + +[brian] Suggestion @ 246 + +Use resolve_relative_path() instead? Maybe a config_relative_path() to just get the path? + +/* Get the path of the rule filename to use as a base */ + rulefile_path = apr_pstrndup(rule->ruleset->mp, rule->filename, strlen(rule->filename) - strlen(apr_filepath_name_get(rule->filename))); + + +[brian] Missing @ 310 + +Need to check return code and log an error on failure. + +acmp_add_pattern(p, buf, NULL, NULL, strlen(buf)); + + +[brian] Missing @ 315 + +Need to check return code and log an error on failure. + +acmp_prepare(p); + + +[brian] Optimization @ 379 + +See if apr_strmatch is faster. + +msre_op_within_execute + + +[brian] Optimization @ 442 + +See if apr_strmatch is faster. + +msre_op_contains_execute + + +[brian] Optimization @ 506 + +See if apr_strmatch is faster. + +msre_op_containsWord_execute + + +[brian] Missing @ 1330 + +Need an error_msg set for lua execution error. + +rc = lua_execute(script, target, msr, rule, error_msg); + if (rc < 0) { + /* Error. */ + return -1; + } + + +[brian] Missing @ 1477 + +@validateurlEncoding does not output VAR name nor offset in error_msg on match. + +msre_op_validateUrlEncoding_execute + + +[brian] Missing @ 1885 + +@m operator is not documented. This does the same as @contains, so it was suggested earlier to use the @m algorithm for contains (if faster) and drop @m. + +/* m */ + msre_engine_op_register(engine, + "m", + msre_op_m_param_init, + msre_op_m_execute + ); + + + +File: apache2/re_tfns.c +=================================================================== +[brian] Optimization @ 77 + +No need to set this on all. Only set it once when we find the first non-space char. + +(*rval)[i] = '\0'; + + + diff --git a/review/review-summary.pl b/review/review-summary.pl new file mode 100755 index 00000000..799f1672 --- /dev/null +++ b/review/review-summary.pl @@ -0,0 +1,50 @@ +#!/usr/bin/perl +use strict; +use XML::Simple; +#use Data::Dumper; + +my $REVIEW = shift @ARGV; +my %ISSUES = (); + +unless (defined $REVIEW) { + print STDERR "Usage: $0 [ ... ]\n"; + exit 1; +} + +# XML => hashref +my $review = XMLin( + $REVIEW, + KeepRoot => 0, + KeyAttr => { ReviewIssue => "+id" }, + ContentKey => "value", + SuppressEmpty => undef, +); +#print Dumper($review); + +# Reorg hashref to be only open issues by filename +for my $rec (values %{$review->{ReviewIssue} || {}}) { + my $key = defined($rec->{File}->{value}) ? $rec->{File}->{value} : ""; + push @{$ISSUES{$key}}, $rec if ($rec->{Status} =~ m/\.open$/); +} + + +# Write report +for my $fn (@ARGV ? (@ARGV) : (sort keys %ISSUES)) { + print "File: $fn\n"; + print "===================================================================\n"; + for my $r (sort { $a->{File}->{line} <=> $b->{File}->{line} || $a->{ReviewerId} cmp $b->{ReviewerId} } @{$ISSUES{$fn} || []}) { + (my $type = $r->{Type}) =~ s/^.*\.([^\.]+)$/$1/; + $type = ucfirst($type); + (my $res = $r->{Resolution}) =~ s/^.*\.([^\.]+)$/$1/; + my $line = ($r->{File}->{line} and $r->{File}->{line} > 1) ? " @ $r->{File}->{line}" : ""; + my $summary = $r->{Summary} ? "\n$r->{Summary}\n\n" : ""; + my $desc = $r->{Description} ? "$r->{Description}\n\n" : ""; + + print << "EOT"; +[$r->{ReviewerId}] $type$line +$summary$desc +EOT + } + print "\n"; +} + diff --git a/rules/CHANGELOG b/rules/CHANGELOG index e78a4fa9..1b5b56ca 100644 --- a/rules/CHANGELOG +++ b/rules/CHANGELOG @@ -1,19 +1,151 @@ +-------------------------- +Version 1.6.0 - 2008/02/19 +-------------------------- + +New Rulesets & Features: +- 42 - Tight Security + This ruleset contains currently 2 rules which are considered highly prone + to FPs. They take care of Path Traversal attacks, and RFI attacks. This + ruleset is included in the optional_rulesets dir +- 42 - Comment Spam + Comment Spam is used by the spammers to increase their rating in search + engines by posting links to their site in other sites that allow posting + of comments and messages. The rules in this ruleset will work against that. + (Requires ModSecurity 2.5) +- Tags + A single type of attack is often detected by multiple rules. The new alert + classification tags solve this issue by providing an alternative alert type + indication and can serve for filtering and analysis of audit logs. + The classification tags are hierarchical with slashes separating levels. + Usually there are two levels with the top level describing the alert group + and the lower level denoting the alert type itself, for example: + WEB_ATTACK/SQL_INJECTION. + +False Positives Fixes: +- Rule 960903 - Moved to phase 4 instead of 5 to avoid FPs +- Rule 950107 - Will look for invalid url decoding in variables that are not + automatically url decoded + +Additional rules logic: +- Using the new "logdata" action for logging the matched signature in rules +- When logging an event once, init the collection only if the alert needs to log +- Using the new operator @pm as a qualifier before large rules to enhance + performance (Requires ModSecurity 2.5) +- SQL injection - A smarter regexp is used to detect 1=1,2=2,etc.. and not + only 1=1. (Thanks to Marc Stern for the idea) +- New XSS signatures - iframe & flash XSS + + +------------------------- +Version 1.5.1 - 2007/12/6 +------------------------- + +False Positives Fixes: +- Protocol Anomalies (file 21) - exception for Apache SSL pinger (Request: GET /) + +New Events: +- 960019 - Detect HTTP/0.9 Requests + HTTP/0.9 request are not common these days. This rule will log by default, + and block in the blocking version of file 21 + +Other Fixes: +- File 40, Rules 950004,950005 - Repaired the correction for the double + url decoding problem +- File 55 contained empty regular expressions. Fixed. + +------------------------ +Version 1.5 - 2007/11/23 +------------------------ + +New Rulesets: +- 23 - Request Limits + "Judging by appearances". This rulesets contains rules blocking based on + the size of the request, for example, a request with too many arguments + will be denied. + +Default policy changes: +- XML protection off by default +- BLOCKING dir renamed to optional_rules +- Ruleset 55 (marketing) is now optional (added to the optional_rules dir) +- Ruleset 21 - The exception for apache internal monitor will not log anymore + +New Events: +- 960912 - Invalid request body + Malformed content will not be parsed by modsecurity, but still there might + be applications that will parse it, ignoring the errors. +- 960913 - Invalid Request + Will trigger a security event when request was rejected by apache with + code 400, without going through ModSecurity rules. + +Additional rules logic: +- 950001 - New signature: delete from +- 950007 - New signature: waitfor delay + +False Positives Fixes: +- 950006 - Will not be looking for /cc pattern in User-Agent header +- 950002 - "Internet Explorer" signature removed +- Double decoding bug used to cause FPs. Some of the parameters are already + url-decoded by apache. This caused FPs when the rule performed another + url-decoding transformation. The rules have been split so that parameters + already decoded by apache will not be decoded by the rules anymore. +- 960911 - Expression is much more permissive now +- 950801 - Commented out entirely. NOTE: If your system uses UTF8 encoding, + then you should uncomment this rule (in file 20) + +-------------------------- +version 1.4.3 - 2007/07/21 +-------------------------- + +New Events: +- 950012 - HTTP Request Smuggling + For more info on this attack: + http://www.cgisecurity.com/lib/HTTP-Request-Smuggling.pdf +- 960912 - Invalid request body + Malformed content will not be parsed by modsecurity, but still there might + be applications that will parse it, ignoring the errors. +- 960913 - Invalid Request + Will trigger a security event when request was rejected by apache with + code 400, without going through ModSecurity rules. + +False Positives Fixes: +- 950107 - Will allow a % sign in the middle of a string as well +- 960911 - A more accurate expression based on the rfc: + http://www.ietf.org/rfc/rfc2396.txt +- 950015 - Will not look for http/ pattern in the request headers + +Additional rules logic: +- Since Apache applies scope directives only after ModSecurity phase 1 + this directives cannot be used to exclude phase 1 rules. Therefore + we moved all inspection rules to phase 2. + -------------------------------- version 1.4 build 2 - 2007/05/17 -------------------------------- + New Feature: - Search for signatures in XML content + XML Content will be parsed and ispected for signatures New Events: -- 950107 - Unicode Full/Half Width Abuse Attack Attempt +- 950116 - Unicode Full/Half Width Abuse Attack Attempt + Full-width unicode can by used to bypass content inspection. Such encoding will be forbidden + http://www.kb.cert.org/vuls/id/739224 - 960911 - Invalid HTTP request line + Enforce request line to be valid, i.e.: - 960904 - Request Missing Content-Type (when there is content) + When a request contains content, the content-type must be specified. If not, the content will not be inspected - 970018 - IIS installed in default location (any drive) + Log once if IIS in installed in the /Inetpub directory (on any drive, not only C) - 950019 - Email Injection + Web forms used for sending mail (such as "tell a friend") are often manipulated by spammers for sending anonymous emails Regular expressions fixes: - Further optimization of some regular expressions (using the non-greediness operator) + The non-greediness operator, , prevents excessive backtracking + +FP fixes: +- Rule 950107 - Will allow a parameter to end in a % sign from now on ------------------------ version 1.4 - 2007/05/02 @@ -23,7 +155,7 @@ New Events: - 970021 - WebLogic information disclosure Matching of "JSP compile error" in the response body, will trigger this rule, with severity 4 (Warning) - 950015,950910,950911 - HTTP Response Splitting - Looking for HTTP Response Splitting patterns as described in Amit Klein's excellent article: + Looking for HTTP Response Splitting patterns as described in Amit Klein's excellent white paper: http://www.packetstormsecurity.org/papers/general/whitepaper_httpresponse.pdf ModSecurity does not support compressed content at the moment. Thus, the following rules have been added: - 960902 - Content-Encoding in request not supported @@ -60,11 +192,11 @@ Added persistent PDF UXSS detection rule Version 1.3.2 build 3 2007/01/10 -------------------------------- -Fixed regular expression in rule 960010 (file #30) to allow multipart form data +Fixed regular expression in rule 960010 (file #30) to allow multipart form data content -------------------------- -Version 1.3.2 - 2006/12/27 +Version 1.3.2 - 2006/12/27 -------------------------- New events: @@ -77,8 +209,8 @@ Regular expressions fixes: - Command Injections now always require certain characters both before and after the command. Important since many are common English words (finger, mail) - The command injection wget is not searched in the UA header as it has different meaning there. - LDAP Fixed to reduce FPs: - + More accurate regular expressions - + high bit characters not accpeted between signature tokens. + + More accurate regular expressions + + high bit characters not accpeted between signature tokens. - Do not detect ',,id:'950009',severity:'2'" - -# Blind SQL injection -SecRule REQUEST_FILENAME|ARGS|ARGS_NAMES|REQUEST_HEADERS|XML://*|!REQUEST_HEADERS:Referer "(?:\b(?:(?:s(?:ys\.(?:user_(?:(?:t(?:ab(?:_column|le)|rigger)|object|view)s|c(?:onstraints|atalog))|all_tables|tab)|elect\b.{0,40}\b(?:substring|ascii|user))|m(?:sys(?:(?:queri|ac)e|relationship|column|object)s|ysql.user)|c(?:onstraint_type|harindex)|attnotnull)\b|(?:locate|instr)\W+\()|\@\@spid\b)" \ - "capture,t:replaceComments,ctl:auditLogParts=+E,deny,log,auditlog,status:501,msg:'Blind SQL Injection Attack. Matched signature <%{TX.0}>',,id:'950007',severity:'2'" -#SecRule REQUEST_FILENAME|ARGS|ARGS_NAMES|REQUEST_HEADERS|XML://*|!REQUEST_HEADERS:Referer "\b(?:benchmark|encode)\b" \ -# "chain,ctl:auditLogParts=+E,deny,log,auditlog,status:501,msg:'Blind SQL Injection Attack. Matched signature <%{TX.0}>',,id:'950903',severity:'2'" -#SecRule REQUEST_FILENAME|ARGS|ARGS_NAMES|REQUEST_HEADERS|XML://*|!REQUEST_HEADERS:Referer "[\\(\)\%#]\|--" -SecRule REQUEST_FILENAME|ARGS|REQUEST_HEADERS|XML://*|!REQUEST_HEADERS:Referer "\b(?:(?:s(?:ys(?:(?:(?:process|tabl)e|filegroup|object)s|c(?:o(?:nstraint|lumn)s|at)|dba|ibm)|ubstr(?:ing)?)|user_(?:(?:(?:constrain|objec)t|tab(?:_column|le)|ind_column|user)s|password|group)|a(?:tt(?:rel|typ)id|ll_objects)|object_(?:(?:nam|typ)e|id)|pg_(?:attribute|class)|column_(?:name|id)|(?:dba|mb)_users|xtype\W+\bchar|rownum)\b|t(?:able_name\b|extpos\W+\())" \ - "capture,t:replaceComments,ctl:auditLogParts=+E,deny,log,auditlog,status:501,msg:'Blind SQL Injection Attack. Matched signature <%{TX.0}>',,id:'950904',severity:'2'" - -# SQL injection -SecRule REQUEST_FILENAME|ARGS|ARGS_NAMES|REQUEST_HEADERS|XML://*|!REQUEST_HEADERS:Referer "(?:\b(?:(?:s(?:elect\b(?:.{1,100}?\b(?:(?:length|count|top)\b.{1,100}?\bfrom|from\b.{1,100}?\bwhere)|.*?\b(?:d(?:ump\b.*\bfrom|ata_type)|(?:to_(?:numbe|cha)|inst)r))|p_(?:(?:addextendedpro|sqlexe)c|(?:oacreat|prepar)e|execute(?:sql)?|makewebtask)|ql_(?:longvarchar|variant))|xp_(?:reg(?:re(?:movemultistring|ad)|delete(?:value|key)|enum(?:value|key)s|addmultistring|write)|e(?:xecresultset|numdsn)|(?:terminat|dirtre)e|availablemedia|loginconfig|cmdshell|filelist|makecab|ntsec)|u(?:nion\b.{1,100}?\bselect|tl_(?:file|http))|group\b.*\bby\b.{1,100}?\bhaving|load\b\W*?\bdata\b.*\binfile|(?:n?varcha|tbcreato)r|autonomous_transaction|open(?:rowset|query)|1\s*=\s*1|dbms_java)\b|i(?:n(?:to\b\W*?\b(?:dump|out)file|sert\b\W*?\binto|ner\b\W*?\bjoin)\b|(?:f(?:\b\W*?\(\W*?\bbenchmark|null\b)|snull\b)\W*?\()|(?:having|or|and)\b\s+(?:\d{1,10}|[\'\"][^=]{1,10}[\'\"])\s*[=<>]+|print\]\b\W*?\@\@|cast\b\W*?\()|(?:;\W*?\b(?:shutdown|drop)|\@\@version)\b|'(?:s(?:qloledb|a)|msdasql|dbo)')" \ - "capture,t:replaceComments,ctl:auditLogParts=+E,deny,log,auditlog,status:501,msg:'SQL Injection Attack. Matched signature <%{TX.0}>',,id:'950001',severity:'2'" -#SecRule REQUEST_FILENAME|ARGS|ARGS_NAMES|REQUEST_HEADERS|XML://*|!REQUEST_HEADERS:Referer "\b(?:rel(?:(?:nam|typ)e|kind)|a(?:ttn(?:ame|um)|scii)|c(?:o(?:nver|un)t|ha?r)|s(?:hutdown|elect)|to_(?:numbe|cha)r|u(?:pdate|nion)|d(?:elete|rop)|group\b\W*\bby|having|insert|length|where)\b" \ -# "chain,ctl:auditLogParts=+E,deny,log,auditlog,status:501,msg:'SQL Injection Attack. Matched signature <%{TX.0}>',,id:'950905',severity:'2'" -#SecRule REQUEST_FILENAME|ARGS|ARGS_NAMES|REQUEST_HEADERS|XML://*|!REQUEST_HEADERS:Referer "[\\(\)\%#]\|--" -SecRule REQUEST_FILENAME|ARGS|REQUEST_HEADERS|XML://*|!REQUEST_HEADERS:Referer "\b(?:user_(?:(?:object|table|user)s|password|group)|a(?:tt(?:rel|typ)id|ll_objects)|object_(?:(?:nam|typ)e|id)|pg_(?:attribute|class)|column_(?:name|id)|substr(?:ing)?|table_name|mb_users|rownum)\b" \ - "capture,t:replaceComments,ctl:auditLogParts=+E,deny,log,auditlog,status:501,msg:'SQL Injection Attack. Matched signature <%{TX.0}>',,id:'950906',severity:'2'" -SecRule REQUEST_FILENAME|ARGS|ARGS_NAMES|REQUEST_HEADERS|XML://*|!REQUEST_HEADERS:Referer|!REQUEST_HEADERS:via "\b(?:coalesce\b|root\@)" \ - "capture,t:replaceComments,ctl:auditLogParts=+E,deny,log,auditlog,status:501,msg:'SQL Injection Attack. Matched signature <%{TX.0}>',,id:'950908',severity:'2'" - -# XSS -SecRule REQUEST_FILENAME|ARGS|ARGS_NAMES|REQUEST_HEADERS|XML://*|!REQUEST_HEADERS:Referer "(?:\b(?:on(?:(?:mo(?:use(?:o(?:ver|ut)|down|move|up)|ve)|key(?:press|down|up)|c(?:hange|lick)|s(?:elec|ubmi)t|(?:un)?load|dragdrop|resize|focus|blur)\b\W*?=|abort\b)|(?:l(?:owsrc\b\W*?\b(?:(?:java|vb)script|shell)|ivescript)|(?:href|url)\b\W*?\b(?:(?:java|vb)script|shell)|background-image|mocha):|type\b\W*?\b(?:text\b(?:\W*?\b(?:j(?:ava)?|ecma)script\b| [vbscript])|application\b\W*?\bx-(?:java|vb)script\b)|s(?:(?:tyle\b\W*=.*\bexpression\b\W*|ettimeout\b\W*?)\(|rc\b\W*?\b(?:(?:java|vb)script|shell|http):)|(?:c(?:opyparentfolder|reatetextrange)|get(?:special|parent)folder)\b|a(?:ctivexobject\b|lert\b\W*?\())|<(?:(?:body\b.*?\b(?:backgroun|onloa)d|input\b.*?\\btype\b\W*?\bimage)\b|!\[CDATA\[|script|meta)|(?:\.(?:(?:execscrip|addimpor)t|(?:fromcharcod|cooki)e|innerhtml)|\@import)\b)" \ - "capture,ctl:auditLogParts=+E,deny,log,auditlog,status:501,msg:'Cross-site Scripting (XSS) Attack. Matched signature <%{TX.0}>',,id:'950004',severity:'2'" - -# file injection -SecRule REQUEST_FILENAME|ARGS|ARGS_NAMES|REQUEST_HEADERS|XML://* "(?:\b(?:\.(?:ht(?:access|passwd|group)|www_?acl)|global\.asa|httpd\.conf|boot\.ini)\b|\/etc\/)" \ - "capture,ctl:auditLogParts=+E,deny,log,auditlog,status:501,msg:'Remote File Access Attempt. Matched signature <%{TX.0}>',,id:'950005',severity:'2'" - -# Command access -SecRule REQUEST_FILENAME "\b(?:n(?:map|et|c)|w(?:guest|sh)|cmd(?:32)?|telnet|rcmd|ftp)\.exe\b" \ - "capture,ctl:auditLogParts=+E,deny,log,auditlog,status:501,msg:'System Command Access. Matched signature <%{TX.0}>',,id:'950002',severity:'2'" - -# Command injection -SecRule ARGS|ARGS_NAMES|REQUEST_HEADERS|XML://*|!REQUEST_HEADERS:'/(Cookie|Referer|X-OS-Prefs)/'|REQUEST_COOKIES|REQUEST_COOKIES_NAMES "(?:\b(?:(?:n(?:et(?:\b\W+?\blocalgroup|\.exe)|(?:map|c)\.exe)|t(?:racer(?:oute|t)|elnet\.exe|clsh8?|ftp)|(?:w(?:guest|sh)|rcmd|ftp)\.exe|echo\b\W*?\by+)\b|c(?:md(?:(?:32)?\.exe\b|\b\W*?\/c)|d(?:\b\W*?[\\\/]|\W*?\.\.)|hmod.{0,40}?\+.{0,3}x))|[\;\|\`]\W*?\b(?:(?:c(?:h(?:grp|mod|own|sh)|md|pp|c)|p(?:asswd|ython|erl|ing|s)|n(?:asm|map|c)|f(?:inger|tp)|(?:kil|mai)l|(?:xte)?rm|ls(?:of)?|telnet|uname|echo|id)\b|g(?:\+\+|cc\b))|\/(?:c(?:h(?:grp|mod|own|sh)|pp|c)|p(?:asswd|ython|erl|ing|s)|n(?:asm|map|c)|f(?:inger|tp)|(?:kil|mai)l|g(?:\+\+|cc)|(?:xte)?rm|ls(?:of)?|telnet|uname|echo|id)(?:[\'\"\|\;\`\-\s]|$))" \ - "capture,ctl:auditLogParts=+E,deny,log,auditlog,status:501,msg:'System Command Injection. Matched signature <%{TX.0}>',,id:'950006',severity:'2'" -SecRule "ARGS|ARGS_NAMES|REQUEST_HEADERS|XML://*|!REQUEST_HEADERS:User-Agent" \ - "\bwget\b" \ - "capture,ctl:auditLogParts=+E,deny,log,auditlog,status:501,msg:'System Command Injection. Matched signature <%{TX.0}>',,id:'950907',severity:'2'" - -# Coldfusion injection -SecRule REQUEST_FILENAME|ARGS|ARGS_NAMES|REQUEST_HEADERS|XML://* "\bcf(?:usion_(?:d(?:bconnections_flush|ecrypt)|set(?:tings_refresh|odbcini)|getodbc(?:dsn|ini)|verifymail|encrypt)|_(?:(?:iscoldfusiondatasourc|getdatasourceusernam)e|setdatasource(?:password|username))|newinternal(?:adminsecurit|registr)y|admin_registry_(?:delete|set)|internaldebug)\b" \ - "capture,ctl:auditLogParts=+E,deny,log,auditlog,status:501,msg:'Injection of Undocumented ColdFusion Tags. Matched signature <%{TX.0}>',,id:'950008',severity:'2'" - -# LDAP injection -SecRule REQUEST_FILENAME|ARGS|ARGS_NAMES|REQUEST_HEADERS|XML://*|!REQUEST_HEADERS:Referer "(?:\((?:\W*?(?:objectc(?:ategory|lass)|homedirectory|[gu]idnumber|cn)\b\W*?=|[^\w\x80-\xFF]*?[\!\&\|][^\w\x80-\xFF]*?\()|\)[^\w\x80-\xFF]*?\([^\w\x80-\xFF]*?[\!\&\|])" \ - "capture,ctl:auditLogParts=+E,deny,log,auditlog,status:501,msg:'LDAP Injection Attack. Matched signature <%{TX.0}>',,id:'950010',severity:'2'" - -# SSI injection -SecRule REQUEST_FILENAME|ARGS|ARGS_NAMES|REQUEST_HEADERS|XML://* "