正文
(pat->flags == ACL_WRITE_PERMISSION) {
base
= sdscatlen(
base
,
"%W~"
,
3
);
}
else
{
# assert failure
→ serverPanic(
"Invalid key pattern flag detected"
);
}
return
sdscatsds(
base
, pat->pattern);
}
那么我们审计规则处理函数ACLSetSelector,尝试从中寻找漏洞。大致看一下处理逻辑,首先根据规则首字符(op[0])分出基本块,注意到处理%规则符时,会进入一个循环,在此循环中给flags赋值,这里的flags就是sdsCatPatternString索引的对象。
考虑这样一种边界条件,在%之后的规则符是~,这样控制流会跳出循环,而flags依旧是初始值0,同时函数返回C_OK,指示命令成功执行。
src/acl.c
intACLSetSelector(aclSelector *selector, constchar* op, size_t oplen) {
...
} else if (op[0] == '~' || op[0] == '%') {
if (selector->flags & SELECTOR_FLAG_ALLKEYS) {
errno = EEXIST;
return C_ERR;
}
int flags = 0;
size_t offset = 1;
if (op[0] == '%') {
for (; offset < oplen; offset++) {
if (toupper(op[offset]) == 'R' && !(flags & ACL_READ_PERMISSION)) {
flags |= ACL_READ_PERMISSION;
} else if (toupper(op[offset]) == 'W' && !(flags & ACL_WRITE_PERMISSION)) {
flags |= ACL_WRITE_PERMISSION;
# 跳出循环
→ } else if (op[offset] == '~') {
offset++;
break;
} else {
errno = EINVAL;
return C_ERR;
}
}
} else {
flags = ACL_ALL_PERMISSION;
}
...
} else if (op[0] == '&') {
...
}
...
return C_OK;
}
该漏洞的成因是,处理逻辑没有考虑到边界条件,在未对flags赋值之前就可以跳出循环。
补丁
所以针对该边界条件,补丁对其进行了检测,增加了对flags的非零判断。
src/acl.c
@@ -1051,7 +1051,7 @@ intACLSetSelector(aclSelector *selector, constchar* op, size_t oplen) {
flags |= ACL_READ_PERMISSION;
} else if (toupper(op[offset]) == 'W' && !(flags & ACL_WRITE_PERMISSION)) {
flags |= ACL_WRITE_PERMISSION;
- } else if (op[offset] == '~') {
+ } else if (op[offset] == '~' && flags) {
offset++;
break;
} else {
CVE-2024-51741 断言错误,DoS
披露时间:2025年1月6日
复现版本: 7.4.1
补丁版本: 7.4.2
在分析上一个漏洞成因时,是否发现了ACLSetSelector其中还潜伏着一个漏洞?
另一个漏洞
这次PoC更简单。
ACL SETUSER user %
ACL GETUSER user
回顾ACLSetSelector的处理逻辑,如果我们构造一个只有%的规则,会发生什么?结果是直接跳出for循环,触发panic。
src/acl.c
intACLSetSelector(aclSelector *selector, constchar* op, size_t oplen) {
...
} else if (op[0] == '~' || op[0] == '%') {
if (selector->flags & SELECTOR_FLAG_ALLKEYS) {
errno = EEXIST;
return C_ERR;
}
int flags = 0;
size_t offset = 1;
if (op[0] == '%') {
# 直接跳出循环
→ for (; offset < oplen; offset++) {
if (toupper(op[offset]) == 'R' && !(flags & ACL_READ_PERMISSION)) {
flags |= ACL_READ_PERMISSION;
} else if (toupper(op[offset]) == 'W' && !(flags & ACL_WRITE_PERMISSION)) {
flags |= ACL_WRITE_PERMISSION;
} else if (op[offset] == '~' && flags) {
offset++;
break;
} else {
errno = EINVAL;
return C_ERR;
}
}
} else