Erik's Security Blog Practicing Constant Vigilance

Using Slither for Smart Contract Security Testing

If you’ve spent any amount of time researching solidity security or doing security reviews on smart contract code, you’ve heard of or used the tool Slither from Trail of Bits. Most Slither tutorials I’ve seen don’t provide step-by-step instructions for using the tool from a security auditor’s perspective. Most tutorials only cover the basics of using Slither. But this tutorial attempts to provide a more in-depth view of how Slither fits into my security testing workflow.

Why Slither

Slither is a static analysis tool that often requires no setup on the security tester’s part. If the code compiles with hardhat or several other frameworks, Slither can be used. The lack of any setup process is a key part of why Slither has become so widely used. Fuzzing tools are great, but setup effort is required, so static analysis tools like Slither keep life simple. Unlike other solidity static analysis tools, Slither is regularly updated and maintained, which has played a large part in why it has superceded most (all?) other solidity static analysis tools at the time of writing.

Installation and First Steps

The first step to using Slither is to install Slither. There are several installation options, and using a Python virtual environment is a good practice, but the lazy way of installing with pip is to use pip3 install slither-analyzer.

Next, make sure the solidity version you have installed is the same one required by the contracts. Compare the solc version you have installed to the pragma version in the .sol contract files you want to test. If you don’t have a project that you want to analyze of your own, you can follow along using a well-known public project.

solc --version # this prints the solc version
grep -inr pragma  # this prints the solidity version in your contracts

If the solc version doesn’t match the version number of the contracts, you can get the correct solc version either with the solc-select tool, by keeping separate Docker images for different solidity versions like Scott Bigelow describes here, or by downloading the proper version of solc binary from the solidity github repository and copying it over your existing solc binary using:

# Add `sudo` to this command if it doesn't work without sudo
cp solc-static-linux $(which solc)

To confirm Slither is installed, print the Slither help message with slither -h. If the output shows a long list of Slither command options (as opposed to an error), Slither should be installed properly. The latest version of Slither at the time of writing was 0.8.2.

Compiling the Contracts

Now that you have Slither installed and solc is the correct version, you need to make sure you can compile the contracts without any errors. Normally a project will require the installation of JavaScript dependencies before it can be built. These dependencies can be installed with either npm or yarn. The best approach is to read the project documentation and use the build instructions provided. If there are no instructions, I use a simple rule of thumb. If there is a yarn.lock file in the project directory, use yarn install to install the dependencies. If there is only a package.json file but no yarn.lock file, then use npm install to install the dependencies.

The command used for compiling the project depends on the development toolchain used. If hardhat is used, you should find a hardhat.config.ts or hardhat.config.js file in the top level directory. Make sure you are in the same directory as the hardhat.config.js file when you run npx hardhat compile.

If another development toolchain is used, such as truffle, waffle, dapptools, foundry, etc., refer to the project’s documentation or visit the official website for the proper development toolchain and read the toolchain documentation on how to compile a project with that tool. If brownie or foundry are the build frameworks, you’re out of luck as of early 2022 - see the upcoming section about that.

Running Slither

After you have the project compiling without errors using its native framework, it’s time to run Slither. The easiest way to run all 76 Slither detectors is by navigating to the top level directory of your project and running slither .. If all goes well, you should see colorful output like the screenshot below.

Slither output

Many examples online have more arguments in the command, but you don’t need them for Slither to run successfully. There are many old issues in the Slither repository where users try running Slither with slither ./contracts, but this is incorrect because Slither expects to receive the top level directory of the project, not the contracts directory. By default, Slither will compile the contracts when it is run. If you just finished compiling your contract with npx hardhat compile or a similar command, there is no need for Slither to waste time repeating the compilation, so you can run Slither with slither . --hardhat-ignore-compile to tell Slither to skip compiling the code and save some time. The slither -h help message shows there is a similar flag to skip compilation with waffle, dapptools, etc.

Troubleshooting Slither Compilation Errors

Slither will sometimes encounter an error. If you are testing a project that uses brownie or foundry, skip to the end to this section for an explanation on known open issues. Sometimes reading the Slither error message will guide you on how to solve the error, but sometimes it’s not very helpful. In these cases, I start by checking whether the program compiles by testing:

  1. Can I compile the project with the native framework? For example, if the project uses hardhat, try npx hardhat compile. If this fails, you might have an error in your code or are missing dependencies.
  2. If the native framework compilation succeeds, does the project compile with crytic-compile? Run crytic-compile . If this fails, read the error messages to see if they guide you to a solution, or you might be using brownie or foundry and are straight out of luck.
  3. Finally, if crytic-compile succeeds, run slither . or slither . --hardhat-ignore-compile (or the corresponding ignore-compile flag for your framework). Slither might not recognize the proper build framework, in which case you can use the --compile-force-framework <framework> option to force a specific framework.

For some deeper background, crytic-compile is what Slither uses to compile the contracts before it begins static analysis, and sometimes crytic-compile doesn’t like the code you try to compile. If the three steps above don’t solve the issue for me, I take some keywords from the error messages I received from Slither and crytic-compile and search for Slither past issues and crytic-compile past issues. It can help to search both projects because sometimes the issue can get posted to the wrong project. If your searches don’t yield fruit, and searching on Google and Stack Overflow doesn’t help either, it might be time to post a new issue to the Slither GitHub.

Other Slither Troubleshooting

Other edge cases I have encountered include:

  1. The error message is related to missing fields in the “networks” section of hardhat.config.ts, which might be due to a missing .env file. If your goal is only to run Slither, you can delete the entire “networks” section of hardhat.config.ts because it’s only used for deployment tests and unit tests.
  2. Sometimes the package.json file doesn’t include all the dependencies for a project. You can install a single npm dependency individually for the local project, without modifying the package.json file, using npm install <package-name>. For example, npm install @rari-capital/solmate. You can use npm install with a GitHub URL like npm install https://github.com/OpenZeppelin/openzeppelin-contracts.
  3. Test files sometimes interfere with Slither, so deleting the contracts/tests directory, if there is one, can help.
  4. Occasionally Slither doesn’t like how the imports are written in the contract files. You can use the --solc-remaps command line flag to fix this. For example, to tell Slither that the “@openzeppelin/contracts” path in an import statement actually means “./build/dependencies”, use slither . --solc-remaps @openzeppelin/contracts=./build/dependencies.

Using a Custom Slither Config File

If you’ve used Slither before, you may have been annoyed by findings concerning dependencies in the node_modules folder. A Slither config file can simplify your output and remove these findings. The full list of config file options is on the project page, but I normally filter out the node_modules folder at a minimum. This config file can have any name, but Slither will require the --config-file NewFileName argument to specify where the config file is if the default name is not used. For this reason, I find it easier to always use the default config file name, which is “slither.config.json”. Copy the filter_paths statement below into a file named “slither.config.json” located in the top level directory of your project (the same location where you run slither .) and Slither will use this config file for your project.

{
    "filter_paths": "node_modules"
}

The config file data above is will remove findings from the node_modules directory. You can go further and remove multiple paths from Slither results. For example, if you wanted to remove the “contract/Interfaces” and the “contract/Math” directories from Slither analysis, you could use the following config file.

{
    "filter_paths": "node_modules|Interfaces|Math"
}

Be aware that Slither documentation as of April 2022 suggests separating filter path values with a , instead of a |, which is incorrect.

Another useful config file option is to filter out findings of a certain risk level. For example, if we wanted to remove optimization, informational, and low risk findings, which often have some false positives that clutter the results output, we could use this config file.

{
    "filter_paths": "node_modules",
    "exclude_optimization": true,
    "exclude_informational": true,
    "exclude_low": true
}

Config File Alternative: Command Flags

The output of slither --help lists the optional flags you can add to the slither . command. Adding flags will give the same results as a config file. I prefer storing the config data in a config file, but that is my personal choice. For example, to duplicate the config file result above, you can use slither . --filter-paths "node_modules" --exclude-optimization --exclude-informational --exclude-low.

Bonus Feature: Slither Github Action

In February 2022, the Slither GitHub action was published. This can run Slither automatically each time a PR or commit is added to your project. Whether you would find Slither output after each commit to be useful is your choice, but filtering out the low risk issues using the config file above may help reduce the noise in the results.

Some Slither Weaknesses

There’s a few quirks with Slither that are minor and understandable for an automated tool with no setup required. It is worth mentioning these in case you encounter the same quirks.

The first quirk is that Slither doesn’t consider the modifiers involved when checking for findings like reentrancy. For example, Slither might identify a function as vulnerable to reentrancy even though there is a “nonReentrant” or “lock” modifier on the function. In other places, it might flag a function such as “selfdestruct” when the “onlyOnly” modifier exists on the function. It is always mandatory to check the output of any automated security tool, but I have learned to expect to see “nonReentrant” modifiers on most functions that Slither highlights as potentially vulnerable to reentrancy.

There is still plenty of room to grow for automated solidity security tools of the future. Several detectors could be added to handle Open Zeppelin dependencies alone to make sure they are used properly, let along other libraries. Possible checks could include: 1. checking for an initializer modifier on any function containing the letters “initialize” or “initialization” to prevent the possibility of multiple initializations 2. validating that inherited contracts are initialized properly if openzeppelin-contracts-upgradeable imports exist 3. checking the package.json for outdated versions of Open Zeppelin contracts with known security issues.

Slither Printers

I first saw the Slither printer feature demoed in this talk by Scott Bigelow. This feature is well documented. Printers are a way to visualize the functions in the project in different ways. There are 18 different printers, each providing a different view on the project code. To list the different printers, use slither --list-printers. Here are some examples of using the printers, and remember that adding the --hardhat-ignore-compile argument can speed up the process by skipping compilation:

  • slither . --print human-summary provides a summary report of the Slither static analyzer output, along with extra information
  • slither . --print function-summary provides a summary of all functions with their corresponding modifiers, internal calls, state variable read/write, etc.
  • slither . --print variable-order lists the storage slots used by each variable
  • slither . --print vars-and-auth lists the state variables modified in each function and the authorization applied to msg.sender
  • slither . --print constructor-calls prints the constructor function contents for each contract and its inherited contracts

The main difficulty I have had using these printers is sizing the output properly so the table renders neatly while the text is still large enough to be legible. I personally prefer the layout provided by VS Code extensions that serve a similar purpose.

Slither human-summary printer output

Slither function-summary printer output

Other Slither Tools

Slither’s static analyzer may be the most commonly used part of Slither, but wait, there’s more! Slither also includes the following CLI tools:

  • slither-check-erc
  • slither-check-kspec
  • slither-check-upgradeability
  • slither-find-paths
  • slither-flat
  • slither-format
  • slither-mutate
  • slither-prop
  • slither-simil

Not all of these tools have much official documentation. I will only mention a few here.

Slither ERC Checker

The slither-check-erc tool is the tool I have used the most, besides the main static analyzer. Any time I encounter a contract that implements a common ERC token, I run this tool as a sanity check. If you’re really lazy, check the output of slither . --print human-summary and look for the ERCs output line to see what is automatically detected. The output of slither-check-erc lists whether all the functions required by the ERC specification are implemented. The output contains optional functions for the ERC too, pointing out possible areas for improvement even if the minimum requirements are met. Using slither-check-erc is very similar to running Slither, but the contract name must be provided as an input argument. The command to run this tool on an ERC20 token contract, which is the default ERC for the tool, is shown below.

slither-check-erc . ERC20ContractName

slither-check-erc supports the following tokens: ERC20,ERC223,ERC165,ERC721,ERC1820,ERC777,ERC1155. To test a token other than ERC20, include the --erc argument.

slither-check-erc . --erc ERC1155 yAcadToken1155

Slither Flattener

The slither-flat tool flattens a codebase. What this means in practice is that all contracts for a project are reorganized into a single folder. This process is most useful to prepare the contracts for another tool like mythx that might encounter problems with external contract imports. You can see the results yourself using slither-flat ., which will store the output files in a new directory named “crytic-export/flattening”.

Slither Upgradeability Checker

The slither-check-upgradeability tool automatically checks contracts that use the delegatecall proxy pattern. Because of the evolving nature of proxy architecture and strategies, I haven’t used this tool as much as expected, but if you do encounter this scenario, the tool is definitely worth using. It can be run with slither-check-upgradeability ContractName.sol ContractName.

Writing Custom Python Code with Slither

You can use Slither’s intermediate representation to write your own Python scripts.

Here’s one custom code example that prints out the contract name and function name if a function match the criteria 1. the function is public or external 2. state variables are modified in the function 3. there is no onlyOwner modifier on the function. Save this code in a Python .py file in the same top level directory where you run slither .. This python script is run like any other python script, using python3 filename.py.

from slither.slither import Slither  
  
slither = Slither('.')

for contract in slither.contracts:
    for function in contract.functions:
        if function.is_constructor:
            continue
        if function.visibility in ['public', 'external']:
            if len(function.state_variables_written) > 0:
                if not 'onlyOwner()' in [m.full_name for m in function.modifiers]:
                    print('Contract: '+ contract.name)
                    print('>>Function: {}'.format(function.name) + ' is unprotected!')

What to do with Brownie and Foundry Projects?

There are open issues that brownie and foundry don’t currently play nice with Slither (technically the issue is with crytic-compile, which Slither relies upon), so what is the workaround? My simple hack is to create a new hardhat project, copy the contracts over, fix dependencies as needed, make sure the code compiles, and run Slither on the hardhat project. Here are my steps to doing this.

First, check that you have hardhat installed globally on your system by viewing the help message from npx hardhat --help. If you see the hardhat help message, then setting up a fresh hardhat project is as simple as running npx hardhat in the directory where you want to store the project, selecting the “Create a basic sample project” option when asked, and answering the prompts (usually by tapping enter to accept the defaults until no more prompts appear).

A few final steps are needed to copy over the original project to the blank hardhat template.

  1. Delete the “contracts” directory from the sample hardhat template, because it contains a default Greeter.sol file that we don’t need. Then copy the “contracts” directory from the original project to the new sample project.
  2. Open the original package.json file and copy the “scripts” and “dependencies” portions of the original package.json file to the new package.json file in the sample hardhat project.
  3. In the new sample hardhat project hardhat.config.js file, set the proper solidity version based on the version used in the .sol files in the “contracts” directory.

If you’re lucky, npx hardhat compile should compile the project normally now. If you’re not lucky, the Slither error messages can often guide you in the right direction, especially if you’re willing to tinker around with the config files and/or imports to get everything working properly. Check out the troubleshooting section for more ideas. If the only goal is to run Slither, I don’t worry about copying the tests or other files because Slither only cares about the files in the contracts directory.

Seth: A Blockchain CLI Tool

Seth is a very useful command line interface (CLI) tool from DappTools that is useful for poking around the blockchain. Seth pulls a lot of the same blockchain data that Etherscan does, and a few small pieces of data come from the Etherscan API. But the CLI tool format makes it easy to include seth for scripting and automation purposes. Let’s see what it can do.

Installing Seth

To use seth, you need to install dapptools. The official instructions using Nix are quite straightforward, so I defer to them. The other important setup step is to set up the “ETH_RPC_URL” environment variable with an RPC endpoint. You can create a free account with Alchemy or Infura to get your own RPC endpoint URL.

Fetching Contract Source Code

Seth connects to the Etherscan API to provide a simple way to download contract source code. If you prefer reading code in your local editor like VS Code instead of reading from etherscan or etherscan.deth.net, this one’s for you. First make sure you have a free Etherscan account so you can get an Etherscan API key. Then store the Etherscan API key in your ETHERSCAN_API_KEY environment variable, because this variable name is where seth is configured to look for the key. Afterwards, you can download any contract source code if the code is verified on Etherscan. For example, to download the WETH contract source code, use the following (and wait a bit if the Etherscan API is slow):

seth etherscan-source 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 | jq .SourceCode -r

To show the potential of packaging this single feature in a CLI, you could use this feature to build a bug bounty automation platform. Although such a system is common for web2 bug bounties, no public example of this type of project for web3 has been published yet. If you want to start building, the system could consist of:

  1. A project like changedetection.io to monitor the list of bug bounty target contracts from Immunefi, Hackenproof, Hats.finance, or other another website
  2. When a change is detected, either when a new project is added or when a new contract is added to the scope of an existing project, download the source code of the contract using seth
  3. Run your code analysis process on the new contract source code (hint: this step is the hard part of the whole process)
  4. Receive automated alerts from your system when it has found something (for an easy option, consider the poor man’s monitoring setup)
  5. Submit findings to bounty platforms & profit

Zooming In On The Latest Block

We can analyze individual blocks with the seth block <block number> command and the most recent block number is queried with seth block-number. We can chain these two commands together and receive data about the most recent block with seth block $(seth block-number), which gives output like:

seth block data

baseFeePerGas           28841430027
difficulty              13016799566303853
extraData               1936027442
gasLimit                30000000
gasUsed                 8255948
hash                    0xb22491dfccef3296e776d94f6ddb738a6f9a6738917589dced7488c4de11a07c
logsBloom               0xa424015a83a6c914902a74028012293d801080081d1100290209810b5401212514b00102c4060040066470c021088d0102a48970ca203a8034808ad0d332298f0043024803445128c85a141e9801442814a5100f0c5192803c5aa421c0331b0166c0888406225412e044d00900a1cc5c265e442924081fc08004533901081050451021610400104a848921410191004386480001c580449b0260ab4c4a1418300280011b2caa2c290e80efd89d000206020c820044b026200b10c02d906c4c31500044020d8008a203901c0046c1400428185600038b52581c52a12e064220004a39252b50081140a19602084d34488400d281512394c050004088a24c207007
miner                   0x3ecef08d0e2dad803847e052249bb4f8bff2d5bb
mixHash                 0x38133a0f95886cdae8fa9da3055dc409b5677398cde2de386c135c2186959df1
nonce                   5683548311596532688
number                  14558180
parentHash              0x8981b0db340d5ce814fd3e4d4004d8072078b2e32878cd6cbbef7bc4f40f49aa
receiptsRoot            0x02132c612be2bf5d7d218a94e0e80d2054bb436bd9acd3cfd9bf6f2bda8c03a6
sha3Uncles              0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347
size                    57834
stateRoot               0xcce2182138ef57aca2595ef47272939f385f172300e2d2c5f817e9c06857e5d7
timestamp               1649596875
totalDifficulty         0x9be29f08d671e01090e
transactions            ["0xe99c8f16acfe4c30ea1d62fc009c52abda866a209265fd0231125b3f5829bd41","0x0607ceb4e5fccf32785905accd2e0b5337f24e756a64d5b9f71b1bc88763971e","0xa56fdefd60068407dce5b794d6e35fd6175fd4afbe118a4adc4cb4145b737f6e","0x53a31f2dea2e57094185a4e6eba9d56f743a4b9acc66e452904f6eeef69e78d9",
        ...truncated transactions list...
"0x0572abcfdcf3df03e47cfdae391bc6c27daf9a2e1636a78601a6f926ed1a6cb7","0xafaeb7f5caf3458ef0531763642511df8b73078d6d1bd2352977e552d304a4fe","0x75ce9178abcdcd92f30549524a259594cd85d13f0aacfa45100896d769573aca"]
transactionsRoot        0xf5c242a4f103b556008866961ab0962982d26f62420fd3a6aa512c6f454379e9
uncles                  []

The data for this block lists the transactions, gasLimit, gasUsed, and other data about the block. To examine the first transaction in the list, use seth tx 0xe99c8f16acfe4c30ea1d62fc009c52abda866a209265fd0231125b3f5829bd41.

accessList              [{"address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","storageKeys":["0x14edb3f2ae9217ac1062a3663ff296e62d08cb5e4c1652d16c28cc4aede04f59","0xa16f6733e9a6e575701fc996707206adb879e7981d0e768b0d54351c053ccb2b"]},{"address":"0xcb9fe6b9f0411ace824d8bf4bba29f03387ff459","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000008","0x9b637a02e6f8cc8aa1e3935c0b27bde663b11428c7707039634076a3fb8a0c48","0x855faaa6e0188a0cdf37dd9705e806a3371b9f2e5713754b396eb30e702b980a","0x855faaa6e0188a0cdf37dd9705e806a3371b9f2e5713754b396eb30e702b9807","0x0000000000000000000000000000000000000000000000000000000000000004","0x855faaa6e0188a0cdf37dd9705e806a3371b9f2e5713754b396eb30e702b9808","0x855faaa6e0188a0cdf37dd9705e806a3371b9f2e5713754b396eb30e702b9809","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000000"]},{"address":"0x6f80310ca7f2c654691d1383149fa1a57d8ab1f8","storageKeys":["0xb3fae41a68fb6884ab6ce157e11f00eb8a737b57cf3936718e61999fd88b0185","0x20f0618767b04ed4c26dde2cc235e173c785c75b16fd723e08eacce0b2479536","0xfe3b247b4660499a8b482ec4c25c60ab4e77c2245c27088eb7b038386c6a9842","0xfa52f1eec9dfd6d84689cfc1c47c7527cf9b4ed5dbbecc964f3259310912525b"]}]
blockHash               0xb22491dfccef3296e776d94f6ddb738a6f9a6738917589dced7488c4de11a07c
blockNumber             14558180
chainId                 1
from                    0x5aa17fc7f2950eca85376c3a8cb1509e8e4b39df
gas                     179307
gasPrice                28841430027
hash                    0xe99c8f16acfe4c30ea1d62fc009c52abda866a209265fd0231125b3f5829bd41
input                   0x040012de32ce7fb9c00000000000012346efe5f60f679dbbcb9fe6b9f0411ace824d8bf4bba29f03387ff45913432bb9cc0cb5cd04abdb5c35b7ecd2c4597192a147a3fd138dd38d65064a95
maxFeePerGas            28841430027
maxPriorityFeePerGas    0
nonce                   48597
r                       0x6a88a78634e50895ea891d5561a9ab1d03d83fcb1c8c567d80bb14940cee83b6
s                       0x771e073dc88668ab4d6107b11897e58af131fa478addcb244ea7e33c5ce773bf
to                      0x01ff6318440f7d5553a82294d78262d5f5084eff
transactionIndex        0
type                    2
v                       0
value                   14558180

This data is a bit hard for a human to decrypt on the spot (though maybe not for you, anon), so we can turn to our friend ethtx.info to examine this transaction more closely. If we compare the seth data above to the screenshot below, we find the seth “from” field matches the ethtx.info “Sender” field, the seth “to” field matches the ethtx.info “Receiver” field, etc. The list of values in the seth “accessList” row refer to the state variables stored at those addresses. This is easier to see with the 2nd address in the seth “accessList” row, where storage slot values of 0x2, 0x8, 0x4, 0x1, and 0x0 exist. The “accessList” values that contain a large hex value likely the hashes of functions, mappings, or other data. We can retrieve the data in these storage slots using seth.

ethtx.info

To see the value stored in storage slot 2 of address 0xcb9fe6b9f0411ace824d8bf4bba29f03387ff459, use seth --to-dec $(seth storage 0xcb9fe6b9f0411ace824d8bf4bba29f03387ff459 2). The value returned is identical to the value of the feeGrowthGlobal1x128 uint256 value visible on Etherscan. While it’s often easier for a human to browse Etherscan where the data is human readable, seth provides the data in a more machine-readable format for scripting and automation purposes. Additionally, when Etherscan is unavailable, using seth to connect directly to an RPC endpoint can provide an alternative way to get the same information.

Switching chains

Seth can easily query data from Ethereum testnet chains if you change the JSON RPC endpoint in the ETH_RPC_URL environment variable to an endpoint for your preferred blockchain.

Real-World Examples

The seth documentation one-pager has a long list of CLI flags and arguments with descriptions, but I think it’s more fun to summarize seth features with real-world use cases:

  1. To check the keccak hash of a value, use seth keccak "totalSupply()"
  2. Forgot the value of type(uint32).max or type(int32).max? Use seth --to-dec $(seth --max-uint 32) and seth --to-dec $(seth --max-int 32)
  3. To solve Ethernaut level 8 by performing a storage slot lookup, first set your “ETH_RPC_URL” environment variable to a rinkeby RPC endpoint (Infura or Alchemix provide endpoints if you make a free account), then run this one-liner: seth --to-ascii $(seth storage 0xB32Ef1e8b55FE270Bc665F52076bc677B0D02f8f 1)
  4. To check the balance of vitalik.eth in ETH units (Note: this requires Etherscan API key for ENS address resolution) seth --from-wei $(seth balance $(seth resolve-name vitalik.eth))
  5. Query the totalSupply of WETH and convert the return hex value to decimal, then convert the decimal value from wei units to ether units seth --to-dec $(seth call 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 "totalSupply()")
  6. Querying the nonce for an address should return the number of transactions this address has sent using. To check this value, use seth nonce 0x5aa17fc7f2950eca85376c3a8cb1509e8e4b39df. At the time of writing, the output was 48603.
  7. You can manually perform the same contract verification process that Etherscan does to validate that the source code of a contract matches the deployed bytecode. First, download the deployed bytecode of a contract using seth code <address>. Second, download the contract source code using the method from before of seth etherscan-source <address> | jq .SourceCode -r. Finally, compile the contract code using solc --bin --optimize filename.sol (replace the filename with the filename you chose for the local copy of the contract code). You can then diff the hex binary output of the locally compiled code with the contract’s hex binary data downloaded using seth. One open question I had after trying this out is why the hex binary value of a contract viewed on Etherscan is different from the hex binary value I downloaded using seth. Both values came from the same blockchain, so what gives? If you find the answer, let me know and I can add an explanation here.
  8. seth send is very useful when you want to send transactions to the blockchain. See the official documentation.

There are many further features of seth, but to avoid rehashing the official documentation, I want to keep this short. Lastly, seth has many further use cases when paired with the rest of the dapptools suite, including hevm, dapp, and ethsign.

Concluding Remarks

If you’ve used Etherscan a lot, you’ll notice you can get similar information using seth. This makes sense, because both tools query data from the same blockchain, so there should be a lot of overlap. If you prefer working in the terminal to working in a web browser, seth is a great option. And if you are building an automated pipeline that needs blockchain data, seth is a great tool for this use case.

Etherscan: Everyone's Favorite Blockchain Explorer

If you’ve been around the Ethereum ecosystem for some time, you’ve already used Etherscan before. Etherscan has many useful features, some more hidden than others, so I will start from the beginning. Skip any sections that are old news to you.

Which Chain?

There are blockchain explorers for Ethereum testnets, like rinkeby and goerli, as well as many other blockchains. This article only focuses on the mainnet Etherscan Etherscan website, but most of this information should translate to other block explorers.

Setup

There is no mandatory installation or setup for Etherscan, but it is helpful to create a free account if you will be using the API. Having an account allows you to send more requests to the website before getting rate limited.

More Information

If you have specified Etherscan questions, search the developer docs or the knowledge base. This article will give a broad overview focusing on security use cases.

EoA Address Lookup

First let’s search for an externally owned account (EOA) address, which is an address owned by some user, as opposed to a contract. You can search by ENS name or using the actual address. Here’s what the result of searching for “vitalik.eth” looks like.

vitalik.eth result

Because “vitalik.eth” is an ENS domain, our search tells us what the resolved address of this ENS domain is. If we click on this address, we will see the transactions made by “vitalik.eth”.

vitalik.eth transactions

The two main sections of address information are:

  1. The information tabs at the bottom that list different types of information. The default tab selected is “Transactions”, but there are six other tabs visible: Internal Txns, Erc20 Token Txns, Erc721 Token Txns, Erc1155 Token Txns, Analytics, and Comments.
  2. The Overview section lists the tokens that an address holds. The “Balance” in the screenshot below only includes the value of the Ether the account holds. If you click on the “Token” dropdown menu, a list of ERC tokens held by the account is shown. A cleaner view of the tokens is available by clicking the “View expanded ERC-20 token holding” button to the right of the dropdown menu.

vitalik.eth tokens

Because most security usage of Etherscan involves investigating contracts, searching for an EoA for security purposes is a less common activity.

Contract Address Lookup

You can search for a contract using either the contract address or a descriptor for the contract. To see the results of searching by descriptor, type “WETH” into the search bar but DO NOT submit the search. Instead, after typing “WETH” you will see a dropdown menu of tokens appear below the search bar. You can click the WETH result from the dropdown to get to the WETH contract. As a sidenote, you can even search for queries besides ERC token names, such as the phrase “Compound Governance”.

WETH Search

We can see the information available for this contract looks different than the EoA result from earlier. There is no “Balance” or “Tokens” list, but in its place is the “Price”, “Fully Diluted Market Cap”, “Max Total Supply”, “Holders”, and “Transfers”. And further down, we can see different tabs to view different information.

WETH Contract

Let’s click on the “Contract” tab and examine the WETH contract more closely.

Contract Function Interaction

The “Contract” tab is where most of the security work in Etherscan happens, because that’s where you will find the contract code. Under the “Contract” tab, click the “Expand All” link on the right. The list you will see is a list of contracts in the function.

WETH Contract Functions

If the function does not take input parameters and is a view function, meaning it only reads the blockchain state and does not modify the state (read-only), then the result will automaticaly be shown. For example, the “totalSupply” function outputs the total supply of WETH, and if you refresh the page a few times, you can see the value changing. Other functions, like the “name” and “decimals” functions, have outputs that are hard-coded to the contract and will not change (this is based on extra knowledge of the ERC20 contract code, so Etherscan doesn’t provide this information). If a function is called with input parameters, you can provide those input parameters and click the “Query” button to query the result from the blockchain. To test out this feature, enter the address “0xd8da6bf26964af9d7eed9e03e53415d37aa96045” (the same vitalik.eth resolved address we used earlier) in the input field for the “balanceOf” function and click the “Query” button. The result in this case is in units of wei, because the decimals value was 18. If we divide this value by 10^18, we get the result 0.05 WETH. We can double check our work by searching for the “0xd8da6bf26964af9d7eed9e03e53415d37aa96045” address, clicking on the dropdown menu of Tokens in the upper left, and observing that this address holds 0.05 WETH. You can even connect your Metamask wallet to Etherscan to interact with contract functions directly, but this approach is not often used for security because custom scripts and contracts are more often the tool of choice.

WETH balanceOf

Contract Code

Often the reputable contracts that you will look at in Etherscan have verified source code. Let’s pretend we want to examine the deployed code for MakerDAO, because we’re either doing some fun research or heard they offer a large bug bounty. We can search the MakerDAO developer documentation and GitHub repository to see if they maintain a public list of their actively deployed contracts, and like any major protocol should, they do have such a list. You can pick any contract address, but for this example I will use the Changelog contract at 0xda0ab1e0017debcd72be8599041a2aa3ba7e740f (which is actually named Chainlog in the contract source code). Make sure you have “Ethereum mainnet” selected on this page, because we are using the Ethereum mainnet Etherscan website.

MakerDAO Changelog

When we search for this address in Etherscan and navigate to the “Contract” tab, we see that the “Contract” tab has a green checkmark and contains source code. This is because the contract developers uploaded their contract source code and Etherscan verified that the compiled source code matches the bytecode that is stored on the Ethereum blockchain. The source code is visible in Etherscan.

MakerDAO Source Code

There are some options to the top right of the “Contract Source Code” that can sometimes be useful:

  1. The “Outline” dropdown menu makes it easy to navigate to specific functions in the code
  2. The “More Options” dropdown menu allows you to
    • Find similar contracts with the “Similar” button (with mixed results in my experience)
    • Visualize the contract function calls with the “Sol2Uml” button (see screenshot below)
    • Diff the contract with another contract with the “Compare” button

UML Diagram

Verified source code might be the most used Etherscan feature for security purposes. Scrolling through code on Etherscan is how you look for bugs on mainnet, because the code in the GitHub repository for projects can often be different than what is currently deployed. Most large projects should have verified their source code on Etherscan, because it improves transparency and trust in the project. If the developers did not upload their source code to Etherscan, you can still try decompiling the bytecode using one of the many options for Ethereum decompilers, but this process can get messy and is normally only necessary when analyzing contracts used in hacks.

When contract source code is verified on Etherscan, the contract must be “flattened”. This means that the import statements are removed and the source code for the contracts that were imported and stored in the same file. You will encounter contract code on Etherscan that is very long because of this, and sometimes you need to scroll to the very bottom of the source code, past all of the imported code, to find the custom contract code that you are seeking.

If you feel more at home looking at code in VS Code that in the Contract tab of Etherscan’s website, try using this DethCode viewer. By replacing modifying the Etherscan URL from etherscan.io to etherscan.deth.net, you can view the same contract code in a browser-based VS Code. If you look at code a lot, it’s a nice tool. Try out the interface at https://etherscan.deth.net/.

Contract Events and Analytics

While the “Contract” tab with the source code is where the most time is spent when analyzing security bugs, the “Events” and “Analytics” tabs can sometimes provide additional data. In the “Events” tab for the MakerDAO Changelog contract, we can see the logs for past emit calls. The UpdateVersion emit is in the setVersion() function, and because it takes a string as an input parameter type, we can use Etherscan to decode the value as text. Similarly, the UpdateAddress emit is found in the setAddress() function and take a bytes32 string and address as input parameters, so we can decode these values accordingly.

Contract Events

The MakerDAO Changelog Contract does not hold any value, so the “Analytics” tab does not show anything exciting. If we return to the WETH contract, the “Analytics” tab shows us a lot of data about the token over time.

Contract Analytics

A more interesting Analytics view is found in the Ronin Bridge contract, which suffered a large hack in early 2022. The screenshot below zooms in on the ETH transfers to and from this contract during the month of the hack, March 2022. Want to guess what day the hack occurred on?

Ronin Contract Analytics

Transaction Lookup

Like searching for addresses and contracts, searching for a transaction is also possible. Let’s continue with the Ronin hack, where transaction 0xc28fad5e8d5e0ce6a2eaf67b6687be5d58113e16be590824d6cfa1a94467d0b7 transferred the hacked Ether. Scroll down in the “Overview” tab, select “Click to see More”, and then click the “Decode Input Data” button to view the input data for this transaction. This information provides insight into the input data that the Ronin hacker used to withdraw the ETH from the bridge contract.

Ronin Transaction Data

The “Logs” and “State” tabs can give more insight into what functions were called and what state variables were changed.

Ronin Transaction Logs

However, Etherscan isn’t necessarily the best tool to tell us everything about what is happening in this transaction. Most security power users prefer the ethtx.info site for this purpose. It provides a more granular view of the function call sequence in this transaction, a view that Etherscan doesn’t currently have. Viewing the same transaction on ethtx, we can see the five different validator addresses that were compromised in MainchainValidator.isValidator() => True calls. The requirement for a 5 out of 8 majority to perform this attack is visible in the MainchainValidator.checkThreshold() => True call. This information cannot (yet?) easily be found in Etherscan to the best of my knowledge.

Ronin Transaction ethtx

Block Lookup

You can investigate individual Ethereum blocks in Etherscan too. I have not used this feature much, but it should be useful for MEV researchers. The easiest way to view a block is to click on one of the most recent block numbers on the Etherscan homepage. From there, you can view basic data about the block (timestamp, block reward, uncles, etc.), but clicking on the transactions within the block is where most of the useful information is. From there, you can manually examine the transactions that interest you.

Etherscan Block Transactions

Etherscan API

Finally, one of the most useful features of Etherscan is its API. To get an API key, you need a free account, and you will be able to create an API key from the account dashboard. Some frameworks and command line tools require the Etherscan API key for full functionality. For example, the command line tool seth requires the Etherscan API key saved to the ETHERSCAN_API_KEY environment variable, while some projects or frameworks will require a variable such as ETHERSCAN_TOKEN to be set in the project’s .env file. You can use the API to query accounts, contracts, transactions, and all the same types of data that can be accessed on the website. Unless you are building a new tool, in which case you should read the Etherscan API documentation thoroughly, the most common way the API key is used is by configuring it in whichever tool needs the API key to work properly.

More Features

There are even more features of Etherscan, including charts and statistics of recent blockchain activity, top ERC20/721/1155 tokens, a list of recent block uncles, and more. I haven’t seen these used at all for security purposes, so consult the Etherscan documentation or just click around the website to learn more about these features.

The Engn33r's Toolbox

This series of blog posts provides a guided walk-through for using different smart contract security tools. These articles are for readers who wish to level-up their Ethereum and solidity security skills. Even if you’re familiar with these tools, these guides may reveal some hidden features.

toolbox

Motivation

If you’ve heard about blockchain and smart contract security, you’ve also heard about the massive crypto hacks. There is currently a severe shortage of security know-how in the smart contract ecosystem, and the demand for these skills is high. yAcademy strives to grow security talent in the blockchain ecosystem, and sharing knowledge of common building blocks is an important piece of that. While the latest major hacks are (usually) beyond the ability of automated security tools, it’s important that good how-to guides for current security tools exist to make it easier to onboard people new to smart contract security. And if we want smart contract security tooling to improve, which would be very helpful given the security skillset shortage, the best way to incentive tool improvement is to use and show appreciation for what already exists. Or even better, after you get familiar with these tools, you can start contributing to the tools, upgrading them, or building new improved tools. While it is true that manual code reviews are mostly what the top smart contract auditing firms get paid for, those same auditors run these tools to catch low-hanging fruit.

The Tools

The obvious question now is: what’s in the engn33r’s toolbox? These tools are what I think are the most useful and most common tools used by security people in the Ethereum ecosystem. If the tool listed below doesn’t have a hyperlink, the article is still being written - check again soon!

  • Slither
  • Echidna
  • Mythril
  • VS Code Extensions
  • Etherscan
  • Seth
  • Tenderly
  • Misc. web tools (ethtx.info, contract-library.com, etc.)

Advanced Web Security: Request Smuggling

The Advanced Web Security series looks beyond the OWASP Top 10 web application security risks. For the full list of articles in this series, visit this page.

Today’s web applications and systems are more complex than ever before. While servers should always send data to users over HTTPS, the usage of HTTPS does not guarantee that the data sent from the web server will be received only by the correct recipient. This may be surprising to hear, since in many minds HTTPS equals a green lock symbol which means everything is secure, right? Research from the past year shows otherwise! Request smuggling is a technique that allows a malicious user to receive server responses intended for other users of a web application. This type of attack makes it ever more important that web applications are designed to NOT send sensitive account data to the user (such as the user’s plaintext password, government ID information, payment details, etc.) and reinforces the importance of defense-in-depth. To reiterate, if a web application transmits sensitive account data back to the user, there is an opportunity for a high impact request smuggling attack - HTTPS will not provide protection against this risk.

What is request smuggling?

Every so often, a new attack surface is investigated in the world of web applications, and the result is that high impact vulnerabilities are discovered to impact a large number of websites. In my opinion, this was previously the case with SSRF (server-side request forgery) a few years ago, and I would argue that request smuggling did this in 2019. While James Kettle, the researcher who popularized the vulnerability, humbly decided to remove his research from eligibility in the main PortSwigger Top 10 web hacking techniques of 2019 competition, request smuggling (specifically the HTTP Desync attack variant) easily won the “community favourite” category, indicating that the security community thinks of this attack vector as an important research development. The way that request smuggling works requires thinking about a web application as more than a single web server. Instead, the results of a web application are the sum of all the servers in between your browser and the backend server, and this includes the frontend server (i.e. reverse proxy). Request smuggling targets the fact that the front-end and back-end of the application both handle HTTP requests from users, but it is possible that they handle the requests in slightly different ways due to implementation differences in different code bases. Specifically, request smuggling exploits a difference in how HTTP requests end, which varies based on the “Content-Length” header and the “Transfer-Encoding” header. Due to this issue, we can encounter a scenario like the one shown in the diagram below (borrowed from the request smuggling section of the excellent PortSwigger Web Academy). At a high level, a possible result of this vulnerability is that the backend server may return the response for a web application victim user to a malicious user, potentially exposing details about the victim user to the malicious user.

Request smuggling high level visual

How does request smuggling really work under the hood?

I can’t claim to understand this attack better than the PortSwigger team that did this research in the first place, and therefore will defer to PortSwigger’s explanation for the deeper details of this attack. At the same time, I can try to provide a briefer explanation that may approach the topic from a different angle.

A web application’s frontend frequently takes incoming requests from users and forwards them to the backend over a single TLS connection. The frontend and backend usually are running different software, and certain edge cases of the HTTP/1.1 protocol are implemented differently in different software packages. A malicious user can exploit these different implementations by crafting a HTTP request that is understood one way by the frontend server and a second way by the backend server. Usually this is done by creating a HTTP request with a request body that contains a header that looks like the start of another HTTP request. The server might take this header information and append it to the following HTTP request (one that the malicious user did not send), and effectively inserting a request into another user’s HTTP session (and therefore unaffected by HTTPS security, because this occurs on the server side after HTTP requests are decrypted). While HTTP request smuggling is usually performed using a POST request, since the body of the POST request is used in the exploit process, this detailed request smuggling writeup from the Knownsec 404 team shows a GET request can also perform request smuggling. While it might be possible to consider request smuggling on its own a form of reverse proxy bypass, the impact changes a lot when the server is using a web cache, has internal APIs, or provides other targets.

How can a penetration tester exploit request smuggling?

There are several options to exploit request smuggling, but if you’re not in a rush, I am currently in favor of exploiting this issue manually, at least at first. This provides a deeper understanding of how the server counts the Content-Length value, and how to build a HTTP request in the exact manner needed to exploit the server in the manner you intend. One tip based on a mistake I overcame: when modifying the Content Length, a newline counts as 2 characters (/r/n). However, once you grasp how this vulnerability works, you no doubt will want to speed up the process. Burp Suite has a request smuggler extension, or you can use the turbo intruder Burp extension for more heavy duty testing. Outside of Burp Suite, this tool named “smuggler” is my preferred choice, and I don’t think there are currently many competing off-the-shelf tools. It may be possible to use curl or ncat based on this follow-up comment from James Kettle in a bug bounty report to the DoD, but this doesn’t appear to be a common method (this post was the only instance I found of using ncat for request smuggling).

Smuggler tool at work testing for request smuggling

How to defend against request smuggling?

The easiest approach for small-scale web applications is to remove any CDN, reverse proxy, or load balancers! However, such a simplifying measure is impractical for larger web applications, who rely on these added complexities for increased performance, so the following ideas are commonly considered other mitigation strategies:

  • Use HTTP/2 for connections to the backend: the root of the request smuggling vulnerability is ambiguity in HTTP/1.1 RFCs. The newer HTTP/2 protocol resolved the problematic ambiguities.
  • Reconfigure frontend/backend communication to avoid connection reuse: Request smuggling relies on confusion about the division of multiple HTTP requests in a single connection. If these HTTP requests are each in a separate connection, there would be no confusion about where each HTTP request starts and ends.
  • Use frontend and backend software that rely on the same HTTP request parsing: Request smuggling occurs because different software implementations in the frontend and backend handle ambiguous HTTP/1.1 edge cases differently. If the frontend and backend handle each HTTP request in the same manner, either because both the frontend and backend use the same software or close examination reveals consistent handling of the relevant headers in both pieces of software, then the frontend and backend will not handle HTTP requests in different ways.

References

James Kettle’s excellent research on the topic: https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn
Portswigger Web Academy Request Smuggling labs: https://portswigger.net/web-security/request-smuggling
defparam’s Smuggler standalone Python tool: https://github.com/defparam/smuggler
Portswigger’s request smuggling Burp extension: https://github.com/PortSwigger/http-request-smuggler