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
- perpetuals/contracts/src/core/components/assets/assets.cairo#L109-L145
- perpetuals/contracts/src/core/components/assets/assets.cairo#L350
- perpetuals/contracts/src/core/components/assets/assets.cairo#L708
- perpetuals/contracts/src/core/components/assets/assets.cairo#L746-L762
거버넌스는 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