Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix operator @validateByteRange working with bytes > 127 #1523

Conversation

asterite3
Copy link

ValidateByteRange::evaluate() acquires bytes it validates by indexing std::string:

bool ValidateByteRange::evaluate(Transaction *transaction, Rule *rule,
    const std::string &input, std::shared_ptr<RuleMessage> ruleMessage) {
    bool ret = true;

    size_t count = 0;
    for (int i = 0; i < input.length(); i++) {
        int x = input.at(i);
        if (!(table[x >> 3] & (1 << (x & 0x7)))) {
            ...
            count++;
        }
    }

    ret = (count != 0);
    // ...
    return ret;
}

The problem is that indexing a string gives a char, which is signed, so bytes with codes > 127 will appear as negative integers, resulting in incorrect validation. This can be illustrated by this minimal example:

#include "modsecurity/modsecurity.h"
#include "modsecurity/rules.h"

static void logCb(void *data, const void *ruleMessagev) {
    std::cout << (char *) ruleMessagev << std::endl;
}

const char * req_uri = "/test?param=%D0%A2%D0%B0%D1%80%D0%B0%D0%B1%D0%B0%D0%BD";

int main() {
    modsecurity::ModSecurity *modsec = new modsecurity::ModSecurity();
    modsecurity::Rules *rules = new modsecurity::Rules();
    modsecurity::Transaction * modsecTransaction;

    modsec->setServerLogCb(logCb, modsecurity::TextLogProperty);

    rules->loadFromUri("test.conf");

    modsecTransaction = new modsecurity::Transaction(modsec, rules, NULL);
    modsecTransaction->processURI(req_uri, "GET", "1.1");
    modsecTransaction->processRequestHeaders();
    modsecTransaction->processRequestBody();

    delete modsecTransaction;
    delete rules;
    delete modsec;
    return 0;
}

And this test config:

SecRule REQUEST_URI "@validateByteRange 1-255" \
  id:920270

Even though this config allows any byte except NULL byte, URI in test will fail the check resulting in alert, because bytes \xd0, \xa2 etc are considered negative, hence < 1 and out of range. This breaks rules 920270 , 920271 etc from OWASP CRS (in fact, config including those rules with tx.paranoia_level=2 can be used as test.conf in the above example, the rules will be mistakenly triggered).

The proposed solution is to cast a byte from input string to unsigned char before further processing - bytes will then fall in range [0;255] and be validated as expected. In fact, the same thing is done in v2 (https://github.com/SpiderLabs/ModSecurity/blob/v2/master/apache2/re_operators.c#L4168):

int x = ((unsigned char *)var->value)[i];

ValidateByteRange::evaluate compared bytes with values in
range [0-255], but acquired bytes by indexing std::string,
which gave type char, which is signed. So bytes with values
more than 127 were treated as negative, resulting in being
incorrectly classified as out-of-range. This commit adds
casting byte values to unsigned char before validating range.
@asterite3
Copy link
Author

Also added a unit-test for this case: owasp-modsecurity/secrules-language-tests#3

@zimmerle
Copy link
Contributor

Hi @asterite3,

Well noticed ;) Thanks! Patch is now merged. Sorry for the delay.

@zimmerle zimmerle closed this Aug 20, 2017
zimmerle pushed a commit that referenced this pull request Aug 20, 2017
zimmerle pushed a commit that referenced this pull request Aug 22, 2017
This fix issue #1535. Solution was the same suggested on #1523.
zimmerle pushed a commit that referenced this pull request Aug 22, 2017
This fix issue #1535. Solution was the same suggested on #1523.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants