code4rena-2025-03-starknet-perpetual-h01

[H-01] A malicious signed price can be injected in assets.price_tick()

보고서

Summary

맵을 조회할 때, 등록된 적 없는 key로 조회해도 트랜잭션을 취소시키지 않는다. 이로 인해 임의의 키로 서명하여 가격 오라클을 조작할 수 있다.

Keyword

signature, oracle manipulation, price oracle

Vulnerability

거버넌스는 assets.add_oracle_to_asset() 를 호출하여 각 자산의 오라클을 등록할 수 있다. 특정 자산에 대한 오라클은 asset_oracle_entry[asset_id][oracle_public_key]자산 이름 + 오라클 이름 을 기록한다.

fn add_oracle_to_asset(
    ref self: ComponentState<TContractState>,
    asset_id: AssetId,
    oracle_public_key: PublicKey,
    oracle_name: felt252,
    asset_name: felt252,
) {
    // ...
    // Validate the oracle does not exist.
@>  let asset_oracle_entry = self.asset_oracle.entry(asset_id).entry(oracle_public_key);
    let asset_oracle_data = asset_oracle_entry.read();
    assert(asset_oracle_data.is_zero(), ORACLE_ALREADY_EXISTS);
    // ...
    // Add the oracle to the asset.
    let shifted_asset_name = TWO_POW_40.into() * asset_name;
@>  asset_oracle_entry.write(shifted_asset_name + oracle_name);
    // ...
}

assets.price_tick() 함수를 통해 자산의 가격을 업데이트하며, 가격을 등록하기 위해서는 각 자산에 등록된 오라클의 서명이 필요하다.

서명을 확인하기 위해서 assets._validate_oracle_signature() 함수가 이용된다. 이 함수는 파라미터로 전달된 signed_price.signer_public_key 를 이용하여 asset_oracle_entry[asset_id][signer_public_key] 매핑을 읽고, 읽은 값(자산 이름 + 오라클 이름)을 업데이트할 가격 및 타임스탬프와 함께 해시한 후, 이 해시에 대한 서명이 유효한지 확인한다.

fn _validate_oracle_signature(
    self: @ComponentState<TContractState>, asset_id: AssetId, signed_price: SignedPrice,
) {
@>  let packed_asset_oracle = self
        .asset_oracle
        .entry(asset_id)
        .read(signed_price.signer_public_key);
    let packed_price_timestamp: felt252 = signed_price.oracle_price.into()
        * TWO_POW_32.into()
        + signed_price.timestamp.into();
@>  let msg_hash = core::pedersen::pedersen(packed_asset_oracle, packed_price_timestamp);
@>  validate_stark_signature(
        public_key: signed_price.signer_public_key,
        :msg_hash,
        signature: signed_price.signature,
    );
}

그러나 제공된 signed_price.signer_public_key가 스토리지에 존재하는 키인지에 대한 검증은 수행하지 않는다. 임의의 서명자 키로 조회했을 때, 트랜잭션을 취소시키는 대신 빈 packed_asset_oracle을 반환한다. 이로 인해 임의의 signed_price.signer_public_key가 0인 패킹된 자산 및 오라클 이름에 대한 서명을 생성하면 서명 확인을 우회할 수 있다.

Impact

서명 확인을 우회하고 가격 오라클을 업데이트 할 수 있음. 오라클을 조작할 수 있음

Mitigation

존재하지 않는 키로 asset_oracle 를 조회했을 때 트랜잭션을 취소시킨다.


tags: bughunting, starknet, smart contract, cairo, perpetual, severity high, signature, oracle manipulation, price oracle