I received reports of 502 server errors this morning after our web application framework accidentally blacklisted our CDN/WAF provider. I wanted to ensure that this wouldn't happen again by whitelisting their IPs, but they have many IPs and their data was only available in IPv4 & IPv6 CIDR notation.
ColdFusion doesn't have any built-in IP-related tools other than determining the IP of the current host... so I'm off to search for a potential existing solution.
I checked CFDocs and discovered that there was an isIPInRange function (without any examples), but it was a Lucee-only function. The Lucee function accepts either a comma-separated list or an array of IP definitions, but doesn't support CIDR or Regex.
I checked CFLib and discovered that there was a isIPInRange UDF with the same name from 2005 that used a list of regex values, but it didn't support CIDR or dash-delimited range (well, unless you wanted to write the regex for that).
I had been using a method that Anjo Gakhar blogged about in 2008 that demonstrates the use of the undocumented coldfusion.util.IPAddressUtils class to identify whether a string is a valid IPv4 or IPv6 IP address or not.
I then found a 2018 blog post by Ben Nadel documenting his implementation of using Commons IP Math to check if an IPv4 or IPv6 IP exists in a CIDR range, but it is dependent on separate java library (MIT) called Commons IP Math. The code example also only supported a single CIDR range.
Since none of the existing solutions fully covered my needs, I decided to write my own ColdFusion UDF and combine the best features from all of them.
- Performs IPv4 & IPv6 validation
- Supports IPv4 & IPv6 CIDR range notation
- Supports list or array of ranges
- Supports regex range rules
- Supports dash-delimited IP address range
- Supports single IP (versus a range)
- Requires Commons IP Math JAR File
Since all my web applications are behind a CDN/WAF and could be negatively impacted if the CDN was blacklisted, I figured it was best for me to add the JAR to the global path instead of using Javaloader. I also didn't write this as a stand-alone CFC like Ben does. Most of my projects use a global UDF library and this was the simplest approach to integrate since I wouldn't have to manually update every project.
Source Code
Here's a link to the gist. Due to the JAR requirement, this unfortunately can't be demoed using TryCF.
<!--- Blog Entry: https://dev.to/gamesover/coldfusion-isipinrange-udf-to-support-ipv4-ipv6-cidr-regex-212h ---> | |
<!--- 20200109 getIPVersionACF() | |
Inspired by https://www.anujgakhar.com/2008/02/21/validate-ip-address-natively-with-coldfusion/ | |
Uses built-in undocumented coldfusion.util.IPAddressUtils, so this may not work on Lucee without this function being rewritten ---> | |
<!--- 20230126 getIPVersion() | |
Inspired by https://docs.lucee.org/reference/functions/isipinrange.html | |
Requires JAR https://github.com/jgonian/commons-ip-math (Add to existing java path (recommended) or modify to use javaloader) ---> | |
<!--- 20200109 isIPInRange() | |
Inspired by https://www.bennadel.com/blog/3503-using-commons-ip-math-to-check-if-an-ip-address-exists-in-an-ipv4-or-ipv6-cidr-range-in-coldfusion.htm | |
Inspired by https://cflib.org/udf/isIPInRange | |
Inspired by https://docs.lucee.org/reference/functions/isipinrange.html | |
Requires JAR https://github.com/jgonian/commons-ip-math (Add to existing java path (recommended) or modify to use javaloader) ---> | |
<cfscript> | |
numeric function getIPVersionACF(required string ip) hint="identifies whether string represents an IPv4 or IPv6 address; returns 0 if invalid (Adobe ColdFusion Only)" { | |
local.response = 0; | |
if (!structkeyexists(request, "getIPVersion_IPAddressUtils")){ | |
request.getIPVersion_IPAddressUtils = createobject("java","coldfusion.util.IPAddressUtils"); | |
} | |
if (request.getIPVersion_IPAddressUtils.validateIPv4Address( javacast("string", trim(arguments.ip)) )){ | |
local.response = 4; | |
} else if (request.getIPVersion_IPAddressUtils.validateIPv6Address( javacast("string", trim(arguments.ip)) )){ | |
local.response = 6; | |
} | |
return javacast("int", local.response); | |
} | |
numeric function getIPVersion(required string ip) hint="identifies whether string represents an IPv4 or IPv6 address; returns 0 if invalid" { | |
local.response = 0; | |
arguments.ip = javacast("string", arguments.ip); | |
if (find(".", arguments.ip) && listlen(arguments.ip,".") eq 4){ | |
if (!structkeyexists(request, "isIPInRange_Ipv4")){ | |
request.isIPInRange_Ipv4 = createobject("java","com.github.jgonian.ipmath.Ipv4"); | |
} | |
try { | |
local.test = request.isIPInRange_Ipv4.parse(arguments.ip); | |
local.response = 4; | |
} catch (any e) {} | |
} else if (find(":", arguments.ip)){ | |
if (!structkeyexists(request, "isIPInRange_Ipv6")){ | |
request.isIPInRange_Ipv6 = createobject("java","com.github.jgonian.ipmath.Ipv6"); | |
} | |
try { | |
local.test = request.isIPInRange_Ipv6.parse(arguments.ip); | |
local.response = 6; | |
} catch (any e) {} | |
} | |
return javacast("int", local.response); | |
} | |
boolean function isIPInRange(any ips, required ip) hint="Checks if an ip is within the range of a list or array of ips; supports regex & CIDR notation for ranges" { | |
local.ipVersion = getIPVersion(arguments.ip); | |
local.response = javacast("boolean", false); | |
if (!val(local.ipVersion)){ | |
return local.response; | |
} else if (isarray(arguments.ips)){ | |
local.ipRange = arguments.ips; | |
} else if (issimplevalue(arguments.ips)){ | |
local.ipRange = listtoarray(arguments.ips); | |
} else { | |
throw(type="Custom", message="ips must be comma-delimited list or array"); | |
} | |
if (!structkeyexists(request, "isIPInRange_Ipv4")){ | |
request.isIPInRange_Ipv4 = createobject("java","com.github.jgonian.ipmath.Ipv4"); | |
} | |
if (!structkeyexists(request, "isIPInRange_Ipv6")){ | |
request.isIPInRange_Ipv6 = createobject("java","com.github.jgonian.ipmath.Ipv6"); | |
} | |
if (local.ipVersion eq 6){ | |
local.parsedIP = request.isIPInRange_Ipv6.parse( javacast("string", trim(arguments.ip)) ); | |
} else if (local.ipVersion eq 4){ | |
local.parsedIP = request.isIPInRange_Ipv4.parse( javacast("string", trim(arguments.ip)) ); | |
} else { | |
return local.response; | |
} | |
if (!structkeyexists(request, "isIPInRange_Ipv4Range")){ | |
request.isIPInRange_Ipv4Range = createobject("java","com.github.jgonian.ipmath.Ipv4Range"); | |
request.isIPInRange_Ipv6Range = createobject("java","com.github.jgonian.ipmath.Ipv6Range"); | |
} | |
for (local.thisRange in local.ipRange) { | |
if (trim(local.thisRange).replaceall("/32$","") eq trim(arguments.ip)) { /* single IP */ | |
local.response = true; | |
break; | |
/* regex */ | |
} else if (refind("(?:\^|\[|\+|\*|\(|\$|\\\.)", local.thisRange)){ | |
try { | |
if (refindnocase(local.thisRange, arguments.ip)){ | |
local.response = true; | |
break; | |
} | |
} catch (any e){} | |
/* compare w/IPv4 dash-delimited range */ | |
} else if (local.ipVersion eq 4 && !find("/",local.thisRange) && listlen(local.thisRange,"-") eq 2 && getIPVersion(listfirst(local.thisRange,"-")) eq 4 && getIPVersion(listlast(local.thisRange,"-")) eq 4){ | |
local.start = request.isIPInRange_Ipv4.of( javacast("string", trim(listfirst(local.thisRange,"-"))) ); | |
local.end = request.isIPInRange_Ipv4.of( javacast("string", trim(listlast(local.thisRange,"-"))) ); | |
local.range = request.isIPInRange_Ipv4Range.from(local.start).to(local.end); | |
if (local.range.contains(local.parsedIP)){ | |
local.response = true; | |
break; | |
} | |
/* compare w/IPv6 dash-delimited range */ | |
} else if (local.ipVersion eq 6 && !find("/",local.thisRange) && listlen(local.thisRange,"-") eq 2 && getIPVersion(listfirst(local.thisRange,"-")) eq 6 && getIPVersion(listlast(local.thisRange,"-")) eq 6){ | |
local.start = request.isIPInRange_Ipv6.of( javacast("string", trim(listfirst(local.thisRange,"-"))) ); | |
local.end = request.isIPInRange_Ipv6.of( javacast("string", trim(listlast(local.thisRange,"-"))) ); | |
local.range = request.isIPInRange_Ipv6Range.from(local.start).to(local.end); | |
if (local.range.contains(local.parsedIP)){ | |
local.response = true; | |
break; | |
} | |
/* compare w/CIDR range */ | |
} else if (find("/", local.thisRange)){ | |
structdelete(local, "CIDR"); | |
if (local.ipVersion eq 4 && !find(":", local.thisRange)){ | |
local.CIDR = request.isIPInRange_Ipv4Range.parse(local.thisRange); | |
} else if (local.ipVersion eq 6 && find(":", local.thisRange)){ | |
local.CIDR = request.isIPInRange_Ipv6Range.parse(local.thisRange); | |
} | |
if (structkeyexists(local, "CIDR")){ | |
if (local.CIDR.contains(local.parsedIP)){ | |
local.response = true; | |
break; | |
} | |
} | |
} | |
} | |
return javacast("boolean", local.response); | |
} | |
testRanges = [ | |
"2a0a:e200:1600::/40" | |
,"209.197.31.0/25" | |
,"81.171.68.64/26" | |
,"95.138.175.4/32" | |
,"127\.0\.1\.*" | |
,"127.0.0.1-127.0.0.5" | |
]; | |
testIPs = [ | |
"209.197.31.25" | |
,"95.138.175.4" | |
,"81.171.68.124" | |
,"2a0a:e200:16ff:ffff:ffff:ffff:ffff:ffff" | |
,"127.0.1.4" | |
,"127.0.0.6" | |
,"127.0.0.15" | |
,"127.1.1.4" | |
,CGI.REMOTE_ADDR | |
,getLocalHostIP() | |
]; | |
writedump(var=testRanges, label="Test Ranges"); | |
writeoutput("<h2>isIPInRange() Results</h2>"); | |
for (thisIP in testIPs){ | |
writeoutput("<div><b>#thisIP# (v#getIPVersion(thisIP)#):</b> #yesnoformat(isIPInRange(TestRanges, thisIP))#</div>"); | |
} | |
</cfscript> |
[https://gist.github.com/JamoCA/27cf5307d7b8854c62539fdeebbea51f ]
Top comments (0)