2017-03-06 15:01:59 -03:00

670 lines
20 KiB
C++

/*
* ModSecurity, http://www.modsecurity.org/
* Copyright (c) 2015 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 __cplusplus
#include <ctime>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <list>
#include <map>
#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#endif
#include <stdlib.h>
#include <stddef.h>
#ifndef HEADERS_MODSECURITY_TRANSACTION_H_
#define HEADERS_MODSECURITY_TRANSACTION_H_
#ifndef __cplusplus
typedef struct ModSecurity_t ModSecurity;
typedef struct Transaction_t Transaction;
typedef struct Rules_t Rules;
#endif
#include "modsecurity/intervention.h"
#include "modsecurity/collection/collections.h"
#include "modsecurity/collection/variable.h"
#include "modsecurity/collection/collection.h"
#include "modsecurity/variable_origin.h"
#define LOGFY_ADD(a, b) \
yajl_gen_string(g, reinterpret_cast<const unsigned char*>(a), strlen(a)); \
if (b == NULL) { \
yajl_gen_string(g, reinterpret_cast<const unsigned char*>(""), \
strlen("")); \
} else { \
yajl_gen_string(g, reinterpret_cast<const unsigned char*>(b), \
strlen(b)); \
}
#define LOGFY_ADD_INT(a, b) \
yajl_gen_string(g, reinterpret_cast<const unsigned char*>(a), strlen(a)); \
yajl_gen_number(g, reinterpret_cast<const char*>(b), strlen(b));
#define LOGFY_ADD_NUM(a, b) \
yajl_gen_string(g, reinterpret_cast<const unsigned char*>(a), strlen(a)); \
yajl_gen_integer(g, b);
#ifdef __cplusplus
namespace modsecurity {
class ModSecurity;
class Transaction;
class Rules;
class RuleMessage;
namespace actions {
class Action;
namespace disruptive {
enum AllowType : int;
}
}
namespace RequestBodyProcessor {
class XML;
class JSON;
}
namespace operators {
class Operator;
}
class VariableOriginRequest : public VariableOrigin {
public:
VariableOriginRequest()
: m_length(0),
m_offset(0) { }
std::string toText() {
#if 0
return "Variable origin was extracted straight from " \
"request/response, offset: " + std::to_string(m_offset) + \
", length: " + std::to_string(m_length) + ".";
#else
return "rr:" + std::to_string(m_offset) + "," \
+ std::to_string(m_length);
#endif
}
int m_length;
int m_offset;
};
class AnchoredVariable {
public:
AnchoredVariable(Transaction *t, std::string name)
: m_name(""),
m_transaction(t),
m_value("") {
m_name.append(name);
m_var = new collection::Variable(&m_name);
m_var->m_dynamic = false;
m_var->m_value = &m_value;
}
void set(const std::string &a, size_t offset) {
std::unique_ptr<VariableOriginRequest> origin (new VariableOriginRequest());
m_offset = offset;
m_value.assign(a.c_str(), a.size());
origin->m_offset = offset;
origin->m_length = m_value.size();
m_var->m_orign.push_back(std::move(origin));
}
void append(const std::string &a, size_t offset,
bool spaceSeparator = false) {
std::unique_ptr<VariableOriginRequest> origin (new VariableOriginRequest());
if (spaceSeparator && !m_value.empty()) {
m_value.append(" " + a);
} else {
m_value.append(a);
}
m_offset = offset;
origin->m_offset = offset;
origin->m_length = a.size();
m_var->m_orign.push_back(std::move(origin));
}
void evaluate(std::vector<const collection::Variable *> *l) {
if (m_name.empty() || m_var->m_key == NULL
|| m_var->m_value == NULL || m_var->m_key->empty()) {
return;
}
l->push_back(m_var);
}
std::string *evaluate() {
if (m_value.empty() == true) {
return NULL;
}
return &m_value;
}
int m_offset;
std::string m_value;
Transaction *m_transaction;
std::string m_name;
collection::Variable *m_var;
};
class TransactionAnchoredVariables {
public:
TransactionAnchoredVariables(Transaction *t)
: m_variableArgsNames(t, "ARGS_NAMES"),
m_variableArgGetNames(t, "ARGS_GET_NAMES"),
m_variableArgPostNames(t, "ARGS_POST_NAMES"),
m_variableRequestHeadersNames(t, "REQUEST_HEADERS_NAMES"),
m_variableResponseContentType(t, "RESPONSE_CONTENT_TYPE"),
m_variableResponseHeadersNames(t, "RESPONSE_HEADERS_NAMES"),
m_variableARGScombinedSize(t, "ARGS_COMBINED_SIZE"),
m_variableAuthType(t, "AUTH_TYPE"),
m_variableFilesCombinedSize(t, "FILES_COMBINED_SIZE"),
m_variableFilesTmpNames(t, "FILES_TMPNAMES"),
m_variableFullRequest(t, "FULL_REQUEST"),
m_variableFullRequestLength(t, "FULL_REQUEST_LENGTH"),
m_variableInboundDataError(t, "INBOUND_DATA_ERROR"),
m_variableMatchedVar(t, "MATCHED_VAR"),
m_variableMatchedVarName(t, "MATCHED_VAR_NAME"),
m_variableMultipartCrlfLFLines(t, "MULTIPART_CRLF_LF_LINES"),
m_variableMultipartDataAfter(t, "MULTIPART_DATA_AFTER"),
m_variableMultipartFileLimitExceeded(t, "MULTIPART_FILE_LIMIT_EXCEEDED"),
m_variableMultipartStrictError(t, "MULTIPART_STRICT_ERROR"),
m_variableMultipartHeaderFolding(t, "MULTIPART_HEADER_FOLDING"),
m_variableMultipartInvalidQuoting(t, "MULTIPART_INVALID_QUOTING"),
m_variableMultipartInvalidHeaderFolding(t, "MULTIPART_INVALID_HEADER_FOLDING"),
m_variableMultipartUnmatchedBoundary(t, "MULTIPART_UNMATCHED_BOUNDARY"),
m_variableOutboundDataError(t, "OUTBOUND_DATA_ERROR"),
m_variablePathInfo(t, "PATH_INFO"),
m_variableQueryString(t, "QUERY_STRING"),
m_variableRemoteAddr(t, "REMOTE_ADDR"),
m_variableRemoteHost(t, "REMOTE_HOST"),
m_variableRemotePort(t, "REMOTE_PORT"),
m_variableReqbodyError(t, "REQBODY_ERROR"),
m_variableReqbodyErrorMsg(t, "REQBODY_ERROR_MSG"),
m_variableReqbodyProcessorErrorMsg(t, "REQBODY_PROCESSOR_ERROR_MSG"),
m_variableReqbodyProcessorError(t, "REQBODY_PROCESSOR_ERROR"),
m_variableReqbodyProcessor(t, "REQBODY_PROCESSOR"),
m_variableRequestBasename(t, "REQUEST_BASENAME"),
m_variableRequestBody(t, "REQUEST_BODY"),
m_variableRequestBodyLength(t, "REQUEST_BODY_LENGTH"),
m_variableRequestFilename(t, "REQUEST_FILENAME"),
m_variableRequestLine(t, "REQUEST_LINE"),
m_variableRequestMethod(t, "REQUEST_METHOD"),
m_variableRequestProtocol(t, "REQUEST_PROTOCOL"),
m_variableRequestURI(t, "REQUEST_URI"),
m_variableRequestURIRaw(t, "REQUEST_URI_RAW"),
m_variableResource(t, "RESOURCE"),
m_variableResponseBody(t, "RESPONSE_BODY"),
m_variableResponseContentLength(t, "RESPONSE_CONTENT_LENGTH"),
m_variableResponseProtocol(t, "RESPONSE_PROTOCOL"),
m_variableResponseStatus(t, "RESPONSE_STATUS"),
m_variableServerAddr(t, "SERVER_ADDR"),
m_variableServerName(t, "SERVER_NAME"),
m_variableServerPort(t, "SERVER_PORT"),
m_variableSessionID(t, "SESSIONID"),
m_variableUniqueID(t, "UNIQUE_ID"),
m_variableUrlEncodedError(t, "URLENCODED_ERROR"),
m_variableUserID(t, "USERID")
{ }
AnchoredVariable m_variableArgsNames;
AnchoredVariable m_variableArgGetNames;
AnchoredVariable m_variableArgPostNames;
AnchoredVariable m_variableRequestHeadersNames;
AnchoredVariable m_variableResponseContentType;
AnchoredVariable m_variableResponseHeadersNames;
AnchoredVariable m_variableARGScombinedSize;
AnchoredVariable m_variableAuthType;
AnchoredVariable m_variableFilesCombinedSize;
AnchoredVariable m_variableFilesTmpNames;
AnchoredVariable m_variableFullRequest;
AnchoredVariable m_variableFullRequestLength;
AnchoredVariable m_variableInboundDataError;
AnchoredVariable m_variableMatchedVar;
AnchoredVariable m_variableMatchedVarName;
AnchoredVariable m_variableMultipartCrlfLFLines;
AnchoredVariable m_variableMultipartDataAfter;
AnchoredVariable m_variableMultipartFileLimitExceeded;
AnchoredVariable m_variableMultipartStrictError;
AnchoredVariable m_variableMultipartHeaderFolding;
AnchoredVariable m_variableMultipartInvalidQuoting;
AnchoredVariable m_variableMultipartInvalidHeaderFolding;
AnchoredVariable m_variableMultipartUnmatchedBoundary;
AnchoredVariable m_variableOutboundDataError;
AnchoredVariable m_variablePathInfo;
AnchoredVariable m_variableQueryString;
AnchoredVariable m_variableRemoteAddr;
AnchoredVariable m_variableRemoteHost;
AnchoredVariable m_variableRemotePort;
AnchoredVariable m_variableReqbodyError;
AnchoredVariable m_variableReqbodyErrorMsg;
AnchoredVariable m_variableReqbodyProcessorError;
AnchoredVariable m_variableReqbodyProcessorErrorMsg;
AnchoredVariable m_variableReqbodyProcessor;
AnchoredVariable m_variableRequestBasename;
AnchoredVariable m_variableRequestBody;
AnchoredVariable m_variableRequestBodyLength;
AnchoredVariable m_variableRequestFilename;
AnchoredVariable m_variableRequestLine;
AnchoredVariable m_variableRequestMethod;
AnchoredVariable m_variableRequestProtocol;
AnchoredVariable m_variableRequestURI;
AnchoredVariable m_variableRequestURIRaw;
AnchoredVariable m_variableResource;
AnchoredVariable m_variableResponseBody;
AnchoredVariable m_variableResponseContentLength;
AnchoredVariable m_variableResponseProtocol;
AnchoredVariable m_variableResponseStatus;
AnchoredVariable m_variableServerAddr;
AnchoredVariable m_variableServerName;
AnchoredVariable m_variableServerPort;
AnchoredVariable m_variableSessionID;
AnchoredVariable m_variableUniqueID;
AnchoredVariable m_variableUrlEncodedError;
AnchoredVariable m_variableUserID;
int m_variableOffset;
};
/** @ingroup ModSecurity_CPP_API */
class Transaction : public TransactionAnchoredVariables {
public:
Transaction(ModSecurity *transaction, Rules *rules, void *logCbData);
~Transaction();
/** TODO: Should be an structure that fits an IP address */
int processConnection(const char *client, int cPort,
const char *server, int sPort);
int processURI(const char *uri, const char *protocol,
const char *http_version);
/**
* Types of request body that ModSecurity may give a special treatment
* for the data.
*/
enum RequestBodyType {
/**
*
*/
UnknownFormat,
/**
*
*/
MultiPartRequestBody,
/**
*
*/
WWWFormUrlEncoded,
/**
*
*/
JSONRequestBody,
/**
*
*/
XMLRequestBody
};
int processRequestHeaders();
int addRequestHeader(const std::string& key, const std::string& value);
int addRequestHeader(const unsigned char *key, const unsigned char *value);
int addRequestHeader(const unsigned char *key, size_t len_key,
const unsigned char *value, size_t len_value);
int processRequestBody();
int appendRequestBody(const unsigned char *body, size_t size);
int requestBodyFromFile(const char *path);
int processResponseHeaders(int code, const std::string& proto);
int addResponseHeader(const std::string& key, const std::string& value);
int addResponseHeader(const unsigned char *key, const unsigned char *value);
int addResponseHeader(const unsigned char *key, size_t len_key,
const unsigned char *value, size_t len_value);
int processResponseBody();
int appendResponseBody(const unsigned char *body, size_t size);
int processLogging();
bool intervention(ModSecurityIntervention *it);
bool addArgument(const std::string& orig, const std::string& key,
const std::string& value, size_t offset);
bool extractArguments(const std::string &orig, const std::string& buf,
size_t offset);
const char *getResponseBody();
int getResponseBodyLenth();
#ifndef NO_LOGS
void debug(int, std::string);
#endif
void serverLog(const std::string& msg);
std::string toJSON(int parts);
std::string toOldAuditLogFormat(int parts, const std::string &trailer);
std::string toOldAuditLogFormatIndex(const std::string &filename,
double size, const std::string &md5);
/**
* Filled during the class instantiation, this variable can be later
* used to fill the SecRule variable `duration'. The variable `duration'
* is dynamic calculated, it is always relative to the value found in
* m_creationTimeStamp.
*
* @note There is space for performance improvement. This value don't
* need to be filled if there is no rule using the variable
* `duration'.
*/
clock_t m_creationTimeStamp;
/**
* Holds the client IP address.
*/
const char *m_clientIpAddress;
/**
* Holds the HTTP version: 1.2, 2.0, 3.0 and so on....
*/
const char *m_httpVersion;
/**
* Holds the request method: GET, POST, HEAD ...
*/
const char *m_method;
/**
* Holds the server IP Address
*/
const char *m_serverIpAddress;
/**
* Holds the raw URI that was requested.
*/
const char *m_uri;
/**
* Holds the URI that was requests (without the query string).
*/
std::string m_uri_no_query_string_decoded;
/**
* Holds the combined size of all arguments, later used to fill the
* variable ARGS_COMBINED_SIZE.
*/
double m_ARGScombinedSizeDouble;
/**
* Client tcp port.
*/
int m_clientPort;
/**
* This variable is set by the action `severity' and later can be
* consulted via the SecLanguage variable HIGHEST_SEVERITY.
*/
int m_highestSeverityAction;
/**
* Holds the HTTP return code when it is known. If 0 nothing was
* set.
*/
int m_httpCodeReturned;
/**
* Holds the server port.
*/
int m_serverPort;
/**
* ModSecurity instance used to start this transaction. Basically used
* to fill the server log whenever is needed.
*/
ModSecurity *m_ms;
/**
* Holds the type of the request body, in case there is one.
*/
RequestBodyType m_requestBodyType;
/**
* Holds the request body "processor"
*/
RequestBodyType m_requestBodyProcessor;
/**
* Rules object utilized during this specific transaction.
*/
Rules *m_rules;
/**
*
*/
std::list<int > m_ruleRemoveById;
/**
*
*/
std::list< std::pair<std::string, std::string> > m_ruleRemoveTargetByTag;
/**
*
*/
std::list< std::pair<int, std::string> > m_ruleRemoveTargetById;
/**
*
*/
int m_requestBodyAccess;
/**
* The list m_auditLogModifier contains modifications to the `auditlogs'
* for this specific request, those modifications can happens via the
* utilization of the action: `ctl:auditLogParts='
*
*/
std::list< std::pair<int, std::string> > m_auditLogModifier;
/**
* This variable holds all the messages asked to be save by the utilization
* of the actions: `log_data' and `msg'. These should be included on the
* auditlogs.
*/
std::list<modsecurity::RuleMessage> m_rulesMessages;
/**
* Holds the request body, in case of any.
*/
std::ostringstream m_requestBody;
/**
* Holds the response body, in case of any.
*/
std::ostringstream m_responseBody;
/**
* Contains the unique ID of the transaction. Use by the variable
* `UNIQUE_ID'. This unique id is also saved as part of the AuditLog.
*/
std::string m_id;
/**
* Holds the SecMarker name that this transaction should wait to perform
* rules evaluation again.
*/
std::string m_marker;
/**
* Holds the amount of rules that should be skipped. If bigger than 0 the
* current rule should be skipped and the number needs to be decreased.
*/
int m_skip_next;
/**
* If allow action was utilized, this variable holds the allow type.
*/
modsecurity::actions::disruptive::AllowType m_allowType;
/**
* Holds the decode URI. Notice that m_uri holds the raw version
* of the URI.
*/
std::string m_uri_decoded;
/**
* Actions (disruptive?) that should be taken by the connector related to
* that transaction.
*/
std::vector<ModSecurityIntervention> m_actions;
ModSecurityIntervention m_it;
/**
* Holds the creation time stamp, using std::time.
*
* TODO: m_timeStamp and m_creationTimeStamp may be merged into a single
* variable.
*/
time_t m_timeStamp;
/**
* Holds all the collections related to that transaction.
*/
collection::Collections m_collections;
/**
* Holds the whatever matched in the operation utilization.
* That variable will be further used by the capture action.
*
*/
std::list<std::string> m_matched;
RequestBodyProcessor::XML *m_xml;
RequestBodyProcessor::JSON *m_json;
std::string m_variableDuration;
std::map<std::string, std::string> m_variableEnvs;
std::string m_variableHighestSeverityAction;
std::string m_variableRemoteUser;
std::string m_variableTime;
std::string m_variableTimeDay;
std::string m_variableTimeEpoch;
std::string m_variableTimeHour;
std::string m_variableTimeMin;
std::string m_variableTimeSec;
std::string m_variableTimeWDay;
std::string m_variableTimeYear;
private:
/**
* Pointer to the callback function that will be called to fill
* the web server (connector) log.
*/
void *m_logCbData;
};
#endif
#ifdef __cplusplus
extern "C" {
#endif
/** @ingroup ModSecurity_C_API */
Transaction *msc_new_transaction(ModSecurity *ms,
Rules *rules, void *logCbData);
/** @ingroup ModSecurity_C_API */
int msc_process_connection(Transaction *transaction,
const char *client, int cPort, const char *server, int sPort);
/** @ingroup ModSecurity_C_API */
int msc_process_request_headers(Transaction *transaction);
/** @ingroup ModSecurity_C_API */
int msc_add_request_header(Transaction *transaction, const unsigned char *key,
const unsigned char *value);
/** @ingroup ModSecurity_C_API */
int msc_add_n_request_header(Transaction *transaction,
const unsigned char *key, size_t len_key, const unsigned char *value,
size_t len_value);
/** @ingroup ModSecurity_C_API */
int msc_process_request_body(Transaction *transaction);
/** @ingroup ModSecurity_C_API */
int msc_append_request_body(Transaction *transaction,
const unsigned char *body, size_t size);
/** @ingroup ModSecurity_C_API */
int msc_request_body_from_file(Transaction *transaction, const char *path);
/** @ingroup ModSecurity_C_API */
int msc_process_response_headers(Transaction *transaction, int code,
const char* protocol);
/** @ingroup ModSecurity_C_API */
int msc_add_response_header(Transaction *transaction,
const unsigned char *key, const unsigned char *value);
/** @ingroup ModSecurity_C_API */
int msc_add_n_response_header(Transaction *transaction,
const unsigned char *key, size_t len_key, const unsigned char *value,
size_t len_value);
/** @ingroup ModSecurity_C_API */
int msc_process_response_body(Transaction *transaction);
/** @ingroup ModSecurity_C_API */
int msc_append_response_body(Transaction *transaction,
const unsigned char *body, size_t size);
/** @ingroup ModSecurity_C_API */
int msc_process_uri(Transaction *transaction, const char *uri,
const char *protocol, const char *http_version);
/** @ingroup ModSecurity_C_API */
const char *msc_get_response_body(Transaction *transaction);
/** @ingroup ModSecurity_C_API */
int msc_get_response_body_length(Transaction *transaction);
/** @ingroup ModSecurity_C_API */
void msc_transaction_cleanup(Transaction *transaction);
/** @ingroup ModSecurity_C_API */
int msc_intervention(Transaction *transaction, ModSecurityIntervention *it);
/** @ingroup ModSecurity_C_API */
int msc_process_logging(Transaction *transaction);
#ifdef __cplusplus
}
} // namespace modsecurity
#endif
#endif // HEADERS_MODSECURITY_TRANSACTION_H_