Back to Blog
Azure
4 min read

Building a WAF Monitoring Workbook with KQL

AzureWAFApplication GatewayKQLSecurity

Azure Application Gateway with WAF blocks thousands of attacks. But most organisations have no visibility into what's being blocked.

Here's how to build a monitoring workbook that shows you what your WAF is actually doing.

The Executive Summary Tile

Start with the numbers that matter:

AzureDiagnostics
| where ResourceType == "APPLICATIONGATEWAYS"
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| where TimeGenerated > ago(30d)
| summarize
    TotalAttacksBlocked = count(),
    UniqueAttackers = dcount(clientIp_s),
    AttacksPerDay = count() / 30

This gives you:

  • Total attacks blocked in the last 30 days
  • Number of unique attacker IPs
  • Average attacks per day

Put these in big number tiles at the top of your workbook.

Attack Trend Over Time

Show whether attacks are increasing or decreasing:

AzureDiagnostics
| where ResourceType == "APPLICATIONGATEWAYS"
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| summarize Attacks = count() by bin(TimeGenerated, 1d)
| render timechart

Spikes in this chart warrant investigation.

Attack Type Distribution

What kinds of attacks are you seeing?

AzureDiagnostics
| where ResourceType == "APPLICATIONGATEWAYS"
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| extend AttackType = case(
    ruleId_s startswith "942", "SQL Injection",
    ruleId_s startswith "941", "Cross-Site Scripting",
    ruleId_s startswith "913", "Scanner Detection",
    ruleId_s startswith "920", "Protocol Violation",
    ruleId_s startswith "931", "Remote File Inclusion",
    ruleId_s startswith "932", "Remote Command Execution",
    "Other"
)
| summarize Count = count() by AttackType
| render piechart

The rule ID prefixes correspond to OWASP CRS categories. This tells you what threats you're facing.

Top Attackers

Who's hitting you the most?

AzureDiagnostics
| where ResourceType == "APPLICATIONGATEWAYS"
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| summarize
    Attacks = count(),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
by clientIp_s
| top 20 by Attacks desc

Persistent attackers might warrant IP blocking at the firewall level.

Scanner and Bot Detection

Find automated scanners in your access logs:

AzureDiagnostics
| where ResourceType == "APPLICATIONGATEWAYS"
| where Category == "ApplicationGatewayAccessLog"
| where userAgent_s contains "scan"
    or userAgent_s contains "bot"
    or userAgent_s contains "crawler"
    or userAgent_s contains "nikto"
    or userAgent_s contains "sqlmap"
    or userAgent_s contains "nmap"
| summarize Count = count() by userAgent_s, clientIP_s
| order by Count desc

These might not all be blocked by WAF (some bots are legitimate), but it's good to know who's probing.

Real-Time Alert Status

A traffic light showing current threat level:

AzureDiagnostics
| where TimeGenerated > ago(1h)
| where ResourceType == "APPLICATIONGATEWAYS"
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| summarize RecentAttacks = count()
| extend AlertLevel = case(
    RecentAttacks > 1000, "CRITICAL",
    RecentAttacks > 500, "HIGH",
    RecentAttacks > 100, "ELEVATED",
    "NORMAL"
)
| project AlertLevel, RecentAttacks

The X-Forwarded-For Problem

If your Application Gateway is behind Azure Firewall, you'll only see the firewall's internal IP as the client IP. The real client IP is in the X-Forwarded-For header.

For custom rules that need to block specific IPs:

resource "azurerm_web_application_firewall_policy" "this" {
  custom_rules {
    name      = "BlockBadIPs"
    priority  = 1
    rule_type = "MatchRule"

    match_conditions {
      match_variables {
        variable_name = "RequestHeaders"
        selector      = "X-Forwarded-For"
      }
      operator           = "IPMatch"
      match_values       = ["1.2.3.4", "5.6.7.8"]
    }
    action = "Block"
  }
}

Use RequestHeaders with X-Forwarded-For selector instead of RemoteAddr.

Building the Workbook

  1. Go to Azure Monitor → Workbooks → New
  2. Add tiles using the queries above
  3. Set appropriate time ranges (30d for trends, 1h for alerts)
  4. Add parameters for time range and App Gateway selection
  5. Save and pin to a dashboard

Alerting

Don't just monitor - alert on anomalies:

AzureDiagnostics
| where TimeGenerated > ago(1h)
| where ResourceType == "APPLICATIONGATEWAYS"
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| summarize AttackCount = count()
| where AttackCount > 500

Create an alert rule that fires when this returns results.


Need help setting up WAF monitoring or tuning your rules? Get in touch - we help organisations get visibility into their security posture.

Need help with your Azure environment?

Get in touch for a free consultation.

Get in Touch