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