From f64c7c39e888f48ec377dba5250f1533463c2a6d Mon Sep 17 00:00:00 2001 From: ivanr Date: Thu, 20 Dec 2007 15:53:23 +0000 Subject: [PATCH] Lua: Added support for scripting to @inspectFile. --- apache2/apache2_config.c | 12 +-- apache2/msc_lua.c | 9 +- apache2/msc_lua.h | 2 +- apache2/msc_util.c | 27 ++++++ apache2/msc_util.h | 4 + apache2/re.c | 2 +- apache2/re_actions.c | 2 +- apache2/re_operators.c | 113 ++++++++++++++++++------ doc/modsecurity2-apache-reference.xml | 122 +++++++++++++++++++------- 9 files changed, 214 insertions(+), 79 deletions(-) diff --git a/apache2/apache2_config.c b/apache2/apache2_config.c index 8d22baa5..bbdeb369 100644 --- a/apache2/apache2_config.c +++ b/apache2/apache2_config.c @@ -9,7 +9,6 @@ * */ #include -#include #include "modsecurity.h" #include "msc_logging.h" @@ -18,15 +17,6 @@ #include "msc_lua.h" -static const char *configuration_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 filename; - - return apr_pstrcat(pool, apr_pstrndup(pool, parent_filename, - strlen(parent_filename) - strlen(apr_filepath_name_get(parent_filename))), - filename, NULL); -} /* -- Directory context creation and initialisation -- */ @@ -1297,7 +1287,7 @@ static const char *cmd_rule_inheritance(cmd_parms *cmd, void *_dcfg, int flag) { static const char *cmd_rule_script(cmd_parms *cmd, void *_dcfg, const char *p1, const char *p2) { - const char *filename = configuration_relative_path(cmd->pool, cmd->directive->filename, p1); + 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); } #endif diff --git a/apache2/msc_lua.c b/apache2/msc_lua.c index b59b5bd9..67fcb992 100644 --- a/apache2/msc_lua.c +++ b/apache2/msc_lua.c @@ -244,7 +244,7 @@ static const struct luaL_Reg mylib[] = { /** * */ -int lua_execute(msc_script *script, modsec_rec *msr, msre_rule *rule, char **error_msg) { +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; @@ -288,7 +288,12 @@ int lua_execute(msc_script *script, modsec_rec *msr, msre_rule *rule, char **err /* Execute main() */ lua_getglobal(L, "main"); - if (lua_pcall(L, 0, 1, 0)) { + /* 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; } diff --git a/apache2/msc_lua.h b/apache2/msc_lua.h index 2fac7674..b0d28891 100644 --- a/apache2/msc_lua.h +++ b/apache2/msc_lua.h @@ -36,7 +36,7 @@ struct msc_script_part { char DSOLOCAL *lua_compile(msc_script **script, const char *filename, apr_pool_t *pool); -int DSOLOCAL lua_execute(msc_script *script, modsec_rec *msr, msre_rule *rule, char **error_msg); +int DSOLOCAL lua_execute(msc_script *script, char *param, modsec_rec *msr, msre_rule *rule, char **error_msg); #endif #endif diff --git a/apache2/msc_util.c b/apache2/msc_util.c index a9fcc97a..843ac136 100644 --- a/apache2/msc_util.c +++ b/apache2/msc_util.c @@ -16,6 +16,8 @@ #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) */ @@ -1176,3 +1178,28 @@ char *modsec_build(apr_pool_t *mp) { build_type, atoi(MODSEC_VERSION_RELEASE)); } + +int is_empty_string(const char *string) { + unsigned int i; + + if (string == NULL) return 1; + if (strlen(string) == 0) return 1; + + for(i = 0; i < strlen(string); 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 8ed5398b..ae531590 100644 --- a/apache2/msc_util.h +++ b/apache2/msc_util.h @@ -78,4 +78,8 @@ 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/re.c b/apache2/re.c index 9d121c39..4418d6f3 100644 --- a/apache2/re.c +++ b/apache2/re.c @@ -1966,7 +1966,7 @@ static apr_status_t msre_rule_process_lua(msre_rule *rule, modsec_rec *msr) { acting_actionset = rule->chain_starter->actionset; } - rc = lua_execute(rule->script, msr, rule, &my_error_msg); + rc = lua_execute(rule->script, NULL, msr, rule, &my_error_msg); if (rc < 0) { msr_log(msr, 1, "%s", my_error_msg); return -1; diff --git a/apache2/re_actions.c b/apache2/re_actions.c index ccba553a..29d08779 100644 --- a/apache2/re_actions.c +++ b/apache2/re_actions.c @@ -1484,7 +1484,7 @@ static apr_status_t msre_action_exec_execute(modsec_rec *msr, apr_pool_t *mptmp, msc_script *script = (msc_script *)action->param_data; char *my_error_msg = NULL; - if (lua_execute(script, msr, rule, &my_error_msg) < 0) { + if (lua_execute(script, NULL, msr, rule, &my_error_msg) < 0) { msr_log(msr, 1, "%s", my_error_msg); return 0; } diff --git a/apache2/re_operators.c b/apache2/re_operators.c index e9e30c0f..fd500d38 100644 --- a/apache2/re_operators.c +++ b/apache2/re_operators.c @@ -1237,44 +1237,99 @@ static int msre_op_rbl_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, } /* 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); + + #ifdef WITH_LUA + // TODO 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) { + // TODO 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; +} - /* No match. */ - return 0; +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. */ + } + + /* No match. */ + return 0; + } 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; + } } /* validateByteRange */ @@ -1867,7 +1922,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/doc/modsecurity2-apache-reference.xml b/doc/modsecurity2-apache-reference.xml index e4e3c5c9..893b05ce 100644 --- a/doc/modsecurity2-apache-reference.xml +++ b/doc/modsecurity2-apache-reference.xml @@ -339,9 +339,8 @@ - Add one line to your configuration to load libxml2: - LoadFile - /usr/lib/libxml2.so + Add one line to your configuration to load libxml2: LoadFile /usr/lib/libxml2.so @@ -420,13 +419,13 @@ "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> @@ -690,7 +689,7 @@ SecAuditLogStorageDir logs/audit K - This part contains a full list of every rule that matched (one per line) in the order - they were matched. + they were matched. Supported as of v2.5.0 @@ -1798,8 +1797,13 @@ ServerAlias www.app2.com optional parameter is the list of actions whose meaning is identical to that of SecRule. - Syntax: SecRuleScript /path/to/script.lua - [ACTIONS] + Syntax: SecRuleScript + /path/to/script.lua [ACTIONS] + + The first parameter, path to the script, can be absolute or + relative to the configuration file in which + SecRuleScript resides. This allows you to place your + script in the same folder as the configuration files using them. Example script: @@ -1819,8 +1823,10 @@ function main() var2 = m.getvar("REQUEST_URI", "normalisePath"); -- Retrieve one variable, applying several transformation functions. - -- Notice how the second parameter is now a list. - var3 = m.getvar("ARGS:p", { "lowercase", "compressWhitespace" } ); + -- 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. + var3 = m.getvar("ARGS.p", { "lowercase", "compressWhitespace" } ); -- If you want this rule to match return a string -- containing the error message. It is a good idea to mention @@ -1830,6 +1836,13 @@ function main() -- Otherwise, simply return null. return null; 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/. +
@@ -2412,7 +2425,7 @@ SecRule ENV:tag "suspicious" 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"
@@ -3913,27 +3926,42 @@ SecRule IP:AUTH_ATTEMPT "@gt 25" \ <literal>exec</literal> Description: Executes an external - script/binary supplied as parameter. + script/binary supplied as parameter. As of v2.5, when the support for + Lua scripting is enabled, and 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 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, provided ModSecurity was +# compiled with Lua support enabled. +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 multithreaded + operation. The script you execute must write something (anything) to + stdout. If it doesn't ModSecurity will assume execution didn't + work. +
@@ -4847,11 +4875,37 @@ SecRule ARGS:route "!@endsWith %{REQUEST_ADDR}" t:none,deny Description: Executes the external script/binary given as parameter to the operator against every file - extracted from the request. + extracted from the request. As of v2.5, 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 if the filename is + determined to be a Lua script (based on its extension) and the Lua + support is compiled in, 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
@@ -5269,4 +5323,4 @@ SecRule REQUEST_METHOD "!@within %{tx.allowed_methods}" t:l
- + \ No newline at end of file