May 27 · 4 min read

Almost two months ago I checked the download process of Easy Digital Downloads e-commerce plugin and there was/is a lot of code that would rise a red alert towards anyone with average interest of web security. Vulnerable version of the Easy Digital Downloads “was” version 2.9.12 according plugin developers and their security team (yes, they told me they have…)

Reporting and communication

Reporting process was “smooth” and standard one when WordPress is in game:

  • Vulnerabilities detected after 30 minutes of code review of dl functionality
  • Reported towards plugins[at]wp[dot]org
  • plugins cauliflower heads acknowledged the bug and they came with advice that isn’t something spectacular
  • I explained “politely” to recheck their statement — worth mentioning here are marketplaces… :)
  • Developer contact in my inbox
  • Few days bugs verification
  • After a month, patch proposal
  • Release in the h1-mattic style (silent), hinted from my side
  • Almost a month after first patch here I am writing this advisory

Overall plugins and developer security team were cooperative almost like je-mattic ones.

Arbitrary file download

Low privileged users as Shop Workers could exploit this vulnerability on the following way:

  • They create any product for download
  • Set file name for download with any absolute file location (/etc/passwd or /var/www/eddtest/wp-config.php)
  • Test the product downloads via check out process
  • Download the file contents

Why this happens?

  • On input there isn’t any check for the path given. It is basically trim-ing only the input value. Check edd_download_files registered meta
  • On the output e.g. while download process is in progress, there isn’t any check about location of the file meant to be downloaded.
Corrected in 2.9.14

Timing attack in order to download any product

Protection of the download links could be observed via code in edd_process_download function e.g. edd_process_signed_download_url where input parameters are processed. If you dive into it you will go into edd_validate_url_token function where checks are done.

This issue is one interesting issue only because nature of the code that is used to handle it. If we observe the generated url for download and check the code,

then this issue rises up the severity. Anyway ttl parameter is used as it is (raw) during the process of validation of the link (check is simple, if its value is bigger than current system time) and its value (raw) is used in token verification. This guides us to the fact that if attacker successfully launches guessing attack towards one product, then it could use ttl parameter value big enough, so derived/learned token never expires. Beside this “feature” of the ttl parameter, it could be used into another attack towards server that could exploit PHP comparative feature e.g. "0" == "0e12345" and in this attack (brute force) ttl would get values of 99999999999999999 random_data and token will get the value 0 — it is sort of mining and lottery attack :)

Is everything fixed?

Absolutely NO, isn’t everything fixed, because at this developer security folks, “scotch” (charlatans dressed up with “introvert” coats doing monkey business in private e.g. burning people) perspective is turned ON.

We saw that fixes are aimed only towards output e.g. checking if the file is local and under certain location, but if everything is accepted then during the code execution we have the following. Use case: phar unserialize attack!

  • Url verification will go trough because it is prepared via admin with low privileged EDD user
  • $requested_file variable will hold the phar://path_to_phar_polyglot_img
  • edd_is_local_file function will return false, so code flow goes beyond that if
  • Being at the line 149 of process-download.php means the following
if ( ( ! isset( $file_details['scheme'] ) || ! in_array( $file_details['scheme'], $schemes ) ) && isset( $file_details['path'] ) && file_exists( $requested_file ) ) {

and the values are:
$file_details[‘scheme’] = “phar”
$schemes = array(“http”, “https”)
$file_details[‘path’] = “/var/path_to_phar”
e.g. is set


$requested_file holds the phar://path_to_phar_polyglot_img value and like that is input of file_exists function :)

Protect your setup:


Across the plugin something like this could be found :)

preg_match( '/[oO]\s*:\s*\d+\s*:\s*"\s*(?!(?i)(stdClass))/', $value, $matches );

and in many cases it is used to check the direct input or to check return meta value already passed trough maybe_unserialize function once, before it sends the data towards another maybe_unserialize. Hint: + :)

PressMattic herring distributed kompania


If you are wp developer or wp host provider or wp security product provider with valuable list of clients, we offer subscription list and we are exceptional (B2B only).


Attack sources + web application security


Written by




Attack sources + web application security

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade