問題の事象
StrutsではDOS攻撃対策として要素数が256件を超えるCollection型の項目をPOSTするとエラーが発生する。
public class HelloAction extends ActionSupport {
private List<String> addresses;
public List<String> getAddresses() {
return this.addresses;
}
public setAddresses(List<String> addresses) {
this.addresses = addresses;
}
public String execute() {
if (this.addresses.isEmpty()) {
for (int i = 0; i < 300; i++) {
this.addresses.add("Address" + i);
}
}
return SUCCESS;
}
}
<s:form action="hello" method="post">
Address:<br/>
<s:iterator value="addresses" status="status">
<s:textfield name="addresses[%{#status.count - 1}]"/><br/>
</s:iterator>
<s:submit type="input" />
</s:form>
submitすると256個以降がエラーになる
このチェックはcom.opensymphony.xwork2.ognl.accessor.XWorkListPropertyAccessorクラスで行われている。
if (target instanceof List && name instanceof Number) {
List list = (List)target;
int listSize = list.size();
count = ((Number)name).intValue();
if (count > this.autoGrowCollectionLimit) {
throw new OgnlException("Error auto growing collection size to " + count + " which limited to " + this.autoGrowCollectionLimit);
}
if (count >= listSize) {
for(i = listSize; i <= count; ++i) {
list.add((Object)null);
}
}
}
対策
考えられる対策方法を以下に記す
- 定数struts.ognl.autoGrowthCollectionLimitを変更する
- Type Conversionを使う
- Mapに変更する
- XWorkListPropertyAccessorの代わりにListPropertyAccessorを使用する
定数struts.ognl.autoGrowthCollectionLimitを変更する
公式HPに記載されているとおりCollectionの自動拡張の上限を変更する。一番簡単な方法と思うが、Listを使っている箇所すべてで制限が緩くなるのが欠点だと思う。
<constant name="struts.ognl.autoGrowthCollectionLimit" value="1024"/>
Type Conversionを使う
Type Conversionを使えば特定の項目のみに限定して制限をなくすことができる。XWorkListPropertyAccessorクラスはType Conversionを使用した場合には上限を適用しないことを利用する。
List<String>のように要素が書き込み不可の場合は適用できない。
// Listに格納するJavaBean
public class Address {
private String id;
private String address;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
package org.demo;
public class HelloAction extends ActionSupport {
private List<Address> addresses;
public List<Address> getAddresses() {
return this.addresses;
}
public setAddresses(List<Address> addresses) {
this.addresses = addresses;
}
public String execute() {
// 初期表示時に要素を300個にする
if (this.addresses.isEmpty()) {
for (int i = 0; i < 300; i++) {
Address address = new Address();
address.setId("id" + i); // jspで丸括弧を使う書き方をする場合は必要
this.addresses.add(address);
}
}
return SUCCESS;
}
}
<s:form action="hello" method="post">
Address:<br/>
<s:iterator value="addresses" status="status">
<s:textfield name="addresses[%{#status.count - 1}].address"/><br/>
<!-- ↓丸括弧を使う書き方も可能 -->
<!-- <s:textfield name="addresses(%{'\\'id' + (#status.count - 1) + '\\''}).address"/><br/> -->
</s:iterator>
<s:submit type="input" />
</s:form>
以下はHelloAction.javaと同じフォルダに置くか、src/main/resources配下にHelloActionと同じパッケージ階層のフォルダを作成して配置する。
KeyProperty_addresses=x ←デタラメでよい。丸括弧を使う書き方の場合はidのようなキーにする項目を指定する
Element_addresses=org.demo.Address
CreateIfNull_addresses=true
以下のような画面となり、submitしてもエラーはならない。
Mapに変更する
Collectionと違ってMapには上限が適用されないためMapにすることで解決する。
Stringでも使用できる。
package org.demo;
public class HelloAction extends ActionSupport {
private Map<Integer, String> addresses;
public Map<Integer, String> getAddresses() {
return this.addresses;
}
public setAddresses(Map<Integer, String> addresses) {
this.addresses = addresses;
}
public String execute() {
// 初期表示時に要素を300個にする
if (this.addresses.isEmpty()) {
for (int i = 0; i < 300; i++) {
this.addresses.put(i, "");
}
}
return SUCCESS;
}
}
<s:form action="hello" method="post">
Address:<br/>
<s:iterator value="addresses" status="status">
<s:textfield name="addresses[%{#status.count - 1}]"/><br/>
</s:iterator>
<s:submit type="input" />
</s:form>
XWorkListPropertyAccessorの代わりにListPropertyAccessorを使用する
大げさかもしれないがOGNLが使用するAccessorを変更することで上限を適用しないようにする。
ArrayListを継承したカスタムリストを作成
package org.demo;
import java.util.ArrayList;
public class MyList<T> extends ArrayList<T> {
}
カスタムリストに対しては上限チェックを行わないAccessorを使用する
<bean type="ognl.PropertyAccessor" name="org.demo.converter.MyList"
class="ognl.ListPropertyAccessor"/>
package org.demo;
public class HelloAction extends ActionSupport {
private MyList<String> addresses = new MyList<>();
public MyList<String> getAddresses() {
return this.addresses;
}
public void setAddresses(MyList<String> addresses) {
this.addresses = addresses;
}
public HelloAction() {
// 自動的にリストを拡張してくれないのでコンストラクタで300個にする
for (int i = 0; i < 300; i++) {
this.addresses.add("");
}
}
public String execute() {
return SUCCESS;
}
}
<s:form action="hello" method="post">
Address:<br/>
<s:iterator value="addresses" status="status">
<s:textfield name="addresses[%{#status.count - 1}]"/><br/>
</s:iterator>
<s:submit type="input" />
</s:form>
リストを自動的に拡張してくれないのがいまいちだ。