Issues (10)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/SubMuncher.php (3 issues)

Upgrade to new PHP Analysis Engine

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
The variable $targetsub_min 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

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
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

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
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
Bug Best Practice introduced by
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 ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == 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