Adds support to Python scripts on ModSecurity core.

Analog of what we have for Lua, Python support is now added by this commit.
This is very experimental.
This commit is contained in:
Felipe Zimmerle
2014-08-26 07:18:57 -07:00
parent c17bf6ad42
commit 2440a23921
18 changed files with 903 additions and 18 deletions

View File

@@ -20,6 +20,7 @@ mod_security2_la_SOURCES = acmp.c \
msc_multipart.c \
msc_parsers.c \
msc_pcre.c \
msc_python.c \
msc_release.c \
msc_reqbody.c \
msc_tree.c \
@@ -41,6 +42,7 @@ mod_security2_la_CFLAGS = @APR_CFLAGS@ \
@LUA_CFLAGS@ \
@MODSEC_EXTRA_CFLAGS@ \
@PCRE_CFLAGS@ \
@PYTHON_CFLAGS@ \
@YAJL_CFLAGS@
@@ -53,6 +55,7 @@ mod_security2_la_LIBADD = @APR_LDADD@ \
@LIBXML2_LDADD@ \
@LUA_LDADD@ \
@PCRE_LDADD@ \
@PYTHON_LDADD@ \
@YAJL_LDADD@
if AIX
@@ -63,7 +66,9 @@ mod_security2_la_LDFLAGS = -module -avoid-version \
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
endif
if HPUX
@@ -74,6 +79,7 @@ mod_security2_la_LDFLAGS = -module -avoid-version \
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
endif
@@ -85,6 +91,7 @@ mod_security2_la_LDFLAGS = -module -avoid-version \
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
endif
@@ -96,6 +103,7 @@ mod_security2_la_LDFLAGS = -module -avoid-version \
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
endif
@@ -107,6 +115,7 @@ mod_security2_la_LDFLAGS = -no-undefined -module -avoid-version -R @PCRE_LD_PATH
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
endif
@@ -118,6 +127,7 @@ mod_security2_la_LDFLAGS = -no-undefined -module -avoid-version \
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
endif
@@ -129,6 +139,7 @@ mod_security2_la_LDFLAGS = -no-undefined -module -avoid-version \
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
endif
@@ -140,6 +151,7 @@ mod_security2_la_LDFLAGS = -no-undefined -module -avoid-version \
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
endif

View File

@@ -26,6 +26,9 @@
#include "msc_lua.h"
#endif
#ifdef WITH_PYTHON
#include "msc_python.h"
#endif
/* -- Directory context creation and initialisation -- */
@@ -771,11 +774,19 @@ static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, int type,
/* Create the rule now. */
switch(type) {
#if defined(WITH_LUA)
case RULE_TYPE_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
#ifdef WITH_PYTHON
case RULE_TYPE_PYTHON:
rule = msre_rule_python_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);
@@ -790,6 +801,9 @@ static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, int type,
if (
#if defined(WITH_LUA)
type != RULE_TYPE_LUA &&
#endif
#ifdef WITH_PYTHON
type != RULE_TYPE_PYTHON &&
#endif
(dcfg->tmp_chain_starter == NULL))
if(rule->actionset == NULL)
@@ -799,23 +813,32 @@ static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, int type,
if(rule->actionset->id == NOT_SET_P
#if defined(WITH_LUA)
&& (type != RULE_TYPE_LUA)
#endif
#ifdef WITH_PYTHON
&& (type != RULE_TYPE_PYTHON)
#endif
)
return "ModSecurity: No action id present within the rule";
#if defined(WITH_LUA)
if(type != RULE_TYPE_LUA)
#ifdef WITH_LUA
if (type != RULE_TYPE_LUA)
#endif
{
rid = apr_hash_get(dcfg->rule_id_htab, rule->actionset->id, APR_HASH_KEY_STRING);
if(rid != NULL) {
return "ModSecurity: Found another rule with the same id";
} else {
apr_hash_set(dcfg->rule_id_htab, apr_pstrdup(dcfg->mp, rule->actionset->id), APR_HASH_KEY_STRING, apr_pstrdup(dcfg->mp, "1"));
}
#ifdef WITH_PYTHON
if (type != RULE_TYPE_PYTHON)
#endif
{
rid = apr_hash_get(dcfg->rule_id_htab, rule->actionset->id, APR_HASH_KEY_STRING);
if(rid != NULL) {
return "ModSecurity: Found another rule with the same id";
} else {
apr_hash_set(dcfg->rule_id_htab, apr_pstrdup(dcfg->mp, rule->actionset->id), APR_HASH_KEY_STRING, apr_pstrdup(dcfg->mp, "1"));
}
//tmp_rule = msre_ruleset_fetch_rule(dcfg->ruleset, rule->actionset->id, offset);
//if(tmp_rule != NULL)
// return "ModSecurity: Found another rule with the same id";
}
}
}
@@ -2246,13 +2269,30 @@ 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)
{
#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);
if (strlen(filename) > 3) {
const char *p = filename + strlen(filename) - 3;
#ifdef WITH_PYTHON
if ((p[0] == '.')&&(p[1] == 'p')&&(p[2] == 'y'))
{
return add_rule(cmd, (directory_config *)_dcfg, RULE_TYPE_PYTHON, filename, p2, NULL);
}
#endif
#ifdef WITH_LUA
if ((p[0] == 'l')&&(p[1] == 'u')&&(p[2] == 'a'))
{
return add_rule(cmd, (directory_config *)_dcfg, RULE_TYPE_LUA, filename, p2, NULL);
}
#endif
}
#if !defined(WITH_PYTHON) || !defined(WITH_LUA)
ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, "Ignoring SecRuleScript \"%s\" directive (%s:%d): No Lua scripting or Python support.", p1, cmd->directive->filename, cmd->directive->line_num);
#endif
return NULL;
#endif
}
static const char *cmd_rule_remove_by_id(cmd_parms *cmd, void *_dcfg,

View File

@@ -37,6 +37,10 @@
#include "msc_lua.h"
#endif
#ifdef WITH_PYTHON
#include "msc_python.h"
#endif
#include "msc_status_engine.h"
/* ModSecurity structure */

View File

@@ -60,6 +60,10 @@ typedef struct msc_parm msc_parm;
#include "msc_lua.h"
#endif
#ifdef WITH_PYTHON
#include "msc_python.h"
#endif
#define PHASE_REQUEST_HEADERS 1
#define PHASE_REQUEST_BODY 2
#define PHASE_RESPONSE_HEADERS 3

View File

@@ -1,11 +1,13 @@
MOD_SECURITY2 = mod_security2 apache2_config apache2_io apache2_util \
re re_operators re_actions re_tfns re_variables msc_json \
msc_logging msc_xml msc_multipart modsecurity msc_parsers msc_util msc_pcre \
persist_dbm msc_reqbody pdf_protect msc_geo msc_gsb msc_crypt msc_tree msc_unicode acmp msc_lua
persist_dbm msc_reqbody pdf_protect msc_geo msc_gsb msc_crypt msc_tree msc_unicode acmp msc_lua \
msc_python
H = re.h modsecurity.h msc_logging.h msc_multipart.h msc_parsers.h msc_json.h \
msc_pcre.h msc_util.h msc_xml.h persist_dbm.h apache2.h pdf_protect.h \
msc_geo.h msc_gsb.h msc_crypt.h msc_tree.h msc_unicode.h acmp.h utf8tables.h msc_lua.h
msc_geo.h msc_gsb.h msc_crypt.h msc_tree.h msc_unicode.h acmp.h utf8tables.h msc_lua.h \
msc_python.h
${MOD_SECURITY2:=.slo}: ${H}
${MOD_SECURITY2:=.lo}: ${H}

442
apache2/msc_python.c Normal file
View File

@@ -0,0 +1,442 @@
/*
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
* Copyright (c) 2004-2013 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* 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
*
* If any of the files related to licensing are missing or if you have any
* other questions related to licensing please contact Trustwave Holdings, Inc.
* directly using the email address security@modsecurity.org.
*/
#ifdef WITH_PYTHON
#include "msc_python.h"
#include "apr_lib.h"
#include "apr_strmatch.h"
#include "apr_strings.h"
#include "apache2.h"
#include <Python.h>
#define PY_STR_DBG(arg, msr, z) { if (arg == NULL) { msr_log(msr, 8, "Python -%s-:", z); } else { PyObject *e = PyObject_Repr(arg); PyObject *a = PyUnicode_AsEncodedString(e, "utf-8", NULL); char *s = PyBytes_AsString(a); msr_log(msr, 8, "Python -%s-: %s", z, s); } }
/* ModSecurityI */
static PyObject *pyModSecurityI_log(PyObject *self, PyObject *args, PyObject *kwds) {
char *str = NULL;
int level = 0;
PyObject *capsuleModSecurity = NULL;
modsec_rec *msr = NULL;
if (PyArg_ParseTuple(args, "is", &level, &str) == 0)
{
/* PyArg already set this.
* PyErr_SetString(PyExc_TypeError, "log() takes exactly 2 arguments.");
*/
goto end;
}
capsuleModSecurity = PyObject_GetAttrString(self, "capsuleModSecurity");
if (capsuleModSecurity == NULL)
{
// FIXME: Use the correct error paramenter.
PyErr_SetString(PyExc_TypeError, "log() needs ModSecurity core to be attached.");
goto end;
}
msr = PyCapsule_GetPointer(capsuleModSecurity, "modsecurity");
if (msr == NULL)
{
// FIXME: Use the correct error paramenter.
PyErr_SetString(PyExc_TypeError, "log() needs ModSecurity core to be attached.");
goto end_no_msr;
}
msr_log(msr, level, str);
end_no_msr:
Py_DECREF(capsuleModSecurity);
end:
Py_RETURN_NONE;
}
static PyObject *pyModSecurityI_setCapsuleModSecurity(PyObject *self, PyObject *args, PyObject *kwds) {
PyObject *capsule = NULL;
if (PyArg_ParseTuple(args, "O", &capsule) == 0)
{
/* PyArg already set this.
* PyErr_SetString(PyExc_TypeError, "setCapsuleModSecurity() takes exactly 1 argument.");
*/
goto end;
}
if (capsule == NULL)
{
// FIXME: Use the correct error object.
PyErr_SetString(PyExc_TypeError, "setCapsuleModSecurity() Capsule cannot be NULL.");
goto end;
}
if (PyModule_AddObject(self, "capsuleModSecurity", capsule) == -1)
{
// FIXME: Use the correct error object.
PyErr_SetString(PyExc_TypeError, "setCapsuleModSecurity() Failed to save capsule.");
goto end;
}
end:
Py_RETURN_NONE;
}
static PyObject *pyModSecurityI_setCapsuleRule(PyObject *self, PyObject *args, PyObject *kwds) {
PyObject *capsule = NULL;
if (PyArg_ParseTuple(args, "O", &capsule) == 0)
{
/* PyArg already set this.
* PyErr_SetString(PyExc_TypeError, "setCapsuleRule() takes exactly 1 argument.");
*/
goto end;
}
if (capsule == NULL)
{
// FIXME: Use the correct error object.
PyErr_SetString(PyExc_TypeError, "setCapsuleRule() Capsule cannot be NULL.");
goto end;
}
if (PyModule_AddObject(self, "capsuleRule", capsule) == -1)
{
// FIXME: Use the correct error object.
PyErr_SetString(PyExc_TypeError, "setCapsuleRule() Failed to save capsule.");
goto end;
}
end:
Py_RETURN_NONE;
}
static PyObject *pyModSecurityI_getVariable(PyObject *self, PyObject *args, PyObject *kwds) {
char *my_error_msg = NULL;
const char *var_name = NULL;
const apr_array_header_t *arr = NULL;
PyObject *ret = Py_None;
PyObject *capsuleRule = NULL;
PyObject *capsuleModSecurity = NULL;
msre_rule *rule = NULL;
msre_var *var = NULL;
modsec_rec *msr = NULL;
apr_table_t *vartab = NULL;
if (PyArg_ParseTuple(args, "s", &var_name) == 0)
{
goto end;
}
capsuleRule = PyObject_GetAttrString(self, "capsuleRule");
if (capsuleRule == NULL)
{
// FIXME: Use the correct error object.
PyErr_SetString(PyExc_TypeError, "getVariable() needs ModSecurity core to be attached.");
goto end_no_rule;
}
capsuleModSecurity = PyObject_GetAttrString(self, "capsuleModSecurity");
if (capsuleModSecurity == NULL)
{
// FIXME: Use the correct error object.
PyErr_SetString(PyExc_TypeError, "getVariable() needs ModSecurity core to be attached.");
goto end_no_msr;
}
rule = PyCapsule_GetPointer(capsuleRule, "rule");
msr = PyCapsule_GetPointer(capsuleModSecurity, "modsecurity");
if (rule == NULL || msr == NULL)
{
// FIXME: Use the correct error object.
PyErr_SetString(PyExc_TypeError, "getVariable() needs ModSecurity core to be attached.");
goto end_no_rule_or_msr;
}
var = msre_create_var_ex(msr->msc_rule_mptmp, msr->modsecurity->msre,
var_name, '\0', msr, &my_error_msg);
if (var == NULL)
{
goto end_no_var;
}
vartab = apr_table_make(msr->msc_rule_mptmp, 16);
if (vartab == NULL)
{
// FIXME: Use the correct error object.
PyErr_SetString(PyExc_TypeError, "getVariable() Internal error, failed to create var table.");
goto end_no_vartab;
}
var->metadata->generate(msr, var, rule, vartab, msr->msc_rule_mptmp);
arr = apr_table_elts(vartab);
if (arr->nelts == 1)
{
msre_var *vx = generate_single_var(msr, var, NULL, rule, msr->msc_rule_mptmp);
if (vx != NULL)
{
ret = Py_BuildValue("s", vx->value);
}
}
else if (arr->nelts > 1)
{
// FIXME: We should have an object to encapsulate this dictionary, in order to auto-save the changes.
int i = 0;
const apr_table_entry_t *te = NULL;
PyObject *pDict = PyDict_New();
te = (apr_table_entry_t *)arr->elts;
for (i = 0; i < arr->nelts; i++)
{
msre_var *str = (msre_var *) te[i].val;
PyDict_SetItemString(pDict, str->name + strlen(var->name) + 1, Py_BuildValue("s", str->value));
}
ret = pDict;
}
end_no_vartab:
end_no_var:
end_no_rule_or_msr:
Py_DECREF(capsuleModSecurity);
end_no_msr:
Py_DECREF(capsuleRule);
end_no_rule:
end:
return ret;
}
static PyObject *pyModSecurityI_applyTransformation(PyObject *self, PyObject *args, PyObject *kwds)
{
// TODO: Implement.
PyErr_SetString(PyExc_TypeError, "applyTransformation() is not ready yet.");
Py_RETURN_NONE;
}
static PyObject *pyModSecurityI_setVariable(PyObject *self, PyObject *args, PyObject *kwds)
{
char *name = NULL;
char *value = NULL;
PyObject *ret = Py_None;
PyObject *capsuleRule = NULL;
PyObject *capsuleModSecurity = NULL;
msre_rule *rule = NULL;
modsec_rec *msr = NULL;
if (PyArg_ParseTuple(args, "ss", &name, &value) == 0)
{
goto end;
}
capsuleRule = PyObject_GetAttrString(self, "capsuleRule");
if (capsuleRule == NULL)
{
// FIXME: Use the correct error object.
PyErr_SetString(PyExc_TypeError, "setVariable() needs ModSecurity core to be attached.");
goto end_no_rule;
}
capsuleModSecurity = PyObject_GetAttrString(self, "capsuleModSecurity");
if (capsuleModSecurity == NULL)
{
// FIXME: Use the correct error object.
PyErr_SetString(PyExc_TypeError, "setVariable() needs ModSecurity core to be attached.");
goto end_no_msr;
}
rule = PyCapsule_GetPointer(capsuleRule, "rule");
msr = PyCapsule_GetPointer(capsuleModSecurity, "modsecurity");
if (rule == NULL || msr == NULL)
{
// FIXME: Use the correct error object.
PyErr_SetString(PyExc_TypeError, "setVariable() needs ModSecurity core to be attached.");
goto end_no_rule_or_msr;
}
if (msre_action_setvar_execute(msr, msr->msc_rule_mptmp, rule, name, value) != 1)
{
// FIXME: Use the correct error object.
PyErr_SetString(PyExc_TypeError, "setVariable() Internal error. Failed to save variable.");
goto end_failed_to_set;
}
end_failed_to_set:
end_no_rule_or_msr:
Py_DECREF(capsuleModSecurity);
end_no_msr:
Py_DECREF(capsuleRule);
end_no_rule:
end:
return ret;
}
static PyMethodDef pyModSecurityI_functions[] = {
{ "log", (PyCFunction)pyModSecurityI_log, METH_VARARGS, NULL },
{ "setCapsuleModSecurity", (PyCFunction)pyModSecurityI_setCapsuleModSecurity, METH_VARARGS, NULL },
{ "setCapsuleRule", (PyCFunction)pyModSecurityI_setCapsuleRule, METH_VARARGS, NULL },
{ "getVariable", (PyCFunction)pyModSecurityI_getVariable, METH_VARARGS, NULL },
{ "setVariable", (PyCFunction)pyModSecurityI_setVariable, METH_VARARGS, NULL },
{ "applyTransformation", (PyCFunction)pyModSecurityI_applyTransformation, METH_VARARGS, NULL },
{ NULL, NULL }
};
static struct PyModuleDef pyModSecurityI_def = {
PyModuleDef_HEAD_INIT,
"ModSecurityI",
NULL,
0,
pyModSecurityI_functions,
NULL,
NULL,
NULL,
NULL
};
// FIXME: Split files.
/**
*
*/
char *python_load(msc_python_script **script, const char *filename, apr_pool_t *pool)
{
PyObject *pName, *pModule;
const char *path = NULL;
const char *file = NULL;
const char *module = NULL;
/*
* Script path?
* FIXME: Avoid to use apr_ functions
*/
file = apr_filepath_name_get(filename);
path = apr_pstrndup(pool, filename, strlen(filename) - strlen(file));
Py_Initialize();
PyObject* sysPath = PySys_GetObject((char*)"path");
PyList_Append(sysPath, PyUnicode_FromFormat("."));
PyList_Append(sysPath, PyUnicode_FromFormat(path));
module = apr_pstrndup(pool, file, strlen(file) - strlen(".py"));
Py_SetProgramName((wchar_t *)module); /* FIXME */
pName = PyUnicode_FromString(module);
pModule = PyImport_Import(pName);
if (pModule == NULL) {
const char *s = NULL;
PyObject *err = NULL;
PyObject *exc_type = NULL, *exc_value = NULL, *exc_tb = NULL;
PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
err = PyObject_Repr(exc_value); //Now a unicode object
PyObject* pyStr = PyUnicode_AsEncodedString(err, "utf-8", NULL);
s = PyBytes_AS_STRING(pyStr);
return apr_psprintf(pool, "ModSecurity: Failed to load script: %s - %s",
filename, s);
}
(*script) = apr_pcalloc(pool, sizeof(msc_python_script));
(*script)->name = strdup(filename);
(*script)->pName = pName;
(*script)->pModule = pModule;
(*script)->extInstance = NULL;
Py_DECREF(pName);
return NULL;
}
// FIXME: Error handling
int python_execute(msc_python_script *script, char *param, modsec_rec *msr, msre_rule *rule, char **error_msg) {
apr_time_t time_before;
int ret = RULE_NO_MATCH;
PyObject* methodRes = NULL;
PyObject *extObject = NULL;
if (error_msg == NULL) {
return -1;
}
*error_msg = NULL;
// if (script->extInstance == NULL) // FIXME: without cache this will draing the performance.
// {
extObject = PyObject_GetAttrString(script->pModule, "ModSecurityExtension");
script->extInstance = PyObject_CallObject(extObject, NULL);
PyObject *logMod = PyModule_Create(&pyModSecurityI_def);
PyObject *capsule_msr = PyCapsule_New(msr, "modsecurity", NULL);
PyObject *capsule_rule = PyCapsule_New(rule, "rule", NULL);
PyObject* setCapsule1 = PyObject_CallMethod(logMod, "setCapsuleModSecurity", "(O)", capsule_msr);
PyObject* setCapsule2 = PyObject_CallMethod(logMod, "setCapsuleRule", "(O)", capsule_rule);
PyObject* setLog = PyObject_CallMethod(script->extInstance, "setModSecurityCore", "(O)", logMod);
if (setLog == NULL)
{
const char *s = NULL;
PyObject *exc_type = NULL, *exc_value = NULL, *exc_tb = NULL, *err = NULL;
PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
err = PyObject_Repr(exc_value); //Now a unicode object
PyObject* pyStr = PyUnicode_AsEncodedString(err, "utf-8", NULL);
s = PyBytes_AS_STRING(pyStr);
msr_log(msr, 8, "Python problem: %s", s);
}
// }
methodRes = PyObject_CallMethod(script->extInstance, "process", NULL);
if (methodRes == NULL)
{
const char *s = NULL;
PyObject *exc_type = NULL, *exc_value = NULL, *exc_tb = NULL, *err = NULL;
PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
err = PyObject_Repr(exc_value); //Now a unicode object
PyObject* pyStr = PyUnicode_AsEncodedString(err, "utf-8", NULL);
s = PyBytes_AS_STRING(pyStr);
*error_msg = apr_psprintf(msr->mp, "Python script failed: '%s'.", s);
goto end;
}
if (methodRes == Py_True)
{
*error_msg = apr_psprintf(msr->mp, "Python script matched");
ret = RULE_MATCH;
}
Py_DECREF(methodRes);
end:
// Py_DECREF(extInstance);
// Py_DECREF(extObject);
return ret;
}
#endif /* WITH_PYTHON */

38
apache2/msc_python.h Normal file
View File

@@ -0,0 +1,38 @@
/*
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
* Copyright (c) 2004-2013 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* 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
*
* If any of the files related to licensing are missing or if you have any
* other questions related to licensing please contact Trustwave Holdings, Inc.
* directly using the email address security@modsecurity.org.
*/
#ifdef WITH_PYTHON
#ifndef _MSC_PYTHON_H_
#define _MSC_PYTHON_H_
typedef struct msc_python_script msc_python_script;
#include <Python.h>
#include "apr_general.h"
#include "apr_tables.h"
#include "modsecurity.h"
struct msc_python_script {
const char *name;
PyObject *pName;
PyObject *pModule;
PyObject *extInstance;
};
char DSOLOCAL *python_load(msc_python_script **script, const char *filename, apr_pool_t *pool);
#endif /* _MSC_PYTHON_H_ */
#endif /* WITH_PYTHON */

View File

@@ -20,6 +20,10 @@
#include "msc_lua.h"
#endif
#ifdef WITH_PYTHON
#include "msc_python.h"
#endif
static const char *const severities[] = {
"EMERGENCY",
"ALERT",
@@ -2327,6 +2331,19 @@ char * msre_rule_generate_unparsed(apr_pool_t *pool, const msre_rule *rule, con
}
break;
#endif
#ifdef WITH_PYTHON
case RULE_TYPE_PYTHON:
/* 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;
@@ -2466,6 +2483,54 @@ msre_rule *msre_rule_lua_create(msre_ruleset *ruleset,
}
#endif
#ifdef WITH_PYTHON
/**
* Allocate and initialize the main structure needed for Python
* script execution.
*
*/
msre_rule *msre_rule_python_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;
char *filename = (char *)rule->op_param;
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_PYTHON;
rule->ruleset = ruleset;
rule->filename = apr_pstrdup(ruleset->mp, fn);
rule->line_num = line;
/* Load the script */
*error_msg = python_load(&rule->python_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, ruleset->mp, 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.
*/
@@ -3331,6 +3396,41 @@ static apr_status_t msre_rule_process_lua(msre_rule *rule, modsec_rec *msr) {
}
#endif
#ifdef WITH_PYTHON
static apr_status_t msre_rule_process_python(msre_rule *rule, modsec_rec *msr) {
msre_actionset *acting_actionset = NULL;
char *my_error_msg = NULL;
int rc = 0;
/* Choose the correct metadata/disruptive action actionset. */
acting_actionset = rule->actionset;
if (rule->chain_starter != NULL) {
acting_actionset = rule->chain_starter->actionset;
}
rc = python_execute(rule->python_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
/**
*
*/
@@ -3350,6 +3450,13 @@ static apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) {
}
#endif
#ifdef WITH_PYTHON
if (rule->type == RULE_TYPE_PYTHON) {
return msre_rule_process_python(rule, msr);
}
#endif
return msre_rule_process_normal(rule, msr);
}

View File

@@ -46,6 +46,10 @@ typedef struct msre_cache_rec msre_cache_rec;
#include "msc_lua.h"
#endif
#ifdef WITH_PYTHON
#include "msc_python.h"
#endif
/* Actions, variables, functions and operator functions */
char DSOLOCAL *update_rule_target_ex(modsec_rec *msr, msre_ruleset *ruleset, msre_rule *rule, const char *p2,
const char *p3);
@@ -136,7 +140,10 @@ int DSOLOCAL msre_ruleset_phase_rule_remove_with_exception(msre_ruleset *ruleset
#define RULE_TYPE_ACTION 1 /* SecAction */
#define RULE_TYPE_MARKER 2 /* SecMarker */
#if defined(WITH_LUA)
#define RULE_TYPE_LUA 3 /* SecRuleScript */
#define RULE_TYPE_LUA 3 /* SecRuleScript - lua */
#endif
#ifdef WITH_PYTHON
#define RULE_TYPE_PYTHON 4 /* SecRuleScript - python */
#endif
struct msre_rule {
@@ -167,6 +174,10 @@ struct msre_rule {
msc_script *script;
#endif
#ifdef WITH_PYTHON
msc_python_script *python_script;
#endif
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 0
ap_regex_t *sub_regex;
#else
@@ -192,6 +203,12 @@ msre_rule DSOLOCAL *msre_rule_lua_create(msre_ruleset *ruleset,
const char *actions, char **error_msg);
#endif
#ifdef WITH_PYTHON
msre_rule DSOLOCAL *msre_rule_python_create(msre_ruleset *ruleset,
const char *fn, int line, const char *script_filename,
const char *actions, char **error_msg);
#endif
#define VAR_SIMPLE 0 /* REQUEST_URI */
#define VAR_LIST 1

View File

@@ -3733,6 +3733,23 @@ static int msre_op_inspectFile_init(msre_rule *rule, char **error_msg) {
filename = resolve_relative_path(rule->ruleset->mp, rule->filename, filename);
#ifdef WITH_PYTHON
/* ENH Write & use string_ends(s, e). */
if (strlen(rule->op_param) > 3) {
char *p = filename + strlen(filename) - 3;
if ((p[0] == '.')&&(p[1] == 'p')&&(p[2] == 'y'))
{
msc_python_script *script = NULL;
/* Compile script. */
*error_msg = python_load(&script, filename, rule->ruleset->mp);
if (*error_msg != NULL) return -1;
rule->op_param_data = script;
}
}
#endif
#if defined(WITH_LUA)
/* ENH Write & use string_ends(s, e). */
if (strlen(rule->op_param) > 4) {
@@ -3748,7 +3765,7 @@ static int msre_op_inspectFile_init(msre_rule *rule, char **error_msg) {
rule->op_param_data = script;
}
}
#endif
#endif
if (rule->op_param_data == NULL) {
/* ENH Verify the script exists and that we have

56
scripts/python/modsecurity.py Executable file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python
from __future__ import print_function
import sys
# Needs to be singlenton ?
class Singleton(object):
_instances = {}
"""
def __new__(class_, *args, **kwargs):
if class_ not in class_._instances:
class_._instances[class_] = super(Singleton, class_).__new__(class_, *args, **kwargs)
return class_._instances[class_]
"""
class ModSecurity():
def __init__(self):
self.default_attr = ["default_attr", "name", "modsecCore"]
self.name = None
self.modsecCore = None
def setModSecurityCore(self, cb):
self.modsecCore = cb
self.log(8, "Log attached");
return True
def log(self, level, msg):
if self.modsecCore == None:
print("ModSecurity Python: ", str(level) + " " + str(msg), file=sys.stderr)
else:
self.modsecCore.log(level, msg)
return True
def __getattribute__(self, key):
v = None
try:
v = object.__getattribute__(self, key)
if hasattr(v, '__get__'):
return v.__get__(None, self)
except:
if self.modsecCore != None:
v = self.modsecCore.getVariable(key)
return v
def __setattr__(self, name, value):
self.__dict__[name] = value
if name not in self.default_attr:
if self.modsecCore != None:
self.modsecCore.setVariable("tx." + name, value)
"""
TODO: transformation
"""

15
scripts/python/setup.py Normal file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python
from distutils.core import setup
setup(name="ModSecurity Python extension",
version="0.1",
description="ModSecurity python externsion",
author="Felipe Zimmerle",
author_email="felipe@zimmerle.org",
url="http://www.modsecurity.org",
py_modules=['modsecurity'],
keywords="ModSecurity WAF Security",
)

50
scripts/python/skell.py Executable file
View File

@@ -0,0 +1,50 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ModSecurity core binding.
from modsecurity import ModSecurity
class ModSecurityExtension(ModSecurity):
"""
Class ModSecurityExtension should represents your custom module.
Nocite that this class should derivate from ModSecurity and should
implement the method process.
"""
def __init__(self):
ModSecurity.__init__(self)
def process(self):
"""
The method is called by ModSecurity core whenever a request is
needed to be evaluated.
"""
# self.log can be utilised to produce content inside the SecDebugLog
# (https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual#secdebuglog)
self.log(8, "This is our custom Python script, it seems that I am working"
"like a charm.")
self.log(8, "Hum... Do we have something at FILES_TMPNAMES? %s" %
self.FILES_TMPNAMES)
# Returns True whenever you want to send a "match" to ModSecurity core.
return True
# Should be used to test your custom extension, deattached from ModSecurity core.
if __name__ == "__main__":
myExtension = ModSecurityExtension()
# Setting FILES_TMPNAMES property.
# https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual#files_tmpnames
myExtension.FILES_TMPNAMES = [ "/etc/issue", "/etc/resolv.conf" ]
# Process the content.
ret = myExtension.process()
if ret == True:
print("Matched!")
else:
print("_not_ matched")

View File

@@ -21,6 +21,7 @@ standalone_la_SOURCES = ../apache2/acmp.c \
../apache2/msc_multipart.c \
../apache2/msc_parsers.c \
../apache2/msc_pcre.c \
../apache2/msc_python.c \
../apache2/msc_release.c \
../apache2/msc_reqbody.c \
../apache2/msc_tree.c \
@@ -51,6 +52,7 @@ standalone_la_CFLAGS = -DVERSION_NGINX \
@LUA_CFLAGS@ \
@MODSEC_EXTRA_CFLAGS@ \
@PCRE_CFLAGS@ \
@PYTHON_CFLAGS@ \
@YAJL_CFLAGS@
standalone_la_CPPFLAGS = @APR_CPPFLAGS@ \
@@ -62,6 +64,7 @@ standalone_la_LIBADD = @APR_LDADD@ \
@LIBXML2_LDADD@ \
@LUA_LDADD@ \
@PCRE_LDADD@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDADD@
if AIX
@@ -72,6 +75,7 @@ standalone_la_LDFLAGS = -module -avoid-version \
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
endif
@@ -83,6 +87,7 @@ standalone_la_LDFLAGS = -module -avoid-version \
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
endif
@@ -94,6 +99,7 @@ standalone_la_LDFLAGS = -module -avoid-version \
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
endif
@@ -105,6 +111,7 @@ standalone_la_LDFLAGS = -module -avoid-version \
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
endif
@@ -116,6 +123,7 @@ standalone_la_LDFLAGS = -no-undefined -module -avoid-version \
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
endif
@@ -127,6 +135,7 @@ standalone_la_LDFLAGS = -no-undefined -module -avoid-version \
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
endif
@@ -138,6 +147,7 @@ standalone_la_LDFLAGS = -no-undefined -module -avoid-version \
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
endif
@@ -149,5 +159,6 @@ standalone_la_LDFLAGS = -no-undefined -module -avoid-version \
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
endif

View File

@@ -14,6 +14,7 @@ msc_test_SOURCES = msc_test.c \
$(top_srcdir)/apache2/msc_multipart.c \
$(top_srcdir)/apache2/msc_parsers.c \
$(top_srcdir)/apache2/msc_pcre.c \
$(top_srcdir)/apache2/msc_python.c \
$(top_srcdir)/apache2/msc_release.c \
$(top_srcdir)/apache2/msc_reqbody.c \
$(top_srcdir)/apache2/msc_tree.c \
@@ -36,6 +37,7 @@ msc_test_CFLAGS = @APR_CFLAGS@ \
@LUA_CFLAGS@ \
@MODSEC_EXTRA_CFLAGS@ \
@PCRE_CFLAGS@ \
@PYTHON_CFLAGS@ \
@YAJL_CFLAGS@
msc_test_CPPFLAGS = -I$(top_srcdir)/apache2 \
@@ -48,6 +50,7 @@ msc_test_LDADD = @APR_LDADD@ \
@LIBXML2_LDADD@ \
@LUA_LDADD@ \
@PCRE_LDADD@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDADD@
msc_test_LDFLAGS = @APR_LDFLAGS@ \
@@ -56,6 +59,7 @@ msc_test_LDFLAGS = @APR_LDFLAGS@ \
@LIBXML2_LDFLAGS@ \
@LUA_LDFLAGS@ \
@PCRE_LDFLAGS@ \
@PYTHON_LDFLAGS@ \
@YAJL_LDFLAGS@
check_SCRIPTS = run-unit-tests.pl

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from modsecurity import ModSecurity
class ModSecurityExtension(ModSecurity):
def process(self):
self.log(8, "Python test message.")
return False
if __name__ == "__main__":
myExtension = ModSecurityExtension()
# Process the content.
ret = myExtension.process()
if ret == True:
print("Matched!")
else:
print("_not_ matched")

View File

@@ -0,0 +1,24 @@
### Test for SecRuleScript
# Python
{
type => "rule",
comment => "SecRuleScript (Python match)",
conf => qq(
SecRuleEngine On
SecDebugLog $ENV{DEBUG_LOG}
SecDebugLogLevel 9
SecRuleScript "script.py" "phase:2,deny"
),
match_log => {
-error => [ qr/Python script matched\./, 1 ],
debug => [ qr/Python test message\./, 1 ],
},
match_response => {
status => qr/^200$/,
},
request => new HTTP::Request(
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?foo=bar&foo2=bar2",
),
}

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from modsecurity import ModSecurity
class ModSecurityExtension(ModSecurity):
def process(self):
self.log(8, "Python test message.")
return False
if __name__ == "__main__":
myExtension = ModSecurityExtension()
# Process the content.
ret = myExtension.process()
if ret == True:
print("Matched!")
else:
print("_not_ matched")