CVE-2014-6271
CVE-2014-6271 è stata la prima vulnerabilità scoperta. Una patch può essere trovata qui .
Da Wikipedia :
Function definitions are exported by encoding them within the
environment variable list as variables whose values begin with
parentheses ("()") followed by a function definition. The new instance
of Bash, upon starting, scans its environment variable list for values
in this format and converts them back into internal functions.
Bash performs this conversion by creating a fragment of code that
defines the function and executing it, but it does not verify that the
fragment is merely a function definition. Therefore anyone who can
cause Bash to execute with a particular name/value pair in its
environment, can also execute arbitrary commands by appending those
commands to an exported function definition.
Nel codice sorgente, possiamo vedere l'importazione delle variabili di funzione in variables.c
:
/* Initialize the shell variables from the current environment.
If PRIVMODE is nonzero, don't import functions from ENV or
parse $SHELLOPTS. */
void
initialize_shell_variables (env, privmode)
char **env;
int privmode;
{
[...]
for (string_index = 0; string = env[string_index++]; )
{
[...]
/* If exported function, define it now. Don't import functions from
the environment in privileged mode. */
if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4))
{
[...]
parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);
[...]
}
}
Possiamo vedere un ciclo for su tutte le variabili di ambiente assegnate alla funzione, e poi un se per sapere se siamo in modalità privilegiata, ma che è disabilitato il più delle volte.
La parte "non verificare che il frammento sia solo una definizione di funzione" si trova nella riga parse_and_execute
. La descrizione della funzione da builtins/evalstring.c
:
/* Parse and execute the commands in STRING. Returns whatever
execute_command () returns. This frees STRING. FLAGS is a
flags word; look in common.h for the possible values. Actions
are:
(flags & SEVAL_NONINT) -> interactive = 0;
(flags & SEVAL_INTERACT) -> interactive = 1;
(flags & SEVAL_NOHIST) -> call bash_history_disable ()
(flags & SEVAL_NOFREE) -> don't free STRING when finished
(flags & SEVAL_RESETLINE) -> reset line_number to 1
*/
int
parse_and_execute (string, from_file, flags)
char *string;
const char *from_file;
int flags;
{
Quindi tutto ciò che viene passato alla funzione viene eseguito come se fosse un normale comando bash. I flag SEVAL_NONINT
e SEVAL_NOHIST
sono autoesplicativi ( spiegazione dell'interattività , NOHIST
non aggiunge la definizione alla cronologia di bash) non impediscono il passaggio di altre cose oltre alle definizioni di funzione. La patch introduce i flag SEVAL_FUNCDEF
e SEVAL_ONECMD
che possono essere passati nel campo flags a parse_and_execute
:
+ #define SEVAL_FUNCDEF 0x080 /* only allow function definitions */
+ #define SEVAL_ONECMD 0x100 /* only allow a single command */
La patch aggiunge anche funzionalità a parse_and_execute
per rispettare questi nuovi flag e modifica la chiamata a parse_and_execute
per passare quei flag:
- parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);
+ /* Don't import function names that are invalid identifiers from the
+ environment. */
+ if (legal_identifier (name))
+ parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST|SEVAL_FUNCDEF|SEVAL_ONECMD);
CVE-2.014-7.169
CVE-2014-7169 si basa su un problema di analisi delle funzioni che è stato evidenziato da Tavis Ormandy. La correzione di parse.y
sembra molto semplice, ma è più complicata di CVE-2014-6271:
/* Called from shell.c when Control-C is typed at top level. Or
by the error rule at top level. */
void
reset_parser ()
[...]
FREE (word_desc_to_read);
word_desc_to_read = (WORD_DESC *)NULL;
+ eol_ungetc_lookahead = 0;
+
current_token = '\n'; /* XXX */
last_read_token = '\n';
token_to_read = '\n';
La variabile eol_ungetc_lookahead
è spiegata alla sua definizione:
/* This implements one-character lookahead/lookbehind across physical input
lines, to avoid something being lost because it's pushed back with
shell_ungetc when we're at the start of a line. */
static int eol_ungetc_lookahead = 0;
È letto all'interno della funzione shell_getc
, e se è impostato, il suo contenuto (un carattere) viene invece letto.
Il comando rm echo; env -i X='() { function a .>\' bash -c 'echo date'; cat echo
crea prima un errore di sintassi con il carattere .
(qui puoi usare anche altri caratteri, come a
o =
), e poi usa la pulizia insufficiente della variabile eol_ungetc_lookahead
nella funzione reset_parser
per iniettare il carattere >
nella stringa 'echo date' che è anche data a bash. È equivalente a rm echo; bash -c '> echo date'; cat echo
.
Altre risorse sulla mailing list di oss-sec .