CTF: M356 Email Signatures

I recently took part in the 2nd Cyberdrain CTF. This was my submission for the signature question
For this script I took this as a base https://github.com/12Knocksinna/Office365itpros/blob/master/UpdateOutlookSignature.PS1
I fixed all the errors in it, tweaked it a lot for the CTF and how I wanted it to work and also implemented a paired Azure Function that performs the actual lookup so local devices don’t need to have access to M365 Keys. The lookup script is then protected with a separate key. I have scripted it so that it would be run in the logged in user context by an RMM with the key, company logo URL and company name passed in.

Azure Function
using namespace System.Net
# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)
# Write to the Azure Functions log stream.
Write-Host "M365 User Check Triggered"
######### Secrets #########
$ApplicationId = $ENV:ApplicationID
$ApplicationSecret = $ENV:ApplicationSecret
$RefreshToken = $ENV:Refreshtoken
$UserLookupKey = $ENV:UserLookupKey
import-module PartnerCenterLW
import-module MSOnlineLW
# Process Post Data
$UserData = $Request.RawBody | ConvertFrom-Json
if ($UserData.UserLookupKey -eq $UserLookupKey) {
$ReturnResultCode = 200
write-host "Creating credentials and tokens." -ForegroundColor Green
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, ($ApplicationSecret | Convertto-SecureString -AsPlainText -Force))
$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal
write-host "Connecting to MSOL" -ForegroundColor Green
Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
$UserTenantName = $UserData.UPN -split '@' | Select-Object -last 1
$UserTenantGUID = (Invoke-WebRequest "https://login.windows.net/$UserTenantName/.well-known/openid-configuration" | ConvertFrom-Json).token_endpoint.Split('/')[3]
$User = Get-MsolUser -TenantID $UserTenantGUID -UserPrincipalName $UserData.UPN
if ($user) {
$ReturnResultMessage = @{
DisplayName = $User.DisplayName
StreetAddress = $User.StreetAddress
City = $User.City
PostalCode = $User.PostalCode
FirstName = $User.FirstName
LastName = $User.LastName
Title = $User.Title
TelephoneNumber = $User.PhoneNumber
Mobile = $User.MobilePhone
}
} else {
$ReturnResultMessage = $null
}
} else {
$ReturnResultCode = 403
$ReturnResultMessage = "No"
}
# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = $ReturnResultCode
Body = $ReturnResultMessage
})
Endpoint Script
# https://github.com/12Knocksinna/Office365itpros/blob/master/UpdateOutlookSignature.PS1
$env:UserLookupKey = "LookupKey"
$CompanyLogo = "https://yourlogo.com/logo.png"
$CompanyName = "Lukes Mega Company"
$SignatureName = 'Standard Signature'
# This function can be adapted for your own lookup requirements.
function Get-UserDetails {
param(
[string]$UPN
)
$body = @{
UPN = $UPN
UserLookupKey = $env:UserLookupKey
} | ConvertTo-Json
$Result = Invoke-WebRequest -uri "https://YOURAzureFunction.azurewebsites.net/api/QueryUser" -method POST -Body $Body
Return $Result.Content | ConvertFrom-JSON
}
$LocalSignaturePath = (Get-Item env:appdata).Value + '\Microsoft\Signatures'
$HtmlPath = $LocalSignaturePath + '\' + $SignatureName + '.htm'
# Find the User Principal Name for the account as stored in the system registry
$UserAccount = Get-ItemProperty -Path HKCU:\Software\Microsoft\Office\Outlook\Settings -Name Accounts | Select-Object -ExpandProperty Accounts
$UserId = (ConvertFrom-Json $UserAccount).UserUpn[0]
if ($UserID) {
# Retrieve the properties of the user from Azure Active Directory
$UserProperties = Get-UserDetails -UPN $UserId
# Find Outlook Profiles in registry
$CommonSettings = $False
# Determine Office Version and Set the Correct Paths
$ol = New-Object -ComObject Excel.Application
if ($ol.Version -eq "16.0") {
$RegBase = "HKCU:\Software\Microsoft\Office\16.0\"
} elseif ($ol.Version -eq "15.0") {
$RegBase = "HKCU:\Software\Microsoft\Office\15.0\"
} else {
Write-Error "The installed version of Office is not supported"
exit 1
}
$Profiles = (Get-ChildItem "$($RegBase)Outlook\Profiles" -ea stop).PSChildName
# Handle Single or Multiple
If ($Null -eq $Profiles -or $Profiles.Count -ne 1) {
Write-Host "Warning - Applying signature to all Outlook profiles"
$OutlookProfilePath = "$($RegBase)Common\MailSettings"
$CommonSettings = $True
} Else {
# Path to default profile is elsewhere in the registry
$OutLookProfilePath = "$($RegBase)Outlook\Profiles\" + $Profiles.Trim() + "\9375CFF0413111d3B88A00104B2A6676\00000001"
}
# If we have an Outlook profile, check that we can match the User Principal name with the account name that's stored in the registry for Outlook ProPlus
# But only do this for now if we're not updating all profiles
If ($CommonSettings -eq $False) {
$OutlookProfile = Get-ItemProperty -Path $OutLookProfilePath
If ($OutlookProfile."Account Name" -ne $UserId) {
$OutLookProfilePath = "$($RegBase)Outlook\Profiles\" + $Profiles.Trim() + "\9375CFF0413111d3B88A00104B2A6676\00000002"
$OutlookProfile = Get-ItemProperty -Path $OutLookProfilePath
If ($OutlookProfile."Account Name" -ne $UserId) {
# We don't have a profile match
Write-Host "Can't match signature and Office 365 user principal name"
exit 1
}
}
}
# Make sure we have a company name
# Construct a signature file in HTML format using the information fetched from Azure Active Directory
$HeadingLine = "<HTML><HEAD><TITLE>Signature</TITLE><BODY><BR><table style=`"FONT-SIZE: 8pt; COLOR: gray; FONT-FAMILY: `'Segoe UI`' `"> <tr>"
$ImageLine = "<td ><img src='" + $CompanyLogo + "' border='0'></td>"
if ($UserProperties.Title) {
$PersonLine = "<td padding='0'><B>" + $UserProperties.DisplayName + " </B> - " + $UserProperties.Title + "<br />"
} else {
$PersonLine = "<td padding='0'><B>" + $UserProperties.DisplayName + " </B><br />"
}
$CompanyLine = "<b>" + $CompanyName + "</b><br />"
if ($UserProperties.StreetAddress) {
$CompanyLine = $CompanyLine + $UserProperties.StreetAddress
}
if ($UserProperties.City) {
$CompanyLine = $CompanyLine + ", " + $UserProperties.City
}
if ($UserProperties.PostalCode) {
$CompanyLine = $CompanyLine + ", " + $UserProperties.PostalCode
}
$CompanyLine = $CompanyLine + "<br />"
if ($UserProperties.TelephoneNumber) {
$CompanyLine = $CompanyLine + "T: $($UserProperties.TelephoneNumber)" + "<br />"
}
if ($UserProperties.Mobile) {
$CompanyLine = $CompanyLine + "M: $($UserProperties.Mobile)" + "<br />"
}
$CompanyLine = $CompanyLine + "E: $UserId" + "<br /><br />"
$EndLine = "</td></tr></table><br /><br /></BODY></HTML>"
If(!(test-path $LocalSignaturePath)) {
New-Item -ItemType Directory -Force -Path $LocalSignaturePath
}
# Put everything together and output the HTML file
$HeadingLine + $ImageLine + $PersonLine + $CompanyLine + $EndLine | Out-File $HtmlPath
# Update the registry settings where Outlook picks up its signature information
Get-Item -Path $OutlookProfilePath | New-Itemproperty -Name "New Signature" -value $SignatureName -Propertytype string -Force
Get-Item -Path $OutlookProfilePath | New-Itemproperty -Name "Reply-Forward Signature" -value $SignatureName -Propertytype string -Force
} else {
Write-Error "User not found"
Exit 1
}