From 313e966b1d498cbab391ace99e6dbbbc1066fc42 Mon Sep 17 00:00:00 2001 From: Nikita Kirillov Date: Thu, 12 Oct 2023 13:57:22 +0500 Subject: [PATCH 01/16] release is a word --- .github/workflows/notification.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/notification.yml b/.github/workflows/notification.yml index 4a2a65f..005720b 100644 --- a/.github/workflows/notification.yml +++ b/.github/workflows/notification.yml @@ -15,4 +15,4 @@ jobs: to: ${{ secrets.TELEGRAM_TO }} token: ${{ secrets.BOT_TOKEN }} message: | - New version of Slitherin got realeased. Pull updates from here: https://github.com/pessimistic-io/slitherin or update a Python package: https://pypi.org/project/slitherin/. Release note: https://github.com/pessimistic-io/slitherin/releases + New version of Slitherin got released. Pull updates from here: https://github.com/pessimistic-io/slitherin or update a Python package: https://pypi.org/project/slitherin/. Release note: https://github.com/pessimistic-io/slitherin/releases From d7205f49c47ab14a7c3f0470222b46bd7373c90d Mon Sep 17 00:00:00 2001 From: Nikita Kirillov <75425665+ndkirillov@users.noreply.github.com> Date: Tue, 17 Oct 2023 12:50:11 +0500 Subject: [PATCH 02/16] Add hexens article --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 19cd5f5..5e7a970 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,7 @@ Our team would like to express our deepest gratitude to the [Slither tool](https - [Blockthreat](https://newsletter.blockthreat.io/p/blockthreat-week-16-2023#:~:text=Slitherin%20a%20collection%20of%20Slither%20detection%20by%20Pessimistic.io%20team) - [Release article by officercia.eth](https://officercia.mirror.xyz/ucWYWnhBXmkKq54BIdJcH5GnrAB-nQkUsZ2F-ytEsR4) - [Defillama](https://t.me/defillama_tg/842) +- [Essential Tools for Auditing Smart Contracts by Hexens](https://hexens.io/blog/toolkit-for-web3-security-engineers) ## Thank you! From 92e34b01e43494efd92d4be2b05ff619d4f088ff Mon Sep 17 00:00:00 2001 From: Yhtyyar Sahatov Date: Mon, 23 Oct 2023 11:00:12 +0300 Subject: [PATCH 03/16] divided arbitrary call detector --- docs/arbitrary_call.md | 19 ++++- slither_pess/__init__.py | 2 +- .../{ => arbitrary_call}/arbitrary_call.py | 82 +++++++++++++++++-- tests/arbitrary_call_test.sol | 18 ++++ 4 files changed, 110 insertions(+), 11 deletions(-) rename slither_pess/detectors/{ => arbitrary_call}/arbitrary_call.py (68%) diff --git a/docs/arbitrary_call.md b/docs/arbitrary_call.md index aa0c0c6..d247918 100644 --- a/docs/arbitrary_call.md +++ b/docs/arbitrary_call.md @@ -2,13 +2,26 @@ ## Configuration -- Check: `pess-arbitrary-call` -- Severity: `High` -- Confidence: `Low` +- - Check: `pess-arbitrary-call` + - Severity: `High` + - Confidence: `High` + +* - Check: `pess-arbitrary-call-with-stored-erc20-approves` + - Severity: `High` + - Confidence: `High` + +- - Check: `pess-arbitrary-call-destination-tainted` + - Severity: `Medium` + - Confidence: `Medium` + +* - Check: `pess-arbitrary-call-calldata-tainted` + - Severity: `Medium` + - Confidence: `Medium` ## Description The detector iterates over all low-level calls, checks if the destination or calldata could be tainted(manipulated). +This detector consists of multiple detectors, which will run with this detector. ### Potential Improvement diff --git a/slither_pess/__init__.py b/slither_pess/__init__.py index 6d18886..cebcd7f 100644 --- a/slither_pess/__init__.py +++ b/slither_pess/__init__.py @@ -1,4 +1,4 @@ -from slither_pess.detectors.arbitrary_call import ArbitraryCall +from slither_pess.detectors.arbitrary_call.arbitrary_call import ArbitraryCall from slither_pess.detectors.double_entry_token_possibility import ( DoubleEntryTokenPossiblity, ) diff --git a/slither_pess/detectors/arbitrary_call.py b/slither_pess/detectors/arbitrary_call/arbitrary_call.py similarity index 68% rename from slither_pess/detectors/arbitrary_call.py rename to slither_pess/detectors/arbitrary_call/arbitrary_call.py index fe0b532..c5e7188 100644 --- a/slither_pess/detectors/arbitrary_call.py +++ b/slither_pess/detectors/arbitrary_call/arbitrary_call.py @@ -1,7 +1,13 @@ +from enum import Enum from typing import List, Tuple +from collections import namedtuple -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.slithir.operations import SolidityCall +from slither.detectors.abstract_detector import ( + AbstractDetector, + DetectorClassification, + classification_txt, +) +from slither.slithir.operations import SolidityCall, HighLevelCall from slither.core.declarations import ( Contract, SolidityVariableComposed, @@ -16,6 +22,33 @@ # look for transferFroms if there is any transferFrom and contract contains whole arbitrary call - it is screwed # Filter out role protected +DetectorParams = namedtuple( + "DetectorParams", ["argument_suffix", "impact", "confidence"] +) + + +class ArbitraryCallDetectors: + ArbitraryCallWithApproveStored = DetectorParams( + "-with-stored-erc20-approves", + DetectorClassification.HIGH, + DetectorClassification.HIGH, + ) + ArbitraryCall = DetectorParams( + "", + DetectorClassification.HIGH, + DetectorClassification.HIGH, + ) + ArbitraryCallDestinationTainted = DetectorParams( + "-destination-tainted", + DetectorClassification.MEDIUM, + DetectorClassification.MEDIUM, + ) + ArbitraryCallCalldataTainted = DetectorParams( + "-calldata-tainted", + DetectorClassification.MEDIUM, + DetectorClassification.MEDIUM, + ) + class ArbitraryCall(AbstractDetector): """ @@ -38,8 +71,11 @@ class ArbitraryCall(AbstractDetector): def analyze_function( self, function: FunctionContract - ) -> List[Tuple[FunctionContract, Node, LowLevelCall, bool, bool]]: + ) -> Tuple[ + List[Tuple[FunctionContract, Node, LowLevelCall, bool, bool]], bool + ]: # TODO(yhtiyar): make return type as named tuple/class results = [] + stores_approve = False for node in function.nodes: for ir in node.irs: try: @@ -76,6 +112,12 @@ def analyze_function( destination_tainted = is_tainted(ir.arguments[1], node, True) args_tainted = is_tainted(ir.arguments[3], node, True) + elif ( + isinstance(ir, HighLevelCall) + and ir.function.name == "transferFrom" + ): + stores_approve = True + if args_tainted or destination_tainted: results.append( ( @@ -90,7 +132,7 @@ def analyze_function( print("ArbitraryCall:Failed to check types", e) print(ir) - return results + return (results, stores_approve) def analyze_contract(self, contract: Contract): stores_approve = False @@ -99,7 +141,8 @@ def analyze_contract(self, contract: Contract): ] = [] results = [] for f in contract.functions: - res = self.analyze_function(f) + (res, _stores_approve) = self.analyze_function(f) + stores_approve |= _stores_approve if res: all_tainted_calls.extend(res) @@ -137,15 +180,40 @@ def analyze_contract(self, contract: Contract): if not (fn_taints_args or fn_taints_destination): continue + detectorParams: DetectorParams = None if fn_taints_args and fn_taints_destination: - text = "The call could be fully manipulated (arbitrary call)" + if stores_approve: + text = "The call could be fully manipulated (arbitrary call). This contract also STORES APPROVES!!!" + detectorParams = ( + ArbitraryCallDetectors.ArbitraryCallWithApproveStored + ) + + else: + text = "The call could be fully manipulated (arbitrary call)" + detectorParams = ArbitraryCallDetectors.ArbitraryCall else: - part = "calldata" if fn_taints_args else "destination" + if fn_taints_args: + part = "calldata" + detectorParams = ( + ArbitraryCallDetectors.ArbitraryCallCalldataTainted + ) + + else: + part = "destination" + detectorParams = ( + ArbitraryCallDetectors.ArbitraryCallDestinationTainted + ) + text = f"The {part} could be manipulated" info += [f"\t{text} through ", f, "\n"] res = self.generate_result(info) res.add(node) + + res.data["check"] = self.ARGUMENT + detectorParams.argument_suffix + res.data["impact"] = classification_txt[detectorParams.impact] + res.data["confidence"] = classification_txt[detectorParams.confidence] + results.append(res) return results diff --git a/tests/arbitrary_call_test.sol b/tests/arbitrary_call_test.sol index 7378d27..64e62f0 100644 --- a/tests/arbitrary_call_test.sol +++ b/tests/arbitrary_call_test.sol @@ -1,3 +1,11 @@ +interface IERC20 { + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool); +} + contract Test { address toAddress; bytes callData; @@ -157,3 +165,13 @@ contract Test { } } } + +contract TestWithApprove { + function swap(IERC20 token) external { + token.transferFrom(msg.sender, address(this), 1); + } + + function call(address to, bytes memory data) public { + to.call(data); + } +} From 166aad1ca96967b71b7bd8a03db40bae41abdb8d Mon Sep 17 00:00:00 2001 From: Yhtyyar Sahatov Date: Mon, 23 Oct 2023 11:52:32 +0300 Subject: [PATCH 04/16] improved docs, filtered out role protected results --- docs/arbitrary_call.md | 2 +- slither_pess/detectors/arbitrary_call/arbitrary_call.py | 8 ++++++++ slither_pess/detectors/unprotected_setter.py | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/arbitrary_call.md b/docs/arbitrary_call.md index d247918..af10d2f 100644 --- a/docs/arbitrary_call.md +++ b/docs/arbitrary_call.md @@ -25,7 +25,7 @@ This detector consists of multiple detectors, which will run with this detector. ### Potential Improvement -Filter out role protected calls, divide detector to multiple detectors with different severity and confidence +Filter out proxy results ## Vulnerable Scenario diff --git a/slither_pess/detectors/arbitrary_call/arbitrary_call.py b/slither_pess/detectors/arbitrary_call/arbitrary_call.py index c5e7188..d605aee 100644 --- a/slither_pess/detectors/arbitrary_call/arbitrary_call.py +++ b/slither_pess/detectors/arbitrary_call/arbitrary_call.py @@ -69,6 +69,12 @@ class ArbitraryCall(AbstractDetector): WIKI_EXPLOIT_SCENARIO = "Attacker can manipulate on inputs" WIKI_RECOMMENDATION = "Do not allow arbitrary calls" + def _is_role_protected(self, function: FunctionContract): + for m in function.modifiers: + if m.name.startswith("only"): + return True + return False + def analyze_function( self, function: FunctionContract ) -> Tuple[ @@ -158,6 +164,8 @@ def analyze_contract(self, contract: Contract): for f in contract.functions: if f.visibility not in ["external", "public"]: continue + if self._is_role_protected(f): + continue fn_taints_args = False fn_taints_destination = False diff --git a/slither_pess/detectors/unprotected_setter.py b/slither_pess/detectors/unprotected_setter.py index 17f41c4..75ae427 100644 --- a/slither_pess/detectors/unprotected_setter.py +++ b/slither_pess/detectors/unprotected_setter.py @@ -41,7 +41,7 @@ def is_setter(self, fun, params=None): def has_access_control(self, fun): for m in fun.modifiers: - for m.name in ["initializer", "onlyOwner"]: + if m.name in ["initializer", "onlyOwner"] or m.name.startswith("only"): return True if fun.visibility in ["internal", "private"]: return True From 9998fbcf3378a44dea49a8ef8551a1f76884f070 Mon Sep 17 00:00:00 2001 From: Yhtyyar Sahatov Date: Tue, 24 Oct 2023 18:01:51 +0300 Subject: [PATCH 05/16] WIP --- setup.py | 6 +- slither_pess/__init__.py | 53 ++++++------- slither_pess/cli.py | 163 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+), 28 deletions(-) create mode 100644 slither_pess/cli.py diff --git a/setup.py b/setup.py index fafd86c..16a140c 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ from setuptools import setup, find_packages +from slither_pess.cli import SLITHERIN_VERSION with open("./README.md", "r") as f: long_description = f.read() @@ -10,8 +11,8 @@ long_description_content_type="text/markdown", url="https://github.com/pessimistic-io/slitherin", author="Pessimistic.io", - version="0.4.0", - package_dir={"":"."}, + version=SLITHERIN_VERSION, + package_dir={"": "."}, packages=find_packages(), license="AGPL-3.0", python_requires=">=3.8", @@ -21,5 +22,6 @@ }, entry_points={ "slither_analyzer.plugin": "slither my-plugin=slither_pess:make_plugin", + "console_scripts": ["slitherin=slither_pess.cli:main"], }, ) diff --git a/slither_pess/__init__.py b/slither_pess/__init__.py index 6d18886..b0e67e7 100644 --- a/slither_pess/__init__.py +++ b/slither_pess/__init__.py @@ -24,31 +24,32 @@ from slither_pess.detectors.public_vs_external import PublicVsExternal -def make_plugin(): - plugin_detectors = [ - DoubleEntryTokenPossiblity, - UnprotectedSetter, - NftApproveWarning, - InconsistentNonreentrant, - StrangeSetter, - OnlyEOACheck, - MagicNumber, - DubiousTypecast, - CallForwardToProtected, - MultipleStorageRead, - TimelockController, - TxGaspriceWarning, - UnprotectedInitialize, - ReadOnlyReentrancy, - EventSetter, - BeforeTokenTransfer, - UniswapV2, - TokenFallback, - ForContinueIncrement, - ArbitraryCall, - Ecrecover, - PublicVsExternal, - ] - plugin_printers = [] +plugin_detectors = [ + DoubleEntryTokenPossiblity, + UnprotectedSetter, + NftApproveWarning, + InconsistentNonreentrant, + StrangeSetter, + OnlyEOACheck, + MagicNumber, + DubiousTypecast, + CallForwardToProtected, + MultipleStorageRead, + TimelockController, + TxGaspriceWarning, + UnprotectedInitialize, + ReadOnlyReentrancy, + EventSetter, + BeforeTokenTransfer, + UniswapV2, + TokenFallback, + ForContinueIncrement, + ArbitraryCall, + Ecrecover, + PublicVsExternal, +] +plugin_printers = [] + +def make_plugin(): return plugin_detectors, plugin_printers diff --git a/slither_pess/cli.py b/slither_pess/cli.py new file mode 100644 index 0000000..31f1e60 --- /dev/null +++ b/slither_pess/cli.py @@ -0,0 +1,163 @@ +import argparse +import logging +from typing import List, Any, Optional +import subprocess +import os +import shutil +from pathlib import Path +import slither_pess + +SLITHERIN_VERSION = "0.4.0" + + +def slitherin_detectors_list_as_arguments() -> str: + return ", ".join([detector.ARGUMENT for detector in slither_pess.plugin_detectors]) + + +logging.basicConfig() +LOGGER = logging.getLogger("slitherinLogger") +LOGGER.setLevel(logging.INFO) + + +# this is modified version from : https://github.com/crytic/crytic-compile/blob/master/crytic_compile/utils/subprocess.py#L14 +def run( + cmd: List[str], + **kwargs: Any, +) -> Optional[subprocess.CompletedProcess]: + subprocess_cwd = Path(os.getcwd()).resolve() + subprocess_exe = shutil.which(cmd[0]) + + if subprocess_exe is None: + LOGGER.error("Cannot execute `%s`, is it installed and in PATH?", cmd[0]) + return None + + LOGGER.info( + "'%s' running (wd: %s)", + " ".join(cmd), + subprocess_cwd, + ) + + try: + process = subprocess.run( + cmd, + executable=subprocess_exe, + cwd=subprocess_cwd, + check=True, + capture_output=True, + text=True, + **kwargs, + ) + if process.stdout: + print(process.stdout) + if process.stderr: + print(process.stderr) + except FileNotFoundError: + print(f"Could not execute {cmd[0]}, is it installed and in PATH?") + except subprocess.CalledProcessError as e: + print(f"{cmd[0]} returned non-zero exit code { e.returncode}") + if e.stdout: + print(e.stdout) + if e.stderr: + print(e.stderr) + except OSError: + print("OS error executing:", exc_info=True) + + +def handle_list() -> None: + detectors = slither_pess.plugin_detectors + for detector in detectors: + print(detector.ARGUMENT) + + print("\nTo pass as argument (for --detect/--exclude):") + print(slitherin_detectors_list_as_arguments()) + + +def handle_parser(args: argparse.Namespace, slither_args) -> None: + slitherin_detectors = slitherin_detectors_list_as_arguments() + slither_with_args = ["slither"] + slither_args + if args.pess: + run( + slither_with_args + ["--detect", slitherin_detectors], + ) + elif args.slither: + run( + slither_with_args + ["--exclude", slitherin_detectors], + ) + elif args.seperated: + print("Only slither results:") + run( + slither_with_args + ["--exclude", slitherin_detectors], + ) + print("Only slitherin results:") + run( + slither_with_args + ["--detect", slitherin_detectors], + ) + else: + run(slither_with_args) + + +def generate_argument_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description=""" + slitherin: Additional slither detectors by pessimistic.io + All additional parameters will be passed to slither + Usage examples: + slitherin --pess PATH + slitherin --pess PATH --json result.json + slitherin --pess PATH --ignore-compile + """, + formatter_class=argparse.RawTextHelpFormatter, + ) + parser.add_argument( + "-v", + "--version", + action="version", + version=f"slitherin {SLITHERIN_VERSION}", + help="Show version", + ) + + parser.add_argument( + "--pess", + action="store_true", + help="Run only pessimistic.io (slitherin) detectors", + ) + + parser.add_argument( + "--slither", + action="store_true", + help="Run only slither detectors", + ) + + parser.add_argument( + "--seperated", + action="store_true", + help="Run slither detectors, then slitherin", + ) + + # parser.add_argument("slither-args", nargs=argparse.REMAINDER) + + # subcommands = parser.add_subparsers(dest="subcommands") + + # list_parser = subcommands.add_parser("list", help="List all slitherin detectors") + # list_parser.set_defaults(func=handle_list) + + return parser + + +def main() -> None: + """ + Handler for the "slitherin" command. + """ + parser = generate_argument_parser() + # parsed = parser.parse_args() + parsed, unknown = parser.parse_known_args() + + if unknown and unknown[0] == "list": + handle_list() + if not unknown and not parsed._get_args(): + parser.print_help() + handle_parser(parsed, unknown) + + +if __name__ == "__main__": + main() From 025edb9fceb2d1c46fdb810ccd02457bfbf5b8a7 Mon Sep 17 00:00:00 2001 From: Yhtyyar Sahatov Date: Wed, 25 Oct 2023 19:22:20 +0300 Subject: [PATCH 06/16] working cli --- setup.py | 2 +- slither_pess/cli.py | 53 +++++++++++++++------------------------------ 2 files changed, 19 insertions(+), 36 deletions(-) diff --git a/setup.py b/setup.py index 16a140c..2b19c42 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ "dev": ["twine>=4.0.2"], }, entry_points={ - "slither_analyzer.plugin": "slither my-plugin=slither_pess:make_plugin", + "slither_analyzer.plugin": "slither slitherin-plugins=slither_pess:make_plugin", "console_scripts": ["slitherin=slither_pess.cli:main"], }, ) diff --git a/slither_pess/cli.py b/slither_pess/cli.py index 31f1e60..b270891 100644 --- a/slither_pess/cli.py +++ b/slither_pess/cli.py @@ -4,20 +4,30 @@ import subprocess import os import shutil +import pty from pathlib import Path import slither_pess +from pkg_resources import iter_entry_points SLITHERIN_VERSION = "0.4.0" def slitherin_detectors_list_as_arguments() -> str: - return ", ".join([detector.ARGUMENT for detector in slither_pess.plugin_detectors]) + return ",".join([detector.ARGUMENT for detector in slither_pess.plugin_detectors]) logging.basicConfig() LOGGER = logging.getLogger("slitherinLogger") LOGGER.setLevel(logging.INFO) +output_bytes = [] + + +def read(fd): + data = os.read(fd, 1024) + output_bytes.append(data) + return data + # this is modified version from : https://github.com/crytic/crytic-compile/blob/master/crytic_compile/utils/subprocess.py#L14 def run( @@ -37,30 +47,7 @@ def run( subprocess_cwd, ) - try: - process = subprocess.run( - cmd, - executable=subprocess_exe, - cwd=subprocess_cwd, - check=True, - capture_output=True, - text=True, - **kwargs, - ) - if process.stdout: - print(process.stdout) - if process.stderr: - print(process.stderr) - except FileNotFoundError: - print(f"Could not execute {cmd[0]}, is it installed and in PATH?") - except subprocess.CalledProcessError as e: - print(f"{cmd[0]} returned non-zero exit code { e.returncode}") - if e.stdout: - print(e.stdout) - if e.stderr: - print(e.stderr) - except OSError: - print("OS error executing:", exc_info=True) + pty.spawn(cmd, read) # this allows to print continuously and with colors def handle_list() -> None: @@ -90,7 +77,7 @@ def handle_parser(args: argparse.Namespace, slither_args) -> None: ) print("Only slitherin results:") run( - slither_with_args + ["--detect", slitherin_detectors], + slither_with_args + ["--ignore-compile", "--detect", slitherin_detectors], ) else: run(slither_with_args) @@ -133,14 +120,6 @@ def generate_argument_parser() -> argparse.ArgumentParser: action="store_true", help="Run slither detectors, then slitherin", ) - - # parser.add_argument("slither-args", nargs=argparse.REMAINDER) - - # subcommands = parser.add_subparsers(dest="subcommands") - - # list_parser = subcommands.add_parser("list", help="List all slitherin detectors") - # list_parser.set_defaults(func=handle_list) - return parser @@ -148,14 +127,18 @@ def main() -> None: """ Handler for the "slitherin" command. """ + parser = generate_argument_parser() - # parsed = parser.parse_args() parsed, unknown = parser.parse_known_args() + # It turned out that argparse has no solution to parse unkown args and subcommands, + # so u need to parse subcommands manually if unknown and unknown[0] == "list": handle_list() + return if not unknown and not parsed._get_args(): parser.print_help() + return handle_parser(parsed, unknown) From eb66cef2230402a0f93d702960a25e13a3e11051 Mon Sep 17 00:00:00 2001 From: Yhtyyar Sahatov Date: Thu, 26 Oct 2023 16:38:58 +0300 Subject: [PATCH 07/16] bump of the version --- slither_pess/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither_pess/cli.py b/slither_pess/cli.py index b270891..de209af 100644 --- a/slither_pess/cli.py +++ b/slither_pess/cli.py @@ -9,7 +9,7 @@ import slither_pess from pkg_resources import iter_entry_points -SLITHERIN_VERSION = "0.4.0" +SLITHERIN_VERSION = "0.5.0" def slitherin_detectors_list_as_arguments() -> str: From 3f5a40acce7c70735c8de45b48aa7b754ce17027 Mon Sep 17 00:00:00 2001 From: Yhtyyar Sahatov Date: Tue, 31 Oct 2023 15:30:48 +0300 Subject: [PATCH 08/16] aave flashloanCallback detector --- slither_pess/__init__.py | 2 + slither_pess/detectors/aave/__init__.py | 0 .../detectors/aave/flashloan_callback.py | 97 +++++++++++++ tests/AaveFlashloanCallback.sol | 130 ++++++++++++++++++ 4 files changed, 229 insertions(+) create mode 100644 slither_pess/detectors/aave/__init__.py create mode 100644 slither_pess/detectors/aave/flashloan_callback.py create mode 100644 tests/AaveFlashloanCallback.sol diff --git a/slither_pess/__init__.py b/slither_pess/__init__.py index b0e67e7..c573a4c 100644 --- a/slither_pess/__init__.py +++ b/slither_pess/__init__.py @@ -22,6 +22,7 @@ from slither_pess.detectors.for_continue_increment import ForContinueIncrement from slither_pess.detectors.ecrecover import Ecrecover from slither_pess.detectors.public_vs_external import PublicVsExternal +from slither_pess.detectors.aave.flashloan_callback import AAVEFlashloanCallbackDetector plugin_detectors = [ @@ -47,6 +48,7 @@ ArbitraryCall, Ecrecover, PublicVsExternal, + AAVEFlashloanCallbackDetector, ] plugin_printers = [] diff --git a/slither_pess/detectors/aave/__init__.py b/slither_pess/detectors/aave/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/slither_pess/detectors/aave/flashloan_callback.py b/slither_pess/detectors/aave/flashloan_callback.py new file mode 100644 index 0000000..3a32d08 --- /dev/null +++ b/slither_pess/detectors/aave/flashloan_callback.py @@ -0,0 +1,97 @@ +import re +from typing import List, Tuple + +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.slithir.operations import SolidityCall, Condition +from slither.core.declarations import ( + FunctionContract, + SolidityVariableComposed, + Function, +) +from slither.core.cfg.node import Node +from slither.slithir.operations import LowLevelCall +from slither.analyses.data_dependency.data_dependency import is_dependent + + +class AAVEFlashloanCallbackDetector(AbstractDetector): + """ + Detects not checked results of ecrecover + """ + + ARGUMENT = "pess-aave-flashloan-callback" # slither will launch the detector with slither.py --detect mydetector + HELP = "signer = ecrecover(hash, v, r, s)" + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.MEDIUM + + WIKI = "https://github.com/pessimistic-io/slitherin/blob/master/docs/ecrecover.md" + WIKI_TITLE = "Ecrecover" + WIKI_DESCRIPTION = "Check docs" + WIKI_EXPLOIT_SCENARIO = "Attacker can validate signatures from 0x0 address" + WIKI_RECOMMENDATION = "Check the result of ecrecover" + + def _analyze_function( + self, + function: FunctionContract, + args_to_verify, + check_recursive=False, + visited=[], + ) -> Tuple[set, set]: + checked = set() + unchecked = set(args_to_verify) + + for node in function.nodes: + if node.is_conditional(include_loop=False): # contains if/assert/require + for a in unchecked: + if any([is_dependent(arg, a, node) for arg in node.variables_read]): + checked.add(a) + + unchecked.difference_update(checked) # remove all checked + + visited.append(function) + if check_recursive: + for node in function.nodes: + for internal_call in node.internal_calls: + # Filter to Function, as internal_call can be a solidity call + if isinstance(internal_call, Function): + _checked, _unchecked = self._analyze_function( + internal_call, unchecked, False, visited + ) # We are going only 1 step into in recursion + checked |= _checked + unchecked = _unchecked + + return checked, unchecked + + def _detect(self): + results = [] + for contract in self.compilation_unit.contracts_derived: + for f in contract.functions: + if ( + f.signature_str + == "executeOperation(address[],uint256[],uint256[],address,bytes) returns(bool)" + ): + _, unchecked = self._analyze_function( + f, + [ + SolidityVariableComposed("msg.sender"), + f.parameters[3], # initiator parameter + ], + check_recursive=True, + ) + if unchecked: + info = [ + "Unchecked function parameters in AAVE callback: ", + f, + "\n", + ] + for var in unchecked: + if var == SolidityVariableComposed("msg.sender"): + info += ["\t", "'msg.sender' is not checked", "\n"] + else: + info += ["\t'", var.name, "' is not checked\n"] + + tres = self.generate_result(info) + # for var in unchecked: + # tres.add(var) + # tres.add(f) + results.append(tres) + return results diff --git a/tests/AaveFlashloanCallback.sol b/tests/AaveFlashloanCallback.sol new file mode 100644 index 0000000..b99b249 --- /dev/null +++ b/tests/AaveFlashloanCallback.sol @@ -0,0 +1,130 @@ +contract AaveFlashloanReceiverOk { + address POOL; + + function executeOperation( + address[] calldata assets, + uint256[] calldata amounts, + uint256[] calldata premiums, + address initiator, + bytes calldata params + ) external returns (bool) { + params; + require( + initiator == address(this), + "Wallet::executeOperation: FORBIDDEN" + ); + require( + msg.sender == address(POOL), + "Wallet::executeOperation: FORBIDDEN" + ); + //doing transfer + // for (uint i = 0; i < assets.length; i++) { + // uint amountOwing = amounts[i].add(premiums[i]); + // IERC20(assets[i]).approve(address(POOL), amountOwing); + // } + return true; + } +} + +contract AaveFlashloanReceiverVulnerable { + address POOL; + + function executeOperation( + address[] calldata assets, + uint256[] calldata amounts, + uint256[] calldata premiums, + address initiator, + bytes calldata params + ) external returns (bool) { + //doing transfer + // for (uint i = 0; i < assets.length; i++) { + // uint amountOwing = amounts[i].add(premiums[i]); + // IERC20(assets[i]).approve(address(POOL), amountOwing); + // } + return true; + } +} + +contract AaveFlashloanReceiverOk2 { + address POOL; + + function executeOperation( + address[] calldata assets, + uint256[] calldata amounts, + uint256[] calldata premiums, + address initiator, + bytes calldata params + ) external returns (bool) { + if (msg.sender != POOL) { + revert("not pool"); + } + assert(initiator == address(this)); + + //doing transfer + // for (uint i = 0; i < assets.length; i++) { + // uint amountOwing = amounts[i].add(premiums[i]); + // IERC20(assets[i]).approve(address(POOL), amountOwing); + // } + return true; + } +} + +contract AaveFlashloanReceiverOk3 { + address POOL; + + function _ensure_params(address initiator) internal { + // if (msg.sender != POOL) { + // revert("not pool"); + // } + assert(initiator == address(this)); + } + + function executeOperation( + address[] calldata assets, + uint256[] calldata amounts, + uint256[] calldata premiums, + address initiator, + bytes calldata params + ) external returns (bool) { + if (msg.sender != POOL) { + revert("not pool"); + } + _ensure_params(initiator); + + //doing transfer + // for (uint i = 0; i < assets.length; i++) { + // uint amountOwing = amounts[i].add(premiums[i]); + // IERC20(assets[i]).approve(address(POOL), amountOwing); + // } + return true; + } +} + +contract AaveFlashloanReceiverOk3WithModifier { + address POOL; + modifier onlyThis() { + require(msg.sender == POOL); + _; + } + + function _ensure_params(address initiator) internal { + assert(initiator == address(this)); + } + + function executeOperation( + address[] calldata assets, + uint256[] calldata amounts, + uint256[] calldata premiums, + address initiator, + bytes calldata params + ) external onlyThis returns (bool) { + _ensure_params(initiator); + + //doing transfer + // for (uint i = 0; i < assets.length; i++) { + // uint amountOwing = amounts[i].add(premiums[i]); + // IERC20(assets[i]).approve(address(POOL), amountOwing); + // } + return true; + } +} From a6760975136c6860d715b3a25f8dc3801b576d27 Mon Sep 17 00:00:00 2001 From: Yhtyyar Sahatov Date: Tue, 31 Oct 2023 15:46:09 +0300 Subject: [PATCH 09/16] added docs --- .../detectors/aave/flashloan_callback.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/slither_pess/detectors/aave/flashloan_callback.py b/slither_pess/detectors/aave/flashloan_callback.py index 3a32d08..4681a40 100644 --- a/slither_pess/detectors/aave/flashloan_callback.py +++ b/slither_pess/detectors/aave/flashloan_callback.py @@ -15,19 +15,21 @@ class AAVEFlashloanCallbackDetector(AbstractDetector): """ - Detects not checked results of ecrecover + Detects if the flashloan callback `executeOperation` has `initiator` and `msg.sender` validation """ ARGUMENT = "pess-aave-flashloan-callback" # slither will launch the detector with slither.py --detect mydetector - HELP = "signer = ecrecover(hash, v, r, s)" + HELP = "see `executeOperation`callback docs" IMPACT = DetectorClassification.HIGH - CONFIDENCE = DetectorClassification.MEDIUM + CONFIDENCE = DetectorClassification.HIGH - WIKI = "https://github.com/pessimistic-io/slitherin/blob/master/docs/ecrecover.md" - WIKI_TITLE = "Ecrecover" + WIKI = "https://github.com/pessimistic-io/slitherin/blob/master/docs/aave/flashloan_callback.md" + WIKI_TITLE = "AAVE Flashloan callback" WIKI_DESCRIPTION = "Check docs" - WIKI_EXPLOIT_SCENARIO = "Attacker can validate signatures from 0x0 address" - WIKI_RECOMMENDATION = "Check the result of ecrecover" + WIKI_EXPLOIT_SCENARIO = ( + "Attacker can directly call or initiate flashloan to this address" + ) + WIKI_RECOMMENDATION = "Validate the msg.sender and `initiator` argument" def _analyze_function( self, From 6a4927f27d20ce80fabd11912f9514aed2bc53c2 Mon Sep 17 00:00:00 2001 From: Yhtyyar Sahatov Date: Tue, 31 Oct 2023 15:51:46 +0300 Subject: [PATCH 10/16] added MANIFEST --- MANIFEST.in | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..08f4f34 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include slither_pess * \ No newline at end of file diff --git a/setup.py b/setup.py index 2b19c42..3337f9b 100644 --- a/setup.py +++ b/setup.py @@ -24,4 +24,5 @@ "slither_analyzer.plugin": "slither slitherin-plugins=slither_pess:make_plugin", "console_scripts": ["slitherin=slither_pess.cli:main"], }, + include_package_data=True, ) From b3fe1d07623d067ad521a89e03f235261957cfc6 Mon Sep 17 00:00:00 2001 From: Yhtyyar Sahatov Date: Tue, 31 Oct 2023 16:49:55 +0300 Subject: [PATCH 11/16] fixed typo --- slither_pess/cli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/slither_pess/cli.py b/slither_pess/cli.py index b270891..7f0879b 100644 --- a/slither_pess/cli.py +++ b/slither_pess/cli.py @@ -9,7 +9,7 @@ import slither_pess from pkg_resources import iter_entry_points -SLITHERIN_VERSION = "0.4.0" +SLITHERIN_VERSION = "0.4.1" def slitherin_detectors_list_as_arguments() -> str: @@ -70,7 +70,7 @@ def handle_parser(args: argparse.Namespace, slither_args) -> None: run( slither_with_args + ["--exclude", slitherin_detectors], ) - elif args.seperated: + elif args.separated: print("Only slither results:") run( slither_with_args + ["--exclude", slitherin_detectors], @@ -116,7 +116,7 @@ def generate_argument_parser() -> argparse.ArgumentParser: ) parser.add_argument( - "--seperated", + "--separated", action="store_true", help="Run slither detectors, then slitherin", ) From fca7fba0ebd582b50d595be626de8d4c5e72119c Mon Sep 17 00:00:00 2001 From: Nikita Kirillov Date: Wed, 1 Nov 2023 11:35:31 +0500 Subject: [PATCH 12/16] Added run chapter into README --- README.md | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5e7a970..48adbba 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,7 @@ To install Pessimistic Detectors: python3 setup.py develop ``` > Keep in mind that you don't have to reinstall the plugin after changes in the repository! -4. Run the original Slither as usual. -5. Dependencies must be installed in order to test the detectors on our test contracts: +4. Dependencies must be installed in order to test the detectors on our test contracts: ```bash npm install ``` @@ -49,7 +48,25 @@ npm install ```bash pip install slitherin ``` -3. Run the original Slither as usual. + +## Usage +### Slitherin-cli (Recommended) +Use Slitherin-cli to run detectors on a Hardhat/Foundry/Dapp/Brownie application. You have the following options: +* Run ONLY Slitherin detectors: +```bash +slitherin . --pess +``` +* Run ONLY Slither detectors: +```bash +slitherin . --slither +``` +* Run Slither detectors, then Slitherin detectors: +```bash +slitherin . --separated +``` +> Keep in mind that Slitherin-cli supports all Slither run options. +### Slither +Slitherin detectors are included into original Slither after the installation. You can use Slither [as usual](https://github.com/crytic/slither#usage). ## **Detectors Table** From 393c2fb619ce6dc9749565a5822d08083740b837 Mon Sep 17 00:00:00 2001 From: Yhtyyar Sahatov Date: Wed, 1 Nov 2023 13:22:50 +0300 Subject: [PATCH 13/16] added docs --- docs/aave/flashloan_callback.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 docs/aave/flashloan_callback.md diff --git a/docs/aave/flashloan_callback.md b/docs/aave/flashloan_callback.md new file mode 100644 index 0000000..542f7b6 --- /dev/null +++ b/docs/aave/flashloan_callback.md @@ -0,0 +1,19 @@ +# AAVE Flashloan callback detector + +## Configuration + +- Check: `pess-aave-flashloan-callback` +- Severity: `High` +- Confidence: `High` + +## Description + +It is important to validate `initiator` and `msg.sender` in `executeOperation` callback + +## Vulnerable Scenario + +[test scenarios](../tests/AaveFlashloanCallback.sol) + +## Recommendation + +Validate `initiator` and `msg.sender` From ca629bbd542a316d711bb20a66251ca3ec7d2c78 Mon Sep 17 00:00:00 2001 From: Yhtyyar Sahatov Date: Wed, 1 Nov 2023 14:55:48 +0300 Subject: [PATCH 14/16] including json files --- MANIFEST.in | 2 +- setup.py | 13 ++++- slither_pess/__init__.py | 57 ------------------- slitherin/__init__.py | 57 +++++++++++++++++++ {slither_pess => slitherin}/cli.py | 0 .../detectors/__init__.py | 0 .../detectors/aave/__init__.py | 0 .../detectors/aave/flashloan_callback.py | 0 .../detectors/arbitrary_call.py | 0 .../detectors/before_token_transfer.py | 0 .../detectors/call_forward_to_protected.py | 0 .../double_entry_token_possibility.py | 0 .../detectors/dubious_typecast.py | 0 .../detectors/ecrecover.py | 0 .../detectors/event_setter.py | 0 .../detectors/falsy_only_eoa_modifier.py | 0 .../detectors/for_continue_increment.py | 0 .../detectors/inconsistent_nonreentrant.py | 0 .../detectors/magic_number.py | 0 .../detectors/multiple_storage_read.py | 0 .../detectors/nft_approve_warning.py | 0 .../detectors/public_vs_external.py | 0 .../detectors/read_only_reentrancy.py | 0 .../detectors/reentrancy/__init__.py | 0 .../detectors/reentrancy/reentrancy.py | 0 .../detectors/strange_setter.py | 0 .../detectors/timelock_controller.py | 0 .../detectors/token_fallback.py | 0 .../detectors/tx_gasprice_warning.py | 0 .../detectors/uni_v2.py | 0 .../detectors/unprotected_initialize.py | 0 .../detectors/unprotected_setter.py | 0 .../utils/deflat_tokens.json | 0 .../utils/rebase_tokens.json | 0 34 files changed, 68 insertions(+), 61 deletions(-) delete mode 100644 slither_pess/__init__.py create mode 100644 slitherin/__init__.py rename {slither_pess => slitherin}/cli.py (100%) rename {slither_pess => slitherin}/detectors/__init__.py (100%) rename {slither_pess => slitherin}/detectors/aave/__init__.py (100%) rename {slither_pess => slitherin}/detectors/aave/flashloan_callback.py (100%) rename {slither_pess => slitherin}/detectors/arbitrary_call.py (100%) rename {slither_pess => slitherin}/detectors/before_token_transfer.py (100%) rename {slither_pess => slitherin}/detectors/call_forward_to_protected.py (100%) rename {slither_pess => slitherin}/detectors/double_entry_token_possibility.py (100%) rename {slither_pess => slitherin}/detectors/dubious_typecast.py (100%) rename {slither_pess => slitherin}/detectors/ecrecover.py (100%) rename {slither_pess => slitherin}/detectors/event_setter.py (100%) rename {slither_pess => slitherin}/detectors/falsy_only_eoa_modifier.py (100%) rename {slither_pess => slitherin}/detectors/for_continue_increment.py (100%) rename {slither_pess => slitherin}/detectors/inconsistent_nonreentrant.py (100%) rename {slither_pess => slitherin}/detectors/magic_number.py (100%) rename {slither_pess => slitherin}/detectors/multiple_storage_read.py (100%) rename {slither_pess => slitherin}/detectors/nft_approve_warning.py (100%) rename {slither_pess => slitherin}/detectors/public_vs_external.py (100%) rename {slither_pess => slitherin}/detectors/read_only_reentrancy.py (100%) rename {slither_pess => slitherin}/detectors/reentrancy/__init__.py (100%) rename {slither_pess => slitherin}/detectors/reentrancy/reentrancy.py (100%) rename {slither_pess => slitherin}/detectors/strange_setter.py (100%) rename {slither_pess => slitherin}/detectors/timelock_controller.py (100%) rename {slither_pess => slitherin}/detectors/token_fallback.py (100%) rename {slither_pess => slitherin}/detectors/tx_gasprice_warning.py (100%) rename {slither_pess => slitherin}/detectors/uni_v2.py (100%) rename {slither_pess => slitherin}/detectors/unprotected_initialize.py (100%) rename {slither_pess => slitherin}/detectors/unprotected_setter.py (100%) rename {slither_pess => slitherin}/utils/deflat_tokens.json (100%) rename {slither_pess => slitherin}/utils/rebase_tokens.json (100%) diff --git a/MANIFEST.in b/MANIFEST.in index 08f4f34..41be851 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1 @@ -recursive-include slither_pess * \ No newline at end of file +recursive-include slitherin * diff --git a/setup.py b/setup.py index 3337f9b..cc94d38 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ from setuptools import setup, find_packages -from slither_pess.cli import SLITHERIN_VERSION +from slitherin.cli import SLITHERIN_VERSION with open("./README.md", "r") as f: long_description = f.read() @@ -9,14 +9,21 @@ description="Pessimistic security Slither detectors", long_description=long_description, long_description_content_type="text/markdown", + package_data={"slitherin": ["py.typed"]}, url="https://github.com/pessimistic-io/slitherin", author="Pessimistic.io", + author_email="engineering@moonstream.to", + classifiers=[ + "Development Status :: 3 - Alpha", + "Programming Language :: Python", + "License :: OSI Approved :: Apache Software License", + "Topic :: Software Development :: Libraries", + ], version=SLITHERIN_VERSION, - package_dir={"": "."}, packages=find_packages(), license="AGPL-3.0", python_requires=">=3.8", - install_requires=["slither-analyzer>=0.8.3"], + install_requires=["slither-analyzer>=0.9.3"], extras_requires={ "dev": ["twine>=4.0.2"], }, diff --git a/slither_pess/__init__.py b/slither_pess/__init__.py deleted file mode 100644 index c573a4c..0000000 --- a/slither_pess/__init__.py +++ /dev/null @@ -1,57 +0,0 @@ -from slither_pess.detectors.arbitrary_call import ArbitraryCall -from slither_pess.detectors.double_entry_token_possibility import ( - DoubleEntryTokenPossiblity, -) -from slither_pess.detectors.dubious_typecast import DubiousTypecast -from slither_pess.detectors.falsy_only_eoa_modifier import OnlyEOACheck -from slither_pess.detectors.magic_number import MagicNumber -from slither_pess.detectors.strange_setter import StrangeSetter -from slither_pess.detectors.unprotected_setter import UnprotectedSetter -from slither_pess.detectors.nft_approve_warning import NftApproveWarning -from slither_pess.detectors.inconsistent_nonreentrant import InconsistentNonreentrant -from slither_pess.detectors.call_forward_to_protected import CallForwardToProtected -from slither_pess.detectors.multiple_storage_read import MultipleStorageRead -from slither_pess.detectors.timelock_controller import TimelockController -from slither_pess.detectors.tx_gasprice_warning import TxGaspriceWarning -from slither_pess.detectors.unprotected_initialize import UnprotectedInitialize -from slither_pess.detectors.read_only_reentrancy import ReadOnlyReentrancy -from slither_pess.detectors.event_setter import EventSetter -from slither_pess.detectors.before_token_transfer import BeforeTokenTransfer -from slither_pess.detectors.uni_v2 import UniswapV2 -from slither_pess.detectors.token_fallback import TokenFallback -from slither_pess.detectors.for_continue_increment import ForContinueIncrement -from slither_pess.detectors.ecrecover import Ecrecover -from slither_pess.detectors.public_vs_external import PublicVsExternal -from slither_pess.detectors.aave.flashloan_callback import AAVEFlashloanCallbackDetector - - -plugin_detectors = [ - DoubleEntryTokenPossiblity, - UnprotectedSetter, - NftApproveWarning, - InconsistentNonreentrant, - StrangeSetter, - OnlyEOACheck, - MagicNumber, - DubiousTypecast, - CallForwardToProtected, - MultipleStorageRead, - TimelockController, - TxGaspriceWarning, - UnprotectedInitialize, - ReadOnlyReentrancy, - EventSetter, - BeforeTokenTransfer, - UniswapV2, - TokenFallback, - ForContinueIncrement, - ArbitraryCall, - Ecrecover, - PublicVsExternal, - AAVEFlashloanCallbackDetector, -] -plugin_printers = [] - - -def make_plugin(): - return plugin_detectors, plugin_printers diff --git a/slitherin/__init__.py b/slitherin/__init__.py new file mode 100644 index 0000000..6c91a95 --- /dev/null +++ b/slitherin/__init__.py @@ -0,0 +1,57 @@ +from slitherin.detectors.arbitrary_call import ArbitraryCall +from slitherin.detectors.double_entry_token_possibility import ( + DoubleEntryTokenPossiblity, +) +from slitherin.detectors.dubious_typecast import DubiousTypecast +from slitherin.detectors.falsy_only_eoa_modifier import OnlyEOACheck +from slitherin.detectors.magic_number import MagicNumber +from slitherin.detectors.strange_setter import StrangeSetter +from slitherin.detectors.unprotected_setter import UnprotectedSetter +from slitherin.detectors.nft_approve_warning import NftApproveWarning +from slitherin.detectors.inconsistent_nonreentrant import InconsistentNonreentrant +from slitherin.detectors.call_forward_to_protected import CallForwardToProtected +from slitherin.detectors.multiple_storage_read import MultipleStorageRead +from slitherin.detectors.timelock_controller import TimelockController +from slitherin.detectors.tx_gasprice_warning import TxGaspriceWarning +from slitherin.detectors.unprotected_initialize import UnprotectedInitialize +from slitherin.detectors.read_only_reentrancy import ReadOnlyReentrancy +from slitherin.detectors.event_setter import EventSetter +from slitherin.detectors.before_token_transfer import BeforeTokenTransfer +from slitherin.detectors.uni_v2 import UniswapV2 +from slitherin.detectors.token_fallback import TokenFallback +from slitherin.detectors.for_continue_increment import ForContinueIncrement +from slitherin.detectors.ecrecover import Ecrecover +from slitherin.detectors.public_vs_external import PublicVsExternal +from slitherin.detectors.aave.flashloan_callback import AAVEFlashloanCallbackDetector + + +plugin_detectors = [ + DoubleEntryTokenPossiblity, + UnprotectedSetter, + NftApproveWarning, + InconsistentNonreentrant, + StrangeSetter, + OnlyEOACheck, + MagicNumber, + DubiousTypecast, + CallForwardToProtected, + MultipleStorageRead, + TimelockController, + TxGaspriceWarning, + UnprotectedInitialize, + ReadOnlyReentrancy, + EventSetter, + BeforeTokenTransfer, + UniswapV2, + TokenFallback, + ForContinueIncrement, + ArbitraryCall, + Ecrecover, + PublicVsExternal, + AAVEFlashloanCallbackDetector, +] +plugin_printers = [] + + +def make_plugin(): + return plugin_detectors, plugin_printers diff --git a/slither_pess/cli.py b/slitherin/cli.py similarity index 100% rename from slither_pess/cli.py rename to slitherin/cli.py diff --git a/slither_pess/detectors/__init__.py b/slitherin/detectors/__init__.py similarity index 100% rename from slither_pess/detectors/__init__.py rename to slitherin/detectors/__init__.py diff --git a/slither_pess/detectors/aave/__init__.py b/slitherin/detectors/aave/__init__.py similarity index 100% rename from slither_pess/detectors/aave/__init__.py rename to slitherin/detectors/aave/__init__.py diff --git a/slither_pess/detectors/aave/flashloan_callback.py b/slitherin/detectors/aave/flashloan_callback.py similarity index 100% rename from slither_pess/detectors/aave/flashloan_callback.py rename to slitherin/detectors/aave/flashloan_callback.py diff --git a/slither_pess/detectors/arbitrary_call.py b/slitherin/detectors/arbitrary_call.py similarity index 100% rename from slither_pess/detectors/arbitrary_call.py rename to slitherin/detectors/arbitrary_call.py diff --git a/slither_pess/detectors/before_token_transfer.py b/slitherin/detectors/before_token_transfer.py similarity index 100% rename from slither_pess/detectors/before_token_transfer.py rename to slitherin/detectors/before_token_transfer.py diff --git a/slither_pess/detectors/call_forward_to_protected.py b/slitherin/detectors/call_forward_to_protected.py similarity index 100% rename from slither_pess/detectors/call_forward_to_protected.py rename to slitherin/detectors/call_forward_to_protected.py diff --git a/slither_pess/detectors/double_entry_token_possibility.py b/slitherin/detectors/double_entry_token_possibility.py similarity index 100% rename from slither_pess/detectors/double_entry_token_possibility.py rename to slitherin/detectors/double_entry_token_possibility.py diff --git a/slither_pess/detectors/dubious_typecast.py b/slitherin/detectors/dubious_typecast.py similarity index 100% rename from slither_pess/detectors/dubious_typecast.py rename to slitherin/detectors/dubious_typecast.py diff --git a/slither_pess/detectors/ecrecover.py b/slitherin/detectors/ecrecover.py similarity index 100% rename from slither_pess/detectors/ecrecover.py rename to slitherin/detectors/ecrecover.py diff --git a/slither_pess/detectors/event_setter.py b/slitherin/detectors/event_setter.py similarity index 100% rename from slither_pess/detectors/event_setter.py rename to slitherin/detectors/event_setter.py diff --git a/slither_pess/detectors/falsy_only_eoa_modifier.py b/slitherin/detectors/falsy_only_eoa_modifier.py similarity index 100% rename from slither_pess/detectors/falsy_only_eoa_modifier.py rename to slitherin/detectors/falsy_only_eoa_modifier.py diff --git a/slither_pess/detectors/for_continue_increment.py b/slitherin/detectors/for_continue_increment.py similarity index 100% rename from slither_pess/detectors/for_continue_increment.py rename to slitherin/detectors/for_continue_increment.py diff --git a/slither_pess/detectors/inconsistent_nonreentrant.py b/slitherin/detectors/inconsistent_nonreentrant.py similarity index 100% rename from slither_pess/detectors/inconsistent_nonreentrant.py rename to slitherin/detectors/inconsistent_nonreentrant.py diff --git a/slither_pess/detectors/magic_number.py b/slitherin/detectors/magic_number.py similarity index 100% rename from slither_pess/detectors/magic_number.py rename to slitherin/detectors/magic_number.py diff --git a/slither_pess/detectors/multiple_storage_read.py b/slitherin/detectors/multiple_storage_read.py similarity index 100% rename from slither_pess/detectors/multiple_storage_read.py rename to slitherin/detectors/multiple_storage_read.py diff --git a/slither_pess/detectors/nft_approve_warning.py b/slitherin/detectors/nft_approve_warning.py similarity index 100% rename from slither_pess/detectors/nft_approve_warning.py rename to slitherin/detectors/nft_approve_warning.py diff --git a/slither_pess/detectors/public_vs_external.py b/slitherin/detectors/public_vs_external.py similarity index 100% rename from slither_pess/detectors/public_vs_external.py rename to slitherin/detectors/public_vs_external.py diff --git a/slither_pess/detectors/read_only_reentrancy.py b/slitherin/detectors/read_only_reentrancy.py similarity index 100% rename from slither_pess/detectors/read_only_reentrancy.py rename to slitherin/detectors/read_only_reentrancy.py diff --git a/slither_pess/detectors/reentrancy/__init__.py b/slitherin/detectors/reentrancy/__init__.py similarity index 100% rename from slither_pess/detectors/reentrancy/__init__.py rename to slitherin/detectors/reentrancy/__init__.py diff --git a/slither_pess/detectors/reentrancy/reentrancy.py b/slitherin/detectors/reentrancy/reentrancy.py similarity index 100% rename from slither_pess/detectors/reentrancy/reentrancy.py rename to slitherin/detectors/reentrancy/reentrancy.py diff --git a/slither_pess/detectors/strange_setter.py b/slitherin/detectors/strange_setter.py similarity index 100% rename from slither_pess/detectors/strange_setter.py rename to slitherin/detectors/strange_setter.py diff --git a/slither_pess/detectors/timelock_controller.py b/slitherin/detectors/timelock_controller.py similarity index 100% rename from slither_pess/detectors/timelock_controller.py rename to slitherin/detectors/timelock_controller.py diff --git a/slither_pess/detectors/token_fallback.py b/slitherin/detectors/token_fallback.py similarity index 100% rename from slither_pess/detectors/token_fallback.py rename to slitherin/detectors/token_fallback.py diff --git a/slither_pess/detectors/tx_gasprice_warning.py b/slitherin/detectors/tx_gasprice_warning.py similarity index 100% rename from slither_pess/detectors/tx_gasprice_warning.py rename to slitherin/detectors/tx_gasprice_warning.py diff --git a/slither_pess/detectors/uni_v2.py b/slitherin/detectors/uni_v2.py similarity index 100% rename from slither_pess/detectors/uni_v2.py rename to slitherin/detectors/uni_v2.py diff --git a/slither_pess/detectors/unprotected_initialize.py b/slitherin/detectors/unprotected_initialize.py similarity index 100% rename from slither_pess/detectors/unprotected_initialize.py rename to slitherin/detectors/unprotected_initialize.py diff --git a/slither_pess/detectors/unprotected_setter.py b/slitherin/detectors/unprotected_setter.py similarity index 100% rename from slither_pess/detectors/unprotected_setter.py rename to slitherin/detectors/unprotected_setter.py diff --git a/slither_pess/utils/deflat_tokens.json b/slitherin/utils/deflat_tokens.json similarity index 100% rename from slither_pess/utils/deflat_tokens.json rename to slitherin/utils/deflat_tokens.json diff --git a/slither_pess/utils/rebase_tokens.json b/slitherin/utils/rebase_tokens.json similarity index 100% rename from slither_pess/utils/rebase_tokens.json rename to slitherin/utils/rebase_tokens.json From b6d06c28284f02465008ed45cca41c5a34888e8d Mon Sep 17 00:00:00 2001 From: Yhtyyar Sahatov Date: Wed, 1 Nov 2023 15:05:16 +0300 Subject: [PATCH 15/16] removing accidentally add old arbitrary call --- slitherin/detectors/arbitrary_call.py | 158 -------------------------- 1 file changed, 158 deletions(-) delete mode 100644 slitherin/detectors/arbitrary_call.py diff --git a/slitherin/detectors/arbitrary_call.py b/slitherin/detectors/arbitrary_call.py deleted file mode 100644 index fe0b532..0000000 --- a/slitherin/detectors/arbitrary_call.py +++ /dev/null @@ -1,158 +0,0 @@ -from typing import List, Tuple - -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.slithir.operations import SolidityCall -from slither.core.declarations import ( - Contract, - SolidityVariableComposed, - FunctionContract, -) -from slither.core.cfg.node import Node -from slither.slithir.operations import LowLevelCall -from slither.analyses.data_dependency.data_dependency import is_dependent, is_tainted - - -# TODO/Possible improvements: -# look for transferFroms if there is any transferFrom and contract contains whole arbitrary call - it is screwed -# Filter out role protected - - -class ArbitraryCall(AbstractDetector): - """ - Detects arbitrary and dangerous calls. - (only detects low-level calls) - """ - - ARGUMENT = "pess-arbitrary-call" # slither will launch the detector with slither.py --detect mydetector - HELP = "someaddress.call(somedata)" - IMPACT = DetectorClassification.HIGH - CONFIDENCE = DetectorClassification.LOW - - WIKI = ( - "https://github.com/pessimistic-io/slitherin/blob/master/docs/arbitrary_call.md" - ) - WIKI_TITLE = "Arbitrary calls" - WIKI_DESCRIPTION = "Check docs" - WIKI_EXPLOIT_SCENARIO = "Attacker can manipulate on inputs" - WIKI_RECOMMENDATION = "Do not allow arbitrary calls" - - def analyze_function( - self, function: FunctionContract - ) -> List[Tuple[FunctionContract, Node, LowLevelCall, bool, bool]]: - results = [] - for node in function.nodes: - for ir in node.irs: - try: - destination_tainted = False - args_tainted = False - if isinstance(ir, LowLevelCall): - destination_tainted = is_tainted(ir.destination, node, True) - if ( - destination_tainted - and ir.destination == SolidityVariableComposed("msg.sender") - ): - # We don't care about msg.sender, since will be only reentrancy issue - destination_tainted = False - args_tainted = any( - is_tainted(arg, node, True) for arg in ir.arguments - ) # seems like ir.arguments = [data] for all low-level calls - - elif ( - isinstance(ir, SolidityCall) - and ir.function.name - == "delegatecall(uint256,uint256,uint256,uint256,uint256,uint256)" - ): - # delegatecall - destination_tainted = is_tainted(ir.arguments[1], node, True) - # #args_tainted = is_tainted(ir.arguments[2], node, True) - # for delegateCall we don't actually care about args, since - # for all proxies, user fully sets args - elif ( - isinstance(ir, SolidityCall) - and ir.function.name - == "call(uint256,uint256,uint256,uint256,uint256,uint256,uint256)" - ): - # call() - destination_tainted = is_tainted(ir.arguments[1], node, True) - args_tainted = is_tainted(ir.arguments[3], node, True) - - if args_tainted or destination_tainted: - results.append( - ( - function, - node, - ir, - args_tainted, - destination_tainted, - ) - ) - except Exception as e: - print("ArbitraryCall:Failed to check types", e) - print(ir) - - return results - - def analyze_contract(self, contract: Contract): - stores_approve = False - all_tainted_calls: List[ - Tuple[FunctionContract, Node, LowLevelCall, bool, bool] - ] = [] - results = [] - for f in contract.functions: - res = self.analyze_function(f) - if res: - all_tainted_calls.extend(res) - - for call_fn, node, ir, args_tainted, destination_tainted in all_tainted_calls: - info = ["Manipulated call found: ", node, " in ", call_fn, "\n"] - if args_tainted and destination_tainted: - text = "Both destination and calldata could be manipulated" - else: - part = "calldata" if args_tainted else "destination" - text = f"Only the {part} could be manipulated" - info += [f"{text}\n"] - - for f in contract.functions: - if f.visibility not in ["external", "public"]: - continue - - fn_taints_args = False - fn_taints_destination = False - args = ( - ir.arguments[0] if isinstance(ir, LowLevelCall) else ir.arguments[3] - ) - if args_tainted and any( - is_dependent(args, fn_arg, node) for fn_arg in f.variables - ): - fn_taints_args = True - - destination = ( - ir.destination if isinstance(ir, LowLevelCall) else ir.arguments[1] - ) - if destination_tainted and any( - is_dependent(destination, fn_arg, node) for fn_arg in f.variables - ): - fn_taints_destination = True - - if not (fn_taints_args or fn_taints_destination): - continue - - if fn_taints_args and fn_taints_destination: - text = "The call could be fully manipulated (arbitrary call)" - else: - part = "calldata" if fn_taints_args else "destination" - text = f"The {part} could be manipulated" - info += [f"\t{text} through ", f, "\n"] - - res = self.generate_result(info) - res.add(node) - results.append(res) - return results - - def _detect(self): - results = [] - for contract in self.compilation_unit.contracts_derived: - r = self.analyze_contract(contract) - if r: - results.extend(r) - return results From 95d65e10c71b2d728adfedfd08b4e04209279868 Mon Sep 17 00:00:00 2001 From: Yhtyyar Sahatov Date: Wed, 1 Nov 2023 15:09:23 +0300 Subject: [PATCH 16/16] fix typo --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cc94d38..4ee972a 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ package_data={"slitherin": ["py.typed"]}, url="https://github.com/pessimistic-io/slitherin", author="Pessimistic.io", - author_email="engineering@moonstream.to", + author_email="info@pessimistic.io", classifiers=[ "Development Status :: 3 - Alpha", "Programming Language :: Python",