Lines of code
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Broker.sol#L116-L118
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Broker.sol#L97-L107
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Broker.sol#L51-L61
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Broker.sol#L68-L71
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Broker.sol#L124-L128
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Broker.sol#L97-L98
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Broker.sol#L126-L127
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Broker.sol#L41
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Broker.sol#L85-L87
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Broker.sol#L85
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Broker.sol#L4
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Broker.sol#L21
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Broker.sol#L85
https://github.com/reserve-protocol/protocol/blob/df7ecadc2bae74244ace5e8b39e94bc992903158/contracts/p1/Broker.sol#L15
Vulnerability details
Impact
gnosis
variable is not protected by the onlyOwner
modifier, so it may be possible for a malicious actor to alter the Gnosis
contract being used by the smart contract, potentially leading to unauthorized
trades or funds being transferred.
If a malicious actor is able to alter the Gnosis
contract being used by the smart contract, it could potentially lead to unauthorized trades or funds being transferred, potentially resulting in financial loss for the users of the contract.
Altering the Gnosis
contract being used by the smart contract could lead to unauthorized
trades or funds being transferred, potentially resulting in a financial loss for the users of the contract, which could be a serious issue.
Proof of Concept
An attacker could potentially exploit the vulnerability of the 'gnosis' variable not being protected by the 'onlyOwner' modifier by calling the 'init' function with an address that they control as the 'gnosis_' parameter. This would allow them to redirect trades and potentially steal funds. Here's an example code snippet that demonstrates the general concept of the attack:
contract Attacker {
function attack(address gnosis, address broker) public {
IBroker b = IBroker(broker);
b.init(..., gnosis, ...);
}
}
Tools Used
Manual audit, Vs Code
Recommended Mitigation Steps
One way to mitigate the vulnerability of the 'gnosis' variable not being protected by the 'onlyOwner' modifier would be to add the 'onlyOwner' modifier to the 'gnosis' variable and the function that initializes it, as well as changing the 'init' function to check that the caller is the owner before initializing the 'gnosis' variable. Here's an example code snippet that demonstrates the general concept of the mitigation:
contract BrokerP1 is ComponentP1, IBroker {
address private owner;
...
function init(
IMain main_,
IGnosis gnosis_,
ITrade tradeImplementation_,
uint48 auctionLength_
) external initializer {
require(address(gnosis_) != address(0), "invalid Gnosis address");
require(
address(tradeImplementation_) != address(0),
"invalid Trade Implementation address"
);
require(msg.sender == owner, "only owner can call this function");
__Component_init(main_);
...
}
function setOwner() external onlyOwner {
owner = msg.sender;
}
...
}
Impact
disabled
variable can be set to true
by the reportViolation
function of the ITrade
interface, which is implemented by the GnosisTrade
contract. However, it is not clear from the provided code what conditions must be met for the reportViolation
function to be called, and whether the function is properly restricted to only be callable by the intended parties {such as the contract owner}.
If the disabled
variable can be set to true
by a malicious actor, it could prevent legitimate trades from being made, leading to loss
of functionality for users of the contract. Additionally, if the conditions for calling reportViolation
are not properly restricted, a malicious actor may be able to disable the contract arbitrarily.
If the 'disabled' variable can be set to 'true' by a malicious actor, it could prevent legitimate trades from being made, leading to loss
of functionality for users of the contract. This could be a serious issue, as it would prevent the intended use of the contract.
Proof of Concept
An attacker could potentially exploit the vulnerability of the 'disabled' variable by calling the reportViolation
function on a GnosisTrade
contract that they control, which would set the 'disabled' variable to 'true' and prevent legitimate trades from being made. The exact method of exploitation would depend on the specifics of the implementation, such as the conditions for calling reportViolation
and the parties that are able to call it.
It would involve creating a new instance of the GnosisTrade
contract and calling the reportViolation
function on it.
pragma solidity ^0.8.0;
contract Attacker {
address public attacker;
GnosisTrade fakeTrade;
BrokerP1 broker;
constructor(address _broker) public {
broker = BrokerP1(_broker);
attacker = msg.sender;
}
function executeAttack() public {
fakeTrade = new GnosisTrade();
fakeTrade.reportViolation();
}
}
Attacker
contract creates a new instance of the GnosisTrade
contract and calls the reportViolation
function on it. The reportViolation
function is supposed to be called when there is a violation of the contract, however, since the attacker controls this GnosisTrade
instance, the attacker can use it to call the function and set the disabled variable in the BrokerP1
contract to true. This would prevent legitimate trades from being made.
Tools Used
Manual audit, Vs Code
Recommended Mitigation Steps
One way to mitigate the vulnerability of the 'disabled' variable would be to add proper restriction to the conditions for calling 'reportViolation' and the parties that are able to call it, in order to ensure that only authorized parties are able to set the 'disabled' variable to 'true'. Additionally, adding a delay before the effect of the function, that is disabling trading, will give the owner or other responsible party an opportunity to react.
interface ITrade {
function reportViolation() external onlyOwner;
}
contract GnosisTrade is ITrade {
function reportViolation() external onlyOwner {
require(msg.sender == owner, "only owner can call this function");
require(block.timestamp > startTime + delay);
...
}
}
Impact
openTrade
function does not check for a sufficient allowance being set before transferring funds from the caller to the newly created trade contract.
If the contract does not check for a sufficient allowance being set before transferring funds from the caller to the newly created trade contract, then it may be possible for a malicious actor to steal funds by creating a trade with a large sellAmount
Not checking
for a sufficient allowance before transferring funds from the caller to the newly created trade contract could allow malicious actors to steal funds, which could be a serious issue.
Proof of Concept
An attacker could potentially exploit the vulnerability of the 'openTrade' function not checking for a sufficient allowance being set before transferring funds by calling the 'openTrade' function with a high 'sellAmount' and a low allowance. This would allow the attacker to steal funds by creating a trade with a large sellAmount. Here's an example code snippet that demonstrates the general concept of the attack:
contract Attacker {
function attack(address broker) public {
IBroker b = IBroker(broker);
b.openTrade(..., 10000000000000000, ...);
}
}
Tools Used
Manual audit, Vs Code
Recommended Mitigation Steps
To mitigate the vulnerability of the 'openTrade' function not checking for a sufficient allowance being set before transferring funds, the function could be updated to check the caller's allowance before transferring the funds. An example code snippet would look like this:
function openTrade(TradeRequest memory req) external notPausedOrFrozen returns (ITrade) {
...
require(req.sell.allowance(req.trader, address(this)) >= req.sellAmount, "insufficient allowance");
...
}
Impact
- The contract use
SafeERC20Upgradeable
from openzeppelin
and "Clones" but it not set any upgrade mechanism, so this smart contract could be in a state that no more can be upgraded
after deployed.
The contract using 'SafeERC20Upgradeable' and "Clones" but it does not set any upgrade mechanism, so this smart contract could be in a state where no more can be upgraded after deployed. This could lead to the contract becoming outdated and vulnerable over time.
The contract uses 'SafeERC20Upgradeable' and "Clones" but does not set any upgrade mechanism, so this smart contract could be in a state where no more can be upgraded after deployed. This could lead to the contract becoming outdated and vulnerable over time and can be a serious issue.
Proof of Concept
An attacker will take advantage of this by creating a new version of the contract with malicious code, and if it's deployed to the same address as the original contract and then tricking users to interact with it. This could potentially allow the attacker to steal user funds and perform unwanted actions.
An attacker could exploit the vulnerability of the contract using 'SafeERC20Upgradeable' and "Clones" but not set
any upgrade mechanism, by creating a new version of the contract with malicious code, and tricking users to upgrade to the new version. This will allow attackers to steal the funds of the users and perform unwanted actions.
For this vulnerability would involve creating a new version of the contract with malicious code, such as a stealFund()
function, and then deploying it to the same address as the original contract. An attacker could then trick users into interacting with the malicious contract instead of the original, allowing the attacker to steal funds or perform other unwanted actions.
For example, an attacker could create a malicious version of the contract that looks identical to the original but has added a stealFund()
function. They would then have to trick users or exchanges to interact with the malicious version, instead of the original one.
Here is an example of the malicious contract:
pragma solidity ^0.8.0;
contract MaliciousUpgrade {
address payable public victim;
constructor() public {
victim = msg.sender;
}
function stealFunds() public {
require(victim.transfer(address(this).balance));
}
}
Tools Used
Manual audit, Vs Code
Recommended Mitigation Steps
To mitigate the vulnerability of the contract using 'SafeERC20Upgradeable' and "Clones" but not set any upgrade mechanism, one way is to implement a upgrade mechanism that allow user to upgrade the contract in a safe way. Such as making use of the 'Upgradability Proxy' that OpenZeppelin provides, or another upgrade library with similar functionality. It will allow the contract owner to manage and deploy new version of the contract in a safer way.
Impact
openTrade
function does not check the address of the caller is a Trader or not.
If the 'openTrade' function does not check the address of the caller is a Trader or not, it can lead to an attacker
opening a trade without having the right to do it, leading to potential financial loss.
Not checking the address of the caller is a Trader
or not, could allow malicious
actors to open trades without having the right to do it, leading to potential financial loss, which could be a serious issue.
Proof of Concept
An attacker could potentially exploit the vulnerability of the 'openTrade' function not checking the address of the caller is a Trader or not, by calling the 'openTrade' function with an address that they control. This would allow them to open trades without having the right to do it and potentially steal funds.
An attacker would involve creating a new Ethereum address that is not an authorized Trader, and then calling the openTrade function from that address.
pragma solidity ^0.8.0;
contract Attacker {
address traderAddress;
address public attacker;
BrokerP1 broker;
constructor(address _broker) public {
broker = BrokerP1(_broker);
attacker = msg.sender;
}
function executeAttack() public {
TradeRequest memory req;
req.sell = ERC20(0x...);
req.buy = ERC20(0x...);
req.sellAmount = 1 ether;
req.buyAmount = 2 ether;
req.startPrice = 1 ether;
req.endPrice = 2 ether;
req.duration = 604800;
broker.openTrade(req);
}
}
In this, Attacker
contract creates a TradeRequest
and calls the openTrade
function of the BrokerP1
contract, and since the function doesn't have any protection to check if the caller is a trader, the Attacker
is able to open trades without being authorized.
Tools Used
Manual audit, Vs Code
Recommended Mitigation Steps
To mitigate the vulnerability of the 'openTrade' function not checking the address of the caller is a Trader or not, the function could be updated to check the caller's role and ensure that only authorized parties are able to open trades. An example code snippet would look like this:
function openTrade(TradeRequest memory req) external notPausedOrFrozen returns (ITrade) {
...
require(traders[msg.sender], "caller must be a trader");
...
}
Impact
- In the future, if the contract will have more
sophisticated
choice logic here, probably by trade size, this could lead to a possibility of front-running
or trade manipulation.
If in the future the contract will have more sophisticated choice logic here, probably by trade size, this could lead to a possibility of front-running
or trade manipulation, meaning that an attacker
could gain an unfair advantage and potentially profit at the expense of other users.
The possibility of front-running
or trade manipulation
could be a serious issue, as it would allow malicious actors to gain an unfair advantage and potentially profit at the expense of other users.
Proof of Concept
An attacker could exploit the possibility of front-running or trade manipulation by monitoring the trade orders in the contract, then quickly placing an order before legitimate traders, allowing them to gain an unfair advantage and potentially profit at the expense of other users.
Would involve creating a new Ethereum address that is not an authorized Trader, and then calling the openTrade
function from that address.
pragma solidity ^0.8.0;
contract Attacker {
address traderAddress;
address public attacker;
BrokerP1 broker;
constructor(address _broker) public {
broker = BrokerP1(_broker);
attacker = msg.sender;
}
function executeAttack() public {
TradeRequest memory req;
req.sell = ERC20(0x...);
req.buy = ERC20(0x...);
req.sellAmount = 1 ether;
req.buyAmount = 2 ether;
req.startPrice = 1 ether;
req.endPrice = 2 ether;
req.duration = 604800;
broker.openTrade(req);
}
}
The Attacker
contract creates a TradeRequest
and calls the openTrade
function of the BrokerP1
contract, and since the function doesn't have any protection to check if the caller is a trader, the Attacker
is able to open trades without being authorized.
Tools Used
Manual audit, Vs Code
Recommended Mitigation Steps
One way to mitigate the vulnerability of front-running or trade manipulation would be to implement a randomization mechanism or block-based restrictions, to reduce the predictability of the ordering. Additionally, implementing a rate-limiting mechanism prevents a large number of trades from being initiated by a single address within a short period of time. Another option could be implementing a centralized order book, in which the broker will be responsible for matching the trade orders
Impact
- There's a large constant
GNOSIS_MAX_TOKENS
with a fixed value of 7e28
which may be subject to overflow issues or unintended consequences if its value is exceeded in any operation.
The constant GNOSIS_MAX_TOKENS
with a fixed value of 7e28
could be subject to overflow issues or unintended consequences if its value is exceeded in any operation, leading to unexpected results and potential errors.
Constant GNOSIS_MAX_TOKENS
with a fixed value of 7e28
could be subject to overflow issues or unintended consequences if its value is exceeded in any operation, leading to unexpected results and potential errors, which could be a serious issue.
Proof of Concept
An attacker could potentially exploit the vulnerability of the constant "GNOSIS_MAX_TOKENS" by passing large values to the contract's functions that could lead to overflow issues or unintended consequences if its value is exceeded in any operation.
Passing large values to the functions of the contract that use the GNOSIS_MAX_TOKENS
constant
pragma solidity ^0.8.0;
contract Attacker {
address public attacker;
BrokerP1 broker;
constructor(address _broker) public {
broker = BrokerP1(_broker);
attacker = msg.sender;
}
function executeAttack() public {
TradeRequest memory req;
req.sellAmount = 2**200;
// This value exceeds the maximum value defined by GNOSIS_MAX_TOKENS
broker.openTrade(req);
}
}
Attacker
contract creates a TradeRequest
with a sellAmount
value that exceeds the maximum value defined by GNOSIS_MAX_TOKENS and calls the openTrade function of the BrokerP1 contract. Depending on how the openTrade
function uses the GNOSIS_MAX_TOKENS constant, this may cause the function to produce unintended results, such as overflow issues.
Tools Used
Manual audit, vs code
Recommended Mitigation Steps
To mitigate the vulnerability of the constant "GNOSIS_MAX_TOKENS" subject to overflow issues, one solution could be to handle the overflow exception by writing a SafeMath
library or using a library like OpenZeppelin's
SafeMath
library, that will automatically handle overflow and underflow errors.
Impact
- The contract doesn't handle any
exceptions
which may occur.
If the contract doesn't handle any exceptions which may occur, it could lead to unexpected results in which the contract stops working or even funds can be locked, which could negatively impact the users of the contract.
If the contract doesn't handle any exceptions that may occur, it could lead to unexpected results in which the contract stops working or even funds can be locked, which could negatively impact the users of the contract, which could be a serious issue.
Proof of Concept
An attacker could exploit the vulnerability that the contract doesn't handle with any exception by performing actions that would trigger exceptions, such as passing invalid input to the contract. This would cause the contract to fail, leading to unexpected results such as funds being locked, and negatively impacting the users of the contract.
pragma solidity ^0.8.0;
contract Attacker {
address public attacker;
BrokerP1 broker;
constructor(address _broker) public {
broker = BrokerP1(_broker);
attacker = msg.sender;
}
function executeAttack() public {
broker.openTrade(0x0);
//the broker contract expect a TradeRequest as input, passing 0x0 will trigger an exception
}
}
Tools Used
Manual audit, vs code
Recommended Mitigation Steps
To mitigate the vulnerability that the contract doesn't handle with any exception, it's important to anticipate and handle with any exception that may occur, such as passing invalid input to the contract. This can be done by including error-handling mechanisms like require() statements, or by using the assert() function to ensure that the preconditions and postconditions of the contract are met.
function myFunction(uint256 _a, uint256 _b) public {
require(_a > 0, "a must be greater than 0");
require(_b > 0, "b must be greater than 0");
uint256 result = _a + _b;
require(result > _a, "overflow detected");
...
}