Merge branch 'experimental_python_support' of https://github.com/SpiderLabs/ModSecurity into experimental_python_support

This commit is contained in:
root 2014-10-24 17:54:34 -04:00
commit 11756d0dcf
20 changed files with 1053 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"

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

149
build/find_python.m4 Normal file
View File

@ -0,0 +1,149 @@
dnl Check for PYTHON Libraries
dnl Sets:
AC_DEFUN([CHECK_PYTHON],
[dnl
AC_REQUIRE([PKG_PROG_PKG_CONFIG])
PYTHON_CONFIG=""
PYTHON_VERSION=""
PYTHON_CFLAGS=""
PYTHON_CPPFLAGS=""
PYTHON_LDADD=""
PYTHON_LDFLAGS=""
PYTHON_CONFIG=${PKG_CONFIG}
PYTHON_PKGNAMES="python3"
PYTHON_SONAMES="so la sl dll dylib"
AC_ARG_WITH(
python,
[AC_HELP_STRING([--with-python=PATH],[Path to Python prefix or config script])]
,, with_python=yes)
AS_CASE(["${with_pythonl}"],
[no], [test_paths=],
[yes], [test_paths="/usr/local/libpython /usr/local/python /usr/local /opt/libpython /opt/python /opt /usr"],
[test_paths="${with_python}"])
AS_IF([test "x${test_paths}" != "x"], [
AC_MSG_CHECKING([for Python config script])
for x in ${test_paths}; do
dnl # Determine if the script was specified and use it directly
if test ! -d "$x" -a -e "$x"; then
PYTHON_CONFIG=$x
break
fi
dnl # Try known config script names/locations
for y in $PYTHON_CONFIG; do
if test -e "${x}/bin/${y}"; then
PYTHON_CONFIG="${x}/bin/${y}"
python_config="${PYTHON_CONFIG}"
break
elif test -e "${x}/${y}"; then
PYTHON_CONFIG="${x}/${y}"
python_config="${PYTHON_CONFIG}"
break
fi
done
if test -n "${python_config}"; then
break
fi
done
dnl # Try known package names
if test -n "${PYTHON_CONFIG}"; then
PYTHON_PKGNAME=""
for x in ${PYTHON_PKGNAMES}; do
if ${PYTHON_CONFIG} --exists ${x}; then
PYTHON_PKGNAME="$x"
break
fi
done
fi
if test -n "${PYTHON_PKGNAME}"; then
AC_MSG_RESULT([${PYTHON_CONFIG}])
PYTHON_VERSION="`${PYTHON_CONFIG} ${PYTHON_PKGNAME} --modversion`"
if test "$verbose_output" -eq 1; then AC_MSG_NOTICE(python VERSION: $PYTHON_VERSION); fi
PYTHON_CFLAGS="`${PYTHON_CONFIG} ${PYTHON_PKGNAME} --cflags`"
if test "$verbose_output" -eq 1; then AC_MSG_NOTICE(python CFLAGS: $PYTHON_CFLAGS); fi
PYTHON_LDADD="`${PYTHON_CONFIG} ${PYTHON_PKGNAME} --libs-only-l`"
if test "$verbose_output" -eq 1; then AC_MSG_NOTICE(python LDADD: $PYTHON_LDADD); fi
PYTHON_LDFLAGS="`${PYTHON_CONFIG} ${PYTHON_PKGNAME} --libs-only-L --libs-only-other`"
if test "$verbose_output" -eq 1; then AC_MSG_NOTICE(python LDFLAGS: $PYTHON_LDFLAGS); fi
else
AC_MSG_RESULT([no])
dnl Hack to just try to find the lib and include
AC_MSG_CHECKING([for python install])
for x in ${test_paths}; do
for y in ${PYTHON_SONAMES}; do
if test -e "${x}/libpython.${y}"; then
python_lib_path="${x}/"
python_lib_name="python"
break
else
python_lib_path=""
python_lib_name=""
fi
done
if test -n "$python_lib_path"; then
break
fi
done
for x in ${test_paths}; do
if test -e "${x}/include/Python.h"; then
python_inc_path="${x}/include"
break
elif test -e "${x}/Python.h"; then
python_inc_path="${x}"
break
fi
dnl # Check some sub-paths as well
for python_pkg_name in ${python_lib_name} ${PYTHON_PKGNAMES}; do
if test -e "${x}/include/${python_pkg_name}/Python.h"; then
python_inc_path="${x}/include"
break
elif test -e "${x}/${python_pkg_name}/Python.h"; then
python_inc_path="${x}"
break
else
python_inc_path=""
fi
done
if test -n "$python_inc_path"; then
break
fi
done
if test -n "${python_lib_path}" -a -n "${python_inc_path}"; then
PYTHON_CONFIG=""
AC_MSG_RESULT([${python_lib_path} ${python_inc_path}])
PYTHON_VERSION="2"
PYTHON_CFLAGS="-I${python_inc_path}"
PYTHON_LDADD="-l${python_lib_name}"
PYTHON_LDFLAGS="-L${python_lib_path}"
else
PYTHON_VERSION=""
AC_MSG_RESULT([no])
fi
fi
])
PYTHON_LIBS=${PYTHON_LDADD}
AC_SUBST(PYTHON_CFLAGS)
AC_SUBST(PYTHON_LDADD)
AC_SUBST(PYTHON_LIBS)
AC_SUBST(PYTHON_LDFLAGS)
if test -z "${PYTHON_VERSION}"; then
ifelse([$2], , AC_MSG_NOTICE([optional python library not found]), $2)
else
AC_MSG_NOTICE([using python v${PYTHON_VERSION}])
PYTHON_CFLAGS="-DWITH_PYTHON ${PYTHON_CFLAGS}"
ifelse([$1], , , $1)
fi
])

View File

@ -701,6 +701,7 @@ fi
# Check for YAJL libs (for JSON body processor)
CHECK_YAJL()
#AC_SEARCH_LIBS([yajl_alloc], [yajl])
CHECK_PYTHON()
AC_CONFIG_FILES([Makefile])
AC_CONFIG_FILES([tools/Makefile])

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