Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions inc/cleantalk-public-integrations.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,10 @@ function ct_woocommerce_wishlist_check($args)
}
}

//If the IP is a Google bot
$hostname = gethostbyaddr(TT::toString(Server::get('REMOTE_ADDR')));
if ( ! strpos($hostname, 'googlebot.com') ) {
//If the IP is a Google bot (with FCrDNS verification to prevent PTR spoofing)
$client_ip = TT::toString(Server::get('REMOTE_ADDR'));
$verified_hostname = \Cleantalk\Common\Helper::ipResolve($client_ip);
if ( ! $verified_hostname || strpos($verified_hostname, 'googlebot.com') === false ) {
do_action('apbct_skipped_request', __FILE__ . ' -> ' . __FUNCTION__ . '():' . __LINE__, $_POST);

return $args;
Expand Down
3 changes: 2 additions & 1 deletion lib/Cleantalk/Antispam/Cleantalk.php
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,8 @@ public function httpPing($host)
$file = @fsockopen($host, 443, $errno, $errstr, $this->max_server_timeout / 1000);
} else {
$http = new Request();
$host = 'https://' . gethostbyaddr($host);
$verified_host = Helper::ipResolve($host);
$host = 'https://' . ($verified_host ?: $host);
$file = $http->setUrl($host)
->setOptions(['timeout' => $this->max_server_timeout / 1000])
->setPresets('get_code get')
Expand Down
7 changes: 5 additions & 2 deletions lib/Cleantalk/ApbctWP/RemoteCalls.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,14 @@ public static function checkWithoutToken()
'netserv3.cleantalk.org',
'netserv4.cleantalk.org',
];

// Resolve IP of the client making the request and verify hostname from it to be in the list of RC servers hostnames
$client_ip = Helper::ipGet('remote_addr');
$verified_hostname = $client_ip ? \Cleantalk\Common\Helper::ipResolve($client_ip) : false;
$is_noc_request = ! $apbct->key_is_ok &&
Request::get('spbc_remote_call_action') &&
in_array(Request::get('plugin_name'), array('antispam', 'anti-spam', 'apbct')) &&
in_array(Helper::ipResolve(Helper::ipGet('remote_addr')), $rc_servers, true);
$verified_hostname !== false &&
in_array($verified_hostname, $rc_servers, true);

// no token needs for this action, at least for now
// todo Probably we still need to validate this, consult with analytics team
Expand Down
4 changes: 3 additions & 1 deletion lib/Cleantalk/Common/DNS.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ public static function getResponseTime($host)
$file = @fsockopen($host, 443, $errno, $errstr, static::$max_server_timeout / 1000);
} else {
$http = new Request();
$host = 'https://' . gethostbyaddr($host);
// Use FCrDNS-verified hostname, or IP directly if verification fails
$verified_host = Helper::ipResolve($host);
$host = 'https://' . ($verified_host ?: $host);
$file = $http->setUrl($host)
->setOptions(['timeout' => static::$max_server_timeout / 1000])
->setPresets('get_code get')
Expand Down
39 changes: 30 additions & 9 deletions lib/Cleantalk/Common/Helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -528,22 +528,43 @@ public static function ipV6Reduce($ip)
}

/**
* Get URL form IP
* Resolve IP to hostname with FCrDNS (Forward-Confirmed reverse DNS) verification.
* Protects against PTR spoofing by verifying the hostname resolves back to the same IP.
*
* @param $ip
* @param string $ip IP address to resolve
*
* @return string
* @return string|false Verified hostname or false on failure
*/
public static function ipResolve($ip)
{
if (self::ipValidate($ip)) {
$url = gethostbyaddr($ip);
if ($url) {
return $url;
}
// Validate IP first
if (!self::ipValidate($ip)) {
return false;
}

return $ip;
// Reverse DNS lookup (PTR record)
$hostname = gethostbyaddr($ip);

// If gethostbyaddr returns the IP itself, it means no PTR record exists
if (!$hostname || $hostname === $ip) {
return false;
}

// Forward DNS lookup (A/AAAA records) - verify the hostname points back to the IP
$forward_ips = gethostbynamel($hostname);

// If forward lookup fails, we can't verify
if (!$forward_ips) {
return false;
}

// Check if the original IP is in the list of IPs the hostname resolves to
if (in_array($ip, $forward_ips, true)) {
return $hostname;
}

// FCrDNS verification failed - possible PTR spoofing attempt
return false;
}

/**
Expand Down