mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-04-08 17:15:08 +02:00
Only open trusted iframe sources by default
Trusted in this case means, it was Icinga Web that rendered a link and the user followed it. Whether a source is trustworthy or not is detected by use of the user's session id to hash it combined with the source similar to how CSRF tokens are assembled. (cherry picked from commit ec40efe1578c3c9cb445638f78e76a940a6864cf)
This commit is contained in:
parent
6ddf61981c
commit
673998bb9a
@ -3,18 +3,108 @@
|
||||
|
||||
namespace Icinga\Controllers;
|
||||
|
||||
use Icinga\Web\Controller;
|
||||
use Icinga\Web\Session;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Html\HtmlString;
|
||||
use ipl\Html\Text;
|
||||
use ipl\Web\Compat\CompatController;
|
||||
use ipl\Web\Url;
|
||||
use ipl\Web\Widget\Icon;
|
||||
use ipl\Web\Widget\Tabs;
|
||||
|
||||
/**
|
||||
* Display external or internal links within an iframe
|
||||
*/
|
||||
class IframeController extends Controller
|
||||
class IframeController extends CompatController
|
||||
{
|
||||
/**
|
||||
* Display iframe w/ the given URL
|
||||
*/
|
||||
public function indexAction()
|
||||
public function indexAction(): void
|
||||
{
|
||||
$this->view->url = $this->params->getRequired('url');
|
||||
$url = Url::fromPath($this->params->getRequired('url'));
|
||||
$urlHash = $this->getRequest()->getHeader('X-Icinga-URLHash');
|
||||
$expectedHash = hash('sha256', $url->getAbsoluteUrl() . Session::getSession()->getId());
|
||||
$iframeUrl = Url::fromPath('iframe', ['url' => $url->getAbsoluteUrl()]);
|
||||
|
||||
if (! in_array($url->getScheme(), ['http', 'https'], true)) {
|
||||
$this->httpBadRequest('Invalid URL scheme');
|
||||
}
|
||||
|
||||
$this->injectTabs();
|
||||
|
||||
$this->getTabs()->setRefreshUrl($iframeUrl);
|
||||
|
||||
if ($urlHash) {
|
||||
if ($urlHash !== $expectedHash) {
|
||||
$this->httpBadRequest('Invalid URL hash');
|
||||
}
|
||||
} else {
|
||||
$this->addContent(Html::tag('div', ['class' => 'iframe-warning'], [
|
||||
Html::tag('h2', $this->translate('Attention!')),
|
||||
Html::tag('p', ['class' => 'note'], $this->translate(
|
||||
'You are about to open untrusted content embedded in Icinga Web! Only proceed,'
|
||||
.' by clicking the link below, if you recognize and trust the source!'
|
||||
)),
|
||||
Html::tag('a', ['data-url-hash' => $expectedHash, 'href' => Html::escape($iframeUrl)], $url),
|
||||
Html::tag('p', ['class' => 'reason'], [
|
||||
new Icon('circle-info'),
|
||||
Text::create($this->translate(
|
||||
'You see this warning because you do not seem to have followed a link in Icinga Web.'
|
||||
. ' You can bypass this in the future by configuring a navigation item instead.'
|
||||
))
|
||||
])
|
||||
]));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->getTabs()->setHash($expectedHash);
|
||||
|
||||
$this->addContent(Html::tag(
|
||||
'div',
|
||||
['class' => 'iframe-container'],
|
||||
Html::tag('iframe', [
|
||||
'src' => $url,
|
||||
'sandbox' => 'allow-same-origin allow-scripts allow-popups allow-forms',
|
||||
])
|
||||
));
|
||||
}
|
||||
|
||||
private function injectTabs(): void
|
||||
{
|
||||
$this->tabs = new class extends Tabs {
|
||||
private $hash;
|
||||
|
||||
public function setHash($hash)
|
||||
{
|
||||
$this->hash = $hash;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function assemble()
|
||||
{
|
||||
$tabHtml = substr($this->tabs->render(), 34, -5);
|
||||
if ($this->refreshUrl !== null) {
|
||||
$tabHtml = preg_replace(
|
||||
[
|
||||
'/(?<=class="refresh-container-control spinner" href=")([^"]*)/',
|
||||
'/(\s)(?=href)/'
|
||||
],
|
||||
[
|
||||
$this->refreshUrl->getAbsoluteUrl(),
|
||||
' data-url-hash="' . $this->hash . '" '
|
||||
],
|
||||
$tabHtml
|
||||
);
|
||||
}
|
||||
|
||||
BaseHtmlElement::add(HtmlString::create($tabHtml));
|
||||
}
|
||||
};
|
||||
|
||||
$this->controls->setTabs($this->tabs);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +0,0 @@
|
||||
<?php if (! $compact): ?>
|
||||
<div class="controls">
|
||||
<?= $tabs ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<div class="iframe-container">
|
||||
<iframe src="<?= $this->escape($url) ?>" frameborder="no"></iframe>
|
||||
</div>
|
@ -7,6 +7,7 @@ use Icinga\Application\Icinga;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Util\StringHelper;
|
||||
use Icinga\Web\Navigation\NavigationItem;
|
||||
use Icinga\Web\Session;
|
||||
use Icinga\Web\Url;
|
||||
use Icinga\Web\View;
|
||||
|
||||
@ -188,6 +189,10 @@ class NavigationItemRenderer
|
||||
|
||||
$target = $item->getTarget();
|
||||
if ($url->isExternal() && (!$target || in_array($target, $this->internalLinkTargets, true))) {
|
||||
$item->setAttribute('data-url-hash', hash(
|
||||
'sha256',
|
||||
$url->getAbsoluteUrl() . Session::getSession()->getId()
|
||||
));
|
||||
$url = Url::fromPath('iframe', array('url' => $url));
|
||||
}
|
||||
|
||||
|
@ -282,6 +282,39 @@ a:hover > .icon-cancel {
|
||||
|
||||
// Responsive iFrames
|
||||
|
||||
.iframe-warning {
|
||||
h2, p, a {
|
||||
display: block;
|
||||
width: fit-content;
|
||||
font-size: 200%;
|
||||
margin: 0 auto;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1000%;
|
||||
color: @state-warning;
|
||||
}
|
||||
|
||||
.note {
|
||||
background: @gray-lighter;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.reason {
|
||||
.icon {
|
||||
color: @text-color;
|
||||
}
|
||||
|
||||
font-size: 100%;
|
||||
background: @gray-lightest;
|
||||
color: @text-color-light;
|
||||
}
|
||||
}
|
||||
|
||||
.iframe-container {
|
||||
position: relative;
|
||||
height: 0;
|
||||
@ -295,6 +328,7 @@ a:hover > .icon-cancel {
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -263,6 +263,7 @@
|
||||
var $eventTarget = $(event.target);
|
||||
var href = $a.attr('href');
|
||||
var linkTarget = $a.attr('target');
|
||||
const urlHash = this.dataset.urlHash;
|
||||
var $target;
|
||||
var formerUrl;
|
||||
|
||||
@ -373,7 +374,20 @@
|
||||
}
|
||||
|
||||
// Load link URL
|
||||
icinga.loader.loadUrl(href, $target);
|
||||
if (urlHash) {
|
||||
icinga.loader.loadUrl(
|
||||
href,
|
||||
$target,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{ "X-Icinga-URLHash": urlHash }
|
||||
);
|
||||
} else {
|
||||
icinga.loader.loadUrl(href, $target);
|
||||
}
|
||||
|
||||
if ($a.closest('#menu').length > 0) {
|
||||
// Menu links should remove all but the first layout column
|
||||
|
Loading…
x
Reference in New Issue
Block a user