Kévin Subileau

Espace personnel


Local file disclosure vulnerability in Crayon Syntax Highlighter

I discovered a local file disclosure vulnerability affecting all versions before 2.7.0 of Crayon Syntax Highlighter, a popular syntax highlighter built in PHP and jQuery. According to wordpress.org, the vulnerable versions of this WordPress plugin are installed on more than 40,000 websites.

This critical vulnerability allows remote attackers to read arbitrary files on server's file system, even outside the web root. This includes PHP source code, configuration files and system files as /etc/passwd or wp-config.php for example. Furthermore, as you will see below, authentication is generally not required to exploit this vulnerability. Of course, the web server must have read access on the target files.

I tested a few versions between the old 1.10 to the latest 2.6.10, with a freshly installed WordPress 4.1 and the default configuration, and all tested versions were vulnerable. This means that the vulnerability exists since at least 3 years.

Technical analysis

Crayon Syntax Highlighter can highlight from a URL, inline code, or a local file. Here are some examples of normal markup that could be put inside post content in order to highlight code from these three source types:

<!-- Inline code -->
<pre class="lang:php">
    <?php //code to highlight here ?>

<!-- URL -->
<pre class="lang:java" data-url="http://example.com/class.java"></pre>

<!-- Local file - the interesting case -->
<pre class="lang:java" data-url="/java_sample/class.java"></pre>

In the local file case, by default, the path given in the data-url attribute is relative to the WordPress root directory. In the plugin settings page, the user can also specify a sub-folder from which this relative path should start. But, as you can see below in this piece of code extracted from crayon, the problem is that there is absolutely no control over the file type or the path given (stored in the variable $url).

// Try to replace the site URL with a path to force local loading
if (strpos($url, $site_http) !== FALSE || strpos($url, $site_path) !== FALSE ) {
    $url = str_replace($site_http, $site_path, $url);
    // Attempt to load locally
    $local = TRUE;
    $local_url = $url;
} else if (empty($scheme)) {
    // No url scheme is given - path may be given as relative
    $local_url = preg_replace('#^((\/|\\\\)*)?#', $site_path . $this->setting_val(CrayonSettings::LOCAL_PATH), $url);
    $local = TRUE;
// Try to find the file locally
if ($local == TRUE) {
    if ( ($contents = CrayonUtil::file($local_url)) !== FALSE ) {
    } else {
        // [...]

The given path is simply prepended with the base path at line 79, and then the file's content is loaded at line 84 (the method CrayonUtil::file just calls the function file_get_contents). So, for example, assuming that the administrator hasn't modified the standard WordPress file structure and with the default settings, it's possible to get the database credentials and WordPress secret keys simply by putting the code below inside a post or page content:

<pre data-url="/wp-config.php"></pre>

You can also use a directory traversal attack to reach the root directory and get the content of /etc/passwd for example:

<pre data-url="/../../../../../../../../../../../../../../../../../etc/passwd"></pre>

Of course it's also possible to get the content of theme's source files, .htaccess and .htpasswd files, log files, and other files that could contain highly sensitive information.

But so far, I told you that these exploits must be put inside a post or page, so normally this requires authentication. But the real issue is that unauthenticated visitors can also exploit the vulnerability simply by posting a comment containing the same malicious markup (except when an alternative comment system is used, as Disqus). It's because Crayon can also highlight inside comments, and this feature is enabled by default. This is why I say that authentication is generally not required to exploit this vulnerability.

Even if comments are moderated before publication, the issue remains the same because WordPress display the comment to its author before moderation with a message like "Your comment is awaiting moderation.". So the file content is revealed before the administrator can delete the malicious comment.

You can see bellow a screen capture showing the exploit in action, both inside the post content and a comment before moderation:

Disclosure of /etc/passwd and wp-config.php (click to enlarge)

Disclosure of /etc/passwd and wp-config.php (click to enlarge)

Vendor response

I sent the initial report of the vulnerability to the plugin author on January 4. He replied quickly and confirmed the issue.

On April 5, he informed me that he had patched the issue. Finally, on April 13, he released the version 2.7.0, which includes this patch.

I thank him for his cooperation and his excellent work.


The preferred solution is to update quickly to the latest version (>= 2.7.0), which fix the vulnerability by removing support of local file highlighting.

In the event where you really cannot install the update, an alternative way to limit risks is to disable Crayon in comments, in order to prevent attacks from unauthenticated visitor. But it will still be possible to exploit the vulnerability through a post or page content...

Disable Crayons in comments to prevent unauthenticated attacks

Disable Crayons in comments to prevent unauthenticated attacks

CVSS Score

I evaluate the CVSS Score at 7.8 (AV:N/AC:L/Au:N/C:C/I:N/A:N).


Secunia : #63998.
WPVDB : #7904.
OSVDB : #121278.

About me

For non-French speakers that can't read others pages about me on this blog (sorry, it's currently primarly intended to French speakers), I will introduce me briefly in English here so that you can know who am I. I'm Kevin Subileau, 23 years old. I'm not a professional security researcher (yet), but I'm a passionate in computing science, junior system & network engineer by day, and web developer by night, also interested by computing security. You can contact me by leaving a comment below (in English or French), on Twitter or by filling this form (fields are, top to bottom, name / email / subject / comment).