aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Http/Client/DnsPinMiddleware.php
diff options
context:
space:
mode:
authorLukas Reschke <lukas@statuscode.ch>2021-03-23 16:41:31 +0000
committerGitHub <noreply@github.com>2021-04-06 11:37:47 +0000
commit5f3abffe6f37b4f8639fde8bcaf35d873a17636c (patch)
tree3498450ac8351f5a292dacc7cb17de9b27e4535b /lib/private/Http/Client/DnsPinMiddleware.php
parent2056b76c5fb29fa9273c50e17e54c5cf43f8a5fc (diff)
downloadnextcloud-server-5f3abffe6f37b4f8639fde8bcaf35d873a17636c.tar.gz
nextcloud-server-5f3abffe6f37b4f8639fde8bcaf35d873a17636c.zip
Improve networking checks
Whilst we currently state that SSRF is generally outside of our threat model, this is something where we should invest to improve this. Signed-off-by: Lukas Reschke <lukas@statuscode.ch>
Diffstat (limited to 'lib/private/Http/Client/DnsPinMiddleware.php')
-rw-r--r--lib/private/Http/Client/DnsPinMiddleware.php128
1 files changed, 128 insertions, 0 deletions
diff --git a/lib/private/Http/Client/DnsPinMiddleware.php b/lib/private/Http/Client/DnsPinMiddleware.php
new file mode 100644
index 00000000000..5a87ff705a6
--- /dev/null
+++ b/lib/private/Http/Client/DnsPinMiddleware.php
@@ -0,0 +1,128 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2021, Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @author Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Http\Client;
+
+use Psr\Http\Message\RequestInterface;
+
+class DnsPinMiddleware {
+ /** @var NegativeDnsCache */
+ private $negativeDnsCache;
+ /** @var LocalAddressChecker */
+ private $localAddressChecker;
+
+ public function __construct(
+ NegativeDnsCache $negativeDnsCache,
+ LocalAddressChecker $localAddressChecker) {
+ $this->negativeDnsCache = $negativeDnsCache;
+ $this->localAddressChecker = $localAddressChecker;
+ }
+
+ private function dnsResolve(string $target, int $recursionCount) : array {
+ if ($recursionCount >= 10) {
+ return [];
+ }
+
+ $recursionCount = $recursionCount++;
+ $targetIps = [];
+
+ $soaDnsEntry = dns_get_record($target, DNS_SOA);
+ if (isset($soaDnsEntry[0]) && isset($soaDnsEntry[0]['minimum-ttl'])) {
+ $dnsNegativeTtl = $soaDnsEntry[0]['minimum-ttl'];
+ } else {
+ $dnsNegativeTtl = null;
+ }
+
+ $dnsTypes = [DNS_A, DNS_AAAA, DNS_CNAME];
+ foreach ($dnsTypes as $key => $dnsType) {
+ if ($this->negativeDnsCache->isNegativeCached($target, $dnsType)) {
+ unset($dnsTypes[$key]);
+ continue;
+ }
+
+ $dnsResponses = dns_get_record($target, $dnsType);
+ $canHaveCnameRecord = true;
+ if (count($dnsResponses) > 0) {
+ foreach ($dnsResponses as $key => $dnsResponse) {
+ if (isset($dnsResponse['ip'])) {
+ $targetIps[] = $dnsResponse['ip'];
+ $canHaveCnameRecord = false;
+ } elseif (isset($dnsResponse['ipv6'])) {
+ $targetIps[] = $dnsResponse['ipv6'];
+ $canHaveCnameRecord = false;
+ } elseif (isset($dnsResponse['target']) && $canHaveCnameRecord) {
+ $targetIps = array_merge($targetIps, $this->dnsResolve($dnsResponse['target'], $recursionCount));
+ $canHaveCnameRecord = true;
+ }
+ }
+ } else {
+ if ($dnsNegativeTtl !== null) {
+ $this->negativeDnsCache->setNegativeCacheForDnsType($target, $dnsType, $dnsNegativeTtl);
+ }
+ }
+ }
+
+ return $targetIps;
+ }
+
+ public function addDnsPinning() {
+ return function (callable $handler) {
+ return function (
+ RequestInterface $request,
+ array $options
+ ) use ($handler) {
+ if ($options['nextcloud']['allow_local_address'] === true) {
+ return $handler($request, $options);
+ }
+
+ $hostName = (string)$request->getUri()->getHost();
+ $port = $request->getUri()->getPort();
+
+ $ports = [
+ '80',
+ '443',
+ ];
+
+ if ($port != null) {
+ $ports[] = (string)$port;
+ }
+
+ $targetIps = $this->dnsResolve($hostName, 0);
+
+ foreach ($targetIps as $ip) {
+ $this->localAddressChecker->ThrowIfLocalIp($ip);
+
+ foreach ($ports as $port) {
+ $curlEntry = $hostName . ':' . $port . ':' . $ip;
+ $options['curl'][CURLOPT_RESOLVE][] = $curlEntry;
+ }
+ }
+
+ return $handler($request, $options);
+ };
+ };
+ }
+}