Quick and Dirty IP Lookup In Microsoft Sentinel

Quick and Dirty IP Lookup In Microsoft Sentinel
Photo by Carlos Muza / Unsplash

I had a need to determine if IPs in Microsoft Sentinel events were from our VPN block or not. Sentinel does not provide any native way to look up WHOIS information for an IP, so I had to do this in a quick and dirty fashion.

Get the IP Ranges

First, I needed to get the IPs in question. Luckily our provider publishes their IP blocks, which makes this part fairly easy.

import requests

ip_ranges = set()

def item_generator(json_input, lookup_key):
    if isinstance(json_input, dict):
        for k, v in json_input.items():
            if k == lookup_key:
                yield v
            else:
                yield from item_generator(v, lookup_key)
    elif isinstance(json_input, list):
        for item in json_input:
            yield from item_generator(item, lookup_key)

resp = requests.get('https://api.config.zscaler.com/zscalerthree.net/cenr/json').json()

ranges = item_generator(resp, 'range')

for i in ranges:
    ip_ranges.add(i)

print(ip_ranges)

A better way to do this would be to publish this as an Azure Function App and have the function either write the values to a Log Analytics Workspace Table or update the affected Sentinel Analytics Rules via the API.

I was a bit too lazy to do it this way at this moment, maybe later.

Write The Query

With the IPs now in a format that I could plug into a query, I had to do just that. Making use of the ipv4_is_in_any_range()function, I am able to create a condition where the source IP is not from the supplied range.

let ip_ranges = dynamic(['ipaddress', 'redacted', 'for', 'brevity']);
MyLogAnalyticsTables
| where Column != "tokenLogin"
| extend IPAddress = tostring(split(SourceIPAddress,':')[0])
| where not(ipv4_is_in_any_range(IPAddress, ip_ranges))

Future Work

Implement the Python code as a Function App and have it run periodically to keep the IP ranges up to date. The data could be written to a Log Analytics Table or Storage Blob for access from a KQL query. In this case, the cost of the extra development and infrastructure seemed higher than the value gained. In the meantime, I have a calendar reminder to update these ranges monthly.