// Illustrative; not executed against a live workspace. Azure Monitor Logs: resource-specific AGWAccessLogs. // Slow, wide probing of the doors used in TrendAI's three Japan cases: admin panels, WebLogic, edge-device login // pages, GIS servers, AI endpoints, and configuration or backup files. A platform that throttles its traffic to // each target stays under rate limits, so this looks for breadth over hours rather than volume. If a CDN or // another proxy fronts the gateway, ClientIp is that proxy. Review candidates, not verdicts. let Window = 7d; let AuthorisedScanners = dynamic(["REPLACE_WITH_AUTHORISED_SCANNER_IP"]); let Probes = AGWAccessLogs | where TimeGenerated > ago(Window) | where not(ClientIp in (AuthorisedScanners)) | extend Path = tolower(tostring(split(RequestUri, "?")[0])) | extend Door = case( Path matches regex @"^/(arcgis/(admin|manager|rest/admin)|geoserver/(web|rest))", "GIS server", Path matches regex @"^/(v1/(models|chat/completions|completions|embeddings)|api/(tags|generate|ps)|rest/workflows|console/api)", "AI endpoint", Path matches regex @"^/(remote/login|\+cscoe\+/|vpn/index\.html|logon/logonpoint|tmui/|mgmt/tm/)", "Edge-device login", Path matches regex @"^/(console/login|wls-wsat|_async|bea_wls_internal)", "WebLogic", Path matches regex @"(^/\.env|/\.git/|/web\.config|/wp-config\.php|\.(bak|old|sql|tar|tgz|zip|7z)$|^/backup)", "Config or backup file", Path matches regex @"^/(admin|administrator|manager|phpmyadmin|actuator|server-status)(/|$)", "Admin panel", "") | where isnotempty(Door); let Pace = Probes | summarize PerMinute = count() by ClientIp, Minute = bin(TimeGenerated, 1m) | summarize PeakPerMinute = max(PerMinute), ActiveMinutes = count() by ClientIp; Probes | summarize Requests = count(), Doors = make_set(Door), Paths = dcount(Path), SamplePaths = make_set(Path, 15), Statuses = make_set(HttpStatus, 10), Hosts = make_set(Host, 10), UserAgents = make_set(UserAgent, 5), ActiveHours = dcount(bin(TimeGenerated, 1h)), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by ClientIp | join kind=inner Pace on ClientIp | extend DoorCount = array_length(Doors) | where DoorCount >= 2 and Paths >= 5 | extend Throttled = PeakPerMinute <= 30 and ActiveHours >= 3, GotA200 = set_has_element(Statuses, 200) | project-away ClientIp1 | order by Throttled desc, DoorCount desc, Paths desc