1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
// Copyright Epic Games, Inc. All Rights Reserved.
#include "zenhttp/security/passwordsecurityfilter.h"
#include <zencore/base64.h>
#include <zencore/compactbinaryutil.h>
#include <zencore/fmtutils.h>
namespace zen {
using namespace std::literals;
PasswordHttpFilter::Configuration
PasswordHttpFilter::ReadConfiguration(CbObjectView Config)
{
Configuration Result;
if (CbObjectView PasswordType = Config["basic"sv].AsObjectView(); PasswordType)
{
Result.AuthenticationTypeString = "Basic ";
std::string_view Username = PasswordType["username"sv].AsString();
std::string_view Password = PasswordType["password"sv].AsString();
std::string UsernamePassword = fmt::format("{}:{}", Username, Password);
Result.PasswordConfig.Password.resize(Base64::GetEncodedDataSize(uint32_t(UsernamePassword.length())));
Base64::Encode(reinterpret_cast<const uint8_t*>(UsernamePassword.data()),
uint32_t(UsernamePassword.size()),
const_cast<char*>(Result.PasswordConfig.Password.data()));
}
Result.PasswordConfig.ProtectMachineLocalRequests = Config["protect-machine-local-requests"sv].AsBool();
Result.PasswordConfig.UnprotectedUris = compactbinary_helpers::ReadArray<std::string>("unprotected-uris"sv, Config);
return Result;
}
IHttpRequestFilter::Result
PasswordHttpFilter::FilterRequest(HttpServerRequest& Request)
{
std::string_view Password;
std::string_view AuthorizationHeader = Request.GetAuthorizationHeader();
// Look for the configured scheme prefix (e.g. "Basic ") case-insensitively.
// Only extract a candidate credential when the scheme prefix matches and the
// remainder passes two defense-in-depth checks:
// * trim any trailing LWS (SP / HTAB) from the credential, so a
// well-behaved but "Authorization: Basic <b64> " style header still
// works instead of producing a silent 403;
// * reject the header outright if the credential contains any control
// byte (< 0x20) or DEL (0x7F) — valid Base64 never does, and passing
// such bytes downstream is a log-injection / smuggling vector.
if (AuthorizationHeader.length() > m_AuthenticationTypeString.length() &&
StrCaseCompare(AuthorizationHeader.data(), m_AuthenticationTypeString.c_str(), m_AuthenticationTypeString.length()) == 0)
{
std::string_view Candidate = AuthorizationHeader.substr(m_AuthenticationTypeString.length());
while (!Candidate.empty() && (Candidate.back() == ' ' || Candidate.back() == '\t'))
{
Candidate.remove_suffix(1);
}
bool HasControlByte = false;
for (char C : Candidate)
{
const uint8_t B = static_cast<uint8_t>(C);
if (B < 0x20 || B == 0x7F)
{
HasControlByte = true;
break;
}
}
if (!HasControlByte)
{
Password = Candidate;
}
}
bool IsAllowed =
m_PasswordSecurity.IsAllowed(Password, Request.Service().BaseUri(), Request.RelativeUri(), Request.IsLocalMachineRequest());
if (IsAllowed)
{
return Result::Accepted;
}
return Result::Forbidden;
}
} // namespace zen
|