acf
This commit is contained in:
283
plugins/system/nrframework/NRFramework/Executer.php
Normal file
283
plugins/system/nrframework/NRFramework/Executer.php
Normal file
@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author Tassos Marinos <info@tassos.gr>
|
||||
* @link https://www.tassos.gr
|
||||
* @copyright Copyright © 2024 Tassos All Rights Reserved
|
||||
* @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
|
||||
*/
|
||||
namespace NRFramework;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\Registry\Registry;
|
||||
use Joomla\Filesystem\File;
|
||||
use Joomla\CMS\Factory;
|
||||
|
||||
/**
|
||||
* Cleverly evaluate php code using a temporary file and without using the evil eval() PHP method
|
||||
*/
|
||||
class Executer
|
||||
{
|
||||
/**
|
||||
* The php code is going to be executed
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $php_code;
|
||||
|
||||
/**
|
||||
* The data object passed as argument to function
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private $payload;
|
||||
|
||||
/**
|
||||
* Executer configuration
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param string $php_code The php code is going to be executed
|
||||
*/
|
||||
public function __construct($php_code = null, &$payload = null, $options = array())
|
||||
{
|
||||
$this->setPhpCode($php_code);
|
||||
$this->setPayload($payload);
|
||||
|
||||
// Default options
|
||||
$defaults = [
|
||||
'forbidden_php_functions' => [
|
||||
'fopen',
|
||||
'popen',
|
||||
'unlink',
|
||||
'rmdir',
|
||||
'dl',
|
||||
'escapeshellarg',
|
||||
'escapeshellcmd',
|
||||
'exec',
|
||||
'passthru',
|
||||
'proc_close',
|
||||
'proc_open',
|
||||
'shell_exec',
|
||||
'symlink',
|
||||
'system',
|
||||
'pcntl_exec',
|
||||
'eval',
|
||||
'create_function'
|
||||
]
|
||||
];
|
||||
|
||||
$options = array_merge($defaults, $options);
|
||||
$this->options = new Registry($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload contains the variables passed as argumentse into the PHP code
|
||||
*
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPayload(&$data)
|
||||
{
|
||||
$this->payload = &$data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set forbidden PHP functions. If any found, the whole PHP block won't run.
|
||||
*
|
||||
* @param array $functions
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setForbiddenPHPFunctions($functions)
|
||||
{
|
||||
if (empty($functions))
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (is_string($functions))
|
||||
{
|
||||
$functions = explode(',', $functions);
|
||||
}
|
||||
|
||||
$this->options->set('forbidden_php_functions', $functions);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to set the php code is about to be executed
|
||||
*
|
||||
* @param string $php_code
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPhpCode($php_code)
|
||||
{
|
||||
$this->php_code = $php_code;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given PHP code is valid and it's allowed to run.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function allowedToRun()
|
||||
{
|
||||
// Get the forbidden PHP functions, but we want to make sure we whitelist 'curl_exec'
|
||||
$forbidden_functions = array_diff($this->options->get('forbidden_php_functions'), ['curl_exec']);
|
||||
|
||||
// Build the regex, making sure 'exec' does not match within 'curl_exec'
|
||||
$re = '/\b(' . implode('|', $forbidden_functions) . ')\b(\s*\(|\s+[\'"])/mi';
|
||||
preg_match_all($re, $this->php_code ?? '', $matches);
|
||||
if (!empty($matches[0]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for backticks ``
|
||||
if ($has_back_ticks = preg_match('/`(.*?)`/s', $this->php_code ?? ''))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run function
|
||||
*
|
||||
* @return function
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
if (!$this->allowedToRun())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$function_name = $this->getFunctionName();
|
||||
|
||||
// Function doesn't exist. Let's create it.
|
||||
if (!function_exists($function_name))
|
||||
{
|
||||
if (!$this->createFunction())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return $function_name($this->payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a temporary function in memory
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function createFunction()
|
||||
{
|
||||
$function_name = $this->getFunctionName();
|
||||
$function_content = $this->getFunctionContent();
|
||||
$temp_file = $this->getTempPath() . '/' . $function_name;
|
||||
|
||||
// Write function's content to a temporary file
|
||||
File::write($temp_file, $function_content);
|
||||
|
||||
// Include file
|
||||
include_once $temp_file;
|
||||
|
||||
// Delete file
|
||||
if (!defined('JDEBUG') || !JDEBUG)
|
||||
{
|
||||
@chmod($temp_file, 0777);
|
||||
@unlink($temp_file);
|
||||
}
|
||||
|
||||
return function_exists($function_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get temporary file content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getFunctionContent()
|
||||
{
|
||||
$function_name = $this->getFunctionName();
|
||||
$variables = $this->getFunctionVariables();
|
||||
|
||||
$contents = [
|
||||
'<?php',
|
||||
'defined(\'_JEXEC\') or die;',
|
||||
'function ' . $function_name . '(&$displayData = null) {
|
||||
if ($displayData) {
|
||||
extract($displayData, EXTR_REFS);
|
||||
}
|
||||
',
|
||||
implode("\n", $variables),
|
||||
$this->php_code,
|
||||
';return true;}'
|
||||
];
|
||||
|
||||
$contents = implode("\n", $contents);
|
||||
|
||||
// Remove Zero Width spaces / (non-)joiners
|
||||
$contents = str_replace(
|
||||
[
|
||||
"\xE2\x80\x8B",
|
||||
"\xE2\x80\x8C",
|
||||
"\xE2\x80\x8D",
|
||||
],
|
||||
'',
|
||||
$contents
|
||||
);
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make user's life easier by initializing some Joomla helpful variables
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getFunctionVariables()
|
||||
{
|
||||
return [
|
||||
'$app = $mainframe = \Joomla\CMS\Factory::getApplication();',
|
||||
'$document = $doc = \Joomla\CMS\Factory::getDocument();',
|
||||
'$database = $db = \Joomla\CMS\Factory::getDbo();',
|
||||
'$user = \Joomla\CMS\Factory::getUser();',
|
||||
'$Itemid = $app->input->getInt(\'Itemid\');'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a temporary function name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getFunctionName()
|
||||
{
|
||||
return 'tassos_php_' . md5($this->php_code ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Joomla temporary path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function getTempPath()
|
||||
{
|
||||
return Factory::getConfig()->get('tmp_path', JPATH_ROOT . '/tmp');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user