From b9080aad183af13b282ed29b566c9afc9bf72d5f Mon Sep 17 00:00:00 2001 From: Mihai Pitu Date: Fri, 5 Jul 2013 14:22:20 +0100 Subject: [PATCH] Java test WebApp --- java/ModSecurityTestApp/build.xml | 71 ++++ java/ModSecurityTestApp/src/conf/MANIFEST.MF | 2 + .../src/java/org/modsecurity/ModSecurity.java | 126 +++++++ .../org/modsecurity/ModSecurityFilter.java | 58 +++ .../web/META-INF/context.xml | 2 + java/ModSecurityTestApp/web/Post.jsp | 12 + .../web/WEB-INF/modsecurity.conf | 214 ++++++++++++ java/ModSecurityTestApp/web/WEB-INF/web.xml | 22 ++ java/ModSecurityTestApp/web/index.jsp | 14 + java/org_modsecurity_ModSecurity.cpp | 329 +++++++++++------- java/org_modsecurity_ModSecurity.h | 14 + 11 files changed, 731 insertions(+), 133 deletions(-) create mode 100644 java/ModSecurityTestApp/build.xml create mode 100644 java/ModSecurityTestApp/src/conf/MANIFEST.MF create mode 100644 java/ModSecurityTestApp/src/java/org/modsecurity/ModSecurity.java create mode 100644 java/ModSecurityTestApp/src/java/org/modsecurity/ModSecurityFilter.java create mode 100644 java/ModSecurityTestApp/web/META-INF/context.xml create mode 100644 java/ModSecurityTestApp/web/Post.jsp create mode 100644 java/ModSecurityTestApp/web/WEB-INF/modsecurity.conf create mode 100644 java/ModSecurityTestApp/web/WEB-INF/web.xml create mode 100644 java/ModSecurityTestApp/web/index.jsp diff --git a/java/ModSecurityTestApp/build.xml b/java/ModSecurityTestApp/build.xml new file mode 100644 index 00000000..ee9dcc7c --- /dev/null +++ b/java/ModSecurityTestApp/build.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + Builds, tests, and runs the project ModSecurityTestApp. + + + diff --git a/java/ModSecurityTestApp/src/conf/MANIFEST.MF b/java/ModSecurityTestApp/src/conf/MANIFEST.MF new file mode 100644 index 00000000..59499bce --- /dev/null +++ b/java/ModSecurityTestApp/src/conf/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/java/ModSecurityTestApp/src/java/org/modsecurity/ModSecurity.java b/java/ModSecurityTestApp/src/java/org/modsecurity/ModSecurity.java new file mode 100644 index 00000000..4bc78a48 --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/modsecurity/ModSecurity.java @@ -0,0 +1,126 @@ +package org.modsecurity; + +import java.io.File; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public final class ModSecurity { + + public static final int DONE = -2; + public static final int DECLINED = -1; + public static final int OK = 0; + + //From build/classes: >"c:\Program Files\Java\jdk1.7.0_05\bin\javah.exe" -classpath c:\work\apache-tomcat-7.0.39\lib\servlet-api.jar;. org.modsecurity.ModSecurity + + private FilterConfig filterConfig; + private String confFilename; + private long confTime; + private final static String pathToLib = "c:\\work\\mod_security\\java\\Debug\\"; + static { + try { + //TODO: bad practice, native libraries should be loaded in server's classloader + System.load("c:\\work\\mod_security\\java\\libs\\zlib1.dll"); + System.load("c:\\work\\mod_security\\java\\libs\\libxml2.dll"); + System.load("c:\\work\\mod_security\\java\\libs\\pcre.dll"); + System.load("c:\\work\\mod_security\\java\\libs\\libapr-1.dll"); + System.load("c:\\work\\mod_security\\java\\libs\\libapriconv-1.dll"); + System.load("c:\\work\\mod_security\\java\\libs\\libaprutil-1.dll"); + System.load("c:\\work\\mod_security\\java\\Debug\\ModSecurityJNI.dll"); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + //java.lang.reflect.Field loadedLibraries = ClassLoader.class.getDeclaredField("loadedLibraryNames"); + //loadedLibraries.setAccessible(true); + //final Vector libraries = (Vector) loadedLibraries.get(ClassLoader.getSystemClassLoader()); + } + + public ModSecurity(FilterConfig fc, String confFile) throws ServletException { + this.filterConfig = fc; + this.confFilename = confFile; + confTime = new File(confFilename).lastModified(); + + this.initialize(); + filterConfig.getServletContext().log("ModSecurity started."); + } + + private native int initialize(); + + public native int destroy(); + + public native int onRequest(String config, ServletRequest request, HttpServletRequest httprequest, String requestID, boolean reloadConfig); + + public native int onResponse(ServletResponse response, HttpServletResponse htttpResponse, String requestID); + + public static String[][] getHttpRequestHeaders(HttpServletRequest req) { + ArrayList aList = Collections.list(req.getHeaderNames()); + String[][] result = new String[aList.size()][2]; + + for (int i = 0; i < aList.size(); i++) { + result[i][0] = aList.get(i); + result[i][1] = req.getHeader(aList.get(i)); + } + + return result; + } + + public static String[][] getHttpResponseHeaders(HttpServletResponse resp) { + + Collection headerNames = resp.getHeaderNames(); + String[][] result = new String[headerNames.size()][2]; + + int i = 0; + for (String headerName : headerNames) { + result[i][0] = headerName; + result[i][1] = resp.getHeader(headerName); + i++; + } + + return result; + } + + public static boolean isIPv6(String addr) { + try { + InetAddress inetAddress = InetAddress.getByName(addr); + + if (inetAddress instanceof Inet6Address) { + return true; + } else { + return false; + } + } catch (UnknownHostException ex) { + return false; + } + } + + public void log(int level, String msg) { + //if (level == 1) { + filterConfig.getServletContext().log(msg); + //} + } + + public boolean checkModifiedConfig() { + if (new File(confFilename).lastModified() > confTime) { + return true; + } else { + return false; + } + } + + public String getAppPath() { + return filterConfig.getServletContext().getContextPath(); + } + + public String getConfFilename() { + return this.confFilename; + } +} diff --git a/java/ModSecurityTestApp/src/java/org/modsecurity/ModSecurityFilter.java b/java/ModSecurityTestApp/src/java/org/modsecurity/ModSecurityFilter.java new file mode 100644 index 00000000..d48b24ab --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/modsecurity/ModSecurityFilter.java @@ -0,0 +1,58 @@ +package org.modsecurity; + +import java.io.IOException; +import java.util.UUID; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + * Docs: http://docs.oracle.com/javaee/6/tutorial/doc/bnagb.html + */ +public class ModSecurityFilter implements Filter { + + ModSecurity modsecurity; + + @Override + public void init(FilterConfig fc) throws ServletException { + String confFilename = fc.getInitParameter("conf"); + if (confFilename == null) { + throw new ServletException("ModSecurity: parameter 'conf' not available in web.xml"); + } else { + confFilename = fc.getServletContext().getRealPath(confFilename); + } + + modsecurity = new ModSecurity(fc, confFilename); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain fc) throws IOException, ServletException { + HttpServletRequest httpReq = (HttpServletRequest) request; + HttpServletResponse httpResp = (HttpServletResponse) response; + + String requestID = UUID.randomUUID().toString(); + + int status = modsecurity.onRequest(modsecurity.getConfFilename(), request, httpReq, requestID, modsecurity.checkModifiedConfig()); + + if (status != ModSecurity.DECLINED) { + return; + } + + fc.doFilter(request, response); + + status = modsecurity.onResponse(response, httpResp, requestID); + } + + @Override + public void destroy() { + if (modsecurity != null) { + modsecurity.destroy(); + } + } +} diff --git a/java/ModSecurityTestApp/web/META-INF/context.xml b/java/ModSecurityTestApp/web/META-INF/context.xml new file mode 100644 index 00000000..0448ceb2 --- /dev/null +++ b/java/ModSecurityTestApp/web/META-INF/context.xml @@ -0,0 +1,2 @@ + + diff --git a/java/ModSecurityTestApp/web/Post.jsp b/java/ModSecurityTestApp/web/Post.jsp new file mode 100644 index 00000000..a9176f2a --- /dev/null +++ b/java/ModSecurityTestApp/web/Post.jsp @@ -0,0 +1,12 @@ +<%@page contentType="text/html" pageEncoding="UTF-8"%> + + + + + + JSP Page + + +

<%= request.getParameter( "data" ) %>

+ + diff --git a/java/ModSecurityTestApp/web/WEB-INF/modsecurity.conf b/java/ModSecurityTestApp/web/WEB-INF/modsecurity.conf new file mode 100644 index 00000000..5b77e4e5 --- /dev/null +++ b/java/ModSecurityTestApp/web/WEB-INF/modsecurity.conf @@ -0,0 +1,214 @@ +# based on modsecurity.conf-recommended +# -- Rule engine initialization ---------------------------------------------- + +# Enable ModSecurity, attaching it to every transaction. Use detection +# only to start with, because that minimises the chances of post-installation +# disruption. +# +SecRuleEngine On + + +# -- Request body handling --------------------------------------------------- + +# Allow ModSecurity to access request bodies. If you don't, ModSecurity +# won't be able to see any POST parameters, which opens a large security +# hole for attackers to exploit. +# +SecRequestBodyAccess On + + +# Enable XML request body parser. +# Initiate XML Processor in case of xml content-type +# +SecRule REQUEST_HEADERS:Content-Type "text/xml" \ + "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + + +# Maximum request body size we will accept for buffering. If you support +# file uploads then the value given on the first line has to be as large +# as the largest file you are willing to accept. The second value refers +# to the size of data, with files excluded. You want to keep that value as +# low as practical. +# +SecRequestBodyLimit 13107200 +SecRequestBodyNoFilesLimit 131072 + +# Store up to 128 KB of request body data in memory. When the multipart +# parser reachers this limit, it will start using your hard disk for +# storage. That is slow, but unavoidable. +# +SecRequestBodyInMemoryLimit 131072 + +# What do do if the request body size is above our configured limit. +# Keep in mind that this setting will automatically be set to ProcessPartial +# when SecRuleEngine is set to DetectionOnly mode in order to minimize +# disruptions when initially deploying ModSecurity. +# +SecRequestBodyLimitAction Reject + +# Verify that we've correctly processed the request body. +# As a rule of thumb, when failing to process a request body +# you should reject the request (when deployed in blocking mode) +# or log a high-severity alert (when deployed in detection-only mode). +# +SecRule REQBODY_ERROR "!@eq 0" \ +"id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + +# By default be strict with what we accept in the multipart/form-data +# request body. If the rule below proves to be too strict for your +# environment consider changing it to detection-only. You are encouraged +# _not_ to remove it altogether. +# +SecRule MULTIPART_STRICT_ERROR "!@eq 0" \ +"id:'200002',phase:2,t:none,log,deny,status:44, \ +msg:'Multipart request body failed strict validation: \ +PE %{REQBODY_PROCESSOR_ERROR}, \ +BQ %{MULTIPART_BOUNDARY_QUOTED}, \ +BW %{MULTIPART_BOUNDARY_WHITESPACE}, \ +DB %{MULTIPART_DATA_BEFORE}, \ +DA %{MULTIPART_DATA_AFTER}, \ +HF %{MULTIPART_HEADER_FOLDING}, \ +LF %{MULTIPART_LF_LINE}, \ +SM %{MULTIPART_MISSING_SEMICOLON}, \ +IQ %{MULTIPART_INVALID_QUOTING}, \ +IP %{MULTIPART_INVALID_PART}, \ +IH %{MULTIPART_INVALID_HEADER_FOLDING}, \ +FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'" + +# Did we see anything that might be a boundary? +# +SecRule MULTIPART_UNMATCHED_BOUNDARY "!@eq 0" \ +"id:'200003',phase:2,t:none,log,deny,status:44,msg:'Multipart parser detected a possible unmatched boundary.'" + +# PCRE Tuning +# We want to avoid a potential RegEx DoS condition +# +SecPcreMatchLimit 1000 +SecPcreMatchLimitRecursion 1000 + +# Some internal errors will set flags in TX and we will need to look for these. +# All of these are prefixed with "MSC_". The following flags currently exist: +# +# MSC_PCRE_LIMITS_EXCEEDED: PCRE match limits were exceeded. +# +SecRule TX:/^MSC_/ "!@streq 0" \ + "id:'200004',phase:2,t:none,deny,msg:'ModSecurity internal error flagged: %{MATCHED_VAR_NAME}'" + + +# -- Response body handling -------------------------------------------------- + +# Allow ModSecurity to access response bodies. +# You should have this directive enabled in order to identify errors +# and data leakage issues. +# +# Do keep in mind that enabling this directive does increases both +# memory consumption and response latency. +# +#SecResponseBodyAccess On + +# Which response MIME types do you want to inspect? You should adjust the +# configuration below to catch documents but avoid static files +# (e.g., images and archives). +# +SecResponseBodyMimeType text/plain text/html text/xml + +# Buffer response bodies of up to 512 KB in length. +SecResponseBodyLimit 524288 + +# What happens when we encounter a response body larger than the configured +# limit? By default, we process what we have and let the rest through. +# That's somewhat less secure, but does not break any legitimate pages. +# +SecResponseBodyLimitAction ProcessPartial + + +# -- Filesystem configuration ------------------------------------------------ + +# The location where ModSecurity stores temporary files (for example, when +# it needs to handle a file upload that is larger than the configured limit). +# +# This default setting is chosen due to all systems have /tmp available however, +# this is less than ideal. It is recommended that you specify a location that's private. +# +SecTmpDir c:\inetpub\temp\ + +# The location where ModSecurity will keep its persistent data. This default setting +# is chosen due to all systems have /tmp available however, it +# too should be updated to a place that other users can't access. +# +SecDataDir c:\inetpub\temp\ + + +# -- File uploads handling configuration ------------------------------------- + +# The location where ModSecurity stores intercepted uploaded files. This +# location must be private to ModSecurity. You don't want other users on +# the server to access the files, do you? +# +#SecUploadDir /opt/modsecurity/var/upload/ + +# By default, only keep the files that were determined to be unusual +# in some way (by an external inspection script). For this to work you +# will also need at least one file inspection rule. +# +#SecUploadKeepFiles RelevantOnly + +# Uploaded files are by default created with permissions that do not allow +# any other user to access them. You may need to relax that if you want to +# interface ModSecurity to an external program (e.g., an anti-virus). +# +#SecUploadFileMode 0600 + + +# -- Debug log configuration ------------------------------------------------- + +# The default debug log configuration is to duplicate the error, warning +# and notice messages from the error log. +# +#SecDebugLog /opt/modsecurity/var/log/debug.log +#SecDebugLogLevel 3 + + +# -- Audit log configuration ------------------------------------------------- + +# Log the transactions that are marked by a rule, as well as those that +# trigger a server error (determined by a 5xx or 4xx, excluding 404, +# level response status codes). +# +#SecAuditEngine RelevantOnly +#SecAuditLogRelevantStatus "^(?:5|4(?!04))" + +# Log everything we know about a transaction. +#SecAuditLogParts ABIJDEFHZ + +# Use a single file for logging. This is much easier to look at, but +# assumes that you will use the audit log only ocassionally. +# +#SecAuditLogType Serial +#SecAuditLog c:\inetpub\log\modsec_audit.log + +# Specify the path for concurrent audit logging. +#SecAuditLogStorageDir c:\inetpub\log\ + + +# -- Miscellaneous ----------------------------------------------------------- + +# Use the most commonly used application/x-www-form-urlencoded parameter +# separator. There's probably only one application somewhere that uses +# something else so don't expect to change this value. +# +SecArgumentSeparator & + +# Settle on version 0 (zero) cookies, as that is what most applications +# use. Using an incorrect cookie version may open your installation to +# evasion attacks (against the rules that examine named cookies). +# +SecCookieFormat 0 + +# Specify your Unicode Code Point. +# This mapping is used by the t:urlDecodeUni transformation function +# to properly map encoded data to your language. Properly setting +# these directives helps to reduce false positives and negatives. +# +#SecUnicodeCodePage 20127 +#SecUnicodeMapFile unicode.mapping diff --git a/java/ModSecurityTestApp/web/WEB-INF/web.xml b/java/ModSecurityTestApp/web/WEB-INF/web.xml new file mode 100644 index 00000000..620ad23c --- /dev/null +++ b/java/ModSecurityTestApp/web/WEB-INF/web.xml @@ -0,0 +1,22 @@ + + + + + + ModSecurity for Java + + + ModSecurityFilter + org.modsecurity.ModSecurityFilter + + conf + /WEB-INF/modsecurity.conf + + + + + ModSecurityFilter + /* + + + \ No newline at end of file diff --git a/java/ModSecurityTestApp/web/index.jsp b/java/ModSecurityTestApp/web/index.jsp new file mode 100644 index 00000000..aff0d0c2 --- /dev/null +++ b/java/ModSecurityTestApp/web/index.jsp @@ -0,0 +1,14 @@ +<%@page contentType="text/html" pageEncoding="UTF-8"%> + + + + + JSP Page + + +
+ Post Action: + +
+ + diff --git a/java/org_modsecurity_ModSecurity.cpp b/java/org_modsecurity_ModSecurity.cpp index 31e3a9ee..187b09b5 100644 --- a/java/org_modsecurity_ModSecurity.cpp +++ b/java/org_modsecurity_ModSecurity.cpp @@ -17,10 +17,10 @@ #define MODSECURITY_LOG_MET "log" #define MODSECURITY_LOG_SIG "(ILjava/lang/String;)V" -#define MODSECURITY__HTTPHEADERS_MET "getHttpHeaders" -#define MODSECURITY__HTTPHEADERS_SIG "(Ljavax/servlet/http/HttpServletRequest;)[[Ljava/lang/String;" -//#define MODSECURITY__REQUESTBODY_MET "getRequestBody" -//#define MODSECURITY__REQUESTBODY_SIG "(Ljavax/servlet/http/HttpServletRequest;)Ljava/lang/String;" +#define MODSECURITY__HTTPREQHEADERS_MET "getHttpRequestHeaders" +#define MODSECURITY__HTTPREQHEADERS_SIG "(Ljavax/servlet/http/HttpServletRequest;)[[Ljava/lang/String;" +#define MODSECURITY__HTTPRESHEADERS_MET "getHttpResponseHeaders" +#define MODSECURITY__HTTPRESHEADERS_SIG "(Ljavax/servlet/http/HttpServletResponse;)[[Ljava/lang/String;" #define MODSECURITY__ISPV6_MET "isIPv6" #define MODSECURITY__ISPV6_SIG "(Ljava/lang/String;)Z" @@ -39,11 +39,14 @@ #define HTTPSERVLETREQUEST_METHOD_MET "getMethod" #define HTTPSERVLETREQUEST_PROTOCOL_MET "getProtocol" - #define HTTPSERVLETREQUEST_REQUESTURL_MET "getRequestURL" #define HTTPSERVLETREQUEST_REQUESTURL_SIG "()Ljava/lang/StringBuffer;" +#define SERVLETRESPONSE_CONTENTTYPE_MET "getContentType" +#define SERVLETRESPONSE_CHARENCODING_MET "getCharacterEncoding" + + //typedef struct { JavaVM *jvm; @@ -52,11 +55,16 @@ directory_config *config; //} JavaModSecurityContext; jmethodID logMethod; -#define JAVASERVLET_CONTEXT "JavaServletContext" + +apr_table_t *requests; +apr_pool_t *requestsPool; + + +#define JAVASERVLET_INSTREAM "RequestInStream" void storeJavaServletContext(request_rec *r, jobject obj) { - apr_table_setn(r->notes, JAVASERVLET_CONTEXT, (const char *)obj); + apr_table_setn(r->notes, JAVASERVLET_INSTREAM, (const char *)obj); } jobject getJavaServletContext(request_rec *r) @@ -65,14 +73,14 @@ jobject getJavaServletContext(request_rec *r) request_rec *rx = NULL; /* Look in the current request first. */ - obj = (jobject)apr_table_get(r->notes, JAVASERVLET_CONTEXT); + obj = (jobject)apr_table_get(r->notes, JAVASERVLET_INSTREAM); if (obj != NULL) return obj; /* If this is a subrequest then look in the main request. */ if (r->main != NULL) { - obj = (jobject)apr_table_get(r->main->notes, JAVASERVLET_CONTEXT); + obj = (jobject)apr_table_get(r->main->notes, JAVASERVLET_INSTREAM); if (obj != NULL) { return obj; @@ -83,7 +91,7 @@ jobject getJavaServletContext(request_rec *r) rx = r->prev; while(rx != NULL) { - obj = (jobject)apr_table_get(rx->notes, JAVASERVLET_CONTEXT); + obj = (jobject)apr_table_get(rx->notes, JAVASERVLET_INSTREAM); if (obj != NULL) { return obj; @@ -94,11 +102,11 @@ jobject getJavaServletContext(request_rec *r) return NULL; } -apr_status_t memCleanup(void *mem) -{ - free(mem); - return APR_SUCCESS; -} +//apr_status_t memCleanup(void *mem) +//{ +// free(mem); +// return APR_SUCCESS; +//} apr_sockaddr_t *CopySockAddr(jclass msClass, JNIEnv *env, apr_pool_t *pool, char *addrstr, jstring addrStrJstr) { @@ -142,24 +150,30 @@ apr_sockaddr_t *CopySockAddr(jclass msClass, JNIEnv *env, apr_pool_t *pool, char return addr; } - -char* fromJStringMethod(JNIEnv *env, jmethodID method, jobject obj, request_rec *r) +inline char* fromJString(JNIEnv *env, jstring jStr, apr_pool_t *pool) { char *str; - jstring jStr = (jstring) (env)->CallObjectMethod(obj, method); if (jStr != NULL) { - str = (char*) (env)->GetStringUTFChars(jStr, 0); - (env)->ReleaseStringUTFChars(jStr, str); + const char *jCStr = (env)->GetStringUTFChars(jStr, NULL); + int len = strlen(jCStr); + str = (char*) apr_palloc(pool, len + 1); + memcpy(str, jCStr, len); + str[len] = '\0'; //null terminate + (env)->ReleaseStringUTFChars(jStr, jCStr); //release java heap memory } else str = ""; - apr_pool_cleanup_register(r->pool, str, memCleanup, apr_pool_cleanup_null); - return str; } +inline char* fromJStringMethod(JNIEnv *env, jmethodID method, jobject obj, apr_pool_t *pool) +{ + jstring jStr = (jstring) (env)->CallObjectMethod(obj, method); + + return fromJString(env, jStr, pool); +} void logSec(void *obj, int level, char *str) @@ -167,7 +181,7 @@ void logSec(void *obj, int level, char *str) JNIEnv *env; jstring jStr; - if (!(jvm)->AttachCurrentThread((void **)&env, NULL)) + if (!(jvm)->AttachCurrentThread((void **)&env, NULL)) //get the Enviroment from the JavaVM { jStr = (env)->NewStringUTF(str); @@ -182,7 +196,7 @@ void logSec(void *obj, int level, char *str) apr_status_t ReadBodyCallback(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos) { - jobject inputStream = getJavaServletContext(r); + jobject inputStream = getJavaServletContext(r); //servlet request input stream JNIEnv *env; *readcnt = 0; @@ -195,12 +209,14 @@ apr_status_t ReadBodyCallback(request_rec *r, char *buf, unsigned int length, un if (!(jvm)->AttachCurrentThread((void **)&env, NULL)) { - jclass inputStreamClass = env->FindClass(SERVLETINPUTSTREAM_JAVACLASS); + //read request body from the servlet input stream using 'read' method + jclass inputStreamClass = env->FindClass(SERVLETINPUTSTREAM_JAVACLASS); jmethodID read = (env)->GetMethodID(inputStreamClass, INPUTSTREAM_READ_MET, INPUTSTREAM_READ_SIG); jbyteArray byteArrayBuf = (env)->NewByteArray(length); jint count = (env)->CallIntMethod(inputStream, read, byteArrayBuf, 0, length); + jbyte* bufferPtr = (env)->GetByteArrayElements(byteArrayBuf, NULL); if (count == -1 || count > length || env->ExceptionCheck() == JNI_TRUE) //end of stream { @@ -210,13 +226,10 @@ apr_status_t ReadBodyCallback(request_rec *r, char *buf, unsigned int length, un { *readcnt = count; - jbyte* bufferPtr = (env)->GetByteArrayElements(byteArrayBuf, NULL); - memcpy(buf, bufferPtr, *readcnt); - - (env)->ReleaseByteArrayElements(byteArrayBuf, bufferPtr, NULL); - (env)->DeleteLocalRef(byteArrayBuf); } + (env)->ReleaseByteArrayElements(byteArrayBuf, bufferPtr, NULL); + (env)->DeleteLocalRef(byteArrayBuf); (jvm)->DetachCurrentThread(); } @@ -242,9 +255,9 @@ apr_status_t WriteResponseCallback(request_rec *r, char *buf, unsigned int lengt JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_initialize(JNIEnv *env, jobject obj) { (env)->GetJavaVM(&jvm); - modSecurityInstance = (env)->NewGlobalRef(obj); - //modSecurityClass = env->GetObjectClass(obj); - logMethod = (env)->GetMethodID(env->GetObjectClass(obj), MODSECURITY_LOG_MET, MODSECURITY_LOG_SIG); + modSecurityInstance = (env)->NewGlobalRef(obj); //Save the ModSecurity object for further use + + logMethod = (env)->GetMethodID(env->GetObjectClass(obj), MODSECURITY_LOG_MET, MODSECURITY_LOG_SIG); //log method ID modsecSetLogHook(NULL, logSec); @@ -254,107 +267,37 @@ JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_initialize(JNIEnv *env, modsecSetWriteResponse(WriteResponseCallback); modsecInit(); - //char *compname = (char *)malloc(128); - //s->server_hostname = compname; + modsecStartConfig(); config = modsecGetDefaultConfig(); modsecFinalizeConfig(); modsecInitProcess(); config = NULL; + //table for requests + apr_pool_create(&requestsPool, NULL); + requests = apr_table_make(requestsPool, 10); + return APR_SUCCESS; } JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_destroy(JNIEnv *env, jobject obj) { (env)->DeleteGlobalRef(modSecurityInstance); - //(env)->DeleteGlobalRef(modSecurityClass); + + apr_pool_destroy(requestsPool); + + modsecTerminate(); return APR_SUCCESS; } -//int getPort(request_rec *r) -//{ -// int port = 0; -// char *port_str = NULL; -// -// if(r->hostname != NULL) -// { -// int k = 0; -// char *ptr = (char *)r->hostname; -// -// while(*ptr != 0 && *ptr != ':') -// ptr++; -// -// if(*ptr == ':') -// { -// *ptr = 0; -// port_str = ptr + 1; -// port = atoi(port_str); -// } -// } -// return port; -//} - - -JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_onRequest(JNIEnv *env, jobject obj, jstring configPath, jobject servletRequest, jobject httpServletRequest, jstring requestID, jboolean reloadConfig) +inline void setHeaders(JNIEnv *env, jclass modSecurityClass, jobject httpServletR, apr_table_t *reqHeaders, apr_pool_t *pool, const char *headersMet, const char *headersSig) { - conn_rec *c; - request_rec *r; - - const char *path = (env)->GetStringUTFChars(configPath, NULL); - const char *reqID = (env)->GetStringUTFChars(requestID, NULL); - - if (config == NULL || reloadConfig) - { - config = modsecGetDefaultConfig(); - const char *err = modsecProcessConfig(config, path, NULL); - - if(err != NULL) - { - logSec(NULL, 0, (char*)err); - return DONE; - } - } - - c = modsecNewConnection(); - modsecProcessConnection(c); - r = modsecNewRequest(c, config); - - - jclass httpServletRequestClass = env->GetObjectClass(httpServletRequest); - jclass servletRequestClass = env->GetObjectClass(servletRequest); - jclass modSecurityClass = env->GetObjectClass(obj); - - - jmethodID getInputStream = (env)->GetMethodID(servletRequestClass, SERVLETREQUEST_INPUTSTREAM_MET, SERVLETREQUEST_INPUTSTREAM_SIG); - jobject inputStream = (env)->CallObjectMethod(servletRequest, getInputStream); - //jobject gref = env->NewGlobalRef(inputStream); - //apr_pool_cleanup_register(r->pool, gref, jDeleteGlobalRef, apr_pool_cleanup_null); - - storeJavaServletContext(r, inputStream); - - jmethodID getServerName = (env)->GetMethodID(servletRequestClass, SERVLETREQUEST_SERVERNAME_MET, STRINGRETURN_SIG); - r->hostname = fromJStringMethod(env, getServerName, servletRequest, r); - - jmethodID getServerPort = (env)->GetMethodID(servletRequestClass, SERVLETREQUEST_SERVERPORT_MET, SERVLETREQUEST_SERVERPORT_SIG); - int port = (env)->CallIntMethod(servletRequest, getServerPort); - size_t len = (size_t) ceil(log10((float) abs(port))); - char *port_str = (char*) apr_palloc(r->pool, len); - itoa(port, port_str, 10); - - - jmethodID getPathInfo = (env)->GetMethodID(httpServletRequestClass, HTTPSERVLETREQUEST_PATHINFO_MET, STRINGRETURN_SIG); - r->path_info = fromJStringMethod(env, getPathInfo, httpServletRequest, r); - - - jmethodID getQueryString = (env)->GetMethodID(httpServletRequestClass, HTTPSERVLETREQUEST_QUERYSTRING_MET, STRINGRETURN_SIG); - r->args = fromJStringMethod(env, getQueryString, httpServletRequest, r); - - - jmethodID getHttpHeaders = (env)->GetStaticMethodID(modSecurityClass, MODSECURITY__HTTPHEADERS_MET, MODSECURITY__HTTPHEADERS_SIG); - jobjectArray headersTable = (jobjectArray) (env)->CallStaticObjectMethod(modSecurityClass, getHttpHeaders, httpServletRequest); + //All headers are returned in a table by a static method from ModSecurity class + jmethodID getHttpHeaders = (env)->GetStaticMethodID(modSecurityClass, headersMet, headersSig); + jobjectArray headersTable = (jobjectArray) (env)->CallStaticObjectMethod(modSecurityClass, getHttpHeaders, httpServletR); jsize size = (env)->GetArrayLength(headersTable); for (int i = 0; i < size; i++) @@ -367,26 +310,88 @@ JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_onRequest(JNIEnv *env, j if (headerNameJStr != NULL && headerValueJStr != NULL) { - headerName = (env)->GetStringUTFChars(headerNameJStr, 0); - apr_pool_cleanup_register(r->pool, headerName, memCleanup, apr_pool_cleanup_null); + headerName = fromJString(env, headerNameJStr, pool); + //apr_pool_cleanup_register(r->pool, headerName, memCleanup, apr_pool_cleanup_null); - headerValue = (env)->GetStringUTFChars(headerValueJStr, 0); - apr_pool_cleanup_register(r->pool, headerValue, memCleanup, apr_pool_cleanup_null); + headerValue = fromJString(env, headerValueJStr, pool); + //apr_pool_cleanup_register(r->pool, headerValue, memCleanup, apr_pool_cleanup_null); - apr_table_setn(r->headers_in, headerName, headerValue); + apr_table_setn(reqHeaders, headerName, headerValue); - (env)->ReleaseStringUTFChars(headerNameJStr, headerName); - (env)->ReleaseStringUTFChars(headerValueJStr, headerValue); + env->DeleteLocalRef(headerNameJStr); + env->DeleteLocalRef(headerValueJStr); + } + } +} + +JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_onRequest(JNIEnv *env, jobject obj, jstring configPath, jobject servletRequest, jobject httpServletRequest, jstring requestID, jboolean reloadConfig) +{ + //critical section ? + conn_rec *c; + request_rec *r; + + const char *path = (env)->GetStringUTFChars(configPath, NULL); //path to modsecurity.conf + + + if (config == NULL || reloadConfig) + { + config = modsecGetDefaultConfig(); + const char *err = modsecProcessConfig(config, path, NULL); + + if(err != NULL) + { + logSec(NULL, 0, (char*)err); + + (env)->ReleaseStringUTFChars(configPath, path); + return DONE; } } + c = modsecNewConnection(); + modsecProcessConnection(c); + r = modsecNewRequest(c, config); + + const char *reqID = fromJString(env, requestID, r->pool); //unique ID of this request + apr_table_setn(requests, reqID, (const char*) r); //store this request for response processing + + + jclass httpServletRequestClass = env->GetObjectClass(httpServletRequest); //HttpServletRequest interface + jclass servletRequestClass = env->GetObjectClass(servletRequest); //ServletRequest interface + jclass modSecurityClass = env->GetObjectClass(obj); //ModSecurity class + + + jmethodID getInputStream = (env)->GetMethodID(servletRequestClass, SERVLETREQUEST_INPUTSTREAM_MET, SERVLETREQUEST_INPUTSTREAM_SIG); + jobject inputStream = (env)->CallObjectMethod(servletRequest, getInputStream); //Request body input stream used in the read body callback + + storeJavaServletContext(r, inputStream); + + jmethodID getServerName = (env)->GetMethodID(servletRequestClass, SERVLETREQUEST_SERVERNAME_MET, STRINGRETURN_SIG); + r->hostname = fromJStringMethod(env, getServerName, servletRequest, r->pool); + + jmethodID getServerPort = (env)->GetMethodID(servletRequestClass, SERVLETREQUEST_SERVERPORT_MET, SERVLETREQUEST_SERVERPORT_SIG); + int port = (env)->CallIntMethod(servletRequest, getServerPort); //server port + size_t len = (size_t) ceil(log10((float) port)); + char *port_str = (char*) apr_palloc(r->pool, len); + itoa(port, port_str, 10); + + + jmethodID getPathInfo = (env)->GetMethodID(httpServletRequestClass, HTTPSERVLETREQUEST_PATHINFO_MET, STRINGRETURN_SIG); + r->path_info = fromJStringMethod(env, getPathInfo, httpServletRequest, r->pool); + + + jmethodID getQueryString = (env)->GetMethodID(httpServletRequestClass, HTTPSERVLETREQUEST_QUERYSTRING_MET, STRINGRETURN_SIG); + r->args = fromJStringMethod(env, getQueryString, httpServletRequest, r->pool); + + + setHeaders(env, modSecurityClass, httpServletRequest, r->headers_in, r->pool, MODSECURITY__HTTPREQHEADERS_MET, MODSECURITY__HTTPREQHEADERS_SIG); + jmethodID getCharacterEncoding = (env)->GetMethodID(servletRequestClass, SERVLETREQUEST_CHARENCODING_MET, STRINGRETURN_SIG); - r->content_encoding = fromJStringMethod(env, getCharacterEncoding, servletRequest, r); + r->content_encoding = fromJStringMethod(env, getCharacterEncoding, servletRequest, r->pool); jmethodID getContentType = (env)->GetMethodID(servletRequestClass, SERVLETREQUEST_CONTENTTYPE_MET, STRINGRETURN_SIG); - r->content_type = fromJStringMethod(env, getContentType, servletRequest, r); + r->content_type = fromJStringMethod(env, getContentType, servletRequest, r->pool); const char *lng = apr_table_get(r->headers_in, "Content-Languages"); @@ -397,7 +402,7 @@ JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_onRequest(JNIEnv *env, j } jmethodID getMethod = (env)->GetMethodID(httpServletRequestClass, HTTPSERVLETREQUEST_METHOD_MET, STRINGRETURN_SIG); - const char* method = fromJStringMethod(env, getMethod, httpServletRequest, r); + const char* method = fromJStringMethod(env, getMethod, httpServletRequest, r->pool); //#define SETMETHOD(m) if(strcmp(method,#m) == 0){ r->method = method; r->method_number = M_##m; } @@ -421,7 +426,7 @@ JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_onRequest(JNIEnv *env, j else if (strcmp(method, "UNLOCK") == 0) { r->method = method; r->method_number = M_UNLOCK; } jmethodID getProtocol = (env)->GetMethodID(httpServletRequestClass, HTTPSERVLETREQUEST_PROTOCOL_MET, STRINGRETURN_SIG); - r->protocol = fromJStringMethod(env, getProtocol, httpServletRequest, r); + r->protocol = fromJStringMethod(env, getProtocol, httpServletRequest, r->pool); r->request_time = apr_time_now(); @@ -438,12 +443,25 @@ JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_onRequest(JNIEnv *env, j r->parsed_uri.user = NULL; r->parsed_uri.fragment = NULL; + //the request Url is in a StringBuffer object jmethodID getRequestURL = (env)->GetMethodID(httpServletRequestClass, HTTPSERVLETREQUEST_REQUESTURL_MET, HTTPSERVLETREQUEST_REQUESTURL_SIG); jobject stringBuffer = (env)->CallObjectMethod(httpServletRequest, getRequestURL); if (stringBuffer != NULL) { jmethodID toStringBuff = (env)->GetMethodID((env)->GetObjectClass(stringBuffer), TOSTRING_MET, STRINGRETURN_SIG); - r->unparsed_uri = fromJStringMethod(env, toStringBuff, stringBuffer, r); + char *url = fromJStringMethod(env, toStringBuff, stringBuffer, r->pool); + + if (strcmp(r->args, "") != 0) + { + r->unparsed_uri = (char*)apr_palloc(r->pool, strlen(url) + 1 + strlen(r->args)); //unparsed uri with full query + strcpy(r->unparsed_uri, url); + strcat(r->unparsed_uri, "?"); + strcat(r->unparsed_uri, r->args); + } + else + { + r->unparsed_uri = url; + } r->uri = r->unparsed_uri; } @@ -460,8 +478,8 @@ JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_onRequest(JNIEnv *env, j jmethodID getRemoteAddr = (env)->GetMethodID(servletRequestClass, SERVLETREQUEST_REMOTEADDR_MET, STRINGRETURN_SIG); jstring remoteAddrJStr = (jstring) (env)->CallObjectMethod(servletRequest, getRemoteAddr); - char *remoteAddr = (char*) (env)->GetStringUTFChars(remoteAddrJStr, 0); - apr_pool_cleanup_register(r->pool, remoteAddr, memCleanup, apr_pool_cleanup_null); + char *remoteAddr = fromJString(env, remoteAddrJStr, r->pool); + //apr_pool_cleanup_register(r->pool, remoteAddr, memCleanup, apr_pool_cleanup_null); #if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER < 3 c->remote_addr = CopySockAddr(modSecurityClass, env, r->pool, remoteAddr, remoteAddrJStr); @@ -472,13 +490,58 @@ JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_onRequest(JNIEnv *env, j #endif c->remote_host = NULL; - + int status = modsecProcessRequest(r); - (env)->ReleaseStringUTFChars(remoteAddrJStr, remoteAddr); (env)->ReleaseStringUTFChars(configPath, path); - (env)->ReleaseStringUTFChars(requestID, reqID); (env)->DeleteLocalRef(inputStream); return status; +} + + + +JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_onResponse(JNIEnv *env, jobject obj, jobject servletResponse, jobject httpServletResponse, jstring requestID) +{ + const char *reqID = env->GetStringUTFChars(requestID, NULL); + request_rec *r = (request_rec*) apr_table_get(requests, reqID); + + if (r == NULL) + { + env->ReleaseStringUTFChars(requestID, reqID); + return DONE; + } + + jclass httpServletResponseClass = env->GetObjectClass(httpServletResponse); //HttpServletResponse interface + jclass servletResponseClass = env->GetObjectClass(servletResponse); //ServletResponse interface + jclass modSecurityClass = env->GetObjectClass(obj); //ModSecurity class + + jmethodID getContentType = (env)->GetMethodID(servletResponseClass, SERVLETRESPONSE_CONTENTTYPE_MET, STRINGRETURN_SIG); + char *ct = fromJStringMethod(env, getContentType, servletResponse, r->pool); + if(strcmp(ct, "") == 0) + ct = "text/html"; + r->content_type = ct; + + + jmethodID getCharEncoding = (env)->GetMethodID(servletResponseClass, SERVLETRESPONSE_CHARENCODING_MET, STRINGRETURN_SIG); + r->content_encoding = fromJStringMethod(env, getCharEncoding, servletResponse, r->pool); + + + setHeaders(env, modSecurityClass, httpServletResponse, r->headers_out, r->pool, MODSECURITY__HTTPRESHEADERS_MET, MODSECURITY__HTTPRESHEADERS_SIG); + + const char *lng = apr_table_get(r->headers_out, "Content-Languages"); + if(lng != NULL) + { + r->content_languages = apr_array_make(r->pool, 1, sizeof(const char *)); + *(const char **)apr_array_push(r->content_languages) = lng; + } + + //modsecProcessResponse(r); + + apr_table_unset(requests, reqID); //remove this request from the requests table + modsecFinishRequest(r); + + env->ReleaseStringUTFChars(requestID, reqID); + + return DONE; } \ No newline at end of file diff --git a/java/org_modsecurity_ModSecurity.h b/java/org_modsecurity_ModSecurity.h index a68a3416..f562d87b 100644 --- a/java/org_modsecurity_ModSecurity.h +++ b/java/org_modsecurity_ModSecurity.h @@ -7,6 +7,12 @@ #ifdef __cplusplus extern "C" { #endif +#undef org_modsecurity_ModSecurity_DONE +#define org_modsecurity_ModSecurity_DONE -2L +#undef org_modsecurity_ModSecurity_DECLINED +#define org_modsecurity_ModSecurity_DECLINED -1L +#undef org_modsecurity_ModSecurity_OK +#define org_modsecurity_ModSecurity_OK 0L /* * Class: org_modsecurity_ModSecurity * Method: initialize @@ -31,6 +37,14 @@ JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_destroy JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_onRequest (JNIEnv *, jobject, jstring, jobject, jobject, jstring, jboolean); +/* + * Class: org_modsecurity_ModSecurity + * Method: onResponse + * Signature: (Ljavax/servlet/ServletResponse;Ljavax/servlet/http/HttpServletResponse;Ljava/lang/String)I + */ +JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_onResponse + (JNIEnv *, jobject, jobject, jobject, jstring); + #ifdef __cplusplus } #endif