Greetings, dear readers! Today we’ll look at the significant news and updates pertaining to our Slitherin project in this article. We assure you that it will be fascinating — Slitherin, our own set of custom detectors for Slither, another awesome update!
In recent months we have been actively developing our own Slither detectors to help with code review and audit process. More recently, we have released several new detectors and we encourage you to use them for your initial internal audit, but let’s now get back to the point of our conversation today.
Simply put, our detectors are a kind of automation of the checks implemented in the checklist, their main purpose is to look for issues and assist the code auditor. Today we’re going to break down our new Arbitrary Call detector and generally understand what it is!
We’ve applied some significant updates during this time, and we appreciate all of your love and attention. Please let us know if you have discovered an issue/bug/vulnerability via our custom Slither detectors. You may contact us via opening a PR/Issue or directly, whichever is more convenient for you!
You can now install a fresh package: pypi.org/project/slitherin!
The detector iterates over all low-level calls, checks if the destination or calldata could be tainted(manipulated). All of the information about the detector is provided below.
General Recommendation: Do not allow users to make arbitrary calls!
General idea: If a contract has arbitrary calls, it should generally not contain transfer/transferFrom/approve calls. If such calls are in the contract, it means that the contract stores either tokens or approvals for them. Thus, they can be stolen via arbitrary calls to the token contract.
Below are our audit notes from which you will learn how exactly we came up with the idea of such a detector and what it actually is. Be sure to study them carefully!
There is a call to a custom address;
There is a call with arbitrary calldata.
You can insert the address of the token and call the function transfer/transferFrom/approve;
You can get privileged access;
You can perform a reentrancy;
Other (order fill, strange/atypical staking, etc.)
Solidity offers convenient high-level syntax for calling functions in other contracts, but this high-level syntax is only available when the target contract’s interface is known at compile time, read more here;
Malicious contracts may withdraw the contract’s balance in response to external calls to arbitrary addresses, resulting in a loss of funds. Due to this flaw, attackers can use the contract’s capabilities to their advantage and run malicious or unauthorized code that can extract assets from the contract or can break the working mechanism of the contract, read more here.
In our audits, we’ve seen different methods like ActionOnBehalfOf — they require all sorts of approvals, but allow you to manage other people’s staking funds. This is quite a rare thing and it requires special care!
Besides the usual transfer, transferFrom, approve, it is necessary to check call scripts/scenarios into the same contract; for example, in one of the project’s code there was token staking (transferFrom(msg.sender, address(this), x)), but when withdrawing it was possible to specify receiver and withdraw there!
Call on behalf of the contract itself. For example, you can get into “protected” code (bypass access control)3+4. You can call yourself to make msg.sender == this. For example, transferFrom(msg.sender, this, N);
Transfer and approve may allow to withdraw tokens from the balance of the contract. And transferFrom may allow you to steal someone else’s tokens (using approvals for this contract).
If possible, do not use arbitrary call ;
If there is an arbitrary call, the contract should not store token approves;
It is also better to make a layer to which approve is given.and the necessary tokens are thrown to the router;
Optionally the layer can be made pausable or add selfdestruct;
Try to minimize the number of “last minute” edits!
Try to filter function signatures that user provides!
a) C1.foo() makes an arbitrary call (in addition to possibly something else);
b) C1.bar() does all sorts of checks and calls C2.xyz();
c) C2.xyz() does something very important and is protected by onlyC1then the checks from C1.bar can be bypassed via C1.foo()->C2.xyz() (onlyC1 will not notice the trick;
If contract A has assets (ether or tokens) on contract A and contract A makes a delegatecall to “user” contract B, contract B can withdraw assets from contract A’s balance sheet;
We cannot rule out the case where there are no tokens, but there is a native currency. If the project is located on a specific chain where the native currency is a precompiled contract (e.g. Moonbeam and MOVR & GLMR tokens), then a delegatecall can also be performed to it. Vulnerability example;
The use of fallback()-function in the contract code should be very careful. Thus, the presence of fallback()-function without revert and conditions allows to execute for this contract a function with any signature that is not presented in the source code of the contract (may be malicious);
Potential DoS: the address can simply drop or “eat” all the gas, the calling contract should not somehow lag or freeze from this (relevant for the State Machine pattern). Also relevant for `.send()`, `.transfer()`. ;
Delegatecall to a spoofed address can remove the calling contract or break storage completely;
Starting with 0.5, the compiler inserts a check of the called address (that it is a contract and not EoA). But for low-level calls (.call, .delegatecall and others) and assembly there is no such check. Calling a non-contract address will not create any errors, but the logic will not even start.
We finished our own research a few months ago; please read it if you haven’t already:
Audit Wizard is an all-in-one platform for auditing smart contracts. Scan for vulnerabilities, leverage AI for security insights, generate audit reports, and more. Read more about it in the following article by Johnny Time!
In auditwizard.io, results from dependencies have also been filtered out from Slither to remove unnecessary results. Slitherin, an extended version of Slither with even more vulnerability detectors, has also been added to increase scanner coverage:
Optimizations to our detectors are coming soon;
More detectors to be released soon as well;
Don’t forget to pull the changes: github.com/pessimistic-io/slitherin;
You can now install a fresh package: pypi.org/project/slitherin!
We’ve also applied for specialized detectors through the Arbitrum grant ecosystem, and we’re planning two more projects. If you are interested, please contact us!
We’ve applied some significant updates during this time, and we appreciate all of your love and attention. Please let us know if you have discovered an issue/bug/vulnerability via our custom Slither detectors.
Major updates, reworks, additions, minor fixes & optimization:
pess-arbitrary-call detector: New detector. Thx Yhtiyar;
pess-strange-setter detector: Functions with no parameters are no longer detected. Thx @Yhtiyar;
pess-unprotected-setter detector: Now has a separate test file;
Our team would also like to express our deepest gratitude to the Slither tool creators: Josselin Feist, Gustavo Grieco, and Alex Groce, as well as Crytic, Trail of Bits’ blockchain security division, and all the people who believe in the original tool and its evolution!
We sincerely hope you find our work useful and appreciate any feedback, so please do not hesitate to contact us! The best answers and questions may be included in the next blog post. We hope that this article was informative and useful for you!
Support is very important to me, with it I can do what I love — educating users!
If you want to support my work, please, consider donating me:
4AhpUrDtfVSWZMJcRMJkZoPwDSdVG6puYBE3ajQABQo6T533cVvx5vJRc5fX7sktJe67mXu1CcDmr7orn1CrGrqsT3ptfds — Monero XMR