mirror of
https://github.com/owasp-modsecurity/ModSecurity.git
synced 2025-08-13 21:36:00 +03:00
Merged the PDF XSS protection functionality into ModSecurity.
This commit is contained in:
parent
c559f3ee21
commit
fca9eabafe
2
CHANGES
2
CHANGES
@ -2,6 +2,8 @@
|
||||
?? ??? 2007 - 2.2.0-trunk
|
||||
-------------------------
|
||||
|
||||
* Merged the PDF XSS protection functionality into ModSecurity.
|
||||
|
||||
* Exported API for registering custom variables. Example in api directory.
|
||||
|
||||
* Added experimental variables RESPONSE_CONTENT_LENGTH, RESPONSE_CONTENT_TYPE,
|
||||
|
@ -23,7 +23,7 @@ LIBS = $(BASE)\lib\libhttpd.lib $(BASE)\lib\libapr.lib $(BASE)\lib\libaprutil.li
|
||||
OBJS = mod_security2.obj apache2_config.obj apache2_io.obj apache2_util.obj \
|
||||
re.obj re_operators.obj re_actions.obj re_tfns.obj re_variables.obj \
|
||||
msc_logging.obj msc_xml.obj msc_multipart.obj modsecurity.obj msc_parsers.obj \
|
||||
msc_util.obj msc_pcre.obj persist_dbm.obj msc_reqbody.obj
|
||||
msc_util.obj msc_pcre.obj persist_dbm.obj msc_reqbody.obj pdf_protec.obj
|
||||
|
||||
all: $(DLL)
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id: apache2.h,v 1.1.1.1 2006/10/14 09:30:43 ivanr Exp $
|
||||
* $Id$
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
|
@ -84,6 +84,13 @@ void *create_directory_config(apr_pool_t *mp, char *path) {
|
||||
/* Content injection. */
|
||||
dcfg->content_injection_enabled = NOT_SET;
|
||||
|
||||
/* PDF XSS protection. */
|
||||
dcfg->pdfp_enabled = NOT_SET;
|
||||
dcfg->pdfp_secret = NOT_SET_P;
|
||||
dcfg->pdfp_timeout = NOT_SET;
|
||||
dcfg->pdfp_token_name = NOT_SET_P;
|
||||
dcfg->pdfp_only_get = NOT_SET;
|
||||
|
||||
return dcfg;
|
||||
}
|
||||
|
||||
@ -363,6 +370,18 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) {
|
||||
merged->content_injection_enabled = (child->content_injection_enabled == NOT_SET
|
||||
? parent->content_injection_enabled : child->content_injection_enabled);
|
||||
|
||||
/* PDF XSS protection. */
|
||||
merged->pdfp_enabled = (child->pdfp_enabled == NOT_SET
|
||||
? parent->pdfp_enabled : child->pdfp_enabled);
|
||||
merged->pdfp_secret = (child->pdfp_secret == NOT_SET_P
|
||||
? parent->pdfp_secret : child->pdfp_secret);
|
||||
merged->pdfp_timeout = (child->pdfp_timeout == NOT_SET
|
||||
? parent->pdfp_timeout : child->pdfp_timeout);
|
||||
merged->pdfp_token_name = (child->pdfp_token_name == NOT_SET_P
|
||||
? parent->pdfp_token_name : child->pdfp_token_name);
|
||||
merged->pdfp_only_get = (child->pdfp_only_get == NOT_SET
|
||||
? parent->pdfp_only_get : child->pdfp_only_get);
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
@ -424,6 +443,13 @@ void init_directory_config(directory_config *dcfg) {
|
||||
|
||||
/* Content injection. */
|
||||
if (dcfg->content_injection_enabled == NOT_SET) dcfg->content_injection_enabled = 0;
|
||||
|
||||
/* PDF XSS protection. */
|
||||
if (dcfg->pdfp_enabled == NOT_SET) dcfg->pdfp_enabled = 0;
|
||||
if (dcfg->pdfp_secret == NOT_SET_P) dcfg->pdfp_secret = NULL;
|
||||
if (dcfg->pdfp_timeout == NOT_SET) dcfg->pdfp_timeout = 10;
|
||||
if (dcfg->pdfp_token_name == NOT_SET_P) dcfg->pdfp_token_name = "PDFPTOKEN";
|
||||
if (dcfg->pdfp_only_get == NOT_SET) dcfg->pdfp_only_get = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1104,6 +1130,61 @@ static const char *cmd_web_app_id(cmd_parms *cmd, void *_dcfg, const char *p1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* -- PDF Protection configuration -- */
|
||||
|
||||
static const char *cmd_pdf_protect(cmd_parms *cmd, void *_dcfg, int flag) {
|
||||
directory_config *dcfg = (directory_config *)_dcfg;
|
||||
if (dcfg == NULL) return NULL;
|
||||
|
||||
dcfg->pdfp_enabled = flag;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *cmd_pdf_protect_secret(cmd_parms *cmd, void *_dcfg,
|
||||
const char *p1)
|
||||
{
|
||||
directory_config *dcfg = (directory_config *)_dcfg;
|
||||
if (dcfg == NULL) return NULL;
|
||||
|
||||
dcfg->pdfp_secret = p1;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *cmd_pdf_protect_timeout(cmd_parms *cmd, void *_dcfg,
|
||||
const char *p1)
|
||||
{
|
||||
directory_config *dcfg = (directory_config *)_dcfg;
|
||||
if (dcfg == NULL) return NULL;
|
||||
|
||||
dcfg->pdfp_timeout = atoi(p1);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *cmd_pdf_protect_token_name(cmd_parms *cmd, void *_dcfg,
|
||||
const char *p1)
|
||||
{
|
||||
directory_config *dcfg = (directory_config *)_dcfg;
|
||||
if (dcfg == NULL) return NULL;
|
||||
|
||||
dcfg->pdfp_token_name = p1;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *cmd_pdf_protect_intercept_get_only(cmd_parms *cmd, void *_dcfg,
|
||||
int flag)
|
||||
{
|
||||
directory_config *dcfg = (directory_config *)_dcfg;
|
||||
if (dcfg == NULL) return NULL;
|
||||
|
||||
dcfg->pdfp_only_get = flag;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* -- Configuration directives definitions -- */
|
||||
|
||||
@ -1403,5 +1484,45 @@ const command_rec module_directives[] = {
|
||||
"" // TODO
|
||||
),
|
||||
|
||||
AP_INIT_FLAG (
|
||||
"SecPdfProtect",
|
||||
cmd_pdf_protect,
|
||||
NULL,
|
||||
RSRC_CONF,
|
||||
"enable PDF protection module."
|
||||
),
|
||||
|
||||
AP_INIT_TAKE1 (
|
||||
"SecPdfProtectSecret",
|
||||
cmd_pdf_protect_secret,
|
||||
NULL,
|
||||
RSRC_CONF,
|
||||
"secret that will be used to construct protection tokens."
|
||||
),
|
||||
|
||||
AP_INIT_TAKE1 (
|
||||
"SecPdfProtectTimeout",
|
||||
cmd_pdf_protect_timeout,
|
||||
NULL,
|
||||
RSRC_CONF,
|
||||
"duration for which protection tokens will be valid."
|
||||
),
|
||||
|
||||
AP_INIT_TAKE1 (
|
||||
"SecPdfProtectTokenName",
|
||||
cmd_pdf_protect_token_name,
|
||||
NULL,
|
||||
RSRC_CONF,
|
||||
"name of the protection token. The name 'PDFTOKEN' is used by default."
|
||||
),
|
||||
|
||||
AP_INIT_FLAG (
|
||||
"SecPdfProtectInterceptGETOnly",
|
||||
cmd_pdf_protect_intercept_get_only,
|
||||
NULL,
|
||||
RSRC_CONF,
|
||||
"whether or not to intercept only GET requess."
|
||||
),
|
||||
|
||||
{ NULL }
|
||||
};
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include "modsecurity.h"
|
||||
#include "apache2.h"
|
||||
#include "pdf_protect.h"
|
||||
#include "msc_logging.h"
|
||||
#include "msc_util.h"
|
||||
|
||||
@ -585,6 +586,15 @@ static int hook_request_late(request_rec *r) {
|
||||
|
||||
init_directory_config(msr->txcfg);
|
||||
|
||||
/* Perform the PDF tests now. */
|
||||
rc = pdfp_check(msr);
|
||||
if (rc > 0) {
|
||||
/* The PDF protection module has decided it needs to
|
||||
* redirect the current transaction. So we let it do that.
|
||||
*/
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (msr->txcfg->is_enabled == 0) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Processing disabled, skipping (hook request_late).");
|
||||
@ -831,12 +841,18 @@ static int hook_log_transaction(request_rec *r) {
|
||||
static void hook_insert_filter(request_rec *r) {
|
||||
modsec_rec *msr = NULL;
|
||||
|
||||
/* Find the transaction context and make sure we are
|
||||
* supposed to proceed.
|
||||
*/
|
||||
/* Find the transaction context first. */
|
||||
msr = retrieve_tx_context(r);
|
||||
if (msr == NULL) return;
|
||||
|
||||
/* We always add the PDF XSS protection filter. */
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Hook insert_filter: Adding PDF XSS protection output filter (r %x).", r);
|
||||
}
|
||||
|
||||
ap_add_output_filter("PDFP_OUT", msr, r, r->connection);
|
||||
|
||||
/* Only proceed to add the second filter if the engine is enabled. */
|
||||
if (msr->txcfg->is_enabled == 0) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Hook insert_filter: Processing disabled, skipping.");
|
||||
@ -844,10 +860,12 @@ static void hook_insert_filter(request_rec *r) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Add the input filter, but only if we need it to run. */
|
||||
if (msr->if_status == IF_STATUS_WANTS_TO_RUN) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Hook insert_filter: Adding input forwarding filter (r %x).", r);
|
||||
}
|
||||
|
||||
ap_add_input_filter("MODSECURITY_IN", msr, r, r->connection);
|
||||
}
|
||||
|
||||
@ -859,6 +877,7 @@ static void hook_insert_filter(request_rec *r) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "Hook insert_filter: Adding output filter (r %x).", r);
|
||||
}
|
||||
|
||||
ap_add_output_filter("MODSECURITY_OUT", msr, r, r->connection);
|
||||
}
|
||||
}
|
||||
@ -994,6 +1013,9 @@ static void register_hooks(apr_pool_t *mp) {
|
||||
NULL, AP_FTYPE_CONTENT_SET);
|
||||
ap_register_output_filter("MODSECURITY_OUT", output_filter,
|
||||
NULL, AP_FTYPE_CONTENT_SET);
|
||||
|
||||
ap_register_output_filter("PDFP_OUT", pdfp_output_filter,
|
||||
NULL, AP_FTYPE_CONTENT_SET);
|
||||
}
|
||||
|
||||
/* Defined in apache2_config.c */
|
||||
|
@ -395,6 +395,13 @@ struct directory_config {
|
||||
|
||||
/* Content injection. */
|
||||
int content_injection_enabled;
|
||||
|
||||
/* PDF XSS Protection. */
|
||||
int pdfp_enabled;
|
||||
const char *pdfp_secret;
|
||||
int pdfp_timeout;
|
||||
const char *pdfp_token_name;
|
||||
int pdfp_only_get;
|
||||
};
|
||||
|
||||
struct error_message {
|
||||
|
@ -2,10 +2,10 @@
|
||||
MOD_SECURITY2 = mod_security2 apache2_config apache2_io apache2_util \
|
||||
re re_operators re_actions re_tfns re_variables \
|
||||
msc_logging msc_xml msc_multipart modsecurity msc_parsers msc_util msc_pcre \
|
||||
persist_dbm msc_reqbody
|
||||
persist_dbm msc_reqbody pdf_protect
|
||||
|
||||
H = re.h modsecurity.h msc_logging.h msc_multipart.h msc_parsers.h \
|
||||
msc_pcre.h msc_util.h msc_xml.h persist_dbm.h apache2.h
|
||||
msc_pcre.h msc_util.h msc_xml.h persist_dbm.h apache2.h pdf_protect.h
|
||||
|
||||
${MOD_SECURITY2:=.slo}: ${H}
|
||||
${MOD_SECURITY2:=.lo}: ${H}
|
||||
|
463
apache2/pdf_protect.c
Normal file
463
apache2/pdf_protect.c
Normal file
@ -0,0 +1,463 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id$
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
#include "modsecurity.h"
|
||||
// #include "apache2.h"
|
||||
#include "pdf_protect.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include "apr_sha1.h"
|
||||
|
||||
#define DEFAULT_TIMEOUT 10
|
||||
#define DEFAULT_TOKEN_NAME "PDFPTOKEN"
|
||||
#define ATTACHMENT_MIME_TYPE "application/octet-stream"
|
||||
#define NOTE_TWEAK_HEADERS "pdfp-note-tweakheaders"
|
||||
#define NOTE_DONE "pdfp-note-done"
|
||||
#define REDIRECT_STATUS HTTP_TEMPORARY_REDIRECT
|
||||
#define DISPOSITION_VALUE "attachment;"
|
||||
|
||||
// TODO We need ID and REV values for the PDF XSS alert.
|
||||
|
||||
// TODO It would be nice if the user could choose the ID/REV/SEVERITY/MESSAGE, etc.
|
||||
|
||||
static char *encode_sha1_base64(apr_pool_t *mp, const char *data) {
|
||||
unsigned char digest[APR_SHA1_DIGESTSIZE];
|
||||
apr_sha1_ctx_t context;
|
||||
|
||||
/* Calculate the hash first. */
|
||||
apr_sha1_init(&context);
|
||||
apr_sha1_update(&context, data, strlen(data));
|
||||
apr_sha1_final(digest, &context);
|
||||
|
||||
/* Now transform with transport-friendly hex encoding. */
|
||||
return bytes2hex(mp, digest, APR_SHA1_DIGESTSIZE);
|
||||
}
|
||||
|
||||
static char *create_hash(modsec_rec *msr,
|
||||
const char *time_string)
|
||||
{
|
||||
const char *content = NULL;
|
||||
|
||||
if (msr->txcfg->pdfp_secret == NULL) {
|
||||
msr_log(msr, 1, "PdfProtect: Unable to generate hash. Please configure SecPdfProtectSecret.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Our protection token is made out of the client's IP
|
||||
* address, the secret key, and the token expiry time.
|
||||
*/
|
||||
content = apr_pstrcat(msr->mp, msr->remote_addr, msr->txcfg->pdfp_secret,
|
||||
time_string, NULL);
|
||||
if (content == NULL) return NULL;
|
||||
|
||||
return encode_sha1_base64(msr->mp, content);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static char *create_token(modsec_rec *msr) {
|
||||
unsigned int current_time;
|
||||
const char *time_string = NULL;
|
||||
const char *hash = NULL;
|
||||
int timeout = DEFAULT_TIMEOUT;
|
||||
|
||||
if (msr->txcfg->pdfp_timeout != -1) {
|
||||
timeout = msr->txcfg->pdfp_timeout;
|
||||
}
|
||||
|
||||
current_time = apr_time_sec(apr_time_now());
|
||||
time_string = apr_psprintf(msr->mp, "%i", current_time + timeout);
|
||||
if (time_string == NULL) return NULL;
|
||||
|
||||
hash = create_hash(msr, time_string);
|
||||
if (hash == NULL) return NULL;
|
||||
|
||||
return apr_pstrcat(msr->mp, hash, "|", time_string, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static char *construct_new_uri(modsec_rec *msr) {
|
||||
const char *token_parameter = NULL;
|
||||
const char *new_uri = NULL;
|
||||
const char *token = NULL;
|
||||
const char *token_name = DEFAULT_TOKEN_NAME;
|
||||
|
||||
token = create_token(msr);
|
||||
if (token == NULL) return NULL;
|
||||
|
||||
if (msr->txcfg->pdfp_token_name != NULL) {
|
||||
token_name = msr->txcfg->pdfp_token_name;
|
||||
}
|
||||
|
||||
token_parameter = apr_pstrcat(msr->mp, token_name, "=", token, NULL);
|
||||
if (token_parameter == NULL) return NULL;
|
||||
|
||||
if (msr->r->args == NULL) { /* No other parameters. */
|
||||
new_uri = apr_pstrcat(msr->mp, msr->r->uri, "?", token_parameter, "#PDFP", NULL);
|
||||
} else { /* Preserve existing paramters. */
|
||||
new_uri = apr_pstrcat(msr->mp, msr->r->uri, "?", msr->r->args, "&",
|
||||
token_parameter, "#PDFP", NULL);
|
||||
}
|
||||
|
||||
return (char *)new_uri;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static char *extract_token(modsec_rec *msr) {
|
||||
char *search_string = NULL;
|
||||
char *p = NULL, *t = NULL;
|
||||
const char *token_name = DEFAULT_TOKEN_NAME;
|
||||
|
||||
if ((msr->r == NULL)||(msr->r->args == NULL)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (msr->txcfg->pdfp_token_name != NULL) {
|
||||
token_name = msr->txcfg->pdfp_token_name;
|
||||
}
|
||||
|
||||
search_string = apr_pstrcat(msr->mp, msr->txcfg->pdfp_token_name, "=", NULL);
|
||||
if (search_string == NULL) return NULL;
|
||||
|
||||
p = strstr(msr->r->args, search_string);
|
||||
if (p == NULL) return NULL;
|
||||
|
||||
t = p = p + strlen(search_string);
|
||||
while ((*t != '\0')&&(*t != '&')) t++;
|
||||
|
||||
return apr_pstrmemdup(msr->mp, p, t - p);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int validate_time_string(const char *time_string) {
|
||||
char *p = (char *)time_string;
|
||||
|
||||
while(*p != '\0') {
|
||||
if (!isdigit(*p)) return 0;
|
||||
p++;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int verify_token(modsec_rec *msr, const char *token, char **error_msg) {
|
||||
unsigned int current_time, expiry_time;
|
||||
const char *time_string = NULL;
|
||||
const char *given_hash = NULL;
|
||||
const char *hash = NULL;
|
||||
const char *p = NULL;
|
||||
|
||||
if (error_msg == NULL) return 0;
|
||||
*error_msg = NULL;
|
||||
|
||||
/* Split token into its parts - hash and expiry time. */
|
||||
p = strstr(token, "|");
|
||||
if (p == NULL) return 0;
|
||||
|
||||
given_hash = apr_pstrmemdup(msr->mp, token, p - token);
|
||||
time_string = p + 1;
|
||||
if (!validate_time_string(time_string)) {
|
||||
*error_msg = apr_psprintf(msr->mp, "PdfProtect: Invalid time string: %s",
|
||||
log_escape_nq(msr->mp, time_string));
|
||||
return 0;
|
||||
}
|
||||
expiry_time = atoi(time_string);
|
||||
|
||||
/* Check the hash. */
|
||||
hash = create_hash(msr, time_string);
|
||||
if (strcmp(given_hash, hash) != 0) {
|
||||
*error_msg = apr_psprintf(msr->mp, "PdfProtect: Invalid hash: %s (expected %s)",
|
||||
log_escape_nq(msr->mp, given_hash), log_escape_nq(msr->mp, hash));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check time. */
|
||||
current_time = apr_time_sec(apr_time_now());
|
||||
if (current_time > expiry_time) {
|
||||
*error_msg = apr_psprintf(msr->mp, "PdfProtect: Token has expired.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static apr_status_t send_error_bucket(ap_filter_t *f, int status) {
|
||||
apr_bucket_brigade *brigade = NULL;
|
||||
apr_bucket *bucket = NULL;
|
||||
|
||||
brigade = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc);
|
||||
if (brigade == NULL) return APR_EGENERAL;
|
||||
bucket = ap_bucket_error_create(status, NULL, f->r->pool, f->r->connection->bucket_alloc);
|
||||
if (bucket == NULL) return APR_EGENERAL;
|
||||
APR_BRIGADE_INSERT_TAIL(brigade, bucket);
|
||||
|
||||
f->r->connection->keepalive = AP_CONN_CLOSE;
|
||||
return ap_pass_brigade(f->next, brigade);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
apr_status_t pdfp_output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) {
|
||||
modsec_rec *msr = (modsec_rec *)f->ctx;
|
||||
|
||||
if (msr == NULL) {
|
||||
ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, f->r->server,
|
||||
"ModSecurity: Internal Error: msr is null in PDF output filter.");
|
||||
ap_remove_output_filter(f);
|
||||
return send_error_bucket(f, HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
if (msr->txcfg->pdfp_enabled == 1) {
|
||||
const char *h_content_type = apr_table_get(f->r->headers_out, "Content-Type");
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 9) {
|
||||
msr_log(msr, 9, "PdfProtect: r->content_type=%s, header C-T=%s",
|
||||
f->r->content_type, h_content_type);
|
||||
}
|
||||
|
||||
/* Have we been asked to tweak the headers? */
|
||||
if (apr_table_get(f->r->notes, NOTE_TWEAK_HEADERS) != NULL) {
|
||||
/* Yes! */
|
||||
apr_table_set(f->r->headers_out, "Content-Disposition", DISPOSITION_VALUE);
|
||||
f->r->content_type = ATTACHMENT_MIME_TYPE;
|
||||
}
|
||||
|
||||
/* Check if we've already done the necessary work in
|
||||
* the first phase.
|
||||
*/
|
||||
if (apr_table_get(f->r->notes, NOTE_DONE) != NULL) {
|
||||
/* We have, so return straight away. */
|
||||
ap_remove_output_filter(f);
|
||||
return ap_pass_brigade(f->next, bb_in);
|
||||
}
|
||||
|
||||
/* Proceed to detect dynamically-generated PDF files. */
|
||||
if (((f->r->content_type != NULL)&&(strcasecmp(f->r->content_type, "application/pdf") == 0))
|
||||
|| ((h_content_type != NULL)&&(strcasecmp(h_content_type, "application/pdf") == 0)))
|
||||
{
|
||||
request_rec *r = f->r;
|
||||
const char *token = NULL;
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 9) {
|
||||
msr_log(msr, 9, "PdfProtect: Detected a dynamically-generated PDF in %s",
|
||||
r->uri);
|
||||
}
|
||||
|
||||
/* Is this a non-GET request? */
|
||||
if ((f->r->method_number != M_GET)&&
|
||||
((msr->txcfg->pdfp_only_get == 1)||(msr->txcfg->pdfp_only_get == -1))
|
||||
) {
|
||||
/* This is a non-GET request and we have been configured
|
||||
* not to intercept it. We are not going to do that but
|
||||
* we are going to tweak the headers to force download.
|
||||
*/
|
||||
if (msr->txcfg->debuglog_level >= 9) {
|
||||
msr_log(msr, 9, "PdfProtect: Forcing download of a dynamically "
|
||||
"generated PDF file and non-GET method.");
|
||||
}
|
||||
|
||||
apr_table_set(f->r->headers_out, "Content-Disposition", DISPOSITION_VALUE);
|
||||
f->r->content_type = ATTACHMENT_MIME_TYPE;
|
||||
|
||||
ap_remove_output_filter(f);
|
||||
return ap_pass_brigade(f->next, bb_in);
|
||||
}
|
||||
|
||||
/* Locate the protection token. */
|
||||
token = extract_token(msr);
|
||||
|
||||
if (token == NULL) { /* No token. */
|
||||
char *new_uri = NULL;
|
||||
|
||||
/* Create a new URI with the protection token inside. */
|
||||
new_uri = construct_new_uri(msr);
|
||||
if (new_uri != NULL) {
|
||||
/* Redirect user to the new URI. */
|
||||
if (msr->txcfg->debuglog_level >= 9) {
|
||||
msr_log(msr, 9, "PdfProtect: PDF request without a token - "
|
||||
"redirecting to %s.", new_uri);
|
||||
}
|
||||
|
||||
apr_table_set(r->headers_out, "Location", new_uri);
|
||||
return send_error_bucket(f, REDIRECT_STATUS);
|
||||
}
|
||||
} else { /* Token found. */
|
||||
char *my_error_msg = NULL;
|
||||
|
||||
/* Verify the token is valid. */
|
||||
|
||||
if (verify_token(msr, token, &my_error_msg)) { /* Valid. */
|
||||
/* Do nothing - serve the PDF file. */
|
||||
if (msr->txcfg->debuglog_level >= 9) {
|
||||
msr_log(msr, 9, "PdfProtect: PDF request with a valid token "
|
||||
"- serving PDF file normally.");
|
||||
}
|
||||
|
||||
/* Fall through. */
|
||||
} else { /* Not valid. */
|
||||
/* The token is not valid. We will tweak the response
|
||||
* to prevent the PDF file from opening in the browser.
|
||||
*/
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 9, "PdfProtect: PDF request with an invalid token "
|
||||
"- serving file as attachment.");
|
||||
}
|
||||
|
||||
apr_table_set(r->headers_out, "Content-Disposition", DISPOSITION_VALUE);
|
||||
r->content_type = ATTACHMENT_MIME_TYPE;
|
||||
|
||||
/* Fall through. */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ap_remove_output_filter(f);
|
||||
|
||||
return ap_pass_brigade(f->next, bb_in);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int DSOLOCAL pdfp_check(modsec_rec *msr) {
|
||||
const char *token = NULL;
|
||||
directory_config *cfg = NULL;
|
||||
char *uri = NULL;
|
||||
char *p = NULL;
|
||||
|
||||
if (msr->txcfg->pdfp_enabled != 1) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "PdfProtect: Not enabled here.");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Then determine whether we need to act at
|
||||
* all. If the request is not for a PDF file
|
||||
* return straight away.
|
||||
*/
|
||||
|
||||
if (msr->r->uri == NULL) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "PdfProtect: Unable to inspect URI because it is NULL.");
|
||||
}
|
||||
/* TODO Should we return -1 instead? */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 9) {
|
||||
msr_log(msr, 9, "PdfProtect: URI=%s, filename=%s, QUERY_STRING=%s.",
|
||||
msr->r->uri, msr->r->filename, msr->r->args);
|
||||
}
|
||||
|
||||
uri = apr_pstrdup(msr->mp, msr->r->uri);
|
||||
if (uri == NULL) return -1;
|
||||
ap_str_tolower(uri);
|
||||
|
||||
/* Attempt to figure out if this is a request for a PDF file. We are
|
||||
* going to be liberal and look for the extension anywhere in the URI,
|
||||
* not just at the end.
|
||||
*/
|
||||
p = strstr(uri, ".pdf");
|
||||
if (p == NULL) {
|
||||
/* We do not think this is a PDF file. */
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "PdfProtect: No indication in the URI this is a "
|
||||
"request for a PDF file.");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Ignore request methods other than GET if
|
||||
* configured to do so.
|
||||
*/
|
||||
if ((msr->r->method_number != M_GET)&&(cfg->pdfp_only_get != 0)) {
|
||||
if (msr->txcfg->debuglog_level >= 4) {
|
||||
msr_log(msr, 4, "PdfProtect: Configured not to intercept non-GET requests "
|
||||
"(method=%s/%i).", msr->r->method, msr->r->method_number);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We make a note for ourselves that we've already handled
|
||||
* the request.
|
||||
*/
|
||||
apr_table_set(msr->r->notes, NOTE_DONE, "1");
|
||||
|
||||
/* Locate the protection token. */
|
||||
token = extract_token(msr);
|
||||
|
||||
if (token == NULL) { /* No token. */
|
||||
char *new_uri = NULL;
|
||||
|
||||
/* Create a new URI with the protection token inside. */
|
||||
new_uri = construct_new_uri(msr);
|
||||
if (new_uri == NULL) return DECLINED;
|
||||
|
||||
/* Redirect user to the new URI. */
|
||||
if (msr->txcfg->debuglog_level >= 9) {
|
||||
msr_log(msr, 9, "PdfProtect: PDF request without a token - redirecting to %s.",
|
||||
new_uri);
|
||||
}
|
||||
|
||||
apr_table_set(msr->r->headers_out, "Location", new_uri);
|
||||
|
||||
return REDIRECT_STATUS;
|
||||
} else { /* Token found. */
|
||||
char *my_error_msg = NULL;
|
||||
|
||||
/* Verify the token is valid. */
|
||||
if (verify_token(msr, token, &my_error_msg)) { /* Valid. */
|
||||
/* Do nothing - serve the PDF file. */
|
||||
if (msr->txcfg->debuglog_level >= 9) {
|
||||
msr_log(msr, 9, "PdfProtect: PDF request with a valid token - "
|
||||
"serving PDF file normally.");
|
||||
}
|
||||
return 0;
|
||||
} else { /* Not valid. */
|
||||
/* The token is not valid. We will tweak the response
|
||||
* to prevent the PDF file from opening in the browser.
|
||||
*/
|
||||
|
||||
// TODO Log alert
|
||||
|
||||
/* Changing response headers before response generation phase takes
|
||||
* place is not really reliable. Although we do this we also make
|
||||
* a note for ourselves (in the output filter) to check the headers
|
||||
* again just before they are sent back to the end user.
|
||||
*/
|
||||
apr_table_set(msr->r->headers_out, "Content-Disposition", DISPOSITION_VALUE);
|
||||
msr->r->content_type = ATTACHMENT_MIME_TYPE;
|
||||
apr_table_set(msr->r->notes, NOTE_TWEAK_HEADERS, "1");
|
||||
|
||||
/* Proceed with response (PDF) generation. */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
21
apache2/pdf_protect.h
Normal file
21
apache2/pdf_protect.h
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||||
* Copyright (c) 2004-2007 Thinking Stone (http://www.thinkingstone.com)
|
||||
*
|
||||
* $Id$
|
||||
*
|
||||
* You should have received a copy of the licence along with this
|
||||
* program (stored in the file "LICENSE"). If the file is missing,
|
||||
* or if you have any other questions related to the licence, please
|
||||
* write to Thinking Stone at contact@thinkingstone.com.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _PDF_PROTECT_H_
|
||||
#define _PDF_PROTECT_H_
|
||||
|
||||
apr_status_t DSOLOCAL pdfp_output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in);
|
||||
|
||||
int DSOLOCAL pdfp_check(modsec_rec *msr);
|
||||
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user