14
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

nem / symbolAdvent Calendar 2022

Day 1

Catapult緊急フォークで修正された脆弱性を再現する

Last updated at Posted at 2022-11-30

突如として行われた緊急フォーク

2022/10/25、緊急のフォークが行われ、Catapult内の脆弱性が修正されました。具体的な内容はこちら

書いてあることは難しいですが、中々にヤバそうなことがかかれています。せっかくなのでこの修正された脆弱性を古いCatapultで再現してみましょう

問題のコード

脆弱性があったコードは次のものと書かれています

hashes.splice(i / 2, 0, this.hash([hashes[i], hashes[i + 1]]));

このコードではマークルコンポーネントハッシュの計算を行いますが、第2引数が0に指定されたことによって置き換えではなく挿入を行っていることがわかります。その結果最初のコンポーネントハッシュ2つの計算以外がうまく行えていなかったことがわかります。つまり、最初の2つのトランザクション以外は署名の検証がされていなかったということです。悪用ができる致命的なバグですね。

恥ずかしいことに、私は以前、署名の検証をするためにこのコードを見た覚えがあるのですが全く気づきませんでした。確かここの計算で詰まった覚えだけはあったのですが公式のコードを見て納得していた覚えがあります、悔しいです。次こそは見つけ出してやります。

再現する

symbol-bootstrapをフォーク前のバージョンに落とし、プライベートネットワークを構築します。フォーセットを作成して配布するのは面倒なので手数料は0にして起動しましょう

custom.yml
minFeeMultiplier: 0
symbol-bootstrap start -d -p bootstrap -a dual -c custom.yml

2つのインナートランザクションのアグリゲートコンプリートをつくってみます
今回はaがbに、bがaにそれぞれ"ping","pong"と送り合うトランザクションを作ってみました
sdkを利用する場合は古いものを使用しましょう(アグリゲートのバージョンの問題)

const aToB = TransferTransaction.create(
    Deadline.create(epochAdjustment),
    b_account.address,
    [],
    PlainMessage.create("ping"),
    networkType
).setMaxFee(feemultiplier);
const bToA = TransferTransaction.create(
    Deadline.create(epochAdjustment),
    a_account.address,
    [],
    PlainMessage.create("pong"),
    networkType
).setMaxFee(feemultiplier);
const bToA_2 = TransferTransaction.create(
    Deadline.create(epochAdjustment),
    a_account.address,
    [],
    PlainMessage.create("pongpong"),
    networkType
).setMaxFee(feemultiplier);
let init_tx = AggregateTransaction.createComplete(
    Deadline.create(epochAdjustment),
    [
        aToB.toAggregate(a_account.publicAccount),
        bToA.toAggregate(b_account.publicAccount),
    ],
    networkType,
    []
).setMaxFeeForAggregate(feemultiplier,1);
const s_init = a_account.sign(init_tx, networkGenerationHash);
const b_cosig = CosignatureTransaction.signTransactionPayload(b_account, s_init.payload, networkGenerationHash);
const final_stx = a_account.signTransactionGivenSignatures(
    init_tx,
    [
        b_cosig
    ],
    networkGenerationHash
);


const res = await transactionHttp.announce(final_stx).toPromise();

作成したfinal_stxのペイロードをbToA_2 のペイロードに差し替えます。
再現すると題名をつけておきながら申し訳ないのですが、差し替え方法は念の為、非開示とします

差し替え前、後のトランザクションをアナウンスします

すると差し替え前のトランザクションのみがアナウンスできます
スクリーンショット 2022-11-22 3.58.08.png

では3つのアグリゲートにしてみましょう
メッセージを新たに考えるのが面倒だったのでaが2回"ping"とおくるトランザクションにしました

const aToB = TransferTransaction.create(
    Deadline.create(epochAdjustment),
    b_account.address,
    [],
    PlainMessage.create("ping"),
    networkType
).setMaxFee(feemultiplier);
const bToA = TransferTransaction.create(
    Deadline.create(epochAdjustment),
    a_account.address,
    [],
    PlainMessage.create("pong"),
    networkType
).setMaxFee(feemultiplier);
const bToA_2 = TransferTransaction.create(
    Deadline.create(epochAdjustment),
    a_account.address,
    [],
    PlainMessage.create("pongpong"),
    networkType
).setMaxFee(feemultiplier);
let init_tx = AggregateTransaction.createComplete(
    Deadline.create(epochAdjustment),
    [
        aToB.toAggregate(a_account.publicAccount),
        aToB.toAggregate(a_account.publicAccount),
        bToA.toAggregate(b_account.publicAccount),
    ],
    networkType,
    []
).setMaxFeeForAggregate(feemultiplier,1);
const s_init = a_account.sign(init_tx, networkGenerationHash);
const b_cosig = CosignatureTransaction.signTransactionPayload(b_account, s_init.payload, networkGenerationHash);
const final_stx = a_account.signTransactionGivenSignatures(
    init_tx,
    [
        b_cosig
    ],
    networkGenerationHash
);

const res = await transactionHttp.announce(final_stx).toPromise();

公表されている脆弱性の内容通りであれば3つめ以降のトランザクションのペイロードを差し替えた場合成功するはずです。3つ目のペイロードを差し替え、アナウンスしてみます

差し替え前
スクリーンショット 2022-11-22 4.06.19.png

差し替え後
スクリーンショット 2022-11-22 4.06.09.png

通っちゃったよ....

無事(?)脆弱性があることを再現できました

バグは潰しきれない

symbolはローンチ前に負荷テストを行ったりしてバグをある程度潰していますが、一般的にテストはバグの存在を示すことはできてもバグが存在しないことを示すことはできません。よって、これからも何らかのバグが見つかるかもしれません。バグが見つかった際はすぐにアップデートを行い、対応するようにしましょう

14
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?