VYPR
Moderate severityNVD Advisory· Published Jun 14, 2023· Updated Feb 13, 2025

Apache Struts: DoS via OOM owing to not properly checking of list bounds

CVE-2023-34149

Description

Allocation of Resources Without Limits or Throttling vulnerability in Apache Software Foundation Apache Struts.This issue affects Apache Struts: through 2.5.30, through 6.1.2.

Upgrade to Struts 2.5.31 or 6.1.2.1 or greater.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Apache Struts versions through 2.5.30 and 6.1.2 contain a resource exhaustion vulnerability via unrestricted multipart uploads, allowing denial of service.

CVE-2023-34149 is an Allocation of Resources Without Limits or Throttling vulnerability in Apache Struts. The root cause lies in the framework's multipart request handling, which does not enforce any size or count limits on uploaded content. This allows an attacker to send arbitrarily large or numerous multipart uploads, consuming server memory, disk space, or thread resources without throttling [1].

Exploitation requires network access to a vulnerable Struts endpoint that accepts multipart/form-data requests. No authentication is needed, as the vulnerability is triggered before any action-level security checks. The attacker simply sends a crafted HTTP request with oversized or excessive multipart parts, causing the server to allocate resources unboundedly. The commit referenced in [2] shows changes that add a -1 parameter to test methods, but the actual fix introduces proper limit enforcement [2].

The impact is a denial of service (DoS) condition, where the target server becomes unresponsive or crashes due to resource exhaustion. This can disrupt legitimate application access and potentially affect other services hosted on the same infrastructure [1].

Apache has released fixed versions: Struts 2.5.31 and Struts 6.1.2.1 (or greater). Users are advised to upgrade immediately. There is no known workaround, making patching essential [1][4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.apache.struts:struts2-coreMaven
< 2.5.312.5.31
org.apache.struts:struts2-coreMaven
>= 6.0.0, < 6.1.2.16.1.2.1

Affected products

2

Patches

1
2d6f1bc0a6f5

add some improvements

https://github.com/apache/strutsYasser ZamaniJun 5, 2023via ghsa
9 files changed · +108 15
  • core/src/main/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessor.java+5 0 modified
    @@ -109,6 +109,11 @@ public Object getProperty(Map context, Object target, Object name) throws OgnlEx
                 if (listSize <= index) {
                     Object result;
     
    +                if (index > autoGrowCollectionLimit) {
    +                    throw new OgnlException("Error auto growing collection size to " + index + " which limited to "
    +                                            + autoGrowCollectionLimit);
    +                }
    +
                     for (int i = listSize; i < index; i++) {
                         list.add(null);
                     }
    
  • core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java+10 0 modified
    @@ -65,6 +65,7 @@ public class ConstantConfig {
         private String uiThemeExpansionToken;
         private Long multipartMaxSize;
         private Long multipartMaxFiles;
    +    private Long multipartMaxStringLength;
         private String multipartSaveDir;
         private Integer multipartBufferSize;
         private BeanConfig multipartParser;
    @@ -197,6 +198,7 @@ public Map<String, String> getAllAsStringsMap() {
             map.put(StrutsConstants.STRUTS_UI_THEME_EXPANSION_TOKEN, uiThemeExpansionToken);
             map.put(StrutsConstants.STRUTS_MULTIPART_MAXSIZE, Objects.toString(multipartMaxSize, null));
             map.put(StrutsConstants.STRUTS_MULTIPART_MAXFILES, Objects.toString(multipartMaxFiles, null));
    +        map.put(StrutsConstants.STRUTS_MULTIPART_MAX_STRING_LENGTH, Objects.toString(multipartMaxStringLength, null));
             map.put(StrutsConstants.STRUTS_MULTIPART_SAVEDIR, multipartSaveDir);
             map.put(StrutsConstants.STRUTS_MULTIPART_BUFFERSIZE, Objects.toString(multipartBufferSize, null));
             map.put(StrutsConstants.STRUTS_MULTIPART_PARSER, beanConfToString(multipartParser));
    @@ -590,6 +592,14 @@ public void setMultipartMaxFiles(Long multipartMaxFiles) {
             this.multipartMaxFiles = multipartMaxFiles;
         }
     
    +    public Long getMultipartMaxStringLength() {
    +        return multipartMaxStringLength;
    +    }
    +
    +    public void setMultipartMaxStringLength(Long multipartMaxStringLength) {
    +        this.multipartMaxStringLength = multipartMaxStringLength;
    +    }
    +
         public String getMultipartSaveDir() {
             return multipartSaveDir;
         }
    
  • core/src/main/java/org/apache/struts2/dispatcher/multipart/AbstractMultiPartRequest.java+10 0 modified
    @@ -58,6 +58,11 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
          */
         protected Long maxFiles;
     
    +    /**
    +     * Specifies the maximum length of a string parameter in a multipart request.
    +     */
    +    protected Long maxStringLength;
    +
         /**
          * Specifies the buffer size to use during streaming.
          */
    @@ -96,6 +101,11 @@ public void setMaxFiles(String maxFiles) {
             this.maxFiles = Long.parseLong(maxFiles);
         }
     
    +    @Inject(StrutsConstants.STRUTS_MULTIPART_MAX_STRING_LENGTH)
    +    public void setMaxStringLength(String maxStringLength) {
    +        this.maxStringLength = Long.parseLong(maxStringLength);
    +    }
    +
         @Inject
         public void setLocaleProviderFactory(LocaleProviderFactory localeProviderFactory) {
             defaultLocale = localeProviderFactory.createLocaleProvider().getLocale();
    
  • core/src/main/java/org/apache/struts2/dispatcher/multipart/JakartaMultiPartRequest.java+12 1 modified
    @@ -137,8 +137,19 @@ protected void processNormalFormField(FileItem item, String charset) throws Unsu
                 values = new ArrayList<>();
             }
     
    -        if (item.getSize() == 0) {
    +        long size = item.getSize();
    +        if (size == 0) {
                 values.add(StringUtils.EMPTY);
    +        } else if (size > maxStringLength) {
    +          String errorKey = "struts.messages.upload.error.parameter.too.long";
    +          LocalizedMessage localizedMessage = new LocalizedMessage(this.getClass(), errorKey, null,
    +                  new Object[] { item.getFieldName(), maxStringLength, size });
    +
    +          if (!errors.contains(localizedMessage)) {
    +              errors.add(localizedMessage);
    +          }
    +          return;
    +
             } else if (charset != null) {
                 values.add(item.getString(charset));
             } else {
    
  • core/src/main/java/org/apache/struts2/StrutsConstants.java+3 0 modified
    @@ -145,6 +145,9 @@ public final class StrutsConstants {
         /** The maximized number of files allowed to upload */
         public static final String STRUTS_MULTIPART_MAXFILES = "struts.multipart.maxFiles";
     
    +    /** The maximum length of a string parameter in a multipart request. */
    +    public static final String STRUTS_MULTIPART_MAX_STRING_LENGTH = "struts.multipart.maxStringLength";
    +
         /** The directory to use for storing uploaded files */
         public static final String STRUTS_MULTIPART_SAVEDIR = "struts.multipart.saveDir";
     
    
  • core/src/main/resources/org/apache/struts2/default.properties+1 0 modified
    @@ -69,6 +69,7 @@ struts.multipart.parser=jakarta
     struts.multipart.saveDir=
     struts.multipart.maxSize=2097152
     struts.multipart.maxFiles=256
    +struts.multipart.maxStringLength=4096
     
     ### Load custom property files (does not override struts.properties!)
     # struts.custom.properties=application,org/apache/struts2/extension/custom
    
  • core/src/main/resources/org/apache/struts2/struts-messages.properties+1 0 modified
    @@ -26,6 +26,7 @@ struts.messages.invalid.content.type=Could not find a Content-Type for {0}. Veri
     struts.messages.removing.file=Removing file {0} {1}
     struts.messages.error.uploading=Error uploading: {0}
     struts.messages.error.file.too.large=File {0} is too large to be uploaded. Maximum allowed size is {4} bytes!
    +struts.messages.upload.error.parameter.too.long=The request parameter "{0}" was too long.  Max length allowed is {1}, but found {2}!
     struts.messages.error.content.type.not.allowed=Content-Type not allowed: {0} "{1}" "{2}" {3}
     struts.messages.error.file.extension.not.allowed=File extension not allowed: {0} "{1}" "{2}" {3}
     
    
  • core/src/test/java/com/opensymphony/xwork2/ognl/accessor/XWorkListPropertyAccessorTest.java+9 7 modified
    @@ -22,7 +22,7 @@
     import com.opensymphony.xwork2.XWorkTestCase;
     import com.opensymphony.xwork2.util.ListHolder;
     import com.opensymphony.xwork2.util.ValueStack;
    -import ognl.ListPropertyAccessor;
    +import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
     import ognl.PropertyAccessor;
     
     import java.util.ArrayList;
    @@ -42,11 +42,11 @@ public void testContains() {
     
             assertNotNull(listHolder.getLongs());
             assertEquals(3, listHolder.getLongs().size());
    -        assertEquals(new Long(1), (Long) listHolder.getLongs().get(0));
    -        assertEquals(new Long(2), (Long) listHolder.getLongs().get(1));
    -        assertEquals(new Long(3), (Long) listHolder.getLongs().get(2));
    +        assertEquals(new Long(1), listHolder.getLongs().get(0));
    +        assertEquals(new Long(2), listHolder.getLongs().get(1));
    +        assertEquals(new Long(3), listHolder.getLongs().get(2));
     
    -        assertTrue(((Boolean) vs.findValue("longs.contains(1)")).booleanValue());
    +        assertTrue((Boolean) vs.findValue("longs.contains(1)"));
         }
     
         public void testCanAccessListSizeProperty() {
    @@ -60,8 +60,8 @@ public void testCanAccessListSizeProperty() {
     
             vs.push(listHolder);
     
    -        assertEquals(new Integer(myList.size()), vs.findValue("strings.size()"));
    -        assertEquals(new Integer(myList.size()), vs.findValue("strings.size"));
    +        assertEquals(myList.size(), vs.findValue("strings.size()"));
    +        assertEquals(myList.size(), vs.findValue("strings.size"));
         }
     
         public void testAutoGrowthCollectionLimit() {
    @@ -73,12 +73,14 @@ public void testAutoGrowthCollectionLimit() {
             listHolder.setStrings(myList);
     
             ValueStack vs = ActionContext.getContext().getValueStack();
    +        ReflectionContextState.setCreatingNullObjects(vs.getContext(), true);
             vs.push(listHolder);
     
             vs.setValue("strings[0]", "a");
             vs.setValue("strings[1]", "b");
             vs.setValue("strings[2]", "c");
             vs.setValue("strings[3]", "d");
    +        vs.findValue("strings[3]");
     
             assertEquals(3, vs.findValue("strings.size()"));
         }
    
  • core/src/test/java/org/apache/struts2/interceptor/FileUploadInterceptorTest.java+57 7 modified
    @@ -235,7 +235,7 @@ public void testInvalidContentTypeMultipartRequest() throws Exception {
             mai.setInvocationContext(ActionContext.getContext());
     
             ActionContext.getContext().setParameters(HttpParameters.create().build());
    -        ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequest(req, 2000));
    +        ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequest(req, 2000, -1));
     
             interceptor.intercept(mai);
     
    @@ -257,7 +257,7 @@ public void testNoContentMultipartRequest() throws Exception {
             mai.setInvocationContext(ActionContext.getContext());
     
             ActionContext.getContext().setParameters(HttpParameters.create().build());
    -        ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequest(req, 2000));
    +        ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequest(req, 2000, -1));
     
             interceptor.intercept(mai);
     
    @@ -288,7 +288,7 @@ public void testSuccessUploadOfATextFileMultipartRequest() throws Exception {
             mai.setInvocationContext(ActionContext.getContext());
             Map<String, Object> param = new HashMap<>();
             ActionContext.getContext().setParameters(HttpParameters.create(param).build());
    -        ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequest(req, 2000));
    +        ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequest(req, 2000, -1));
     
             interceptor.intercept(mai);
     
    @@ -349,7 +349,7 @@ public void testMultipleAccept() throws Exception {
             mai.setInvocationContext(ActionContext.getContext());
             Map<String, Object> param = new HashMap<String, Object>();
             ActionContext.getContext().setParameters(HttpParameters.create(param).build());
    -        ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequest(req, 2000));
    +        ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequest(req, 2000, -1));
     
             interceptor.setAllowedTypes("text/html");
             interceptor.intercept(mai);
    @@ -402,7 +402,7 @@ public void testUnacceptedNumberOfFiles() throws Exception {
             mai.setInvocationContext(ActionContext.getContext());
             Map<String, Object> param = new HashMap<>();
             ActionContext.getContext().setParameters(HttpParameters.create(param).build());
    -        ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequest(req, 2000));
    +        ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequest(req, 2000, -1));
     
             interceptor.setAllowedTypes("text/html");
             interceptor.intercept(mai);
    @@ -413,6 +413,55 @@ public void testUnacceptedNumberOfFiles() throws Exception {
             assertEquals("Request exceeded allowed number of files! Max allowed files number is: 3!", action.getActionErrors().iterator().next());
         }
     
    +    public void testMultipartRequestMaxStringLength() throws Exception {
    +        MockHttpServletRequest req = new MockHttpServletRequest();
    +        req.setCharacterEncoding(StandardCharsets.UTF_8.name());
    +        req.setMethod("post");
    +        req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
    +
    +        // inspired by the unit tests for jakarta commons fileupload
    +        String content = ("-----1234\r\n" +
    +                "Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
    +                "Content-Type: text/html\r\n" +
    +                "\r\n" +
    +                "Unit test of FileUploadInterceptor" +
    +                "\r\n" +
    +                "-----1234\r\n" +
    +                "Content-Disposition: form-data; name=\"normalFormField1\"\r\n" +
    +                "\r\n" +
    +                "it works" +
    +                "\r\n" +
    +                "-----1234\r\n" +
    +                "Content-Disposition: form-data; name=\"normalFormField2\"\r\n" +
    +                "\r\n" +
    +                "long string should not work" +
    +                "\r\n" +
    +                "-----1234--\r\n");
    +        req.setContent(content.getBytes(StandardCharsets.US_ASCII));
    +
    +        MyFileupAction action = container.inject(MyFileupAction.class);
    +
    +        MockActionInvocation mai = new MockActionInvocation();
    +        mai.setAction(action);
    +        mai.setResultCode("success");
    +        mai.setInvocationContext(ActionContext.getContext());
    +        Map<String, Object> param = new HashMap<>();
    +        ActionContext.getContext()
    +                .withParameters(HttpParameters.create(param).build())
    +                .withServletRequest(createMultipartRequest(req, -1, 20));
    +
    +        interceptor.intercept(mai);
    +
    +        assertTrue(action.hasActionErrors());
    +
    +        Collection<String> errors = action.getActionErrors();
    +        assertEquals(1, errors.size());
    +        String msg = errors.iterator().next();
    +        assertEquals(
    +                "The request parameter \"normalFormField2\" was too long.  Max length allowed is 20, but found 27!",
    +                msg);
    +    }
    +
         public void testMultipartRequestLocalizedError() throws Exception {
             MockHttpServletRequest req = new MockHttpServletRequest();
             req.setCharacterEncoding(StandardCharsets.UTF_8.name());
    @@ -439,7 +488,7 @@ public void testMultipartRequestLocalizedError() throws Exception {
             ActionContext.getContext()
                 .withParameters(HttpParameters.create(param).build())
                 .withLocale(Locale.GERMAN)
    -            .withServletRequest(createMultipartRequest(req, 10));
    +            .withServletRequest(createMultipartRequest(req, 10, -1));
     
             interceptor.intercept(mai);
     
    @@ -472,10 +521,11 @@ private String encodeTextFile(String bondary, String endline, String name, Strin
             return sb.toString();
         }
     
    -    private MultiPartRequestWrapper createMultipartRequest(HttpServletRequest req, int maxsize) throws IOException {
    +    private MultiPartRequestWrapper createMultipartRequest(HttpServletRequest req, int maxsize, int maxStringLength) throws IOException {
             JakartaMultiPartRequest jak = new JakartaMultiPartRequest();
             jak.setMaxSize(String.valueOf(maxsize));
             jak.setMaxFiles("3");
    +        jak.setMaxStringLength(String.valueOf(maxStringLength));
             return new MultiPartRequestWrapper(jak, req, tempDir.getAbsolutePath(), new DefaultLocaleProvider());
         }
     
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.