1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
|
From faddd99f2b9408b524e5eb8a01589fe1fa282df2 Mon Sep 17 00:00:00 2001
From: George Joseph <gjoseph@sangoma.com>
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
|