Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation for form limits & improve configuration via context attributes #12560

Merged
merged 9 commits into from
Jan 15, 2025
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.docs.programming.security;

import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.server.FormFields;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.Fields;

public class FormDocs
{
public void limitFormContent()
{
ServletContextHandler servletContextHandler = new ServletContextHandler();
// tag::limitFormContent[]
int maxFormKeys = 100;
int maxFormSizeInBytes = 1024;
servletContextHandler.setMaxFormContentSize(maxFormSizeInBytes);
servletContextHandler.setMaxFormKeys(maxFormKeys);
// end::limitFormContent[]
}

public void jettyCoreAPI()
{
Request request = null;
// tag::jettyCoreAPI[]
int maxFormKeys = 100;
int maxFormSizeInBytes = 1024;
Fields fields;

// Explicit set the form limits.
fields = FormFields.getFields(request, maxFormKeys, maxFormSizeInBytes);

// Rely on default form limits.
fields = FormFields.getFields(request);
// end::jettyCoreAPI[]
}
}
2 changes: 2 additions & 0 deletions documentation/jetty/modules/operations-guide/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
* xref:jstl/index.adoc[]
* xref:jsf-taglibs/index.adoc[]
* xref:jndi/index.adoc[]
* Jetty Security
** xref:security/configuring-form-size.adoc[]
* xref:jaas/index.adoc[]
* xref:jaspi/index.adoc[]
* xref:jmx/index.adoc[]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

[[limit-form-content]]
= Limiting Form Content

Forms can be a vector for denial-of-service attacks, like explained in xref:programming-guide:security/configuring-form-size.adoc[this section] of the Programming Guide.

== Configuring Form Limits for a Web Application

To configure the form limits for a single web application, the `WebAppContext` instance can be configured from a context XML file or `WEB-INF/jetty-web.xml` file:

[,xml,subs=attributes+]
----
<Configure class="org.eclipse.jetty.{ee-current}.webapp.WebAppContext">

...

<Set name="maxFormContentSize">200000</Set>
<Set name="maxFormKeys">200</Set>
</Configure>

----

These settings can also be set via the following `ServletContext` attributes.

- `org.eclipse.jetty.server.Request.maxFormKeys`
- `org.eclipse.jetty.server.Request.maxFormContentSize`
2 changes: 2 additions & 0 deletions documentation/jetty/modules/programming-guide/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
** xref:troubleshooting/state-tracking.adoc[]
** xref:troubleshooting/component-dump.adoc[]
** xref:troubleshooting/debugging.adoc[]
* Jetty Security
** xref:security/configuring-form-size.adoc[]
* Migration Guides
** xref:migration/94-to-10.adoc[]
** xref:migration/11-to-12.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

[[limit-form-content]]
= Limiting Form Content

Form content sent to the server is processed by Jetty into a map of parameters to be used by the web application.
Forms can be a vector for denial-of-service attacks, since significant memory and CPU can be consumed if a malicious client sends very large form content or a large number of form keys.
Thus, Jetty limits the amount of data and keys that can be in a form posted to Jetty.

The default maximum size Jetty permits is 200000 bytes and 1000 keys.
You can change this default for a particular web application or for all web applications on a particular `Server` instance.

== Configuring Form Limits for a Web Application

To configure the form limits for a single web application, the `ServletContextHandler` (or `WebAppContext`) instance can be configured using the following methods:

[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/FormDocs.java[tags=limitFormContent]
----

These settings can also be set via the following `ServletContext` attributes.

- `org.eclipse.jetty.server.Request.maxFormKeys`
- `org.eclipse.jetty.server.Request.maxFormContentSize`

== Configuring Default Form Limits for the Server

The following system properties can be used to configure form limits for the entire server, including all contexts without explicit configuration:

- `org.eclipse.jetty.server.Request.maxFormKeys`
- `org.eclipse.jetty.server.Request.maxFormContentSize`.

If not configured for either the server or a specific context, then the default `maxFormKeys` is 1000 and the default `maxFormContentSize` is 200000.

== Limiting Form Content with Jetty Core API

The class `FormFields` is used to parse forms with the Jetty Core API, which provides `onFields` and `getFields` static methods to provide both async & blocking ways to parse a form.

These methods can take parameters for `maxFields` and `maxLength` which can be used to limit the form content.

[,java,indent=0]
----
include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/FormDocs.java[tags=jettyCoreAPI]
----
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ public static Fields getFields(Request request)
* Calls to {@code onFields} and {@code getFields} methods are idempotent, and
* can be called multiple times, with subsequent calls returning the results of the first call.
* @param request The request to get or read the Fields from
* @param maxFields The maximum number of fields to accept
* @param maxLength The maximum length of fields
* @param maxFields The maximum number of fields to accept or -1 for unlimited
* @param maxLength The maximum length of fields or -1 for unlimited.
* @return the Fields
* @see #onFields(Request, Promise.Invocable)
* @see #onFields(Request, Charset, Promise.Invocable)
Expand Down Expand Up @@ -185,8 +185,8 @@ public static void onFields(Request request, Charset charset, Promise.Invocable<
*
* @param request The request to get or read the Fields from
* @param charset The {@link Charset} of the request content, if previously extracted.
* @param maxFields The maximum number of fields to accept
* @param maxLength The maximum length of fields
* @param maxFields The maximum number of fields to accept or -1 for unlimited
* @param maxLength The maximum length of fields or -1 for unlimited
* @param promise The action to take when the FormFields are available.
*/
public static void onFields(Request request, Charset charset, int maxFields, int maxLength, Promise.Invocable<Fields> promise)
Expand Down Expand Up @@ -249,8 +249,8 @@ public static CompletableFuture<Fields> from(Request request, Charset charset)
* @param request The {@link Request} in which to look for an existing {@link FormFields} attribute,
* using the classname as the attribute name, else the request is used
* as a {@link Content.Source} from which to read the fields and set the attribute.
* @param maxFields The maximum number of fields to be parsed
* @param maxLength The maximum total size of the fields
* @param maxFields The maximum number of fields to be parsed or -1 for unlimited
* @param maxLength The maximum total size of the fields or -1 for unlimited
* @return A {@link CompletableFuture} that will provide the {@link Fields} or a failure.
* @deprecated use {@link #onFields(Request, Charset, int, int, Promise.Invocable)} instead.
*/
Expand All @@ -266,8 +266,8 @@ public static CompletableFuture<Fields> from(Request request, int maxFields, int
* using the classname as the attribute name, else the request is used
* as a {@link Content.Source} from which to read the fields and set the attribute.
* @param charset the {@link Charset} to use for byte to string conversion.
* @param maxFields The maximum number of fields to be parsed
* @param maxLength The maximum total size of the fields
* @param maxFields The maximum number of fields to be parsed or -1 for unlimited
* @param maxLength The maximum total size of the fields or -1 for unlimited
* @return A {@link CompletableFuture} that will provide the {@link Fields} or a failure.
* @deprecated use {@link #onFields(Request, Charset, int, int, Promise.Invocable)} instead.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@ public void dump(Appendable out, String indent) throws IOException
new ClassLoaderDump(getClassLoader()),
Dumpable.named("context " + this, getContext()),
Dumpable.named("handler attributes " + this, getContext().getPersistentAttributes()),
Dumpable.named("maxFormKeys ", getMaxFormKeys()),
Dumpable.named("maxFormContentSize ", getMaxFormContentSize()),
new DumpableCollection("initparams " + this, getInitParams().entrySet()));
}

Expand Down Expand Up @@ -2045,6 +2047,53 @@ public void setExtendedListenerTypes(boolean b)
{
_servletContext.setExtendedListenerTypes(b);
}

@Override
public Object getAttribute(String name)
{
return switch (name)
{
case FormFields.MAX_FIELDS_ATTRIBUTE -> getMaxFormKeys();
case FormFields.MAX_LENGTH_ATTRIBUTE -> getMaxFormContentSize();
default -> super.getAttribute(name);
};
}

@Override
public Object setAttribute(String name, Object attribute)
{
return switch (name)
{
case FormFields.MAX_FIELDS_ATTRIBUTE ->
{
int oldValue = getMaxFormKeys();
if (attribute == null)
setMaxFormKeys(DEFAULT_MAX_FORM_KEYS);
else
setMaxFormKeys(Integer.parseInt(attribute.toString()));
yield oldValue;
}
case FormFields.MAX_LENGTH_ATTRIBUTE ->
{
int oldValue = getMaxFormContentSize();
if (attribute == null)
setMaxFormContentSize(DEFAULT_MAX_FORM_CONTENT_SIZE);
else
setMaxFormContentSize(Integer.parseInt(attribute.toString()));
yield oldValue;
}
default -> super.setAttribute(name, attribute);
};
}

@Override
public Set<String> getAttributeNameSet()
{
Set<String> names = new HashSet<>(super.getAttributeNameSet());
names.add(FormFields.MAX_FIELDS_ATTRIBUTE);
names.add(FormFields.MAX_LENGTH_ATTRIBUTE);
return Collections.unmodifiableSet(names);
}
}

public class ServletContextApi implements jakarta.servlet.ServletContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ClassLoaderDump;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
Expand Down Expand Up @@ -987,12 +988,15 @@ else if (getBaseResource() != null)
name = String.format("%s@%x", name, hashCode());

dumpObjects(out, indent,
Dumpable.named("environment", ServletContextHandler.ENVIRONMENT.getName()),
new ClassLoaderDump(getClassLoader()),
new DumpableCollection("Systemclasses " + name, systemClasses),
new DumpableCollection("Serverclasses " + name, serverClasses),
new DumpableCollection("Configurations " + name, _configurations),
new DumpableCollection("Handler attributes " + name, asAttributeMap().entrySet()),
new DumpableCollection("Context attributes " + name, getContext().asAttributeMap().entrySet()),
Dumpable.named("maxFormKeys ", getMaxFormKeys()),
Dumpable.named("maxFormContentSize ", getMaxFormContentSize()),
new DumpableCollection("EventListeners " + this, getEventListeners()),
new DumpableCollection("Initparams " + name, getInitParams().entrySet())
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.component.Environment;
import org.eclipse.jetty.util.component.LifeCycle;
Expand Down Expand Up @@ -302,7 +303,10 @@ public void insertHandler(org.eclipse.jetty.server.Handler.Singleton coreHandler
@Override
public void dump(Appendable out, String indent) throws IOException
{
dumpObjects(out, indent, new DumpableCollection("initparams " + this, getInitParams().entrySet()));
dumpObjects(out, indent,
Dumpable.named("maxFormKeys ", getMaxFormKeys()),
Dumpable.named("maxFormContentSize ", getMaxFormContentSize()),
new DumpableCollection("initparams " + this, getInitParams().entrySet()));
}

public APIContext getServletContext()
Expand Down Expand Up @@ -2861,6 +2865,53 @@ public APIContext getAPIContext()
{
return _apiContext;
}

@Override
public Object getAttribute(String name)
lachlan-roberts marked this conversation as resolved.
Show resolved Hide resolved
{
return switch (name)
{
case FormFields.MAX_FIELDS_ATTRIBUTE -> getMaxFormKeys();
case FormFields.MAX_LENGTH_ATTRIBUTE -> getMaxFormContentSize();
default -> super.getAttribute(name);
};
}

@Override
public Object setAttribute(String name, Object attribute)
{
return switch (name)
{
case FormFields.MAX_FIELDS_ATTRIBUTE ->
{
int oldValue = getMaxFormKeys();
if (attribute == null)
setMaxFormKeys(DEFAULT_MAX_FORM_KEYS);
else
setMaxFormKeys(Integer.parseInt(attribute.toString()));
yield oldValue;
}
case FormFields.MAX_LENGTH_ATTRIBUTE ->
{
int oldValue = getMaxFormContentSize();
if (attribute == null)
setMaxFormContentSize(DEFAULT_MAX_FORM_CONTENT_SIZE);
else
setMaxFormContentSize(Integer.parseInt(attribute.toString()));
yield oldValue;
}
default -> super.setAttribute(name, attribute);
};
}
}

@Override
public Set<String> getAttributeNameSet()
{
Set<String> names = new HashSet<>(super.getAttributeNameSet());
names.add(FormFields.MAX_FIELDS_ATTRIBUTE);
names.add(FormFields.MAX_LENGTH_ATTRIBUTE);
return Collections.unmodifiableSet(names);
}

private class CoreToNestedHandler extends Abstract
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,8 @@ else if (getResourceBase() != null)

dumpObjects(out, indent,
Dumpable.named("environment", ContextHandler.ENVIRONMENT.getName()),
Dumpable.named("maxFormKeys ", getMaxFormKeys()),
Dumpable.named("maxFormContentSize ", getMaxFormContentSize()),
new ClassLoaderDump(getClassLoader()),
new DumpableCollection("Systemclasses " + name, systemClasses),
new DumpableCollection("Serverclasses " + name, serverClasses),
Expand Down
Loading