From 9714d0f03682f33e3c0cee38e1bf510448ea48c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mr=E2=9D=A4=EF=B8=8F=F0=9F=A4=A2?= <99399973+mrilyew@users.noreply.github.com> Date: Sun, 15 Jun 2025 16:55:27 +0300 Subject: [PATCH 1/2] feat(notes): use whitelist for images sources (#1352) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Переиначенный #942, но в нём картинка скачивалась с сервера, в этом в конфиге задаётся список разрешённых хостов и затем идёт редирект если ссылка прошла проверку. Если не прошла то редиректает на заглушку. Впрочем, это не поможет если в конфиге не указан cdn, но по крайней мере не будет приколов с автозапуском методов на основном сайте После мержа в конфиг добавьте kaslana.ovk.to ![image](https://github.com/user-attachments/assets/6f72add7-1d9f-4ca8-8938-3e00be5b61f7) --------- Co-authored-by: n1rwana <93197434+n1rwana@users.noreply.github.com> --- Web/Models/Entities/Note.php | 51 ++++++++++++++---- Web/Presenters/InternalAPIPresenter.php | 22 ++++++++ Web/Presenters/templates/About/Version.xml | 8 +-- .../templates/components/textArea.xml | 4 +- Web/routes.yml | 2 + Web/static/img/fn_placeholder.jpg | Bin 0 -> 14532 bytes openvk-example.yml | 3 ++ 7 files changed, 76 insertions(+), 14 deletions(-) create mode 100644 Web/static/img/fn_placeholder.jpg diff --git a/Web/Models/Entities/Note.php b/Web/Models/Entities/Note.php index a44c421b..b9829822 100644 --- a/Web/Models/Entities/Note.php +++ b/Web/Models/Entities/Note.php @@ -6,12 +6,42 @@ namespace openvk\Web\Models\Entities; use HTMLPurifier_Config; use HTMLPurifier; +use HTMLPurifier_Filter; + +class SecurityFilter extends HTMLPurifier_Filter +{ + public function preFilter($html, $config, $context) + { + $html = preg_replace_callback( + '/]*src\s*=\s*["\']([^"\']*)["\'][^>]*>/i', + function ($matches) { + $originalSrc = $matches[1]; + $src = $originalSrc; + + if (OPENVK_ROOT_CONF["openvk"]["preferences"]["notes"]["disableHotlinking"] ?? true) { + if (!str_contains($src, "/image.php?url=")) { + $src = '/image.php?url=' . base64_encode($originalSrc); + } /*else { + $src = preg_replace_callback('/(.*)\/image\.php\?url=(.*)/i', function ($matches) { + return base64_decode($matches[2]); + }, $src); + }*/ + } + + return str_replace($originalSrc, $src, $matches[0]); + }, + $html + ); + + return $html; + } +} class Note extends Postable { protected $tableName = "notes"; - protected function renderHTML(): string + protected function renderHTML(?string $content = null): string { $config = HTMLPurifier_Config::createDefault(); $config->set("Attr.AllowedClasses", []); @@ -78,16 +108,19 @@ class Note extends Postable $config->set("Attr.AllowedClasses", [ "underline", ]); + $config->set('Filter.Custom', [new SecurityFilter()]); - $source = null; - if (is_null($this->getRecord())) { - if (isset($this->changes["source"])) { - $source = $this->changes["source"]; + $source = $content; + if (!$source) { + if (is_null($this->getRecord())) { + if (isset($this->changes["source"])) { + $source = $this->changes["source"]; + } else { + throw new \LogicException("Can't render note without content set."); + } } else { - throw new \LogicException("Can't render note without content set."); + $source = $this->getRecord()->source; } - } else { - $source = $this->getRecord()->source; } $purifier = new HTMLPurifier($config); @@ -117,7 +150,7 @@ class Note extends Postable $this->save(); } - return $cached; + return $this->renderHTML($cached); } public function getSource(): string diff --git a/Web/Presenters/InternalAPIPresenter.php b/Web/Presenters/InternalAPIPresenter.php index 464059cb..eb9daac2 100644 --- a/Web/Presenters/InternalAPIPresenter.php +++ b/Web/Presenters/InternalAPIPresenter.php @@ -176,4 +176,26 @@ final class InternalAPIPresenter extends OpenVKPresenter exit(''); } } + + public function renderImageFilter() + { + $is_enabled = OPENVK_ROOT_CONF["openvk"]["preferences"]["notes"]["disableHotlinking"] ?? true; + $allowed_hosts = OPENVK_ROOT_CONF["openvk"]["preferences"]["notes"]["allowedHosts"] ?? []; + + $url = $this->requestParam("url"); + $url = base64_decode($url); + + if (!$is_enabled) { + $this->redirect($url); + } + + $url_parsed = parse_url($url); + $host = $url_parsed['host']; + + if (in_array($host, $allowed_hosts)) { + $this->redirect($url); + } else { + $this->redirect('/assets/packages/static/openvk/img/fn_placeholder.jpg'); + } + } } diff --git a/Web/Presenters/templates/About/Version.xml b/Web/Presenters/templates/About/Version.xml index c4d9c132..8b61d41c 100644 --- a/Web/Presenters/templates/About/Version.xml +++ b/Web/Presenters/templates/About/Version.xml @@ -385,8 +385,8 @@ Vladimir Barinov (veselcraft), Celestora, Konstantin Kichulkin (kosfurler), - Daniel Myslivets, Maxim Leshchenko (maksales / maksalees), n1rwana and - Jillian Österreich (Lumaeris) + Daniel Myslivets, Maxim Leshchenko (maksales / maksalees), n1rwana, + Jillian Österreich (Lumaeris) and MrIlyew (V00d00 M4g1c) @@ -472,7 +472,7 @@ - + {*
@@ -486,7 +486,7 @@ -
OpenVK QA Team
+ *}
diff --git a/Web/Presenters/templates/components/textArea.xml b/Web/Presenters/templates/components/textArea.xml index 2be32280..c516eca2 100644 --- a/Web/Presenters/templates/components/textArea.xml +++ b/Web/Presenters/templates/components/textArea.xml @@ -22,6 +22,8 @@ {if !is_null($thisUser) && !is_null($club ?? NULL) && $owner < 0} {if $club->canBeModifiedBy($thisUser)} + {var $anonHide = true} +