diff --git a/src/actions/transformations/js_decode.cc b/src/actions/transformations/js_decode.cc index d451cdec..a326d039 100644 --- a/src/actions/transformations/js_decode.cc +++ b/src/actions/transformations/js_decode.cc @@ -15,6 +15,8 @@ #include "actions/transformations/js_decode.h" +#include + #include #include #include @@ -24,26 +26,26 @@ #include "modsecurity/assay.h" #include "actions/transformations/transformation.h" +#include "src/utils.h" namespace ModSecurity { namespace actions { namespace transformations { -JsDecode::JsDecode(std::string action) - : Transformation(action) { - this->action_kind = 1; -} std::string JsDecode::evaluate(std::string value, Assay *assay) { - /** - * @todo Implement the transformation JsDecode - */ - assay->debug(4, "Transformation JsDecode is not implemented yet."); - return value; + char *tmp = strdup(value.c_str()); + int res = js_decode_nonstrict_inplace((unsigned char *)tmp, value.size()); + std::string ret(""); + ret.assign(tmp); + free(tmp); + + return ret; } + } // namespace transformations } // namespace actions } // namespace ModSecurity diff --git a/src/actions/transformations/js_decode.h b/src/actions/transformations/js_decode.h index 50423d9c..34e08ea6 100644 --- a/src/actions/transformations/js_decode.h +++ b/src/actions/transformations/js_decode.h @@ -30,7 +30,9 @@ namespace transformations { class JsDecode : public Transformation { public: - explicit JsDecode(std::string action); + explicit JsDecode(std::string action) + : Transformation(action) { } + std::string evaluate(std::string exp, Assay *assay) override; }; diff --git a/src/actions/transformations/transformation.cc b/src/actions/transformations/transformation.cc index 7f2d8d9e..b2d70a26 100644 --- a/src/actions/transformations/transformation.cc +++ b/src/actions/transformations/transformation.cc @@ -65,18 +65,13 @@ namespace ModSecurity { namespace actions { namespace transformations { -Transformation::Transformation(std::string action) - : Action(action) { - this->name = this->action; - this->name.erase(0, 2); - this->action_kind = 1; -} std::string Transformation::evaluate(std::string value, Assay *assay) { return value; } + Transformation* Transformation::instantiate(std::string a) { IF_MATCH(base64_decode_ext) { return new Base64DecodeExt(a); } IF_MATCH(base64_decode) { return new Base64Decode(a); } @@ -87,7 +82,7 @@ Transformation* Transformation::instantiate(std::string a) { IF_MATCH(hex_decode) { return new HexDecode(a); } IF_MATCH(hex_encode) { return new HexEncode(a); } IF_MATCH(html_entity_decode) { return new HtmlEntityDecode(a); } - IF_MATCH(js_decode) { return new JsDecode(a); } + IF_MATCH(jsDecode) { return new JsDecode(a); } IF_MATCH(length) { return new Length(a); } IF_MATCH(lowercase) { return new LowerCase(a); } IF_MATCH(md5) { return new Md5(a); } diff --git a/src/actions/transformations/transformation.h b/src/actions/transformations/transformation.h index 6cef77e1..f9d9ef24 100644 --- a/src/actions/transformations/transformation.h +++ b/src/actions/transformations/transformation.h @@ -30,7 +30,12 @@ namespace transformations { class Transformation : public Action { public: - explicit Transformation(std::string action); + explicit Transformation(const std::string& _action) + : Action(_action, RunTimeBeforeMatchAttemptKind) { } + + explicit Transformation(const std::string& _action, int kind) + : Action(_action, kind) { } + static Transformation* instantiate(std::string); std::string evaluate(std::string exp, diff --git a/src/utils.cc b/src/utils.cc index 1251a140..2f2abfd4 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -20,6 +20,11 @@ #include #include + +#include +#include + + #include #include #include @@ -37,8 +42,13 @@ #include "modsecurity/modsecurity.h" +#define VALID_HEX(X) (((X >= '0') && (X <= '9')) || \ + ((X >= 'a') && (X <= 'f')) || ((X >= 'A') && (X <= 'F'))) +#define ISODIGIT(X) ((X >= '0') && (X <= '7')) + namespace ModSecurity { + std::vector split(std::string str, char delimiter) { std::vector internal; std::stringstream ss(str); // Turn the string into a stream. @@ -209,5 +219,125 @@ double cpu_seconds(void) { } +int js_decode_nonstrict_inplace(unsigned char *input, int64_t input_len) { + unsigned char *d = (unsigned char *)input; + int64_t i, count; + + if (input == NULL) return -1; + + i = count = 0; + while (i < input_len) { + if (input[i] == '\\') { + /* Character is an escape. */ + + if ((i + 5 < input_len) && (input[i + 1] == 'u') + && (VALID_HEX(input[i + 2])) && (VALID_HEX(input[i + 3])) + && (VALID_HEX(input[i + 4])) && (VALID_HEX(input[i + 5]))) { + /* \uHHHH */ + + /* Use only the lower byte. */ + *d = x2c(&input[i + 4]); + + /* Full width ASCII (ff01 - ff5e) needs 0x20 added */ + if ((*d > 0x00) && (*d < 0x5f) + && ((input[i + 2] == 'f') || (input[i + 2] == 'F')) + && ((input[i + 3] == 'f') || (input[i + 3] == 'F'))) { + (*d) += 0x20; + } + + d++; + count++; + i += 6; + } else if ((i + 3 < input_len) && (input[i + 1] == 'x') + && VALID_HEX(input[i + 2]) && VALID_HEX(input[i + 3])) { + /* \xHH */ + *d++ = x2c(&input[i + 2]); + count++; + i += 4; + } else if ((i + 1 < input_len) && ISODIGIT(input[i + 1])) { + /* \OOO (only one byte, \000 - \377) */ + char buf[4]; + int j = 0; + + while ((i + 1 + j < input_len) && (j < 3)) { + buf[j] = input[i + 1 + j]; + j++; + if (!ISODIGIT(input[i + 1 + j])) break; + } + buf[j] = '\0'; + + if (j > 0) { + /* Do not use 3 characters if we will be > 1 byte */ + if ((j == 3) && (buf[0] > '3')) { + j = 2; + buf[j] = '\0'; + } + *d++ = (unsigned char)strtol(buf, NULL, 8); + i += 1 + j; + count++; + } + } else if (i + 1 < input_len) { + /* \C */ + unsigned char c = input[i + 1]; + switch (input[i + 1]) { + case 'a' : + c = '\a'; + break; + case 'b' : + c = '\b'; + break; + case 'f' : + c = '\f'; + break; + case 'n' : + c = '\n'; + break; + case 'r' : + c = '\r'; + break; + case 't' : + c = '\t'; + break; + case 'v' : + c = '\v'; + break; + /* The remaining (\?,\\,\',\") are just a removal + * of the escape char which is default. + */ + } + + *d++ = c; + i += 2; + count++; + } else { + /* Not enough bytes */ + while (i < input_len) { + *d++ = input[i++]; + count++; + } + } + } else { + *d++ = input[i++]; + count++; + } + } + + *d = '\0'; + + return count; +} + + +static unsigned char x2c(unsigned char *what) { + register unsigned char digit; + + digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0')); + digit *= 16; + digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0')); + + return digit; +} + + } // namespace ModSecurity diff --git a/src/utils.h b/src/utils.h index c58c0e19..d8335ebb 100644 --- a/src/utils.h +++ b/src/utils.h @@ -33,6 +33,8 @@ namespace ModSecurity { std::string uri_decode(const std::string & sSrc); std::string tolower(std::string str); double cpu_seconds(void); + int js_decode_nonstrict_inplace(unsigned char *input, int64_t input_len); + static unsigned char x2c(unsigned char *what); } // namespace ModSecurity #define SRC_UTILS_H_ diff --git a/test/test-cases/secrules-language-tests b/test/test-cases/secrules-language-tests index 859fc906..35f10894 160000 --- a/test/test-cases/secrules-language-tests +++ b/test/test-cases/secrules-language-tests @@ -1 +1 @@ -Subproject commit 859fc906634b1e169863272f1a80ee91a86dea9d +Subproject commit 35f108949b1e54c75e95817836e2ddaba227be75 diff --git a/test/unit/unit.cc b/test/unit/unit.cc index e776f1fb..2c97b8ba 100644 --- a/test/unit/unit.cc +++ b/test/unit/unit.cc @@ -22,6 +22,7 @@ #include "modsecurity/modsecurity.h" #include "modsecurity/rules.h" #include "operators/operator.h" +#include "actions/transformations/transformation.h" #include "common/modsecurity_test.h" #include "common/modsecurity_test_results.h" @@ -32,6 +33,8 @@ using modsecurity_test::UnitTest; using modsecurity_test::ModSecurityTest; using modsecurity_test::ModSecurityTestResults; +using ModSecurity::actions::transformations::Transformation; +using ModSecurity::operators::Operator; std::string default_test_path = "test-cases/secrules-language-tests/operators"; @@ -55,17 +58,35 @@ void print_help() { void perform_unit_test(UnitTest *t, ModSecurityTestResults* res) { const char *error = NULL; - ModSecurity::operators::Operator *op = - ModSecurity::operators::Operator::instantiate("\"@" + t->name + \ - " " + t->param + "\""); - op->init(&error); + int ret = 0; - int ret = op->evaluate(NULL, t->input); - if (ret != t->ret) { - t->obtained = ret; - res->push_back(t); + if (t->type == "op") { + Operator *op = Operator::instantiate("\"@" + t->name + \ + " " + t->param + "\""); + op->init(&error); + ret = op->evaluate(NULL, t->input); + if (ret != t->ret) { + t->obtained = ret; + res->push_back(t); + } + + delete op; + } else if (t->type == "tfn") { + Transformation *tfn = Transformation::instantiate("t:" + t->name); + std::string ret = tfn->evaluate(t->input, NULL); + t->obtained = 1; + if (ret != t->output) { + std::cout << "ret: !" << ret << "!" << std::endl; + std::cout << "obt: !" << t->output << "!" << std::endl; + t->obtainedOutput = ret; + res->push_back(t); + } + + delete tfn; + } else { + std::cerr << "Failed. Test type is unknown: << " << t->type; + std::cerr << std::endl; } - delete op; } diff --git a/test/unit/unit_test.cc b/test/unit/unit_test.cc index b0413cb7..7775fc1b 100644 --- a/test/unit/unit_test.cc +++ b/test/unit/unit_test.cc @@ -35,9 +35,16 @@ std::string UnitTest::print() { i << " \"name\": \"" << this->name << "\"" << std::endl; i << " \"input\": \"" << this->input << "\"" << std::endl; i << " \"param\": \"" << this->param << "\"" << std::endl; + i << " \"output\": \"" << this->output << "\"" << std::endl; i << "}" << std::endl; - i << "Expecting: " << this->ret << " - operator returned: "; - i << this->obtained << std::endl; + if (this->ret != this->obtained) { + i << "Expecting: " << this->ret << " - operator returned: "; + i << this->obtained << std::endl; + } + if (this->output != this->obtainedOutput) { + i << "Expecting: " << this->output << " - operator returned: "; + i << this->obtainedOutput << std::endl; + } return i.str(); } @@ -62,6 +69,8 @@ UnitTest *UnitTest::from_yajl_node(yajl_val &node) { u->type = YAJL_GET_STRING(val); } else if (strcmp(key, "ret") == 0) { u->ret = YAJL_GET_INTEGER(val); + } else if (strcmp(key, "output") == 0) { + u->output = YAJL_GET_STRING(val); } } diff --git a/test/unit/unit_test.h b/test/unit/unit_test.h index d1f058e5..9fa2338d 100644 --- a/test/unit/unit_test.h +++ b/test/unit/unit_test.h @@ -36,8 +36,10 @@ class UnitTest { std::string name; std::string type; std::string filename; + std::string output; int ret; int obtained; + std::string obtainedOutput; }; } // namespace modsecurity_test