From 50c0fd6738f078720d0966ceb3f4302b0af83266 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 10 Feb 2024 04:58:11 +0000 Subject: [PATCH] Ckeck for host differences of fetched objects --- src/Protocol/ActivityPub/Processor.php | 92 ++++++++++++++++++++++++++ src/Util/JsonLD.php | 5 ++ 2 files changed, 97 insertions(+) diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index 9b1c84bbb1..35ae4e504d 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -1538,6 +1538,11 @@ class Processor } $object = HTTPSignature::fetch($url, $uid); + + if (!empty($object)) { + $object = self::refetchObjectOnHostDifference($object, $url); + } + if (empty($object)) { Logger::notice('Activity was not fetchable, aborting.', ['url' => $url, 'uid' => $uid]); // We perform negative caching. @@ -1549,6 +1554,11 @@ class Processor Logger::notice('Activity has got not id, aborting. ', ['url' => $url, 'object' => $object]); return []; } + + if (!self::isValidObject($object, $url)) { + return []; + } + DI::cache()->set($cachekey, $object, Duration::FIVE_MINUTES); Logger::debug('Activity was fetched successfully', ['url' => $url, 'uid' => $uid]); @@ -1594,6 +1604,11 @@ class Processor } $object = json_decode($body, true); + + if (!empty($object)) { + $object = self::refetchObjectOnHostDifference($object, $url); + } + if (empty($object) || !is_array($object)) { $element = explode(';', $curlResult->getContentType()); if (!in_array($element[0], ['application/activity+json', 'application/ld+json', 'application/json'])) { @@ -1604,6 +1619,10 @@ class Processor return ''; } + if (!self::isValidObject($object, $url)) { + return ''; + } + $ldobject = JsonLD::compact($object); $signer = []; @@ -1693,6 +1712,79 @@ class Processor return $activity['id']; } + private static function refetchObjectOnHostDifference(array $object, string $url): array + { + $ldobject = JsonLD::compact($object); + if (empty($ldobject)) { + Logger::info('Invalid object', ['url' => $url]); + return $object; + } + + $id = JsonLD::fetchElement($ldobject, '@id'); + if (empty($id)) { + Logger::info('No id found in object', ['url' => $url, 'object' => $object]); + return $object; + } + + $url_host = parse_url($url, PHP_URL_HOST); + $id_host = parse_url($id, PHP_URL_HOST); + + if ($id_host == $url_host) { + return $object; + } + + Logger::notice('Refetch activity because of a host mismatch between requested and received id', ['url-host' => $url_host, 'id-host' => $id_host, 'url' => $url, 'id' => $id]); + return HTTPSignature::fetch($id); + } + + private static function isValidObject(array $object): bool + { + $ldobject = JsonLD::compact($object); + if (empty($ldobject)) { + Logger::info('Invalid object'); + return false; + } + + $id = JsonLD::fetchElement($ldobject, '@id'); + if (empty($id)) { + Logger::info('No id found in object'); + return false; + } + + $type = JsonLD::fetchElement($ldobject, '@type'); + $object_id = JsonLD::fetchElement($ldobject, 'as:object', '@id'); + $object_type = JsonLD::fetchElement($ldobject, 'as:object', '@type'); + $actor = JsonLD::fetchElement($ldobject, 'as:actor', '@id'); + $attributed_to = JsonLD::fetchElement($ldobject, 'as:attributedTo', '@id'); + + $id_host = parse_url($id, PHP_URL_HOST); + + if (!empty($actor) && !in_array($type, Receiver::CONTENT_TYPES) && !empty($object_id)) { + $actor_host = parse_url($actor, PHP_URL_HOST); + if ($actor_host != $id_host) { + Logger::notice('Host mismatch between received id and actor', ['id-host' => $id_host, 'actor-host' => $actor_host, 'id' => $id, 'type' => $type, 'object-id' => $object_id, 'object_type' => $object_type, 'actor' => $actor, 'attributed_to' => $attributed_to]); + return false; + } + if (!empty($object_type)) { + $object_attributed_to = JsonLD::fetchElement($ldobject['as:object'], 'as:attributedTo', '@id'); + $attributed_to_host = parse_url($object_attributed_to, PHP_URL_HOST); + $object_id_host = parse_url($object_id, PHP_URL_HOST); + if (!empty($attributed_to_host) && ($attributed_to_host != $object_id_host)) { + Logger::notice('Host mismatch between received object id and attributed actor', ['id-object-host' => $object_id_host, 'attributed-host' => $attributed_to_host, 'id' => $id, 'type' => $type, 'object-id' => $object_id, 'object_type' => $object_type, 'actor' => $actor, 'object_attributed_to' => $object_attributed_to]); + return false; + } + } + } elseif (!empty($attributed_to)) { + $attributed_to_host = parse_url($attributed_to, PHP_URL_HOST); + if ($attributed_to_host != $id_host) { + Logger::notice('Host mismatch between received id and attributed actor', ['id-host' => $id_host, 'attributed-host' => $attributed_to_host, 'id' => $id, 'type' => $type, 'object-id' => $object_id, 'object_type' => $object_type, 'actor' => $actor, 'attributed_to' => $attributed_to]); + return false; + } + } + + return true; + } + private static function getActivityForObject(array $object, string $actor): array { if (!empty($object['published'])) { diff --git a/src/Util/JsonLD.php b/src/Util/JsonLD.php index f1d4da3f5b..8b70933217 100644 --- a/src/Util/JsonLD.php +++ b/src/Util/JsonLD.php @@ -26,6 +26,7 @@ use Friendica\Core\Logger; use Exception; use Friendica\Core\System; use Friendica\DI; +use Friendica\Protocol\ActivityPub; /** * This class contain methods to work with JsonLD data @@ -179,6 +180,10 @@ class JsonLD $orig_json = $json; + if (empty($json['@context'])) { + $json['@context'] = ActivityPub::CONTEXT; + } + // Preparation for adding possibly missing content to the context if (!empty($json['@context']) && is_string($json['@context'])) { $json['@context'] = [$json['@context']];