The GmxProxy contract uses tx.origin for access control in its setPerpVault() function:
This is insecure because an attacker can deploy a malicious contract and trick the genuine owner into calling setPerpVault() through it, thereby impersonating the owner. Since tx.origin remains the actual owner’s externally owned account (EOA), the malicious contract’s call will pass the require(tx.origin == owner(), ...) check. As a result, the attacker can set the perpVault to any address of their choosing, potentially hijacking the contract’s functionality.
tx.origin vs. msg.sender: In Solidity, tx.origin is the original EOA that initiated the transaction, while msg.sender is the immediate caller. Access control using tx.origin is widely discouraged because any intermediate contract can forward the call, retaining the same tx.origin.
Phishing scenario: A malicious dApp or contract can trick the real owner into calling a function on this malicious contract, which in turn calls setPerpVault() on GmxProxy. The require(tx.origin == owner()) check succeeds because the real owner is still the tx.origin, even though the immediate caller (msg.sender) is the malicious contract.
By passing the ownership check, the attacker can:
Set perpVault to a malicious address they control.
Potentially disrupt or redirect the contract’s main functionality, especially if perpVault is critical for handling positions, tokens, or settlement logic.
Carry out further privilege escalation or malicious transactions using the new perpVault.
The result can be a loss of control over the contract’s core operations, which is a severe security breach.
Proof of Concept (PoC)
Below is a minimal example showing how an attacker could abuse tx.origin by deploying a deceptive contract named GmxPr0xy (notice the zero in place of the letter “o”) that forwards calls to GmxProxy.setPerpVault(). The real owner is tricked into calling the malicious function, causing tx.origin to be the legitimate owner’s address but msg.sender to be the malicious contract.
Deployment: Attacker deploys GmxPr0xy (the phishing contract) with the legitimate GmxProxy address in the constructor.
Phishing: Attacker lures the legitimate owner to call maliciousSetPerpVault() on GmxPr0xy, under false pretenses (e.g., a “claim rewards” button on a malicious dApp).
Execution: When the owner sends the transaction, tx.origin is owner, but msg.sender is GmxPr0xy. Inside setPerpVault(), the check require(tx.origin == owner(), "not owner") succeeds. This lets the phishing contract forcibly set perpVault to any address the attacker wants.
Result: The attacker has effectively hijacked the GmxProxy by redirecting or controlling its core functionality, likely enabling theft of funds or manipulation of positions.
Manual review.
Replace tx.origin with msg.sender:
Always use msg.sender for access control checks. For instance:
This ensures only the direct caller (the real user or the contract they’re interacting with) can pass the ownership check, preventing a malicious contract from impersonating the owner.
Lightchaser: Medium-5
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.