#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string_view>
#include <variant>
#include "condition/base.hpp"
#include "dynamic_string.hpp"
#include "log.hpp"
#include "obfuscator.hpp"
#include "re2.h"
namespace ddwaf {
namespace {
bool match(re2::RE2 ®ex, std::string_view value)
{
const std::string_view key_ref(value.data(), value.size());
return regex.Match(key_ref, 0, value.size(), re2::RE2::UNANCHORED, nullptr, 0);
}
bool load_regex(std::string_view regex_str, std::unique_ptr<re2::RE2> ®ex)
{
if (regex_str.empty()) {
return true;
}
re2::RE2::Options options;
options.set_max_mem(static_cast<int64_t>(512 * 1024));
options.set_log_errors(false);
options.set_case_sensitive(false);
const std::string_view sp(regex_str.data(), regex_str.size());
regex = std::make_unique<re2::RE2>(sp, options);
return regex->ok();
}
template <std::size_t N>
bool find_and_consume(
std::string_view *input, re2::RE2 ®ex, std::array<std::string_view, N> &array)
{
return std::apply(
[input, ®ex](
auto &...elements) { return re2::RE2::FindAndConsume(input, regex, &elements...); },
array);
}
}
match_obfuscator::match_obfuscator(std::string_view key_regex_str, std::string_view value_regex_str)
{
if (!load_regex(key_regex_str, key_regex_)) {
DDWAF_ERROR("invalid obfuscator key regex: {} - using default", key_regex_->error_arg());
load_regex(default_key_regex_str, key_regex_);
}
bool value_regex_is_default = (value_regex_str == default_value_regex_str);
if (!load_regex(value_regex_str, value_regex_)) {
DDWAF_ERROR(
"invalid obfuscator value regex: {} - using default", value_regex_->error_arg());
load_regex(default_value_regex_str, value_regex_);
value_regex_is_default = true;
}
if (value_regex_ && value_regex_->ok()) {
partial_value_obfuscation_ = value_regex_is_default;
}
}
void match_obfuscator::obfuscate_match(condition_match &match) const
{
bool redact_highlight = false;
for (auto &arg : match.args) {
bool sensitive_key = false;
for (const auto &key : arg.key_path) {
if (std::holds_alternative<std::string_view>(key) &&
is_sensitive_key(std::get<std::string_view>(key))) {
sensitive_key = true;
break;
}
}
if (sensitive_key) {
redact_highlight = (arg.name == "params" || arg.name == "input");
arg.resolved = redaction_msg;
} else {
if (obfuscate_value(arg.resolved)) {
redact_highlight = (arg.name == "params" || arg.name == "input");
}
}
}
if (redact_highlight) {
for (auto &highlight : match.highlights) { highlight = redaction_msg; }
}
}
bool match_obfuscator::is_sensitive_key(std::string_view key) const
{
return (key_regex_ && key_regex_->ok()) ? match(*key_regex_, key) : false;
}
bool match_obfuscator::is_sensitive_value(std::string_view value) const
{
return (value_regex_ && value_regex_->ok()) ? match(*value_regex_, value) : false;
}
bool match_obfuscator::obfuscate_value(dynamic_string &value) const
{
if (!partial_value_obfuscation_) {
if (is_sensitive_value(value)) {
value = redaction_msg;
return true;
}
return false;
}
static constexpr std::size_t capturing_groups = 8;
assert(value_regex_->NumberOfCapturingGroups() == capturing_groups);
dynamic_string output{value.size()};
std::string_view input{value.data(), value.size()};
const auto *read = input.data();
while (!input.empty()) {
std::array<std::string_view, capturing_groups> matches;
if (!find_and_consume(&input, *value_regex_, matches)) {
break;
}
for (auto &match : matches) {
if (!match.empty()) {
output.append({read, static_cast<std::size_t>(match.data() - read)});
output.append(redaction_msg);
read = match.data() + match.size();
break;
}
}
}
if (!output.empty()) {
output.append({read, static_cast<std::size_t>(value.data() + value.size() - read)});
value = output;
}
return !output.empty();
}
}