# T1190 - Exploit Public-Facing Application

## SOC Recommendation
Investigate Exploit Public-Facing Application activity in the context of Initial Access: confirm scope, affected host/identity, and whether it matches expected administrative behaviour before deciding this is benign.

## D3FEND Mappings
| D3FEND Technique | Relationship | Practical SOC Action |
|---|---|---|
| User Geolocation Logon Pattern Analysis | Detect | Monitor for User Geolocation Logon Pattern Analysis indicators relevant to this technique. |
| Process Segment Execution Prevention | Harden | Apply Process Segment Execution Prevention to reduce this technique's viability before an incident occurs. |
| Network Traffic Filtering | Isolate | Apply Network Traffic Filtering to contain the blast radius once this technique is observed. |

## Investigation Steps
1. project Timestamp, RemoteIP, RemotePort, InitiatingProcessFileName

## Evidence to Collect
- Host or resource affected
- Account or identity involved
- Timestamp of the activity
- Related process, file, or network artifact
- Any preceding or follow-on alerts

## Response Actions
| Action | Risk | Automation Safe | Approval Required |
|---|---|---|---|
| Isolate the web server if exploitation confirmed (web server | Medium | No | Yes |
| Enable WAF in Prevention mode if currently in Detection mode | Low | No | Yes |
| Block attacking IP(s) at firewall/WAF and in Sentinel TI wat | Medium | No | Yes |
| Preserve web server logs before any restart or patching | Low | No | Yes |
| Patch the exploited vulnerability if CVE identified | Low | No | Yes |
| Remove any web shells found (file write query above) | Low | No | Yes |
| Rotate all service account passwords with access from the we | Medium | No | Yes |
| Check for lateral movement into internal network | Low | Yes | No |

## KQL
```kql
let lookback = 1h;
let correlationWindow = 15m;
let webRoots = dynamic(["\\inetpub\\wwwroot", "\\wwwroot", "\\public_html", "\\htdocs", "\\site\\wwwroot"]);
let suspiciousExtensions = dynamic(["aspx", "ashx", "asmx", "jsp", "jspx", "php", "phtml", "pl", "py", "ps1", "cmd"]);
let suspiciousNames = dynamic(["shell", "cmd", "upload", "webadmin", "backdoor", "wso", "c99", "r57", "regeorg", "tunnel"]);
let approvedDeploymentTools = dynamic(["msdeploy.exe", "git.exe", "runner.exe", "octopus.tentacle.exe", "kudu.exe", "zipdeploy.exe"]);
let fileEventsSource =
    union isfuzzy=true DeviceFileEvents,
    (datatable(TimeGenerated:datetime, ActionType:string, DeviceId:string, DeviceName:string, FolderPath:string, FileName:string, SHA256:string, InitiatingProcessFileName:string, InitiatingProcessCommandLine:string, InitiatingProcessAccountName:string)[]);
let iisLogsSource =
    union isfuzzy=true W3CIISLog,
    (datatable(TimeGenerated:datetime, sComputerName:string, cIP:string, csMethod:string, csUriStem:string, csUserName:string, scStatus:int, csUserAgent:string)[]);
let suspiciousFileWrites =
    fileEventsSource
    | where TimeGenerated >= ago(lookback)
    | where ActionType in~ ("FileCreated", "FileModified", "FileRenamed")
    | where FolderPath has_any (webRoots)
    | extend FileExtension = tolower(tostring(split(FileName, ".")[-1])),
             FileNameLower = tolower(FileName),
             InitiatingProcessFileName = tostring(InitiatingProcessFileName)
    | where FileExtension in (suspiciousExtensions) or FileNameLower has_any (suspiciousNames)
    | where InitiatingProcessFileName !in~ (approvedDeploymentTools)
    | project WriteTime = TimeGenerated, DeviceId, DeviceName, FolderPath, FileName, FileExtension, SHA256,
              InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessAccountName;
let inboundWrites =
    iisLogsSource
    | where TimeGenerated >= ago(lookback)
    | where csMethod in~ ("POST", "PUT", "PATCH")
    | where csUriStem has_any ("/upload", "/admin", "/editor", "/api", "/wp-admin", "/owa", "/ecp")
    | project RequestTime = TimeGenerated, DeviceName = sComputerName, SrcIP = cIP, csMethod, csUriStem, csUserName, scStatus, csUserAgent;
suspiciousFileWrites
| join kind=leftouter inboundWrites on DeviceName
| where isnull(RequestTime) or abs(datetime_diff("minute", WriteTime, RequestTime)) <= toint(correlationWindow / 1m)
| extend CorrelatedInboundRequest = isnotempty(RequestTime)
| project
    TimeGenerated = coalesce(RequestTime, WriteTime),
    WriteTime,
    RequestTime,
    DeviceId,
    DeviceName,
    FolderPath,
    FileName,
    FileExtension,
    SHA256,
    InitiatingProcessFileName,
    InitiatingProcessCommandLine,
    InitiatingProcessAccountName,
    CorrelatedInboundRequest,
    SrcIP,
    csMethod,
    csUriStem,
    csUserName,
    scStatus,
    csUserAgent
| extend timestamp = TimeGenerated,
         HostCustomEntity = DeviceName,
         AccountCustomEntity = InitiatingProcessAccountName,
         IPCustomEntity = SrcIP
| order by TimeGenerated desc
```
```kql
let Threshold = 3;
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Matched"
| project transactionId_g, hostname_s, requestUri_s, TimeGenerated, clientIp_s, Message, details_message_s, details_data_s
| join kind = inner(
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| parse Message with MessageText 'Total Inbound Score: ' TotalInboundScore ' - SQLI=' SQLI_Score ',XSS=' XSS_Score ',RFI=' RFI_Score ',LFI=' LFI_Score ',RCE=' RCE_Score ',PHPI=' PHPI_Score ',HTTP=' HTTP_Score ',SESS=' SESS_Score '): ' Blocked_Reason '; individual paranoia level scores:' Paranoia_Score
| where Blocked_Reason contains "SQL Injection Attack" and toint(SQLI_Score) >=10 and toint(TotalInboundScore) >= 15) on transactionId_g
| extend Uri = strcat(hostname_s,requestUri_s)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), TransactionID = make_set(transactionId_g), Message = make_set(Message), Detail_Message = make_set(details_message_s), Detail_Data = make_set(details_data_s), Total_TransactionId = dcount(transactionId_g) by clientIp_s, Uri, action_s, SQLI_Score, TotalInboundScore
| where Total_TransactionId >= Threshold
```
```kql
let threshold = 3;
PulseConnectSecure
| where Messages contains "Unauthenticated request url /dana-na/"
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by Source_IP
| where count_ > threshold
```

## Escalation Criteria
- Exploit Public-Facing Application activity observed on a privileged account or critical system.
- Activity follows or precedes other suspicious behaviour in the same investigation.
- Automated triage cannot confidently rule out malicious intent.

## False Positive Considerations
- Authorised penetration test — Suppress WAF detection blocks during confirmed pentest window
- Legitimate automation using curl/wget — Exclude specific signed management processes

Generated by SOC Response Atlas by Basyrix.
