From 62692af7c2089340c90c5739496b7f569630bc85 Mon Sep 17 00:00:00 2001 From: Andreas Kellas Date: Thu, 2 Feb 2023 11:13:11 -0500 Subject: [PATCH 1/5] Modify PHP hooks to consider 'allowed_classes' --- Utils/HookFiles/HookHead.php | 52 ++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/Utils/HookFiles/HookHead.php b/Utils/HookFiles/HookHead.php index 651f076..0a7c58c 100644 --- a/Utils/HookFiles/HookHead.php +++ b/Utils/HookFiles/HookHead.php @@ -151,11 +151,13 @@ function get_class_props_r353t($clsname) { return $class_props; } -function get_user_classes_r353t() { +function get_user_classes_r353t($trigger_func, $func_argv) { $user_classes = Array(); - $declared_list = array_merge(get_declared_classes(), - get_declared_interfaces()); - $declared_list = array_merge($declared_list, get_declared_traits()); + $declared_list = array_merge(get_declared_classes(), get_declared_interfaces()); + $declared_list = filter_allowed_classes( + array_merge($declared_list, get_declared_traits()), + $trigger_func, + $func_argv); foreach ($declared_list as $clsname) { $cls = new ReflectionClass($clsname); $cls_org = $cls; @@ -251,9 +253,9 @@ function isPharDetected_r353t($case, $validator_md5) { return False; } -function get_declared_classes_r353t() { +function get_declared_classes_r353t($trigger_func, $func_argv) { $declared_classes = array(); - foreach (get_declared_classes() as $clsname) { + foreach (filter_allowed_classes(get_declared_classes(), $trigger_func, $func_argv) as $clsname) { $cls = new \ReflectionClass($clsname); $clsname = str_replace("\0", "", $clsname); $clsname = str_replace("/", "_", $clsname); @@ -286,6 +288,25 @@ function get_declared_traits_r353t() { return $traits; } +function filter_allowed_classes($array, $trigger_func, $func_argv) { + $return_array = $array; + if ($trigger_func == "unserialize" && count($func_argv) > 1) { + if (array_key_exists("allowed_classes", $func_argv[1])) { + if (gettype($func_argv[1]["allowed_classes"]) == "boolean") { + if ($func_argv[1]["allowed_classes"] == false) { + $return_array = Array(); + } else if ($func_argv[1]["allowed_classes"] == true) { + $return_array = $array; + } + } else if (gettype($func_argv[1]["allowed_classes"]) == "array") { + $allowed_classes = $func_argv[1]["allowed_classes"]; + $return_array = array_intersect($array, $allowed_classes); + } + } + } + return $return_array; +} + function saveDatas_r353t($argv_list, $trigger_func, $func_argv, $inject_idxs) { global $argv_list_r353t; $inject_points = explode(",", $inject_idxs); @@ -324,6 +345,21 @@ function saveDatas_r353t($argv_list, $trigger_func, $func_argv, $inject_idxs) { $class_list = Array(); $pid_list = Array(); + if ($trigger_func == "unserialize" && count($func_argv) > 1) { + if (array_key_exists("allowed_classes", $func_argv[1])) { + if (gettype($func_argv[1]["allowed_classes"]) == "boolean") { + if ($func_argv[1]["allowed_classes"] == false) { + $class_name_list = Array(); + } else if ($func_argv[1]["allowed_classes"] == true) { + $class_name_list = $class_name_list; + } + } else if (gettype($func_argv[1]["allowed_classes"]) == "array") { + $allowed_classes = $func_argv[1]["allowed_classes"]; + $class_name_list = array_intersect($class_name_list, $allowed_classes); + } + } + } + foreach($class_name_list as $cls_name) { $pid = pcntl_fork(); // error_log("Fork: $cls_name\n"); @@ -396,10 +432,10 @@ class_exists($cls_name); $argv_list_r353t['TRIGGER_FUNC'] = $trigger_func; $argv_list_r353t['FUNC_ARGV'] = $func_argv; - $argv_list_r353t['CLASSES'] = get_declared_classes_r353t(); + $argv_list_r353t['CLASSES'] = get_declared_classes_r353t($trigger_func, $func_argv); $argv_list_r353t['INTERFACES'] = get_declared_interfaces_r353t(); $argv_list_r353t['TRAITS'] = get_declared_traits_r353t(); - $argv_list_r353t['USER_CLASSES'] = get_user_classes_r353t(); + $argv_list_r353t['USER_CLASSES'] = get_user_classes_r353t($trigger_func, $func_argv); $argv_list_r353t['INCLUDED_FILES'] = get_included_files(); $copied_globals = new ArrayObject($GLOBALS); $argv_list_r353t['GLOBALS'] = $copied_globals->getArrayCopy(); From d235c8c3bb3ac4a4b3ab5a1519f60706f6fa8107 Mon Sep 17 00:00:00 2001 From: Andreas Kellas Date: Mon, 19 Jun 2023 17:03:31 -0400 Subject: [PATCH 2/5] Bugfix: prevent crash when classes list is empty --- Analyzer/dynamic.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Analyzer/dynamic.py b/Analyzer/dynamic.py index 4a3cdf2..16362d8 100644 --- a/Analyzer/dynamic.py +++ b/Analyzer/dynamic.py @@ -124,6 +124,9 @@ def update_info(self, user_classes, user_functions, declared_classes, new_func_info['FILE_NAME'] = func_file target_function[func_name] = new_func_info + if not user_classes: + return target_class, target_function + for class_name, class_info in user_classes.items(): if not re.match(EXCLUDED_CLASSES_REGEX, class_name): file_name = class_info['FILE_NAME'] From b8671a41dcd15f93e7387a506e16343fcdbe52dc Mon Sep 17 00:00:00 2001 From: Andreas Kellas Date: Mon, 19 Jun 2023 17:24:00 -0400 Subject: [PATCH 3/5] Bugfix: prevent crash when classes list is empty --- Analyzer/analyzer.py | 9 +++++++++ Analyzer/dynamic.py | 5 +++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Analyzer/analyzer.py b/Analyzer/analyzer.py index 7ea12da..1e68d4e 100644 --- a/Analyzer/analyzer.py +++ b/Analyzer/analyzer.py @@ -386,6 +386,10 @@ def preprocess(self, user_classes, user_functions, declared_interfaces, f.write(b'?>') self.target_file_list = self.s_analyzer.parse_file(EVAL_FILE) + # If PHP returns an empty array, Python treats it as a list. + if isinstance(user_classes, list): + user_classes = {} + target_class, target_function = \ self.d_analyzer.update_info(user_classes, user_functions, @@ -1153,6 +1157,11 @@ def write_code(item_name, item_info, cls=True, write=True): class_list = [] declared_flag = False + + # If PHP returns an empty array, Python treats it as a list. + if isinstance(user_classes, list): + user_classes = {} + for class_name, class_info in user_classes.items(): if not re.match(EXCLUDED_CLASSES_REGEX, class_name) and \ class_info['FILE_NAME'].startswith(self.target): diff --git a/Analyzer/dynamic.py b/Analyzer/dynamic.py index 16362d8..b287287 100644 --- a/Analyzer/dynamic.py +++ b/Analyzer/dynamic.py @@ -124,8 +124,9 @@ def update_info(self, user_classes, user_functions, declared_classes, new_func_info['FILE_NAME'] = func_file target_function[func_name] = new_func_info - if not user_classes: - return target_class, target_function + # If PHP returns an empty array, Python treats it as a list. + if isinstance(user_classes, list): + user_classes = {} for class_name, class_info in user_classes.items(): if not re.match(EXCLUDED_CLASSES_REGEX, class_name): From 8047a04148733c8ce89f4b03eab70599c79e029e Mon Sep 17 00:00:00 2001 From: Andreas Kellas Date: Thu, 22 Jun 2023 12:02:30 -0400 Subject: [PATCH 4/5] Hook fugio_unserialize for old PHP compatibility --- Files/sensitive_functions_list.txt | 1 + Utils/HookFiles/HookHead.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Files/sensitive_functions_list.txt b/Files/sensitive_functions_list.txt index 6051b00..d8ccd8a 100644 --- a/Files/sensitive_functions_list.txt +++ b/Files/sensitive_functions_list.txt @@ -2,6 +2,7 @@ unserialize|1 +fugio_unserialize|1 # ==== NEED TO SET VULN INJECT POINT NUMBERS!! ==== copy|1 file_exists|1 diff --git a/Utils/HookFiles/HookHead.php b/Utils/HookFiles/HookHead.php index 0a7c58c..adb3e8d 100644 --- a/Utils/HookFiles/HookHead.php +++ b/Utils/HookFiles/HookHead.php @@ -290,7 +290,7 @@ function get_declared_traits_r353t() { function filter_allowed_classes($array, $trigger_func, $func_argv) { $return_array = $array; - if ($trigger_func == "unserialize" && count($func_argv) > 1) { + if (($trigger_func == "unserialize" || $trigger_func == "fugio_unserialize") && count($func_argv) > 1) { if (array_key_exists("allowed_classes", $func_argv[1])) { if (gettype($func_argv[1]["allowed_classes"]) == "boolean") { if ($func_argv[1]["allowed_classes"] == false) { From 1ce85eb0f8c0184710aba0e7324478836af72009 Mon Sep 17 00:00:00 2001 From: Andreas Kellas Date: Mon, 26 Jun 2023 21:56:46 -0400 Subject: [PATCH 5/5] Revert "Hook fugio_unserialize for old PHP compatibility" This reverts commit 8047a04148733c8ce89f4b03eab70599c79e029e. --- Files/sensitive_functions_list.txt | 1 - Utils/HookFiles/HookHead.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Files/sensitive_functions_list.txt b/Files/sensitive_functions_list.txt index d8ccd8a..6051b00 100644 --- a/Files/sensitive_functions_list.txt +++ b/Files/sensitive_functions_list.txt @@ -2,7 +2,6 @@ unserialize|1 -fugio_unserialize|1 # ==== NEED TO SET VULN INJECT POINT NUMBERS!! ==== copy|1 file_exists|1 diff --git a/Utils/HookFiles/HookHead.php b/Utils/HookFiles/HookHead.php index adb3e8d..0a7c58c 100644 --- a/Utils/HookFiles/HookHead.php +++ b/Utils/HookFiles/HookHead.php @@ -290,7 +290,7 @@ function get_declared_traits_r353t() { function filter_allowed_classes($array, $trigger_func, $func_argv) { $return_array = $array; - if (($trigger_func == "unserialize" || $trigger_func == "fugio_unserialize") && count($func_argv) > 1) { + if ($trigger_func == "unserialize" && count($func_argv) > 1) { if (array_key_exists("allowed_classes", $func_argv[1])) { if (gettype($func_argv[1]["allowed_classes"]) == "boolean") { if ($func_argv[1]["allowed_classes"] == false) {