shell bypass 403
<?php
/**
* @package admintools
* @copyright Copyright (c)2010-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Plugin\System\AdminTools\Utility;
defined('_JEXEC') or die;
use Joomla\Utilities\IpHelper;
/**
* IP Filtering utility class
*/
abstract class Filter
{
/** @var string The IP address of the current visitor */
protected static $ip = null;
/**
* Get the current visitor's IP address
*
* @return string
*/
public static function getIp()
{
if (is_null(static::$ip))
{
$ip = IpHelper::getIp();
static::setIp($ip);
}
return static::$ip;
}
/**
* Set the IP address of the current visitor (to be used in testing)
*
* @param string $ip
*
* @return void
*/
public static function setIp($ip)
{
static::$ip = $ip;
}
/**
* Checks if the user's IP is contained in a list of IPs or IP expressions
*
* This code has been copied from FOF to lower the amount of dependencies required
*
* @param array|string $ipTable The list of IP expressions
* @param string $ip The user's IP address, leave empty / null to get the current IP address
*
* @return null|bool True if it's in the list, null if the filtering can't proceed
*/
public static function IPinList($ipTable = [], $ip = null)
{
// Get our IP address
if (empty($ip))
{
$ip = static::getIp();
}
// No point proceeding with an empty IP list
if (empty($ipTable))
{
return false;
}
// If the IP list is not an array, convert it to an array
if (!is_array($ipTable))
{
if (strpos($ipTable, ',') !== false)
{
$ipTable = explode(',', $ipTable);
$ipTable = array_map(function ($x) {
return trim($x);
}, $ipTable);
}
else
{
$ipTable = trim($ipTable);
$ipTable = [$ipTable];
}
}
// If no IP address is found, return false
if ($ip == '0.0.0.0')
{
return false;
}
// If no IP is given, return false
if (empty($ip))
{
return false;
}
// Sanity check
if (!function_exists('inet_pton'))
{
return false;
}
// Get the IP's in_adds representation
$myIP = @inet_pton($ip);
// If the IP is in an unrecognisable format, quite
if ($myIP === false)
{
return false;
}
$ipv6 = self::isIPv6($ip);
/**
* Resolve any domains given in the list (e.g. @example.dyndns.info) into IP addresses.
*
* WARNING! This incurs a significant time penalty, up to 3 seconds per DNS query.
*/
$ipTable = array_map(function ($v) {
if (substr($v, 0, 1) != '@')
{
return $v;
}
/** @see https://secure.php.net/manual/en/function.gethostbyname.php */
putenv('RES_OPTIONS=retrans:1 retry:1 timeout:3 attempts:1');
$domain = substr($v, 1);
$domain = rtrim($domain, '.') . '.';
$ip = gethostbyname($domain);
if ($ip == $domain)
{
return '';
}
return $ip;
}, $ipTable);
/**
* Resolve any IPv6 domains given in the list (e.g. #example.dyndns.info) into IP addresses.
*
* WARNING! This incurs a significant time penalty, up to 3 seconds per DNS query.
*/
$ipTable = array_map(function ($v) {
if (substr($v, 0, 1) != '#')
{
return $v;
}
$domain = substr($v, 1);
$dns = dns_get_record($domain, DNS_AAAA);
foreach ($dns as $record)
{
if ($record['type'] === 'AAAA')
{
return $record['ipv6'];
}
}
return '';
}, $ipTable);
// Perform the filtering
foreach ($ipTable as $ipExpression)
{
$ipExpression = trim($ipExpression);
// Ignore empty records
if (empty($ipExpression))
{
continue;
}
// Inclusive IP range, i.e. 123.123.123.123-124.125.126.127
if (strstr($ipExpression, '-'))
{
[$from, $to] = explode('-', $ipExpression, 2);
if ($ipv6 && (!self::isIPv6($from) || !self::isIPv6($to)))
{
// Do not apply IPv4 filtering on an IPv6 address
continue;
}
elseif (!$ipv6 && (self::isIPv6($from) || self::isIPv6($to)))
{
// Do not apply IPv6 filtering on an IPv4 address
continue;
}
$from = @inet_pton(trim($from));
$to = @inet_pton(trim($to));
// Sanity check
if (($from === false) || ($to === false))
{
continue;
}
// Swap from/to if they're in the wrong order
if ($from > $to)
{
[$from, $to] = [$to, $from];
}
if (($myIP >= $from) && ($myIP <= $to))
{
return true;
}
}
// Netmask or CIDR provided
elseif (strstr($ipExpression, '/'))
{
$binaryip = self::inet_to_bits($myIP);
[$net, $maskbits] = explode('/', $ipExpression, 2);
if ($ipv6 && !self::isIPv6($net))
{
// Do not apply IPv4 filtering on an IPv6 address
continue;
}
elseif (!$ipv6 && self::isIPv6($net))
{
// Do not apply IPv6 filtering on an IPv4 address
continue;
}
elseif ($ipv6 && strstr($maskbits, ':'))
{
// Perform an IPv6 CIDR check
if (self::checkIPv6CIDR($myIP, $ipExpression))
{
return true;
}
// If we didn't match it proceed to the next expression
continue;
}
elseif (!$ipv6 && strstr($maskbits, '.'))
{
// Convert IPv4 netmask to CIDR
$long = ip2long($maskbits);
$base = ip2long('255.255.255.255');
$maskbits = 32 - log(($long ^ $base) + 1, 2);
}
// Convert network IP to in_addr representation
$net = @inet_pton($net);
// Sanity check
if ($net === false)
{
continue;
}
// Get the network's binary representation
$binarynet = self::inet_to_bits($net);
$expectedNumberOfBits = $ipv6 ? 128 : 24;
$binarynet = str_pad($binarynet, $expectedNumberOfBits, '0', STR_PAD_RIGHT);
// Check the corresponding bits of the IP and the network
$ip_net_bits = substr($binaryip, 0, $maskbits);
$net_bits = substr($binarynet, 0, $maskbits);
if ($ip_net_bits == $net_bits)
{
return true;
}
}
else
{
// IPv6: Only single IPs are supported
if ($ipv6)
{
$ipExpression = trim($ipExpression);
if (!self::isIPv6($ipExpression))
{
continue;
}
$ipCheck = @inet_pton($ipExpression);
if ($ipCheck === false)
{
continue;
}
if ($ipCheck == $myIP)
{
return true;
}
}
else
{
// Standard IPv4 address, i.e. 123.123.123.123 or partial IP address, i.e. 123.[123.][123.][123]
$dots = 0;
if (substr($ipExpression, -1) == '.')
{
// Partial IP address. Convert to CIDR and re-match
foreach (count_chars($ipExpression, 1) as $i => $val)
{
if ($i == 46)
{
$dots = $val;
}
}
$netmask = '255.255.255.255';
switch ($dots)
{
case 1:
$netmask = '255.0.0.0';
$ipExpression .= '0.0.0';
break;
case 2:
$netmask = '255.255.0.0';
$ipExpression .= '0.0';
break;
case 3:
$netmask = '255.255.255.0';
$ipExpression .= '0';
break;
default:
$dots = 0;
}
if ($dots)
{
$binaryip = self::inet_to_bits($myIP);
// Convert netmask to CIDR
$long = ip2long($netmask);
$base = ip2long('255.255.255.255');
$maskbits = 32 - log(($long ^ $base) + 1, 2);
$net = @inet_pton($ipExpression);
// Sanity check
if ($net === false)
{
continue;
}
// Get the network's binary representation
$binarynet = self::inet_to_bits($net);
$expectedNumberOfBits = $ipv6 ? 128 : 24;
$binarynet = str_pad($binarynet, $expectedNumberOfBits, '0', STR_PAD_RIGHT);
// Check the corresponding bits of the IP and the network
$ip_net_bits = substr($binaryip, 0, $maskbits);
$net_bits = substr($binarynet, 0, $maskbits);
if ($ip_net_bits == $net_bits)
{
return true;
}
}
}
if (!$dots)
{
$ip = @inet_pton(trim($ipExpression));
if ($ip == $myIP)
{
return true;
}
}
}
}
}
return false;
}
/**
* Is it an IPv6 IP address?
*
* @param string $ip An IPv4 or IPv6 address
*
* @return boolean True if it's IPv6
*/
private static function isIPv6($ip)
{
if (strstr($ip, ':'))
{
return true;
}
return false;
}
/**
* Converts inet_pton output to bits string
*
* @param string $inet The in_addr representation of an IPv4 or IPv6 address
*
* @return string
*/
private static function inet_to_bits($inet)
{
if (strlen($inet) == 4)
{
$unpacked = unpack('C4', $inet);
}
else
{
$unpacked = unpack('C16', $inet);
}
$binaryip = '';
foreach ($unpacked as $byte)
{
$binaryip .= str_pad(decbin($byte), 8, '0', STR_PAD_LEFT);
}
return $binaryip;
}
/**
* Checks if an IPv6 address $ip is part of the IPv6 CIDR block $cidrnet
*
* @param string $ip The IPv6 address to check, e.g. 21DA:00D3:0000:2F3B:02AC:00FF:FE28:9C5A
* @param string $cidrnet The IPv6 CIDR block, e.g. 21DA:00D3:0000:2F3B::/64
*
* @return bool
*/
private static function checkIPv6CIDR($ip, $cidrnet)
{
$ip = inet_pton($ip);
$binaryip = self::inet_to_bits($ip);
[$net, $maskbits] = explode('/', $cidrnet);
$net = inet_pton($net);
$binarynet = self::inet_to_bits($net);
$ip_net_bits = substr($binaryip, 0, $maskbits);
$net_bits = substr($binarynet, 0, $maskbits);
return $ip_net_bits === $net_bits;
}
}