From c17bf6ad4296761eb8d2489c69254497abba75ba Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Mon, 4 Aug 2014 12:38:23 -0700 Subject: [PATCH 1/2] Adds python build script. Not the final version just to test --- build/find_python.m4 | 149 +++++++++++++++++++++++++++++++++++++++++++ configure.ac | 1 + 2 files changed, 150 insertions(+) create mode 100644 build/find_python.m4 diff --git a/build/find_python.m4 b/build/find_python.m4 new file mode 100644 index 00000000..5683ff12 --- /dev/null +++ b/build/find_python.m4 @@ -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 +]) diff --git a/configure.ac b/configure.ac index 2d7ac1c3..8222b1f1 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) From 2440a23921fbab38210abe1744c1514904401346 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Tue, 26 Aug 2014 07:18:57 -0700 Subject: [PATCH 2/2] 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. --- apache2/Makefile.am | 12 + apache2/apache2_config.c | 68 ++- apache2/mod_security2.c | 4 + apache2/modsecurity.h | 4 + apache2/modules.mk | 6 +- apache2/msc_python.c | 442 ++++++++++++++++++++ apache2/msc_python.h | 38 ++ apache2/re.c | 107 +++++ apache2/re.h | 19 +- apache2/re_operators.c | 19 +- scripts/python/modsecurity.py | 56 +++ scripts/python/setup.py | 15 + scripts/python/skell.py | 50 +++ standalone/Makefile.am | 11 + tests/Makefile.am | 4 + tests/regression/nginx/conf/script.py | 21 + tests/regression/rule/01-python-script.t | 24 ++ tests/regression/server_root/conf/script.py | 21 + 18 files changed, 903 insertions(+), 18 deletions(-) create mode 100644 apache2/msc_python.c create mode 100644 apache2/msc_python.h create mode 100755 scripts/python/modsecurity.py create mode 100644 scripts/python/setup.py create mode 100755 scripts/python/skell.py create mode 100755 tests/regression/nginx/conf/script.py create mode 100644 tests/regression/rule/01-python-script.t create mode 100755 tests/regression/server_root/conf/script.py diff --git a/apache2/Makefile.am b/apache2/Makefile.am index 77a0884a..897a7034 100644 --- a/apache2/Makefile.am +++ b/apache2/Makefile.am @@ -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 diff --git a/apache2/apache2_config.c b/apache2/apache2_config.c index 1fa8669b..f94df90e 100644 --- a/apache2/apache2_config.c +++ b/apache2/apache2_config.c @@ -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, diff --git a/apache2/mod_security2.c b/apache2/mod_security2.c index c0240ef5..da0c99e1 100644 --- a/apache2/mod_security2.c +++ b/apache2/mod_security2.c @@ -37,6 +37,10 @@ #include "msc_lua.h" #endif +#ifdef WITH_PYTHON +#include "msc_python.h" +#endif + #include "msc_status_engine.h" /* ModSecurity structure */ diff --git a/apache2/modsecurity.h b/apache2/modsecurity.h index 3b1badf4..42528daa 100644 --- a/apache2/modsecurity.h +++ b/apache2/modsecurity.h @@ -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 diff --git a/apache2/modules.mk b/apache2/modules.mk index d4de0371..c97cb4ef 100644 --- a/apache2/modules.mk +++ b/apache2/modules.mk @@ -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} diff --git a/apache2/msc_python.c b/apache2/msc_python.c new file mode 100644 index 00000000..8f4b3937 --- /dev/null +++ b/apache2/msc_python.c @@ -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 + +#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 */ diff --git a/apache2/msc_python.h b/apache2/msc_python.h new file mode 100644 index 00000000..df0759cb --- /dev/null +++ b/apache2/msc_python.h @@ -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 + +#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 */ diff --git a/apache2/re.c b/apache2/re.c index 1d843e21..f8ee2973 100644 --- a/apache2/re.c +++ b/apache2/re.c @@ -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); } diff --git a/apache2/re.h b/apache2/re.h index df659a4b..fdcabb2c 100644 --- a/apache2/re.h +++ b/apache2/re.h @@ -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 diff --git a/apache2/re_operators.c b/apache2/re_operators.c index aa7dd849..473b4296 100644 --- a/apache2/re_operators.c +++ b/apache2/re_operators.c @@ -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 diff --git a/scripts/python/modsecurity.py b/scripts/python/modsecurity.py new file mode 100755 index 00000000..53fc3528 --- /dev/null +++ b/scripts/python/modsecurity.py @@ -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 +""" diff --git a/scripts/python/setup.py b/scripts/python/setup.py new file mode 100644 index 00000000..2550732a --- /dev/null +++ b/scripts/python/setup.py @@ -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", + + ) + diff --git a/scripts/python/skell.py b/scripts/python/skell.py new file mode 100755 index 00000000..a1d3d70d --- /dev/null +++ b/scripts/python/skell.py @@ -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") + diff --git a/standalone/Makefile.am b/standalone/Makefile.am index c19b2f94..6074d4c0 100644 --- a/standalone/Makefile.am +++ b/standalone/Makefile.am @@ -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 diff --git a/tests/Makefile.am b/tests/Makefile.am index e7a75dd2..2b032b20 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -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 diff --git a/tests/regression/nginx/conf/script.py b/tests/regression/nginx/conf/script.py new file mode 100755 index 00000000..2ad23bd1 --- /dev/null +++ b/tests/regression/nginx/conf/script.py @@ -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") + diff --git a/tests/regression/rule/01-python-script.t b/tests/regression/rule/01-python-script.t new file mode 100644 index 00000000..78502fd7 --- /dev/null +++ b/tests/regression/rule/01-python-script.t @@ -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", + ), +} + diff --git a/tests/regression/server_root/conf/script.py b/tests/regression/server_root/conf/script.py new file mode 100755 index 00000000..2ad23bd1 --- /dev/null +++ b/tests/regression/server_root/conf/script.py @@ -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") +