Skip to content

Commit d5915a9

Browse files
committed
Preparing CertBasedAuth
1 parent 198c404 commit d5915a9

File tree

6 files changed

+295
-25
lines changed

6 files changed

+295
-25
lines changed

.build/cspell-words.txt

+3-1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ hostnames
5858
HRESULT
5959
hsts
6060
httperr
61+
ietf
6162
imap
6263
Infoworker
6364
INSUFF
@@ -105,11 +106,12 @@ ntlm
105106
NTFS
106107
NUMA
107108
nupkg
109+
onmicrosoft
108110
onprem
109111
OutlookiOS
110112
Perflib
111113
perfmon
112-
PKCS
114+
Pkcs
113115
Prelicensing
114116
PROCMON
115117
QWORD

Hybrid/Test-HMAEAS.ps1

+1-1
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ process {
256256
$uri = New-Object "System.Uri" -ArgumentList $easUrl
257257
$resource = "$($uri.Scheme)://$($uri.Host)"
258258
$applicationClientId = "27922004-5251-4030-b22d-91ecd9a37ea4"
259-
$redirectUri = [System.Uri]("urn:ieTf:wg:oauth:2.0:oob").ToLower()
259+
$redirectUri = [System.Uri]("urn:ietf:wg:oauth:2.0:oob").ToLower()
260260
$authenticationContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
261261
$PromptBehavior = [Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::RefreshSession
262262
$platformParam = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList $PromptBehavior, $NULL

Security/src/CVE-2023-23397/CVE-2023-23397.ps1

+125-20
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@
4343
This optional parameter allows you to provide Azure environment
4444
.PARAMETER AzureApplicationName
4545
This optional parameter allows you to provide Azure application name
46+
.PARAMETER CertificateThumbprint
47+
This optional parameter allows you to provide a certificate thumbprint to use the Azure application created by the script
48+
.PARAMETER AppId
49+
This optional parameter allows you to provide the AppId of the Azure application created by the script
50+
.PARAMETER Organization
51+
This optional parameter allows you to provide the organization name e.g., contoso.onmicrosoft.com
4652
.PARAMETER EWSOnlineURL
4753
This optional parameter allows you to provide EWS online url
4854
.PARAMETER EWSOnlineScope
@@ -130,6 +136,18 @@ param(
130136
[Parameter(Mandatory = $false, ParameterSetName = "Cleanup")]
131137
[String]$AzureApplicationName = "CVE-2023-23397Application",
132138

139+
[Parameter(Mandatory = $false, ParameterSetName = "Audit")]
140+
[Parameter(Mandatory = $false, ParameterSetName = "Cleanup")]
141+
[String]$CertificateThumbprint,
142+
143+
[Parameter(Mandatory = $false, ParameterSetName = "Audit")]
144+
[Parameter(Mandatory = $false, ParameterSetName = "Cleanup")]
145+
[String]$AppId,
146+
147+
[Parameter(Mandatory = $false, ParameterSetName = "Audit")]
148+
[Parameter(Mandatory = $false, ParameterSetName = "Cleanup")]
149+
[String]$Organization,
150+
133151
[Parameter(Mandatory = $false, ParameterSetName = "Audit")]
134152
[Parameter(Mandatory = $false, ParameterSetName = "Cleanup")]
135153
[Uri]$EWSOnlineURL = "https://outlook.office365.com/EWS/Exchange.asmx",
@@ -227,6 +245,7 @@ begin {
227245
. $PSScriptRoot\..\..\..\Shared\Get-NuGetPackage.ps1
228246
. $PSScriptRoot\..\..\..\Shared\Invoke-ExtractArchive.ps1
229247
. $PSScriptRoot\..\..\..\Shared\LoggerFunctions.ps1
248+
. $PSScriptRoot\..\..\..\Shared\AzureFunctions\Get-NewJsonWebToken.ps1
230249
. $PSScriptRoot\..\..\..\Shared\Show-Disclaimer.ps1
231250

232251
$loggerParams = @{
@@ -400,15 +419,22 @@ begin {
400419
[string]$ClientID,
401420
[string]$AppSecret,
402421
[string]$AzureADEndpoint,
422+
[bool]$UseCertificateToAuthenticate = $false,
403423
[string]$Scope
404424
)
405425

406426
try {
407427
$body = @{
408-
scope = $Scope
409-
client_id = $ClientID
410-
client_secret = $AppSecret
411-
grant_type = "client_credentials"
428+
scope = $Scope
429+
client_id = $ClientID
430+
grant_type = "client_credentials"
431+
}
432+
433+
if ($UseCertificateToAuthenticate) {
434+
$body.Add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
435+
$body.Add("client_assertion", $AppSecret)
436+
} else {
437+
$body.Add("client_secret", $AppSecret)
412438
}
413439

414440
$PostSplat = @{
@@ -456,7 +482,8 @@ begin {
456482
function GetApplicationDetails {
457483
param (
458484
$AzureApplicationName,
459-
$AzureEnvironmentName
485+
$AzureEnvironmentName,
486+
$UseCertificateToAuthenticate = $false
460487
)
461488

462489
ConnectAzureAD -AzureEnvironmentName $AzureEnvironmentName
@@ -473,18 +500,22 @@ begin {
473500
exit
474501
}
475502

476-
#Assign App Password, make it valid for 7 days
477-
$appPassword = New-AzureADApplicationPasswordCredential -ObjectId $aadApplication.ObjectId -CustomKeyIdentifier "AppAccessKey" -EndDate (Get-Date).AddDays(7) -ErrorAction Stop
503+
$returnObject = @{
504+
TenantID = (Get-AzureADTenantDetail).ObjectId
505+
ClientID = $aadApplication.AppId
506+
}
478507

479-
Write-Host "`nWaiting 60 seconds for app credentials to register.."
480-
Start-Sleep -Seconds 60
481-
Write-Host "`nContinuing..."
508+
if ($UseCertificateToAuthenticate -ne $true) {
509+
#Assign App Password, make it valid for 7 days
510+
$appPassword = New-AzureADApplicationPasswordCredential -ObjectId $aadApplication.ObjectId -CustomKeyIdentifier "AppAccessKey" -EndDate (Get-Date).AddDays(7) -ErrorAction Stop
482511

483-
return @{
484-
"TenantID" = (Get-AzureADTenantDetail).ObjectId
485-
"ClientID" = $aadApplication.AppId
486-
"AppSecret" = $appPassword.Value
512+
Write-Host "`nWaiting 60 seconds for app credentials to register..."
513+
Start-Sleep -Seconds 60
514+
Write-Host "`nContinuing..."
515+
$returnObject.Add("AppSecret", $appPassword.Value)
487516
}
517+
518+
return $returnObject
488519
}
489520

490521
## function to delete Azure AD application
@@ -628,7 +659,36 @@ begin {
628659

629660
# if token is going to expire in next 5 min then refresh it
630661
if ($null -eq $script:tokenLastRefreshTime -or $script:tokenLastRefreshTime.AddMinutes(55) -lt (Get-Date)) {
631-
$token.Value = CreateOAUTHToken -TenantID $applicationInfo.TenantID -ClientID $applicationInfo.ClientID -AppSecret $applicationInfo.AppSecret -AzureADEndpoint $AzureADEndpoint -Scope $EWSOnlineScope
662+
$createOAuthTokenParams = @{
663+
TenantID = $applicationInfo.TenantID
664+
ClientID = $applicationInfo.ClientID
665+
AzureADEndpoint = $AzureADEndpoint
666+
UseCertificateToAuthenticate = (-not([System.String]::IsNullOrEmpty($applicationInfo.CertificateThumbprint)))
667+
Scope = $EWSOnlineScope
668+
}
669+
670+
# Check if we use an app secret or certificate by using regex to match Json Web Token (JWT)
671+
if ($applicationInfo.AppSecret -match "^([a-zA-Z0-9_=]+)\.([a-zA-Z0-9_=]+)\.([a-zA-Z0-9_\-\+\/=]*)") {
672+
$jwtParams = @{
673+
CertificateThumbprint = $applicationInfo.CertificateThumbprint
674+
CertificateStore = "CurrentUser"
675+
Issuer = $applicationInfo.ClientID
676+
Audience = ("{0}{1}/oauth2/v2.0/token" -f $AzureADEndpoint, $applicationInfo.TenantID)
677+
Subject = $applicationInfo.ClientID
678+
}
679+
$jwt = Get-NewJsonWebToken @jwtParams
680+
681+
if ($null -eq $jwt) {
682+
Write-Host "Unable to sign a new Json Web Token by using certificate: $($applicationInfo.CertificateThumbprint)" -ForegroundColor Red
683+
exit
684+
}
685+
686+
$createOAuthTokenParams.Add("AppSecret", $jwt)
687+
} else {
688+
$createOAuthTokenParams.Add("AppSecret", $applicationInfo.AppSecret)
689+
}
690+
691+
$token.Value = CreateOAUTHToken @createOAuthTokenParams
632692
$ewsService.Value = EWSAuth -Environment $Environment -token $token.Value -EWSOnlineURL $EWSOnlineURL
633693
}
634694
}
@@ -806,16 +866,61 @@ begin {
806866
}
807867

808868
if ($Environment -eq "Online") {
809-
$application = GetApplicationDetails -AzureApplicationName $AzureApplicationName -AzureEnvironmentName $AzureEnvironmentName
869+
$getApplicationDetailsParams = @{
870+
AzureApplicationName = $AzureApplicationName
871+
AzureEnvironmentName = $AzureEnvironmentName
872+
UseCertificateToAuthenticate = (-not([System.String]::IsNullOrEmpty($CertificateThumbprint)))
873+
}
874+
875+
if (([System.String]::IsNullOrEmpty($AppId)) -or
876+
([System.String]::IsNullOrEmpty($Organization)) -or
877+
([System.String]::IsNullOrEmpty($CertificateThumbprint))) {
878+
$application = GetApplicationDetails @getApplicationDetailsParams
879+
880+
$TenantID = $application.Tenant.Id
881+
$ClientID = $application.ClientID
882+
} else {
883+
$TenantID = $Organization
884+
$ClientID = $AppId
885+
}
810886

811887
$applicationInfo = @{
812-
"TenantID" = $application.Tenant.Id
813-
"ClientID" = $application.ClientID
814-
"AppSecret" = $application.AppSecret
888+
"TenantID" = $TenantID
889+
"ClientID" = $ClientID
890+
}
891+
892+
if ($null -ne $application.AppSecret) {
893+
$applicationInfo.Add("AppSecret", $application.AppSecret)
894+
} else {
895+
$jwtParams = @{
896+
CertificateThumbprint = $CertificateThumbprint
897+
CertificateStore = "CurrentUser"
898+
Issuer = $ClientID
899+
Audience = ("{0}{1}/oauth2/v2.0/token" -f $AzureADEndpoint, $TenantID)
900+
Subject = $ClientID
901+
}
902+
$jwt = Get-NewJsonWebToken @jwtParams
903+
904+
if ($null -eq $jwt) {
905+
Write-Host "Unable to generate Json Web Token by using certificate: $CertificateThumbprint" -ForegroundColor Red
906+
exit
907+
}
908+
909+
$applicationInfo.Add("AppSecret", $jwt)
910+
$applicationInfo.Add("CertificateThumbprint", $CertificateThumbprint)
911+
}
912+
913+
$createOAuthTokenParams = @{
914+
TenantID = $TenantID
915+
ClientID = $ClientID
916+
AppSecret = $applicationInfo.AppSecret
917+
Scope = $EWSOnlineScope
918+
AzureADEndpoint = $AzureADEndpoint
919+
UseCertificateToAuthenticate = (-not([System.String]::IsNullOrEmpty($CertificateThumbprint)))
815920
}
816921

817922
#Create OAUTH token
818-
$EWSToken = CreateOAUTHToken -TenantID $applicationInfo.TenantID -ClientID $applicationInfo.ClientID -AppSecret $applicationInfo.AppSecret -Scope $EWSOnlineScope -AzureADEndpoint $AzureADEndpoint
923+
$EWSToken = CreateOAUTHToken @createOAuthTokenParams
819924

820925
$ewsService = EWSAuth -Environment $Environment -Token $EWSToken -EWSOnlineURL $EWSOnlineURL
821926
} else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
function Get-NewJsonWebToken {
5+
[CmdletBinding()]
6+
param (
7+
[Parameter(Mandatory = $true)]
8+
[string]$CertificateThumbprint,
9+
10+
[ValidateSet("CurrentUser", "LocalMachine")]
11+
[Parameter(Mandatory = $false)]
12+
[string]$CertificateStore = "CurrentUser",
13+
14+
[Parameter(Mandatory = $false)]
15+
[string]$Issuer,
16+
17+
[Parameter(Mandatory = $false)]
18+
[string]$Audience,
19+
20+
[Parameter(Mandatory = $false)]
21+
[string]$Subject,
22+
23+
[Parameter(Mandatory = $false)]
24+
[int]$TokenLifetimeInSeconds = 3600,
25+
26+
[ValidateSet("RS256", "RS384", "RS512")]
27+
[Parameter(Mandatory = $false)]
28+
[string]$SigningAlgorithm = "RS256"
29+
)
30+
31+
<#
32+
Shared function to create a signed Json Web Token (JWT) by using a certificate.
33+
It is also possible to use a secret key to sign the token, but that is not supported in this function.
34+
The function returns the token as a string if successful, otherwise it returns $null.
35+
https://www.rfc-editor.org/rfc/rfc7519
36+
https://learn.microsoft.com/azure/active-directory/develop/active-directory-certificate-credentials
37+
https://learn.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
38+
#>
39+
40+
begin {
41+
Write-Verbose "Calling $($MyInvocation.MyCommand)"
42+
}
43+
process {
44+
try {
45+
$certificate = Get-ChildItem Cert:\$CertificateStore\My\$CertificateThumbprint
46+
if ($certificate.HasPrivateKey) {
47+
$privateKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certificate)
48+
# Base64url-encoded SHA-1 thumbprint of the X.509 certificate's DER encoding
49+
$x5t = [System.Convert]::ToBase64String($certificate.GetCertHash())
50+
$x5t = ((($x5t).Replace("\+", "-")).Replace("/", "_")).Replace("=", "")
51+
Write-Verbose "x5t is: $x5t"
52+
} else {
53+
Write-Verbose "We don't have a private key for certificate: $CertificateThumbprint and so cannot sign the token"
54+
return
55+
}
56+
} catch {
57+
Write-Verbose "Unable to import the certificate - Exception: $($Error[0].Exception.Message)"
58+
return
59+
}
60+
61+
$header = [ordered]@{
62+
alg = $SigningAlgorithm
63+
typ = "JWT"
64+
x5t = $x5t
65+
}
66+
67+
# "iat" (issued at) and "exp" (expiration time) must be UTC and in UNIX time format
68+
$payload = @{
69+
iat = [Math]::Round((Get-Date).ToUniversalTime().Subtract((Get-Date -Date "01/01/1970")).TotalSeconds)
70+
exp = [Math]::Round((Get-Date).ToUniversalTime().Subtract((Get-Date -Date "01/01/1970")).TotalSeconds) + $TokenLifetimeInSeconds
71+
}
72+
73+
# Issuer, Audience and Subject are optional as per RFC 7519
74+
if (-not([System.String]::IsNullOrEmpty($Issuer))) {
75+
Write-Verbose "Issuer: $Issuer will be added to payload"
76+
$payload.Add("iss", $Issuer)
77+
}
78+
79+
if (-not([System.String]::IsNullOrEmpty($Audience))) {
80+
Write-Verbose "Audience: $Audience will be added to payload"
81+
$payload.Add("aud", $Audience)
82+
}
83+
84+
if (-not([System.String]::IsNullOrEmpty($Subject))) {
85+
Write-Verbose "Subject: $Subject will be added to payload"
86+
$payload.Add("sub", $Subject)
87+
}
88+
89+
$headerJson = $header | ConvertTo-Json -Compress
90+
$payloadJson = $payload | ConvertTo-Json -Compress
91+
92+
$headerBase64 = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($headerJson)).Split("=")[0].Replace("+", "-").Replace("/", "_")
93+
$payloadBase64 = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($payloadJson)).Split("=")[0].Replace("+", "-").Replace("/", "_")
94+
95+
$signatureInput = [System.Text.Encoding]::ASCII.GetBytes("$headerBase64.$payloadBase64")
96+
97+
Write-Verbose "Header (Base64) is: $headerBase64"
98+
Write-Verbose "Payload (Base64) is: $payloadBase64"
99+
Write-Verbose "Signature input is: $signatureInput"
100+
101+
$signingAlgorithmToUse = switch ($SigningAlgorithm) {
102+
("RS384") { [Security.Cryptography.HashAlgorithmName]::SHA384 }
103+
("RS512") { [Security.Cryptography.HashAlgorithmName]::SHA512 }
104+
default { [Security.Cryptography.HashAlgorithmName]::SHA256 }
105+
}
106+
Write-Verbose "Signing the Json Web Token using: $SigningAlgorithm"
107+
108+
$signature = $privateKey.SignData($signatureInput, $signingAlgorithmToUse, [Security.Cryptography.RSASignaturePadding]::Pkcs1)
109+
$signature = [Convert]::ToBase64String($signature).Split("=")[0].Replace("+", "-").Replace("/", "_")
110+
}
111+
end {
112+
if ((-not([System.String]::IsNullOrEmpty($headerBase64))) -and
113+
(-not([System.String]::IsNullOrEmpty($payloadBase64))) -and
114+
(-not([System.String]::IsNullOrEmpty($signature)))) {
115+
Write-Verbose "Returning Json Web Token"
116+
return ("$headerBase64.$payloadBase64.$signature")
117+
} else {
118+
Write-Verbose "Unable to create Json Web Token"
119+
return
120+
}
121+
}
122+
}

docs/Security/CVE-2023-23397/FAQ.md

+22
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,25 @@ It will only export individual items that contain the PidLidReminderFileParamete
9696
## Why does my output file contain multiple entries for the same mailbox?
9797

9898
For each individual item that does contain the PidLidReminderFileParameter property, it will be exported out with a item ID that is needed to possibly take action against.
99+
100+
## What are the required steps to prepare the 'CVE-2023-23397Application' application to support Certificate Based Authentication (CBA)
101+
102+
Step 1: Create the Azure application by running the script with the `CreateAzureApplication`. This step must be performed by someone who is `Global Administrator` or an `Application Administrator`.
103+
104+
Step 2: Generate a new self-signed certificate and export the public part:
105+
106+
```powershell
107+
$cert = New-SelfSignedCertificate -Subject $env:COMPUTERNAME -CertStoreLocation "Cert:\CurrentUser\My"
108+
$cert | Export-Certificate -FilePath MySelfSignedCertificate.cer
109+
$cert.Thumbprint
110+
```
111+
112+
!!! warning "Important"
113+
114+
The certificate must be kept confidential as it allows the owner to access the Azure application without further authentication.
115+
116+
Step 3: Upload the certificate to the `CVE-2023-23397Application` Azure application
117+
118+
Go to the Azure Active Directory and search for `App registrations`. Select the `CVE-2023-23397Application` application and go to `Certificates & secrets`. From here, select `Certificates` and click on `Upload certificate`. Select the `MySelfSignedCertificate.cer` file which was created in Step 2, add a descriptive description. Complete the process by clicking on `Add`.
119+
120+
The application is now ready for CBA.

0 commit comments

Comments
 (0)