andrewandante /
submuncher
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | namespace AndrewAndante\SubMuncher; |
||
| 4 | |||
| 5 | class SubMuncher |
||
| 6 | { |
||
| 7 | /** |
||
| 8 | * This class should not be instantiated. |
||
| 9 | */ |
||
| 10 | private function __construct() |
||
| 11 | { |
||
| 12 | } |
||
| 13 | |||
| 14 | /** |
||
| 15 | * @param array $ipsArray |
||
| 16 | * @param int $max max number of rules returned |
||
| 17 | * @return array |
||
| 18 | */ |
||
| 19 | public static function consolidate($ipsArray, $max = null) |
||
| 20 | { |
||
| 21 | $consolidatedSubnets = []; |
||
| 22 | $subnetStart = null; |
||
| 23 | |||
| 24 | $ips = array_unique($ipsArray); |
||
| 25 | $sortedIPs = Util::sort_addresses($ips); |
||
| 26 | |||
| 27 | foreach ($sortedIPs as $index => $ipv4) { |
||
| 28 | // If not last and the next IP is the next sequential one, we are at the beginning of a subnet |
||
| 29 | if (isset($sortedIPs[$index + 1]) && $sortedIPs[$index + 1] == Util::ip_after($ipv4)) { |
||
| 30 | // if we've already started, just keep going, else kick one off |
||
| 31 | $subnetStart = $subnetStart ?: $ipv4; |
||
| 32 | // if not the first IP and the previous IP is sequential, we're at the end of a subnet |
||
| 33 | } elseif (isset($sortedIPs[$index - 1]) && $subnetStart !== null) { |
||
| 34 | $result = self::ip_range_to_subnet_array($subnetStart, $ipv4); |
||
| 35 | $consolidatedSubnets = array_merge($consolidatedSubnets, $result); |
||
| 36 | $subnetStart = null; |
||
| 37 | // otherwise we are a lone /32, so add it straight in |
||
| 38 | } else { |
||
| 39 | $consolidatedSubnets[]= $ipv4.'/32'; |
||
| 40 | $subnetStart = null; |
||
| 41 | } |
||
| 42 | } |
||
| 43 | |||
| 44 | if ($max === null || count($consolidatedSubnets) <= $max) { |
||
| 45 | return $consolidatedSubnets; |
||
| 46 | } |
||
| 47 | |||
| 48 | return self::consolidate_subnets($consolidatedSubnets, $max); |
||
| 49 | } |
||
| 50 | |||
| 51 | /** |
||
| 52 | * @param string $startip an IPv4 address |
||
| 53 | * @param string $endip an IPv4 address |
||
| 54 | * |
||
| 55 | * @return string[] list of subnets that cover the ip range specified |
||
| 56 | */ |
||
| 57 | public static function ip_range_to_subnet_array($startip, $endip) |
||
| 58 | { |
||
| 59 | |||
| 60 | if (!Util::is_ipaddr($startip) || !Util::is_ipaddr($endip)) { |
||
| 61 | return []; |
||
| 62 | } |
||
| 63 | |||
| 64 | // Container for subnets within this range. |
||
| 65 | $rangesubnets = []; |
||
| 66 | |||
| 67 | // Figure out what the smallest subnet is that holds the number of IPs in the |
||
| 68 | // given range. |
||
| 69 | $cidr = Util::find_smallest_cidr(Util::ip_range_size($startip, $endip)); |
||
| 70 | |||
| 71 | // Loop here to reduce subnet size and retest as needed. We need to make sure |
||
| 72 | // that the target subnet is wholly contained between $startip and $endip. |
||
| 73 | for ($cidr; $cidr <= 32; $cidr++) { |
||
| 74 | // Find the network and broadcast addresses for the subnet being tested. |
||
| 75 | $targetsub_min = Util::gen_subnet($startip, $cidr); |
||
| 76 | $targetsub_max = Util::gen_subnet_max($startip, $cidr); |
||
| 77 | |||
| 78 | // Check best case where the range is exactly one subnet. |
||
| 79 | if (($targetsub_min == $startip) && ($targetsub_max == $endip)) { |
||
| 80 | // Hooray, the range is exactly this subnet! |
||
| 81 | return ["{$startip}/{$cidr}"]; |
||
| 82 | } |
||
| 83 | |||
| 84 | // These remaining scenarios will find a subnet that uses the largest |
||
| 85 | // chunk possible of the range being tested, and leave the rest to be |
||
| 86 | // tested recursively after the loop. |
||
| 87 | |||
| 88 | // Check if the subnet begins with $startip and ends before $endip |
||
| 89 | if (($targetsub_min == $startip) && Util::ip_less_than($targetsub_max, $endip)) { |
||
| 90 | break; |
||
| 91 | } |
||
| 92 | |||
| 93 | // Check if the subnet ends at $endip and starts after $startip |
||
| 94 | if (Util::ip_greater_than($targetsub_min, $startip) && ($targetsub_max == $endip)) { |
||
| 95 | break; |
||
| 96 | } |
||
| 97 | |||
| 98 | // Check if the subnet is between $startip and $endip |
||
| 99 | if (Util::ip_greater_than($targetsub_min, $startip) && Util::ip_less_than($targetsub_max, $endip)) { |
||
| 100 | break; |
||
| 101 | } |
||
| 102 | } |
||
| 103 | |||
| 104 | // Some logic that will recursively search from $startip to the first IP before |
||
| 105 | // the start of the subnet we just found. |
||
| 106 | // NOTE: This may never be hit, the way the above algo turned out, but is left |
||
| 107 | // for completeness. |
||
| 108 | if ($startip != $targetsub_min) { |
||
| 109 | $rangesubnets = array_merge( |
||
| 110 | $rangesubnets, |
||
| 111 | self::ip_range_to_subnet_array($startip, Util::ip_before($targetsub_min)) |
||
|
0 ignored issues
–
show
|
|||
| 112 | ); |
||
| 113 | } |
||
| 114 | |||
| 115 | // Add in the subnet we found before, to preserve ordering |
||
| 116 | $rangesubnets[] = "{$targetsub_min}/{$cidr}"; |
||
| 117 | |||
| 118 | // And some more logic that will search after the subnet we found to fill in |
||
| 119 | // to the end of the range. |
||
| 120 | if ($endip != $targetsub_max) { |
||
| 121 | $rangesubnets = array_merge( |
||
| 122 | $rangesubnets, |
||
| 123 | self::ip_range_to_subnet_array(Util::ip_after($targetsub_max), $endip) |
||
|
0 ignored issues
–
show
The variable
$targetsub_max does not seem to be defined for all execution paths leading up to this point.
If you define a variable conditionally, it can happen that it is not defined for all execution paths. Let’s take a look at an example: function myFunction($a) {
switch ($a) {
case 'foo':
$x = 1;
break;
case 'bar':
$x = 2;
break;
}
// $x is potentially undefined here.
echo $x;
}
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined. Available Fixes
Loading history...
|
|||
| 124 | ); |
||
| 125 | } |
||
| 126 | |||
| 127 | return $rangesubnets; |
||
| 128 | } |
||
| 129 | |||
| 130 | /** |
||
| 131 | * Function to figure out the least problematic subnets to combine based on |
||
| 132 | * fewest additional IPs introduced. Then combines them as such, and runs |
||
| 133 | * it back through the consolidator with one less subnet - until we have |
||
| 134 | * reduced it down to the maximum number of rules |
||
| 135 | * |
||
| 136 | * @param array $subnetsArray array of cidrs |
||
| 137 | * @param int $max |
||
| 138 | * |
||
| 139 | * @return array |
||
| 140 | */ |
||
| 141 | public static function consolidate_subnets($subnetsArray, $max = null) |
||
| 142 | { |
||
| 143 | |||
| 144 | $subnetsArray = Util::sort_cidrs(array_unique($subnetsArray)); |
||
| 145 | |||
| 146 | do { |
||
| 147 | $countSubnetsArray = count($subnetsArray); |
||
| 148 | $newSubnetsArray = []; |
||
| 149 | $subnetToMaskMap = []; |
||
| 150 | $ipReductionBySubnet = []; |
||
| 151 | reset($subnetsArray); |
||
| 152 | do { |
||
| 153 | $cidr = current($subnetsArray); |
||
| 154 | list($currentIP, $currentMask) = explode('/', $cidr); |
||
| 155 | $nextIP = null; |
||
| 156 | $nextMask = null; |
||
| 157 | |||
| 158 | if (next($subnetsArray) !== false) { |
||
| 159 | list($nextIP, $nextMask) = explode('/', current($subnetsArray)); |
||
| 160 | prev($subnetsArray); |
||
| 161 | } else { |
||
| 162 | end($subnetsArray); |
||
| 163 | } |
||
| 164 | |||
| 165 | $endIP = Util::gen_subnet_max($currentIP, $currentMask); |
||
| 166 | while (isset($nextIP) && Util::ip_after($endIP) == $nextIP) { |
||
| 167 | $nextEndIP = Util::gen_subnet_max($nextIP, $nextMask); |
||
| 168 | $consolidated = self::ip_range_to_subnet_array($currentIP, $nextEndIP); |
||
| 169 | if (count($consolidated) == 1) { |
||
| 170 | $endIP = $nextEndIP; |
||
| 171 | list($currentIP, $currentMask) = explode('/', $consolidated[0]); |
||
| 172 | if (next($subnetsArray) !== false) { |
||
| 173 | list($nextIP, $nextMask) = explode('/', current($subnetsArray)); |
||
| 174 | } else { |
||
| 175 | end($subnetsArray); |
||
| 176 | $nextIP = null; |
||
| 177 | $nextMask = null; |
||
| 178 | } |
||
| 179 | } else { |
||
| 180 | break; |
||
| 181 | } |
||
| 182 | } |
||
| 183 | |||
| 184 | $newSubnetsArray[] = $currentIP . '/' . $currentMask; |
||
| 185 | |||
| 186 | $subnetToMaskMap[$currentIP] = [ |
||
| 187 | 'startIP' => $currentIP, |
||
| 188 | 'endIP' => $endIP, |
||
| 189 | 'mask' => $currentMask, |
||
| 190 | 'next' => isset($nextIP) ? $nextIP : 'none', |
||
| 191 | ]; |
||
| 192 | |||
| 193 | $toJoin = Util::get_single_subnet($currentIP, Util::gen_subnet_max($nextIP, $nextMask)); |
||
| 194 | if (!$toJoin) { |
||
|
0 ignored issues
–
show
The expression
$toJoin of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
Loading history...
|
|||
| 195 | continue; |
||
| 196 | } |
||
| 197 | list($joinIP, $joinMask) = explode('/', $toJoin); |
||
| 198 | $diff = abs(Util::subnet_range_size($currentMask) - Util::subnet_range_size($joinMask)); |
||
| 199 | |||
| 200 | $ipReductionBySubnet[$joinIP] = [ |
||
| 201 | 'mask' => $joinMask, |
||
| 202 | 'diff' => $diff, |
||
| 203 | 'original' => $currentIP, |
||
| 204 | ]; |
||
| 205 | } while (next($subnetsArray) !== false); |
||
| 206 | $subnetsArray = $newSubnetsArray; |
||
| 207 | } while (count($subnetsArray) !== $countSubnetsArray); |
||
| 208 | |||
| 209 | // sort array by number of additional IPs introduced |
||
| 210 | uasort($ipReductionBySubnet, function ($a, $b) { |
||
| 211 | return $a['diff'] - $b['diff']; |
||
| 212 | }); |
||
| 213 | |||
| 214 | $returnCIDRs = []; |
||
| 215 | foreach ($subnetToMaskMap as $ip => $config) { |
||
| 216 | $returnCIDRs[] = $ip.'/'.$config['mask']; |
||
| 217 | } |
||
| 218 | |||
| 219 | if ($max === null || count($returnCIDRs) <= $max) { |
||
| 220 | return $returnCIDRs; |
||
| 221 | } |
||
| 222 | |||
| 223 | reset($ipReductionBySubnet); |
||
| 224 | do { |
||
| 225 | current($ipReductionBySubnet); |
||
| 226 | $injectedIP = key($ipReductionBySubnet); |
||
| 227 | |||
| 228 | $toUpdate = $ipReductionBySubnet[$injectedIP]['original']; |
||
| 229 | if (isset($subnetToMaskMap[$toUpdate])) { |
||
| 230 | $next = $subnetToMaskMap[$toUpdate]['next']; |
||
| 231 | |||
| 232 | // remove the two subnets we've just mushed |
||
| 233 | unset($subnetToMaskMap[$toUpdate]); |
||
| 234 | unset($subnetToMaskMap[$next]); |
||
| 235 | |||
| 236 | // chuck in the new one |
||
| 237 | $subnetToMaskMap[$injectedIP] = [ |
||
| 238 | 'mask' => $ipReductionBySubnet[$injectedIP]['mask'], |
||
| 239 | ]; |
||
| 240 | |||
| 241 | $returnCIDRs = []; |
||
| 242 | foreach ($subnetToMaskMap as $ip => $config) { |
||
| 243 | $returnCIDRs[] = $ip . '/' . $config['mask']; |
||
| 244 | } |
||
| 245 | |||
| 246 | $returnCIDRs = Util::sort_cidrs($returnCIDRs); |
||
| 247 | } |
||
| 248 | } while (count($returnCIDRs) > $max && next($ipReductionBySubnet) !== false); |
||
| 249 | |||
| 250 | if (count($returnCIDRs > $max)) { |
||
| 251 | return self::consolidate_subnets($returnCIDRs, $max); |
||
| 252 | } |
||
| 253 | |||
| 254 | return $returnCIDRs; |
||
| 255 | } |
||
| 256 | |||
| 257 | /** |
||
| 258 | * @param string[] $ipsArray |
||
| 259 | * @param int|null $max |
||
| 260 | * @return array |
||
| 261 | */ |
||
| 262 | public static function consolidate_verbose($ipsArray, $max = null) |
||
| 263 | { |
||
| 264 | $consolidateResults = self::consolidate($ipsArray, $max); |
||
| 265 | $totalIPs = []; |
||
| 266 | foreach ($consolidateResults as $cidr) { |
||
| 267 | $totalIPs = array_merge($totalIPs, Util::cidr_to_ips_array($cidr)); |
||
| 268 | } |
||
| 269 | |||
| 270 | return [ |
||
| 271 | 'consolidated_subnets' => $consolidateResults, |
||
| 272 | 'initial_IPs' => Util::sort_addresses($ipsArray), |
||
| 273 | 'total_IPs' => $totalIPs |
||
| 274 | ]; |
||
| 275 | } |
||
| 276 | |||
| 277 | /** |
||
| 278 | * @param string[] $subnetsArray |
||
| 279 | * @param int|null $max |
||
| 280 | * @return array |
||
| 281 | */ |
||
| 282 | public static function consolidate_subnets_verbose($subnetsArray, $max = null) |
||
| 283 | { |
||
| 284 | $ips = []; |
||
| 285 | foreach ($subnetsArray as $subnet) { |
||
| 286 | $ips = array_merge($ips, Util::cidr_to_ips_array($subnet)); |
||
| 287 | } |
||
| 288 | |||
| 289 | return self::consolidate_verbose($ips, $max); |
||
| 290 | } |
||
| 291 | } |
||
| 292 |
If you define a variable conditionally, it can happen that it is not defined for all execution paths.
Let’s take a look at an example:
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.
Available Fixes
Check for existence of the variable explicitly:
Define a default value for the variable:
Add a value for the missing path: