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; }