Skip to content

Commit 859552a

Browse files
Neeraj Poddaristio-testing
authored andcommitted
Cherry-pick JWT CVE fix into 1.4 (istio#12)
* Fixed JWT CVE related to exact PATH matches (istio#9) * Fixed JWT CVE related to exact PATH matches Problem: The JWT filter when matching exact paths included query parameters which meant the JWT requirement could be bypassed by adding a "?" after the path. The API was intended to only work for URIs. Solution: The fix updates the match logic to only include URIs i.e. path stripped off the query section. Added unit tests to validate these cases. * Fixed formatting * Strip fragment of Path Added unit tests to validate combination of query & fragment * Fix lint * Minor refactoring and more unit test cases (istio#11) * Minor refactoring and more unit test cases * Lint fixes
1 parent 5f5d621 commit 859552a

File tree

2 files changed

+73
-8
lines changed

2 files changed

+73
-8
lines changed

src/envoy/http/authn/origin_authenticator.cc

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "absl/strings/match.h"
1919
#include "authentication/v1alpha1/policy.pb.h"
2020
#include "common/http/headers.h"
21+
#include "common/http/utility.h"
2122
#include "src/envoy/http/authn/authn_utils.h"
2223

2324
using istio::authn::Payload;
@@ -64,11 +65,16 @@ bool OriginAuthenticator::run(Payload* payload) {
6465
return true;
6566
}
6667

67-
absl::string_view request_path;
68+
absl::string_view path;
6869
if (filter_context()->headerMap().Path() != nullptr) {
69-
request_path =
70-
filter_context()->headerMap().Path()->value().getStringView();
71-
ENVOY_LOG(debug, "Got request path {}", request_path);
70+
path = filter_context()->headerMap().Path()->value().getStringView();
71+
72+
// Trim query parameters and/or fragment if present
73+
size_t offset = path.find_first_of("?#");
74+
if (offset != absl::string_view::npos) {
75+
path.remove_suffix(path.length() - offset);
76+
}
77+
ENVOY_LOG(trace, "Got request path {}", path);
7278
} else {
7379
ENVOY_LOG(error,
7480
"Failed to get request path, JWT will always be used for "
@@ -80,8 +86,8 @@ bool OriginAuthenticator::run(Payload* payload) {
8086
for (const auto& method : policy_.origins()) {
8187
const auto& jwt = method.jwt();
8288

83-
if (AuthnUtils::ShouldValidateJwtPerPath(request_path, jwt)) {
84-
ENVOY_LOG(debug, "Validating request path {} for jwt {}", request_path,
89+
if (AuthnUtils::ShouldValidateJwtPerPath(path, jwt)) {
90+
ENVOY_LOG(debug, "Validating request path {} for jwt {}", path,
8591
jwt.DebugString());
8692
// set triggered to true if any of the jwt trigger rule matched.
8793
triggered = true;

src/envoy/http/authn/origin_authenticator_test.cc

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,20 @@ const char kSingleOriginMethodWithTriggerRulePolicy[] = R"(
101101
}
102102
)";
103103

104+
const char kSingleOriginMethodWithExcludeTriggerRulePolicy[] = R"(
105+
principal_binding: USE_ORIGIN
106+
origins {
107+
jwt {
108+
issuer: "abc.xyz"
109+
trigger_rules: {
110+
excluded_paths: {
111+
exact: "/login"
112+
}
113+
}
114+
}
115+
}
116+
)";
117+
104118
const char kMultipleOriginMethodWithTriggerRulePolicy[] = R"(
105119
principal_binding: USE_ORIGIN
106120
origins {
@@ -327,15 +341,60 @@ TEST_P(OriginAuthenticatorTest, SingleRuleTriggered) {
327341
filter_context_.authenticationResult()));
328342
}
329343

344+
TEST_P(OriginAuthenticatorTest, SingleRuleTriggeredWithComponents) {
345+
const std::vector<std::string> input_paths{"/allow?",
346+
"/allow?a=b&c=d",
347+
"/allow??",
348+
"/allow??",
349+
"/allow?#",
350+
"/allow#?",
351+
"/allow#a",
352+
"/allow#$"
353+
"/allow?a=b#c",
354+
"/allow#a?b=c"};
355+
for (const auto& path : input_paths) {
356+
ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(
357+
kSingleOriginMethodWithTriggerRulePolicy, &policy_));
358+
359+
createAuthenticator();
360+
361+
EXPECT_CALL(*authenticator_, validateJwt(_, _))
362+
.Times(1)
363+
.WillOnce(DoAll(SetArgPointee<1>(jwt_payload_), Return(true)));
364+
365+
setPath(path);
366+
EXPECT_TRUE(authenticator_->run(payload_));
367+
EXPECT_TRUE(TestUtility::protoEqual(
368+
expected_result_when_pass_, filter_context_.authenticationResult()));
369+
}
370+
}
371+
330372
TEST_P(OriginAuthenticatorTest, SingleRuleNotTriggered) {
373+
const std::vector<std::string> input_paths{"/bad", "/allow$?", "/allow$#"};
374+
for (const auto& path : input_paths) {
375+
ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(
376+
kSingleOriginMethodWithTriggerRulePolicy, &policy_));
377+
378+
createAuthenticator();
379+
380+
EXPECT_CALL(*authenticator_, validateJwt(_, _)).Times(0);
381+
382+
setPath(path);
383+
EXPECT_TRUE(authenticator_->run(payload_));
384+
EXPECT_TRUE(TestUtility::protoEqual(
385+
initial_result_, filter_context_.authenticationResult()));
386+
}
387+
}
388+
389+
TEST_P(OriginAuthenticatorTest, SingleExcludeRuleTriggeredWithQueryParam) {
331390
ASSERT_TRUE(Protobuf::TextFormat::ParseFromString(
332-
kSingleOriginMethodWithTriggerRulePolicy, &policy_));
391+
kSingleOriginMethodWithExcludeTriggerRulePolicy, &policy_));
333392

334393
createAuthenticator();
335394

336395
EXPECT_CALL(*authenticator_, validateJwt(_, _)).Times(0);
337396

338-
setPath("/bad");
397+
setPath("/login?a=b&c=d");
339398
EXPECT_TRUE(authenticator_->run(payload_));
340399
EXPECT_TRUE(TestUtility::protoEqual(initial_result_,
341400
filter_context_.authenticationResult()));

0 commit comments

Comments
 (0)