3

我目前正在通过蛮力学习 ssh/只是继续破解,直到我理解它的方法。经过反复试验,我已经能够成功发送“pty-req”,然后是“shell”请求,我可以获得登录前导码,发送命令并接收标准输出,但我不确定如何告诉SSH 服务 我想接收标准错误和状态消息。目前,通读其他 SSH 实现(paramiko、Net::SSH)并没有太多指导意义。

也就是说,查看 SSH 的 RFC 之一,我相信列出的请求之一可能是我正在寻找的:https ://www.rfc-editor.org/rfc/rfc4250#section-4.9.3

#!/usr/bin/env python


from twisted.conch.ssh import transport
from twisted.conch.ssh import userauth
from twisted.conch.ssh import connection
from twisted.conch.ssh import common
from twisted.conch.ssh.common import NS
from twisted.conch.ssh import keys
from twisted.conch.ssh import channel
from twisted.conch.ssh import session
from twisted.internet import defer

from twisted.internet import defer, protocol, reactor
from twisted.python import log
import struct, sys, getpass, os
log.startLogging(sys.stdout)


USER = 'dward'  
HOST = '192.168.0.19' # pristine.local
PASSWD = "password"
PRIVATE_KEY = "~/id_rsa"

class SimpleTransport(transport.SSHClientTransport):
    def verifyHostKey(self, hostKey, fingerprint):
        print 'host key fingerprint: %s' % fingerprint
        return defer.succeed(1) 

    def connectionSecure(self):
        self.requestService(
            SimpleUserAuth(USER,
                SimpleConnection()))

class SimpleUserAuth(userauth.SSHUserAuthClient):
    def getPassword(self):
        return defer.succeed(PASSWD)

    def getGenericAnswers(self, name, instruction, questions):
        print name
        print instruction
        answers = []
        for prompt, echo in questions:
            if echo:
                answer = raw_input(prompt)
            else:
                answer = getpass.getpass(prompt)
            answers.append(answer)
        return defer.succeed(answers)
            
    def getPublicKey(self):
        path = os.path.expanduser(PRIVATE_KEY) 
        # this works with rsa too
        # just change the name here and in getPrivateKey
        if not os.path.exists(path) or self.lastPublicKey:
            # the file doesn't exist, or we've tried a public key
            return
        return keys.Key.fromFile(filename=path+'.pub').blob()

    def getPrivateKey(self):
        path = os.path.expanduser(PRIVATE_KEY)
        return defer.succeed(keys.Key.fromFile(path).keyObject)
        
    
    
class SimpleConnection(connection.SSHConnection):
    def serviceStarted(self):
        self.openChannel(SmartChannel(2**16, 2**15, self))        




class SmartChannel(channel.SSHChannel):
    name = "session"
    
    
    def getResponse(self, timeout = 10):
        self.onData = defer.Deferred()
        self.timeout = reactor.callLater( timeout, self.onData.errback, Exception("Timeout") )
        return self.onData
    
    def openFailed(self, reason):
        print "Failed", reason
        
    @defer.inlineCallbacks    
    def channelOpen(self, ignoredData):
        self.data = ''
        self.oldData = ''
        self.onData = None
        self.timeout = None
        term = os.environ.get('TERM', 'xterm')
        #winsz = fcntl.ioctl(fd, tty.TIOCGWINSZ, '12345678')
        winSize = (25,80,0,0) #struct.unpack('4H', winsz)
        ptyReqData = session.packRequest_pty_req(term, winSize, '')
        
        try:
            result = yield self.conn.sendRequest(self, 'pty-req', ptyReqData, wantReply = 1 )
        except Exception as e:
            print "Failed with ", e
        
        try:
            result = yield self.conn.sendRequest(self, "shell", '', wantReply = 1)
        except Exception as e:
            print "Failed shell with ", e
        
        
        #fetch preample    
        data = yield self.getResponse()
        """
        Welcome to Ubuntu 11.04 (GNU/Linux 2.6.38-8-server x86_64)

            * Documentation:  http://www.ubuntu.com/server/doc
           
             System information as of Sat Oct 29 13:09:50 MDT 2011
           
             System load:  0.0               Processes:           111
             Usage of /:   48.0% of 6.62GB   Users logged in:     1
             Memory usage: 39%               IP address for eth1: 192.168.0.19
             Swap usage:   3%
           
             Graph this data and manage this system at https://landscape.canonical.com/
           New release 'oneiric' available.
           Run 'do-release-upgrade' to upgrade to it.
           
           Last login: Sat Oct 29 01:23:16 2011 from 192.168.0.17
        """
        print data
        while data != "" and data.strip().endswith("~$") == False:
            try:
                data = yield self.getResponse()
                print repr(data)
                """
                \x1B]0;dward@pristine: ~\x07dward@pristine:~$ 
                """
            except Exception as e:
                print e
                break
                
        self.write("false\n")
        #fetch response
        try:
            data = yield self.getResponse()
        except Exception as e:
            print "Failed to catch response?", e
        else:
            print data
            """
                false
                \x1B]0;dward@pristine: ~\x07dward@pristine:~$ 
            """
            
        self.write("true\n")
        #fetch response
        try:
            data = yield self.getResponse()
        except Exception as e:
            print "Failed to catch response?", e
        else:
            print data
            """
            true
            \x1B]0;dward@pristine: ~\x07dward@pristine:~$ 
            """
        
        self.write("echo Hello World\n\x00")
        try:
            data = yield self.getResponse()
        except Exception as e:
            print "Failed to catch response?", e
        else:            
            print data
            """
            echo Hello World
            Hello World
            \x1B]0;dward@pristine: ~\x07dward@pristine:~$ 
            """
        
        #Close up shop
        self.loseConnection()
        dbgp = 1
        
    
    def request_exit_status(self, data):
        status = struct.unpack('>L', data)[0]
        print 'status was: %s' % status    
    
    def dataReceived(self, data):
        self.data += data
        if self.onData is not None:
            if self.timeout and self.timeout.active():
                self.timeout.cancel()
            if self.onData.called == False:                
                self.onData.callback(data)
    
    def extReceived(self, dataType, data):
        dbgp = 1
        print "Extended Data recieved! dataType = %s , data = %s " % ( dataType, data, )
        self.extendData = data

    def closed(self):
        print 'got data : %s' % self.data.replace("\\r\\n","\r\n")
        self.loseConnection()
        reactor.stop()
        
    

protocol.ClientCreator(reactor, SimpleTransport).connectTCP(HOST, 22)
reactor.run()

此外,我尝试向远程 shell 添加显式错误命令:

    self.write("ls -alF badPathHere\n\x00")
    try:
        data = yield self.getResponse()
    except Exception as e:
        print "Failed to catch response?", e
    else:            
        print data
        """
        ls -alF badPathHere
        ls: cannot access badPathHere: No such file or directory
        \x1B]0;dward@pristine: ~\x07dward@pristine:~$ 
        """

看起来 stderr 正在混入 stderr

4

1 回答 1

0

深入挖掘 OpenSSH 的源代码,通道会话逻辑在session.c中的第 2227 行函数 -> session_input_channel_req 中处理,如果给定 pty-req 则“shell”请求会导致 do_exec_pty 最终导致调用 session_set_fds(s , ptyfd, fdout, -1, 1, 1)。第四个参数通常是负责处理 stderr 的文件描述符,但由于没有提供任何 stderr 的扩展数据。

Ultimately, even if I modified openssh to provide a stderr FD, the problem resides with the shell. Complete guess work at this point but I believe that similar to logging into a ssh service via a terminal like xterm or putty, that stderr and stdout are sent together unless explicitly redirected via something like "2> someFile" which is beyond the scope of a SSH service provider.

于 2011-10-30T00:15:17.880 回答