diff --git a/README-SERIOUSLY.bestpractices.txt b/README-SERIOUSLY.bestpractices.txt index b6b418d9fae290e1e5960ef2b29ad6e0ad53b642..0d3e670cf24deffe9575817591f088432e24ec1e 100644 --- a/README-SERIOUSLY.bestpractices.txt +++ b/README-SERIOUSLY.bestpractices.txt @@ -94,6 +94,13 @@ your ITSP in a place where you didn't expect to allow it. There are a couple of ways in which you can mitigate this impact: stricter pattern matching, or using the FILTER() dialplan function. +The CALLERID(num) and CALLERID(name) values are other commonly used values that +are sources of data potentially supplied by outside sources. If you use these +values as parameters to the System(), MixMonitor(), or Monitor() applications +or the SHELL() dialplan function, you can allow injection of arbitrary operating +system command execution. The FILTER() dialplan function is available to remove +dangerous characters from untrusted strings to block the command injection. + Strict Pattern Matching ----------------------- diff --git a/apps/app_minivm.c b/apps/app_minivm.c index 15449ad4eac5bac9395ed60fe9a8ceaf2ed8a621..0d7a5f4077ff3eaff243f51905cadf4f3f20f3a1 100644 --- a/apps/app_minivm.c +++ b/apps/app_minivm.c @@ -1774,21 +1774,35 @@ static int play_record_review(struct ast_channel *chan, char *playfile, char *re /*! \brief Run external notification for voicemail message */ static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu) { - char arguments[BUFSIZ]; + char fquser[AST_MAX_CONTEXT * 2]; + char *argv[5] = { NULL }; + struct ast_party_caller *caller; + char *cid; + int idx; - if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify)) + if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify)) { return; + } + + snprintf(fquser, sizeof(fquser), "%s@%s", vmu->username, vmu->domain); - snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&", - ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify, - vmu->username, vmu->domain, - (ast_channel_caller(chan)->id.name.valid && ast_channel_caller(chan)->id.name.str) - ? ast_channel_caller(chan)->id.name.str : "", - (ast_channel_caller(chan)->id.number.valid && ast_channel_caller(chan)->id.number.str) - ? ast_channel_caller(chan)->id.number.str : ""); + caller = ast_channel_caller(chan); + idx = 0; + argv[idx++] = ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify; + argv[idx++] = fquser; + cid = S_COR(caller->id.name.valid, caller->id.name.str, NULL); + if (cid) { + argv[idx++] = cid; + } + cid = S_COR(caller->id.number.valid, caller->id.number.str, NULL); + if (cid) { + argv[idx++] = cid; + } + argv[idx] = NULL; - ast_debug(1, "Executing: %s\n", arguments); - ast_safe_system(arguments); + ast_debug(1, "Executing: %s %s %s %s\n", + argv[0], argv[1], argv[2] ?: "", argv[3] ?: ""); + ast_safe_execvp(1, argv[0], argv); } /*!\internal diff --git a/apps/app_mixmonitor.c b/apps/app_mixmonitor.c index 3258b301fa09d78339cc5019f3ee05ac519e9e34..ac45642052a8f69a1f72b80b65f6f90ec7d75aec 100644 --- a/apps/app_mixmonitor.c +++ b/apps/app_mixmonitor.c @@ -136,6 +136,11 @@ <para>Will be executed when the recording is over.</para> <para>Any strings matching <literal>^{X}</literal> will be unescaped to <variable>X</variable>.</para> <para>All variables will be evaluated at the time MixMonitor is called.</para> + <warning><para>Do not use untrusted strings such as <variable>CALLERID(num)</variable> + or <variable>CALLERID(name)</variable> as part of the command parameters. You + risk a command injection attack executing arbitrary commands if the untrusted + strings aren't filtered to remove dangerous characters. See function + <variable>FILTER()</variable>.</para></warning> </parameter> </syntax> <description> @@ -148,6 +153,11 @@ <para>Will contain the filename used to record.</para> </variable> </variablelist> + <warning><para>Do not use untrusted strings such as <variable>CALLERID(num)</variable> + or <variable>CALLERID(name)</variable> as part of ANY of the application's + parameters. You risk a command injection attack executing arbitrary commands + if the untrusted strings aren't filtered to remove dangerous characters. See + function <variable>FILTER()</variable>.</para></warning> </description> <see-also> <ref type="application">Monitor</ref> @@ -222,6 +232,11 @@ <para>Will be executed when the recording is over. Any strings matching <literal>^{X}</literal> will be unescaped to <variable>X</variable>. All variables will be evaluated at the time MixMonitor is called.</para> + <warning><para>Do not use untrusted strings such as <variable>CALLERID(num)</variable> + or <variable>CALLERID(name)</variable> as part of the command parameters. You + risk a command injection attack executing arbitrary commands if the untrusted + strings aren't filtered to remove dangerous characters. See function + <variable>FILTER()</variable>.</para></warning> </parameter> </syntax> <description> diff --git a/apps/app_system.c b/apps/app_system.c index 09179f7f7076352972ca44be73912cfccee63e2d..64d529798c1bcedae561f1f5ff943b326a9b71bf 100644 --- a/apps/app_system.c +++ b/apps/app_system.c @@ -46,6 +46,11 @@ <syntax> <parameter name="command" required="true"> <para>Command to execute</para> + <warning><para>Do not use untrusted strings such as <variable>CALLERID(num)</variable> + or <variable>CALLERID(name)</variable> as part of the command parameters. You + risk a command injection attack executing arbitrary commands if the untrusted + strings aren't filtered to remove dangerous characters. See function + <variable>FILTER()</variable>.</para></warning> </parameter> </syntax> <description> @@ -71,6 +76,11 @@ <syntax> <parameter name="command" required="true"> <para>Command to execute</para> + <warning><para>Do not use untrusted strings such as <variable>CALLERID(num)</variable> + or <variable>CALLERID(name)</variable> as part of the command parameters. You + risk a command injection attack executing arbitrary commands if the untrusted + strings aren't filtered to remove dangerous characters. See function + <variable>FILTER()</variable>.</para></warning> </parameter> </syntax> <description> diff --git a/configs/samples/minivm.conf.sample b/configs/samples/minivm.conf.sample index 2df3449d13f64915ddfac0505c89fc0d08f30b0c..79fdbb0e2ce952b02245bc86ea0201a671b3d6a3 100644 --- a/configs/samples/minivm.conf.sample +++ b/configs/samples/minivm.conf.sample @@ -51,7 +51,7 @@ silencethreshold=128 ; If you need to have an external program, i.e. /usr/bin/myapp called when a ; voicemail is received by the server. The arguments are ; -; <app> <username@domain> <callerid-number> <callerid-name> +; <app> <username@domain> <callerid-name> <callerid-number> ; ;externnotify=/usr/bin/myapp ; The character set for voicemail messages can be specified here diff --git a/funcs/func_shell.c b/funcs/func_shell.c index 0398cd83960d55321324a9a0834dfdd0d1e6abb5..fe1debe8848466d15990353dedc4095110659216 100644 --- a/funcs/func_shell.c +++ b/funcs/func_shell.c @@ -82,6 +82,11 @@ static int shell_helper(struct ast_channel *chan, const char *cmd, char *data, <syntax> <parameter name="command" required="true"> <para>The command that the shell should execute.</para> + <warning><para>Do not use untrusted strings such as <variable>CALLERID(num)</variable> + or <variable>CALLERID(name)</variable> as part of the command parameters. You + risk a command injection attack executing arbitrary commands if the untrusted + strings aren't filtered to remove dangerous characters. See function + <variable>FILTER()</variable>.</para></warning> </parameter> </syntax> <description> diff --git a/include/asterisk/app.h b/include/asterisk/app.h index d86b63338f2fc40f776e2927253a71a8ae292f77..0505a6b987dad9786b2d336b66a984ebc2babf6c 100644 --- a/include/asterisk/app.h +++ b/include/asterisk/app.h @@ -871,9 +871,34 @@ int ast_vm_test_destroy_user(const char *context, const char *mailbox); int ast_vm_test_create_user(const char *context, const char *mailbox); #endif -/*! \brief Safely spawn an external program while closing file descriptors - \note This replaces the \b system call in all Asterisk modules -*/ +/*! + * \brief Safely spawn an external program while closing file descriptors + * + * \note This replaces the \b execvp call in all Asterisk modules + * + * \param dualfork Non-zero to simulate running the program in the + * background by forking twice. The option provides similar + * functionality to the '&' in the OS shell command "cmd &". The + * option allows Asterisk to run a reaper loop to watch the first fork + * which immediately exits after spaning the second fork. The actual + * program is run in the second fork. + * \param file execvp(file, argv) file parameter + * \param argv execvp(file, argv) argv parameter + */ +int ast_safe_execvp(int dualfork, const char *file, char *const argv[]); + +/*! + * \brief Safely spawn an OS shell command while closing file descriptors + * + * \note This replaces the \b system call in all Asterisk modules + * + * \param s - OS shell command string to execute. + * + * \warning Command injection can happen using this call if the passed + * in string is created using untrusted data from an external source. + * It is best not to use untrusted data. However, the caller could + * filter out dangerous characters to avoid command injection. + */ int ast_safe_system(const char *s); /*! diff --git a/main/asterisk.c b/main/asterisk.c index 0a131fda96462910f4d51bd6d94eb06affef7ef8..d555949839a20ddcfe121fb9f4f2856d8fea9520 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -1170,11 +1170,10 @@ void ast_unreplace_sigchld(void) ast_mutex_unlock(&safe_system_lock); } -int ast_safe_system(const char *s) +/*! \brief fork and perform other preparations for spawning applications */ +static pid_t safe_exec_prep(int dualfork) { pid_t pid; - int res; - int status; #if defined(HAVE_WORKING_FORK) || defined(HAVE_WORKING_VFORK) ast_replace_sigchld(); @@ -1196,35 +1195,101 @@ int ast_safe_system(const char *s) cap_free(cap); #endif #ifdef HAVE_WORKING_FORK - if (ast_opt_high_priority) + if (ast_opt_high_priority) { ast_set_priority(0); + } /* Close file descriptors and launch system command */ ast_close_fds_above_n(STDERR_FILENO); #endif - execl("/bin/sh", "/bin/sh", "-c", s, (char *) NULL); - _exit(1); - } else if (pid > 0) { + if (dualfork) { +#ifdef HAVE_WORKING_FORK + pid = fork(); +#else + pid = vfork(); +#endif + if (pid < 0) { + /* Second fork failed. */ + /* No logger available. */ + _exit(1); + } + + if (pid > 0) { + /* This is the first fork, exit so the reaper finishes right away. */ + _exit(0); + } + + /* This is the second fork. The first fork will exit immediately so + * Asterisk doesn't have to wait for completion. + * ast_safe_system("cmd &") would run in the background, but the '&' + * cannot be added with ast_safe_execvp, so we have to double fork. + */ + } + } + + if (pid < 0) { + ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno)); + } +#else + ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(ENOTSUP)); + pid = -1; +#endif + + return pid; +} + +/*! \brief wait for spawned application to complete and unreplace sigchld */ +static int safe_exec_wait(pid_t pid) +{ + int res = -1; + +#if defined(HAVE_WORKING_FORK) || defined(HAVE_WORKING_VFORK) + if (pid > 0) { for (;;) { + int status; + res = waitpid(pid, &status, 0); if (res > -1) { res = WIFEXITED(status) ? WEXITSTATUS(status) : -1; break; - } else if (errno != EINTR) + } + if (errno != EINTR) { break; + } } - } else { - ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno)); - res = -1; } ast_unreplace_sigchld(); -#else /* !defined(HAVE_WORKING_FORK) && !defined(HAVE_WORKING_VFORK) */ - res = -1; #endif return res; } +int ast_safe_execvp(int dualfork, const char *file, char *const argv[]) +{ + pid_t pid = safe_exec_prep(dualfork); + + if (pid == 0) { + execvp(file, argv); + _exit(1); + /* noreturn from _exit */ + } + + return safe_exec_wait(pid); +} + +int ast_safe_system(const char *s) +{ + pid_t pid = safe_exec_prep(0); + + if (pid == 0) { + execl("/bin/sh", "/bin/sh", "-c", s, (char *) NULL); + _exit(1); + /* noreturn from _exit */ + } + + return safe_exec_wait(pid); +} + /*! * \brief enable or disable a logging level to a specified console */ diff --git a/res/res_monitor.c b/res/res_monitor.c index fd3ff7a1c9cafd6d296b02ff9930503c82c7b5fc..3e3611b3615ff1fc80ac14ae61dcd89b2c5337bd 100644 --- a/res/res_monitor.c +++ b/res/res_monitor.c @@ -59,17 +59,17 @@ <syntax> <parameter name="file_format" argsep=":"> <argument name="file_format" required="true"> - <para>optional, if not set, defaults to <literal>wav</literal></para> + <para>Optional. If not set, defaults to <literal>wav</literal></para> </argument> <argument name="urlbase" /> </parameter> <parameter name="fname_base"> - <para>if set, changes the filename used to the one specified.</para> + <para>If set, changes the filename used to the one specified.</para> </parameter> <parameter name="options"> <optionlist> <option name="m"> - <para>when the recording ends mix the two leg files into one and + <para>When the recording ends mix the two leg files into one and delete the two leg files. If the variable <variable>MONITOR_EXEC</variable> is set, the application referenced in it will be executed instead of soxmix/sox and the raw leg files will NOT be deleted automatically. @@ -80,6 +80,13 @@ will be passed on as additional arguments to <variable>MONITOR_EXEC</variable>. Both <variable>MONITOR_EXEC</variable> and the Mix flag can be set from the administrator interface.</para> + <warning><para>Do not use untrusted strings such as + <variable>CALLERID(num)</variable> or <variable>CALLERID(name)</variable> + as part of <variable>MONITOR_EXEC</variable> or + <variable>MONITOR_EXEC_ARGS</variable>. You risk a command injection + attack executing arbitrary commands if the untrusted strings aren't + filtered to remove dangerous characters. See function + <variable>FILTER()</variable>.</para></warning> </option> <option name="b"> <para>Don't begin recording unless a call is bridged to another channel.</para>