diff --git a/.gitignore b/.gitignore index 6a3b819..e3a51a0 100644 --- a/.gitignore +++ b/.gitignore @@ -46,5 +46,15 @@ response.json .ctf config +# challenge specific files +azure/challenges/Cloud Jumping/data/container/tmp.txt +azure/challenges/Image-ination Continuation/data/logicapp_identity.txt +azure/challenges/Image-ination Station/data/sp-creds.json +azure/challenges/Not so secret/data/assets.csv +azure/challenges/Permisery/data/app.py +azure/challenges/Permisery/data/requirements.txt +azure/challenges/Permisery/zip/ - +# other azure files +azure/credentials.txt +azure/terraform.tfvars diff --git a/azure/challenges/Cloud Jumping/cloudjumping.tf b/azure/challenges/Cloud Jumping/cloudjumping.tf new file mode 100644 index 0000000..1db2f0c --- /dev/null +++ b/azure/challenges/Cloud Jumping/cloudjumping.tf @@ -0,0 +1,282 @@ +# resource group +resource "azurerm_resource_group" "cloudjumping" { + name = "cloudjumpingRG" + location = var.azure_region +} + +data "azuread_domains" "domain" { + only_initial = true +} + +data "azurerm_subscription" "current" {} + +data "http" "current_ip" { + url = "https://icanhazip.com" +} + +locals { + user_ip = chomp(data.http.current_ip.response_body) +} + +# password for read user +resource "random_password" "read_user_password" { + length = 16 + special = true +} + +# store creds locally then later upload them to storage account +resource "local_file" "credentials" { + filename = "${path.module}/data/container/tmp.txt" + content = <<-EOF + "az.reader@${data.azuread_domains.domain.domains.0.domain_name}" + ${random_password.read_user_password.result} + EOF +} + +# create read user and give them global reader +resource "azuread_user" "read_user" { + user_principal_name = "az.reader@${data.azuread_domains.domain.domains.0.domain_name}" + display_name = "Azure Reader" + password = random_password.read_user_password.result +} + +resource "azuread_directory_role_assignment" "global_reader_assigment" { + role_id = "f2ef992c-3afb-46b9-b7cf-a126ee74c451" + principal_object_id = azuread_user.read_user.object_id +} + +resource "azurerm_role_assignment" "subscription_reader_access" { + scope = data.azurerm_subscription.current.id + role_definition_name = "Reader" + principal_id = azuread_user.read_user.object_id +} + +/*---------------------------------------------*/ + + +################################################# +// Create publicly accessible blob container +################################################# + +resource "random_id" "challenge_instance_id" { + byte_length = 4 +} + +locals { + uploaded_files = fileset("${path.module}/data/container", "*") +} + +resource "azurerm_storage_account" "sa" { + name = "cloudjumping${random_id.challenge_instance_id.hex}" + resource_group_name = azurerm_resource_group.cloudjumping.name + location = azurerm_resource_group.cloudjumping.location + account_tier = "Standard" + account_replication_type = "LRS" + allow_nested_items_to_be_public = true + + network_rules { + default_action = "Deny" + bypass = ["AzureServices"] + ip_rules = [ + local.user_ip + ] + } +} + +resource "azurerm_storage_container" "container" { + name = "files" + storage_account_id = azurerm_storage_account.sa.id + container_access_type = "container" +} + +resource "azurerm_storage_blob" "blob" { + for_each = { for file in local.uploaded_files : file => file } + name = each.value + storage_account_name = azurerm_storage_account.sa.name + storage_container_name = azurerm_storage_container.container.name + type = "Block" + source = "${path.module}/data/container/${each.value}" +} + +# give player permissions to read storage account and data +resource "azurerm_role_assignment" "storage_blob_reader" { + principal_id = var.player_object_id + role_definition_name = "Storage Blob Data Reader" + scope = azurerm_storage_account.sa.id +} + +resource "azurerm_role_assignment" "reader" { + principal_id = var.player_object_id + role_definition_name = "Reader" + scope = azurerm_storage_account.sa.id +} + +/*---------------------------------------------*/ + + +################################################# +// Create a managed identity +################################################# + +resource "azurerm_user_assigned_identity" "contributor_managed_identity" { + name = "contributor-managed-identity" + resource_group_name = azurerm_resource_group.cloudjumping.name + location = azurerm_resource_group.cloudjumping.location +} + +# managed identity should have contributor role for cloudjumping resource group +resource "azurerm_role_assignment" "contributor" { + scope = azurerm_resource_group.cloudjumping.id + role_definition_name = "Contributor" + principal_id = azurerm_user_assigned_identity.contributor_managed_identity.principal_id +} + +/*---------------------------------------------*/ + + +################################################# +// Create Linux VM with a managed identity +################################################# + +# vm1 (jump-host) gets deployed from a template +data "local_file" "jump_host_template" { + filename = "${path.module}/data/jump-host-template.json" +} + +resource "random_password" "jump_host_user_password" { + length = 16 + special = true +} + +resource "azurerm_resource_group_template_deployment" "jump_host_vm" { + name = "jump_host" + resource_group_name = azurerm_resource_group.cloudjumping.name + deployment_mode = "Incremental" + template_content = data.local_file.jump_host_template.content + parameters_content = jsonencode({ + adminPasswordOrKey = { + value = random_password.jump_host_user_password.result + } + }) + + depends_on = [ + azurerm_role_assignment.contributor + ] +} + +/*---------------------------------------------*/ + + +################################################# +// Create Linux VM with the flag +################################################# + +resource "random_password" "linux_vm_user_password" { + length = 16 + special = true +} + +resource "azurerm_virtual_network" "linux_vm_vnet" { + name = "production-vnet" + address_space = ["10.2.0.0/16"] + resource_group_name = azurerm_resource_group.cloudjumping.name + location = azurerm_resource_group.cloudjumping.location +} + +resource "azurerm_subnet" "linux_vm_subnet" { + name = "production-subnet" + resource_group_name = azurerm_resource_group.cloudjumping.name + virtual_network_name = azurerm_virtual_network.linux_vm_vnet.name + address_prefixes = ["10.2.1.0/24"] +} + +resource "azurerm_public_ip" "linux_vm_ip" { + name = "production-public-ip" + resource_group_name = azurerm_resource_group.cloudjumping.name + location = azurerm_resource_group.cloudjumping.location + allocation_method = "Static" +} + +resource "azurerm_network_interface" "linux_vm_nic" { + name = "production-vm-nic" + resource_group_name = azurerm_resource_group.cloudjumping.name + location = azurerm_resource_group.cloudjumping.location + + ip_configuration { + name = "internal" + subnet_id = azurerm_subnet.linux_vm_subnet.id + private_ip_address_allocation = "Dynamic" + public_ip_address_id = azurerm_public_ip.linux_vm_ip.id + } + + depends_on = [ + azurerm_public_ip.linux_vm_ip + ] +} + +resource "azurerm_network_security_group" "ctf_nsg" { + name = "ctf-nsg" + resource_group_name = azurerm_resource_group.cloudjumping.name + location = azurerm_resource_group.cloudjumping.location + + security_rule { + name = "Allow-SSH" + priority = 1001 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "22" + source_address_prefix = local.user_ip + destination_address_prefix = "*" + } +} + +resource "azurerm_subnet_network_security_group_association" "ctf_subnet_assoc" { + subnet_id = azurerm_subnet.linux_vm_subnet.id + network_security_group_id = azurerm_network_security_group.ctf_nsg.id +} + +resource "azurerm_linux_virtual_machine" "linux_vm" { + name = "production-server" + resource_group_name = azurerm_resource_group.cloudjumping.name + location = azurerm_resource_group.cloudjumping.location + size = "Standard_B1s" + admin_username = "azureadmin" + admin_password = random_password.linux_vm_user_password.result + disable_password_authentication = false + + network_interface_ids = [ + azurerm_network_interface.linux_vm_nic.id + ] + + os_disk { + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + name = "production-server-disk" + } + + source_image_reference { + publisher = "Canonical" + offer = "0001-com-ubuntu-server-focal" + sku = "20_04-lts" + version = "latest" + } + + connection { + type = "ssh" + host = azurerm_public_ip.linux_vm_ip.ip_address + user = "azureadmin" + password = random_password.linux_vm_user_password.result + } + + provisioner "file" { + source = "${path.module}/data/flag.txt" + destination = "/home/azureadmin/flag.txt" + } + + depends_on = [ + azurerm_public_ip.linux_vm_ip, + azurerm_network_interface.linux_vm_nic + ] +} \ No newline at end of file diff --git a/azure/challenges/Cloud Jumping/data/container/important.odt b/azure/challenges/Cloud Jumping/data/container/important.odt new file mode 100755 index 0000000..6224d74 Binary files /dev/null and b/azure/challenges/Cloud Jumping/data/container/important.odt differ diff --git a/azure/challenges/Cloud Jumping/data/container/urgent.pdf b/azure/challenges/Cloud Jumping/data/container/urgent.pdf new file mode 100755 index 0000000..398307f Binary files /dev/null and b/azure/challenges/Cloud Jumping/data/container/urgent.pdf differ diff --git a/azure/challenges/Cloud Jumping/data/flag.txt b/azure/challenges/Cloud Jumping/data/flag.txt new file mode 100644 index 0000000..5a0104b --- /dev/null +++ b/azure/challenges/Cloud Jumping/data/flag.txt @@ -0,0 +1 @@ +FLAG{cloudJumping:justAnotherDayCloudJumping} \ No newline at end of file diff --git a/azure/challenges/Cloud Jumping/data/jump-host-template.json b/azure/challenges/Cloud Jumping/data/jump-host-template.json new file mode 100644 index 0000000..2361004 --- /dev/null +++ b/azure/challenges/Cloud Jumping/data/jump-host-template.json @@ -0,0 +1,340 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.1.11899", + "templateHash": "6608977399677963554" + } + }, + "parameters": { + "vmName": { + "type": "string", + "defaultValue": "jump-host", + "metadata": { + "description": "The name of your Virtual Machine." + } + }, + "adminUsername": { + "type": "string", + "defaultValue": "azureuser", + "metadata": { + "description": "Username for the Virtual Machine." + } + }, + "authenticationType": { + "type": "string", + "defaultValue": "password", + "allowedValues": [ + "sshPublicKey", + "password" + ], + "metadata": { + "description": "Type of authentication to use on the Virtual Machine. SSH key is recommended." + } + }, + "adminPasswordOrKey": { + "type": "string", + "metadata": { + "description": "SSH Key or password for the Virtual Machine. SSH key is recommended." + } + }, + "dnsLabelPrefix": { + "type": "string", + "defaultValue": "[toLower(format('{0}-{1}', parameters('vmName'), uniqueString(resourceGroup().id)))]", + "metadata": { + "description": "Unique DNS Name for the Public IP used to access the Virtual Machine." + } + }, + "ubuntuOSVersion": { + "type": "string", + "defaultValue": "Ubuntu-2204", + "allowedValues": [ + "Ubuntu-2004", + "Ubuntu-2204" + ], + "metadata": { + "description": "The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for all resources." + } + }, + "vmSize": { + "type": "string", + "defaultValue": "Standard_B1s", + "metadata": { + "description": "The size of the VM" + } + }, + "virtualNetworkName": { + "type": "string", + "defaultValue": "vNet", + "metadata": { + "description": "Name of the VNET" + } + }, + "subnetName": { + "type": "string", + "defaultValue": "Subnet", + "metadata": { + "description": "Name of the subnet in the virtual network" + } + }, + "networkSecurityGroupName": { + "type": "string", + "defaultValue": "CJjumpHostSecGroup", + "metadata": { + "description": "Name of the Network Security Group" + } + }, + "securityType": { + "type": "string", + "defaultValue": "TrustedLaunch", + "allowedValues": [ + "Standard", + "TrustedLaunch" + ], + "metadata": { + "description": "Security Type of the Virtual Machine." + } + } + }, + "variables": { + "imageReference": { + "Ubuntu-2004": { + "publisher": "Canonical", + "offer": "0001-com-ubuntu-server-focal", + "sku": "20_04-lts-gen2", + "version": "latest" + }, + "Ubuntu-2204": { + "publisher": "Canonical", + "offer": "0001-com-ubuntu-server-jammy", + "sku": "22_04-lts-gen2", + "version": "latest" + } + }, + "publicIPAddressName": "[format('{0}PublicIP', parameters('vmName'))]", + "networkInterfaceName": "[format('{0}NetInt', parameters('vmName'))]", + "osDiskType": "Standard_LRS", + "subnetAddressPrefix": "10.1.0.0/24", + "addressPrefix": "10.1.0.0/16", + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "path": "[format('/home/{0}/.ssh/authorized_keys', parameters('adminUsername'))]", + "keyData": "[parameters('adminPasswordOrKey')]" + } + ] + } + }, + "securityProfileJson": { + "uefiSettings": { + "secureBootEnabled": true, + "vTpmEnabled": true + }, + "securityType": "[parameters('securityType')]" + }, + "extensionName": "GuestAttestation", + "extensionPublisher": "Microsoft.Azure.Security.LinuxAttestation", + "extensionVersion": "1.0", + "maaTenantName": "GuestAttestation", + "maaEndpoint": "[substring('emptystring', 0, 0)]" + }, + "resources": [ + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2023-09-01", + "name": "[variables('networkInterfaceName')]", + "location": "[parameters('location')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[reference(resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName')), '2023-09-01').subnets[0].id]" + }, + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]" + } + } + } + ], + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]", + "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]", + "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]" + ] + }, + { + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2023-09-01", + "name": "[parameters('networkSecurityGroupName')]", + "location": "[parameters('location')]", + "properties": { + "securityRules": [ + { + "name": "SSH", + "properties": { + "priority": 1000, + "protocol": "Tcp", + "access": "Allow", + "direction": "Inbound", + "sourceAddressPrefix": "*", + "sourcePortRange": "*", + "destinationAddressPrefix": "*", + "destinationPortRange": "22" + } + } + ] + } + }, + { + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2023-09-01", + "name": "[parameters('virtualNetworkName')]", + "location": "[parameters('location')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[variables('addressPrefix')]" + ] + }, + "subnets": [ + { + "name": "[parameters('subnetName')]", + "properties": { + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]" + }, + "addressPrefix": "[variables('subnetAddressPrefix')]", + "privateEndpointNetworkPolicies": "Enabled", + "privateLinkServiceNetworkPolicies": "Enabled" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]" + ] + }, + { + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2023-09-01", + "name": "[variables('publicIPAddressName')]", + "location": "[parameters('location')]", + "sku": { + "name": "Basic" + }, + "properties": { + "publicIPAllocationMethod": "Dynamic", + "publicIPAddressVersion": "IPv4", + "dnsSettings": { + "domainNameLabel": "[parameters('dnsLabelPrefix')]" + }, + "idleTimeoutInMinutes": 4 + } + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2023-09-01", + "name": "[parameters('vmName')]", + "location": "[parameters('location')]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'contributor-managed-identity')]": {} + } + }, + "properties": { + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "storageProfile": { + "osDisk": { + "createOption": "FromImage", + "deleteOption": "Delete", + "managedDisk": { + "storageAccountType": "[variables('osDiskType')]" + } + }, + "imageReference": "[variables('imageReference')[parameters('ubuntuOSVersion')]]" + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" + } + ] + }, + "osProfile": { + "computerName": "[parameters('vmName')]", + "adminUsername": "[parameters('adminUsername')]", + "adminPassword": "[parameters('adminPasswordOrKey')]", + "linuxConfiguration": "[if(equals(parameters('authenticationType'), 'password'), null(), variables('linuxConfiguration'))]" + }, + "securityProfile": "[if(equals(parameters('securityType'), 'TrustedLaunch'), variables('securityProfileJson'), null())]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]" + ] + }, + { + "condition": "[and(and(equals(parameters('securityType'), 'TrustedLaunch'), variables('securityProfileJson').uefiSettings.secureBootEnabled), variables('securityProfileJson').uefiSettings.vTpmEnabled)]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[format('{0}/{1}', parameters('vmName'), variables('extensionName'))]", + "location": "[parameters('location')]", + "properties": { + "publisher": "[variables('extensionPublisher')]", + "type": "[variables('extensionName')]", + "typeHandlerVersion": "[variables('extensionVersion')]", + "autoUpgradeMinorVersion": true, + "enableAutomaticUpgrade": true, + "settings": { + "AttestationConfig": { + "MaaSettings": { + "maaEndpoint": "[variables('maaEndpoint')]", + "maaTenantName": "[variables('maaTenantName')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]" + ] + } + ], + "outputs": { + "adminUsername": { + "type": "string", + "value": "[parameters('adminUsername')]" + }, + "adminPassword": { + "type": "string", + "value": "[parameters('adminPasswordOrKey')]" + }, + "hostname": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName')), '2023-09-01').dnsSettings.fqdn]" + }, + "sshCommand": { + "type": "string", + "value": "[format('ssh {0}@{1}', parameters('adminUsername'), reference(resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName')), '2023-09-01').dnsSettings.fqdn)]" + } + } + } \ No newline at end of file diff --git a/azure/challenges/Cloud Jumping/variables.tf b/azure/challenges/Cloud Jumping/variables.tf new file mode 100644 index 0000000..72dc9d0 --- /dev/null +++ b/azure/challenges/Cloud Jumping/variables.tf @@ -0,0 +1,11 @@ +variable "azure_region" { + type = string +} + +variable "player_upn" { + type = string +} + +variable "player_object_id" { + type = string +} \ No newline at end of file diff --git a/azure/challenges/Image-ination Continuation/image-inationcontinuation.tf b/azure/challenges/Image-ination Continuation/image-inationcontinuation.tf new file mode 100644 index 0000000..110af74 --- /dev/null +++ b/azure/challenges/Image-ination Continuation/image-inationcontinuation.tf @@ -0,0 +1,217 @@ +# resource group +resource "azurerm_resource_group" "imageinationcontinuation" { + name = "imageinationcontinuationRG" + location = var.azure_region +} + +resource "random_id" "suffix" { + byte_length = 4 +} + +resource "random_password" "sp_pwd" { + length = 20 + special = true +} + +data "azurerm_client_config" "current" {} + +# create a key vault +resource "azurerm_key_vault" "ctf" { + name = "ctfkv${random_id.suffix.hex}" + resource_group_name = azurerm_resource_group.imageinationcontinuation.name + location = azurerm_resource_group.imageinationcontinuation.location + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "standard" + purge_protection_enabled = false + enable_rbac_authorization = true +} + +# place the flag in the key vault +resource "azurerm_key_vault_secret" "flag" { + name = "flag" + value = "Flag{imageinationContinuation:protectThoseServicePrincipalCreds}" + key_vault_id = azurerm_key_vault.ctf.id + + depends_on = [azurerm_role_assignment.kv_admin] +} + +# create an identity for the logic app +resource "azurerm_user_assigned_identity" "logicapp_identity" { + name = "logicapp-mi-${random_id.suffix.hex}" + resource_group_name = azurerm_resource_group.imageinationcontinuation.name + location = azurerm_resource_group.imageinationcontinuation.location +} + +locals { + logicapp_name = "flag-logicapp-${random_id.suffix.hex}" +} + +# create a deployment template for the logic app +resource "azurerm_resource_group_template_deployment" "logicapp_with_flag" { + name = "logicapp-deploy-${random_id.suffix.hex}" + resource_group_name = azurerm_resource_group.imageinationcontinuation.name + deployment_mode = "Incremental" + + template_content = jsonencode({ + "$schema" = "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion" = "1.0.0.0", + "parameters" = {}, + "resources" = [ + { + "type" = "Microsoft.Logic/workflows", + "apiVersion" = "2019-05-01", + "name" = "${local.logicapp_name}", + "location" = azurerm_resource_group.imageinationcontinuation.location, + "identity" = { + "type" = "SystemAssigned" + }, + "properties" = { + "definition" = { + "$schema" = "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", + "actions" = { + "GetFlag" = { + "type" = "Http", + "inputs" = { + "method" = "GET", + "uri" = "${azurerm_key_vault_secret.flag.id}?api-version=7.2", + "authentication" = { + "type" = "ManagedServiceIdentity", + "audience": "https://vault.azure.net" + } + }, + "runAfter" = {} + }, + "Response" = { + "type" = "Response", + "inputs" = { + "body" = { + "flag" = "@body('GetFlag').value" + }, + "statusCode" = 200 + }, + "runAfter" = { + "GetFlag" = ["Succeeded"] + } + } + }, + "triggers" = { + "manual" = { + "type" = "Request", + "kind" = "Http", + "inputs" = { + "schema" = {} + } + } + }, + "outputs" = {} + }, + "state" = "Enabled" + } + } + ] + }) +} + +# provide admin permissions to the key vault for the terraform deployment user, which should be azure admin +resource "azurerm_role_assignment" "kv_admin" { + principal_id = data.azurerm_client_config.current.object_id + role_definition_name = "Key Vault Administrator" + scope = azurerm_key_vault.ctf.id +} + + + +# get the logic app identity and store in a local file +resource "null_resource" "get_logicapp_identity" { + provisioner "local-exec" { + command = < logicapp_identity.txt + EOT + interpreter = ["/bin/bash", "-c"] + } + + triggers = { + always_run = timestamp() + } + + depends_on = [azurerm_resource_group_template_deployment.logicapp_with_flag] +} + +data "local_file" "logicapp_identity" { + filename = "challenges/Image-ination Continuation/data/logicapp_identity.txt" + depends_on = [null_resource.get_logicapp_identity] +} + +# provide logic app with permissions to get the flag +resource "azurerm_role_assignment" "logicapp_kv_reader" { + principal_id = data.local_file.logicapp_identity.content + role_definition_name = "Key Vault Secrets User" + scope = azurerm_key_vault.ctf.id + + depends_on = [data.local_file.logicapp_identity] +} + +# create the logic app +resource "azuread_application" "sp_app" { + display_name = "ctf-logicapp-access" +} + +# create service principal +resource "azuread_service_principal" "sp" { + client_id = azuread_application.sp_app.client_id +} + +# create service principal password +resource "azuread_service_principal_password" "sp_secret" { + service_principal_id = azuread_service_principal.sp.id + end_date = timeadd(timestamp(), "720h") + + lifecycle { + create_before_destroy = true + } +} + +# give service principal Reader permissions in the environment +resource "azurerm_role_assignment" "sp_reader" { + principal_id = azuread_service_principal.sp.object_id + role_definition_name = "Reader" + scope = "/subscriptions/${data.azurerm_client_config.current.subscription_id}" +} + +# Give service principal logic app contributor permissions +resource "azurerm_role_assignment" "logicapp_reader" { + principal_id = azuread_service_principal.sp.object_id + role_definition_name = "Logic App Contributor" + scope = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/resourceGroups/${azurerm_resource_group.imageinationcontinuation.name}/providers/Microsoft.Logic/workflows/flag-logicapp-${random_id.suffix.hex}" + + depends_on = [azurerm_resource_group_template_deployment.logicapp_with_flag] +} + +# generate local file with service principal credentials, which get stored in the container image from the image-ination station challenge +resource "null_resource" "generate_sp_creds" { + triggers = { + password_version = azuread_service_principal_password.sp_secret.key_id + } + + provisioner "local-exec" { + command = < challenges/Image-ination\ Station/data/sp-creds.json +EOT + } +} + +output "some_output" { + value = azuread_service_principal_password.sp_secret.key_id +} \ No newline at end of file diff --git a/azure/challenges/Image-ination Continuation/variables.tf b/azure/challenges/Image-ination Continuation/variables.tf new file mode 100644 index 0000000..72dc9d0 --- /dev/null +++ b/azure/challenges/Image-ination Continuation/variables.tf @@ -0,0 +1,11 @@ +variable "azure_region" { + type = string +} + +variable "player_upn" { + type = string +} + +variable "player_object_id" { + type = string +} \ No newline at end of file diff --git a/azure/challenges/Image-ination Station/data/Dockerfile b/azure/challenges/Image-ination Station/data/Dockerfile new file mode 100644 index 0000000..8b2d626 --- /dev/null +++ b/azure/challenges/Image-ination Station/data/Dockerfile @@ -0,0 +1,7 @@ +FROM alpine:latest +RUN adduser -D alice +RUN mkdir -p /home/alice && echo "FLAG{imageinationStation:interestingStuffCanBeHiddenInImageFiles}" > /home/alice/flag.txt && chown -R alice:alice /home/alice +COPY sp-creds.json /tmp/sp-creds.json +RUN cat /tmp/sp-creds.json >> /home/alice/logic_app_creds.json +RUN apk add --no-cache bash coreutils curl wget iputils busybox-extras procps +CMD ["/bin/bash"] diff --git a/azure/challenges/Image-ination Station/image-inationstation.tf b/azure/challenges/Image-ination Station/image-inationstation.tf new file mode 100644 index 0000000..07c3656 --- /dev/null +++ b/azure/challenges/Image-ination Station/image-inationstation.tf @@ -0,0 +1,50 @@ +# resource group +resource "azurerm_resource_group" "imageinationstation" { + name = "imageinationstationRG" + location = var.azure_region +} + +resource "random_id" "suffix" { + byte_length = 4 +} + +# create container registry +resource "azurerm_container_registry" "acr" { + name = "myacr${random_id.suffix.hex}" + resource_group_name = azurerm_resource_group.imageinationstation.name + location = azurerm_resource_group.imageinationstation.location + sku = "Basic" + admin_enabled = true +} + +# push image from data folder to container registry +resource "null_resource" "docker_push" { + provisioner "local-exec" { + command = < + +Test Site + +

Hello World! This is just a test site to generate web requests. Enter the target URL below. Custom headers can be included if needed.

+

Additionally, for testing purposes the system environment variables can be found here. +

+
+

+ +
+ Key: + Value:

+ +
+ Key: + Value:

+ +
+ Key: + Value:

+ + +
+ + {% if result %} +
+

Response from {{ url }}:

+ Status: {{ status }}

+
{{ result }}
+ {% endif %} + + +""" + +@app.route("/", methods=["GET", "POST"]) +def index(): + result = status = url = None + + if request.method == "POST": + url = request.form.get("url") + headers = {} + + for i in range(1, 4): + key = request.form.get(f"header{i}_key") + value = request.form.get(f"header{i}_value") + if key and value: + headers[key] = value + + try: + response = requests.get(url, headers=headers, timeout=3) + status = response.status_code + result = response.text + except Exception as e: + status = "Error" + result = str(e) + + return render_template_string(form_template, result=result, status=status, url=url) + +@app.route("/env") +def show_env(): + env_vars = dict(os.environ) + formatted = "

Environment Variables

{}
".format( + "\n".join(f"{k}={v}" for k, v in sorted(env_vars.items())) + ) + return formatted + +if __name__ == '__main__': + app.run() +EOT +} + +# requirements.txt +resource "local_file" "requirements_txt" { + filename = "challenges/Permisery/data/requirements.txt" + content = < +resource "azuread_user" "ctf_user" { + display_name = "willem" + user_principal_name = "willem${var.domain}" + mail_nickname = "willem" + password = random_password.ctf_user_pw.result + account_enabled = true +} + +module "challenge_notsosecret" { + count = var.notsosecret_enabled ? 1 : 0 + source = "./challenges/Not so secret" + azure_region = var.azure_region + player_upn = azuread_user.ctf_user.user_principal_name + player_object_id = azuread_user.ctf_user.object_id +} + +module "challenge_permisery" { + count = var.permisery_enabled ? 1 : 0 + source = "./challenges/Permisery" + azure_region = var.azure_region + player_upn = azuread_user.ctf_user.user_principal_name + player_object_id = azuread_user.ctf_user.object_id +} + +module "challenge_image-inationcontinuation" { + count = var.imageination_enabled ? 1 : 0 + source = "./challenges/Image-ination Continuation" + azure_region = var.azure_region + player_upn = azuread_user.ctf_user.user_principal_name + player_object_id = azuread_user.ctf_user.object_id +} + +module "challenge_image-inationstation" { + count = var.imageination_enabled ? 1 : 0 + source = "./challenges/Image-ination Station" + azure_region = var.azure_region + player_upn = azuread_user.ctf_user.user_principal_name + player_object_id = azuread_user.ctf_user.object_id + depends_on = [module.challenge_image-inationcontinuation] + depends_on_output = module.challenge_image-inationcontinuation[0].some_output # needed to make sure stuff finishes before other stuff +} + +module "challenge_vmiam" { + count = var.vmiam_enabled ? 1 : 0 + source = "./challenges/VM I Am" + azure_region = var.azure_region + player_upn = azuread_user.ctf_user.user_principal_name + player_object_id = azuread_user.ctf_user.object_id +} + +module "challenge_cloudjumping" { + count = var.cloudjumping_enabled ? 1 : 0 + source = "./challenges/Cloud Jumping" + azure_region = var.azure_region + player_upn = azuread_user.ctf_user.user_principal_name + player_object_id = azuread_user.ctf_user.object_id +} + +locals { + challenge_catalog = [ + { + enabled = var.notsosecret_enabled + name = "Not So Secret" + cost = "~$0.01/month" + }, + { + enabled = var.permisery_enabled + name = "Permisery" + cost = "~$0/month" + }, + { + enabled = var.imageination_enabled + name = "Image-ination" + cost = "~$5/month" + }, + { + enabled = var.vmiam_enabled + name = "VM I Am" + cost = "~$15/month" + }, + { + enabled = var.cloudjumping_enabled + name = "Cloud Jumping" + cost = "~$15/month" + } + ] + + enabled_challenges = [ + for c in local.challenge_catalog : + format("| | %-31s | %-12s |", c.name, c.cost) + if c.enabled + ] +} + +output "Next_Steps" { + value = < 0 ? join("\n", local.enabled_challenges) : "| None | |"} +| +-------------------------------------------------+ +| ++--------- +EOT +} + +locals { + credentials_text = <<-EOT + client_id = ${azuread_user.ctf_user.user_principal_name} + password = ${random_password.ctf_user_pw.result} + EOT +} + +resource "local_file" "credentials" { + filename = "credentials.txt" + content = local.credentials_text + file_permission = "0600" +} \ No newline at end of file diff --git a/azure/terraform.tfvars.example b/azure/terraform.tfvars.example new file mode 100644 index 0000000..fbbcb4c --- /dev/null +++ b/azure/terraform.tfvars.example @@ -0,0 +1,37 @@ +###################### +# Azure Configuration +###################### + +# Set your subscription_id +subscription_id = "" + +# Set the region to which to deploy resources. The default region is westus2 +# azure_region = "" + +####################### +# User Configuration +####################### + +# Use this variable to set the domain for your CTF users. You can find this by logging into Azure, going to Users, and looking at the domain name associated with the User Principal Name of your user account. For example: @johndoe.onmicrosoft.com +domain = "" + +####################### +# User IP Configuration +####################### + +# Specify your IP address here to bre used with security groups (so that "public" challenges are only accessible to you) +# If you don't specify anything, the default behavior is to use https://icanhazip.com to pull your IP address +# user_ip = "[YOUR_IP_ADDRESS]" + +############################ +# Enabled/Disabled Challenges +############################ + +# enabled by default +notsosecret_enabled = true +permisery_enabled = true + +# disabled by default (cost) +imageination_enabled = false # about $5 per month / $0.17 per day +vmiam_enabled = false # about $15 per month / $0.50 per day +cloudjumping_enabled = false # about $15 per month / $0.50 per day \ No newline at end of file diff --git a/azure/variables.tf b/azure/variables.tf new file mode 100644 index 0000000..aaaeb37 --- /dev/null +++ b/azure/variables.tf @@ -0,0 +1,38 @@ +variable "azure_region" { + description = "The Azure region to deploy to" + type = string + default = "westus2" +} + +variable "subscription_id" { + type = string +} + +variable "domain" { + type = string +} + +variable "notsosecret_enabled" { + type = bool + default = true +} + +variable "permisery_enabled" { + type = bool + default = true +} + +variable "imageination_enabled" { + type = bool + default = false +} + +variable "vmiam_enabled" { + type = bool + default = false +} + +variable "cloudjumping_enabled" { + type = bool + default = false +} \ No newline at end of file