/** * 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. * */ #include "modsecurity/assay.h" #include #include #include #include #include #include #include #include "modsecurity/modsecurity.h" #include "modsecurity/intervention.h" #include "actions/action.h" #include "src/utils.h" #include "src/audit_log.h" using ModSecurity::actions::Action; namespace ModSecurity { /** * @name Assay * @brief Represents the inspection on an entire request. * * An instance of the Assay class represents an entire request, on its * different phases. * * @note Remember to cleanup the assay when the transaction is complete. * * @param ms ModSecurity core pointer. * @param rules Rules pointer. * * Example Usage: * @code * * using ModSecurity::ModSecurity; * using ModSecurity::Rules; * using ModSecurity::Assay; * * ModSecurity *modsec; * ModSecurity::Rules *rules; * * modsec = new ModSecurity(); * rules = new Rules(); * rules->loadFromUri(rules_file); * * Assay *modsecAssay = new Assay(modsec, rules); * modsecAssay->processConnection("127.0.0.1"); * * if (modsecAssay->intervention()) { * std::cout << "There is an intervention" << std::endl; * } * * ... * * @endcode * */ Assay::Assay(ModSecurity *ms, Rules *rules) : m_ipAddress(NULL), m_uri(NULL), m_rules(rules), save_in_auditlog(false), do_not_save_in_auditlog(false), http_code_returned(200) { m_rules->incrementReferenceCount(); this->debug(4, "Initialising transaction"); } Assay::~Assay() { m_rules->decrementReferenceCount(); } /** * @name debug * @brief Prints a message on the debug logs. * * Debug logs are important during the rules creation phase, this method can be * used to print message on this debug log. * * @param level Debug level, current supported from 0 to 9. * @param message Message to be logged. * */ void Assay::debug(int level, std::string message) { m_rules->debug(level, message); } /** * @name processConnection * @brief Perform the analysis on the connection. * * This method should be called at very beginning of a request process, it is * expected to be executed prior to the virtual host resolution, when the * connection arrives on the server. * * @note Remember to check for a possible intervention. * * @param buf Client's IP address in text format. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::processConnection(const char *ipAddress) { this->m_ipAddress = ipAddress; debug(4, "Transaction context created (blah blah)"); debug(4, "Starting phase CONNECTION. (SecRules 0)"); this->store_variable("REMOTE_ADDR", ipAddress); this->m_rules->evaluate(ModSecurity::ConnectionPhase, this); return true; } /** * @name processURI * @brief Perform the analysis on the URI and all the query string variables. * * This method should be called at very beginning of a request process, it is * expected to be executed prior to the virtual host resolution, when the * connection arrives on the server. * * @note There is no direct connection between this function and any phase of * the SecLanguage's phases. It is something that may occur between the * SecLanguage phase 1 and 2. * @note Remember to check for a possible intervention. * * @param buf Uri. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::processURI(const char *uri) { debug(4, "Starting phase URI. (SecRules 0 + 1/2)"); const char *pos = strchr(uri, '?'); if (pos != NULL && strlen(pos) > 2) { /** * FIXME: * * This is configurable by secrules, we should respect whatever * the secrules said about it. * */ char sep1 = '&'; std::vector key_value = split(pos+1, sep1); for (std::string t : key_value) { /** * FIXME: * * Mimic modsecurity when there are multiple keys with the same name. * */ char sep2 = '='; std::vector key_value = split(t, sep2); store_variable("ARGS:" + key_value[0], key_value[1]); debug(4, "Adding request argument (QUERY_STRING): name \"" + \ key_value[0] + "\", value \"" + key_value[1] + "\""); store_variable("QUERY_STRING:" + key_value[0], key_value[1]); } } return 0; } /** * @name processRequestHeaders * @brief Perform the analysis on the request readers. * * This method perform the analysis on the request headers, notice however * that the headers should be added prior to the execution of this function. * * @note Remember to check for a possible intervention. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::processRequestHeaders() { debug(4, "Starting phase REQUEST_HEADERS. (SecRules 1)"); this->m_rules->evaluate(ModSecurity::RequestHeadersPhase, this); return 0; } /** * @name addRequestHeader * @brief Adds a request header * * With this method it is possible to feed ModSecurity with a request header. * * @note This function expects a NULL terminated string, for both: key and * value. * * @param key header name. * @param value header value. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::addRequestHeader(const std::string& key, const std::string& value) { std::string names = resolve_variable("REQUEST_HEADERS_NAMES"); this->store_variable("REQUEST_HEADERS:" + key, value); if (names.length() > 0) { names = names + " " + key; } else { names = key; } this->store_variable("REQUEST_HEADERS_NAMES", names + " " + key); return 1; } /** * @name addRequestHeader * @brief Adds a request header * * With this method it is possible to feed ModSecurity with a request header. * * @note This function expects a NULL terminated string, for both: key and * value. * * @param key header name. * @param value header value. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::addRequestHeader(const unsigned char *key, const unsigned char *value) { return this->addRequestHeader(key, strlen(reinterpret_cast(key)), value, strlen(reinterpret_cast(value))); } /** * @name addRequestHeader * @brief Adds a request header * * Do not expect a NULL terminated string, instead it expect the string and the * string size, for the value and key. * * @param assay ModSecurity assay. * @param key header name. * @param key_n header name size. * @param value header value. * @param value_n header value size. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ int Assay::addRequestHeader(const unsigned char *key, size_t key_n, const unsigned char *value, size_t value_n) { std::string keys; std::string values; keys.assign(reinterpret_cast(key), key_n); values.assign(reinterpret_cast(value), value_n); return this->addRequestHeader(keys, values); } /** * @name processRequestBody * @brief Perform the request body (if any) * * This method perform the analysis on the request body. It is optional to * call that function. If this API consumer already know that there isn't a * body for inspect it is recommended to skip this step. * * @note It is necessary to "append" the request body prior to the execution * of this function. * @note Remember to check for a possible intervention. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::processRequestBody() { debug(4, "Starting phase REQUEST_BODY. (SecRules 2)"); this->m_rules->evaluate(ModSecurity::RequestBodyPhase, this); return 0; } /** * @name appendRequestBody * @brief Adds request body to be inspected. * * With this method it is possible to feed ModSecurity with data for * inspection regarding the request body. There are two possibilities here: * * 1 - Adds the buffer in a row; * 2 - Adds it in chunks; * * A third option should be developed which is share your application buffer. * In any case, remember that the utilization of this function may reduce your * server throughput, as this buffer creations is computationally expensive. * * @note While feeding ModSecurity remember to keep checking if there is an * intervention, Sec Language has the capability to set the maximum * inspection size which may be reached, and the decision on what to do * in this case is upon the rules. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::appendRequestBody(const unsigned char *buf, size_t len) { /** * as part of the confiurations or rules it is expected to * set a higher value for this. Will not handle it for now. */ this->m_requestBody.write(reinterpret_cast(buf), len); return 0; } /** * @name processResponseHeaders * @brief Perform the analysis on the response readers. * * This method perform the analysis on the response headers, notice however * that the headers should be added prior to the execution of this function. * * @note Remember to check for a possible intervention. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::processResponseHeaders() { debug(4, "Starting phase RESPONSE_HEADERS. (SecRules 3)"); this->m_rules->evaluate(ModSecurity::ResponseHeadersPhase, this); return 0; } /** * @name addResponseHeader * @brief Adds a response header * * With this method it is possible to feed ModSecurity with a response * header. * * @note This method expects a NULL terminated string, for both: key and * value. * * @param key header name. * @param value header value. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::addResponseHeader(const std::string& key, const std::string& value) { std::string names = resolve_variable("RESPONSE_HEADERS_NAMES"); this->store_variable("RESPONSE_HEADERS:" + key, value); if (names.length() > 0) { names = names + " " + key; } else { names = key; } this->store_variable("RESPONSE_HEADERS_NAMES", names + " " + key); return 1; } /** * @name addResponseHeader * @brief Adds a response header * * With this method it is possible to feed ModSecurity with a response * header. * * @note This method expects a NULL terminated string, for both: key and * value. * * @param key header name. * @param value header value. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::addResponseHeader(const unsigned char *key, const unsigned char *value) { return this->addResponseHeader(key, strlen(reinterpret_cast(key)), value, strlen(reinterpret_cast(value))); } /** * @name msc_add_n_response_header * @brief Adds a response header * * Do not expect a NULL terminated string, instead it expect the string and the * string size, for the value and key. * * @param key header name. * @param key_n header name size. * @param value header value. * @param value_n header value size. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::addResponseHeader(const unsigned char *key, size_t key_n, const unsigned char *value, size_t value_n) { std::string keys; std::string values; keys.assign(reinterpret_cast(key), key_n); values.assign(reinterpret_cast(value), value_n); return this->addResponseHeader(keys, values); } /** * @name processResponseBody * @brief Perform the request body (if any) * * This method perform the analysis on the request body. It is optional to * call that method. If this API consumer already know that there isn't a * body for inspect it is recommended to skip this step. * * @note It is necessary to "append" the request body prior to the execution * of this method. * @note Remember to check for a possible intervention. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::processResponseBody() { debug(4, "Starting phase RESPONSE_BODY. (SecRules 4)"); this->m_rules->evaluate(ModSecurity::ResponseBodyPhase, this); return 0; } /** * @name appendResponseBody * @brief Adds reponse body to be inspected. * * With this method it is possible to feed ModSecurity with data for * inspection regarding the response body. ModSecurity can also update the * contents of the response body, this is not quite ready yet on this version * of the API. * * @note If the content is updated, the client cannot receive the content * length header filled, at least not with the old values. Otherwise * unexpected behavior may happens. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::appendResponseBody(const unsigned char *buf, size_t len) { /** * as part of the confiurations or rules it is expected to * set a higher value for this. Will not handle it for now. */ this->m_responseBody.write(reinterpret_cast(buf), len); return 0; } /** * @name getResponseBody * @brief Retrieve a buffer with the updated response body. * * This method is needed to be called whenever ModSecurity update the * contents of the response body, otherwise there is no need to call this * method. * * @return It returns a buffer (const char *) * @retval >0 body was update and available. * @retval NULL Nothing was updated. * */ const char *Assay::getResponseBody() { // int there_is_update = this->rules->loadResponseBodyFromJS(this); return this->m_responseBody.str().c_str(); } /** * @name getResponseBodyLenth * @brief Retrieve the length of the updated response body. * * This method returns the size of the update response body buffer, notice * however, that most likely there isn't an update. Thus, this method will * return 0. * * * @return Size of the update response body. * @retval ==0 there is no update. * @retval >0 the size of the updated buffer. * */ int Assay::getResponseBodyLenth() { int size = 0; #if 0 int there_is_update = this->rules->loadResponseBodyFromJS(this); if (there_is_update == -1) { return -1; } #endif this->m_responseBody.seekp(0, std::ios::end); size = this->m_responseBody.tellp(); return size; } /** * @name processLogging * @brief Logging all information relative to this assay. * * At this point there is not need to hold the connection, the response can be * delivered prior to the execution of this method. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ int Assay::processLogging() { debug(4, "Starting phase LOGGING. (SecRules 5)"); this->m_rules->evaluate(ModSecurity::LoggingPhase, this); return 0; } /** * @name cleanup * @brief Removes all the resources allocated by a given Assay. * * It is mandatory to call this function after every request being finished, * otherwise it may end up in a huge memory leak. * * @returns If the operation was successful or not. * @retval true Operation was successful. * @retval false Operation failed. * */ void Assay::cleanup() { m_responseBody.str(""); m_responseBody.clear(); m_requestBody.str(""); m_requestBody.clear(); delete this; } /** * @name intervention * @brief Check if ModSecurity has anything to ask to the server. * * Intervention can generate a log event and/or perform a disruptive action. * * @return Pointer to ModSecurityIntervention structure * @retval >0 A intervention should be made. * @retval NULL Nothing to be done. * */ ModSecurityIntervention *Assay::intervention() { ModSecurityIntervention *ret = NULL; /** * FIXME: Implement this. * */ if (actions.size() > 0) { for (Action *a : actions) { if (a->action_kind == Action::Kind::RunTimeOnlyIfMatchKind) { if (ret == NULL) { ret = static_cast(calloc( \ 1, sizeof(struct ModSecurityIntervention_t))); ret->status = 200; } a->fill_intervention(ret); } } actions.clear(); } return ret; } void Assay::store_variable(std::string key, std::string value) { this->m_variables_strings[key] = value; } void Assay::store_variable(std::string key, std::unordered_map value) { std::cout << "Storing variable: " << key << ", value is a collection." \ << std::endl; } std::string Assay::resolve_variable(std::string var) { return this->m_variables_strings[var]; } /** * @name msc_new_assay * @brief Create a new assay for a given configuration and ModSecurity core. * * The assay is the unit that will be used the inspect every request. It holds * all the information for a given request. * * @note Remember to cleanup the assay when the transaction is complete. * * @param ms ModSecurity core pointer. * @param rules Rules pointer. * * @return Pointer to Assay structure * @retval >0 Assay structure was initialized correctly * @retval NULL Assay cannot be initialized, either by problems with the rules, * problems with the ModSecurity core or missing memory to * allocate the resources needed by the assay. * */ extern "C" Assay *msc_new_assay(ModSecurity *ms, Rules *rules) { return new Assay(ms, rules); } /** * @name msc_process_connection * @brief Perform the analysis on the connection. * * This function should be called at very beginning of a request process, it is * expected to be executed prior to the virtual host resolution, when the * connection arrives on the server. * * @note Remember to check for a possible intervention. * * @param assay ModSecurity assay. * @param buf Client's IP address in text format. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_process_connection(Assay *assay, const char *buf) { return assay->processConnection(buf); } /** * @name msc_process_uri * @brief Perform the analysis on the URI and all the query string variables. * * This function should be called at very beginning of a request process, it is * expected to be executed prior to the virtual host resolution, when the * connection arrives on the server. * * @note There is no direct connection between this function and any phase of * the SecLanguage's phases. It is something that may occur between the * SecLanguage phase 1 and 2. * @note Remember to check for a possible intervention. * * @param assay ModSecurity assay. * @param buf Uri. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_process_uri(Assay *assay, const char *buf) { return assay->processURI(buf); } /** * @name msc_process_request_headers * @brief Perform the analysis on the request readers. * * This function perform the analysis on the request headers, notice however * that the headers should be added prior to the execution of this function. * * @note Remember to check for a possible intervention. * * @param assay ModSecurity assay. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_process_request_headers(Assay *assay) { return assay->processRequestHeaders(); } /** * @name msc_process_request_body * @brief Perform the request body (if any) * * This function perform the analysis on the request body. It is optional to * call that function. If this API consumer already know that there isn't a * body for inspect it is recommended to skip this step. * * @note It is necessary to "append" the request body prior to the execution * of this function. * @note Remember to check for a possible intervention. * * @param assay ModSecurity assay. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_process_request_body(Assay *assay) { return assay->processRequestBody(); } /** * @name msc_append_request_body * @brief Adds request body to be inspected. * * With this function it is possible to feed ModSecurity with data for * inspection regarding the request body. There are two possibilities here: * * 1 - Adds the buffer in a row; * 2 - Adds it in chunks; * * A third option should be developed which is share your application buffer. * In any case, remember that the utilization of this function may reduce your * server throughput, as this buffer creations is computationally expensive. * * @note While feeding ModSecurity remember to keep checking if there is an * intervention, Sec Language has the capability to set the maximum * inspection size which may be reached, and the decision on what to do * in this case is upon the rules. * * @param assay ModSecurity assay. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_append_request_body(Assay *assay, const unsigned char *buf, size_t len) { return assay->appendRequestBody(buf, len); } /** * @name msc_process_response_headers * @brief Perform the analysis on the response readers. * * This function perform the analysis on the response headers, notice however * that the headers should be added prior to the execution of this function. * * @note Remember to check for a possible intervention. * * @param assay ModSecurity assay. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_process_response_headers(Assay *assay) { return assay->processResponseHeaders(); } /** * @name msc_process_response_body * @brief Perform the request body (if any) * * This function perform the analysis on the request body. It is optional to * call that function. If this API consumer already know that there isn't a * body for inspect it is recommended to skip this step. * * @note It is necessary to "append" the request body prior to the execution * of this function. * @note Remember to check for a possible intervention. * * @param assay ModSecurity assay. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_process_response_body(Assay *assay) { return assay->processResponseBody(); } /** * @name msc_append_response_body * @brief Adds reponse body to be inspected. * * With this function it is possible to feed ModSecurity with data for * inspection regarding the response body. ModSecurity can also update the * contents of the response body, this is not quite ready yet on this version * of the API. * * @note If the content is updated, the client cannot receive the content * length header filled, at least not with the old values. Otherwise * unexpected behavior may happens. * * @param assay ModSecurity assay. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_append_response_body(Assay *assay, const unsigned char *buf, size_t len) { return assay->appendResponseBody(buf, len); } /** * @name msc_add_request_header * @brief Adds a request header * * With this function it is possible to feed ModSecurity with a request header. * * @note This function expects a NULL terminated string, for both: key and * value. * * @param assay ModSecurity assay. * @param key header name. * @param value header value. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_add_request_header(Assay *assay, const unsigned char *key, const unsigned char *value) { return assay->addRequestHeader(key, value); } /** * @name msc_add_n_request_header * @brief Adds a request header * * Same as msc_add_request_header, do not expect a NULL terminated string, * instead it expect the string and the string size, for the value and key. * * @param assay ModSecurity assay. * @param key header name. * @param key_len header name size. * @param value header value. * @param val_len header value size. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_add_n_request_header(Assay *assay, const unsigned char *key, size_t key_len, const unsigned char *value, size_t value_len) { return assay->addRequestHeader(key, key_len, value, value_len); } /** * @name msc_add_response_header * @brief Adds a response header * * With this function it is possible to feed ModSecurity with a response * header. * * @note This function expects a NULL terminated string, for both: key and * value. * * @param assay ModSecurity assay. * @param key header name. * @param value header value. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_add_response_header(Assay *assay, const unsigned char *key, const unsigned char *value) { return assay->addResponseHeader(key, value); } /** * @name msc_add_n_response_header * @brief Adds a response header * * Same as msc_add_response_header, do not expect a NULL terminated string, * instead it expect the string and the string size, for the value and key. * * @param assay ModSecurity assay. * @param key header name. * @param key_len header name size. * @param value header value. * @param val_len header value size. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_add_n_response_header(Assay *assay, const unsigned char *key, size_t key_len, const unsigned char *value, size_t value_len) { return assay->addResponseHeader(key, key_len, value, value_len); } /** * @name msc_assay_cleanup * @brief Removes all the resources allocated by a given Assay. * * It is mandatory to call this function after every request being finished, * otherwise it may end up in a huge memory leak. * * @param assay ModSecurity assay. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" void msc_assay_cleanup(Assay *assay) { assay->cleanup(); } /** * @name msc_intervention * @brief Check if ModSecurity has anything to ask to the server. * * Intervention can generate a log event and/or perform a disruptive action. * * @param assay ModSecurity assay. * * @return Pointer to ModSecurityIntervention structure * @retval >0 A intervention should be made. * @retval NULL Nothing to be done. * */ extern "C" ModSecurityIntervention *msc_intervention(Assay *assay) { return assay->intervention(); } /** * @name msc_get_response_body * @brief Retrieve a buffer with the updated response body. * * This function is needed to be called whenever ModSecurity update the * contents of the response body, otherwise there is no need to call this * function. * * @param assay ModSecurity assay. * * @return It returns a buffer (const char *) * @retval >0 body was update and available. * @retval NULL Nothing was updated. * */ extern "C" const char *msc_get_response_body(Assay *assay) { return assay->getResponseBody(); } /** * @name msc_get_response_body_length * @brief Retrieve the length of the updated response body. * * This function returns the size of the update response body buffer, notice * however, that most likely there isn't an update. Thus, this function will * return 0. * * @param assay ModSecurity assay. * * @return Size of the update response body. * @retval ==0 there is no update. * @retval >0 the size of the updated buffer. * */ extern "C" int msc_get_response_body_length(Assay *assay) { return assay->getResponseBodyLenth(); } /** * @name msc_process_logging * @brief Logging all information relative to this assay. * * At this point there is not need to hold the connection, the response can be * delivered prior to the execution of this function. * * @param assay ModSecurity assay. * * @returns If the operation was successful or not. * @retval 1 Operation was successful. * @retval 0 Operation failed. * */ extern "C" int msc_process_logging(Assay *assay) { return assay->processLogging(); } } // namespace ModSecurity