ericaghdz/threat-hunting-scenario-publicly-exposed-vm
GitHub: ericaghdz/threat-hunting-scenario-publicly-exposed-vm
Stars: 0 | Forks: 0
# Official [Cyber Range](http://joshmadakor.tech/cyber-range) Project
# Threat Hunting Scenario #1: Searching for Brute Force Attempts on a Publicly Exposed Virtual Machine
In this project, we simulated a scenario to seek out Virtual Machines running on the Microsoft Azure platform that may have been mistakenly exposed to the public internet. As a Security Analyst, I was tasked with identifying which of these machines, if any, were exposed. Once identified, I searched through various logs for signs of successful brute force attacks.
# Technology Utilized
- Microsoft Defender for Endpoint | Endpoint Detection & Response (EDR) platform
- Kusto Query Language (KQL) | Log Querying & Analytics
- Microsoft Azure Virtual Machines (VM)
# Cybersecurity Framework
- MITRE ATT&CK Framework | Mapping to relevant tactics, techniques, & procedures (TTPs)
## Table of Contents
* [Step 1: Confirm Internet Exposure of Target Device](#step-1-confirm-internet-exposure-of-target-device)
* [Step 2: Identify Top Source IPs Associated with Failed Logon Attempts](#step-2-identify-top-source-ips-associated-with-failed-logon-attempts)
* [Step 3: Determine Whether Top Failed IPs Successfully Authenticated](#step-3-determine-whether-top-failed-ips-successfully-authenticated)
* [Step 4: Validate Successful Logons to Legitimate User Account](#step-4-validate-successful-logons-to-legitimate-user-account)
* [Step 5: Review Successful Authentication Activity](#step-5-review-successful-authentication-activity)
* [Conclusion](#conclusion)
* [Relevant MITRE ATT&CK Techniques](#relevant-mitre-attck-techniques)
* [Response and Mitigation Actions](#response-and-mitigation-actions)
* [Threat Hunt Process Improvements](#threat-hunt-process-improvements)
## Step 1: Confirm Internet Exposure of Target Device
A query was executed to determine whether the target system had been publicly exposed to the internet.
### KQL Query
// Confirm whether target device has been internet-facing
let honeypot = "windows-target-";
DeviceInfo
| where DeviceName == honeypot
| where IsInternetFacing == true
| order by Timestamp desc
### Findings
The `windows-target-1` VM was confirmed to be internet-facing for several consecutive days, increasing exposure to unsolicited external authentication attempts and potential malicious activity.
**Last observed internet-facing timestamp:**
- May 26, 2026 6:59:51 PM
**Approximate investigation time:**
- May 26, 2026 7:03:55 PM
## Step 2: Identify Top Source IPs Associated with Failed Logon Attempts
A query was conducted to identify the top external IP addresses responsible for failed authentication attempts against the `windows-target-1` VM over the previous seven days.
### KQL Query
// Retrieve top 10 IPs involved in failed logon attempts to windows-target-1
let honeypot = "windows-target-";
DeviceLogonEvents
| where DeviceName == honeypot
| where LogonType has_any ("Network", "Interactive", "RemoteInteractive", "Unlock")
| where ActionType == "LogonFailed"
| summarize Attempts = count() by ActionType, RemoteIP, DeviceName
| order by Attempts desc
| take 10
### Findings
The following IP addresses generated the highest number of failed authentication attempts within the previous seven days:
| Remote IP | Failed Attempts |
|------------|----------------|
| `159.100.20.23` | 64 |
| `51.178.174.31` | 61 |
| `102.88.21.214` | 55 |
| `54.151.176.0` | 47 |
| `188.246.226.124` | 46 |
| `135.125.90.97` | 36 |
| `45.238.132.30` | 32 |
| `95.213.184.95` | 31 |
| `211.229.255.252` | 29 |
| `188.68.217.132` | 23 |
The repeated failed logon attempts from multiple external IP addresses are consistent with **brute force password spraying or credential guessing activity** targeting an exposed system.
## Step 3: Determine Whether Top Failed IPs Successfully Authenticated
### KQL Query
// Investigate whether top failed IPs later succeeded in authentication
let RemoteIPs = dynamic([
"159.100.20.23",
"51.178.174.31",
"102.88.21.214",
"54.151.176.0",
"188.246.226.124",
"135.125.90.97",
"45.238.132.30",
"95.213.184.95",
"211.229.255.252",
"188.68.217.132"
]);
DeviceLogonEvents
| where LogonType has_any ("Network", "Interactive", "RemoteInteractive", "Unlock")
| where ActionType == "LogonSuccess"
| where RemoteIP has_any(RemoteIPs)
### Findings
No successful authentication attempts were returned from the query results.
This indicates that **none of the top 10 external IP addresses responsible for failed logon activity successfully authenticated to the system**, reducing the likelihood of a successful brute force compromise originating from these sources.
## Step 4: Validate Successful Logons to Legitimate User Account
The investigation shifted toward validating all successful network logons associated with the legitimate user account `labuser0`.
### KQL Query — Successful Logons
// Determine successful logon attempts to labuser0
DeviceLogonEvents
| where LogonType == "Network"
| where ActionType == "LogonSuccess"
| where DeviceName == "windows-target-"
| where AccountName == "labuser0"
| summarize count()
### Findings
A total of **3 successful logons** to the `labuser0` account were identified on `windows-target-1` during the previous 30 days.
### KQL Query — Failed Logons
// Determine failed logon attempts to labuser0
DeviceLogonEvents
| where LogonType == "Network"
| where ActionType == "LogonFailed"
| where DeviceName == "windows-target-"
| where AccountName == "labuser0"
| summarize count()
### Findings
A total of **0 failed authentication attempts** were observed against the `labuser0` account during the previous 30 days.
This significantly reduces the likelihood that the legitimate account was targeted or compromised through brute force activity.
## Step 5: Review Successful Authentication Activity
A final review was conducted on successful authentication events associated with the `labuser0` account to determine whether any suspicious characteristics were present.
### KQL Query
// Investigate successful logons to labuser0
DeviceLogonEvents
| where LogonType == "Network"
| where ActionType == "LogonSuccess"
| where DeviceName == "windows-target-"
| where AccountName == "labuser0"
| summarize LogonCount = count() by DeviceName, ActionType, AccountName, RemoteIP
### Findings
Manual review of all three successful logon events associated with `labuser0` showed normal and expected activity. No indicators of suspicious behavior, anomalous access patterns, or unauthorized usage were identified.
## Conclusion
Although `windows-target-1` was publicly exposed to the internet and experienced repeated failed authentication attempts indicative of brute force behavior, there is **no evidence of successful unauthorized access** at this time.
Key conclusions include:
- The VM was internet-facing for multiple days, increasing attack surface exposure.
- Multiple external IP addresses attempted repeated failed authentications consistent with brute force or credential guessing activity.
- None of the highest-volume offending IP addresses successfully authenticated.
- The legitimate account `labuser0` experienced only successful, expected logons with no associated failed attempts.
- No suspicious activity was identified during review of authenticated sessions.
Based on available telemetry, the activity appears to represent **unsuccessful brute force attempts against an exposed system rather than a confirmed compromise**.
## Relevant MITRE ATT&CK Techniques
| Technique ID | Technique | Relevance |
|--------------|-----------|------------|
| **T1190** | Exploit Public-Facing Application | Target system was internet-facing and externally exposed |
| **T1110** | Brute Force | Repeated failed authentication attempts from multiple IP addresses |
| **T1078** | Valid Accounts | Successful logons observed from the legitimate `labuser0` account |
## Response and Mitigation Actions
Although no successful compromise was identified, several hardening measures were implemented to reduce future risk exposure:
- Restricted inbound Remote Desktop Protocol (RDP) access using an Azure Network Security Group (NSG) to allow only authorized connections.
- Implemented an account lockout policy to limit repeated authentication failures and mitigate brute force attempts.
- Reduced the VM’s external attack surface by limiting unnecessary internet exposure and strengthening access controls.
## Threat Hunt Process Improvements
After review of the threat hunt process, an area of improvement in KQL syntax was identified in `Step 3: Determine Whether Top Failed IPs Successfully Authenticated`.
Recall that the KQL query used in Step 3 was used to investigate the list of 10 IPs with the highest failed logon attempts.
// Investigate whether top failed IPs later succeeded in authentication
let RemoteIPs = dynamic([
"159.100.20.23",
"51.178.174.31",
"102.88.21.214",
"54.151.176.0",
"188.246.226.124",
"135.125.90.97",
"45.238.132.30",
"95.213.184.95",
"211.229.255.252",
"188.68.217.132"
]);
DeviceLogonEvents
| where LogonType has_any ("Network", "Interactive", "RemoteInteractive", "Unlock")
| where ActionType == "LogonSuccess"
| where RemoteIP has_any(RemoteIPs)
Although this query worked sufficiently for the purposes of this threat hunt, the line `| where RemoteIP has_any(RemoteIPs)` should be `| where RemoteIP in (RemoteIPs)`.
Why?
- `has_any` is generally used for searching inside multi-value fields or text blobs
- `in` is more appropriately used for explicit value matching against a list
This slight change in syntax will be incorporated into future threat hunts for cleaner and semantically clearer querying.
The following IP addresses generated the highest number of failed authentication attempts within the previous seven days:
| Remote IP | Failed Attempts |
|------------|----------------|
| `159.100.20.23` | 64 |
| `51.178.174.31` | 61 |
| `102.88.21.214` | 55 |
| `54.151.176.0` | 47 |
| `188.246.226.124` | 46 |
| `135.125.90.97` | 36 |
| `45.238.132.30` | 32 |
| `95.213.184.95` | 31 |
| `211.229.255.252` | 29 |
| `188.68.217.132` | 23 |
The repeated failed logon attempts from multiple external IP addresses are consistent with **brute force password spraying or credential guessing activity** targeting an exposed system.
## Step 3: Determine Whether Top Failed IPs Successfully Authenticated
### KQL Query
// Investigate whether top failed IPs later succeeded in authentication
let RemoteIPs = dynamic([
"159.100.20.23",
"51.178.174.31",
"102.88.21.214",
"54.151.176.0",
"188.246.226.124",
"135.125.90.97",
"45.238.132.30",
"95.213.184.95",
"211.229.255.252",
"188.68.217.132"
]);
DeviceLogonEvents
| where LogonType has_any ("Network", "Interactive", "RemoteInteractive", "Unlock")
| where ActionType == "LogonSuccess"
| where RemoteIP has_any(RemoteIPs)
### Findings
No successful authentication attempts were returned from the query results.
This indicates that **none of the top 10 external IP addresses responsible for failed logon activity successfully authenticated to the system**, reducing the likelihood of a successful brute force compromise originating from these sources.
## Step 4: Validate Successful Logons to Legitimate User Account
The investigation shifted toward validating all successful network logons associated with the legitimate user account `labuser0`.
### KQL Query — Successful Logons
// Determine successful logon attempts to labuser0
DeviceLogonEvents
| where LogonType == "Network"
| where ActionType == "LogonSuccess"
| where DeviceName == "windows-target-"
| where AccountName == "labuser0"
| summarize count()
### Findings
A total of **3 successful logons** to the `labuser0` account were identified on `windows-target-1` during the previous 30 days.
### KQL Query — Failed Logons
// Determine failed logon attempts to labuser0
DeviceLogonEvents
| where LogonType == "Network"
| where ActionType == "LogonFailed"
| where DeviceName == "windows-target-"
| where AccountName == "labuser0"
| summarize count()
### Findings
A total of **0 failed authentication attempts** were observed against the `labuser0` account during the previous 30 days.
This significantly reduces the likelihood that the legitimate account was targeted or compromised through brute force activity.
## Step 5: Review Successful Authentication Activity
A final review was conducted on successful authentication events associated with the `labuser0` account to determine whether any suspicious characteristics were present.
### KQL Query
// Investigate successful logons to labuser0
DeviceLogonEvents
| where LogonType == "Network"
| where ActionType == "LogonSuccess"
| where DeviceName == "windows-target-"
| where AccountName == "labuser0"
| summarize LogonCount = count() by DeviceName, ActionType, AccountName, RemoteIP
### Findings
Manual review of all three successful logon events associated with `labuser0` showed normal and expected activity. No indicators of suspicious behavior, anomalous access patterns, or unauthorized usage were identified.
## Conclusion
Although `windows-target-1` was publicly exposed to the internet and experienced repeated failed authentication attempts indicative of brute force behavior, there is **no evidence of successful unauthorized access** at this time.
Key conclusions include:
- The VM was internet-facing for multiple days, increasing attack surface exposure.
- Multiple external IP addresses attempted repeated failed authentications consistent with brute force or credential guessing activity.
- None of the highest-volume offending IP addresses successfully authenticated.
- The legitimate account `labuser0` experienced only successful, expected logons with no associated failed attempts.
- No suspicious activity was identified during review of authenticated sessions.
Based on available telemetry, the activity appears to represent **unsuccessful brute force attempts against an exposed system rather than a confirmed compromise**.
## Relevant MITRE ATT&CK Techniques
| Technique ID | Technique | Relevance |
|--------------|-----------|------------|
| **T1190** | Exploit Public-Facing Application | Target system was internet-facing and externally exposed |
| **T1110** | Brute Force | Repeated failed authentication attempts from multiple IP addresses |
| **T1078** | Valid Accounts | Successful logons observed from the legitimate `labuser0` account |
## Response and Mitigation Actions
Although no successful compromise was identified, several hardening measures were implemented to reduce future risk exposure:
- Restricted inbound Remote Desktop Protocol (RDP) access using an Azure Network Security Group (NSG) to allow only authorized connections.
- Implemented an account lockout policy to limit repeated authentication failures and mitigate brute force attempts.
- Reduced the VM’s external attack surface by limiting unnecessary internet exposure and strengthening access controls.
## Threat Hunt Process Improvements
After review of the threat hunt process, an area of improvement in KQL syntax was identified in `Step 3: Determine Whether Top Failed IPs Successfully Authenticated`.
Recall that the KQL query used in Step 3 was used to investigate the list of 10 IPs with the highest failed logon attempts.
// Investigate whether top failed IPs later succeeded in authentication
let RemoteIPs = dynamic([
"159.100.20.23",
"51.178.174.31",
"102.88.21.214",
"54.151.176.0",
"188.246.226.124",
"135.125.90.97",
"45.238.132.30",
"95.213.184.95",
"211.229.255.252",
"188.68.217.132"
]);
DeviceLogonEvents
| where LogonType has_any ("Network", "Interactive", "RemoteInteractive", "Unlock")
| where ActionType == "LogonSuccess"
| where RemoteIP has_any(RemoteIPs)
Although this query worked sufficiently for the purposes of this threat hunt, the line `| where RemoteIP has_any(RemoteIPs)` should be `| where RemoteIP in (RemoteIPs)`.
Why?
- `has_any` is generally used for searching inside multi-value fields or text blobs
- `in` is more appropriately used for explicit value matching against a list
This slight change in syntax will be incorporated into future threat hunts for cleaner and semantically clearer querying.