From faddd99f2b9408b524e5eb8a01589fe1fa282df2 Mon Sep 17 00:00:00 2001 From: George Joseph Date: Mon, 22 Jul 2024 08:05:03 -0600 Subject: [PATCH 1/2] manager.c: Add entries to Originate blacklist Added Reload and DBdeltree to the list of dialplan application that can't be executed via the Originate manager action without also having write SYSTEM permissions. Added CURL, DB*, FILE, ODBC and REALTIME* to the list of dialplan functions that can't be executed via the Originate manager action without also having write SYSTEM permissions. If the Queue application is attempted to be run by the Originate manager action and an AGI parameter is specified in the app data, it'll be rejected unless the manager user has either the AGI or SYSTEM permissions. Resolves: #GHSA-c4cg-9275-6w44 --- main/manager.c | 161 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 141 insertions(+), 20 deletions(-) diff --git a/main/manager.c b/main/manager.c index cb64a234e5..2ce88a3ab8 100644 --- a/main/manager.c +++ b/main/manager.c @@ -6325,6 +6325,145 @@ aocmessage_cleanup: return 0; } +struct originate_permissions_entry { + const char *search; + int permission; + int (*searchfn)(const char *app, const char *data, const char *search); +}; + +/*! + * \internal + * \brief Check if the application is allowed for Originate + * + * \param app The "app" parameter + * \param data The "appdata" parameter (ignored) + * \param search The search string + * \retval 1 Match + * \retval 0 No match + */ +static int app_match(const char *app, const char *data, const char *search) +{ + /* + * We use strcasestr so we don't have to trim any blanks + * from the front or back of the string. + */ + return !!(strcasestr(app, search)); +} + +/*! + * \internal + * \brief Check if the appdata is allowed for Originate + * + * \param app The "app" parameter (ignored) + * \param data The "appdata" parameter + * \param search The search string + * \retval 1 Match + * \retval 0 No match + */ +static int appdata_match(const char *app, const char *data, const char *search) +{ + return !!(strstr(data, search)); +} + +/*! + * \internal + * \brief Check if the Queue application is allowed for Originate + * + * It's only allowed if there's no AGI parameter set + * + * \param app The "app" parameter + * \param data The "appdata" parameter + * \param search The search string + * \retval 1 Match + * \retval 0 No match + */ +static int queue_match(const char *app, const char *data, const char *search) +{ + char *parse; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(queuename); + AST_APP_ARG(options); + AST_APP_ARG(url); + AST_APP_ARG(announceoverride); + AST_APP_ARG(queuetimeoutstr); + AST_APP_ARG(agi); + AST_APP_ARG(gosub); + AST_APP_ARG(rule); + AST_APP_ARG(position); + ); + + if (!strcasestr(app, "queue")) { + return 0; + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + /* + * The Queue application is fine unless the AGI parameter is set. + * If it is, we need to check the user's permissions. + */ + return !ast_strlen_zero(args.agi); +} + +/* + * The Originate application and application data are passed + * to each searchfn in the list. If a searchfn returns true + * and the user's permissions don't include the permissions specified + * in the list entry, the Originate action will be denied. + * + * If no searchfn returns true, the Originate action is allowed. + */ +static struct originate_permissions_entry originate_app_permissions[] = { + /* + * The app_match function checks if the search string is + * anywhere in the app parameter. The check is case-insensitive. + */ + { "agi", EVENT_FLAG_SYSTEM, app_match }, + { "dbdeltree", EVENT_FLAG_SYSTEM, app_match }, + { "exec", EVENT_FLAG_SYSTEM, app_match }, + { "externalivr", EVENT_FLAG_SYSTEM, app_match }, + { "mixmonitor", EVENT_FLAG_SYSTEM, app_match }, + { "originate", EVENT_FLAG_SYSTEM, app_match }, + { "reload", EVENT_FLAG_SYSTEM, app_match }, + { "system", EVENT_FLAG_SYSTEM, app_match }, + /* + * Since the queue_match function specifically checks + * for the presence of the AGI parameter, we'll allow + * the call if the user has either the AGI or SYSTEM + * permission. + */ + { "queue", EVENT_FLAG_AGI | EVENT_FLAG_SYSTEM, queue_match }, + /* + * The appdata_match function checks if the search string is + * anywhere in the appdata parameter. Unlike app_match, + * the check is case-sensitive. These are generally + * dialplan functions. + */ + { "CURL", EVENT_FLAG_SYSTEM, appdata_match }, + { "DB", EVENT_FLAG_SYSTEM, appdata_match }, + { "EVAL", EVENT_FLAG_SYSTEM, appdata_match }, + { "FILE", EVENT_FLAG_SYSTEM, appdata_match }, + { "ODBC", EVENT_FLAG_SYSTEM, appdata_match }, + { "REALTIME", EVENT_FLAG_SYSTEM, appdata_match }, + { "SHELL", EVENT_FLAG_SYSTEM, appdata_match }, + { NULL, 0 }, +}; + +static int is_originate_app_permitted(const char *app, const char *data, + int permission) +{ + int i; + + for (i = 0; originate_app_permissions[i].search; i++) { + if (originate_app_permissions[i].searchfn(app, data, originate_app_permissions[i].search)) { + return !!(permission & originate_app_permissions[i].permission); + } + } + + return 1; +} + static int action_originate(struct mansession *s, const struct message *m) { const char *name = astman_get_header(m, "Channel"); @@ -6418,26 +6557,8 @@ static int action_originate(struct mansession *s, const struct message *m) } if (!ast_strlen_zero(app) && s->session) { - int bad_appdata = 0; - /* To run the System application (or anything else that goes to - * shell), you must have the additional System privilege */ - if (!(s->session->writeperm & EVENT_FLAG_SYSTEM) - && ( - strcasestr(app, "system") || /* System(rm -rf /) - TrySystem(rm -rf /) */ - strcasestr(app, "exec") || /* Exec(System(rm -rf /)) - TryExec(System(rm -rf /)) */ - strcasestr(app, "agi") || /* AGI(/bin/rm,-rf /) - EAGI(/bin/rm,-rf /) */ - strcasestr(app, "mixmonitor") || /* MixMonitor(blah,,rm -rf) */ - strcasestr(app, "externalivr") || /* ExternalIVR(rm -rf) */ - strcasestr(app, "originate") || /* Originate(Local/1234,app,System,rm -rf) */ - (strstr(appdata, "SHELL") && (bad_appdata = 1)) || /* NoOp(${SHELL(rm -rf /)}) */ - (strstr(appdata, "EVAL") && (bad_appdata = 1)) /* NoOp(${EVAL(${some_var_containing_SHELL})}) */ - )) { - char error_buf[64]; - snprintf(error_buf, sizeof(error_buf), "Originate Access Forbidden: %s", bad_appdata ? "Data" : "Application"); - astman_send_error(s, m, error_buf); + if (!is_originate_app_permitted(app, appdata, s->session->writeperm)) { + astman_send_error(s, m, "Originate Access Forbidden: app or data blacklisted"); res = 0; goto fast_orig_cleanup; } -- 2.44.2