記一次struts2漏洞修復帶來的問題
阿新 • • 發佈:2018-04-26
pub size desc zed 依賴包 響應 ext substring lte
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類也需要相應調整。
由於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(); } } } } }
記一次struts2漏洞修復帶來的問題