Skip to content

Commit

Permalink
New waf features (#55)
Browse files Browse the repository at this point in the history
* support ruleset_info

* support obfuscator config

* zero init ddwaf_config

* use DefineProperty instead of Set for rulesInfo
  • Loading branch information
simon-id authored Apr 20, 2022
1 parent 674c99e commit eea0de0
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 4 deletions.
11 changes: 10 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,16 @@ export class DDWAF {

readonly disposed: boolean;

constructor(rules: rules);
readonly rulesInfo: {
version?: string,
loaded: number,
failed: number
};

constructor(rules: rules, config?: {
obfuscatorKeyRegex?: string,
obfuscatorValueRegex?: string
});

createContext(): DDWAFContext;
dispose(): void;
Expand Down
61 changes: 58 additions & 3 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Napi::Object DDWAF::Init(Napi::Env env, Napi::Object exports) {
InstanceMethod<&DDWAF::createContext>("createContext"),
InstanceMethod<&DDWAF::dispose>("dispose"),
InstanceAccessor("disposed", &DDWAF::GetDisposed, nullptr, napi_enumerable),
// TODO(simon-id): should we have an InstanceValue for rulesInfo here ?
});
exports.Set("DDWAF", func);
return exports;
Expand All @@ -43,20 +44,74 @@ Napi::Value DDWAF::GetDisposed(const Napi::CallbackInfo& info) {

DDWAF::DDWAF(const Napi::CallbackInfo& info) : Napi::ObjectWrap<DDWAF>(info) {
Napi::Env env = info.Env();
if (info.Length() < 1) {
Napi::Error::New(env, "Wrong number of arguments, expected 1").ThrowAsJavaScriptException();
size_t arg_len = info.Length();
if (arg_len < 1) {
Napi::Error::New(env, "Wrong number of arguments, expected at least 1").ThrowAsJavaScriptException();
return;
}
if (!info[0].IsObject()) {
Napi::TypeError::New(env, "First argument must be an object").ThrowAsJavaScriptException();
return;
}

ddwaf_config waf_config{{0, 0, 0}, {nullptr, nullptr}};

if (arg_len >= 2) {
// TODO(@simon-id) make a macro here someday
if (!info[1].IsObject()) {
Napi::TypeError::New(env, "Second argument must be an object").ThrowAsJavaScriptException();
return;
}

Napi::Object config = info[1].ToObject();

if (config.Has("obfuscatorKeyRegex")) {
Napi::Value key_regex = config.Get("obfuscatorKeyRegex");

if (!key_regex.IsString()) {
Napi::TypeError::New(env, "obfuscatorKeyRegex must be a string").ThrowAsJavaScriptException();
return;
}

waf_config.obfuscator.key_regex = key_regex.ToString().Utf8Value().c_str();
}

if (config.Has("obfuscatorValueRegex")) {
Napi::Value value_regex = config.Get("obfuscatorValueRegex");

if (!value_regex.IsString()) {
Napi::TypeError::New(env, "obfuscatorValueRegex must be a string").ThrowAsJavaScriptException();
return;
}

waf_config.obfuscator.value_regex = value_regex.ToString().Utf8Value().c_str();
}
}

ddwaf_object rules;
mlog("building rules");
to_ddwaf_object(&rules, env, info[0], 0, false);

ddwaf_ruleset_info rules_info;

mlog("Init WAF");
ddwaf_handle handle = ddwaf_init(&rules, nullptr, nullptr);
ddwaf_handle handle = ddwaf_init(&rules, &waf_config, &rules_info);
ddwaf_object_free(&rules);

Napi::Object result = Napi::Object::New(env);

if (rules_info.version != nullptr) {
result.Set("version", Napi::String::New(env, rules_info.version));
}
result.Set("loaded", Napi::Number::New(env, rules_info.loaded));
result.Set("failed", Napi::Number::New(env, rules_info.failed));

Napi::PropertyDescriptor pd = Napi::PropertyDescriptor::Value("rulesInfo", result, napi_enumerable);

info.This().As<Napi::Object>().DefineProperty(pd);

ddwaf_ruleset_info_free(&rules_info);

if (handle == nullptr) {
Napi::Error::New(env, "Invalid rules").ThrowAsJavaScriptException();
return;
Expand Down
46 changes: 46 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ describe('DDWAF lifecycle', () => {
assert.strictEqual([v.major, v.minor, v.patch].join('.'), pkg.libddwaf_version)
})

it('should have rulesInfo', () => {
const waf = new DDWAF(rules)
assert(waf.rulesInfo)
assert.strictEqual(waf.rulesInfo.version, '1.3.1')
assert.strictEqual(waf.rulesInfo.loaded, 6)
assert.strictEqual(waf.rulesInfo.failed, 0)
})

it('should collect an attack and cleanup everything', () => {
const waf = new DDWAF(rules)
const context = waf.createContext()
Expand Down Expand Up @@ -144,6 +152,44 @@ describe('DDWAF lifecycle', () => {
assert.strictEqual(JSON.parse(result.data)[0].rule_matches[0].parameters[0].value, 'kattack')
}
})

it('should obfuscate keys', () => {
const waf = new DDWAF(rules, {
obfuscatorKeyRegex: 'password'
})
const context = waf.createContext()

const result = context.run({
atk: {
password: {
a: 'sensitive'
}
}
}, TIMEOUT)

assert(result)
assert(result.data)
assert.strictEqual(JSON.parse(result.data)[0].rule_matches[0].parameters[0].value, '<Redacted>')
assert.strictEqual(JSON.parse(result.data)[0].rule_matches[0].parameters[0].highlight[0], '<Redacted>')
})

it('should obfuscate values', () => {
const waf = new DDWAF(rules, {
obfuscatorValueRegex: 'hello world'
})
const context = waf.createContext()

const result = context.run({
'server.request.headers.no_cookies': {
header: 'hello world'
}
}, TIMEOUT)

assert(result)
assert(result.data)
assert.strictEqual(JSON.parse(result.data)[0].rule_matches[0].parameters[0].value, '<Redacted>')
assert.strictEqual(JSON.parse(result.data)[0].rule_matches[0].parameters[0].highlight[0], '<Redacted>')
})
})

describe('limit tests', () => {
Expand Down

0 comments on commit eea0de0

Please sign in to comment.