zoukankan      html  css  js  c++  java
  • 记一次struts2漏洞修复带来的问题

    struts2作为万年漏洞王,感觉已经被弃如敝屣了,除了一些古老的项目,比如我手上的一个项目,以前每次出现漏洞就如临大敌,手忙脚乱的赶在公司红头文件发出来前修复它。然后改了一两次后毅然决然用别的框架代替它了。

    万事大吉,再也不用担心struts2出漏洞了。然而上个月又爆出了个,还有个还在维护期的古老项目需要修复。。。又要填坑了。

    攻略是将struts2的jar包替换,然后加一些配置就完了。

    修复办法:

    然后带来了一系列问题。

    由于struts包由2.3.?升级到2.5.16,很多依赖包、配置需要响应调整。找了个总结的:

    1,2.5.X版本不再提供xwork.jar ,整合到了 struts-core包中。

    2,方法不能访问的问题,需要在每个action配置文件中加上 strict-method-invocation="false":

    <package name="login" namespace="/login" extends="struts-default" strict-method-invocation="false">
    并修改strut2.xml配置文件头部为2.5版本的:
    <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN" "http://struts.apache.org/dtds/struts-2.5.dtd"><struts>
    

    3 ,session失效的问题,针对weblogic server,增加session-descriptor节点:

    <?xml version="1.0" encoding="UTF-8"?>
    <weblogic-web-app xmlns="http://www.bea.com/ns/weblogic/90">
    <context-root>/ynwjnw</context-root>
    <container-descriptor>
    <servlet-reload-check-secs>-1</servlet-reload-check-secs>
    <prefer-web-inf-classes>true</prefer-web-inf-classes>
    </container-descriptor>
    <session-descriptor>
    <cookie-name>JSESSIONID1</cookie-name>
    </session-descriptor>
    </weblogic-web-app>
    

    4,2.5.16版本jdk要求1.7。1.8版本编译后部署有问题

    web.xml中

    org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
    

    修改为

    org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter 
    

     5.struts2对multipart/form-data解析需要自定义方法解析,否则丢失表单参数。

    <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest"
    		name="requestParser" class="com.***.omp.***.util.MyMultiPartRequest"
    		scope="default"/>
    

    由于xwork做了大改,AbstractMultiPartRequest类的一些属性和方法也修改了。MyMultiPartRequest类也需要相应调整。

    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.UnsupportedEncodingException;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.apache.commons.fileupload.FileItem;
    import org.apache.commons.fileupload.FileUploadBase;
    import org.apache.commons.fileupload.FileUploadException;
    import org.apache.commons.fileupload.RequestContext;
    import org.apache.commons.fileupload.disk.DiskFileItem;
    import org.apache.commons.fileupload.disk.DiskFileItemFactory;
    import org.apache.commons.fileupload.servlet.ServletFileUpload;
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.apache.struts2.dispatcher.LocalizedMessage;
    import org.apache.struts2.dispatcher.multipart.AbstractMultiPartRequest;
    import org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest;
    import org.apache.struts2.dispatcher.multipart.StrutsUploadedFile;
    import org.apache.struts2.dispatcher.multipart.UploadedFile;
    
    
    public class MyMultiPartRequest extends AbstractMultiPartRequest{
    	
    	
    		static final Logger LOG = LogManager.getLogger(JakartaMultiPartRequest.class);
    
    	    // maps parameter name -> List of FileItem objects
    	    protected Map<String, List<FileItem>> files = new HashMap<String, List<FileItem>>();
    
    	    // maps parameter name -> List of param values
    	    protected Map<String, List<String>> params = new HashMap<>();
    
    	    // any errors while processing this request
    	    protected List<LocalizedMessage> errors = new ArrayList<LocalizedMessage>();
    
    //	    protected long maxSize;
    //	    private Locale defaultLocale = Locale.ENGLISH;
    
    
    	    /**
    	     * Creates a new request wrapper to handle multi-part data using methods adapted from Jason Pell's
    	     * multipart classes (see class description).
    	     *
    	     * @param saveDir the directory to save off the file
    	     * @param request the request containing the multipart
    	     * @throws java.io.IOException is thrown if encoding fails.
    	     */
    	    public void parse(HttpServletRequest request, String saveDir) throws IOException {
    	        try {
    	            setLocale(request);
    	            processUpload(request, saveDir);
    	        } catch (FileUploadBase.SizeLimitExceededException e) {
    	            if (LOG.isWarnEnabled()) {
    	                LOG.warn("Request exceeded size limit!", e);
    	            }
    	            LocalizedMessage errorMessage = buildErrorMessage(e, new Object[]{e.getPermittedSize(), e.getActualSize()});
    	            if (!errors.contains(errorMessage)) {
    	                errors.add(errorMessage);
    	            }
    	        } catch (Exception e) {
    	            if (LOG.isWarnEnabled()) {
    	                LOG.warn("Unable to parse request", e);
    	            }
    	            LocalizedMessage errorMessage = buildErrorMessage(e, new Object[]{});
    	            if (!errors.contains(errorMessage)) {
    	                errors.add(errorMessage);
    	            }
    	        }
    	    }
    
    	    protected void setLocale(HttpServletRequest request) {
    	        if (defaultLocale == null) {
    	            defaultLocale = request.getLocale();
    	        }
    	    }
    
    	    protected LocalizedMessage buildErrorMessage(Throwable e, Object[] args) {
    	        String errorKey = "struts.messages.upload.error." + e.getClass().getSimpleName();
    	        LOG.debug("Preparing error message for key: [{}]", errorKey);
    
    	        return new LocalizedMessage(this.getClass(), errorKey, e.getMessage(), args);
    	    }
    
    	    private void processUpload(HttpServletRequest request, String saveDir) throws FileUploadException, UnsupportedEncodingException {
    	        for (FileItem item : parseRequest(request, saveDir)) {
    	            if (LOG.isDebugEnabled()) {
    	                LOG.debug("Found item " + item.getFieldName());
    	            }
    	            if (item.isFormField()) {
    	                processNormalFormField(item, request.getCharacterEncoding());
    	            } else {
    	                processFileField(item);
    	            }
    	        }
    	    }
    
    	    private void processFileField(FileItem item) {
    	        if (LOG.isDebugEnabled()) {
    	            LOG.debug("Item is a file upload");
    	        }
    
    	        // Skip file uploads that don't have a file name - meaning that no file was selected.
    	        if (item.getName() == null || item.getName().trim().length() < 1) {
    	            LOG.debug("No file has been uploaded for the field: " + item.getFieldName());
    	            return;
    	        }
    
    	        List<FileItem> values;
    	        if (files.get(item.getFieldName()) != null) {
    	            values = files.get(item.getFieldName());
    	        } else {
    	            values = new ArrayList<FileItem>();
    	        }
    
    	        values.add(item);
    	        files.put(item.getFieldName(), values);
    	    }
    
    	    private void processNormalFormField(FileItem item, String charset) throws UnsupportedEncodingException {
    	        if (LOG.isDebugEnabled()) {
    	            LOG.debug("Item is a normal form field");
    	        }
    	        List<String> values;
    	        if (params.get(item.getFieldName()) != null) {
    	            values = params.get(item.getFieldName());
    	        } else {
    	            values = new ArrayList<String>();
    	        }
    
    	        // note: see http://jira.opensymphony.com/browse/WW-633
    	        // basically, in some cases the charset may be null, so
    	        // we're just going to try to "other" method (no idea if this
    	        // will work)
    	        if (charset != null) {
    	            values.add(item.getString(charset));
    	        } else {
    	            values.add(item.getString());
    	        }
    	        params.put(item.getFieldName(), values);
    	        item.delete();
    	    }
    
    	    private List<FileItem> parseRequest(HttpServletRequest servletRequest, String saveDir) throws FileUploadException {
    	        DiskFileItemFactory fac = createDiskFileItemFactory(saveDir);
    	        ServletFileUpload upload = new ServletFileUpload(fac);
    	        upload.setSizeMax(maxSize);
    	        // 设置监听器
    	        FileUploadListener progressListener = new FileUploadListener(servletRequest);
    	        upload.setProgressListener(progressListener);
    	        return upload.parseRequest(createRequestContext(servletRequest));
    	    }
    
    	    private DiskFileItemFactory createDiskFileItemFactory(String saveDir) {
    	        DiskFileItemFactory fac = new DiskFileItemFactory();
    	        // Make sure that the data is written to file
    	        fac.setSizeThreshold(0);
    	        if (saveDir != null) {
    	            fac.setRepository(new File(saveDir));
    	        }
    	        return fac;
    	    }
    
    	    /* (non-Javadoc)
    	     * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileParameterNames()
    	     */
    	    public Enumeration<String> getFileParameterNames() {
    	        return Collections.enumeration(files.keySet());
    	    }
    
    	    /* (non-Javadoc)
    	     * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getContentType(java.lang.String)
    	     */
    	    public String[] getContentType(String fieldName) {
    	        List<FileItem> items = files.get(fieldName);
    
    	        if (items == null) {
    	            return null;
    	        }
    
    	        List<String> contentTypes = new ArrayList<String>(items.size());
    	        for (FileItem fileItem : items) {
    	            contentTypes.add(fileItem.getContentType());
    	        }
    
    	        return contentTypes.toArray(new String[contentTypes.size()]);
    	    }
    
    	    /* (non-Javadoc)
    	     * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFile(java.lang.String)
    	     */
    	    public UploadedFile[] getFile(String fieldName) {
    	        List<FileItem> items = files.get(fieldName);
    
    	        if (items == null) {
    	            return null;
    	        }
    
    	        List<UploadedFile> fileList = new ArrayList<>(items.size());
    	        for (FileItem fileItem : items) {
    	            File storeLocation = ((DiskFileItem) fileItem).getStoreLocation();
    	            if (fileItem.isInMemory() && storeLocation != null && !storeLocation.exists()) {
    	                try {
    	                    storeLocation.createNewFile();
    	                } catch (IOException e) {
    	                    LOG.error("Cannot write uploaded empty file to disk: {}", storeLocation.getAbsolutePath(), e);
    	                }
    	            }
    	            fileList.add(new StrutsUploadedFile(storeLocation));
    	        }
    
    	        return fileList.toArray(new UploadedFile[fileList.size()]);
    	    }
    
    	    /* (non-Javadoc)
    	     * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileNames(java.lang.String)
    	     */
    	    public String[] getFileNames(String fieldName) {
    	        List<FileItem> items = files.get(fieldName);
    
    	        if (items == null) {
    	            return null;
    	        }
    
    	        List<String> fileNames = new ArrayList<String>(items.size());
    	        for (FileItem fileItem : items) {
    	            fileNames.add(getCanonicalName(fileItem.getName()));
    	        }
    
    	        return fileNames.toArray(new String[fileNames.size()]);
    	    }
    
    	    /* (non-Javadoc)
    	     * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFilesystemName(java.lang.String)
    	     */
    	    public String[] getFilesystemName(String fieldName) {
    	        List<FileItem> items = files.get(fieldName);
    
    	        if (items == null) {
    	            return null;
    	        }
    
    	        List<String> fileNames = new ArrayList<String>(items.size());
    	        for (FileItem fileItem : items) {
    	            fileNames.add(((DiskFileItem) fileItem).getStoreLocation().getName());
    	        }
    
    	        return fileNames.toArray(new String[fileNames.size()]);
    	    }
    
    	    /* (non-Javadoc)
    	     * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameter(java.lang.String)
    	     */
    	    public String getParameter(String name) {
    	        List<String> v = params.get(name);
    	        if (v != null && v.size() > 0) {
    	            return v.get(0);
    	        }
    
    	        return null;
    	    }
    
    	    /* (non-Javadoc)
    	     * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterNames()
    	     */
    	    public Enumeration<String> getParameterNames() {
    	        return Collections.enumeration(params.keySet());
    	    }
    
    	    /* (non-Javadoc)
    	     * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterValues(java.lang.String)
    	     */
    	    public String[] getParameterValues(String name) {
    	        List<String> v = params.get(name);
    	        if (v != null && v.size() > 0) {
    	            return v.toArray(new String[v.size()]);
    	        }
    
    	        return null;
    	    }
    
    	    /* (non-Javadoc)
    	     * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getErrors()
    	     */
    	    public List<LocalizedMessage> getErrors() {
    	        return errors;
    	    }
    
    	    /**
    	     * Returns the canonical name of the given file.
    	     *
    	     * @param filename the given file
    	     * @return the canonical name of the given file
    	     */
    //	    private String getCanonicalName(String filename) {
    //	        int forwardSlash = filename.lastIndexOf("/");
    //	        int backwardSlash = filename.lastIndexOf("\");
    //	        if (forwardSlash != -1 && forwardSlash > backwardSlash) {
    //	            filename = filename.substring(forwardSlash + 1, filename.length());
    //	        } else if (backwardSlash != -1 && backwardSlash >= forwardSlash) {
    //	            filename = filename.substring(backwardSlash + 1, filename.length());
    //	        }
    //
    //	        return filename;
    //	    }
    
    	    /**
    	     * Creates a RequestContext needed by Jakarta Commons Upload.
    	     *
    	     * @param req the request.
    	     * @return a new request context.
    	     */
    	    private RequestContext createRequestContext(final HttpServletRequest req) {
    	        return new RequestContext() {
    	            public String getCharacterEncoding() {
    	                return req.getCharacterEncoding();
    	            }
    
    	            public String getContentType() {
    	                return req.getContentType();
    	            }
    
    	            public int getContentLength() {
    	                return req.getContentLength();
    	            }
    
    	            public InputStream getInputStream() throws IOException {
    	                InputStream in = req.getInputStream();
    	                if (in == null) {
    	                    throw new IOException("Missing content in the request");
    	                }
    	                return req.getInputStream();
    	            }
    	        };
    	    }
    
    	    /* (non-Javadoc)
    	    * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#cleanUp()
    	    */
    	    public void cleanUp() {
    	        Set<String> names = files.keySet();
    	        for (String name : names) {
    	            List<FileItem> items = files.get(name);
    	            for (FileItem item : items) {
    	                LOG.debug("Removing file {} {}", name, item );
    	                if (!item.isInMemory()) {
    	                    item.delete();
    	                }
    	            }
    	        }
    	    }
    
    }
    

           修改完成,以为完事大吉,但又出问题了。发现上传功能后台接受表单的参数会叠加,比如第一次传name是123,第二次再传123,name变成了123,123.经过定位,排除了前端问题。后端debug发现是每次参数都会叠加,目测是单例问题。但struts2默认是多例的,为什么会出现这个问题呢。配置没改,multi数据解析类也只是改了一些兼容性问题。难道我记错了?于是尝试把action上能加@scope("prototype")的地方都尝试加下,发现没解决问题,继续debug。发现自定义的MyMultiPartRequest类每次解析都会保存上一次的内容。原因找到了,这个类是单例的。看了下配置,bean的scope是“dufault”,默认是单例?不会呀。改成多例试试,问题解决。查了下原因,Struts2单独使用是多例,但交给spring管理后默认是单例了?百撕不得其解。

      问题来了。之前的版本为什么没出现这个问题?未完待续。。。

  • 相关阅读:
    Python 对象的绑定方法
    Python 类的属性
    Python 对象与类
    Python 面向对象
    Python Subprocess模块
    Python Hashlib模块
    Python Configparser模块
    Python XML模块
    Python Re模块
    Gridview中Datakeys 通过主键取得各列的值。(转)
  • 原文地址:https://www.cnblogs.com/wuweidong/p/8953463.html
Copyright © 2011-2022 走看看