[653] in linux-security and linux-alert archive

home help back first fref pref prev next nref lref last post

[linux-security] Vulnerabilities in mgetty+sendfax (root access by fax)

daemon@ATHENA.MIT.EDU (Zygo Blaxell)
Tue Apr 2 18:10:09 1996

From: Zygo Blaxell <zblaxell@myrus.com>
To: linux-security@tarsier.cv.nrao.edu, best-of-security@suburbia.net,
        gert@greenie.muc.de, gert.doering@physik.tu-muenchen.de
Date: Tue, 2 Apr 1996 13:11:29 -0500 (EST)

(sorry about sending this to Gert twice; I found two email addresses in
the README files)

This is probably of special interest to the Linux community, as at least
RedHat has mgetty in their contrib section.  mgetty has been around for
a while, and judging from the mailing list traffic it is in use at a
significant number of sites.

Program:  mgetty+sendfax
Version:  all those that support FAX_NOTIFY_PROGRAM
Platform: all

Vulnerability 1:  mgetty does not properly parse input from remote fax
                  modem

Impact:   if the FAX_NOTIFY_PROGRAM feature is used, and fax reception
          is allowed, anyone who can send a fax to a machine running
          mgetty can execute up to 17 bytes of shell command as root on
          that machine.  This can be escalated to full-blown root access
          if the attacker has a shell account on the receiving machine.
          It does not matter what the FAX_NOTIFY_PROGRAM does; the
          vulnerability can be exploited as long as mgetty has support
          for such a program.

Workaround:  disable fax reception.  Recompile mgetty with
             'FAX_NOTIFY_PROGRAM' undefined in policy.h.  mgetty 0.98
             is distributed with this macro defined by default.

Exploit:  Call a machine running mgetty+sendfax and send it a fax, with
          the fax modem's local ID string set to
                  ';17-byte-command;
          The 17-byte-command will be executed using /bin/sh with root
          privileges.

Fix:      in faxlib.c, function fax_wait_for(), add code to remove
          characters not in the set {alphanumeric, dot, dash, plus}
          from string 'fax_remote_id', and enforce the limit of 20
          characters.

Discussion:

RTFM, and you'll find the bug.  Start with:

http://www.leo.org/~doering/mgetty/mgetty_19.html#SEC19:

>If you define FAX_NOTIFY_PROGRAM in `policy.h', mgetty will call this
>program (or shell script) when a fax has been completely received. It
>will be called with the following command line arguments:
>
>FAX_NOTIFY_PROGRAM <hangup code> '<sender id>' <nr of pages> \
>                   <file name page 1> <file name page 2> ...
>
><hangup code> is 0 if the receive was successful, non-zero otherwise.
><sender id> is the fax identification string received from the other
>side. <file name page (i)> is the full path name for each received page.
>
>A sample command line might look like this: 
>
>/usr/local/bin/new_fax 0 "+49 89 3243328" 1 /var/spool/fax/ff-01.a123

If this specification is naively implemented, it should be possible to
inject a command into mgetty by storing it between "';" and ";" in the
sending fax modem's ID string.  A quick check of the source code reveals
that this is the case.  Actually, the sample command line really looks
like:

   /usr/local/bin/new_fax 0 '+49 89 3243328' 1 /var/spool/fax/ff-01.a123

There are other instances in the source code where the incorrect form
(with " characters) is used in comments, including 'policy.h-dist'.

>From http://www.nb.rockwell.com/ref/1048PR3a.html:

>6.5.4 +FLID=, Local ID String 
>Write syntax: +FLID="<local ID string>"
>Valid value: 20-character ASCII string
>Default value: Empty
>
>If FLID is not a null string, it generates a TSI or CSI frame. Table
>3/T.30 includes digits 0-9, "+" and space.
>
>If the DCE supports use of Table 3/T.30 only, the response to a +FLID=?
>command is "(20) (32, 43, 48-57)." If the DCE supports printable ASCII
><, the response is: "(20) (32-127)<CRLF>." The first "(20)" represents
>string length: the second (character values) field reports supported
>string values.
>
>1. The string is saved in RAM. 
>2. Non-numeric characters are not filtered out.
>3. The string is right justified. 

As far as I can tell, this specifies the allowed format of the +FTSI and
related commands for setting and reading fax modem ID strings.  Note
that any printable ASCII data can be sent, not just numbers.  This is
even mentioned elsewhere in the mgetty manual.

The function fax_get_line is replaced by mdm_get_line() in mgetty
version 0.99, but it is otherwise the same as in 0.98.

The remote TSI is read in faxlib.c in fax_get_line(), into a 1000-byte
buffer:

[do {... -zb]
[get a byte from fax modem in 'c' -zb]
>        if ( isprint( c ) &&
>             bufferp < sizeof(buffer) )
>        {
>            buffer[ bufferp++ ] = c;
>        }
[} while... -zb]

The set of characters for which isprint() is true includes most of the
shell metacharacters.

Now, in faxrec.c, in fax_notify_program(), we have:

>char *  line;
...
>    line = malloc( fax_fn_size + sizeof( FAX_NOTIFY_PROGRAM) + 100 );
...
>    sprintf( line, "%s %d '%s' %d %s >%s 2>&1 </dev/null",
>                                         FAX_NOTIFY_PROGRAM,
>                                         fax_hangup_code,
>                                         fax_remote_id,
>                                         pagenum,
>                                         fax_file_names,
>                                         CONSOLE);
            [ouch, only 100 bytes for a string that could be 1000! -zb]
...
>            r = system( line );

Note that the Rockwell specs only allow 20 characters for fax ID.
Certainly if the fax protocol and modem firmware allow more than 20
characters, then there is a buffer-overrun attack possible against
mgetty.  It might be possible to do this by violating the fax protocol
and exploiting a naive modem on the receiving end.  However, you don't
need any of this to get root with mgetty, as mgetty conveniently invokes
system() for an attacker with data supplied by that attacker as root.

Impact:  

If a perfectly valid and harmless fax ID such as "Joe's Diner" is used,
the fax notification part of mgetty will break as a side-effect of this
bug (due to mismatched ' characters in system()).

Whatever command can fit in 17 ASCII bytes (20 minus the "';" and ";"
necessary for the resulting string to be parsed by the shell) with
values between 32 and 126 can be executed as root on the receiving
machine.  This is good for remote denial-of-service attacks
(strlen("/bin/rm -rf /")<17), but less useful for appending entries to
/etc/passwd and such, as the commands probably require more than 17
bytes of shell code.  

stdin/stdout/stderr are not available; they are closed in
fax_notify_program() before the system() call.  The modem is not in data
or fax receive mode at this time, so an attacker would not be able to
communicate with the command as it ran unless the command took control
of the modem.  

If the attacker has an account on the machine and write access to a
directory with a short name, then the attacker can pre-arrange for a
program or shell script to be present and invoke it as root by
exploiting this bug.

Recommended Fixes:  

Fix 1.  Disallow the character ' in fax_remote_id.  (Actually,
it probably doesn't hurt to disallow anything but {alphanumerics, dot,
dash, plus} and convert everything else to underscore or ignore it.
Explicitly limit the length of fax_remote_id to 20 characters.

Fix 2.  Replace the system() call with an execlp(), thus
preventing a shell from becoming involved and also avoiding the
potential buffer-overrun bug.  Explicitly limit the length of
fax_remote_id to 20 characters.  Also make a note in the documentation
about this sort of problem, as it might rear its ugly head again if
someone uses a naive shell script as FAX_NOTIFY_PROGRAM.
A variation is to put the ID in an environment variable.
This is better design but incompatible with existing practice.

Fix 3.  All of the above.  Extra paranoia never hurt anyone.

Interestingly enough, I found the following code in faxrec.c, in
fax_get_page_data(), while looking for existing code that might plug
this hole:

>    /* filter out characters that will give problems in the shell */
>    for ( j=0; fax_remote_id[j] != 0; j++ )
>    {
>        char c = fax_remote_id[j];
>         if ( c == ' ' || c == '/' || c == '\\'|| c == '&' ||
>              c == '(' || c == ')' || c == '>' || c == '<' )
>         {
>             if ( temp[i-1] != '-' ) temp[i++] = '-' ;
>         }
>         else if ( c != '"' && c != '\'' ) temp[i++] = c;
>    }
>    if ( temp[i-1] == '-' ) i--;
>    sprintf( &temp[i], ".%02d", pagenum );

This prevents incoming fax filenames from having common obnoxious
characters in them.  However, the list is incomplete; notably absent are
characters like ` and |.   The code also doesn't modify fax_remote_id,
which would have quite effectively plugged this hole.


-- 
Zygo Blaxell, Network admin, Linux system support, Windows '95 moral support
Myrus Design Inc.                       Tel: +1 613 233 2339
Suite 203, 275 Bank St.                 93 Glebe Avenue
Ottawa, Ontario, Canada K2C 1E3         Ottawa, Ontario, Canada K1S 2C2
-- 
Zygo Blaxell.  Former Unix/soft/hardware guru, U of Waterloo Computer Science 
Club.  Current sysadmin for Myrus Design, Inc.  10th place, ACM Intl Collegiate
Programming Contest Finals, 1994.  Administer Linux nets for food, clothing, 
and anime.  "I gave up $1000 to avoid working on windoze... *sigh*" - Amy Fong

home help back first fref pref prev next nref lref last post