2021/12/16 平見知久
Log4j JndiLookupを抜いたときの挙動
ここのところホットなLog4jで見つかった脆弱性ですが、Log4j バージョン2.10より前のときの対策で、「JndiLookupクラスをクラスパスから削除する」というのを見て、ぱっと直感的に「抜いて大丈夫なのかな?なんか変な挙動起こさないかな?」と疑問に思ったので軽くソースを覗いてみました。
ちなみにOTRS, OTOBOはperlとJavascriptで書かれたアプリですのでLog4J(というかJava)の脆弱性の影響は受けません。ただ、OTOBOでは検索の高速化の機能のためにElasticsearchと連携できるようになっています。ElasticsearchはJavaでLog4jも使用していますので影響を受けることになります。また、OTRSでも弊社独自で用意したElasticsearch連携のアドオンが存在しており、その関連で気になった次第です。
お断り: 軽くソースを覗いた内容ですので、その真正性はあくまでご自身でご判断ください。本記事を信じて行ったこと/行わなかったことに対するいかなる責任も負いませせん。テストは大事です。
ソースは諸々の事情から、Log4j 2.8.2を使っています。
展開してgrepすると、問題のJndiLookupはlog4j-core.jarの、org/apache/logging/log4j/core/lookup/JndiLookup.javaが該当します。同列のlookupディレクトリを見ると、AbstractLookupを筆頭にDateLookupとか、EnvironmentLookup, JavaLookup等と並んでいますので、おそらくAbstractLookupを基底クラスにしたプラグインっぽい実装になっていそうです。
JndiLookup.javaを見てみると、クラスの出だしが次の通り。
@Plugin(name = "jndi", category = StrLookup.CATEGORY)
public class JndiLookup extends AbstractLookup {
やっぱりAbstractLookupを基底にして、まんまPluginというアノテーションついていますので、扱い的にはプラグイン、と。
そうするとどこからロードしているかが気になりますのでソースに対してgrepかけてみると、テストコードやドキュメント類を除くとorg/apache/logging/log4j/core/lookup/Interpolator.javaがクラスを呼んでいます。
public Interpolator(final Map<String, String> properties) {
this.defaultLookup = new MapLookup(properties == null ? new HashMap<String, String>() : properties);
// TODO: this ought to use the PluginManager
lookups.put("log4j", new Log4jLookup());
lookups.put("sys", new SystemPropertiesLookup());
lookups.put("env", new EnvironmentLookup());
lookups.put("main", MainMapLookup.MAIN_SINGLETON);
lookups.put("marker", new MarkerLookup());
lookups.put("java", new JavaLookup());
// JNDI
try {
// [LOG4J2-703] We might be on Android
lookups.put(LOOKUP_KEY_JNDI,
Loader.newCheckedInstanceOf("org.apache.logging.log4j.core.lookup.JndiLookup", StrLookup.class));
} catch (final LinkageError | Exception e) {
handleError(LOOKUP_KEY_JNDI, e);
}
// JMX input args
他のLookupプラグインはともかく、JndiLookupについてはエラーハンドリングされていて、クラスが読めなかったらhandleErrorで処理しているみたいです。JavaLooupクラス等を抜いてしまうとロード時にエラーで落ちますが、このコードの書き方はない場合にはないなりに動かそうという書き方です。
handleErrorはというと、特に落ちたりせずに警告ログだけ出して終わっています。
private void handleError(final String lookupKey, final Throwable t) {
switch (lookupKey) {
case LOOKUP_KEY_JNDI:
// java.lang.VerifyError: org/apache/logging/log4j/core/lookup/JndiLookup
LOGGER.warn( // LOG4J2-1582 don't print the whole stack trace (it is just a warning...)
"JNDI lookup class is not available because this JRE does not support JNDI." +
" JNDI string lookups will not be available, continuing configuration. Ignoring " + t);
break;
ちなみに、大本のInterpolatorについてはどこから呼ばれてるかというと、何か所かありますが、org/apache/logging/log4j/core/config/AbstractConfiguration.java あたりでクラスのメンバ変数の初期化でばっちり呼ばれています。
private final StrLookup tempLookup = new Interpolator(properties);
AbstractConfigurationの名の通り、Log4Jの設定の基底クラスですから、ライブラリの初期化さえ通ってしまえばまずまず問題はなさそうです。
まとめると
- 問題を起こしているJndiLookupクラスについてはプラグインの1種として扱われている(=必須機能として扱われていない)
- もともと存在しない場合でも大丈夫なようなコードになっている
- ライブラリの初期化さえ通ってしまえば後で問題になる可能性は低い
ということで、提示されている対処通り、「JndiLookupクラスをクラスパスから削除する」という対処で問題なさそうということで個人的には納得です。
ちなみに、クラスパスを削除するといってもJndiLookup.classはlog4j-core.jarの中に入っていますので、実質jarから該当クラスを抜くという操作になります。
いくつか思いついたり探したりした中ではjarは実質zipファイルですので、
# zip -d log4j-core-2.8.2.jar org/apache/logging/log4j/core/lookup/JndiLookup.class