blob: b11aee71b1becba09da74bef16764ca9c14c62a4 [file] [log] [blame]
<?php
/**
* [Bug 474734] [security] xss vulnerability on mmt website
*
* SQL injection is a code injection technique,
* used to attack data-driven applications, in which malicious
* SQL statements are inserted into an entry field for execution
* (e.g. to dump the database contents to the attacker).
*
* Cross-Site Scripting (XSS) vulnerabilities are a type of
* computer security vulnerability typically found in Web applications.
* XSS vulnerabilities enable attackers to inject client-side script
* into Web pages viewed by other users.
*
* Given the severity of this bug, we added an exit() at the top
* of this file to stop it from being executed on our servers.
*
* The owner(s) of this website should review every request to MYSQL before
* removing the exit() on this page.
*
*/
exit();
/*
* A plugin/feature version checking auditor with support for cli and www mode.
*/
define("LOGGER_FAIL", 0);
define("LOGGER_OK", 1);
define("LOGGER_INFO", 2);
define("LOGGER_SQL", 10);
require_once ("buildServer-common.php");
$verbosity = 0;
$cli = isset($argv); // $argv is only defined when running in cli mode
/* in cli mode, you'll need to have the includes directory (with db.php) in the current directory, then invoke this script directly, rather than one of the placeholders */
if ($cli)
{
$require_db = "includes/db.php";
$dirs = array();
for ($i = 1; $i < sizeof($argv); $i++)
{
$m = null;
if (preg_match("/^-(v+)$/", $argv[$i], $m))
{
$verbosity = strlen($m[1]);
}
else if (is_dir($argv[$i]))
{
$dirs[] = $argv[$i];
}
else
{
print "$argv[$i] wasn't a directory or a verbosity flag, I don't know what to do with it!\n";
exit(-4);
}
}
$html = false;
}
else
{
require_once($_SERVER['DOCUMENT_ROOT'] . "/eclipse.org-common/system/app.class.php"); require_once($_SERVER['DOCUMENT_ROOT'] . "/eclipse.org-common/system/nav.class.php"); require_once($_SERVER['DOCUMENT_ROOT'] . "/eclipse.org-common/system/menu.class.php"); $App = new App(); $Nav = new Nav(); $Menu = new Menu(); include($App->getProjectCommon());
$require_db = $_SERVER["DOCUMENT_ROOT"] . "/modeling/includes/db.php";
if ((isset($_GET["debug"]) && preg_match("/(^\d+$)/", $_GET["debug"], $m)) || (isset($_GET["verbosity"]) && preg_match("/(^\d+$)/", $_GET["verbosity"], $m)))
{
$verbosity = $m[1];
}
/* here we inherit $dirs from the placeholder file that includes us */
if (isset($_GET["branch"]))
{
$b = $_GET["branch"];
if (isset($dirs[$b]))
{
$dirs = array($dirs[$b]);
}
else
{
header("Content-type: text/html");
print "<pre>$b wasn't a valid branch, please try again with a valid branch, such as:\n";
print join("\n", preg_replace("/^(.+)$/", "- <a href=\"?branch=$1\">$1</a>", array_keys($dirs))) . "</pre>\n";
exit(-5);
}
}
foreach ($dirs as $dir)
{
if (!is_dir($dir))
{
print "$dir wasn't a directory, please amend the definition of \$dirs in {$_SERVER["PHP_SELF"]}\n";
exit(-4);
}
}
if (isset($_GET["html"]))
{
$html = true;
header("Content-type: text/html");
}
else
{
$html = false;
header("Content-type: text/plain");
}
}
if (!isset($dirs) || !is_array($dirs) || sizeof($dirs) == 0)
{
print "I need to know what project/component you'd like me to audit!\n\n";
if ($cli)
{
print "For example:\n";
print "\tphp $argv[0] org.eclipse.emf\n\n";
print "You may also add -v or -vv for greater verbosity.\n";
}
else
{
print "Make sure \$dirs is defined in {$_SERVER["PHP_SELF"]} and contains a list of branches => directories you'd like to process.\n";
}
exit(-1);
}
ob_start();
foreach ($dirs as $dir)
{
$r = trim(file_get_contents("$dir/CVS/Repository"));
$m = null;
if (preg_match("#^(org\.eclipse\.[^/]+)(?:/([^/]+))?$#", $r, $m))
{
$proj = $m[1];
$com = (isset($m[2]) ? $m[2] : "");
logger(LOGGER_INFO, "found $proj/$com\n");
}
else
{
$msg = "I couldn't figure out what project/component that is, quitting...\n";
$msg .= " --> make sure $dir/CVS/Repository exists and is correct\n";
logger(LOGGER_FAIL, $msg);
exit(-3);
}
$branch = "HEAD";
if (is_file("$dir/CVS/Tag"))
{
$branch = preg_replace("/^./", "", trim(file_get_contents("$dir/CVS/Tag")));
logger(LOGGER_INFO, "detected branch $branch\n");
}
else
{
logger(LOGGER_INFO, "no CVS/Tag file found, assuming HEAD branch\n");
}
$branchfails = 0;
$issues = array();
require_once($require_db);
foreach (glob("$dir/{plugins,tests,examples}/org.eclipse.*", GLOB_BRACE) as $plugdir)
{
$plugin = basename($plugdir);
$type = preg_replace("#^.+/([^/]+)/$plugin$#", "$1", $plugdir); //plugins, tests, or examples
$deps = array();
$checked = array();
$queue = array($plugin);
$versions = array();
$lastversions = array();
$vcache = array();
$fails = 0;
if (preg_match("/-feature$/", $plugin))
{
logger(LOGGER_INFO, "skipping $plugdir as it looks like a feature, not a plugin\n\n");
continue;
}
if ($tmp = plugin_version($plugdir))
{
$vanityname = preg_replace("/\.qualifier$/", "", $tmp);
$version = convert_version($tmp);
$lastdir = preg_replace("#cvssrc(?:_branches)?(/" . basename($dir) . ")#", "cvssrc_branches$1-latest", $dir);
$lastplugdir = preg_replace("#cvssrc(?:_branches)?(/" . basename($dir) . ")#", "cvssrc_branches$1-latest", $plugdir);
if (!is_dir($lastplugdir))
{
logger(LOGGER_INFO, "$lastplugdir does not exist, skipping all checks on $plugdir\n");
continue;
}
$lastversion = preg_replace("/\.qualifier$/", "", plugin_version($lastplugdir));
if (is_dir($lastplugdir))
{
logger(LOGGER_INFO, "Last released version of $lastplugdir is $lastversion\n");
}
$p = ($com == "" ? $proj : "$proj/$com");
/* it's quite possible for us to end up with changes in a branch (say R2_1_maintenance) with the plugin only being versioned at 2.1.0, of course 2.1.0 wasn't released from the R2_1_maintenance branch, so we'll never find it there
* keep trying to find the last build in progressively less picky ways... */
$lastbuild = array(
"(SELECT MAX(`buildtime`) FROM `releases` WHERE `project` = '$proj' AND `component` = '$com' AND `branch` = '$branch' AND `type` = 'R')",
"(SELECT `buildtime` FROM `releases` WHERE `project` = '$proj' AND `component` = '$com' AND `vanityname` = '$vanityname' AND `type` = 'R')",
"(SELECT MIN(`buildtime`) FROM `releases` WHERE `project` = '$proj' AND `component` = '$com' AND `type` = 'R')",
"'2000-01-01'"
);
$query = "SELECT `bugid`, `cvsname`, `date` FROM `cvsfiles` NATURAL JOIN `commits` NATURAL LEFT JOIN `bugs` WHERE `project` = '$proj' AND `branch` = '$branch' AND `cvsname` LIKE '/cvsroot/modeling/$p/$type/$plugin/%' AND `date` >= COALESCE(" . join(", ", $lastbuild) . ")";
$result = wmysql_query($query);
if (mysql_num_rows($result) == 0)
{
logger(LOGGER_SQL, "$query\n");
logger(LOGGER_OK, "no commits found >= $p/$type/$plugin/ $vanityname\n");
}
else
{
$plugtext = $plugin;
if ($html)
{
$query = "SELECT MIN(`date`) FROM `cvsfiles` NATURAL JOIN `commits` WHERE `project` = '$proj' AND `branch` = '$branch' AND `cvsname` LIKE '/cvsroot/modeling/$p/$type/$plugin/%' AND `date` >= COALESCE(" . join(", ", $lastbuild) . ")";
$result2 = wmysql_query($query);
logger(LOGGER_SQL, "$query\n");
$row2 = mysql_fetch_row($result2);
$plugtext = "<a href=\"http://www.eclipse.org/modeling/emf/searchcvs.php?q=" . urlencode("file: $p/$type/$plugin/ startdate: $row2[0] branch: $branch") . "\">$plugin</a>";
}
$msg = mysql_num_rows($result) . " commit(s) found >= $plugtext $lastversion, currently at $vanityname\n";
while ($row = mysql_fetch_row($result))
{
$msg .= " ref: http://www.eclipse.org/modeling/emf/searchcvs.php?q=";
if ($row[0])
{
$msg .= "$row[0]\n";
}
else
{
$msg .= urlencode("file: $row[1] startdate: $row[2] enddate: $row[2]") . "\n";
}
}
if ($version > convert_version($lastversion))
{
logger(LOGGER_OK, $msg);
}
else
{
$msg .= " --> $plugin must be > $vanityname\n";
logger(LOGGER_FAIL, $msg);
$fails++;
}
}
while (sizeof($queue) > 0)
{
foreach (array_keys($queue) as $z)
{
$actual = preg_replace("/-feature$/", "", $queue[$z]);
$fails += depgrep($dir, $actual, $vanityname, $plugin);
unset($queue[$z]);
}
}
$f = "doc/$proj.doc-feature/feature.xml";
if (is_file("$dir/$f"))
{
$deps["$proj.doc-feature"] = "$dir/$f";
}
else
{
logger(LOGGER_INFO, "couldn't find $f\n");
}
foreach (array_keys($deps) as $z)
{
$versions[$z] = convert_version(feature_version($deps[$z]));
$lastversions[$z] = convert_version(feature_version(preg_replace("#cvssrc(?:_branches)?(/" . basename($dir) . ")#", "cvssrc_branches$1-latest", $deps[$z])));
}
//print_r($deps);
$f = "doc/$proj.doc/build.xml";
if (is_file("$dir/$f"))
{
$versions["$proj.doc"] = doc_version($dir, $proj);
$lastversions["$proj.doc"] = doc_version($lastdir, $proj);
}
else
{
logger(LOGGER_INFO, "couldn't find $f\n");
}
if ($version > convert_version($lastversion))
{
foreach (array_keys($versions) as $z)
{
if (preg_match("/\.doc(?:-feature)?$/", $z) && $version - $versions[$z] <= 999)
{
$v1 = $vcache[$versions[$z]];
$v2 = $vcache[$version];
logger(LOGGER_OK, "$z (" . preg_replace("/\.\d+$/", "", $v1) . ") >= $plugin (" . preg_replace("/\.\d+$/", "", $v2) . "), ignoring service versions (actual versions were $v1 and $v2, respectively)\n");
}
else if ($versions[$z] > $lastversions[$z])
{
if ($versions[$z] - $lastversions[$z] == 1 || $versions[$z] - $lastversions[$z] == 1000)
{
logger(LOGGER_OK, "$z last released at " . $vcache[$lastversions[$z]] . ", currently at " . $vcache[$versions[$z]] . "\n");
}
else if (!isset($vcache[$lastversions[$z]]) || !$vcache[$lastversions[$z]]) // if feature is new, no previous version will exist
{
logger(LOGGER_INFO, "$z appears to be new, skipping checks\n");
}
else
{
$msg = "$z last released at " . $vcache[$lastversions[$z]] . ", currently at " . $vcache[$versions[$z]] . "\n";
$msg .= " --> $z must be incremented only once per release cycle (last released at " . $vcache[$lastversions[$z]] . ", currently at " . $vcache[$versions[$z]] . ")\n";
logger(LOGGER_FAIL, $msg);
$fails++;
}
}
else
{
$msg = "$z last released at " . $vcache[$lastversions[$z]] . ", currently at " . $vcache[$versions[$z]] . ", but $plugin has been incremented\n";
$msg .= " --> $z must be > " . $vcache[$versions[$z]] . "\n";
logger(LOGGER_FAIL, $msg);
$fails++;
}
}
}
else
{
logger(LOGGER_OK, "$plugin last released at $lastversion, currently at $vcache[$version]\n");
}
if ($fails == 0)
{
logger(LOGGER_OK, "$plugdir appears to be fine\n\n");
}
else if ($verbosity >= LOGGER_OK)
{
print "\n";
}
}
else if (!preg_match("/-feature$/", $plugdir))
{
logger(LOGGER_INFO, "couldn't find a MANIFEST.MF for $plugdir\n");
}
$branchfails += $fails;
}
/* we don't use the logger() interface here because we always want these to show, and we don't want a prepended tag */
if ($branchfails == 0)
{
print "$branch: ok\n\n";
}
else
{
print "$branch: $branchfails failure(s): commit plugin/feature fixes to CVS, then run http://build.eclipse.org/modeling/build/updateSearchCVS.php to refresh database.\n" .
" Or, if you recently released an R build, update the searchcvs/cvssrc_branches/*-latest folder to the newer tag.\n";
ksort($issues);
print join("\n", array_keys($issues)) . "\n\n";
$issues = array();
}
}
$content = ob_get_contents();
ob_end_clean();
if ($html)
{
print "<pre>\n";
print preg_replace("#(?<!href=\")(https?://[^ \n\t]+)#", "<a href=\"$1\">$1</a>", $content);
print "</pre>\n";
print "<p>&nbsp;</p>\n";
print '<p align="right"><i><small><a href="http://wiki.eclipse.org/Plugin_Version_Auditing">about this tool</a></small></i></p>'."\n";
}
else
{
print $content;
}
/* find features which include or depend on the given plugin within the given directory */
function depgrep($dir, $plugin, $vanityname, $origplugin)
{
global $deps, $checked, $queue;
foreach (glob("$dir/{plugins,features,tests,examples}/*-feature/{,org.eclipse.*.sdk/}feature.xml", GLOB_BRACE) as $z)
{
$regs = null;
if (preg_match("/<(?:includes|plugin)[^>]+id=\"\Q$plugin\E\"[^>]+version=\"([^\"]+)\"/s", file_get_contents($z), $regs))
{
if ($plugin === $origplugin)
{
if ($vanityname !== $regs[1] && $regs[1] !== "0.0.0")
{
$msg = "$plugin is at $vanityname, but $z wants $regs[1]\n";
$msg .= " --> $plugin must be $vanityname in $z\n";
logger(LOGGER_FAIL, $msg);
}
else
{
logger(LOGGER_OK, "$z wants $regs[1]\n");
}
}
$m = null;
if (preg_match("#/([^/]+)/feature\.xml$#", $z, $m))
{
$deps[$m[1]] = $z;
if (!isset($checked[$m[1]]))
{
$checked[$m[1]] = true;
$queue[] = $m[1];
}
}
}
}
}
/* parse the feature version out of a feature.xml file */
function feature_version($file)
{
$m = null;
if (is_file($file) && is_readable($file))
{
if (preg_match("/<feature[^>]+version=\"([^\"]+)\"/s", file_get_contents($file), $m))
{
return $m[1];
}
}
return null;
}
/* parse the bundle version out of a MANIFEST.MF file (or plugin.xml if we can't find a MANIFEST.MF) */
function plugin_version($plugdir)
{
$ret = false;
$m = null;
if (is_file("$plugdir/META-INF/MANIFEST.MF"))
{
if (preg_match("#^Bundle-Version:\s+(.+)$#m", file_get_contents("$plugdir/META-INF/MANIFEST.MF"), $m))
{
$ret = $m[1];
}
}
else if (is_file("$plugdir/plugin.xml"))
{
if (preg_match("#<plugin[^>]+version\s*=\s*\"([^\"]+)\"[^>]+>#s", file_get_contents("$plugdir/plugin.xml"), $m))
{
$ret = $m[1];
}
}
else
{
logger(LOGGER_FAIL, "couldn't find a MANIFEST.MF or plugin.xml for $plugdir!\n");
}
return $ret;
}
/* convert a version number like 2.3.0.qualifier to 2*10^6 + 3*10^3 + 0, so they can be compared numerically
* also associate the two values in $vcache so the operation can be reversed easily */
function convert_version($version)
{
global $vcache;
$num = null;
if ($version)
{
$version = preg_replace("/\.qualifier$/", "", $version);
list($major, $minor, $patch) = split("\.", $version);
$num = ($major * pow(10, 6) + $minor * pow(10, 3) + $patch);
$vcache[$num] = $version;
}
return $num;
}
/* we need to check 2 different files for the doc plugin, so we'll just add it's version directly */
function doc_version($dir, $proj)
{
global $fail;
$v1 = null;
$v2 = null;
$m = null;
if (preg_match("#<property\s+name=\"pluginVersion\"\s+value=\"([^\"]+)\"\s*/>#", file_get_contents("$dir/doc/$proj.doc/build.xml"), $m))
{
$v1 = $m[1];
}
$v2 = plugin_version("$dir/doc/$proj.doc");
if ($v1 !== null && $v2 !== null)
{
if ("$v1.qualifier" === $v2 || $v1 === $v2)
{
return convert_version($v2);
}
else
{
$msg = "$dir/doc/$proj.doc is not internally consistent! ($v1 != $v2)\n";
$msg .= " --> the versions in build.xml (currently $v1) and META-INF/MANIFEST.MF (currently $v2) must match\n";
logger(LOGGER_FAIL, $msg);
}
}
else
{
$msg = "couldn't find all version information for $dir/doc/$proj.doc!\n";
if ($v1 === null)
{
$msg .= " --> build.xml must have a <property name=\"pluginVersion\" value=\"x.x.x\"/>\n";
}
if ($v2 === null)
{
$msg .= " --> META-INF/MANIFEST.MF must have a Bundle-Version: x.x.x\n";
}
logger(LOGGER_FAIL, $msg);
}
}
/* wrapper for output, prepends a label and controls output based on the verbosity level */
function logger($type, $msg)
{
global $verbosity, $issues;
$labels = array(
LOGGER_FAIL => "[ fail ]",
LOGGER_OK => "[ ok ]",
LOGGER_INFO => "[ info ]",
LOGGER_SQL => "[ sql ]",
);
if ($type <= $verbosity)
{
print $labels[$type] . " $msg";
$m = null;
if (preg_match_all("/^( -->.+)$/m", $msg, $m))
{
foreach ($m[1] as $z)
{
$issues[$z] = true;
}
}
}
}
?>