The recent DOM-based Cross-Site-Script vulnerability in WordPress has made me wonder how this could have happened in days where automated static code scanners are even integrated in standard tools such as Burp Suite (the leading toolkit for web application security testing). In this blog post I go a little bit into details about the vulnerability and what can be done to catch such a vulnerability.

The Vulnerability in a nutshell

The vulnerability in the WordPress theme is actually a very trivial one, the source code for the example.html file can be found in the Git history on GitHub as of 734cf336a9f. If we take a look at the source code the problem is not too hard to spot for people that are familiar with the peculiarities of older jQuery versions:

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script><script>
    
    jQuery(document).ready(function() {
	    
	    permalink = "genericon-" + window.location.hash.split('#')[1];
	    attr = jQuery( '.' + permalink ).attr( 'alt' );
	    cssclass = jQuery( '.' + permalink ).attr('class');
	    
    });
    
</script>

Basically, window.location.hash returns the hash of the URL (for example in example.html#foo it would be foo) and this result is then used in a call to jQuery('.'+permalink). jQuery itself tends to be a sink and versions before 1.9.0 are happily generating the vulnerable DOM element even if it starts with a .. To be automatically protected against this specific sink hole one has to use at least jQuery 1.9.0, since this version this is only exploitable if the string starts with a <. Though one should obviously be aware that there are still a ton of other sinkholes in jQuery.

Detecting it statically

As it is with such easily detectable vulnerabilities one would expect that static code analysis would be able to spot these. And indeed, at least BurpSuite Pro as well as DominatorPro would have spotted these. So why wasn’t it found before considering that WordPress is such a widely deployed software?

My take on this is that example.html was an example file and thus not actively linked. Many tools used to do web application penetration testing are only be able to discover content if it is actively linked. In most scenarios the security analyst will however be likely be able to get access to the actual sources. In an open-source world scenario this is also often the case for so called black-box-testing.

I wrote a short Python script (written for Python 2.7) that will search all files of a defined type (defaults to .js and .html) and serve it using the built-in Python web server. The directory listening is ensuring that all packaged files are getting properly analyzed. While there are other existing approaches I don’t feel to keen to add huge Plugins to Burp and writing a short Python script is way easier than to audit the existing solutions.

The following script can be invoked using the following parameters:

-i /var/www/wordpress # Absolute path to folder to scan
-p 8000 # Port the webserver should listen on, defaults to 8000
-h 127.0.0.1 # IP the webserver should bind to, defaults to 127.0.0.1
-types .html,.js # Filetypes that should get scanned, defaults to .html,.js
#!/usr/bin/env python

import sys
import getopt
import os
import tempfile
import atexit
import shutil
import BaseHTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler

temporary_folder = ''

# Clean-up temporary folder after all files after script ends
@atexit.register
def exit():
    if temporary_folder != '':
        shutil.rmtree(temporary_folder)

def main(argv):
    input_folder = ''
    port = 8000
    host = '127.0.0.1'
    types = '.html,.js'

    try:
        opts, args = getopt.getopt(argv, 'i:p:h:t:',['inputfolder=', 'port=', 'host=', 'types='])
    except getopt.GetoptError:
        print os.path.basename(__file__) + ' -i <inputfolder> -p <port> -h <host> -t <types>'
        sys.exit(2)
    for opt, arg in opts:
        if opt in ('-i', '--inputfolder'):
            input_folder= arg
        elif opt in ('-p', '--port'):
            port = int(arg)
        elif opt in ('-h', '--host'):
            host = arg
        elif opt in ('-t', '--types'):
            types = arg

    if input_folder == '':
        print os.path.basename(__file__) + ' -i <inputfolder> -p <port> -h <host> -t <types>'
        sys.exit(2)

    # Create a temporary folder for the items
    global temporary_folder
    temporary_folder = tempfile.mkdtemp()
    os.chdir(temporary_folder)

    # Discover files
    print 'Starting file discovery...'
    for root, dirs, files in os.walk(input_folder):
        for file in files:
            if file.endswith(tuple(types.split(','))):
                absoluteSourcePath = os.path.join(root, file)
                shutil.copyfile(absoluteSourcePath, temporary_folder + '/' + absoluteSourcePath.replace('/', '.'))
                print 'Copied ', absoluteSourcePath

    print 'Ending file discovery...'

    # Serve files
    httpd = BaseHTTPServer.HTTPServer((host, port), SimpleHTTPRequestHandler)
    sa = httpd.socket.getsockname()
    print 'Serving data on', sa[0], 'port', sa[1], "..."
    httpd.serve_forever()

if __name__ == '__main__':
    main(sys.argv[1:])

The script can get stopped by pressing CTRL + C and will try to clean-up all created files.

Using it

In this example I’m using the Jetpack 3.5.2 version from GitHub, as it features the vulnerability highlighted in this blog post. You can obviously also run this script over much bigger code bases.

➜  master git:(master) ✗ python burp-scanner.py -i ~/Downloads/jetpack -h 10.211.55.2
Starting file discovery...
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/Gruntfile.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/_inc/gallery-settings.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/_inc/jetpack-admin.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/_inc/jetpack-modules.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/_inc/jetpack-modules.models.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/_inc/jetpack-modules.views.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/_inc/jetpack.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/_inc/jp.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/_inc/jquery.inview.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/_inc/jquery.jetpack-resize.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/_inc/jquery.jetpack-sync.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/_inc/jquery.spin.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/_inc/postmessage.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/_inc/spin.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/_inc/genericons/genericons/example.html
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/wpgroho.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/after-the-deadline/atd-autoproofread.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/after-the-deadline/atd-nonvis-editor-plugin.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/after-the-deadline/atd.core.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/after-the-deadline/jquery.atd.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/after-the-deadline/tinymce/editor_plugin.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/after-the-deadline/tinymce/plugin.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/carousel/jetpack-carousel.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/contact-form/js/grunion-admin.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/contact-form/js/grunion-frontend.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/contact-form/js/grunion.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/custom-css/custom-css/js/codemirror.min.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/custom-css/custom-css/js/css-editor.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/custom-css/custom-css/js/use-codemirror.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/custom-post-types/comics/comics.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/custom-post-types/js/many-items.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/custom-post-types/js/menu-checkboxes.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/custom-post-types/js/nova-drag-drop.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/holiday-snow/snowstorm.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/infinite-scroll/infinity.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/likes/post-count-jetpack.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/likes/post-count.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/likes/queuehandler.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/minileven/theme/pub/minileven/js/small-menu.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/photon/photon.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/post-by-email/post-by-email.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/publicize/assets/publicize.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/related-posts/related-posts.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/sharedaddy/admin-sharing.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/sharedaddy/sharing.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/shortcodes/js/audio-shortcode.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/shortcodes/js/jmpress.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/shortcodes/js/jmpress.min.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/shortcodes/js/jquery.cycle.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/shortcodes/js/main.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/shortcodes/js/recipes-printthis.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/shortcodes/js/recipes.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/shortcodes/js/slideshow-shortcode.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/site-icon/js/site-icon-admin.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/site-icon/js/site-icon-crop.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/theme-tools/js/suggest.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/theme-tools/responsive-videos/responsive-videos.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/theme-tools/responsive-videos/responsive-videos.min.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/theme-tools/site-logo/js/site-logo-control.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/theme-tools/site-logo/js/site-logo-control.min.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/theme-tools/site-logo/js/site-logo-header-text.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/theme-tools/site-logo/js/site-logo-header-text.min.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/theme-tools/site-logo/js/site-logo.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/theme-tools/site-logo/js/site-logo.min.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/tiled-gallery/tiled-gallery/tiled-gallery.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/videopress/videopress-admin.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/widget-visibility/widget-conditions/widget-conditions.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/widgets/contact-info/contact-info-map.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/widgets/gallery/js/admin.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/modules/widgets/gallery/js/gallery.js
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/tests/modules/photon/sample-content/a-tags-without-images.html
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/tests/modules/photon/sample-content/blank.html
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/tests/modules/photon/sample-content/empty-a-tag.html
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/tests/modules/photon/sample-content/extra-attributes.html
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/tests/modules/photon/sample-content/minimum-multiple-with-links.html
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/tests/modules/photon/sample-content/minimum-multiple.html
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/tests/modules/photon/sample-content/minimum.html
Copied  /Users/lukasreschke/Downloads/jetpack/jetpack-3.5.2/tests/modules/photon/sample-content/multiline.html
Ending file discovery...
Serving data on 10.211.55.2 port 8000 ...

One has now to configure Burp Scanner to also perform static code analysis on passive scans, this can be done via:

  1. Scanner
  2. Options
  3. Static Code Analysis
    • Enable: “Active and passive scanning”

Enable static code analysis

After this is done the Spider has to be configured to also follow links to non-text content, this can be done via:

  1. Spider
  2. Options
  3. Crawler Settings
    • Uncheck “Ignore links to non-text content”

Follow non-text items as well

As a next step it is required to spider the specified host:

Spider the host

After spidering is done all entries should be black in the site map and not gray anymore. Now the final step is required by enabling the passive scan of the whole domain:

Scan the host

While some of the results will likely be false positives Burp might also find valid items, in this case Burp was able to identify the discussed vulnerability on it’s own:

Results

Mitigations and what it means for ownCloud

As explained in an earlier blog post, Content-Security-Policy would indeed help to prevent such XSS problems. In this specific case however this was a static served resource and the CSP header would have to get applied by the web server.

At ownCloud we are thus considering and evaluating adding a Content-Security-Policy header also for static ressources for one of our upcoming major releases. The progress can be tracked on our GitHub page at issue core/16164.