CVE-2022-23551: Insufficient Information (NVD-CWE-noinfo)
value: aad-pod-identity assigns Azure Active Directory identities to Kubernetes applications and has now been deprecated as of 24 October 2022. The NMI component in AAD Pod Identity intercepts and validates token requests based on regex. In this case, a token request made with backslash in the request (example: `/metadata/identity\oauth2\token/`) would bypass the NMI validation and be sent to IMDS allowing a pod in the cluster to access identities that it shouldn't have access to. This issue has been fixed and has been included in AAD Pod Identity release version 1.8.13. If using the AKS pod-managed identities add-on, no action is required. The clusters should now be running the version 1.8.13 release.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 5.3
Published: 2022-12-21T20:15Z
Repository: https://github.com/Azure/aad-pod-identity
Language: Go
Files changed: pkg/nmi/server/server.go, pkg/nmi/server/server_test.go
Commit date: 2022-10-03 10:29:05 -0700
Commit message: fix: add handler for invalid token requests (#1325)

Signed-off-by: Anish Ramasekar <anish.ramasekar@gmail.com>
Diff: diff --git a/pkg/nmi/server/server.go b/pkg/nmi/server/server.go
index 1c2ecb5..d0646e1 100644
--- a/pkg/nmi/server/server.go
+++ b/pkg/nmi/server/server.go
@@ -38,10 +38,21 @@ const (
 	// "/metadata" portion is case-insensitive in IMDS
 	instancePathPrefix = "/{type:(?i:metadata)}/instance" // #nosec
 	headerRetryAfter   = "Retry-After"
 )
 
+var (
+	// invalidTokenPathMatcher matches the token path that is not supported by IMDS
+	// this handler is configured right after the token path handler to block requests with
+	// invalid token path instead of sending it to IMDS.
+	// we don't have to handle case sensitivity for "/identity/" as that's rejected by IMDS
+	invalidTokenPathMatcher = mux.MatcherFunc(func(req *http.Request, rm *mux.RouteMatch) bool {
+		r := regexp.MustCompile("/(?i:metadata)/identity(.*?)oauth2(.*?)token") // #nosec
+		return r.MatchString(req.URL.Path)
+	})
+)
+
 // Server encapsulates all of the parameters necessary for starting up
 // the server. These can be set via command line.
 type Server struct {
 	KubeClient                         k8s.Client
 	NMIHost                            string
@@ -96,11 +107,19 @@ func NewServer(micNamespace string, blockInstanceMetadata, metadataHeaderRequire
 // Run runs the specified Server.
 func (s *Server) Run() error {
 	go s.updateIPTableRules()
 
 	rtr := mux.NewRouter()
+	// Flow for the request is as follows:
+	// 1. If the request is for token, then it will be handled by tokenHandler post validation.
+	// 2. If the request is for token but the path is invalid, then it will be handled by invalidTokenPathHandler.
+	// 3. If the request is for host token, then it will be handled by hostTokenHandler.
+	// 4. If the request is for instance metadata
+	//    4.1 If blockInstanceMetadata is set to true, then it will be handled by blockInstanceMetadataHandler (deny access to instance metadata).
+	// 5. If the request is for any other path, it will be proxied to IMDS and the response will be returned to the caller.
 	rtr.PathPrefix(tokenPathPrefix).Handler(appHandler(s.msiHandler))
+	rtr.MatcherFunc(invalidTokenPathMatcher).HandlerFunc(invalidTokenPathHandler)
 	rtr.PathPrefix(hostTokenPathPrefix).Handler(appHandler(s.hostHandler))
 	if s.BlockInstanceMetadata {
 		rtr.PathPrefix(instancePathPrefix).HandlerFunc(forbiddenHandler)
 	}
 	rtr.PathPrefix("/").HandlerFunc(s.defaultPathHandler)
@@ -579,10 +598,15 @@ func (s *Server) defaultPathHandler(w http.ResponseWriter, r *http.Request) {
 // forbiddenHandler responds to any request with HTTP 403 Forbidden
 func forbiddenHandler(w http.ResponseWriter, r *http.Request) {
 	http.Error(w, "Request blocked by AAD Pod Identity NMI", http.StatusForbidden)
 }
 
+// invalidTokenPathHandler responds to invalid token requests with HTTP 400 Bad Request
+func invalidTokenPathHandler(w http.ResponseWriter, r *http.Request) {
+	http.Error(w, "Invalid request", http.StatusBadRequest)
+}
+
 func copyHeader(dst, src http.Header) {
 	for k, vv := range src {
 		for _, v := range vv {
 			dst.Add(k, v)
 		}
diff --git a/pkg/nmi/server/server_test.go b/pkg/nmi/server/server_test.go
index 70f43b0..4455f75 100644
--- a/pkg/nmi/server/server_test.go
+++ b/pkg/nmi/server/server_test.go
@@ -215,18 +215,49 @@ func TestRouterPathPrefix(t *testing.T) {
 			name:               "default metadata request",
 			url:                "/metadata/",
 			expectedStatusCode: http.StatusOK,
 			expectedBody:       "default_handler",
 		},
+		{
+			name:               "invalid token request with \\oauth2",
+			url:                `/metadata/identity\oauth2/token/`,
+			expectedStatusCode: http.StatusOK,
+			expectedBody:       "invalid_request_handler",
+		},
+		{
+			name:               "invalid token request with \\token",
+			url:                `/metadata/identity/oauth2\token/`,
+			expectedStatusCode: http.StatusOK,
+			expectedBody:       "invalid_request_handler",
+		},
+		{
+			name:               "invalid token request with \\oauth2\\token",
+			url:                `/metadata/identity\oauth2\token/`,
+			expectedStatusCode: http.StatusOK,
+			expectedBody:       "invalid_request_handler",
+		},
+		{
+			name:               "invalid token request with mix of / and \\",
+			url:                `/metadata/identity/\oauth2\token/`,
+			expectedStatusCode: http.StatusOK,
+			expectedBody:       "invalid_request_handler",
+		},
+		{
+			name:               "invalid token request with multiple \\",
+			url:                `/metadata/identity\\\oauth2\\token/`,
+			expectedStatusCode: http.StatusOK,
+			expectedBody:       "invalid_request_handler",
+		},
 	}
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			setup()
 			defer teardown()
 
 			rtr.PathPrefix(tokenPathPrefix).HandlerFunc(testTokenHandler)
+			rtr.MatcherFunc(invalidTokenPathMatcher).HandlerFunc(testInvalidRequestHandler)
 			rtr.PathPrefix(hostTokenPathPrefix).HandlerFunc(testHostTokenHandler)
 			rtr.PathPrefix(instancePathPrefix).HandlerFunc(testInstanceHandler)
 			rtr.PathPrefix("/").HandlerFunc(testDefaultHandler)
 
 			req, err := http.NewRequest(http.MethodGet, test.url, nil)
@@ -261,5 +292,9 @@ func testInstanceHandler(w http.ResponseWriter, r *http.Request) {
 }
 
 func testDefaultHandler(w http.ResponseWriter, r *http.Request) {
 	fmt.Fprintf(w, "default_handler\n")
 }
+
+func testInvalidRequestHandler(w http.ResponseWriter, r *http.Request) {
+	fmt.Fprintf(w, "invalid_request_handler\n")
+}

Fixed code: var (
	// invalidTokenPathMatcher matches the token path that is not supported by IMDS
	// this handler is configured right after the token path handler to block requests with
	// invalid token path instead of sending it to IMDS.
	// we don't have to handle case sensitivity for "/identity/" as that's rejected by IMDS
	invalidTokenPathMatcher = mux.MatcherFunc(func(req *http.Request, rm *mux.RouteMatch) bool {
		r := regexp.MustCompile("/(?i:metadata)/identity(.*?)oauth2(.*?)token") // #nosec
		return r.MatchString(req.URL.Path)
	})
)

	// Flow for the request is as follows:
	// 1. If the request is for token, then it will be handled by tokenHandler post validation.
	// 2. If the request is for token but the path is invalid, then it will be handled by invalidTokenPathHandler.
	// 3. If the request is for host token, then it will be handled by hostTokenHandler.
	// 4. If the request is for instance metadata
	//    4.1 If blockInstanceMetadata is set to true, then it will be handled by blockInstanceMetadataHandler (deny access to instance metadata).
	// 5. If the request is for any other path, it will be proxied to IMDS and the response will be returned to the caller.
	rtr.MatcherFunc(invalidTokenPathMatcher).HandlerFunc(invalidTokenPathHandler)
// invalidTokenPathHandler responds to invalid token requests with HTTP 400 Bad Request
func invalidTokenPathHandler(w http.ResponseWriter, r *http.Request) {
	http.Error(w, "Invalid request", http.StatusBadRequest)
}

		{
			name:               "invalid token request with \\oauth2",
			url:                `/metadata/identity\oauth2/token/`,
			expectedStatusCode: http.StatusOK,
			expectedBody:       "invalid_request_handler",
		},
		{
			name:               "invalid token request with \\token",
			url:                `/metadata/identity/oauth2\token/`,
			expectedStatusCode: http.StatusOK,
			expectedBody:       "invalid_request_handler",
		},
		{
			name:               "invalid token request with \\oauth2\\token",
			url:                `/metadata/identity\oauth2\token/`,
			expectedStatusCode: http.StatusOK,
			expectedBody:       "invalid_request_handler",
		},
		{
			name:               "invalid token request with mix of / and \\",
			url:                `/metadata/identity/\oauth2\token/`,
			expectedStatusCode: http.StatusOK,
			expectedBody:       "invalid_request_handler",
		},
		{
			name:               "invalid token request with multiple \\",
			url:                `/metadata/identity\\\oauth2\\token/`,
			expectedStatusCode: http.StatusOK,
			expectedBody:       "invalid_request_handler",
		},
			rtr.MatcherFunc(invalidTokenPathMatcher).HandlerFunc(testInvalidRequestHandler)

func testInvalidRequestHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "invalid_request_handler\n")
}