通过 JSch 建立 SSH 隧道

Posted

技术标签:

【中文标题】通过 JSch 建立 SSH 隧道【英文标题】:SSH tunneling via JSch 【发布时间】:2015-05-05 04:32:47 【问题描述】:

我的目标是连接到防火墙后面的服务器(主机)。我可以通过连接到网络中的另一台服务器(隧道)然后通过 SSH 连接到该服务器来访问该服务器。但是我无法通过 JSch 实现相同的场景。

我无法使用我为此目的编写的以下代码。如果我在这里做任何愚蠢的事情,请告诉我。

public class JschExecutor 

    public static void main(String[] args)
        JschExecutor t=new JschExecutor();
        try
            t.go();
         catch(Exception ex)
            ex.printStackTrace();
        
    
    public void go() throws Exception

        StringBuilder outputBuffer = new StringBuilder();

        String host="xxx.xxx.xxx.xxx"; // The host to be connected finally
        String user="user";
        String password="passwrd";
        int port=22;

        String tunnelRemoteHost="xx.xx.xx.xx"; // The host from where the tunnel is created

        JSch jsch=new JSch();
        Session session=jsch.getSession(user, host, port);
        session.setPassword(password);
        localUserInfo lui=new localUserInfo();
        session.setUserInfo(lui);
        session.setConfig("StrictHostKeyChecking", "no");

        ProxySOCKS5 proxyTunnel = new ProxySOCKS5(tunnelRemoteHost, 22);
        proxyTunnel.setUserPasswd(user, password);
        session.setProxy(proxyTunnel);

        session.connect(30000);

        Channel channel=session.openChannel("exec");
        ((ChannelExec)channel).setCommand("hostname");

        channel.setInputStream(null);
        ((ChannelExec)channel).setErrStream(System.err);

        InputStream in=channel.getInputStream();
        BufferedReader ebr = new BufferedReader(new InputStreamReader(in));

        channel.connect();

        while (true) 
            byte[] tmpArray=new byte[1024];
            while(in.available()>0)
                int i=in.read(tmpArray, 0, 1024);
                if(i<0)break;
                outputBuffer.append(new String(tmpArray, 0, i)).append("\n");
             
            if(channel.isClosed())
                System.out.println("exit-status: "+channel.getExitStatus());
                break;
             
        
        ebr.close();

        channel.disconnect();

        session.disconnect();

        System.out.println(outputBuffer.toString());
    

  class localUserInfo implements UserInfo
    String passwd;
    public String getPassword() return passwd; 
    public boolean promptYesNo(String str)return true;
    public String getPassphrase() return null; 
    public boolean promptPassphrase(String message)return true; 
    public boolean promptPassword(String message)return true;
    public void showMessage(String message)
       

 

上面的代码在session.connect(30000); 行给出了以下异常。

com.jcraft.jsch.JSchException: ProxySOCKS5: com.jcraft.jsch.JSchException: fail in SOCKS5 proxy
    at com.jcraft.jsch.ProxySOCKS5.connect(ProxySOCKS5.java:317)
    at com.jcraft.jsch.Session.connect(Session.java:231)
    at com.ukris.main.JschExecutor.go(JschExecutor.java:50)
    at com.ukris.main.JschExecutor.main(JschExecutor.java:19)
Caused by: com.jcraft.jsch.JSchException: fail in SOCKS5 proxy
    at com.jcraft.jsch.ProxySOCKS5.connect(ProxySOCKS5.java:200)
    ... 3 more

【问题讨论】:

那么你在哪里使用ProxySOCKS5连接? 我希望 'tunnelRemoteHost' 服务器充当连接到 'host' 服务器的代理。 当然。但是tunnelRemoteHost 上在听什么?你是如何设置隧道的? @MartinPrikryl:我对socks5 的整个逻辑都是错误的:)。请参阅下面的答案以推断我希望做什么。 【参考方案1】:

jsch 上的SOCKS 代理设置允许您连接到远程端的正在运行的 代理服务器。远程端的 sshd被视为 SOCKS 代理。您需要做的是建立一个本地端口转发到您要通过隧道连接的机器上的 ssh 端口,然后使用 api 建立到该系统的辅助 ssh 连接。

我采用了您的示例并稍微重写了它以完成此操作:

import com.jcraft.jsch.*;
import java.io.*;

public class JschExecutor2 

    public static void main(String[] args)
        JschExecutor2 t=new JschExecutor2();
        try
            t.go();
         catch(Exception ex)
            ex.printStackTrace();
        
    

    public void go() throws Exception

        StringBuilder outputBuffer = new StringBuilder();

        String host="firstsystem"; // First level target
        String user="username";
        String password="firstlevelpassword";
        String tunnelRemoteHost="secondlevelhost"; // The host of the second target
        String secondPassword="targetsystempassword";
        int port=22;


        JSch jsch=new JSch();
        Session session=jsch.getSession(user, host, port);
        session.setPassword(password);
        localUserInfo lui=new localUserInfo();
        session.setUserInfo(lui);
        session.setConfig("StrictHostKeyChecking", "no");
        // create port from 2233 on local system to port 22 on tunnelRemoteHost
        session.setPortForwardingL(2233, tunnelRemoteHost, 22);
        session.connect();
        session.openChannel("direct-tcpip");

        // create a session connected to port 2233 on the local host.
        Session secondSession = jsch.getSession(user, "localhost", 2233);
        secondSession.setPassword(secondPassword);
        secondSession.setUserInfo(lui);
        secondSession.setConfig("StrictHostKeyChecking", "no");

        secondSession.connect(); // now we're connected to the secondary system
        Channel channel=secondSession.openChannel("exec");
        ((ChannelExec)channel).setCommand("hostname");

        channel.setInputStream(null);

        InputStream stdout=channel.getInputStream();

        channel.connect();

        while (true) 
            byte[] tmpArray=new byte[1024];
            while(stdout.available() > 0)
                int i=stdout.read(tmpArray, 0, 1024);
                if(i<0)break;
                outputBuffer.append(new String(tmpArray, 0, i));
             
            if(channel.isClosed())
                System.out.println("exit-status: "+channel.getExitStatus());
                break;
             
        
        stdout.close();

        channel.disconnect();

        secondSession.disconnect();
        session.disconnect();

        System.out.print(outputBuffer.toString());
    

  class localUserInfo implements UserInfo
    String passwd;
    public String getPassword() return passwd; 
    public boolean promptYesNo(String str)return true;
    public String getPassphrase() return null; 
    public boolean promptPassphrase(String message)return true; 
    public boolean promptPassword(String message)return true;
    public void showMessage(String message)
  

 

此代码所做的是创建一个本地端口转发到目标系统上的 ssh 端口,然后通过它进行连接。 hostname 命令的运行说明它确实在转发到的系统上运行。

【讨论】:

谢谢Petesh ..这就像一个魅力。感谢您对SOCKS 部分的澄清。一个查询,session.openChannel("direct-tcpip"),你有一个 JSch 会话的所有可用频道的列表吗? 这些都列在openChannel 文档中。 @Petesh 如果我理解正确的话,这与 Putty 中的隧道设置所做的事情差不多吗?当它运行时,我可以将 Firefox 的 SOCKS 代理指向端口 2233,它会将其用作 SOCKS 代理? 是的,这与 putty 中的“隧道”设置相同。在这种情况下,它会生成一个本地端口,该端口镜像远程端的端口,允许您连接到它。如果您连接的远程端口是 SOCKS 代理服务器的端口,那么您可以将 firefox 的设置指向该本地端口作为 SOCKS 代理。它不会以任何方式“SOCKSify”端口,它只是将本地镜像添加到远程端口。 @FedericoTaschin 我不知道你从哪里得到异常来尝试调试它。第一个连接建立从本地端口到远程系统的隧道。如果连接后立即“消失”,那么这将导致第二次调用时来自外部主机异常的关闭连接;但我只是提出一种方法。您可以使用命令行 ssh ssh -L 2233:tunnelRemoteHost:22 hostssh -p 2233 localhost 从命令行对其进行测试,看看它在哪里崩溃。【参考方案2】:

这是经过测试并且工作正常。这就像安全管道一样,最适合挖隧道

        String strSshUser = "ssh_user_name"; // SSH loging username
        String strSshPassword = "abcd1234"; // SSH login password
        String strSshHost = "your.ssh.hostname.com"; // hostname or ip or
                                                        // SSH server
        int nSshPort = 22; // remote SSH host port number
        String strRemoteHost = "your.database.hostname.com"; // hostname or
                                                                // ip of
                                                                // your
                                                                // database
                                                                // server
        int nLocalPort = 3366; // local port number use to bind SSH tunnel
        int nRemotePort = 3306; // remote port number of your database
        String strDbUser = "db_user_name"; // database loging username
        String strDbPassword = "4321dcba"; // database login password

    final JSch jsch = new JSch();
    Session session = jsch.getSession(strSshUser, strSshHost, 22);
    session.setPassword(strSshPassword);

    final Properties config = new Properties();
    config.put("StrictHostKeyChecking", "no");
    session.setConfig(config);

    session.connect();
    session.setPortForwardingL(nLocalPort, strRemoteHost, nRemotePort);

【讨论】:

以上是关于通过 JSch 建立 SSH 隧道的主要内容,如果未能解决你的问题,请参考以下文章

使用 JSch 创建一个通过 SSH 隧道化的 SOCKS 代理

基于 JSCH (SSH) 和 HTTPS 的反向隧道

使用 JSCH Java 反向 SSH 隧道 [关闭]

android 使用jsch 开启ssh隧道 ssh tunnel

JSch 多个隧道/跳转主机

JSch 多个隧道/跳转主机