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 <apr_lib.h>
#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

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;
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;
}

View File

@ -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

View File

@ -16,6 +16,8 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <apr_lib.h>
/* 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);
}

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);
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

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;
}
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;

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;
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;
}

View File

@ -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
);

View File

@ -339,9 +339,8 @@
</listitem>
<listitem>
<para>Add one line to your configuration to load libxml2:
<filename moreinfo="none">LoadFile
/usr/lib/libxml2.so</filename></para>
<para>Add one line to your configuration to load libxml2: <filename
moreinfo="none">LoadFile /usr/lib/libxml2.so</filename></para>
</listitem>
<listitem>
@ -420,13 +419,13 @@
"allow" rules or to correct any false positives in the Core rules as they
are applied to your site.</para>
<para><emphasis>Note</emphasis></para>
<para>It is highly encouraged that you do not edit the Core rules files
themselves but rather place all changes (such as
<literal>SecRuleRemoveByID</literal>, 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.</para>
<note>
<para>It is highly encouraged that you do not edit the Core rules files
themselves but rather place all changes (such as
<literal>SecRuleRemoveByID</literal>, 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.</para>
</note>
<section>
<title><literal>SecAction</literal></title>
@ -690,7 +689,7 @@ SecAuditLogStorageDir logs/audit
<listitem>
<para><literal moreinfo="none">K</literal> - This part contains a
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>
@ -1798,8 +1797,13 @@ ServerAlias www.app2.com
optional parameter is the list of actions whose meaning is identical to
that of <literal>SecRule</literal>.</para>
<para><emphasis>Syntax:</emphasis> SecRuleScript /path/to/script.lua
[ACTIONS]</para>
<para><emphasis>Syntax:</emphasis> <literal>SecRuleScript
/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>
@ -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</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>
@ -2412,7 +2425,7 @@ SecRule <emphasis>ENV:tag</emphasis> "suspicious"</programlisting>
<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>
</section>
@ -3913,27 +3926,42 @@ SecRule IP:AUTH_ATTEMPT "@gt 25" \
<title><literal>exec</literal></title>
<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>Example:</para>
<programlisting format="linespecific">SecRule REQUEST_URI "^/cgi-bin/script\.pl" \
"log,<emphasis>exec:/usr/local/apache/bin/test.sh</emphasis>,phase:1"</programlisting>
<programlisting format="linespecific"># 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,<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
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.</para>
<note>
<para>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.</para>
</note>
</section>
<section>
@ -4847,11 +4875,37 @@ SecRule ARGS:route "!<emphasis>@endsWith %{REQUEST_ADDR}</emphasis>" t:none,deny
<para><emphasis>Description:</emphasis> Executes the external
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>
@ -5269,4 +5323,4 @@ SecRule REQUEST_METHOD "!<emphasis>@within %{tx.allowed_methods}</emphasis>" t:l
</section>
</section>
</section>
</article>
</article>