Public/Helpers/Read-SASToken.ps1

function Read-SASToken {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [Alias('SasUri', 'SasToken', 'Uri', 'Token', 'Url')]
        [string]$InputString
    )

    process {
        #region common

        Write-Output "[+] Start collection SAS Token information"

        #Variables
        Add-Type -AssemblyName system.web

        # Clean up input - remove leading ? if present
        $InputString = $InputString.TrimStart('?')

        # Auto-detect if input is a full URI or just a token
        if ($InputString -match '^https?://') {
            # Input is a full URI - extract the token portion
            $storageUri = $InputString -split "\?"
            $baseUri = $storageUri[0]
            $tokenArray = $storageUri[1] -split '&'
            Write-Verbose "[+] Detected full URI input"
        }
        elseif ($InputString -match 'sv=') {
            # Input is just a SAS token
            $tokenArray = $InputString -split '&'
            $baseUri = $null
            Write-Verbose "[+] Detected SAS token input"
        }
        else {
            Write-Message -FunctionName $MyInvocation.MyCommand.Name -Message "Invalid input: expected a SAS URI or SAS token containing 'sv=' parameter" -Severity 'Error'
            break
        }

        if ($tokenArray.count -lt 1) {
            Write-Message -FunctionName $MyInvocation.MyCommand.Name -Message "No valid SAS token parameters found" -Severity 'Error'
            break
        }

        $permissionList = New-Object System.Collections.ArrayList
        $resourceList = New-Object System.Collections.ArrayList
        $resourceTypes = New-Object System.Collections.ArrayList
        $services = New-Object System.Collections.ArrayList

        $tokenObjects = [ordered]@{}
        
        # Only add Storage Uri if we detected a full URI
        if ($baseUri) {
            $tokenObjects.'Storage Uri' = $baseUri
        }

        Write-Verbose '[+] Processing token properties'
        $tokenArray | ForEach-Object {
            if ($_ -like "spr=*") { $tokenObjects.Protocol = ($_).substring(4) }
            if ($_ -like "st=*") { $tokenObjects."Start Time" = ($_).substring(3) }
            if ($_ -like "se=*") { $tokenObjects."Expiry Time" = ($_).substring(3) }
            if ($_ -like "sv=*") { $tokenObjects."Service Version" = ($_).substring(3) }
            if ($_ -like "sp=*") { $tokenObjects."Permissions" = ($_).substring(3) }
            if ($_ -like "sip=*") { $tokenObjects."IP Address" = ($_).substring(4) }

            if ($_ -like "sig=*") {
                $tokenObjects."Signature" = ($_).substring(4)
                $tokenObjects."Base64 Signature" = [System.Web.HttpUtility]::UrlDecode($tokenObjects."Signature")
            }

            if ($_ -like "srt=*") {
                $tokenObjects."Resource Types" = ($_).substring(4)
                $tokenObjects."Token Type" = 'Account-level SAS'

                $tokenObjects."Resource Types".ToCharArray() | ForEach-Object {
                    if ($_ -eq 's') { $resourceTypes += ('Service-level APIs') }
                    if ($_ -eq 'c') { $resourceTypes += ('Container-level APIs') }
                    if ($_ -eq 'o') { $resourceTypes += ('Object-level APIs') }
                }

                $tokenObjects."Resource Types" = $resourceTypes

            }

            if ($_ -like "sr=*") {
                $tokenObjects."Storage Resource" = ($_).substring(3)
                $tokenObjects."Token Type" = 'user-level SAS'

                $tokenObjects."Storage Resource".ToCharArray() | ForEach-Object {
                    if ($_ -eq 'b') { $resourceList += ('Blob') }
                    if ($_ -eq 'bv') { $resourceList += ('Blob version') }
                    if ($_ -eq 'bs') { $resourceList += ('Blob snapshot') }
                    if ($_ -eq 'c') { $resourceList += ('Container') }
                    if ($_ -eq 'd') { $resourceList += ('Directory') }
                }

                $tokenObjects."Storage Resource" = $resourceList
            }

            if ($_ -like "ss=*") {
                $tokenObjects."Services" = ($_).substring(3)
                Write-Verbose "[+] Processing Services"

                $tokenObjects."Services".ToCharArray() | ForEach-Object {
                    if ($_ -eq 'b') { $services += ('Blob') }
                    if ($_ -eq 'q') { $services += ('Queue') }
                    if ($_ -eq 't') { $services += ('Table') }
                    if ($_ -eq 'f') { $services += ('File') }
                }

                $tokenObjects."Services" = $services
            }

            if ($_ -like "sp=*") {
                Write-Verbose "[+] Processing Permissions"
                $tokenObjects.Permissions.ToCharArray() | ForEach-Object {
                    if ($_ -eq 'r') { $permissionList += ('Read') }
                    if ($_ -eq 'a') { $permissionList += ('Add') }
                    if ($_ -eq 'c') { $permissionList += ('Create') }
                    if ($_ -eq 'w') { $permissionList += ('Write') }
                    if ($_ -eq 'd') { $permissionList += ('Delete') }
                    if ($_ -eq 'x') { $permissionList += ('Delete Version') }
                    if ($_ -eq 'y') { $permissionList += ('Permanent Delete') }
                    if ($_ -eq 'l') { $permissionList += ('List') }
                    if ($_ -eq 't') { $permissionList += ('Tags') }
                    if ($_ -eq 'f') { $permissionList += ('Find') }
                    if ($_ -eq 'm') { $permissionList += ('Move') }
                    if ($_ -eq 'e') { $permissionList += ('Execute') }
                    if ($_ -eq 'o') { $permissionList += ('Ownership') }
                    if ($_ -eq 'P') { $permissionList += ('Permissions') }
                    if ($_ -eq 'i') { $permissionList += ('Set Immutability Policy') }
                }

                $tokenObjects."Permissions" = $permissionList
            }
        }
        return $tokenObjects | ConvertTo-Json | ConvertFrom-Json
    }
<#
    .SYNOPSIS
        Reads and processes a Shared Access Signature (SAS) token or URI.
 
    .DESCRIPTION
        Parses SAS URI or token to extract properties including permissions and signature.
 
    .PARAMETER InputString
        The SAS URI or SAS token string to parse. The function automatically detects the input type:
        - If the input starts with 'http://' or 'https://', it's treated as a full URI
        - Otherwise, it's treated as a SAS token string
        Aliases: SasUri, SasToken, Uri, Token, Url
 
    .EXAMPLE
        Read-SASToken "https://example.blob.core.windows.net/container?sv=2019-12-12&ss=b&srt=s&sp=rwdlac&se=2022-01-01T00:00:00Z&st=2021-01-01T00:00:00Z&spr=https&sig=xxxx"
 
        This example reads the information from a full SAS URI. The storage URI will be extracted and displayed.
 
    .EXAMPLE
        Read-SASToken "sv=2019-12-12&ss=b&srt=s&sp=rwdlac&se=2022-01-01T00:00:00Z&st=2021-01-01T00:00:00Z&spr=https&sig=xxxx"
 
        This example reads the information from just a SAS token string.
 
    .EXAMPLE
        Read-SASToken "?sv=2019-12-12&ss=b&srt=sco&sp=rl&se=2028-01-21T22:14:47Z&st=2026-01-21T13:59:47Z&spr=https&sig=xxxx"
 
        This example shows that leading '?' characters are automatically trimmed.
 
    .EXAMPLE
        $url | Read-SASToken
 
        This example shows pipeline input support.
 
    .NOTES
    Author: Rogier Dijkman (https://securehats.gitbook.io/BlackCat)
 
.LINK
    MITRE ATT&CK Tactic: TA0006 - Credential Access
    https://attack.mitre.org/tactics/TA0006/
 
.LINK
    MITRE ATT&CK Technique: T1552.005 - Unsecured Credentials: Cloud Instance Metadata API
    https://attack.mitre.org/techniques/T1552/005/
#>

}