【问题标题】:Obfuscating the SSL client Hello v2 message in Java在 Java 中混淆 SSL 客户端 Hello v2 消息
【发布时间】:2011-10-08 00:36:30
【问题描述】:

我目前正在开发一个客户端/服务器 TLS 工具,该工具要求我们通过防火墙进行连接。 由于我们无法控制的原因,我们只被授予传出 TCP 连接。

问题在于我们客户端的防火墙阻止了客户端 Hello v2 消息(可能还有整个 SSL 握手)。

有没有办法以某种方式混淆流? 我正在考虑尝试使用压缩来使防火墙无法读取流。 (也许使用 JDK7 的 GzipOutputStream 现在允许同步刷新)

我不是 SSL 专家,但在我看来应该可以翻译整个流,这应该使防火墙无法获取连接并阻止它。

据我所知,有几种(两种?)方法可以解决这个问题:

  • 覆盖默认实现
  • 实现 SSLServerSocketFactory

第一个选项对我来说不起作用,因为我找不到 com.sun.net.ssl.internal.ssl.SSLServerSocketFactoryImpl 的源代码,这是默认实现。 我确实浏览了它的 openJDK 源代码,但即使在那里,源似乎也丢失了。

实现 SSLServerSocketFactory 超出了我的能力范围。正如我所说,我不是 SSL 专家。

请注意,该应用程序确实可以通过其他不那么激进的防火墙/防火墙规则正常工作。

【问题讨论】:

  • 你不能把 ssl 套接字放在另一个套接字中吗?外部套接字将负责压缩,SSL 套接字将通过它进行通信
  • 它是否专门阻止 SSLv2 而不是 SSLv3 或 TLS 1.x?如果是这样,为什么不升级到 SSLv3(或更好的 TLS 1.x),反正 SSLv2 是不安全的。
  • @Bruno 除非他使用 IBM JSSE,否则他不会使用 SSLv2,只是使用 ClientHandshake 消息进行升级。 Sun JSSE 不再支持 SSLv2。
  • 我从未听说过仅阻止 SSLv2 的防火墙。要做到这一点,防火墙必须解析 SSL 客户端 hello。
  • 感谢大家分享您的见解。我正在使用 TLSv1。我相信 Bruno 是对的,Sun 的实现只对 clientHandshake 使用 SSLv2 规范。

标签: java ssl client-server obfuscation jsse


【解决方案1】:

看来解决方案是将布鲁诺的建议和保罗的解决方案结合起来。

Paulo 的解决方案允许我们使用委托自定义 SSLSocket 或 SSLServerSocket 的行为。

Bruno 的建议允许我们告诉默认 SSL 实现使用我们修改后的 SSLSocket 或 SSLServerSocket。

这就是我所做的:

  • 创建委托 ServerSocket 类 (MyServerSocket)
  • 创建委托 ServerSocketFactory 类 (MyServerSocketFactory)
  • 创建委托 SocketFactory 类 (MySocketFactory)
  • 创建一个委托 Socket 类 (MySocket)
  • 创建 XorInputStream(找到它here
  • 创建 XorOutputStream(找到它here

在服务器端:

    // Initialisation as usual
    ...
    sslSocketFactory = sslContext.getSocketFactory();

    serverSocketFactory = ServerSocketFactory.getDefault();
    serverSocketFactory = new MyServerSocketFactory(serverSocketFactory);
    serverSocket = serverSocketFactory.createServerSocket(port);
    ...
    Socket s = (Socket) serverSocket.accept();
    sslSocket = (SSLSocket) sslSocketFactory.createSocket(s, null, s.getPort(), false);
    sslSocket.setUseClientMode(false);
    sslSocket.setEnabledCipherSuites(new String[]{"SSL_RSA_WITH_RC4_128_MD5"});
    sslSocket.setNeedClientAuth(true);
    ...

在客户端:

    Socket s = new MySocketFactory(SocketFactory.getDefault()).createSocket(host, port);
    SSLSocket socket = (SSLSocket) factory.createSocket(s, host, port, false);

来源



    public class MyServerSocket extends ServerSocket {
    private ServerSocket baseSocket;

    public MyServerSocket(ServerSocket baseSocket) throws IOException {
        this.baseSocket = baseSocket;
    }

    @Override
    public Socket accept() throws IOException {
        return new MySocket(baseSocket.accept());
    }

    @Override
    public void bind(SocketAddress endpoint) throws IOException {
        baseSocket.bind(endpoint);
    }

    @Override
    public void bind(SocketAddress endpoint, int backlog) throws IOException {
        baseSocket.bind(endpoint, backlog);
    }

    @Override
    public void close() throws IOException {
        baseSocket.close();
    }

    @Override
    public ServerSocketChannel getChannel() {
        return baseSocket.getChannel();
    }

    @Override
    public InetAddress getInetAddress() {
        return baseSocket.getInetAddress();
    }

    @Override
    public int getLocalPort() {
        return baseSocket.getLocalPort();
    }

    @Override
    public SocketAddress getLocalSocketAddress() {
        return baseSocket.getLocalSocketAddress();
    }

    @Override
    public synchronized int getReceiveBufferSize() throws SocketException {
        return baseSocket.getReceiveBufferSize();
    }

    @Override
    public boolean getReuseAddress() throws SocketException {
        return baseSocket.getReuseAddress();
    }

    @Override
    public synchronized int getSoTimeout() throws IOException {
        return baseSocket.getSoTimeout();
    }

    @Override
    public boolean isBound() {
        return baseSocket.isBound();
    }

    @Override
    public boolean isClosed() {
        return baseSocket.isClosed();
    }

    @Override
    public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
        baseSocket.setPerformancePreferences(connectionTime, latency, bandwidth);
    }

    @Override
    public synchronized void setReceiveBufferSize(int size) throws SocketException {
        baseSocket.setReceiveBufferSize(size);
    }

    @Override
    public void setReuseAddress(boolean on) throws SocketException {
        baseSocket.setReuseAddress(on);
    }

    @Override
    public synchronized void setSoTimeout(int timeout) throws SocketException {
        baseSocket.setSoTimeout(timeout);
    }

    @Override
    public String toString() {
        return baseSocket.toString();
    }


    }


    public class MyServerSocketFactory extends ServerSocketFactory {

    private ServerSocketFactory baseFactory;

    public MyServerSocketFactory(ServerSocketFactory baseFactory) {
        this.baseFactory = baseFactory;
    }


    @Override
    public ServerSocket createServerSocket(int i) throws IOException {
        return new MyServerSocket(baseFactory.createServerSocket(i));
    }

    @Override
    public ServerSocket createServerSocket(int i, int i1) throws IOException {
        return new MyServerSocket(baseFactory.createServerSocket(i, i1));
    }

    @Override
    public ServerSocket createServerSocket(int i, int i1, InetAddress ia) throws IOException {
        return new MyServerSocket(baseFactory.createServerSocket(i, i1, ia));
    }


    }


    public class MySocket extends Socket {
    private Socket baseSocket;

    public MySocket(Socket baseSocket) {
        this.baseSocket = baseSocket;
    }

    private XorInputStream xorInputStream = null;
    private XorOutputStream xorOutputStream = null;
    private final byte pattern = (byte)0xAC;

    @Override
    public InputStream getInputStream() throws IOException {
        if (xorInputStream == null)
        {
            xorInputStream = new XorInputStream(baseSocket.getInputStream(), pattern);
        }
        return xorInputStream;
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        if (xorOutputStream == null)
        {
            xorOutputStream = new XorOutputStream(baseSocket.getOutputStream(), pattern);
        }
        return xorOutputStream;
    }

    @Override
    public void bind(SocketAddress bindpoint) throws IOException {
        baseSocket.bind(bindpoint);
    }

    @Override
    public synchronized void close() throws IOException {
        baseSocket.close();
    }

    @Override
    public void connect(SocketAddress endpoint) throws IOException {
        baseSocket.connect(endpoint);
    }

    @Override
    public void connect(SocketAddress endpoint, int timeout) throws IOException {
        baseSocket.connect(endpoint, timeout);
    }

    @Override
    public SocketChannel getChannel() {
        return baseSocket.getChannel();
    }

    @Override
    public InetAddress getInetAddress() {
        return baseSocket.getInetAddress();
    }

    @Override
    public boolean getKeepAlive() throws SocketException {
        return baseSocket.getKeepAlive();
    }

    @Override
    public InetAddress getLocalAddress() {
        return baseSocket.getLocalAddress();
    }

    @Override
    public int getLocalPort() {
        return baseSocket.getLocalPort();
    }

    @Override
    public SocketAddress getLocalSocketAddress() {
        return baseSocket.getLocalSocketAddress();
    }

    @Override
    public boolean getOOBInline() throws SocketException {
        return baseSocket.getOOBInline();
    }

    @Override
    public int getPort() {
        return baseSocket.getPort();
    }

    @Override
    public synchronized int getReceiveBufferSize() throws SocketException {
        return baseSocket.getReceiveBufferSize();
    }

    @Override
    public SocketAddress getRemoteSocketAddress() {
        return baseSocket.getRemoteSocketAddress();
    }

    @Override
    public boolean getReuseAddress() throws SocketException {
        return baseSocket.getReuseAddress();
    }

    @Override
    public synchronized int getSendBufferSize() throws SocketException {
        return baseSocket.getSendBufferSize();
    }

    @Override
    public int getSoLinger() throws SocketException {
        return baseSocket.getSoLinger();
    }

    @Override
    public synchronized int getSoTimeout() throws SocketException {
        return baseSocket.getSoTimeout();
    }

    @Override
    public boolean getTcpNoDelay() throws SocketException {
        return baseSocket.getTcpNoDelay();
    }

    @Override
    public int getTrafficClass() throws SocketException {
        return baseSocket.getTrafficClass();
    }

    @Override
    public boolean isBound() {
        return baseSocket.isBound();
    }

    @Override
    public boolean isClosed() {
        return baseSocket.isClosed();
    }

    @Override
    public boolean isConnected() {
        return baseSocket.isConnected();
    }

    @Override
    public boolean isInputShutdown() {
        return baseSocket.isInputShutdown();
    }

    @Override
    public boolean isOutputShutdown() {
        return baseSocket.isOutputShutdown();
    }

    @Override
    public void sendUrgentData(int data) throws IOException {
        baseSocket.sendUrgentData(data);
    }

    @Override
    public void setKeepAlive(boolean on) throws SocketException {
        baseSocket.setKeepAlive(on);
    }

    @Override
    public void setOOBInline(boolean on) throws SocketException {
        baseSocket.setOOBInline(on);
    }

    @Override
    public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
        baseSocket.setPerformancePreferences(connectionTime, latency, bandwidth);
    }

    @Override
    public synchronized void setReceiveBufferSize(int size) throws SocketException {
        baseSocket.setReceiveBufferSize(size);
    }

    @Override
    public void setReuseAddress(boolean on) throws SocketException {
        baseSocket.setReuseAddress(on);
    }

    @Override
    public synchronized void setSendBufferSize(int size) throws SocketException {
        baseSocket.setSendBufferSize(size);
    }

    @Override
    public void setSoLinger(boolean on, int linger) throws SocketException {
        baseSocket.setSoLinger(on, linger);
    }

    @Override
    public synchronized void setSoTimeout(int timeout) throws SocketException {
        baseSocket.setSoTimeout(timeout);
    }

    @Override
    public void setTcpNoDelay(boolean on) throws SocketException {
        baseSocket.setTcpNoDelay(on);
    }

    @Override
    public void setTrafficClass(int tc) throws SocketException {
        baseSocket.setTrafficClass(tc);
    }

    @Override
    public void shutdownInput() throws IOException {
        baseSocket.shutdownInput();
    }

    @Override
    public void shutdownOutput() throws IOException {
        baseSocket.shutdownOutput();
    }

    @Override
    public String toString() {
        return baseSocket.toString();
    }



    }

    public class MySocketFactory extends SocketFactory {

    private SocketFactory baseFactory;


    public MySocketFactory(SocketFactory baseFactory) {
        this.baseFactory = baseFactory;
    }


    @Override
    public Socket createSocket() throws IOException {
        return baseFactory.createSocket();
    }


    @Override
    public boolean equals(Object obj) {
        return baseFactory.equals(obj);
    }



    @Override
    public int hashCode() {
        return baseFactory.hashCode();
    }

    @Override
    public String toString() {
        return baseFactory.toString();
    }



    @Override
    public Socket createSocket(String string, int i) throws IOException, UnknownHostException {
        return new MySocket(baseFactory.createSocket(string, i));
    }

    @Override
    public Socket createSocket(String string, int i, InetAddress ia, int i1) throws IOException, UnknownHostException {
        return baseFactory.createSocket(string, i, ia, i1);
    }

    @Override
    public Socket createSocket(InetAddress ia, int i) throws IOException {
        return baseFactory.createSocket(ia, i);
    }

    @Override
    public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1) throws IOException {
        return baseFactory.createSocket(ia, i, ia1, i1);
    }

    }

【讨论】:

  • 不幸的是,防火墙似乎不允许传输 ascii 范围之外的任何数据...所以我将不得不再次与客户的网络管理员交谈...跨度>
  • 您仍然可以通过 HTTP base64 编码流进行隧道传输——它将在 ascii 范围内:) 或者可能不使用 XOR 使用 base64
  • Base64 确实可以解决问题。我没有告诉你的是,由于某些超出此问题范围的原因,防火墙被配置为允许 FTP 连接通过。更多测试表明,它不允许我发送二进制数据(正如我之前发布的那样),如果后面没有换行符,它也不会发送 ascii 数据。如果我要模拟 FTP 传输,我可能会发送二进制数据,但这太过分了。无论如何,我的问题确实得到了解决,所以这是题外话。谢谢大家的时间。
  • 防火墙(在本例中为 WatchGuard 防火墙)使用代理 FTP 服务器强制流量在标记为“FTP”的任何规则上采用 FTP 协议的形式。最后,我们不得不要求客户端在防火墙上打开一个传出的“TCP”连接规则。它也应该可以模拟 FTP 连接。
【解决方案2】:

可能有点跑题了,但如果问题是关于 SSLv2,并且 SSLv3 或 TLS 1.x 握手工作正常,您可以使用 SSLSocket.setEnabledProtocols(new String[] { "SSLv3", "TLSv1", "TLSv1.1" }) 禁用 V2 ClientHello

请参阅JSSE Reference guide (section on SSLContext)

编辑:对于那些不阅读 cmets 的人,这里是指向 @EJP 答案的链接,其中包含有关此主题的更多详细信息:Why does Java's SSLSocket send a version 2 client hello?

【讨论】:

  • 这里还有其他信息:stackoverflow.com/questions/4682957/…
  • +1 这是正确的解决方案。 Sun Java 实际上并不支持 SSLv2,仅支持客户端握手部分,因此无论如何防火墙的“帮助”在这种情况下毫无意义。出于某种原因,IBM 的 JSSE 确实支持 SSLv2。
  • 感谢您的回答。这似乎是设置我的 SSL 连接的最佳方式。然而,如果防火墙确实在解析 clientHello v2 握手,那么防火墙是否有机会获取 clientHello v3(如果甚至有这样的事情?)
  • 我使用 -Djavax.net.debug=all 启用了调试。仅启用 SSLv3 和 TLSv1 协议后,我可以看到它使用 TLSv1 握手。仅一两秒后连接断开(服务器:SSLHandshakeException,客户端:EOFException)。当我使用 SSLv2 客户端握手时,数据永远不会到达服务器端,导致超时。在我看来,这意味着防火墙也在接受 TLSv1 握手。不过这是一个很好的建议。
【解决方案3】:

无需额外开发即可通过 HTTP 建立 TCP 隧道。有各种各样的工具。看GNU httptunnel。 httptunnel 在 HTTP 请求中创建一个双向虚拟数据连接。如果需要,可以通过 HTTP 代理发送 HTTP 请求。Httpc 也很有趣。

【讨论】:

  • 这种隧道不会导致显着的开销吗?它肯定会增加依赖关系,这意味着更多的维护、停机时间......此外,我正在与之对抗的那些防火墙仍有可能在 http 隧道中获取 clientHellov2(我假设这些是明文)。
  • 是的,它可能是开销,但我不认为它会矫枉过正。如果它在 http 隧道中捕获 clientHellov2,您可以简单地添加 xor 或 base64 编码。这很简单,因为这些工具是开源的。检查下面布鲁诺斯的解决方案。尝试一下很好,但恕我直言,防火墙会捕获它。
【解决方案4】:

压缩加密流没有用处,您实际上只需要一些掩码来避开防火墙。

在客户端,您可以使用SSLSocketFactory 的方法createSocket(socket, host, port, autoclose) 创建基于另一个套接字的 SSL 套接字——而这个另一个套接字可以得到您特殊的 SocketImpl 实现,在首先是一些字节。

在服务器端,它更复杂,因为 SSLServerSocketFactory 没有这种方法。

answer to Java RMI + SSL + Compression = IMPOSSIBLE! 中,我描述了如何构建一个委托 Socket 工厂。那里是为 Rmi(Client|Server)SocketFactory 完成的,但它会以类似的方式为 ServerSocketFactory 或 SocketFactory 工作。


当然,您的防火墙可能实际上并没有阻止 SSL 流量,而是阻止了未列入白名单的任何内容(如 HTTP)。在构建包装套接字实现之前,请尝试发送一些随机数据并接收它们的简单套接字+服务器套接字是否有效。

【讨论】:

  • 感谢您的回答,我会更深入地研究您之前的回答(我记得读过那个帖子)。很抱歉,我忘了说明文 TCP 通信是有效的。
  • @Paŭlo Ebermann:您也可以在服务器端执行此操作。 createSocket(socket, host, port, autoclose) 不特定于客户端,您仍然可以使用setUseClientMode(false) 将服务器端转换后的套接字配置为服务器套接字。见stackoverflow.com/questions/6559859/…
  • @Bruno:好主意,这将有助于一切。
  • @Charles:见布鲁诺的评论。
  • 谢谢两位,我成功应用了布鲁诺的建议。然后我继续应用 Paŭlo 的解决方案。我终于能够影响底层的输入和输出流了!我想我最终会撤销布鲁诺的建议:它确实使事情更简洁/更短,但我发现它使代码更难阅读(而没有它我有更多的覆盖,但逻辑更容易理解)。完成后我会用一些代码示例进行报告,以便其他人可以从中受益。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-04
  • 1970-01-01
  • 1970-01-01
  • 2023-04-04
  • 2017-01-18
  • 1970-01-01
相关资源
最近更新 更多