S2-020類似攻撃のStruts1での対策方法

  • 91
    いいね
  • 21
    コメント
この記事は最終更新日から1年以上が経過しています。

恐ろしいことですが、実装が全然違うStruts2の脆弱性S2-020と同様の攻撃手法で、Struts1も脆弱性があることが分かりました。
http://www.lac.co.jp/security/alert/2014/04/24_alert_01.html

ここではあまり明らかになっていませんが、原因は

https://github.com/apache/struts1/blob/STRUTS_1_2_BRANCH/src/share/org/apache/struts/util/RequestUtils.java#L493

RequestUtils.java
        // Set the corresponding properties of our bean
        try {
            BeanUtils.populate(bean, properties);
        } catch(Exception e) {
            throw new ServletException("BeanUtils.populate", e);
        } finally {
            if (multipartHandler != null) {
                // Set the multipart request handler for our ActionForm.
                // If the bean isn't an ActionForm, an exception would have been
                // thrown earlier, so it's safe to assume that our bean is
                // in fact an ActionForm.
                ((ActionForm) bean).setMultipartRequestHandler(multipartHandler);
            }
        }

BeanUtils.populate(bean, properties)で値を検証すること無く、populateを呼んでることに起因します。

populateはBeanUtilsBeansetPropertyを順次呼び出していくわけですが、ここに問題があります。
BeanUtilsの仕様にNestedPropertyというのがあって、要はA.B.Cとプロパティをドットつなぎで書いておくと、bean.getA().getB()と順次呼び出され、最後にsetC()で値がセットされます。ここがノーチェックなので、class.classLoader.などと書いておくと、getclass().getClassLoader()と等価であり、クラスローダにもアクセスできてしまうことになるわけです。

なので、対策としてはStruts1というよりは、commons-beanutilsにチェックロジックを入れるべきかと思います。逆にいうとStruts1使っていなくても、commons-beanutilsのpopulateをユーザ入力値ノーバリデーションで使っていれば、同じ脆弱性が存在することになります。

commons-beanutilsのバージョン1.8.xからは、このプロパティを辿っていくロジックが定義できるようになりました。したがって、これを置き換えてしまえば、脆弱性はなくなるはずです。

https://gist.github.com/kawasima/11275430

(4/30 修正) nakamura-toさんにオシャレにしていただいたのでそちらをお使いください。
https://gist.github.com/nakamura-to/11347570

package example;

import org.apache.commons.beanutils.expression.DefaultResolver;

public class SafeResolver extends DefaultResolver {

    @Override
    public String next(String expression) {
        String property = super.next(expression);
        if ("class".equalsIgnoreCase(property)) {
            return "";
        }
        return property;
    }
}

(4/30 追記)
上記のBeanUtilsに対する修正は、Struts1だけでなく独自にユーザ入力値をBeanにマッピングするような処理にも有効ですが、ことStruts1だけの対応であれば、以下に示すようにRequestProcessorを置き換えることで脆弱性対応可能です。
古いシステムに対してBeanUtilsのバージョンアップがちょっとハードル高い(対応に時間がかかる)という声があったので、そういう場合にお使い頂ければよいかと思います。

@yuuyu さんからの指摘を受けて書き直しました。

https://gist.github.com/kawasima/c22a1c706656e4004d41

これで、struts-config.xmlで、このRequestProcessorを使うようにしておけば、悪意のあるパラメータにはServletExceptionがthrowされるようになります。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE struts-config PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
        "http://struts.apache.org/dtds/struts-config_1_2.dtd">

<struts-config>
    <controller processorClass="example.SafeRequestProcessor"/>
</struts-config>

そしてこれであれば、本記事のコメントServletFilterによる対処で指摘のあったMultipartリクエストに対しても、有効かと思います。


ちなみに、SAStrusはpublicフィールドもプロパティとして扱うため、BeanUtilsは使っていなく、かつ、
SnapCrab_NoName_2014-4-25_10-58-36_No-00.png

という状況なので、おそらく大丈夫かと思われます。

(4/30 追記) ひがさんからSAStrutsは大丈夫という声明が発表されております。
http://d.hatena.ne.jp/higayasuo/20140425/1398403491