DEV Community

James Moberg
James Moberg

Posted on

4

ColdFusion isIPInRange() UDF to support IPv4, IPv6, CIDR & Regex

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>
view raw isIPInRange.cfm hosted with ❤ by GitHub

[https://gist.github.com/JamoCA/27cf5307d7b8854c62539fdeebbea51f ]

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay