Problem:
Spring's data binder allows you to set maximum size() of automatically created List<>
e.g. to 3 items. It's quite easy to bypass this limitation and cause Spring to create a List of 3000+ items simply by modifying HTTP content sent to the server.
In other words: while testing my webapp I was able by creating malicious HTTP request to force Spring's data binder to create a List<> consisting of 4000 items although I had set the limit to 3 items. This may easily lead to Out of Memory exceptions on any app server.
Question:
Am I missing something obvious how to prevent Spring from doing this or rather it's a bug that should be reported to Spring's bug tracker?
Version used:
spring-tool-suite-3.3.0.RELEASE,
D:\m2\repo\org\springframework\spring-web\3.2.4.RELEASE\spring-web-3.2.4.RELEASE.jar
Description:
I needed to bind multiple html <input />
elements to a single List<String>
object, something like:
<input type="text" name="phoneNumber[0]" />
...
<input type="text" name="phoneNumber[n]" />
Spring performs such conversion by default using org.springframework.beans.propertyeditors.CustomCollectionEditor
. Below is a simple code snippet presenting the issue described above.
Code:
public class ContactDataEntity {
private List<String> phoneNumber;
// getters and setters
}
@RequestMapping(value = VIEW_PAGE_1, method = RequestMethod.POST)
public String xxx(HttpServletRequest request, Model model) {
// set and bind
ContactDataEntity contactData = new ContactDataEntity();
ServletRequestDataBinder binder = new ServletRequestDataBinder(contactData);
binder.setAutoGrowCollectionLimit(3); // set limit to 3 items
binder.bind(request);
// test binding results
List<String> numbers = contactData.getPhoneNumber();
if (numbers != null) {
System.out.print("numbers SIZE: " + numbers.size() + ", DATA: ");
for (String s : numbers) System.out.print(s + ", ");
System.out.print("\n");
}
// validate and return view name...
}
Results for correct data (<= 3 items, everything works ok, I use Live HTTP Headers for Firefox):
https://i.sstatic.net/AEjsA.jpg
Results for too many items (> 3 items, everything works ok, 500 Internal Server Error occurred):
https://i.sstatic.net/MfWYy.jpg
Simple trick (> 3 items, no errors reported, sorry for my typo in 'overwritten'):
https://i.sstatic.net/FNlXE.jpg
Let's exploit the above:
https://i.sstatic.net/XPIhc.jpg
So, my question again: am I missing something obvious how to prevent Spring from doing this or rather it's a bug that should be reported to Spring's bug tracker?
// EDIT:
I reported it as a bug: https://jira.springsource.org/browse/SPR-11472
Ok, until official fix in Spring 3.2.9 and 4.0.3 is released, I've overwritten Spring's default CustomCollectionEditor to fix this bug temporarily.
The only drawback is that you cannot use in your HTML code / HTTP request this:
&phoneNumber=0
&phoneNumber=1
&phoneNumber=2
&phoneNumber=3
but you rather need to index every parameter explicitly:
&phoneNumber[0]=0
&phoneNumber[1]=1
&phoneNumber[2]=2
&phoneNumber[3]=3
Multiple parameters without [] on the end are now simply ignored, see comment in the code below.
package xxx;
import java.util.List;
import org.springframework.beans.propertyeditors.CustomCollectionEditor;
/**
* @see <a href="https://jira.springsource.org/browse/SPR-11472">https://jira.springsource.org/browse/SPR-11472</a>
*/
public class CustomListEditorSPR11472 extends CustomCollectionEditor {
@SuppressWarnings("rawtypes")
public CustomListEditorSPR11472(Class<List> collectionType) {
super(collectionType);
}
@Override
public void setValue(Object value) {
/*
* Force Spring to ignore all HTTP request **MULTIPLE** parameters without "[]" on the end so that
* binder.setAutoGrowCollectionLimit() could work correctly. Example:
*
* phoneNumber[2]=2
* Above request is OK, a List containing: 'null, null, 2' is created.
*
* phoneNumber=2
* Above request is OK, **SINGLE** parameter without "[]", a List containing: '2' is created
*
* phoneNumber[0]=0&phoneNumber=1&phoneNumber=2
* **MULTIPLE** parameters without "[]" are ignored, a List containing: '0' is created.
*/
if ((value != null && value.getClass().isArray()) == false) {
super.setValue(value);
}
}
}
Of course, you also need to register your CustomEditor in your binder:
binder.registerCustomEditor(List.class, new CustomListEditorSPR11472(List.class));
or more fine-grained version for a single property:
binder.registerCustomEditor(List.class, "phoneNumber", new CustomListEditorSPR11472(List.class));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With