Azure Honeypot Lab

By Mohamed Ali Krichen

MS AzureMS SentinelMS DefenderLog AnalyticsLogic AppsKQL

This is not a step-by-step deployment tutorial, but rather a documentation of what I implemented and the results I achieved. While you may be able to follow along and set up your own deployment, some components and configurations are not fully detailed here, such as the Flask HTTP server code, workbook contents, Logic App properties, and the specific settings for Analytics Rules and Automation Rules.

1. Infrastructure Setup

The following diagram represents the end result of this deployment:

Azure Security Architecture Diagram

The lab starts by creating a dedicated Azure Resource Group called DalliLab, hosted in the France Central region. A resource group is a logical container in Azure that groups everything related to this project so it can be managed, monitored, and deleted as a single unit.

This will simulate a realistic corporate looking target Tunisbank as bait during the enumeration process, I will keep the naming persistent across all resources.

Virtual Network

A Virtual Network VNetLab is deployed next. The VNet defines the private network space the VM will live in and controls how it communicates with other Azure resources and the internet.

Virtual Machine — HR-Tunisbank

A Windows Server virtual machine named HR-Tunisbank is deployed inside the VNet.

Resource Group Overview

Looking back at the resource group after deployment, we can see every component Azure provisioned alongside the VM:

2. Opening the Attack Surface

For the honeypot to be effective, the VM needs to be reachable from anywhere on the internet with no filtering, both at the network level (NSG) and the host level (Windows Firewall).

Opening the NSG

The HR-Tunisbank-nsg is opened and a new inbound rule named Allow_all_any is added at priority 299. NSG rules are evaluated in priority order, lower numbers first. Since 299 sits above the existing RDP (300) and HTTP (320) rules, it overrides everything: any traffic from any source on any port is now allowed in. I am just keeping these rules for later use, they are currently useless.

Disabling the Windows Firewall

Even with the NSG wide open, Windows runs its own host-based firewall independently. To fully expose the VM, the Windows Firewall is disabled across all three profiles: Domain, Private, and Public.

Confirming External Reachability

With both layers of protection removed, a ping check confirms that the VM is accessible from the public internet.

3. Building the Detection Pipeline

With the honeypot exposed, the next step is building the infrastructure that will collect, store, and surface everything that happens to it.

Log Analytics Workspace

A Log Analytics Workspace named LogAnalytics-Lab is created. This is the backbone of the setup, a centralized cloud repository where logs from the VM are ingested, stored, and made queryable via KQL.

Connecting to Microsoft Sentinel

Microsoft Sentinel is enabled on top of the Log Analytics Workspace. Sentinel is Azure's cloud-native SIEM and SOAR platform which adds the intelligence and automation layer on top of raw log storage.

Windows Security Events Connector via AMA

The Windows Security Events via AMA connector is installed from the Sentinel Content Hub. This deploys an agent to the VM and creates a Data Collection Rule (DCR) that defines which Windows event channels forward and where to send them. Without this, logs stay local and never reach the workspace.

The connector scope is set to target HR-Tunisbank, creating and applying the DCR. After a short wait, the connector confirms a successful connection and the agent is installed, the DCR is active, and Security Events are flowing into the workspace in real time.

4. Validating Log Collection

Now to test the logging of this VM, I did a few deliberate wrong login attempts to generate Windows Security Event ID 4625 which is the standard event for a failed logon attempt.

Enabling Login Failure Auditing

The events do not show up in Event Viewer initially. Windows does not log failed logons by default it seems! The local audit policy needs to explicitly enable Audit Logon Events for failures.

Once enabled, filtering by Event ID 4625 shows the new attempts appearing.

Verifying in Log Analytics

Back to Azure, the same events are seen in Log Analytics using this KQL query, proving the VM is forwarding its Security Event logs to the central workspace via the DCR connector.

5. Geo IP Attack Map

With log collection confirmed, the next step is enriching the attack data geographically so we can see not just that someone failed to log in, but where in the world they were trying from.

Adding the GeoIP Watchlist

A Watchlist is added into Sentinel (more specifically to our Log Analytics) using a GeoIP CSV file that maps IP address ranges to city, country, latitude, and longitude (as seen in the preview in the screenshot below).

This watchlist can then be joined in KQL queries via _GetWatchlist("geoip") where geoip is the name of the watchlist.

SecurityEvent
| where EventID == 4625
| evaluate ipv4_lookup(_GetWatchlist("geoip"), IpAddress, network)

Creating the Attack Map Workbook

To create a visual attack map from our collected data that is now linked to real world locations instead of just IP addresses, we need to create a new Sentinel Workbook.

On the New Workbook interface : Edit the workbook> delete the existing predefined elements> +Add “add data source + visualization”> advanced editor> input the following json> apply> done editing> save the workbook> open in Azure

▸ Workbook JSON
{"type": 3,"content": {"version": "KqlItem/1.0","query": "let GeoIPDB_FULL = _GetWatchlist(\"geoip\");\nlet WindowsEvents = SecurityEvent;\nWindowsEvents | where EventID == 4625\n| order by TimeGenerated desc\n| evaluate ipv4_lookup(GeoIPDB_FULL, IpAddress, network)\n| summarize FailureCount = count() by IpAddress, latitude, longitude, cityname, countryname\n| project FailureCount, AttackerIp = IpAddress, latitude, longitude, city = cityname, country = countryname,\nfriendly_location = strcat(cityname, \" (\", countryname, \")\");","size": 3,"timeContext": {"durationMs": 2592000000},"queryType": 0,"resourceType": "microsoft.operationalinsights/workspaces","visualization": "map","mapSettings": {"locInfo": "LatLong","locInfoColumn": "countryname","latitude": "latitude","longitude": "longitude","sizeSettings": "FailureCount","sizeAggregation": "Sum","opacity": 0.8,"labelSettings": "friendly_location","legendMetric": "FailureCount","legendAggregation": "Sum","itemColorSettings": {"nodeColorField": "FailureCount","colorAggregation": "Sum","type": "heatmap","heatmapPalette": "greenRed"}}},"name": "query - 0"}

First Results

We now can see a word map with the 4 test failed logins that I performed earlier appearing attributed to Zagazig, Egypt instead of Tunisia. this is most likely to be due to a non-updated file content (usually most recent files are paid).

I created a second workbook and tried to download another ip-location file to correlate my data, but i was faced with a file size limit for the upload (csv file needs to be less than 4mb)

In the meantime, without advertising the VM’s public ip address, the workbook already shows 14 attacks from Milan, Italy. real automated scanners found the open RDP port on their own.

6. Results: After 24–48 Hours

24 Hours — ~20,000 Failed Login Attempts

Checking back after 24 hours, Log Analytics shows 19,177 entries for Event ID 4625. The table reveals bots cycling through common usernames — \ADMIN, \BACKUP, \ADMINISTRATOR, \SYSTEM ...

The attack map shows the geographic spread, concentrated in Europe.

I have also launched an HTTP server with Flask, intentional exposure and kept for a different future project.

HTTP connections are also hitting port 80, separate bots scanning for web vulnerabilities.

48 Hours — Global Scale

By 48 hours the volume has grown enormously across the globe. The top source is Maam, Netherlands with 26.4K attempts, followed by Milan, Italy at 17.2K. South Korea, Vietnam, Argentina, Spain, Peru, and India also appear. attack infrastructure distributed worldwide, converging on a single exposed Windows VM.

7. Defense Implementation

With the observation phase complete, defensive measures are put back in place following a defense-in-depth model.

Layer 1 — Remove the Allow-All NSG Rule

The Allow_all_any inbound rule is removed from the NSG, immediately closing off all non-explicitly-permitted inbound traffic (rdp and http).

Layer 2 — Re-Enable the Windows Firewall

The host-level firewall is turned back on across all profiles.

Layer 3 — Explicit VM Ports Allow-listing

On the local machine, I disabled all connections to all ports except for ports 80 and 3389 to only allow http and rdp connections. This could be considered overkill, but it is just to showcase another layer of the defense-in-depth security model.

Layer 4 — Microsoft Defender for Servers

Additionally on Azure, Defender for Servers could be enabled on the VM as an additional layer. It automatically monitors for RDP brute force, alerts on port scans and suspicious logons, detects malware, and feeds everything into Sentinel.

This is a paid service, as it is part of the Azure ecosystem and delivered through Microsoft’s cloud platform

8. Automated Incident Response

The final part of the lab closes the loop between detection and response automatically. The system detects a brute-force attempt and blocks the source IP in the NSG, all with no human intervention in the loop.

Step 1 — The NSG Deny Rule

A dedicated NSG deny rule named SentinelBlockedIPs is created at priority 100, the highest priority in the ruleset. It starts with a dummy IP (in my example 1.1.1.1) and moving forward its source IP list gets populated automatically by the automation process that will be created.

Step 2 — The Logic App Playbook

First of all, a Logic App named Block_IP_playbook is created that runs through these steps:

Enable Managed Identity

Before adding roles, the Playbook needs its own account in Azure Active Directory.

To ensure the Playbook has authority to modify the NSG, the Identity and Access Management (IAM) settings need to be configured correctly .The Playbook needs the Network Contributor role so it can execute actions on the NSG.

Azure uses Role-Based Access Control (RBAC) to grant the Playbook's managed identity permission to write changes to network resources.

Step 3: The Analytics Rule

The Detected RDP Brute Force analytic rule runs a KQL query looking for (in our example) 2 Event ID 4625 failures from the same IP within a time window. If the query returns a result (more than 0 results) this is considered as an incident registered by this analytics rule.

The automated response is an Automation Rule, it can be created from this window of the Analytics Rule wizard or from its own page (next step).

Step 4: The Automation Rule

Finally an Automation Rule is configured in Sentinel to trigger the playbook every time the incident named Detected RDP Brute Force is created.

The Complete Flow

Analytics Rule (NRT) detects ≥2 Event ID 4625 from same IP
         ↓
Sentinel incident created: "Detected RDP Brute Force"
         ↓
Automation Rule fires → Block_IP_playbook triggered
         ↓
Logic App extracts attacker IPs from incident entities
         ↓
SentinelBlockedIPs NSG rule updated with new IP
         ↓
All inbound traffic from that IP denied at priority 100

Every 5 minutes (the lowest rate that the scheduled query can be run at) the analytics rule runs the query, and the playbook will automatically block the extracted IP addresses from that query.

Switching to Near Real-Time Rules

The initial analytics rule ran every 5 minutes, this could leave a window of up to 5 minutes of brute force attempts to an attacker. Replacing it with an NRT (Near Real-Time) rule cuts the detection-to-block window to approximately one minute. The scheduled rule is disabled and the a new NRT rule is created (replaced the old rule’s name to keep overall change to a minimum).

The NSG block list is cleared before running a clean end-to-end test.

Live End-to-End Test

A few deliberate failed RDP logins are triggered from my personal machine (IP *.2.245.56).

The logs show the failed attempts at 2:25.

RDP connection attempts from the blocked machine are no longer allowed.

Conclusion

This lab walked through the full lifecycle of a cloud honeypot: provisioning intentionally exposed infrastructure, building a centralized SIEM pipeline with Log Analytics and Sentinel, visualizing attacker origins on a live geo map, hardening the system through defense-in-depth, and finally wiring an automated playbook to detect and block attackers at the NSG level without any manual step.

Each component — the NSG, Windows Firewall, AMA connector, GeoIP watchlist, analytics rule, Logic App, and automation rule — was configured to serve a specific purpose within the detection and response chain. The end result is a working SOC workflow running entirely in Azure, from raw log ingestion to automated firewall enforcement.