Lua: Added support for scripting to @inspectFile.

This commit is contained in:
ivanr 2007-12-20 15:53:23 +00:00
parent 4cecdf4c5b
commit f64c7c39e8
9 changed files with 214 additions and 79 deletions

View File

@ -9,7 +9,6 @@
* *
*/ */
#include <limits.h> #include <limits.h>
#include <apr_lib.h>
#include "modsecurity.h" #include "modsecurity.h"
#include "msc_logging.h" #include "msc_logging.h"
@ -18,15 +17,6 @@
#include "msc_lua.h" #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 -- */ /* -- 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, static const char *cmd_rule_script(cmd_parms *cmd, void *_dcfg, const char *p1,
const char *p2) 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); return add_rule(cmd, (directory_config *)_dcfg, RULE_TYPE_LUA, filename, p2, NULL);
} }
#endif #endif

View File

@ -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; apr_time_t time_before;
lua_State *L = NULL; lua_State *L = NULL;
int rc; int rc;
@ -288,7 +288,12 @@ int lua_execute(msc_script *script, modsec_rec *msr, msre_rule *rule, char **err
/* Execute main() */ /* Execute main() */
lua_getglobal(L, "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)); *error_msg = apr_psprintf(msr->mp, "Lua: Script execution failed: %s", lua_tostring(L, -1));
return -1; return -1;
} }

View File

@ -36,7 +36,7 @@ struct msc_script_part {
char DSOLOCAL *lua_compile(msc_script **script, const char *filename, apr_pool_t *pool); 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
#endif #endif

View File

@ -16,6 +16,8 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <apr_lib.h>
/* NOTE: Be careful as these can ONLY be used on static values for X. /* NOTE: Be careful as these can ONLY be used on static values for X.
* (i.e. VALID_HEX(c++) will NOT work) * (i.e. VALID_HEX(c++) will NOT work)
*/ */
@ -1176,3 +1178,28 @@ char *modsec_build(apr_pool_t *mp) {
build_type, build_type,
atoi(MODSEC_VERSION_RELEASE)); 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);
}

View File

@ -78,4 +78,8 @@ int DSOLOCAL ansi_c_sequences_decode_inplace(unsigned char *input, int len);
char DSOLOCAL *modsec_build(apr_pool_t *mp); 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 #endif

View File

@ -1966,7 +1966,7 @@ static apr_status_t msre_rule_process_lua(msre_rule *rule, modsec_rec *msr) {
acting_actionset = rule->chain_starter->actionset; 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) { if (rc < 0) {
msr_log(msr, 1, "%s", my_error_msg); msr_log(msr, 1, "%s", my_error_msg);
return -1; return -1;

View File

@ -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; msc_script *script = (msc_script *)action->param_data;
char *my_error_msg = NULL; 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); msr_log(msr, 1, "%s", my_error_msg);
return 0; return 0;
} }

View File

@ -1237,44 +1237,99 @@ static int msre_op_rbl_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
} }
/* inspectFile */ /* inspectFile */
static int msre_op_inspectFile_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
char **error_msg) static int msre_op_inspectFile_init(msre_rule *rule, char **error_msg) {
{ char *filename = (char *)rule->op_param;
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);
if (error_msg == NULL) return -1; if (error_msg == NULL) return -1;
*error_msg = NULL; *error_msg = NULL;
msr_log(msr, 4, "Executing %s to inspect %s.", approver_script, target_file); if ((filename == NULL)||(is_empty_string(filename))) {
*error_msg = apr_psprintf(rule->ruleset->mp, "Operator @inspectFile requires parameter.");
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; return -1;
} }
if (script_output == NULL) { filename = resolve_relative_path(rule->ruleset->mp, rule->filename, filename);
*error_msg = apr_psprintf(msr->mp, "Execution of the approver script \"%s\" failed (no output).",
log_escape(msr->mp, approver_script)); #ifdef WITH_LUA
return -1; // 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') { return 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. */ static int msre_op_inspectFile_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
return 0; 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 */ /* validateByteRange */
@ -1867,7 +1922,7 @@ void msre_engine_register_default_operators(msre_engine *engine) {
/* inspectFile */ /* inspectFile */
msre_engine_op_register(engine, msre_engine_op_register(engine,
"inspectFile", "inspectFile",
NULL, msre_op_inspectFile_init,
msre_op_inspectFile_execute msre_op_inspectFile_execute
); );

View File

@ -339,9 +339,8 @@
</listitem> </listitem>
<listitem> <listitem>
<para>Add one line to your configuration to load libxml2: <para>Add one line to your configuration to load libxml2: <filename
<filename moreinfo="none">LoadFile moreinfo="none">LoadFile /usr/lib/libxml2.so</filename></para>
/usr/lib/libxml2.so</filename></para>
</listitem> </listitem>
<listitem> <listitem>
@ -420,13 +419,13 @@
"allow" rules or to correct any false positives in the Core rules as they "allow" rules or to correct any false positives in the Core rules as they
are applied to your site.</para> are applied to your site.</para>
<para><emphasis>Note</emphasis></para> <note>
<para>It is highly encouraged that you do not edit the Core rules files
<para>It is highly encouraged that you do not edit the Core rules files themselves but rather place all changes (such as
themselves but rather place all changes (such as <literal>SecRuleRemoveByID</literal>, etc...) in your custom rules file.
<literal>SecRuleRemoveByID</literal>, etc...) in your custom rules file. This will allow for easier upgrading as newer Core rules are released by
This will allow for easier upgrading as newer Core rules are released by Breach Security on the ModSecurity website.</para>
Breach Security on the ModSecurity website.</para> </note>
<section> <section>
<title><literal>SecAction</literal></title> <title><literal>SecAction</literal></title>
@ -690,7 +689,7 @@ SecAuditLogStorageDir logs/audit
<listitem> <listitem>
<para><literal moreinfo="none">K</literal> - This part contains a <para><literal moreinfo="none">K</literal> - This part contains a
full list of every rule that matched (one per line) in the order full list of every rule that matched (one per line) in the order
they were matched.</para> they were matched. Supported as of v2.5.0</para>
</listitem> </listitem>
<listitem> <listitem>
@ -1798,8 +1797,13 @@ ServerAlias www.app2.com
optional parameter is the list of actions whose meaning is identical to optional parameter is the list of actions whose meaning is identical to
that of <literal>SecRule</literal>.</para> that of <literal>SecRule</literal>.</para>
<para><emphasis>Syntax:</emphasis> SecRuleScript /path/to/script.lua <para><emphasis>Syntax:</emphasis> <literal>SecRuleScript
[ACTIONS]</para> /path/to/script.lua [ACTIONS]</literal></para>
<para>The first parameter, path to the script, can be absolute or
relative to the configuration file in which
<literal>SecRuleScript</literal> resides. This allows you to place your
script in the same folder as the configuration files using them.</para>
<para>Example script:</para> <para>Example script:</para>
@ -1819,8 +1823,10 @@ function main()
var2 = m.getvar("REQUEST_URI", "normalisePath"); var2 = m.getvar("REQUEST_URI", "normalisePath");
-- Retrieve one variable, applying several transformation functions. -- Retrieve one variable, applying several transformation functions.
-- Notice how the second parameter is now a list. -- The second parameter is now a list. You should note that m.getvar()
var3 = m.getvar("ARGS:p", { "lowercase", "compressWhitespace" } ); -- 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 -- If you want this rule to match return a string
-- containing the error message. It is a good idea to mention -- containing the error message. It is a good idea to mention
@ -1830,6 +1836,13 @@ function main()
-- Otherwise, simply return null. -- Otherwise, simply return null.
return null; return null;
end</programlisting> end</programlisting>
<note>
<para>Go to <ulink url="http://www.lua.org">http://www.lua.org</ulink>
to find more about the Lua programming language. The reference manual
too is available online, at <ulink
url="http://www.lua.org/manual/5.1/">http://www.lua.org/manual/5.1/</ulink>.</para>
</note>
</section> </section>
<section> <section>
@ -2412,7 +2425,7 @@ SecRule <emphasis>ENV:tag</emphasis> "suspicious"</programlisting>
<para>Example:</para> <para>Example:</para>
<programlisting format="linespecific">SecRule REMOTE_ADDR "<emphasis>@geoLookup</emphasis>" chain,drop,msg:'Non-UK IP address' <programlisting format="linespecific">SecRule REMOTE_ADDR "<emphasis>@geoLookup</emphasis>" "chain,drop,msg:'Non-UK IP address'"
SecRule GEO:COUNTRY_CODE "!@streq UK"</programlisting> SecRule GEO:COUNTRY_CODE "!@streq UK"</programlisting>
</section> </section>
@ -3913,27 +3926,42 @@ SecRule IP:AUTH_ATTEMPT "@gt 25" \
<title><literal>exec</literal></title> <title><literal>exec</literal></title>
<para><emphasis>Description:</emphasis> Executes an external <para><emphasis>Description:</emphasis> Executes an external
script/binary supplied as parameter.</para> script/binary supplied as parameter. As of v2.5, when the support for
Lua scripting is enabled, and the parameter supplied to
<literal>exec</literal> is a Lua script (detected by the
<filename>.lua</filename> extension) the script will be processed
<emphasis>internally</emphasis>. This means you will get direct access
to the internal request context from the script. Please read the
<literal>SecRuleScript</literal> documentation for more details on how
to write Lua scripts.</para>
<para><emphasis>Action Group:</emphasis> Non-Disruptive</para> <para><emphasis>Action Group:</emphasis> Non-Disruptive</para>
<para>Example:</para> <para>Example:</para>
<programlisting format="linespecific">SecRule REQUEST_URI "^/cgi-bin/script\.pl" \ <programlisting format="linespecific"># The following is going to execute /usr/local/apache/bin/test.sh
"log,<emphasis>exec:/usr/local/apache/bin/test.sh</emphasis>,phase:1"</programlisting> # as a shell script on rule match.
SecRule REQUEST_URI "^/cgi-bin/script\.pl" \
"log,<emphasis>exec:/usr/local/apache/bin/test.sh</emphasis>"
<para><emphasis>Note</emphasis></para> # 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,<emphasis>exec:/usr/local/apache/conf/exec.lua</emphasis></programlisting>
<para>This directive does not effect a primary action if it exists. This <note>
action will always call script with no parameters, but providing all <para>This directive does not effect a primary action if it exists.
information in the environment. All the usual CGI environment variables This action will always call script with no parameters, but providing
will be there. You can have one binary executed per filter match. all information in the environment. All the usual CGI environment
Execution will add the header mod_security-executed to the list of variables will be there. You can have one binary executed per filter
request headers. You should be aware that forking a threaded process match. Execution will add the header mod_security-executed to the list
results in all threads being replicated in the new process. Forking can of request headers. You should be aware that forking a threaded
therefore incur larger overhead in multithreaded operation. The script process results in all threads being replicated in the new process.
you execute must write something (anything) to stdout. If it doesn't Forking can therefore incur larger overhead in multithreaded
ModSecurity will assume execution didn't work.</para> operation. The script you execute must write something (anything) to
stdout. If it doesn't ModSecurity will assume execution didn't
work.</para>
</note>
</section> </section>
<section> <section>
@ -4847,11 +4875,37 @@ SecRule ARGS:route "!<emphasis>@endsWith %{REQUEST_ADDR}</emphasis>" t:none,deny
<para><emphasis>Description:</emphasis> Executes the external <para><emphasis>Description:</emphasis> Executes the external
script/binary given as parameter to the operator against every file script/binary given as parameter to the operator against every file
extracted from the request.</para> 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.</para>
<para>Example:</para> <para>Example of using an external binary/script:</para>
<programlisting format="linespecific">SecRule FILES_TMPNAMES "<emphasis>@inspectFile</emphasis> /opt/apache/bin/inspect_script.pl"</programlisting> <programlisting format="linespecific"># Execute external script to validate uploaded files.
SecRule FILES_TMPNAMES "<emphasis>@inspectFile</emphasis> /opt/apache/bin/inspect_script.pl"</programlisting>
<para>Example of using Lua script:</para>
<programlisting>SecRule FILES_TMPNANMES "@inspectFile <emphasis>inspect.lua</emphasis>"</programlisting>
<para>Script <filename>inspect.lua</filename>:</para>
<programlisting>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</programlisting>
</section> </section>
<section> <section>
@ -5269,4 +5323,4 @@ SecRule REQUEST_METHOD "!<emphasis>@within %{tx.allowed_methods}</emphasis>" t:l
</section> </section>
</section> </section>
</section> </section>
</article> </article>