From 936d9f2c318cbf9be8d943014a32242feba9cfaa Mon Sep 17 00:00:00 2001 From: Chris Arcand Date: Thu, 19 Mar 2026 14:12:32 -0500 Subject: [PATCH] Harden STS endpoint parsing in AWS auth Replace permissive STS endpoint matching with strict HTTPS host validation so malformed inputs are rejected without regex backtracking risk. --- lib/vault/api/auth.rb | 23 +++++++++++++++++++---- spec/unit/auth_spec.rb | 30 ++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/lib/vault/api/auth.rb b/lib/vault/api/auth.rb index 0069aec..51f571e 100644 --- a/lib/vault/api/auth.rb +++ b/lib/vault/api/auth.rb @@ -2,6 +2,7 @@ # SPDX-License-Identifier: MPL-2.0 require "json" +require "uri" require_relative "secret" require_relative "../client" @@ -302,7 +303,7 @@ def gcp(role, jwt, path = 'gcp') # The path to the auth backend to use for the login procedure. # # @param [String] name optional - # The named certificate role provided to the login request. + # The named certificate role provided to the login request. # # @return [Secret] def tls(pem = nil, path = 'cert', name: nil) @@ -328,9 +329,23 @@ def tls(pem = nil, path = 'cert', name: nil) # # @return [String] aws region def region_from_sts_endpoint(sts_endpoint) - valid_sts_endpoint = %r{https:\/\/sts\.?(.*)\.amazonaws\.com}.match(sts_endpoint) - raise "Unable to parse STS endpoint #{sts_endpoint}" unless valid_sts_endpoint - valid_sts_endpoint[1].empty? ? 'us-east-1' : valid_sts_endpoint[1] + uri = URI.parse(sts_endpoint) + + unless uri.is_a?(URI::HTTPS) && uri.userinfo.nil? + raise "Unable to parse STS endpoint #{sts_endpoint}" + end + + case uri.host + when "sts.amazonaws.com" + "us-east-1" + when /\Asts\.([a-z0-9-]+)\.amazonaws\.com\z/, + /\Asts\.([a-z0-9-]+)\.amazonaws\.com\.cn\z/ + Regexp.last_match(1) + else + raise "Unable to parse STS endpoint #{sts_endpoint}" + end + rescue URI::InvalidURIError + raise "Unable to parse STS endpoint #{sts_endpoint}" end end end diff --git a/spec/unit/auth_spec.rb b/spec/unit/auth_spec.rb index 2c0f4a8..65eabb6 100644 --- a/spec/unit/auth_spec.rb +++ b/spec/unit/auth_spec.rb @@ -19,6 +19,11 @@ module Vault it { is_expected.to eq 'us-gov-west-1' } end + context 'with a standard regional endpoint' do + let(:sts_endpoint) { "https://sts.us-west-2.amazonaws.com" } + it { is_expected.to eq 'us-west-2' } + end + context 'with no regional endpoint' do let(:sts_endpoint) { "https://sts.amazonaws.com" } it { is_expected.to eq 'us-east-1' } @@ -33,6 +38,31 @@ module Vault let(:sts_endpoint) { "https://stsXamazonaws.com" } it { expect {subject}.to raise_exception(StandardError, "Unable to parse STS endpoint https://stsXamazonaws.com") } end + + context 'with a host suffix attack' do + let(:sts_endpoint) { 'https://sts.amazonaws.com.evil.example' } + it { expect { subject }.to raise_exception(StandardError, 'Unable to parse STS endpoint https://sts.amazonaws.com.evil.example') } + end + + context 'with a query string' do + let(:sts_endpoint) { 'https://sts.us-west-2.amazonaws.com?foo=bar' } + it { is_expected.to eq 'us-west-2' } + end + + context 'with a non-root path' do + let(:sts_endpoint) { 'https://sts.us-west-2.amazonaws.com/foo' } + it { is_expected.to eq 'us-west-2' } + end + + context 'with a non-default port' do + let(:sts_endpoint) { 'https://sts.us-west-2.amazonaws.com:8443' } + it { is_expected.to eq 'us-west-2' } + end + + context 'with embedded user info' do + let(:sts_endpoint) { 'https://user:pass@sts.us-west-2.amazonaws.com' } + it { expect { subject }.to raise_exception(StandardError, 'Unable to parse STS endpoint https://user:pass@sts.us-west-2.amazonaws.com') } + end end end end