From e546013dac118e981c85bdae0ed6fd62ee60f701 Mon Sep 17 00:00:00 2001 From: Rey5 Date: Sun, 7 May 2023 04:18:19 +0800 Subject: [PATCH 1/5] ajax.php ACE security patch --- public/ajax.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/public/ajax.php b/public/ajax.php index c376fa62..271251e7 100644 --- a/public/ajax.php +++ b/public/ajax.php @@ -6,6 +6,29 @@ loggedinorreturn(); $action = $_POST['action'] ?? 'noAction'; $params = $_POST['params'] ?? []; +const ALLOWED_ACTION = [ + 'toggleUserMedalStatus', + 'attendanceRetroactive', + 'getPtGen', + 'addClaim', + 'removeClaim', + 'removeUserLeechWarn', + 'getOffer', + 'approvalModal', + 'approval', + 'addSeedBoxRecord', + 'removeSeedBoxRecord', + 'removeHitAndRun', + 'consumeBenefit', + 'clearShoutBox', + 'buyMedal', + 'giftMedal', + 'saveUserMedal', +]; +if(!in_array($action,ALLOWED_ACTION)){ + do_log('hack attempt '.print_r($CURUSER,true),'error'); + $action = 'noAction'; +} function noAction() { throw new \RuntimeException("no Action"); @@ -13,6 +36,7 @@ function noAction() try { + if(!isset($CURUSER))throw new \RuntimeException('Permission Denied'); $result = call_user_func($action, $params); exit(json_encode(success($result))); } catch (\Throwable $exception) { From d09e57a09f44482ebcb4340387f65aa1cc5559de Mon Sep 17 00:00:00 2001 From: Rey5 Date: Sun, 7 May 2023 04:14:25 +0800 Subject: [PATCH 2/5] fix ajax.php ACE security problem --- public/ajax.php | 1 - 1 file changed, 1 deletion(-) diff --git a/public/ajax.php b/public/ajax.php index 271251e7..8af1b1fd 100644 --- a/public/ajax.php +++ b/public/ajax.php @@ -36,7 +36,6 @@ function noAction() try { - if(!isset($CURUSER))throw new \RuntimeException('Permission Denied'); $result = call_user_func($action, $params); exit(json_encode(success($result))); } catch (\Throwable $exception) { From 72f260c76aed03cbfb414c164085bd6a9c412ba8 Mon Sep 17 00:00:00 2001 From: Rey5 Date: Sun, 7 May 2023 04:10:08 +0800 Subject: [PATCH 3/5] fix a user_can security problem --- include/globalfunctions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/globalfunctions.php b/include/globalfunctions.php index cbc0fd67..ec85a17a 100644 --- a/include/globalfunctions.php +++ b/include/globalfunctions.php @@ -1088,7 +1088,7 @@ function user_can($permission, $fail = false, $uid = 0): bool $uid = get_user_id(); $log .= ", set current uid: $uid"; } - if ($uid <= 0) { + if (!$fail && $uid <= 0) { do_log("$log, unauthenticated, false"); return false; } From 744d1ef05cb9068ebfa8e6c8d98fa1d34ba29fb9 Mon Sep 17 00:00:00 2001 From: Rey5 Date: Sun, 7 May 2023 08:38:46 +0800 Subject: [PATCH 4/5] reconstruct ajax --- public/ajax.php | 326 +++++++++++++++++++++++------------------------- 1 file changed, 154 insertions(+), 172 deletions(-) diff --git a/public/ajax.php b/public/ajax.php index 8af1b1fd..86bf9bb4 100644 --- a/public/ajax.php +++ b/public/ajax.php @@ -6,181 +6,163 @@ loggedinorreturn(); $action = $_POST['action'] ?? 'noAction'; $params = $_POST['params'] ?? []; -const ALLOWED_ACTION = [ - 'toggleUserMedalStatus', - 'attendanceRetroactive', - 'getPtGen', - 'addClaim', - 'removeClaim', - 'removeUserLeechWarn', - 'getOffer', - 'approvalModal', - 'approval', - 'addSeedBoxRecord', - 'removeSeedBoxRecord', - 'removeHitAndRun', - 'consumeBenefit', - 'clearShoutBox', - 'buyMedal', - 'giftMedal', - 'saveUserMedal', -]; -if(!in_array($action,ALLOWED_ACTION)){ - do_log('hack attempt '.print_r($CURUSER,true),'error'); - $action = 'noAction'; -} -function noAction() -{ - throw new \RuntimeException("no Action"); -} +class AjaxInterface{ - -try { - $result = call_user_func($action, $params); - exit(json_encode(success($result))); -} catch (\Throwable $exception) { - exit(json_encode(fail($exception->getMessage(), $_POST))); -} - -function toggleUserMedalStatus($params) -{ - global $CURUSER; - $rep = new \App\Repositories\MedalRepository(); - return $rep->toggleUserMedalStatus($params['id'], $CURUSER['id']); -} - - -function attendanceRetroactive($params) -{ - global $CURUSER; - $rep = new \App\Repositories\AttendanceRepository(); - return $rep->retroactive($CURUSER['id'], $params['timestamp']); -} - -function getPtGen($params) -{ - $rep = new Nexus\PTGen\PTGen(); - $result = $rep->generate($params['url']); - if ($rep->isRawPTGen($result)) { - return $result; - } elseif ($rep->isIyuu($result)) { - return $result['data']; - } else { - return ''; + public static function toggleUserMedalStatus($params) + { + global $CURUSER; + $rep = new \App\Repositories\MedalRepository(); + return $rep->toggleUserMedalStatus($params['id'], $CURUSER['id']); } -} - -function addClaim($params) -{ - global $CURUSER; - $rep = new \App\Repositories\ClaimRepository(); - return $rep->store($CURUSER['id'], $params['torrent_id']); -} - -function removeClaim($params) -{ - global $CURUSER; - $rep = new \App\Repositories\ClaimRepository(); - return $rep->delete($params['id'], $CURUSER['id']); -} - -function removeUserLeechWarn($params) -{ - global $CURUSER; - $rep = new \App\Repositories\UserRepository(); - return $rep->removeLeechWarn($CURUSER['id'], $params['uid']); -} - -function getOffer($params) -{ - $offer = \App\Models\Offer::query()->findOrFail($params['id']); - return $offer->toArray(); -} - -function approvalModal($params) -{ - global $CURUSER; - $rep = new \App\Repositories\TorrentRepository(); - return $rep->buildApprovalModal($CURUSER['id'], $params['torrent_id']); -} - -function approval($params) -{ - global $CURUSER; - foreach (['torrent_id', 'approval_status',] as $field) { - if (!isset($params[$field])) { - throw new \InvalidArgumentException("Require $field"); + + + public static function attendanceRetroactive($params) + { + global $CURUSER; + $rep = new \App\Repositories\AttendanceRepository(); + return $rep->retroactive($CURUSER['id'], $params['timestamp']); + } + + public static function getPtGen($params) + { + $rep = new Nexus\PTGen\PTGen(); + $result = $rep->generate($params['url']); + if ($rep->isRawPTGen($result)) { + return $result; + } elseif ($rep->isIyuu($result)) { + return $result['data']; + } else { + return ''; } } - $rep = new \App\Repositories\TorrentRepository(); - return $rep->approval($CURUSER['id'], $params); -} - -function addSeedBoxRecord($params) -{ - global $CURUSER; - $rep = new \App\Repositories\SeedBoxRepository(); - $params['uid'] = $CURUSER['id']; - $params['type'] = \App\Models\SeedBoxRecord::TYPE_USER; - $params['status'] = \App\Models\SeedBoxRecord::STATUS_UNAUDITED; - return $rep->store($params); -} - -function removeSeedBoxRecord($params) -{ - global $CURUSER; - $rep = new \App\Repositories\SeedBoxRepository(); - return $rep->delete($params['id'], $CURUSER['id']); -} - -function removeHitAndRun($params) -{ - global $CURUSER; - $rep = new \App\Repositories\BonusRepository(); - return $rep->consumeToCancelHitAndRun($CURUSER['id'], $params['id']); -} - -function consumeBenefit($params) -{ - global $CURUSER; - $rep = new \App\Repositories\UserRepository(); - return $rep->consumeBenefit($CURUSER['id'], $params); -} - -function clearShoutBox($params) -{ - global $CURUSER; - user_can('sbmanage', true); - \Nexus\Database\NexusDB::table('shoutbox')->delete(); - return true; -} - -function buyMedal($params) -{ - global $CURUSER; - $rep = new \App\Repositories\BonusRepository(); - return $rep->consumeToBuyMedal($CURUSER['id'], $params['medal_id']); -} - -function giftMedal($params) -{ - global $CURUSER; - $rep = new \App\Repositories\BonusRepository(); - return $rep->consumeToGiftMedal($CURUSER['id'], $params['medal_id'], $params['uid']); -} - -function saveUserMedal($params) -{ - global $CURUSER; - $data = []; - foreach ($params as $param) { - $fieldAndId = explode('_', $param['name']); - $field = $fieldAndId[0]; - $id = $fieldAndId[1]; - $value = $param['value']; - $data[$id][$field] = $value; + + public static function addClaim($params) + { + global $CURUSER; + $rep = new \App\Repositories\ClaimRepository(); + return $rep->store($CURUSER['id'], $params['torrent_id']); + } + + public static function removeClaim($params) + { + global $CURUSER; + $rep = new \App\Repositories\ClaimRepository(); + return $rep->delete($params['id'], $CURUSER['id']); + } + + public static function removeUserLeechWarn($params) + { + global $CURUSER; + $rep = new \App\Repositories\UserRepository(); + return $rep->removeLeechWarn($CURUSER['id'], $params['uid']); + } + + public static function getOffer($params) + { + $offer = \App\Models\Offer::query()->findOrFail($params['id']); + return $offer->toArray(); + } + + public static function approvalModal($params) + { + global $CURUSER; + $rep = new \App\Repositories\TorrentRepository(); + return $rep->buildApprovalModal($CURUSER['id'], $params['torrent_id']); + } + + public static function approval($params) + { + global $CURUSER; + foreach (['torrent_id', 'approval_status',] as $field) { + if (!isset($params[$field])) { + throw new \InvalidArgumentException("Require $field"); + } + } + $rep = new \App\Repositories\TorrentRepository(); + return $rep->approval($CURUSER['id'], $params); + } + + public static function addSeedBoxRecord($params) + { + global $CURUSER; + $rep = new \App\Repositories\SeedBoxRepository(); + $params['uid'] = $CURUSER['id']; + $params['type'] = \App\Models\SeedBoxRecord::TYPE_USER; + $params['status'] = \App\Models\SeedBoxRecord::STATUS_UNAUDITED; + return $rep->store($params); + } + + public static function removeSeedBoxRecord($params) + { + global $CURUSER; + $rep = new \App\Repositories\SeedBoxRepository(); + return $rep->delete($params['id'], $CURUSER['id']); + } + + public static function removeHitAndRun($params) + { + global $CURUSER; + $rep = new \App\Repositories\BonusRepository(); + return $rep->consumeToCancelHitAndRun($CURUSER['id'], $params['id']); + } + + public static function consumeBenefit($params) + { + global $CURUSER; + $rep = new \App\Repositories\UserRepository(); + return $rep->consumeBenefit($CURUSER['id'], $params); + } + + public static function clearShoutBox($params) + { + global $CURUSER; + user_can('sbmanage', true); + \Nexus\Database\NexusDB::table('shoutbox')->delete(); + return true; + } + + public static function buyMedal($params) + { + global $CURUSER; + $rep = new \App\Repositories\BonusRepository(); + return $rep->consumeToBuyMedal($CURUSER['id'], $params['medal_id']); + } + + public static function giftMedal($params) + { + global $CURUSER; + $rep = new \App\Repositories\BonusRepository(); + return $rep->consumeToGiftMedal($CURUSER['id'], $params['medal_id'], $params['uid']); + } + + public static function saveUserMedal($params) + { + global $CURUSER; + $data = []; + foreach ($params as $param) { + $fieldAndId = explode('_', $param['name']); + $field = $fieldAndId[0]; + $id = $fieldAndId[1]; + $value = $param['value']; + $data[$id][$field] = $value; + } + // dd($params, $data); + $rep = new \App\Repositories\MedalRepository(); + return $rep->saveUserMedal($CURUSER['id'], $data); } -// dd($params, $data); - $rep = new \App\Repositories\MedalRepository(); - return $rep->saveUserMedal($CURUSER['id'], $data); +} + +$class = 'AjaxInterface'; +$reflection = new ReflectionClass($class); + +try { + if($reflection->hasMethod($action)&&$reflection->getMethod($action)->isStatic()) { + $result = $class::$action($params); + exit(json_encode(success($result))); + } else { + do_log('hack attempt '.print_r($CURUSER, true), 'error'); + throw new \RuntimeException("no Action"); + } +}catch(\Throwable $exception){ + exit(json_encode(fail($exception->getMessage(), $_POST))); } From 6dd1b248f7960421221fcc93fd2ed2951c301cd4 Mon Sep 17 00:00:00 2001 From: Rey5 Date: Sun, 7 May 2023 09:07:15 +0800 Subject: [PATCH 5/5] simplify no action output --- public/ajax.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/ajax.php b/public/ajax.php index 86bf9bb4..ff2073e6 100644 --- a/public/ajax.php +++ b/public/ajax.php @@ -160,7 +160,7 @@ try { $result = $class::$action($params); exit(json_encode(success($result))); } else { - do_log('hack attempt '.print_r($CURUSER, true), 'error'); + do_log("hacking attempt made by {$CURUSER['username']},uid {$CURUSER['id']}", 'error'); throw new \RuntimeException("no Action"); } }catch(\Throwable $exception){