java - 官方的 Oracle SSLSocketClient.java 演示代码不安全吗?
问题描述
从此链接,给出了 SSLSocketClient.java 的演示:
import java.net.*;
import java.io.*;
import javax.net.ssl.*;
/*
* This example demostrates how to use a SSLSocket as client to
* send a HTTP request and get response from an HTTPS server.
* It assumes that the client is not behind a firewall
*/
public class SSLSocketClient {
public static void main(String[] args) throws Exception {
try {
SSLSocketFactory factory =
(SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket socket =
(SSLSocket)factory.createSocket("www.verisign.com", 443);
/*
* send http request
*
* Before any application data is sent or received, the
* SSL socket will do SSL handshaking first to set up
* the security attributes.
*
* SSL handshaking can be initiated by either flushing data
* down the pipe, or by starting the handshaking by hand.
*
* Handshaking is started manually in this example because
* PrintWriter catches all IOExceptions (including
* SSLExceptions), sets an internal error flag, and then
* returns without rethrowing the exception.
*
* Unfortunately, this means any error messages are lost,
* which caused lots of confusion for others using this
* code. The only way to tell there was an error is to call
* PrintWriter.checkError().
*/
socket.startHandshake();
PrintWriter out = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
socket.getOutputStream())));
out.println("GET / HTTP/1.0");
out.println();
out.flush();
/*
* Make sure there were no surprises
*/
if (out.checkError())
System.out.println(
"SSLSocketClient: java.io.PrintWriter error");
/* read response */
BufferedReader in = new BufferedReader(
new InputStreamReader(
socket.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
in.close();
out.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
我有两个问题:
- 根据这个官方文档,如果我们使用的是原始 SSLSocketFactory 而不是 HttpsURLConnection,则在握手过程中不会强制执行主机名验证。因此,主机名验证应手动完成。
使用原始 SSLSocket 和 SSLEngine 类时,您应该始终在发送任何数据之前检查对等方的凭据。SSLSocket 和 SSLEngine 类不会自动验证 URL 中的主机名是否与对等方凭据中的主机名匹配。如果未验证主机名,则可能会通过 URL 欺骗来利用应用程序。从 JDK 7 开始,可以在 SSL/TLS 握手期间处理端点识别/验证过程。请参阅 SSLParameters.getEndpointIdentificationAlgorithm 方法。
这是否意味着演示不安全?
我看到了在 Java 7 中添加主机名验证的解决方案:
SSLParameters sslParams = new SSLParameters(); sslParams.setEndpointIdentificationAlgorithm("HTTPS"); sslSocket.setSSLParameters(sslParams);
当算法指定为“HTTPS”时,握手将验证主机名。否则(仅使用原始 SSLSockeFactory 算法为空),根本没有调用主机名验证。我很好奇我可以按如下方式修复它:
SSLSocketFactory factory =
(SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket socket =
(SSLSocket)factory.createSocket("www.verisign.com", 443);
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
if(!hv.verify(socket.getSession().getPeerHost(),socket.getSession())){
threw CertificateException("Hostname does not match!")
}
我看到HttpsURLConnection.getDefaultHostnameVerifier()
可以返回一个默认的 HostnameVerifier,我可以用它来做验证吗?我看到很多人在谈论使用自定义 HostnameVerifier。我不明白是否有默认的为什么我们需要自定义它?
解决方案
边界作为答案,但评论时间太长了。
(1) 是的,对于 HTTPS(如您引用的段落之后的段落中所述),这是一个安全漏洞;可能此示例是在 Java 7 之前编写的,此后没有更新。您可以提交错误报告以供他们更新。(当然有一些使用 SSL/TLS 的应用程序不验证主机名,例如 SNMPS 和 LDAPS,甚至没有URL,但仍然可以使用 Java JSSE 实现。)
(2) HTTP 错误或错误:
PrintWriter 使用因平台而异的 JVM 的 lineSeparator,但 HTTP 标准(RFC 2068、2616、7230)要求所有平台上的请求标头都使用 CRLF,尽管一些服务器(可能包括 google)将接受传统 Postel 之后的 just-LF格言“在你发送的东西上要保守,在你收到的东西上要自由”;
读取端假定所有数据都是面向行的,并且不会被规范化 EOL 损坏,这对于 HTTP标头和某些正文(如 text/html)是正确的,当请求没有接受(或接受编码)时,您将从大多数网络服务器获得),但不能保证;
读取端还假设所有数据都可以安全地从 JVM 默认“字符集”解码和重新编码;这对于 HTTP 标头(实际上是 7 位 ASCII)是正确的,但不是很多/大多数主体:特别是处理 8859 或类似 UTF8 会破坏其中的大部分,而将 UTF8 处理为 8859 或 CP1252 会 mojibake 它。
(3) HTTP/1.0 已正式过时,尽管它仍然受到广泛支持,并且演示非常简单,所以我先放一张。
推荐阅读
- c# - 在 .net 标准库中扩展 IdentityUser
- arrays - 数组旋转与获取数组 C 语言的最大值和索引位置
- sql - 在查询 SQL 中提取为逗号分隔值
- python - MS Teams 传出 webhook 与 python bot(bot 框架)身份验证与 HMAC
- javascript - Html 页面内容在滚动时加载
- php - 在 PHP 中使用 if then 和多个条件以及运算符
- assembly - 在汇编程序中调用外部 dll(逆向工程)
- javascript - 根据字符串中的时间进行数组排序
- javascript - 使用 AmCharts.makeChart V3 - 没有获得包括 POK 在内的新印度地图
- python - 使用python网格搜索的高斯过程回归hyparameter优化