記一次struts2漏洞修復帶來的問題

struts2做爲萬年漏洞王,感受已經被棄如敝屣了,除了一些古老的項目,好比我手上的一個項目,之前每次出現漏洞就如臨大敵,手忙腳亂的趕在公司紅頭文件發出來前修復它。而後改了一兩次後毅然決然用別的框架代替它了。
前端

萬事大吉,不再用擔憂struts2出漏洞了。然而上個月又爆出了個,還有個還在維護期的古老項目須要修復。。。又要填坑了。java

攻略是將struts2的jar包替換,而後加一些配置就完了。web

修復辦法:spring

而後帶來了一系列問題。apache

因爲struts包由2.3.?升級到2.5.16,不少依賴包、配置須要響應調整。找了個總結的:後端

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

2,方法不能訪問的問題,須要在每一個action配置文件中加上 strict-method-invocation="false":session

<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節點:app

<?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管理後默認是單例了?百撕不得其解。

  問題來了。以前的版本爲何沒出現這個問題?未完待續。。。

相關文章
相關標籤/搜索