前言

随便写的,有机会再补充

tomcat 中的规范路由处理

org.apache.catalina.connector.CoyoteAdapter 负责将来自客户端的 HTTP 请求转换为 Tomcat 内部处理所需的请求格式。这包括解析请求头、请求参数等信息

以下为tomcat 8.5

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115

MessageBytes undecodedURI = req.requestURI();//this.uriMB request.getRequestURI()就是获取的这个值
.................
MessageBytes decodedURI = req.decodedURI(); //this.decodedUriMB
if (undecodedURI.getType() == 2) {
decodedURI.duplicate(undecodedURI);
this.parsePathParameters(req, request);//处理;

try {
req.getURLDecoder().convert(decodedURI.getByteChunk(), this.connector.getEncodedSolidusHandlingInternal());
} catch (IOException var22) {
response.sendError(400, "Invalid URI: " + var22.getMessage());
}

if (normalize(req.decodedURI())) {//处理目录



protected void parsePathParameters(Request req, org.apache.catalina.connector.Request request) {
int semicolon = uriBC.indexOf(';', 0);
for(; semicolon > -1; semicolon = uriBC.indexOf(';', semicolon)) {
int start = uriBC.getStart();
int end = uriBC.getEnd();
int pathParamStart = semicolon + 1;
int pathParamEnd = ByteChunk.findBytes(uriBC.getBuffer(), start + pathParamStart, end, new byte[]{59, 47});
String pv = null;
if (pathParamEnd < 0) {
if (charset != null) {
pv = new String(uriBC.getBuffer(), start + pathParamStart, end - start - pathParamStart, charset);
}

uriBC.setEnd(start + semicolon); //只需要记住这里是;后面截断就好了


public static boolean normalize(MessageBytes uriMB) {
ByteChunk uriBC = uriMB.getByteChunk();
byte[] b = uriBC.getBytes();
int start = uriBC.getStart();
int end = uriBC.getEnd();
if (start == end) {
return false;
} else {
int pos = false;
int index = false;
if (b[start] != 47 && b[start] != 92) {// 47-/ 92-\
return false;
} else {
int pos;
//转义\为/
for(pos = start; pos < end; ++pos) {
if (b[pos] == 92) {
if (!ALLOW_BACKSLASH) {
return false;
}
b[pos] = 47;
} else if (b[pos] == 0) {
return false;
}
}
//将//替换为/
for(pos = start; pos < end - 1; ++pos) {
if (b[pos] == 47) {
while(pos + 1 < end && b[pos + 1] == 47) {
copyBytes(b, pos, pos + 1, end - pos - 1); // b 数组中从 start + index 开始的数据复制到同一数组中,copyBytes直接调的System.arraycopy
--end;
}
}
}
// 匹配结尾为/. /.. 转化为/
if (end - start >= 2 && b[end - 1] == 46 && (b[end - 2] == 47 || b[end - 2] == 46 && b[end - 3] == 47)) {
b[end] = 47;
++end;
}

uriBC.setEnd(end); //缩短字符串
int index = 0;

while(true) {
index = uriBC.indexOf("/./", 0, 3, index);//0:查找的起始索引,从字符串的开头开始查找。3:查找的结束索引(不包括该位置),表示最多查找到第 3 个字符。index:继续查找的起始位置,通常是上一次查找的结果或当前处理的上下文,
//循环匹配,有点正则的意思

if (index < 0) {
index = 0;
//循环处理/../
while(true) {
index = uriBC.indexOf("/../", 0, 4, index);
return true;
}

if (index == 0) {
return false;
}

int index2 = -1;

for(pos = start + index - 1; pos >= 0 && index2 < 0; --pos) {
if (b[pos] == 47) {
index2 = pos;
}
}

copyBytes(b, start + index2, start + index + 3, end - start - index - 3);
end = end + index2 - index - 3;
uriBC.setEnd(end);
index = index2;
}
}
//循环处理/./
copyBytes(b, start + index, start + index + 2, end - start - index - 2);
end -= 2;
uriBC.setEnd(end);
}
}
}
}

基于不规范路径的权限绕过

1
2
3
4
//admin
/./admin
/abc/../admin shiro CVE-2010-3863 ,路径匹配前没有规范路径,tomcat调用方法时又规范路由
/api/../abc/admin shiro CVE-2016-6802,只进行了根目录的规范路径

基于二次url解码的权限绕过

1
2
/admin/%3bbypass                            shiro会进行二次解码
/console/images/%252e%252e/console.portal Weblogic未授权访问

基于路径匹配差异的权限绕过

当(spring Boot < 2.3.0.RELEASE)或者当(Spring <5.3.0)时,才会规范路径

1
2
3
4
5
6
/admin/            shiro和SpringBoot 路径匹配的差异, SpringBoot对 URL 后加/也能访问
/admin/;bypass SpringBoot会把;后面的内容去掉,如/1;/..;2/;3/admin,shiro的处理是/1,而SpringBoot匹配的是/1/..//admin
/admin/%3baaa 如果设置/admin/*,shiro是先url解码再规范路径,所以匹配的是/admin/,而springBoot匹配的是/admin/;baaa
/admin/.. 如果设置/admin/**,shiro会把admin后面的内容当成目录操作,处理结果是/,而高版本springBoot不规范路由

/Admin Solon 框架对 url 的匹配默认是 “大小写敏感” 的,当设置caseSensitive(false),存在权限绕过的风险

HttpServletRequest中几个解析URL的函数

Servlet的匹配路径为/test%3F/*,并且Web应用是部署在/app下
getRequestURL() no http://30thh.loc:8480/app/test%3F/a%3F+b;jsessionid=s%3F+ID
getRequestURI() no /app/test%3F/a%3F+b;jsessionid=s%3F+ID
getContextPath() no /app
getServletPath() yes /test?
getPathInfo() yes /a?+b