security: Inhibit execution of privilege escalating functions
This patch allows individual dialplan functions to be marked as 'dangerous', to inhibit their execution from external sources. A 'dangerous' function is one which results in a privilege escalation. For example, if one were to read the channel variable SHELL(rm -rf /) Bad Things(TM) could happen; even if the external source has only read permissions. Execution from external sources may be enabled by setting 'live_dangerously' to 'yes' in the [options] section of asterisk.conf. Although doing so is not recommended. (closes issue ASTERISK-22905) Review: http://reviewboard.digium.internal/r/432/ ........ Merged revisions 403913 from http://svn.asterisk.org/svn/asterisk/branches/1.8 git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/11@403917 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
parent
faf4f6187b
commit
a9ee948e90
12 changed files with 472 additions and 37 deletions
|
@ -26,6 +26,9 @@ Sections
|
|||
* Manager Class Authorizations:
|
||||
Recognizing potential issues with certain classes of authorization
|
||||
|
||||
* Avoid Privilege Escalations:
|
||||
Disable the ability to execute functions that may escalate privileges
|
||||
|
||||
----------------
|
||||
Additional Links
|
||||
----------------
|
||||
|
@ -344,3 +347,24 @@ same as the class authorization "system". Good system configuration, such as
|
|||
not running Asterisk as root, can prevent serious problems from arising when
|
||||
allowing external connections to originate calls into Asterisk.
|
||||
|
||||
===========================
|
||||
Avoid Privilege Escalations
|
||||
===========================
|
||||
|
||||
External control protocols, such as Manager, often have the ability to get and
|
||||
set channel variables; which allows the execution of dialplan functions.
|
||||
|
||||
Dialplan functions within Asterisk are incredibly powerful, which is wonderful
|
||||
for building applications using Asterisk. But during the read or write
|
||||
execution, certain diaplan functions do much more. For example, reading the
|
||||
SHELL() function can execute arbitrary commands on the system Asterisk is
|
||||
running on. Writing to the FILE() function can change any file that Asterisk has
|
||||
write access to.
|
||||
|
||||
When these functions are executed from an external protocol, that execution
|
||||
could result in a privilege escalation. Asterisk can inhibit the execution of
|
||||
these functions, if live_dangerously in the [options] section of asterisk.conf
|
||||
is set to no.
|
||||
|
||||
For backwards compatibility, live_dangerously defaults to yes, and must be
|
||||
explicitly set to no to enable this privilege escalation protection.
|
||||
|
|
|
@ -36,6 +36,14 @@ chan_sip - Clarify The "sip show peers" Forcerport Column And Add Comedia
|
|||
display the Comedia setting since this is useful information as well. We
|
||||
no longer have a simple "NAT" setting like other versions before 11.
|
||||
|
||||
* Certain dialplan functions have been marked as 'dangerous', and may only be
|
||||
executed from the dialplan. Execution from extenal sources (AMI's GetVar and
|
||||
SetVar actions; etc.) may be inhibited by setting live_dangerously in the
|
||||
[options] section of asterisk.conf to no. SHELL(), channel locking, and direct
|
||||
file read/write functions are marked as dangerous. DB_DELETE() and
|
||||
REALTIME_DESTROY() are marked as dangerous for reads, but can now safely
|
||||
accept writes (which ignore the provided value).
|
||||
|
||||
From 11.5 to 11.6:
|
||||
* res_agi will now properly indicate if there was an error in streaming an
|
||||
audio file. The result code will be -1 and the result returned from the
|
||||
|
|
|
@ -83,6 +83,12 @@ documentation_language = en_US ; Set the language you want documentation
|
|||
; gosub - Invoke the stdexten using a gosub as
|
||||
; documented in extensions.conf.sample.
|
||||
; Default gosub.
|
||||
;live_dangerously = no ; Enable the execution of 'dangerous' dialplan
|
||||
; functions from external sources (AMI,
|
||||
; etc.) These functions (such as SHELL) are
|
||||
; considered dangerous because they can allow
|
||||
; privilege escalation.
|
||||
; Default yes, for backward compatability.
|
||||
|
||||
; Changing the following lines may compromise your security.
|
||||
;[files]
|
||||
|
|
|
@ -110,6 +110,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||
<para>This function will retrieve a value from the Asterisk database
|
||||
and then remove that key from the database. <variable>DB_RESULT</variable>
|
||||
will be set to the key's value if it exists.</para>
|
||||
<note>
|
||||
<para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
|
||||
is set to <literal>no</literal>, this function can only be read from the
|
||||
dialplan, and not directly from external protocols. It can, however, be
|
||||
executed as a write operation (<literal>DB_DELETE(family, key)=ignored</literal>)</para>
|
||||
</note>
|
||||
</description>
|
||||
<see-also>
|
||||
<ref type="application">DBdel</ref>
|
||||
|
@ -311,10 +317,22 @@ static int function_db_delete(struct ast_channel *chan, const char *cmd,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Wrapper to execute DB_DELETE from a write operation. Allows execution
|
||||
* even if live_dangerously is disabled.
|
||||
*/
|
||||
static int function_db_delete_write(struct ast_channel *chan, const char *cmd, char *parse,
|
||||
const char *value)
|
||||
{
|
||||
/* Throwaway to hold the result from the read */
|
||||
char buf[128];
|
||||
return function_db_delete(chan, cmd, parse, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
static struct ast_custom_function db_delete_function = {
|
||||
.name = "DB_DELETE",
|
||||
.read = function_db_delete,
|
||||
.write = function_db_delete_write,
|
||||
};
|
||||
|
||||
static int unload_module(void)
|
||||
|
@ -335,7 +353,7 @@ static int load_module(void)
|
|||
|
||||
res |= ast_custom_function_register(&db_function);
|
||||
res |= ast_custom_function_register(&db_exists_function);
|
||||
res |= ast_custom_function_register(&db_delete_function);
|
||||
res |= ast_custom_function_register_escalating(&db_delete_function, AST_CFE_READ);
|
||||
res |= ast_custom_function_register(&db_keys_function);
|
||||
|
||||
return res;
|
||||
|
|
|
@ -71,6 +71,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||
<parameter name="filename" required="true" />
|
||||
</syntax>
|
||||
<description>
|
||||
<note>
|
||||
<para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
|
||||
is set to <literal>no</literal>, this function can only be executed from the
|
||||
dialplan, and not directly from external protocols.</para>
|
||||
</note>
|
||||
</description>
|
||||
</function>
|
||||
<function name="FILE" language="en_US">
|
||||
|
@ -167,6 +172,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||
<para> Set(FILE(/tmp/foo.txt,-1,,l)=bar)</para>
|
||||
<para> ; Append "bar" to the file with a newline</para>
|
||||
<para> Set(FILE(/tmp/foo.txt,,,al)=bar)</para>
|
||||
<note>
|
||||
<para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
|
||||
is set to <literal>no</literal>, this function can only be executed from the
|
||||
dialplan, and not directly from external protocols.</para>
|
||||
</note>
|
||||
</description>
|
||||
<see-also>
|
||||
<ref type="function">FILE_COUNT_LINE</ref>
|
||||
|
@ -197,6 +207,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||
</syntax>
|
||||
<description>
|
||||
<para>Returns the number of lines, or <literal>-1</literal> on error.</para>
|
||||
<note>
|
||||
<para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
|
||||
is set to <literal>no</literal>, this function can only be executed from the
|
||||
dialplan, and not directly from external protocols.</para>
|
||||
</note>
|
||||
</description>
|
||||
<see-also>
|
||||
<ref type="function">FILE</ref>
|
||||
|
@ -216,6 +231,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||
<para>'d' - DOS "\r\n" format</para>
|
||||
<para>'m' - Macintosh "\r" format</para>
|
||||
<para>'x' - Cannot be determined</para>
|
||||
<note>
|
||||
<para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
|
||||
is set to <literal>no</literal>, this function can only be executed from the
|
||||
dialplan, and not directly from external protocols.</para>
|
||||
</note>
|
||||
</description>
|
||||
<see-also>
|
||||
<ref type="function">FILE</ref>
|
||||
|
@ -1259,10 +1279,10 @@ static int load_module(void)
|
|||
int res = 0;
|
||||
|
||||
res |= ast_custom_function_register(&env_function);
|
||||
res |= ast_custom_function_register(&stat_function);
|
||||
res |= ast_custom_function_register(&file_function);
|
||||
res |= ast_custom_function_register(&file_count_line_function);
|
||||
res |= ast_custom_function_register(&file_format_function);
|
||||
res |= ast_custom_function_register_escalating(&stat_function, AST_CFE_READ);
|
||||
res |= ast_custom_function_register_escalating(&file_function, AST_CFE_BOTH);
|
||||
res |= ast_custom_function_register_escalating(&file_count_line_function, AST_CFE_READ);
|
||||
res |= ast_custom_function_register_escalating(&file_format_function, AST_CFE_READ);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -59,6 +59,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||
Returns <literal>1</literal> if the lock was obtained or <literal>0</literal> on error.</para>
|
||||
<note><para>To avoid the possibility of a deadlock, LOCK will only attempt to
|
||||
obtain the lock for 3 seconds if the channel already has another lock.</para></note>
|
||||
<note>
|
||||
<para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
|
||||
is set to <literal>no</literal>, this function can only be executed from the
|
||||
dialplan, and not directly from external protocols.</para>
|
||||
</note>
|
||||
</description>
|
||||
</function>
|
||||
<function name="TRYLOCK" language="en_US">
|
||||
|
@ -72,6 +77,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||
<para>Attempts to grab a named lock exclusively, and prevents other channels
|
||||
from obtaining the same lock. Returns <literal>1</literal> if the lock was
|
||||
available or <literal>0</literal> otherwise.</para>
|
||||
<note>
|
||||
<para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
|
||||
is set to <literal>no</literal>, this function can only be executed from the
|
||||
dialplan, and not directly from external protocols.</para>
|
||||
</note>
|
||||
</description>
|
||||
</function>
|
||||
<function name="UNLOCK" language="en_US">
|
||||
|
@ -86,6 +96,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||
had a lock or <literal>0</literal> otherwise.</para>
|
||||
<note><para>It is generally unnecessary to unlock in a hangup routine, as any locks
|
||||
held are automatically freed when the channel is destroyed.</para></note>
|
||||
<note>
|
||||
<para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
|
||||
is set to <literal>no</literal>, this function can only be executed from the
|
||||
dialplan, and not directly from external protocols.</para>
|
||||
</note>
|
||||
</description>
|
||||
</function>
|
||||
***/
|
||||
|
@ -502,9 +517,9 @@ static int unload_module(void)
|
|||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res = ast_custom_function_register(&lock_function);
|
||||
res |= ast_custom_function_register(&trylock_function);
|
||||
res |= ast_custom_function_register(&unlock_function);
|
||||
int res = ast_custom_function_register_escalating(&lock_function, AST_CFE_READ);
|
||||
res |= ast_custom_function_register_escalating(&trylock_function, AST_CFE_READ);
|
||||
res |= ast_custom_function_register_escalating(&unlock_function, AST_CFE_READ);
|
||||
|
||||
if (ast_pthread_create_background(&broker_tid, NULL, lock_broker, NULL)) {
|
||||
ast_log(LOG_ERROR, "Failed to start lock broker thread. Unloading func_lock module.\n");
|
||||
|
|
|
@ -115,6 +115,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||
<description>
|
||||
<para>This function acts in the same way as REALTIME(....) does, except that
|
||||
it destroys the matched record in the RT engine.</para>
|
||||
<note>
|
||||
<para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
|
||||
is set to <literal>no</literal>, this function can only be read from the
|
||||
dialplan, and not directly from external protocols. It can, however, be
|
||||
executed as a write operation (<literal>REALTIME_DESTROY(family, fieldmatch)=ignored</literal>)</para>
|
||||
</note>
|
||||
</description>
|
||||
<see-also>
|
||||
<ref type="function">REALTIME</ref>
|
||||
|
@ -439,28 +445,32 @@ static int function_realtime_readdestroy(struct ast_channel *chan, const char *c
|
|||
return -1;
|
||||
}
|
||||
|
||||
resultslen = 0;
|
||||
n = 0;
|
||||
for (var = head; var; n++, var = var->next)
|
||||
resultslen += strlen(var->name) + strlen(var->value);
|
||||
/* add space for delimiters and final '\0' */
|
||||
resultslen += n * (strlen(args.delim1) + strlen(args.delim2)) + 1;
|
||||
if (len > 0) {
|
||||
resultslen = 0;
|
||||
n = 0;
|
||||
for (var = head; var; n++, var = var->next) {
|
||||
resultslen += strlen(var->name) + strlen(var->value);
|
||||
}
|
||||
/* add space for delimiters and final '\0' */
|
||||
resultslen += n * (strlen(args.delim1) + strlen(args.delim2)) + 1;
|
||||
|
||||
if (resultslen > len) {
|
||||
/* Unfortunately this does mean that we cannot destroy the row
|
||||
* anymore. But OTOH, we're not destroying someones data without
|
||||
* giving him the chance to look at it. */
|
||||
ast_log(LOG_WARNING, "Failed to fetch/destroy. Realtime data is too large: need %zu, have %zu.\n", resultslen, len);
|
||||
return -1;
|
||||
}
|
||||
if (resultslen > len) {
|
||||
/* Unfortunately this does mean that we cannot destroy
|
||||
* the row anymore. But OTOH, we're not destroying
|
||||
* someones data without giving him the chance to look
|
||||
* at it. */
|
||||
ast_log(LOG_WARNING, "Failed to fetch/destroy. Realtime data is too large: need %zu, have %zu.\n", resultslen, len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* len is going to be sensible, so we don't need to check for stack
|
||||
* overflows here. */
|
||||
out = ast_str_alloca(resultslen);
|
||||
for (var = head; var; var = var->next) {
|
||||
ast_str_append(&out, 0, "%s%s%s%s", var->name, args.delim2, var->value, args.delim1);
|
||||
/* len is going to be sensible, so we don't need to check for
|
||||
* stack overflows here. */
|
||||
out = ast_str_alloca(resultslen);
|
||||
for (var = head; var; var = var->next) {
|
||||
ast_str_append(&out, 0, "%s%s%s%s", var->name, args.delim2, var->value, args.delim1);
|
||||
}
|
||||
ast_copy_string(buf, ast_str_buffer(out), len);
|
||||
}
|
||||
ast_copy_string(buf, ast_str_buffer(out), len);
|
||||
|
||||
ast_destroy_realtime(args.family, args.fieldmatch, args.value, SENTINEL);
|
||||
ast_variables_destroy(head);
|
||||
|
@ -471,6 +481,15 @@ static int function_realtime_readdestroy(struct ast_channel *chan, const char *c
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Wrapper to execute REALTIME_DESTROY from a write operation. Allows
|
||||
* execution even if live_dangerously is disabled.
|
||||
*/
|
||||
static int function_realtime_writedestroy(struct ast_channel *chan, const char *cmd, char *data, const char *value)
|
||||
{
|
||||
return function_realtime_readdestroy(chan, cmd, data, NULL, 0);
|
||||
}
|
||||
|
||||
static struct ast_custom_function realtime_function = {
|
||||
.name = "REALTIME",
|
||||
.read = function_realtime_read,
|
||||
|
@ -496,6 +515,7 @@ static struct ast_custom_function realtime_store_function = {
|
|||
static struct ast_custom_function realtime_destroy_function = {
|
||||
.name = "REALTIME_DESTROY",
|
||||
.read = function_realtime_readdestroy,
|
||||
.write = function_realtime_writedestroy,
|
||||
};
|
||||
|
||||
static int unload_module(void)
|
||||
|
@ -514,7 +534,7 @@ static int load_module(void)
|
|||
int res = 0;
|
||||
res |= ast_custom_function_register(&realtime_function);
|
||||
res |= ast_custom_function_register(&realtime_store_function);
|
||||
res |= ast_custom_function_register(&realtime_destroy_function);
|
||||
res |= ast_custom_function_register_escalating(&realtime_destroy_function, AST_CFE_READ);
|
||||
res |= ast_custom_function_register(&realtimefield_function);
|
||||
res |= ast_custom_function_register(&realtimehash_function);
|
||||
return res;
|
||||
|
|
|
@ -88,11 +88,17 @@ static int shell_helper(struct ast_channel *chan, const char *cmd, char *data,
|
|||
</syntax>
|
||||
<description>
|
||||
<para>Collects the output generated by a command executed by the system shell</para>
|
||||
<para>Example: <literal>Set(foo=${SHELL(echo \bar\)})</literal></para>
|
||||
<note><para>The command supplied to this function will be executed by the
|
||||
system's shell, typically specified in the SHELL environment variable. There
|
||||
are many different system shells available with somewhat different behaviors,
|
||||
so the output generated by this function may vary between platforms.</para></note>
|
||||
<para>Example: <literal>Set(foo=${SHELL(echo bar)})</literal></para>
|
||||
<note>
|
||||
<para>The command supplied to this function will be executed by the
|
||||
system's shell, typically specified in the SHELL environment variable. There
|
||||
are many different system shells available with somewhat different behaviors,
|
||||
so the output generated by this function may vary between platforms.</para>
|
||||
|
||||
<para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
|
||||
is set to <literal>no</literal>, this function can only be executed from the
|
||||
dialplan, and not directly from external protocols.</para>
|
||||
</note>
|
||||
</description>
|
||||
|
||||
</function>
|
||||
|
@ -109,7 +115,7 @@ static int unload_module(void)
|
|||
|
||||
static int load_module(void)
|
||||
{
|
||||
return ast_custom_function_register(&shell_function);
|
||||
return ast_custom_function_register_escalating(&shell_function, AST_CFE_READ);
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Collects the output generated by a command executed by the system shell");
|
||||
|
|
|
@ -1309,16 +1309,44 @@ struct ast_custom_function* ast_custom_function_find(const char *name);
|
|||
*/
|
||||
int ast_custom_function_unregister(struct ast_custom_function *acf);
|
||||
|
||||
/*!
|
||||
* \brief Description of the ways in which a function may escalate privileges.
|
||||
*/
|
||||
enum ast_custom_function_escalation {
|
||||
AST_CFE_NONE,
|
||||
AST_CFE_READ,
|
||||
AST_CFE_WRITE,
|
||||
AST_CFE_BOTH,
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Register a custom function
|
||||
*/
|
||||
#define ast_custom_function_register(acf) __ast_custom_function_register(acf, ast_module_info->self)
|
||||
|
||||
/*!
|
||||
* \brief Register a custom function which requires escalated privileges.
|
||||
*
|
||||
* Examples would be SHELL() (for which a read needs permission to execute
|
||||
* arbitrary code) or FILE() (for which write needs permission to change files
|
||||
* on the filesystem).
|
||||
*/
|
||||
#define ast_custom_function_register_escalating(acf, escalation) __ast_custom_function_register_escalating(acf, escalation, ast_module_info->self)
|
||||
|
||||
/*!
|
||||
* \brief Register a custom function
|
||||
*/
|
||||
int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_module *mod);
|
||||
|
||||
/*!
|
||||
* \brief Register a custom function which requires escalated privileges.
|
||||
*
|
||||
* Examples would be SHELL() (for which a read needs permission to execute
|
||||
* arbitrary code) or FILE() (for which write needs permission to change files
|
||||
* on the filesystem).
|
||||
*/
|
||||
int __ast_custom_function_register_escalating(struct ast_custom_function *acf, enum ast_custom_function_escalation escalation, struct ast_module *mod);
|
||||
|
||||
/*!
|
||||
* \brief Retrieve the number of active calls
|
||||
*/
|
||||
|
@ -1432,6 +1460,32 @@ unsigned int ast_hashtab_hash_contexts(const void *obj);
|
|||
*/
|
||||
char *ast_complete_applications(const char *line, const char *word, int state);
|
||||
|
||||
/*!
|
||||
* \brief Enable/disable the execution of 'dangerous' functions from external
|
||||
* protocols (AMI, etc.).
|
||||
*
|
||||
* These dialplan functions (such as \c SHELL) provide an opportunity for
|
||||
* privilege escalation. They are okay to invoke from the dialplan, but external
|
||||
* protocols with permission controls should not normally invoke them.
|
||||
*
|
||||
* This function can globally enable/disable the execution of dangerous
|
||||
* functions from external protocols.
|
||||
*
|
||||
* \param new_live_dangerously If true, enable the execution of escalating
|
||||
* functions from external protocols.
|
||||
*/
|
||||
void pbx_live_dangerously(int new_live_dangerously);
|
||||
|
||||
/*!
|
||||
* \brief Inhibit (in the current thread) the execution of dialplan functions
|
||||
* which cause privilege escalations. If pbx_live_dangerously() has been
|
||||
* called, this function has no effect.
|
||||
*
|
||||
* \return 0 if successfuly marked current thread.
|
||||
* \return Non-zero if marking current thread failed.
|
||||
*/
|
||||
int ast_thread_inhibit_escalations(void);
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -3184,6 +3184,8 @@ static void ast_readconfig(void)
|
|||
unsigned int dbdir:1;
|
||||
unsigned int keydir:1;
|
||||
} found = { 0, 0 };
|
||||
/* Default to true for backward compatibility */
|
||||
int live_dangerously = 1;
|
||||
|
||||
/* Set default value */
|
||||
option_dtmfminduration = AST_MIN_DTMF_DURATION;
|
||||
|
@ -3415,8 +3417,11 @@ static void ast_readconfig(void)
|
|||
v->value);
|
||||
ast_clear_flag(&ast_options, AST_OPT_FLAG_STDEXTEN_MACRO);
|
||||
}
|
||||
} else if (!strcasecmp(v->name, "live_dangerously")) {
|
||||
live_dangerously = ast_true(v->value);
|
||||
}
|
||||
}
|
||||
pbx_live_dangerously(live_dangerously);
|
||||
for (v = ast_variable_browse(cfg, "compat"); v; v = v->next) {
|
||||
float version;
|
||||
if (sscanf(v->value, "%30f", &version) != 1) {
|
||||
|
|
254
main/pbx.c
254
main/pbx.c
|
@ -822,6 +822,17 @@ static struct ast_taskprocessor *extension_state_tps;
|
|||
|
||||
AST_THREADSTORAGE(switch_data);
|
||||
AST_THREADSTORAGE(extensionstate_buf);
|
||||
/*!
|
||||
* \brief A thread local indicating whether the current thread can run
|
||||
* 'dangerous' dialplan functions.
|
||||
*/
|
||||
AST_THREADSTORAGE(thread_inhibit_escalations_tl);
|
||||
|
||||
/*!
|
||||
* \brief Set to true (non-zero) to globally allow all dangerous dialplan
|
||||
* functions to run.
|
||||
*/
|
||||
static int live_dangerously;
|
||||
|
||||
/*!
|
||||
\brief ast_exten: An extension
|
||||
|
@ -1318,6 +1329,19 @@ static int totalcalls;
|
|||
|
||||
static AST_RWLIST_HEAD_STATIC(acf_root, ast_custom_function);
|
||||
|
||||
/*!
|
||||
* \brief Extra information for an \ref ast_custom_function holding privilege
|
||||
* escalation information. Kept in a separate structure for ABI compatibility.
|
||||
*/
|
||||
struct ast_custom_escalating_function {
|
||||
AST_RWLIST_ENTRY(ast_custom_escalating_function) list;
|
||||
const struct ast_custom_function *acf;
|
||||
unsigned int read_escalates:1;
|
||||
unsigned int write_escalates:1;
|
||||
};
|
||||
|
||||
static AST_RWLIST_HEAD_STATIC(escalation_root, ast_custom_escalating_function);
|
||||
|
||||
/*! \brief Declaration of builtin applications */
|
||||
static struct pbx_builtin {
|
||||
char name[AST_MAX_APP];
|
||||
|
@ -3911,6 +3935,7 @@ struct ast_custom_function *ast_custom_function_find(const char *name)
|
|||
int ast_custom_function_unregister(struct ast_custom_function *acf)
|
||||
{
|
||||
struct ast_custom_function *cur;
|
||||
struct ast_custom_escalating_function *cur_escalation;
|
||||
|
||||
if (!acf) {
|
||||
return -1;
|
||||
|
@ -3927,9 +3952,64 @@ int ast_custom_function_unregister(struct ast_custom_function *acf)
|
|||
}
|
||||
AST_RWLIST_UNLOCK(&acf_root);
|
||||
|
||||
/* Remove from the escalation list */
|
||||
AST_RWLIST_WRLOCK(&escalation_root);
|
||||
AST_RWLIST_TRAVERSE_SAFE_BEGIN(&escalation_root, cur_escalation, list) {
|
||||
if (cur_escalation->acf == acf) {
|
||||
AST_RWLIST_REMOVE_CURRENT(list);
|
||||
break;
|
||||
}
|
||||
}
|
||||
AST_RWLIST_TRAVERSE_SAFE_END;
|
||||
AST_RWLIST_UNLOCK(&escalation_root);
|
||||
|
||||
return cur ? 0 : -1;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns true if given custom function escalates privileges on read.
|
||||
*
|
||||
* \param acf Custom function to query.
|
||||
* \return True (non-zero) if reads escalate privileges.
|
||||
* \return False (zero) if reads just read.
|
||||
*/
|
||||
static int read_escalates(const struct ast_custom_function *acf) {
|
||||
int res = 0;
|
||||
struct ast_custom_escalating_function *cur_escalation;
|
||||
|
||||
AST_RWLIST_RDLOCK(&escalation_root);
|
||||
AST_RWLIST_TRAVERSE(&escalation_root, cur_escalation, list) {
|
||||
if (cur_escalation->acf == acf) {
|
||||
res = cur_escalation->read_escalates;
|
||||
break;
|
||||
}
|
||||
}
|
||||
AST_RWLIST_UNLOCK(&escalation_root);
|
||||
return res;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns true if given custom function escalates privileges on write.
|
||||
*
|
||||
* \param acf Custom function to query.
|
||||
* \return True (non-zero) if writes escalate privileges.
|
||||
* \return False (zero) if writes just write.
|
||||
*/
|
||||
static int write_escalates(const struct ast_custom_function *acf) {
|
||||
int res = 0;
|
||||
struct ast_custom_escalating_function *cur_escalation;
|
||||
|
||||
AST_RWLIST_RDLOCK(&escalation_root);
|
||||
AST_RWLIST_TRAVERSE(&escalation_root, cur_escalation, list) {
|
||||
if (cur_escalation->acf == acf) {
|
||||
res = cur_escalation->write_escalates;
|
||||
break;
|
||||
}
|
||||
}
|
||||
AST_RWLIST_UNLOCK(&escalation_root);
|
||||
return res;
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
* \brief Retrieve the XML documentation of a specified ast_custom_function,
|
||||
* and populate ast_custom_function string fields.
|
||||
|
@ -4031,6 +4111,50 @@ int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_m
|
|||
return 0;
|
||||
}
|
||||
|
||||
int __ast_custom_function_register_escalating(struct ast_custom_function *acf, enum ast_custom_function_escalation escalation, struct ast_module *mod)
|
||||
{
|
||||
struct ast_custom_escalating_function *acf_escalation = NULL;
|
||||
int res;
|
||||
|
||||
res = __ast_custom_function_register(acf, mod);
|
||||
if (res != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (escalation == AST_CFE_NONE) {
|
||||
/* No escalations; no need to do anything else */
|
||||
return 0;
|
||||
}
|
||||
|
||||
acf_escalation = ast_calloc(1, sizeof(*acf_escalation));
|
||||
if (!acf_escalation) {
|
||||
ast_custom_function_unregister(acf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
acf_escalation->acf = acf;
|
||||
switch (escalation) {
|
||||
case AST_CFE_NONE:
|
||||
break;
|
||||
case AST_CFE_READ:
|
||||
acf_escalation->read_escalates = 1;
|
||||
break;
|
||||
case AST_CFE_WRITE:
|
||||
acf_escalation->write_escalates = 1;
|
||||
break;
|
||||
case AST_CFE_BOTH:
|
||||
acf_escalation->read_escalates = 1;
|
||||
acf_escalation->write_escalates = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
AST_RWLIST_WRLOCK(&escalation_root);
|
||||
AST_RWLIST_INSERT_TAIL(&escalation_root, acf_escalation, list);
|
||||
AST_RWLIST_UNLOCK(&escalation_root);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! \brief return a pointer to the arguments of the function,
|
||||
* and terminates the function name with '\\0'
|
||||
*/
|
||||
|
@ -4052,6 +4176,124 @@ static char *func_args(char *function)
|
|||
return args;
|
||||
}
|
||||
|
||||
void pbx_live_dangerously(int new_live_dangerously)
|
||||
{
|
||||
if (new_live_dangerously && !live_dangerously) {
|
||||
ast_log(LOG_WARNING, "Privilege escalation protection disabled!\n"
|
||||
"See https://wiki.asterisk.org/wiki/x/1gKfAQ for more details.\n");
|
||||
}
|
||||
|
||||
if (!new_live_dangerously && live_dangerously) {
|
||||
ast_log(LOG_NOTICE, "Privilege escalation protection enabled.\n");
|
||||
}
|
||||
live_dangerously = new_live_dangerously;
|
||||
}
|
||||
|
||||
int ast_thread_inhibit_escalations(void)
|
||||
{
|
||||
int *thread_inhibit_escalations;
|
||||
|
||||
thread_inhibit_escalations = ast_threadstorage_get(
|
||||
&thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations));
|
||||
|
||||
if (thread_inhibit_escalations == NULL) {
|
||||
ast_log(LOG_ERROR, "Error inhibiting privilege escalations for current thread\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
*thread_inhibit_escalations = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Indicates whether the current thread inhibits the execution of
|
||||
* dangerous functions.
|
||||
*
|
||||
* \return True (non-zero) if dangerous function execution is inhibited.
|
||||
* \return False (zero) if dangerous function execution is allowed.
|
||||
*/
|
||||
static int thread_inhibits_escalations(void)
|
||||
{
|
||||
int *thread_inhibit_escalations;
|
||||
|
||||
thread_inhibit_escalations = ast_threadstorage_get(
|
||||
&thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations));
|
||||
|
||||
if (thread_inhibit_escalations == NULL) {
|
||||
ast_log(LOG_ERROR, "Error checking thread's ability to run dangerous functions\n");
|
||||
/* On error, assume that we are inhibiting */
|
||||
return 1;
|
||||
}
|
||||
|
||||
return *thread_inhibit_escalations;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Determines whether execution of a custom function's read function
|
||||
* is allowed.
|
||||
*
|
||||
* \param acfptr Custom function to check
|
||||
* \return True (non-zero) if reading is allowed.
|
||||
* \return False (zero) if reading is not allowed.
|
||||
*/
|
||||
static int is_read_allowed(struct ast_custom_function *acfptr)
|
||||
{
|
||||
if (!acfptr) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!read_escalates(acfptr)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!thread_inhibits_escalations()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (live_dangerously) {
|
||||
/* Global setting overrides the thread's preference */
|
||||
ast_debug(2, "Reading %s from a dangerous context\n",
|
||||
acfptr->name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* We have no reason to allow this function to execute */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Determines whether execution of a custom function's write function
|
||||
* is allowed.
|
||||
*
|
||||
* \param acfptr Custom function to check
|
||||
* \return True (non-zero) if writing is allowed.
|
||||
* \return False (zero) if writing is not allowed.
|
||||
*/
|
||||
static int is_write_allowed(struct ast_custom_function *acfptr)
|
||||
{
|
||||
if (!acfptr) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!write_escalates(acfptr)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!thread_inhibits_escalations()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (live_dangerously) {
|
||||
/* Global setting overrides the thread's preference */
|
||||
ast_debug(2, "Writing %s from a dangerous context\n",
|
||||
acfptr->name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* We have no reason to allow this function to execute */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ast_func_read(struct ast_channel *chan, const char *function, char *workspace, size_t len)
|
||||
{
|
||||
char *copy = ast_strdupa(function);
|
||||
|
@ -4064,6 +4306,8 @@ int ast_func_read(struct ast_channel *chan, const char *function, char *workspac
|
|||
ast_log(LOG_ERROR, "Function %s not registered\n", copy);
|
||||
} else if (!acfptr->read && !acfptr->read2) {
|
||||
ast_log(LOG_ERROR, "Function %s cannot be read\n", copy);
|
||||
} else if (!is_read_allowed(acfptr)) {
|
||||
ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy);
|
||||
} else if (acfptr->read) {
|
||||
if (acfptr->mod) {
|
||||
u = __ast_module_user_add(acfptr->mod, chan);
|
||||
|
@ -4101,6 +4345,8 @@ int ast_func_read2(struct ast_channel *chan, const char *function, struct ast_st
|
|||
ast_log(LOG_ERROR, "Function %s not registered\n", copy);
|
||||
} else if (!acfptr->read && !acfptr->read2) {
|
||||
ast_log(LOG_ERROR, "Function %s cannot be read\n", copy);
|
||||
} else if (!is_read_allowed(acfptr)) {
|
||||
ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy);
|
||||
} else {
|
||||
if (acfptr->mod) {
|
||||
u = __ast_module_user_add(acfptr->mod, chan);
|
||||
|
@ -4140,11 +4386,13 @@ int ast_func_write(struct ast_channel *chan, const char *function, const char *v
|
|||
char *args = func_args(copy);
|
||||
struct ast_custom_function *acfptr = ast_custom_function_find(copy);
|
||||
|
||||
if (acfptr == NULL)
|
||||
if (acfptr == NULL) {
|
||||
ast_log(LOG_ERROR, "Function %s not registered\n", copy);
|
||||
else if (!acfptr->write)
|
||||
} else if (!acfptr->write) {
|
||||
ast_log(LOG_ERROR, "Function %s cannot be written to\n", copy);
|
||||
else {
|
||||
} else if (!is_write_allowed(acfptr)) {
|
||||
ast_log(LOG_ERROR, "Dangerous function %s write blocked\n", copy);
|
||||
} else {
|
||||
int res;
|
||||
struct ast_module_user *u = NULL;
|
||||
if (acfptr->mod)
|
||||
|
|
|
@ -48,6 +48,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||
#include "asterisk/options.h"
|
||||
#include "asterisk/manager.h"
|
||||
#include "asterisk/astobj2.h"
|
||||
#include "asterisk/pbx.h"
|
||||
|
||||
/*! \brief
|
||||
* replacement read/write functions for SSL support.
|
||||
|
@ -164,6 +165,16 @@ static void *handle_tcptls_connection(void *data)
|
|||
char err[256];
|
||||
#endif
|
||||
|
||||
/* TCP/TLS connections are associated with external protocols, and
|
||||
* should not be allowed to execute 'dangerous' functions. This may
|
||||
* need to be pushed down into the individual protocol handlers, but
|
||||
* this seems like a good general policy.
|
||||
*/
|
||||
if (ast_thread_inhibit_escalations()) {
|
||||
ast_log(LOG_ERROR, "Failed to inhibit privilege escalations; killing connection\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* open a FILE * as appropriate.
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue