If you’re a WordPress developer that writes HTML/CSS/JS (which is 100% of theme developers and 99% of plugin developers), you need to know the basics of front end security for WordPress. WordPress gives you all the tools you need to make your theme or plugin secure. You just need to know how and when to use each tool.
For example, one huge responsibility you have as a front-end developer is to prevent unescaped content from being printed to the page.
Security Is Your Responsibility
Before we talk specifics, I’d like to squash an idea that I would (previously) use to rationalize a “less strict” view of front end security:
“If a hacker is already able to change the code/database/content, what difference does it make if my front-end code is secure? Couldn’t they already do more substantial damage?”
Yes, if a hacker already has control of the code or database, chances are they don’t care how well-secured the output is (they can probably change it anyway).
Even so, you still have a responsibility to make the front-end code secure. A few reasons why:
- Proper front end security also prevents user error from causing big problems. If the user accidentally puts a special character in a field they weren’t supposed to, the page won’t explode.
- Some attacks are limited. Perhaps the hacker had minimal control and could only change a single piece of content in the database. You can prevent that narrow attack from becoming much larger.
- Security is an onion. The outermost (often inedible) layer is the front-end display. Good security practices make it that tiny bit harder for a hacker to exploit a site.
Enough with the pep talk. Let’s talk about securing our WordPress themes and plugins.
What Are We Worried About?
A major concern for a front end developer is avoiding Cross-Site Scripting (XSS for short, because ‘CSS’ would cause all sorts of problems. Puns and one-liners welcome.).
XSS vulnerabilities allow bad people (“hackers”) to steal information. Typically that means stealing cookies and session information. If a bad person can send your active session back to themselves (hence the “cross site” part), they could use that to be logged in to your account and do anything you could do (bad news bears).
What is Escaping?
In computer science the word “escape” has a special meaning:
To convert a character or string of characters to be interpreted literally within a specific context, typically to prevent those characters from being interpreted as code.
Put another way, in the context of WordPress front-end development: Escaping changes possibly evil content into safe content.
The biggest danger here is generally <script>
tags and other methods of executing JavaScript (e.g. onclick="javascript:"
). If a <script>
can be output on the page an executed, that’s super dangerous. Escaping means turning that into <script> which is safe.
Why You Need To Escape: An Example
In case you’re not familiar with XSS, a quick example will demonstrate the risk.
Suppose your theme had a field to add a link for further reading. A “Read More” link, if you will. On the WordPress dashboard, it might look like this:

In your theme, you’d like to display this link at the bottom of the post, like so:

So, you open up single.php
(the file responsible for displaying blog posts) and add the following near the bottom:
<?php
$read_more_link = get_post_meta(
get_the_ID(),
'read_more_link',
true
);
?>
<!-- Don't do this -->
<a href="<?php echo $read_more_link; ?>">Read More</a>
Suppose someone maliciously enters this text into your custom field:

When you visit the page, here’s what you’ll see:

Yikes! If that bad input allowed a bad person to execute JavaScript, they could:
- Redirect the user
- Hijack the user’s cookies
- Do other bad things
An Escape for Everything
Now that we know how important escaping is, let’s look at the escaping methods WordPress provides and give some context for each.
Function: esc_html
Used for: Output that should have absolutely no HTML in the output.
What it does: Converts HTML special characters (such as <
, >
, &
) into their “escaped” entity (<
, >
, &
).
A common example would be displaying text-only custom fields in a theme:
<?php $dog_name = get_post_meta( $post_id, 'dog_name', true ); ?>
<span class="dog-name"><?php echo esc_html( $dog_name ); ?></span>
Function: esc_attr
Used for: Output being used in the context of an HTML attribute (think “title”, “data-” fields, “alt” text).
What it does: The exact same thing as esc_html
. The only difference is that different WordPress filters are applied to each function.
Here’s esc_attr
used on an image:
<img src="/images/duck.png"
alt="<?php echo esc_attr( $alt_text ); ?>"
title="<?php echo esc_attr( $title ); ?>" >
Function: esc_url
Used for: Output that is necessarily a URL. Examples would be image src attributes and href values.
What it does: A more thorough, specific escaping than the esc_attr
& esc_html
functions, which removes or converts any characters not allowed in URLs into their URL-safe format.
Use esc_url
when you need to output a link or dynamic image path:
<a href="<?php echo esc_url( $url ); ?>">Link Text</a>
<img src="<?php echo esc_url( $image_url ); ?>" >
Function: esc_js
Used for: Printing JavaScript strings, primarily on inline attributes like onclick
.
What it does: Converts special characters like <
, >
, quotes – anything that could break JavaScript code.
esc_js
is probably the least used of the esc_ functions, for a few reasons:
- Most JS is loaded in a separate file.
- Most JS isn’t written inline as attributes.
- For non-inline JS,
json_encode
is a better option.
But, if you ever need to escape some inline JS, here’s how you’d do it:
<?php $message = get_post_meta( get_the_ID(), 'onclick_message', true ); ?>
<a href="/blog/" onclick="alert('<?php echo esc_js( $message ); ?>')">...</a>
Function: json_encode
Used for: Printing a PHP variable for use in JavaScript.
What it does: Converts a PHP variable (object, string, array, whatever) into a sensible, escaped JavaScript representation of that PHP variable.
Useful in particular for <script>
blocks making use of WP variables. A simple example:
<
pre rel=”PHP”><?php $categories = get_categories(); ?>
<script type="text/javascript">
var allCategories = <?php echo json_encode( $categories ); ?>;
// Do something interesting with categories here
</script>
Function: wp_kses
Used for: Output that needs to allow some HTML, but not all tags or attributes.
What it does: Strips the content of any tags or attributes that don’t match the list of rules passed in.
Use wp_kses
if there is a context that allows certain tags (e.g. inline formatting tags like <strong> and <em>) to be printed as HTML.
A basic example would be displaying comments:
esc_attr_e
, esc_attr_x
)?
What are the _e and _x versions of the escaping functions (These are convenience functions (to make your life easier), and they’re useful when printing translatable strings: text that can be changed with a WordPress translation file.
If you’re developing a theme or plugin for wide distribution (vs. a one-use client project), you’ll want to make every string internationalization-friendly. That means PHP strings that were once under your control become editable by translators – hence the need for escaping (you can’t trust anybody):
Blog
The second parameter to the _e
functions is the translation domain. It is used by translators when they write and generate their translations.
The _x
functions (like esc_html_x) are essentially the same as their _e
counterparts, with an added “context” argument to explain the context a word or phrase is used in. Useful for words with multiple meanings:
Codex entry for esc_attr_e
Codex entry for esc_attr_x
the_title
and the_content
?
What about If you crack open the default WordPress theme (named “Twenty Fifteen” at the time of this writing), you’ll see code like this outputting the title of a post:
You’ll also see unescaped calls to the_content
like so:
You may be panicking. Why aren’t those functions escaped?! 2 reasons:
1. WordPress automatically escapes them
Functions like the_title
are convenience functions – tools that make front-end development easier. As such, to make it even easier for developers, WordPress already automatically escapes the output for the_title
Update: WordPress does not escape the_title
, be careful there!.
2. Context: Some Content is HTML
In the case of the_content
, the output is expected to be unescaped HTML. When the user adds a
to their content, we can assume they actually want a
output to the page – instead of the escaped version.
If you have security concerns with a user being able to add scripts to the_content
‘s output, you can use wp_kses
to strip unwanted tags from the final output. This would be useful on subdomains and shared hosting, and I’m pretty sure this is what they use on WordPress.com (a hosted version of WordPress where users aren’t allowed to add their own JavaScript).
Handy tip: If you want to use the_title
as an HTML attribute, use the_title_attibute to save yourself an escape. the_title_attribute
in action:
...
Escape the Things
I work with a lot of plugins and themes – commercial, free, and custom-built – and I see lots of unescaped content (and resulting XSS vulnerabilities). Hopefully, this article will equip you with the basic tools you need to make your WordPress things safer.
Did I overlook anything? Let me know in the comments!
I always thought that WordPress should escape all queries by default — with an optional parameter to disable this. The world would be a safer place, wouldn’t it?
This is what Ruby on Rails does, and yes it would be nice. But I’m not sure how it could be done without breaking basically every plugin ever written!
It’s 2015, yet we’re still writing escape calls on the template layer like it’s 1999. And this is among the reasons I’ve stopped working with WP; it’s just too easy to write really terrible code. I don’t want to get into a debate about template languages being good or bad but after using something like Twig (in Craft, Drupal 8 and other CMS), where it escapes variables by default, it’s one less thing as a designer/developer ultimately has to worry about.
On the custom fields example, this is an instance of why WP really needs to bake in proper custom field support as well (something like ACF or Pods comes to mind) that also validates the user’s input (text / html / image / link / etc) and purifies that input as necessary before it even hits the database.
OMG, I think I use it
<a href="<?php echo $read_more_link; " read more /a
I hope that does not happen on my website. thank you for the info, its very useful for beginners like me.
Love the casual reference to your website. I sure hope I never go there since apparently it is filled with security holes.
/s
What about wrapping your output in wp_strip_all_tags() ?
https://codex.wordpress.org/Function_Reference/wp_strip_all_tags
I always use this function if I have to let someone post on my site without me knowing them very well.
i didnt understand how can you plant a js script , if the attacker cant access the server files, if i was the attacker i would delete all the websites content and database
This is awesome! I am currently working inside Craft CMS and came across this page as I am looking into Twig: http://bit.ly/1BnRslK. Hope it’s helpful for those attempting to replicate these principles in Craft.
Thanks so much for this. As a self-taught designer/developer using mainly HTML, CSS, JS, it seems like this an area that no one ever really talks about (or maybe I don’t know where to look). Definitely learned some things. Definitely have a few things to fix ASAP.
Sadly nearly no plugin has that fine & safe code. But I hope this will change! – When I see “valid code” or “well coded” – Mostly the plugins are completely unsafe!
Just look a few weeks ago:
http://blog.sucuri.net/2014/09/slider-revolution-plugin-critical-vulnerability-being-exploited.html
Thanks for that great post!
What if one would use htmlspecialchars()? Does that still have the security vulnerabilities?
Under the hood,
esc_attr
and the like usehtmlspecialchars
. The biggest difference I see is thathtmlspecialchars
might skip escaping quotes if a PHP flag is set:http://php.net/htmlspecialchars
There’s a mistake:
esc_html_e
already echoes, soecho esc_html_e
does a double echo.In your example of
json_encode()
for passing PHP variables to JS, the “WordPress Way” would be to usewp_localize_script()
instead.A handy function for escaping content which may contain HTML is
wp_kses_post()
which is a shortcut for thewp_kses()
function and whitelists common HTML tags.For more, have a look at
wp-includes/formatting.php
Thanks for the correction! I got echo-happy.
Sorry if I’m not understanding this, I’m not much of a backend developer, and I rarely work with wordpress. My question is how would someone be able to put the malicious code into the custom input field? Wouldn’t they need access to your dashboard, which means you’ve already sort of been hacked anyway?
If you’re a front end dev, XSS is less of an issue here unless your visitors regularly login or have privileges other than read only on the site. Keep in mind though that XSS, even if not destructive, can be annoying. Hackers don’t have to necessarily steal login details to get their lulz, cleaning up a site after it has been defaced is not fun.
Most hack attempts don’t access your dashboard directly, they exploit flaws in themes and plugins. (Instead of going through the back door, a thief might notice a window that’s easy to open in the basement instead.)
WP is very extensible with hooks and filters but in that power, if something isn’t written well, it can open you up to security problems. Even if your own code is bulletproof, other plugins in the ecosystem may not be and that’s where you need to be careful.
A lot of exploits come from not the plugin in question but piggybacking other plugins flaws. (That slider plugin mentioned above is an example of a really nasty bug.)
Mark Jaquith had a WordCamp talk on this subject that’s good further reading.