Shortcode Security Suggestions
This page contains important information about how shortcodes work, and will work in future versions. If you use shortcode with custom formats, this is of particular importance to you!
Prior to version 6.4.8
, shortcodes behaved slightly differently by decoding html entities on all supplied formats. This included wrapped content like [shortcode]content[/shortcode]
and the parameters such as format
, format_header
and format_footer
.
This left shortcodes exposed to potential vulnerabilities since any users with publishing capabilities could potentially supply harmful HTML content through these shortcode formats. We received a report from the WordFence security team, who had discovered a vulnerability, which was fixed in 6.4.8
.
The end result is that some websites that legitimately supplied html entity-encoded formats within shortcodes would end up with unexpected results. Check this short gif video for a demonstration on how this can be confusing on editors once running 6.4.8
or higher:
Ultimately, on the classic editor, or on Gutenberg, if you're using a visual editor and you see raw HTML, then that's what you'll get on the front-end because what you're really outputting to your visitor is this:
[events_list]<p>#_EVENTNAME</p>[/events_list]
Which will look exactly like this on the page:
<p>Event X</p><p>Event Y</p>
Previously, EM would decode and display the content as one may have hoped. This, in reality, is the wrong approach. Entity-encoded HTML cannot be reliably sanitized and becomes a security risk once you allow users into your site to submit content that gets published.
The Ideal Solution
The majority of users these days will likely be doing it this way. You'll know you're 'doing it wrong' if you see the the HTML format in an editor, whilst other parts of that editor or block may be bold or formatted.
Ideally, you will use shortcode as intended, and supply HTML via the HTML or shortcode blocks in Gutenberg, or in 'text' mode via the Classic Editor. This ensures that any submitted content is correctly sanitized by WordPress itself, allowing only the HTML the submitting user is permitted to use.
In the video further up, we show you how to achieve this with the Classic Editor, Shortcode, and HTML blocks. Paragraph blocks are not recommended if using formatting.
Intermediate Solutions and Risks
Risks
This is particularly of importance if you allow untrusted users to submit content on your site. This includes MultiSite instances, site admins are also not considered 'trustworthy' as they are still submitting content to your network.
If you trust all your users, or you have only limited user(s) that can publish content, then the risk is significantly lower. However, we still recommend transitioning at some point, since if a hacker does manage to inject content into your site somehow, they could leverage this risk.
Solutions
These solutions are included in the 6.4.11
release, which is not yet released at time of writing. You can upgrade to dev version 6.4.10.2
, which does include these fixes.
Alternatively, you can implement the code snippet provided further down, which will work on 6.4.9
and later.
If you have been using Events Manager for some time, then it's very possible you've unknowingly added shortcode throughout your pages and received a shock updating to 6.4.8
or later. Version 6.4.11
introduces a few important settings you can adjust in your settings page to temporarily rectify this.
You will find the following options under Events > Settings > Admin Tools > HTML Escaping Options :
Decode shortcode content?
If you have shortcode like this:
[events_list]<p>#_EVENTNAME</p>[/events_list]
Then enabling this option will display your content as expected, Event names, one per line/paragraph. We recommend going through your content and modifyng it accordingly. In most cases, it's a copy/paste job. Copy the desired HTML whilst in visual mode, switch to text and paste/replace the entity-encoded content.
Sanitize shortcode content?
If the above setting is enabled, then we recommend leaving this setting on too, as it will pass all decoded content through wp_kses()
each time it is displayed so only specific allowed HTML will pass through, and any malicious content is removed.
wp_kses()
is a more resource-intensive function than other escaping methods used during output, and is intended for when saving content. This may impact server performance to some degree particularly for high-traffic websites.
Allow format shortcode parameters?
Format parameters format
, format_header
and format_footer
ideally should not be used in shortcode. These formatting parameters should be added within, before and after the shortcode, in respective order.
If enabled, all three format parameters are run through wp_kses()
each time the containing page is displayed. This is more resource intensive, but necessary for security and there is no option to disable this behaviour.
Decode shortcode format parameters?
If enabled, you can opt not to decode these format parameters, this is certainly a safer option, however we will always sanitize via wp_kses()
each time (see above setting).
Transitioning Forward
At some point in the future, we will completely remove the above options, and cease to allow format parameters in shortcode or entity-decode the content supplied within shortcode.
If you do use entity-escaped content and still wish to use it, then your only option is to use some PHP to reverse the transition. You can add the following code as per these instructions, and you will always be able to use format parameters and entity-encoded text on your shortcode.
function legacy_em_clean_shortcde_args ( $args, $format, $supplied_args = array() ){
// re-enable format params if they were removed
if( !empty($supplied_args['format']) && empty($format) ){
$args['format'] = $supplied_args['format'];
}
if( !empty($supplied_args['format_header']) ){
$args['format_header'] = $supplied_args['format_header'];
}
if( !empty($supplied_args['format_footer']) ){
$args['format_footer'] = $supplied_args['format_footer'];
}
// html_entity_decode all three format array items in $args
if( !empty($args['format']) ) $args['format'] = html_entity_decode($args['format']);
if( !empty($args['format_header']) ) $args['format_header'] = html_entity_decode($args['format_header']);
if( !empty($args['format_footer']) ) $args['format_footer'] = html_entity_decode($args['format_footer']);
// wp_kses() the format content
global $allowedposttags;
if( !empty($args['format']) ) $args['format'] = wp_kses($args['format'], $allowedposttags );
if( !empty($args['format_header']) ) $args['format_header'] = wp_kses($args['format_header'], $allowedposttags );
if( !empty($args['format_footer']) ) $args['format_footer'] = wp_kses($args['format_footer'], $allowedposttags );
// return the params
return $args;
}
add_filter('em_clean_shortcode_args', 'legacy_em_clean_shortcde_args', 10, 3);