New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce whitelist test for link protocols. #13798
Conversation
private static $validLinkProtocols = [ | ||
'http', | ||
'https', | ||
'tel', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also asume callto
as safe, as it is still used by some old system for initiating Skype calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When in doubt, I'm against it. callto
is no official standard, so I can't promise that there is no way to exploit people clicking on the link accidentally and maybe automatically call a paid number or something like this, because some old badly written software doesn't want to make a confirm screen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can also happen with tel
if you (or some malware) changed the default program.
@@ -11,7 +11,7 @@ | |||
url|trim|lower starts with 'data:' %} | |||
{{ url }} | |||
{% else %} | |||
<a href="{{ url }}" rel="noreferrer noopener" target="_blank" | |||
<a href="{{ url|e('url') }}" rel="noreferrer noopener" target="_blank" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
haven't tested but think the URL escape should be usually used only on components, not on the whole URL? maybe double check those links still work when they include protocol etc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from the docs
url: escapes a string for the URI or parameter contexts. This should not be used to escape an entire URI; only a subcomponent being inserted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same for the other usages of this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@diosmosis can you change the escaping?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the PR, added a whole_url
filter that can be used to whitelist the url. Has to be used in conjunction w/ html_attr or |raw
otherwise twig will automatically escape w/ html
.
core/Twig.php
Outdated
@@ -65,7 +65,9 @@ function piwik_fix_lbrace($string) | |||
|
|||
function piwik_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false) { | |||
|
|||
$string = twig_escape_filter($env, $string, $strategy, $charset, $autoescape); | |||
if ($strategy != 'whole_url') { | |||
$string = twig_escape_filter($env, $string, $strategy, $charset, $autoescape); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any reason this is not done for whole_url? wouldn't it be the same as simple adding like a filter whole_url
that does below logic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When twig sees an escape like |e('html_attr')
it doesn't apply the default |e('html')
escape to a variable. But when twig sees |e('whole_url')
it applies the default escape. So if we call twig_escape_filter
above, it double encodes the URL. Only way I could come up w/ to not do that, is to make sure whole_url doesn't escape anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My message was bit confusing, not sure if understood what I meant. Just to be sure... what I meant was we could add whole_url
as a regular twig filter like this maybe:
private function addFilter_wholeUrl()
{
$filter = new \Twig_SimpleFilter('wholeUrl', function ($value) {
if (!UrlHelper::isLookLikeSafeUrl($string)) { ... } return $value
});
$this->twig->addFilter($filter);
}
and then use it like {{ link|wholeUrl|e('html_attr')}}
.
Then we wouldn't need the above if
... as whole_url
is not really escaping but more like filtering/changing the value? And using |e('whole_url')
could give a developer the impression the value will be escaped there (and think another escape html_attr is not needed) which in reality it doesn't...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see, yes, makes sense.
core/Twig.php
Outdated
@@ -74,6 +76,11 @@ function piwik_escape_filter(Twig_Environment $env, $string, $strategy = 'html', | |||
case 'url': | |||
$encoded = rawurlencode('{'); | |||
return str_replace('{{', $encoded . $encoded, $string); | |||
case 'whole_url': | |||
if (!UrlHelper::isLookLikeSafeUrl($string)) { // if this could be a dangerous link, replace it w/ an empty url | |||
return 'javascript:;'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would this need to be javascript:void(0)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it could be? I'm not sure if it would change anything (assuming it's for preventDefault()
?).
@tsteur looks like there was already a safelink filter, modified & used that. |
; | ||
} | ||
|
||
public static function isLookLikeSafeUrl($url) | ||
{ | ||
if (strpos($url, ':') === false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to merge but maybe we should here also execute self:: isLookLikeUrl ($url)
and only return true if it actually is a url?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tel:555...
and sms:...
links do not pass isLookLikeUrl, since they don't have //
. I didn't want to regress anything since isLookLikeUrl is used in tracking and probably everywhere else, so didn't modify it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: I did try and make the //
optional, but then everything passes isLookLikeUrl
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense 👍
core/UrlHelper.php
Outdated
} | ||
|
||
$protocol = explode(':', $url, 2)[0]; | ||
return preg_match('/^' . implode('|', self::$validLinkProtocols) . '/i', $protocol); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
small tweak maybe... should the protocol also check for ends with this? as in '/^' . implode('|', $validLinkProtocols) . '$/i'
otherwise smstest
might be considered a valid protocol?
Tweaked the regex. |
feel free to merge when tests pass |
No description provided.