Apusic权限绕过浅析

Apusic权限绕过浅析

真的是浅析

前几天去参加补天了,一直想写但是一直抽不出时间学习,由于漏洞比较简单这里也不过多篇幅的讲解,仅分享一些关键的点,在这里关于权限校验Apusic没有使用第三方框架(毕竟是迫真信创产品)

而是使用了自定义实现的安全性约束(关于什么安全性约束百度搜很多文章了不作搬运工)去实现了访问控制

1
2
3
4
5
6
7
8
9
10
11
<security-constraint>
<display-name>test</display-name>
<web-resource-collection>
<web-resource-name>protect</web-resource-name>
<url-pattern>/protect</url-pattern>
<url-pattern>/protect/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>

通过迫真的简单debug过后,发现在com.apusic.web.container.ServletInvocation#preRequest对security-constraint做了检查

image-20231226205248131

跳过垃圾时间(懒),代码如下很简单,在这里调用了checkResourcePermission去做权限的检查

image-20231226205351565

1
2
3
4
5
public boolean checkResourcePermission(HttpServletRequest req) {
WebResourcePermission perm = new WebResourcePermission(req);
Principal principal = Security.getCurrentUser();
return this.checkPermission(perm, principal);
}

在这里,通过javax.security.jacc.WebResourcePermission去实现了权限的检查

首先在初始化WebResourcePermission中,先将request对象经过getUriMinusContextPath处理后传入父类,在这里我们可以看到uri是通过request.getRequestURI()去获取之后去除了上下文路径

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
public WebResourcePermission(HttpServletRequest request) {
super(getUriMinusContextPath(request));
this.urlPatternSpec = new URLPatternSpec(super.getName());
this.methodSpec = HttpMethodSpec.getSpec(request.getMethod());
}

private static String getUriMinusContextPath(HttpServletRequest request) {
String uri = request.getRequestURI();
if (uri != null) {
String contextPath = request.getContextPath();
int contextLength = contextPath == null ? 0 : contextPath.length();
if (contextLength > 0) {
uri = uri.substring(contextLength);
}

if (uri.equals("/")) {
uri = "";
} else {
uri = uri.replaceAll(":", "%3A");
}
} else {
uri = "";
}

return uri;
}

调用父类构造函数其实就是将处理好的uri赋值给name

image-20231226205848624

回到WebResourcePermission的构造函数继续往下看,对上面这个name以及请求方法做了赋值,到此构造函数部分执行完毕

1
2
this.urlPatternSpec = new URLPatternSpec(super.getName());
this.methodSpec = HttpMethodSpec.getSpec(request.getMethod());

接下来就是通过com.apusic.web.container.ConstraintMapper#checkPermission去做权限校验了,最终会走到javax.security.jacc.WebResourcePermission#implies处理

有兴趣的可以看看从checkResourcePermissionimplies:358之间的调用栈

1
2
3
4
5
6
7
8
9
10
implies:358, WebResourcePermission (javax.security.jacc)
implies:525, PermissionsHash (java.security)
implies:182, Permissions (java.security)
implies:48, ApplicationPolicy (com.apusic.security.jacc)
implies:58, ServerPolicy (com.apusic.security.jacc)
checkPermission:272, ConstraintMapper (com.apusic.web.container)
checkResourcePermission:255, ConstraintMapper (com.apusic.web.container)
checkResourcePermission:1565, WebContainer (com.apusic.web.container)
checkSecurityConstraint:310, ServletInvocation (com.apusic.web.container)
preRequest:228, ServletInvocation (com.apusic.web.container)

回到正题,在这里我们可以看到这部分的处理也很简单,一个是对方法的判断,一个是对url的判断,在这里我们看后者即可

image-20231226210514987

继续往下走最终我们可以看到他的比对逻辑,也很简单以/protect/*为例子

  1. pattern是否等于path
  2. patter如果以/开头/*结尾,去除后两位后(/protect)如果长度为0也就是仅为/*则返回true,反之判断path是否为/protect或者以/protect开头
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (pattern.equals(path)) {
return true;
} else {
int slash;
if (pattern.startsWith("/") && pattern.endsWith("/*")) {
pattern = pattern.substring(0, pattern.length() - 2);
slash = pattern.length();
if (slash == 0) {
return true;
} else {
return path.startsWith(pattern) && (path.length() == slash || path.substring(slash).startsWith("/"));
}
} else if (pattern.startsWith("*.")) {
slash = path.lastIndexOf(47);
int period = path.lastIndexOf(46);
return slash >= 0 && period > slash && path.endsWith(pattern.substring(1));
} else {
return pattern.equals(DEFAULT_PATTERN);
}
}

权限绕过构造

在这里我们关注几个细节一个是url的处理是通过request.getRequestURI()处理那么在这里不会做url解码,在这里可以作为一个绕过的点

另一个细节从lib当中我们可以看到spring的版本是远古版本,因此alwaysUseFullPath为false,因此是会做归一化再去做路由匹配

image-20231226212603255

那么结合这两个点我们就可以构造出非常多的绕过姿势,在这里就不列举了

至于RCE的点那就更多了,可自行发现,源码很简单