このドキュメントとリポジトリは、CVE−2022-3602の書き込みであり、punycodeバッファです。 OpenSSL のオーバーフローの問題。これは「アンチPOC」です(問題は 悪用可能)独自のOpenSSLビルドを維持する人々を対象としており、 コンパイラメンテナ。
同じリリースには別のCVEがあります, CVE-2022-3786, これもにつながります バッファオーバーフローしますが、その場合、攻撃者はコンテンツを制御できません。そこ ここではその問題の再現はありませんが、その問題はの拒否につながる可能性があります クラッシュによるサービス。
クラッシュやバッファオーバーフローは決して良くなく、OpenSSL 3.0.xを使用している場合は、 できるだけ早く更新するのが賢明です。
エラーや脱落は、GitHubの問題やプルリクエストを介して自由に報告してください。
punycodeの処理方法には1つずつ問題があります デコードすると、4 バイトのオーバーフローが発生します。この問題は、次の場合にのみ妥当です。 OpenSSL は証明書チェーンを処理し、2 つの条件を必要とします。まず、 チェーン内の CA 証明書または中間証明書には、名前制約フィールドが含まれている必要があります それはプニーコードを使用します。
ossl_punycode_decode
nameConstraints = permitted;email:xn-maccrthaigh-n7a.com
次に、リーフ証明書にはサブジェクト代替名 (SAN) が含まれている必要があります。 SmtpUTF8メールボックス文字列を指定する他の名前フィールド。
otherName = 1.3.6.1.5.5.7.8.9;UTF8:admin@xn-maccrthaigh-n7a.com
トリガーされると、名前制約フィールド内のプニーコードですが、プニーコードは含まれません 他の名前フィールドでは、脆弱なOpenSSLプニーコードによって処理されます パージング。
デビッド・ベンジャミンとマット・キャスウェルは、名前制約チェックが行われると判断しました 通常の証明書チェーン検証と署名検証の後。対して ほとんどのアプリケーションでは、この問題は、 自己署名証明書または無効なチェーン。
openssl'sandアプリケーションが意図されていることに注意してください デバッグ用であり、チェーンが無効なときに処理を停止しないでください。
s_client
s_server
信頼できるCAまたは中間には、悪意のあるペイロードが含まれている必要があります。 また、問題を引き起こすリーフ証明書に署名する必要があります。
信頼できないパーティが CA または 仲介者 (顧客提供をサポートするホスティング サービスなど) プライベート CA ですが、これは一般的ではありません。
多くのアプリケーションの答えは、コンパイラがどのように持っているかのために「いいえ」になります スタックをレイアウトし、次のような他の保護が存在するため スタックカナリア/スタッククッキー、パディング、PIE、FORTIFY_SOURCE。
この問題は、スタック上の32ビットのオーバーフローにつながります。これはそうではありません シェルコードを直接実行するのに十分ですが、コントロールを変更するのに十分かもしれません アプリケーションのフロー。たとえば、シェルコードにジャンプします X509証明書チェーンに埋め込まれているのは、このデータも 実行可能な場所のスタックにコピーされます。
私がテストしたすべてのLinuxプラットフォームで、オーバーフローはパディングに発生し、 無害な。理論的には、コンパイラはオーバーフローのような変数をレイアウトすることができます 関数内の他の変数の 1 つに発生します。
ossl_a2ulabel
インライン化に応じて、存在する変数の完全なリストは次のとおりです。
outptr, inptr, size, result, tmpptr, delta, seed, utfsize
特権昇格への明白な道を提供するように私には見えませんか、または 興味深いコントロール。
複製を作成するために使用できるツールを備えたtarballを取り付けました。 は、4 バイトすべてを可能な限り制御してオーバーフローします。ザ 参照再生文字列 () が 4 バイトをオーバーフローします を値に置き換えます。それでもアプリケーションがクラッシュしない場合は、 そのアプリケーションが脆弱ではない可能性があります(おそらく?)。
xn--ww90271...aaaa
0xFF 0x0F 0x0F 0x0F
シェルスクリプトを使用して、悪意のある証明書チェーンを生成できます。 悪意のある CA 証明書がトリガー リーフから生成される 証明書はから生成されます。
run-poc
ca.cnf
leaf.cnf
CA 証明書は、次の参照ペイロードを使用します。
xn--ww902716aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Pythonスクリプトを使用して、さまざまな他のpunycode文字列を生成できます ペイロード。
実行すると、opensslクライアントとサーバーを実行し、 この問題を10回悪用します。
run-poc
脆弱なOpenSSLがクラッシュする可能性があります。それはのバージョンを意味するものではありません OpenSSL は、スタックカナリアおよびスタッククッキー保護として、RCE に対して脆弱です。 また、通常、(より安全な)アプリケーションのクラッシュを引き起こします。また、これは 同じリリース内の他の CVE の重大度を変更します。
4つのオーバーフローバイトすべてをほぼ完全に制御するのは驚くほど微妙です OpenSSLのpunycodeデコーダーを非標準/無効なものに悪用する必要があります プニーコード。添付のtarballには、 ニュアンスを処理する文字列。以下は、それがどのように機能するかの説明です。
セキュリティの問題は
ossl_punycode_decode()
int ossl_punycode_decode(const char *pEncoded, const size_t enc_len, unsigned int *pDecoded, unsigned int *pout_length)
ossl_punycode_decodeから呼び出されます。バッファ は、X509証明書からの多かれ少なかれ任意のサイズのバッファです 鎖。これは、名前制約フィールドの "xn--" の後に来る部分です。見る このような証明書チェーンを再現する方法の【再現】です。
ossl_a2ulabel
pEncoded
pDecodedは512 ofs.is 配列のサイズであり、ほとんどのプラットフォームではanisは次のようになります。 4 バイト幅。したがって、ほとんどのプラットフォームでは長さは2048バイトです。
LABEL_BUF_SIZE
unsigned int
LABEL_BUF_SIZE
unsigned int
pDecoded
内部問題の核心は、この誤った長さチェックです。
ossl_punycode_decode()
if (written_out > max_out)
max_outに対応しますこれは常に512です。そして、書き込まれた数を追跡します。 なぜなら、後で書き込んだ後にのみインクリメントされるため、この欠陥があります チェックは513への書き込みを許可します。最終結果 こんな感じ...
*pout_length
written_out
unsigned int
pDecoded
written_out
unsigned int
pDecoded
pDecoded = [ ... , 'X , 'Y' , 'Z' ] 'P' // Indices 509 510 511
ここでは、Cの規則に従って、インデックスはゼロインデックスであるため、スロット番号511は array.is の512番目の要素は、出力された4バイトのペイロードです。 の境界、スタックに割り当てられたスペースを超えてバッファは指すものです。
'P'
buf
ossl_a2ulabel()
pDecoded
4バイトは小さなオーバーフローであり、ノップスレッドを運ぶには不十分です。 シェルコードを直接実行しますが、の制御フローを変更するのに十分です アプリケーション。たとえば、に埋め込まれているシェルコードにジャンプする X509証明書チェーンは、このデータ(またはコピー)の方法に応じて可能になる場合があります このデータの断片)が格納され、そのメモリが実行可能かどうか。 ただし、攻撃者になる可能性のある人にとっては、さらに多くの困難があります。
まず、コンパイラのパディングとスタックの配置、または次のような防御 スタックカナリアは、搾取を完全に不可能にする可能性があります。
第二に、へのパスは1つしかなく、このパスは スタック上のバッファー。これにより、この問題が 異なるメモリ位置での同時 4 バイト オーバーフロー。
ossl_punycode_decode()
Punycode 文字列には基本的に 2 つの形式があります。1つは(點看)と もう一つは(マッカータイ)。の後に来る部分 最後の区切り文字は、任意のユニコードコードポイントの36aryブート文字列エンコーディングです 基本的な通常のASCIIと、それらを挿入する文字列の位置ではありません。 ここで重要なのは、デコード プロセスによって 2 つの値が生成されることです。1つは挿入されるコードポイント値である「n」であり、もう1つは「i」です。 挿入するバッファー内の位置。
xn--c1yn36f
xn--maccrthaigh-n7a
-
ossl_punycode_decode()
unsigned int
書き込みは2つの異なる方法で行うことができます。真ん中のどこかにイフィス 文字列の次に、コピーによって最初に「スペースを作る」ものがあります すべてを右の1つのスロットに:
i
memmove()
memmove(pDecoded + i + 1, pDecoded + i, (written_out - i) * sizeof *pDecoded);
そしてそれは書きますそれが作ったばかりのスペースに:
n
pDecoded[i] = n;
文字列の末尾にあるifisの場合、効果はありません。 最後のパラメーターは 0 になります。もう一方の行は単純な追加になります。
i
memmove()
次に、ペイロード「P」を取得するための3つの異なる方法を見ていきます。 オーバーフロー位置と制約が発生する理由。
オーバーフローをトリガーする最も簡単な方法は、次のような punycode 文字列を作成することです。 その中に511のASCII文字、および2つの非ASCII文字。プニーコードエンコーディング "ÁÁAAAAAAAAA...AAA」でいいでしょう。この場合 何が起こるかは、いつIS510バッファが配置されるかです として...
written_out
pDecoded = [ 'A' , 'A' , ... , 'A' , 'A', ] // Indices 0 1 ... 509 510 511
これは、コピーされた基本的なASCII文字です。次に、解析します punycode ブート文字列を挿入し、アナット位置 0 を挿入します。それは可能性がありますが 0 以上 511 以下の任意の位置。
'Á'
pDecoded = [ 'Á' , 'A' , ... , 'A' , 'A', 'A' ] // Indices 0 1 ... 509 510 511
次に、これを繰り返します。
pDecoded = [ 'Á' , 'Á' , ... , 'A' , 'A', 'A' ] 'A' // Indices 0 1 ... 509 510 511 512
これにより、通常のASCII 'A'が「移動」されるときにオーバーフローします。ザ この場合のペイロードは4バイトとなります。これから説明するように、 Punycodeがどのように機能するかにより、これは最終的な値を持つ唯一の方法です ASCII 範囲のバイトを表すことができます。
0x00 0x00 0x00 0x41
正しい境界チェックがあるため、2つの非ASCII文字を使用する必要があります 基本文字の数については、512未満である必要があります。
最後のバイト値を46にできないという追加の制約は、文字列の前の部分で呼び出されるという理由で発生します。 リテラル文字。Punycodeはドメインラベル用です。 それらの中のドット。
ossl_punycode_decode()
.
オーバーフローをトリガーする次の最も簡単な方法は、513文字を作成することです。 末尾に非 ASCII 文字を含む文字列。何かのようなもの 「あぁぁあぁ�この場合、最後の2つのステップは次のようになります。
pデコード = [ 'A' , 'A' , ... , 'A' , 'A' ] インデックス 0 1 ...510 511
そして
pDecoded = [ 'A' , 'A' , ... , 'A' , 'A' ] 'Á' // Indices 0 1 ... 510 511
非ASCII文字は、オーバーフロー位置に直接移動します。ザ OpenSSLプニーコードパーサーは、ここでのオーバーフロー値が 実際には有効なユニコード文字です。それは多かれ少なかれバイナリデコッキングです 過程。しかし、punycodeデコードのニュアンスは、方法2が 最初に表示されるかもしれない柔軟性。
punycode では、値と両方とも単一の可変長としてエンコードされます base36 を使用して ASCII エンコードされる整数。それは不可能に思えるかもしれません 2つの無関係な数値を単一の整数としてエンコードしますが、巧妙なトリックpunycode hasは、文字列の長さ(これまでのところ)を隠しフィールドとして使用することです。
n
i
たとえば、4つの基本文字を含むpunycode文字列があるとします。 そして1つの非基本的なものが好きです。これは、最初は単に 基本キャラクター....'Á' ユニコード値は 225 で、その位置は 文字列2で。秘訣は、値に長さに1を加えたものを掛けることです。 次に、位置を追加します。したがって、((225 *(4 +1))+ 2)になり、1127になり、 それがエンコード方法です(可変長ベース36)。
AAÁAA
AAAA
デコードするには、逆の方法に進みます。1127 / 5は225であり、1127%5は2である。こうやって 1 つから 2 つの数値を回復します。ただし、文字列が長くなるほど、 あなたは値の大きさ、または倍数でより制約を受けます 符号なし整数には収まりません。一般に、文字列が M 文字の長さの場合 次に、値から幅のログMビットを失います。
512番目の整数を処理するまでに、9ビットの幅が失われます。使用 方法2、一見32ビットペイロードの最大値は実際には 2^23。3バイトもいません。方法2は最適ではありません。
4バイトの制御を取り戻すための最も効率的な手段は、 ペイロード文字を何度も繰り返します。これまでのところ、他の2つの関連する詳細を省略しました プニーコードがどのように処理されるかの。
最初の詳細は、非ASCII文字が文字列順にエンコードされないことです。 代わりにエンコードされているのは値の昇順です。文字列 "ÉÁ" は終了します up は "位置 1 の Á、位置 0 の É" としてエンコードされます。 値 (225) は É (233) よりも優れています。
2番目の詳細は、非ASCII文字がリテラルとしてエンコードされないことです 値ですが、最後にデコードされた値に対する相対的なデルタとして。以来、 最初の値には相対的な以前の値がなく、ハードコードされています 128の出発点。
These little nuances make punycode very space-efficient, but also mean that a non-ascii character simply can't be decoded to a value lower than 128. The smallest delta is 0, and there is no way to express a negative delta. So if you want a number less than 128, you have to use method 1.
It also means that the best strategy for as much control over the payload as possible is to make the payload the only value in the full string, as that way we get the full width to work with from its place at the 0th position in the encoding. The string you encode ends up looking like;
[ 'P', 'P', ... 'P', 'P', 'P' ] 0 1 510 511 512
これはOpenSSLによってデコードされます...
pDecoded = [ 'P', 'P', ... 'P', 'P' ] 'P' 0 1 510 511 512
オーバーフロー位置内にあり、任意の値を表すことができます 128 と (2^32 - 1) の間。
P
これにはすべて、非標準のpunycodeエンコーダーが必要であり、 方法1または方法のいずれかを使用してペイロードを作成できるスクリプト 必要に応じて3つ。
OpenSSL の更新以外に、他の軽減策はありますか?
証明書チェーンは、ほとんどの環境でクリアテキストで渡され、 悪意のあるチェーンは、を含むTCP接続を拒否することでブロックされる可能性があります DERエンコードされたNIDをフィールドに入れました。
1.3.6.1.5.5.7.8.9
SubjectAlternateName
OtherName
残念ながら、このフィールドは2つ以上の間で任意に分割できます パケットと実際にはある種のステートフルパターンマッチャーをブロックする必要があります。 証明書も圧縮できますが、OpenSSL 3.0.x はサポートしていません この時点での証明書の圧縮。
さらに、TLS1.3クライアント証明書チェーンはネットワーク上で暗号化され、 以前のバージョンの TLS は、次の場合に暗号化された証明書チェーンをサポートします。 既存の接続を再ネゴシエーションします。これは時々行われます サーバーによって開始される証明書認証。ネットワークフィルタは そのような場合に効果的です。
静的にリンクされたバイナリでopenssl 3を使用しているかどうかはどうすればわかりますか?
readelf -a [binary] | grep -i ossl_punycode_decode
静的にリンクされたバイナリで脆弱な関数を検索します。唯 OpenSSL >= 3.0 にはこの関数が含まれています。