SOAR: Block Log Analytics IP Entities on Azure Frontdoor / WAF #2

Use this function and logic app, to block abusive IP addresses using a custom rule, that hit a certain block limit on Azure WAF / Frontdoor. Leveraging Log Analytics Alerts.

This greatly improves security for your origins, as threat actors won’t be able to scan indefinitely (or until rate limit is reached) for vulnerabilities.

In a previous blog I published details on how to block IP addresses from Sentinel. I recently found out Sentinel has a limit of max 10 entities, making it less suitable for sites with more traffic and frequent attacks.

For this reason, I’ve modified the script to block IP addresses straight from Log Analytics. Below step by step on how to set this up!

Azure AD App Registration
Create an App Registration in Azure AD. This will allow you to create a secret that you can use to authenticate your function app. Make sure to save the tenant ID, application ID, and secret for later use.

Set Access Rights on WAF Policy
Assign the App Registration you created in the previous step, owner rights on the WAF Policy that you want to update. This will give the function app the necessary permissions to modify the policy.

Azure Function App

Create a new .NET Function App.

Grab the code published on github, and Update the code in the cs file with your specific values. This will include the tenant ID, application ID and secret.


Create a new function, with an HTTP trigger, and name the function “BlockWAFIPLogAnalytics”, set Authorization level to “Anonymous.

If you use Visual Studio you can use that to push the app, if not, select “Develop in portal”.

Click on “Code + Test” & paste the code edited earlier as shown below.

public static string secret = "";
public static string appid = "";
public static string tenantid = "";
public static string azureid = "Azure Subscription ID";
public static string resgrp = "ResourceGroup";
public static string wafpolicyname = "WAFPolicyName";

Hit Save, go back to Overview, and copy the Function URL for later use.

Storage Account

In Azure, Create an Storage Account and a (default) table, that we will use to store the entities to be blocked in the WAF policies.

Logic App

In order to get the entities from Log Analytics Alerts to the tables in the storage accounts, and finally to the Function App, we need an Azure Logic App. Create a new Logic App with HTTP trigger.

Paste this json as the Body JSON Schema: LogicAppTrigger.json

Copy the HTTP POST URL, we need this later for Alerting.

The next part is somewhat difficult to recreate, and you’ll need to setup an connection to the Storage Account created earlier.

And finally, we add an delay, get all the entities currently in the storage account, and send it all over to the function app URL to update the WAF Policy.

Log Analytics

Create an new Alert rule in Azure Monitor Monitor – Microsoft Azure.

  • Paste the search query below
  • Dimension name = ClientIP
  • Threshold value = greater than 0
  • 5 minutes evaluation
  • Toggle advanced options & set time query range to 1 day (or longer/shorter, depending on how long you want to block ip addresses)
| where ResourceProvider == "MICROSOFT.CDN"
and Category == "FrontDoorWebApplicationFirewallLog"
and action_s == "Block"
| summarize RequestCount = count() by ClientIP = clientIP_s, UserAgent = userAgent_s, Resource
| where RequestCount > 500
| order by RequestCount desc

WAF Policy

The last peace of the puzzle! To complete the setup, you will need to add a custom rule “BlockedIPs” to the WAF Policy. This rule will be used to block the IP addresses identified by the Azure Sentinel analytic rule. Set it to Match type IP address & Does contain + a bogus IP address.

The function app will receive the IP addresses from the logic app received by the Log Analytics Alert. Any values in this rule will be overwritten with values received.


Below a example of requests that are blocked that would not have been blocked by any other rule.

Test with Postman

You can use Postman to send json sample data to test the functionality.

Post to Logic App URL:

    "searchQuery": "AzureDiagnostics\n| where ResourceProvider == \"MICROSOFT.CDN\"\n    and Category == \"FrontDoorWebApplicationFirewallLog\"\n    and action_s == \"Block\"\n| summarize RequestCount = count() by ClientIP = clientIP_s, UserAgent = userAgent_s, Resource\n| where RequestCount > 500\n| order by RequestCount desc\n\n",
    "metricMeasureColumn": null,
    "targetResourceTypes": null,
    "operator": "GreaterThan",
    "threshold": "0",
    "timeAggregation": "Count",
    "dimensions": [
        "name": "ClientIP",
        "value": ""
    "metricValue": 1,
    "failingPeriods": {
      "numberOfEvaluationPeriods": 1,
      "minFailingPeriodsToAlert": 1
    "linkToSearchResultsUI": "",
    "linkToFilteredSearchResultsUI": "Z",
    "linkToSearchResultsAPI": "",
    "linkToFilteredSearchResultsAPI": ""

Post to Function App URL:

  "odata.metadata": "$metadata#WAFBlockIPS",
  "value": [
      "odata.etag": "W/\"datetime'2023-05-19T23%3A55%3A35.3378043Z'\"",
      "PartitionKey": "",
      "RowKey": "BLOCK",
      "Timestamp": "2023-05-19T23:55:35.3378043Z"

All done!
Keep an eye on the Azure Alerts, and optionally set an email notification. You’re just greatly improved the security for you web apps!

C# Code:

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Collections.Generic;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Linq;
using System.Text;

namespace BlockWAFIP
    public class http
        public static string secret = "";
        public static string appid = "";
        public static string tenantid = "";
        public static string azureid = "";
        public static string resgrp = "";
        public static string wafpolicyname = "";

        public static string token { get; set; }
        public static string accessToken { get; set; }

        public static HttpClient httpclient { get; set; }

        public static async Task GetToken()
            ClientCredential credential = new ClientCredential(appid, secret);
            AuthenticationContext authContext = new AuthenticationContext($"" + tenantid);
            AuthenticationResult authResult = await authContext.AcquireTokenAsync("", credential);

            // Use the access token to authenticate to Azure resources
            accessToken = authResult.AccessToken;


    public static class BlockWAFIP
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
            log.LogInformation("C# HTTP trigger function processed a request.");

                await http.GetToken();

                //get post
                string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
                var entities = JsonConvert.DeserializeObject<Entitie.EntitieRoot>(requestBody);

                var client = new HttpClient();
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", http.accessToken);
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                HttpResponseMessage response = await client.GetAsync("" + http.azureid + "/resourceGroups/" + http.resgrp + "/providers/Microsoft.Network/frontdoorWebApplicationFirewallPolicies/" + http.wafpolicyname + "?api-version=2020-11-01");

                string result = await response.Content.ReadAsStringAsync();

                var currentwaf = JsonConvert.DeserializeObject<WAF.WAFRoot>(result);

                var newwaf = new WAF.WAFRoot();

                newwaf.location = currentwaf.location;
       = new WAF.Properties();
                newwaf.sku = currentwaf.sku;

                var otherrules = => != "BlockedIPs");
                var blockedIPs = => == "BlockedIPs");

                var matchConditions = new List<WAF.MatchCondition>();

                var ips = entities.value.Select(entity => entity.PartitionKey.ToString()).ToList();

                if (blockedIPs.matchConditions.FirstOrDefault().matchValue.SequenceEqual(ips))
                    return new OkObjectResult("ok");
                blockedIPs.matchConditions.FirstOrDefault().matchValue = ips;

                var rules = (otherrules).ToList();
       = rules;
                var post = JsonConvert.SerializeObject(newwaf, Formatting.None, new JsonSerializerSettings
                    NullValueHandling = NullValueHandling.Ignore
                var poststring = post.ToString();
                var postresponse = await client.PutAsync("" + http.azureid + "/resourceGroups/" + http.resgrp + "/providers/Microsoft.Network/frontdoorWebApplicationFirewallPolicies/" + http.wafpolicyname + "?api-version=2020-11-01", new StringContent(post, Encoding.UTF8, "application/json"));

            catch (Exception e)

            return new OkObjectResult("ok");

namespace Entitie
    public class EntitieRoot
        public string odatametadata { get; set; }
        public List<Value> value { get; set; }

    public class Value
        public string odataetag { get; set; }
        public string PartitionKey { get; set; }
        public object RowKey { get; set; }
        public DateTime Timestamp { get; set; }

namespace WAF
    // Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
    public class CustomRules
        public List<Rule> rules { get; set; }

    public class Exclusion
        public string matchVariable { get; set; }
        public string selectorMatchOperator { get; set; }
        public string selector { get; set; }

    public class ManagedRules
        public List<ManagedRuleSet> managedRuleSets { get; set; }

    public class ManagedRuleSet
        public string ruleSetType { get; set; }
        public string ruleSetVersion { get; set; }
        public string ruleSetAction { get; set; }
        public List<RuleGroupOverride> ruleGroupOverrides { get; set; }
        public List<Exclusion> exclusions { get; set; }

    public class MatchCondition
        public string matchVariable { get; set; }
        public string selector { get; set; }
        public string @operator { get; set; }
        public bool negateCondition { get; set; }
        public List<string> matchValue { get; set; }
        public List<string> transforms { get; set; }

    public class PolicySettings
        public string enabledState { get; set; }
        public string mode { get; set; }
        public object redirectUrl { get; set; }
        public object customBlockResponseStatusCode { get; set; }
        public object customBlockResponseBody { get; set; }
        public string requestBodyCheck { get; set; }

    public class Properties
        public PolicySettings policySettings { get; set; }
        public CustomRules customRules { get; set; }
        public ManagedRules managedRules { get; set; }
        public List<object> frontendEndpointLinks { get; set; }
        public List<SecurityPolicyLink> securityPolicyLinks { get; set; }
        public List<object> routingRuleLinks { get; set; }
        public string resourceState { get; set; }
        public string provisioningState { get; set; }

    public class WAFRoot
        public string id { get; set; }
        public string type { get; set; }
        public string name { get; set; }
        public string location { get; set; }
        public Tags tags { get; set; }
        public Sku sku { get; set; }
        public Properties properties { get; set; }

    public class Rule
        public string name { get; set; }
        public string enabledState { get; set; }
        public int? priority { get; set; }
        public string ruleType { get; set; }
        public int? rateLimitDurationInMinutes { get; set; }
        public int? rateLimitThreshold { get; set; }
        public List<MatchCondition> matchConditions { get; set; }
        public string action { get; set; }
        public string ruleId { get; set; }
        public List<Exclusion> exclusions { get; set; }

    public class RuleGroupOverride
        public string ruleGroupName { get; set; }
        public List<Rule> rules { get; set; }
        public List<Exclusion> exclusions { get; set; }

    public class SecurityPolicyLink
        public string id { get; set; }

    public class Sku
        public string name { get; set; }

    public class Tags



No responses yet

Leave a Reply

Your email address will not be published. Required fields are marked *