Navigating the Wild West: Preparing for the Challenges of Smart Contract Audits
23 sept 2024
25 Minutes
Security audits are essential, even for the most skilled developers. This is because audits bring fresh perspectives to the code that may not be obvious to those who originally wrote it. Developers often develop a kind of tunnel vision, making assumptions about their product or system that can result in overlooked vulnerabilities. Auditors, coming from an external viewpoint, can challenge these assumptions and identify weak spots that the developers might have missed.
One of the main reasons security audits are crucial is that developers are often focused on delivering a functional product to a client. It is their responsibility to ensure that the product is not only functional but also secure. In domains like information security, this means safeguarding sensitive data, and in the context of blockchain or cryptocurrency, it means ensuring user funds are protected.
A key benefit of audits is that they involve multiple sets of eyes examining the project. Often, when an audit is conducted by a team, each auditor will identify unique vulnerabilities. This highlights that different types of bugs require different ways of thinking, and no one person will catch everything. Different auditors approach the problem from different angles, leading to a more thorough review of the code.
Additionally, open bug bounty programs, such as those on CodeArena, provide further evidence of the value of diverse perspectives. Even top contributors, with extensive experience, miss bugs that less experienced participants may find. This shows that factors like creativity, approach, and diverse experience levels play a role in identifying issues.
Finally, it's crucial for any software development company, regardless of its budget or the experience level of its developers, to engage in security audits. Even well-funded companies with highly experienced developers have shipped products containing vulnerabilities. Therefore, security audits are a necessary step to ensure the safety and integrity of the product, safeguarding both the company's reputation and the end-users' security.
The Hacker's Methodology
A hacker's primary goal is to exploit a system, whether it’s for personal gain, financial profit, or to prove a point. In the context of cryptocurrencies, the motivation is predominantly financial, with attackers aiming to steal user funds or disrupt the platform. Understanding the hacker's methodology is crucial for defending against these threats.
1. Reconnaissance and Information Gathering
The first step a hacker takes is gathering as much information as possible about the system, particularly in the case of smart contracts, which are often open source. Open-source code provides attackers with a blueprint of the system, allowing them to study its mechanics and identify potential weaknesses. Since smart contracts frequently handle funds and transfers of value, they become high-value targets for attackers.
2. Focusing on Inputs
One of the first areas of focus for an attacker is the inputs, as they are often the only direct way to interact with the system. While it may seem obvious, inputs are where vulnerabilities are most likely to be found. Attackers will meticulously analyze these inputs, trying to determine if improper validation or unchecked assumptions can be exploited. This often involves attempting edge cases, unexpected inputs, or invalid data to see if the system behaves in unintended ways.
3. Challenging Assumptions
Hackers employ a mindset of challenging the assumptions made by developers. They ask heuristic questions such as:
What is this system supposed to do?
What assumptions did the developers make about user behavior or system state?
Can I break these assumptions, and if so, what would happen?
Are there any critical design flaws that were overlooked?
The attacker then tests these assumptions by interacting with the smart contract, trying to break the system and exploit any oversights in its design or logic.
4. Understanding the Protocol
A deep understanding of the protocol is essential for an attacker. Hackers typically study how the system operates under normal conditions so they can identify deviations or weaknesses. They also focus on how assets, such as cryptocurrency, move within the system, seeking out opportunities to misappropriate funds by exploiting gaps in the logic or flow of value.
5. Mapping Known Vulnerabilities
Most attacks are not entirely novel. Instead, attackers are often well-versed in previous exploits and vulnerabilities. They map known weaknesses from similar protocols or systems and apply these strategies to their target. This can include replay attacks, re-entrancy attacks, integer overflow/underflow, or manipulation of external calls within the contract. For instance, the infamous DAO hack of 2016 exploited a re-entrancy vulnerability, a type of attack that is now well-documented but can still pose a risk to systems that don’t account for it.
6. Following the Money
A common principle attackers follow is "follow the money." In the context of smart contracts, this means analyzing how inputs interact with value transfers and looking for areas where they can manipulate the flow of funds. Whether it’s through triggering unexpected re-entrancy loops, exploiting gas limitations, or manipulating token balances, the end goal is often to divert or steal funds.
7. Leveraging Attack Frameworks and Tools
Hackers often use well-established frameworks and automated tools to analyze smart contracts and identify potential vulnerabilities. Tools like Mythril, Slither, and Manticore allow attackers to conduct static and dynamic analysis on smart contracts, enabling them to uncover subtle bugs that may be missed during manual reviews.
Links to some static analysis tools:
https://github.com/crytic/slither
https://github.com/Consensys/mythril
8. Post-Exploitation
After identifying and exploiting a vulnerability, the attacker will often focus on covering their tracks and maintaining access. In decentralized systems, once funds are stolen, they are often quickly moved across wallets, mixed through privacy-enhancing protocols like Tornado Cash, and converted into other assets to obfuscate the transaction trail.
The Importance of Being Ready for an Audit
Once you've recognized the need for a security audit, especially for open-source smart contracts handling user funds, the next step is to ensure that you are fully prepared before the audit begins. Preparing thoroughly before the audit not only improves the quality of the review but also saves time, resources, and prevents avoidable issues later on. Here are some key reasons and steps to take before starting an audit:
1. Ensure Code Completeness and Feature Finalization
The most important aspect of audit readiness is submitting a completed codebase. This means all planned features should be implemented, tested, and finalized before you hand the code over to auditors. Auditors are tasked with reviewing the security of your code as it stands, not the code that you intend to add later. If you introduce new features after the audit starts, you risk invalidating parts of the audit because even small changes can alter the overall security landscape of the contract.
For example, adding just a couple of new functions can change how the smart contracts interact with each other, introducing unforeseen vulnerabilities that weren't present during the initial audit. This may force the auditors to restart the audit process or miss critical issues, leaving your code less secure. This situation often arises due to rushed deadlines or lack of preparation, and it compromises the quality of the audit.
2. Avoid Post-Audit Additions
A major mistake teams make is adding functionality after the audit is completed, assuming that the entire contract is still secure. However, any change to the codebase after the audit, even minor tweaks, could introduce vulnerabilities that weren't accounted for during the audit. As soon as new functionality is added, the contract is essentially unaudited again and becomes susceptible to exploitation.
A real-world example of this is the case of Level Finance. After their smart contracts were audited, they introduced a new feature—an upgrade to the LevelReferralControllerV2 contract. Unfortunately, this upgrade contained a vulnerability that allowed an attacker to repeatedly claim referral rewards in the same epoch. Despite the contract being audited by reputable firms like Quantstamp and Obelisk, the vulnerability arose because the updated code was not part of the original audit. This oversight led to the loss of over $1.1 million in user funds when the vulnerability was exploited.
3. Prepare Documentation and Tests
Before starting the audit, ensure that all relevant documentation is complete and up to date. This includes:
Technical specifications: Provide detailed documentation that outlines the intended behavior of your smart contracts. This helps auditors understand the purpose and flow of the code.
Assumptions and invariants: Document any assumptions or invariants you made while developing the code. Auditors need to challenge these assumptions to ensure they hold up in practice.
Test coverage: Ensure that your test suite is comprehensive and includes both unit and integration tests. Having thorough test coverage gives auditors confidence that the core functionality of your system is stable, which allows them to focus more on finding subtle security issues.
4. Clear Communication with Auditors
Establish clear communication with the auditors from the beginning. Make sure they understand the scope of the audit, what functionality needs the most attention, and any particular concerns your team has about the code. Being transparent and communicative throughout the process helps ensure a more effective and thorough audit.
Additionally, if there are specific deadlines for the project, communicate this with the auditing team early so they can allocate the appropriate time to thoroughly review the code. Rushed audits increase the likelihood of missed vulnerabilities.
5. Understanding the Audit Process
Be familiar with the standard audit process, which generally includes:
Initial review and scoping: Auditors will scope the audit based on your codebase, identifying areas that are most critical or high-risk.
Manual code review: A manual review where auditors look for common vulnerabilities such as reentrancy attacks, unchecked external calls, or integer overflows.
Automated analysis: Auditors may also use automated tools like Mythril or Slither to detect potential vulnerabilities that could be missed during manual review.
Final report: After the review, the auditors will provide a report detailing any vulnerabilities and suggestions for improving the security of your contract. Addressing these findings promptly is crucial for the safety of your project.
6. Budgeting Adequately for Security
Preparing for an audit also means budgeting appropriately for security measures, including post-audit revisions. Security audits are not one-time processes. Even after the first audit is completed, ongoing audits may be required as the code evolves or as new features are introduced. Allocating funds and resources for both the initial audit and any necessary follow-up audits will help prevent vulnerabilities from slipping through the cracks.
7. Post-Audit Responsibility
After receiving the audit report and implementing the necessary changes, your responsibility doesn’t end there. Continuous security monitoring and re-auditing are essential, especially as your project evolves. Auditors can only review the current state of your code at the time of the audit. If new features are added or the code is modified, you must engage auditors again to ensure those changes are secure.
In summary, the success of a security audit largely depends on how prepared your team is before the audit begins. By finalizing your code, avoiding post-audit changes, providing comprehensive documentation, and maintaining clear communication with auditors, you increase the likelihood of a thorough and effective review. Security is a continuous process, and being audit-ready from the start helps ensure your project's long-term safety and integrity.
The Foundation of a Successful Audit
As mentioned in the hacker’s methodology, one of the key steps an attacker takes is understanding the project by comparing what is intended with what is actually implemented. Two pieces of code can appear functionally secure on the surface, yet one may contain a critical vulnerability simply because it deviates from the original intent. The vulnerability could stem not from the code itself, but from the misalignment between its implementation and the original design.
This is why comprehensive and accurate documentation is essential when submitting your code for an audit. The documentation serves to establish the context and intentions behind the code, providing auditors with the necessary understanding to evaluate the security of the implementation. Without this context, auditors may spend a significant amount of time trying to deduce what the code is supposed to accomplish, which introduces several risks:
Wasted Time and Resources: If auditors are forced to spend time figuring out the purpose and functionality of the code due to lack of documentation, the actual time spent on identifying vulnerabilities is reduced. This not only delays the audit process but may also affect its depth and thoroughness.
Inaccurate Assumptions: Without clear documentation, auditors may make assumptions about the intended functionality of the code. These assumptions can lead to overlooked vulnerabilities, as the auditor might believe the code is functioning as intended, when in fact it deviates from the original design. Even if the code seems secure, it may still introduce unintended behavior that can be exploited.
Frequent Communication Interruptions: Poor or missing documentation often forces auditors to continually contact the development team for clarification on code functionality, slowing down the audit process. This back-and-forth communication reduces the efficiency of the audit and detracts from the focus on security testing. A project that is well-documented minimizes the need for these interruptions, allowing auditors to focus fully on their task.
Challenging Assumptions Like Hackers: Ethical hackers, or auditors, are tasked with challenging the same assumptions that malicious attackers will target. For them to properly challenge these assumptions, they need to know what the developer’s original intentions were. Detailed documentation gives auditors insight into what assumptions were made during development, enabling them to identify areas where those assumptions might break down. This allows the auditors to identify potential vulnerabilities before a malicious actor has the chance to exploit them.
Unlike attackers, auditors are working against the clock. They have a limited timeframe to thoroughly review your code. Good documentation ensures they don’t waste any of that valuable time on deciphering the code’s purpose but instead focus on ensuring its security. The more comprehensive the documentation, the more effective the audit.
What Should the Documentation Include?
When preparing for a smart contract audit, it's essential to provide clear and comprehensive documentation tailored specifically to the context of smart contract development. This not only ensures that the auditors have a full understanding of the system’s functionality but also helps them efficiently identify vulnerabilities and weaknesses in the code. Here’s what should be included in your documentation, with a focus on smart contract auditing:
1. High-Level Overview
Purpose of the Smart Contract: Provide a high-level summary of the smart contract's purpose and its role within the larger project or protocol. Explain the contract's core functions, its interaction with other contracts, and any key mechanisms it implements (such as token transfers, staking, or governance).
UML Diagrams: Use Unified Modeling Language (UML) diagrams to visually represent the structure and flow of your smart contracts. This helps auditors understand the relationships between different components, the interactions between contracts, and the overall architecture of the system. Common diagrams include:
Class Diagrams: Show the different classes (contracts) and their attributes, methods, and relationships.
Sequence Diagrams: Demonstrate how various functions interact with each other in a sequence, especially in complex multi-contract systems.
PPT Slides for Overview: Summarize key points from the documentation in PowerPoint slides or similar presentation formats. This is useful for giving auditors a quick understanding of the system's architecture and the most important aspects of the smart contract.
2. Detailed Technical Specifications
Functionality Specifications: Provide a breakdown of each smart contract, including its purpose, key functions, and interaction points. For each function, include:
Expected inputs and outputs
Pre-conditions and post-conditions (what must be true before and after execution)
Any events emitted by the function
Description of how it interacts with other contracts or external systems (if applicable)
Edge Cases: Describe any potential edge cases or complex scenarios that your smart contract is expected to handle. This helps auditors focus on these areas to ensure they are thoroughly reviewed.
3. Inline Commenting
Code Annotations: Ensure your smart contracts include comprehensive inline comments. These comments should describe what each function does, how it works, and the developer’s assumptions behind the implementation. Inline comments help auditors quickly understand specific lines of code without having to refer back to external documents constantly.
Explanation of Complex Logic: If there are any areas of the code that involve complex or non-obvious logic, provide additional commentary to make it easier for auditors to follow. Complex algorithms, unusual tokenomics, or custom access control mechanisms should be thoroughly explained within the code.
4. Assumptions and Constraints (Specific to Smart Contracts)
Developer Assumptions: Clearly state any assumptions that were made during the development process. For example, assumptions about gas limits, the expected behavior of external oracles, or restrictions on user input. These assumptions will guide the auditors in testing how the system behaves when those assumptions are challenged or broken.
Security Constraints: List any specific security measures you have implemented or expect auditors to review. This might include reentrancy protections, safe arithmetic operations, or multi-sig control mechanisms. Specifying these helps auditors verify if the protections are sufficient or if there are any potential gaps.
5. Security Considerations (for Smart Contracts)
Known Vulnerabilities: If there are areas in the contract where known vulnerabilities have been considered and mitigated (e.g., handling reentrancy, preventing overflow/underflow, ensuring gas efficiency), document how these issues were addressed.
External Dependencies: Highlight any external dependencies or third-party services your smart contracts rely on. For example, if your contract interacts with an external price oracle or a specific blockchain service, make sure this is documented. Auditors need to know about any interactions with external systems that could introduce risks.
6. Test Coverage
Unit and Integration Tests: Provide detailed information about the test suite you have implemented for the smart contract. This should include:
Unit tests that cover individual functions.
Integration tests that simulate interactions between multiple contracts.
Edge cases and stress tests (e.g., testing large input values or gas limits).
Test Results: Include the results of these tests. If any tests failed, explain why and what steps are being taken to address those issues.
Continuous Integration: Mention if you are using any CI/CD tools to automatically run tests during development. This shows auditors that the project maintains a high standard of quality control.
7. Versioning and Change Log
Contract Versions: Clearly indicate which version of the smart contract is being submitted for audit. If there are multiple versions or iterations, ensure that auditors are reviewing the correct one. Provide a link to a versioned repository (e.g., GitHub) where the auditors can verify the code’s history and compare it to previous versions.
Change Log: Maintain a detailed change log that documents any modifications made to the codebase, particularly after the audit begins. This ensures transparency and allows auditors to keep track of any new changes that could impact the security of the contract.
8. Flow Diagrams and Interaction Models
Transaction Flows: Use flow diagrams to illustrate how transactions flow through the smart contract system. For instance, show how funds are transferred, how state changes are managed, and how users or external contracts interact with your system.
Interaction Models: Provide models that show how various actors (e.g., users, admin, external contracts) interact with the smart contract. This helps auditors assess whether proper access control mechanisms are in place and whether critical functions can be exploited.
Proper documentation is more than just a courtesy to the audit team—it is an essential part of the security process. Documentation helps bridge the gap between your development team’s understanding of the project and the auditors' fresh perspective. It reduces inefficiencies, minimizes the risk of missing critical vulnerabilities, and ensures a more thorough and effective audit.
Inadequate documentation can lead to delays, assumptions that obscure vulnerabilities, and ultimately, less secure code. By prioritizing clear and comprehensive documentation, you set the stage for a successful audit that delivers meaningful security improvements.
The Importance of Testing in Auditing
Even with the most experienced and capable human auditors, testing remains a crucial element of the auditing process. Human auditors, no matter how skilled, are subject to limitations such as fatigue, lapses in concentration, tunnel vision, and limited stamina. These natural human limitations can result in overlooked vulnerabilities or errors, making it essential to complement manual auditing with rigorous, automated testing.
Why Testing Complements Auditors
Auditors can thoroughly review code, analyze system architecture, and identify vulnerabilities, but they are still susceptible to missing subtle bugs due to the sheer volume of code or complexity of the system. Moreover, as audits are often performed under time constraints, even the most meticulous auditor may miss issues as their focus narrows or shifts.
This is where automated testing comes in. Automated tests are highly efficient and unbiased—they run consistently without the limitations of human auditors, helping to ensure that important invariants in the codebase are upheld. Invariably, computers excel at tasks that require constant, repeatable processes, such as verifying the correctness of functions and ensuring that critical conditions are maintained under all possible inputs.
To better illustrate this, consider an analogy: a human auditor is like a detective combing through a crime scene, looking for clues with great attention to detail. They may eventually become tired or miss subtle details after hours of work. However, automated tests are like surveillance cameras—they record and monitor every detail continuously and never tire. They don’t replace the detective’s expertise but act as a reliable supplement, covering more ground than a human could alone.
Automated Testing: Why It’s Essential
Eliminating Human Error: Automated testing helps mitigate human error by continuously checking for specific conditions, ensuring that each function or line of code behaves as expected across all potential inputs and scenarios.
Time Efficiency: Machines can run thousands of tests in the time it takes for a human auditor to manually review just a few key sections of the code. This drastically speeds up the audit process and ensures that more potential vulnerabilities are tested in less time.
Consistency and Reliability: Automated tests run the same checks every time, ensuring that tests are repeatable and accurate. This means auditors can rely on the consistency of test results without worrying about human factors like fatigue or distractions.
Now, let’s dive into different types of testing that can be applied during an audit and why they are crucial.
Unit Testing
The first type of testing that plays a significant role in smart contract auditing is unit testing. Unit testing focuses on the smallest, individual parts of a system—usually functions or methods—and verifies that each one works correctly in isolation. Here’s why it’s essential:
1. Positive and Negative Testing
In unit testing, there are two main types of tests: positive testing and negative testing.
Positive Testing: This type of test checks whether a function behaves as expected under normal, valid conditions. For example, if a smart contract function is supposed to transfer tokens, positive tests will verify that the function transfers the correct amount to the intended address when valid input is provided. This ensures that the code works as intended in common use cases.
Negative Testing: Negative testing, on the other hand, tests how the system behaves when given invalid or unexpected input. It is designed to break the system, pushing it beyond its normal bounds to uncover potential vulnerabilities. For instance, in a token transfer function, negative tests may attempt to transfer more tokens than the sender owns or to an invalid address. These tests check whether the smart contract handles edge cases gracefully and securely without causing unexpected failures or exploits.
Both positive and negative testing are crucial for catching a wide range of bugs. Positive tests ensure the system works as intended under typical conditions, while negative tests identify how well it defends against abnormal or malicious input. Without negative testing, it’s easy to overlook edge cases or scenarios that hackers might exploit to break the system.
2. Integration Testing
Integration testing is crucial in order to test your code in an environment as close to a production environment as possible. Whilst unit testing will address various issues in smaller units of code, this variant of testing involves putting the protocol together and deploying the contract on a forked mainnet environment which will assess how the contracts will react to each other as well as untrusted external contracts. This will allow the developers to make proper assertions about external contracts and the data returned from other contracts to ensure the code is functioning as expected. Node urls for forking can be found on various platforms such as Alchemy and Infura.
References:
3. Fuzz Testing (A variant of stress testing code)
While you should always be security conscious while writing the code for your protocol in a passive manner, fuzz testing is the first active step to taking security measures when fortifying your code base. Fuzz testing involves inputting a large sample size of random data into your functions to ensure that they are sufficiently handling edge case scenarios which may cause the code to behave in an unexpected way as these edge cases could be exploited by potential attackers.
This technique is most effective when rigorously testing your mathematical libraries to ensure that under a large amount of data, your code still behaves as expected and is handling input accordingly whether it be invalid or valid input data.
References:
4. Beta Testing
Beta testing is quite an uncommon way to test your codebase but is highly recommended as it’s an effective way to test it’s resiliency under a significant amount of user traffic and was used by notable protocols such as Eigen Layer, ZKSync and Starknet. This involves the use of public test nets where you can deploy your contracts and configure them as though they are in a production environment.
Various test nets exist which may include the following:
Ethereum
Goerli
Arbitrum
Arbitrum Sepolia
Optimism
Optimism Sepolia
Starknet
Starknet Goerli
Solana
Solana Devnet
Testnet native currency can be found through the various faucets for their corresponding testnet. You can encourage your users to use your protocol as though it was deployed in production for the purpose of bug fixing.
Pro Tip: If your protocol is deploying a DAO token, consider offering a small portion of the total supply to your loyal users who use your dapp in beta testing to incentivize the most amount of users. This may also include individual rewards for exploitable bug reports and their suggested fix. The state of the testnet blockchain at a certain point in time can undergo a snapshot to capture addresses who have interacted with your deployed contracts.
References:
Pre Audit Checks
1. Static Analysers
Static analysers and static code analysis play an important role in securing your code at a basic level. They are responsible for examining your codebase without actually executing the contract. This is used as one of the first techniques by security professionals in order to capture low hanging fruit which may pose a risk to your protocol. These tools will also address the various programming standards which may be violated for the language you are using. The go-to piece of software which can be used for Solidity is the Slither Static Analyser built using Python3 which will assess the inheritance graph and the flow of code execution in the smart contract. Alternatively, if you intend to deploy to Solana, whilst tools for this environment are not as sophisticated as Solidity (but is very quickly improving), semgrep could be a good way to perform static analysis on your codebase. This technique can be very helpful in getting the most bang for your buck during the security review as the personnel assigned to your engagement can draw most of their attention to High and Medium level findings as opposed to using more time than needed on low hanging fruit.
References and Suggested Tooling:
https://research.kudelskisecurity.com/2021/04/14/advancing-rust-support-in-semgrep/
L3X - An AI-Driven Static Analyzer for Solidity and Rust by VulnPlanet
2. Continuous Integration
Continuous integration is the process in software development which handles the automation of integrating new code changes and should be one the the first things you set up when you start a new project. When a developer writes new code and pushes such code to the repository, the (unit, integration, fuzz) tests and static analysers are automatically run by the CI/CD pipeline. If any problems are detected, the developer will be required to make the relevant changes and push the code again to the repository as the rules (typically in a .yaml file) in place will not allow the code to be merged. The workflow is as followed:
This makes it easy to have maximum visibility by all developers over the test results within the codebase which will allow swift action to remediate any issues.
References:
The Importance of the Audit Report and Working with Your Security Team
One of the primary values of an audit report is that it provides a comprehensive overview of security vulnerabilities present in the system. This allows the development team to clearly understand each vulnerability, its impact, and how to remediate it effectively. Without this thorough understanding, it would be difficult to prioritize and address security issues, leading to potential exploitation. In order to achieve a thorough security audit, the developers should have finished implementing all features and changes. In addition to this, a security audit should be booked well in advance to ensure that there is adequate time for the security team to discover the most critical vulnerabilities as these can take time to find.
Another critical aspect of the audit report is the classification of vulnerabilities by severity, which helps the development team focus on the most critical issues first. Typically, vulnerabilities are classified into categories such as critical, high, medium, and low severity based on both the potential impact and the likelihood of the vulnerability being exploited. By organizing vulnerabilities in this manner, the team can allocate resources efficiently, ensuring that the most dangerous risks are mitigated first.
A commonly used severity classification system in the industry is the Immunefi Severity System, which categorizes vulnerabilities by their impact and the probability of occurrence. This structured system helps developers and auditors stay aligned on which vulnerabilities pose the greatest risk.
Levels of Effort in Exploitation
When assessing vulnerabilities, it’s important to consider the difficulty of exploitation—how easy or challenging it would be for an attacker to leverage a specific vulnerability. The difficulty level typically correlates with the resources, skills, and time required to execute the attack successfully. Here’s a breakdown of how effort levels are often categorized:
Low Effort to Exploit, High Likelihood of Exploitation: These vulnerabilities are relatively easy for attackers to exploit, typically requiring minimal resources or technical skills. They can often be triggered consistently and may not involve complex attack paths. For instance, attackers may only need to pay minimal gas fees or protocol fees to exploit a flaw, or they could bypass weak access controls with no authentication required.
Medium Effort to Exploit, Medium Likelihood of Exploitation: Medium-level vulnerabilities require more resources, time, or collaboration to exploit. Attackers may need a certain level of capital to perform front-running attacks or collaborate with miners to manipulate block ordering. The exploitation process could involve a combination of common techniques, such as manipulating external oracles or using more sophisticated methods to trigger the vulnerability.
High Effort to Exploit, Low Likelihood of Exploitation: High-effort exploits involve significant resources, strict conditions, or substantial time to carry out. For example, an attacker may need to gain access to privileged roles, invest large amounts of capital, or exploit unpredictable factors like hash manipulation. These types of vulnerabilities are harder to trigger but may still pose serious risks if left unaddressed.
By understanding the complexity and cost of exploitation, the security team can assess whether vulnerabilities are likely to be targeted by attackers. Higher-effort exploits are less likely to be pursued but could still result in substantial damage if they are successfully executed. On the other hand, low-effort exploits are more likely to be targeted, as they require minimal effort and can yield significant rewards. Thus, the audit report should clearly explain not only the severity of each vulnerability but also how difficult it is to exploit, helping the development team prioritize fixes effectively.
Ultimately, the audit report acts as both a diagnostic tool and a roadmap for improving security, enabling the development team to systematically address vulnerabilities and strengthen the system against future attacks. Working closely with the security team ensures that every issue is well-understood, mitigated, and retested, creating a robust defense mechanism for the project.
Another crucial aspect of the audit report, beyond identifying vulnerabilities, is the recommended mitigation steps. This section is as important as discovering the vulnerabilities because it provides a solution to the problem that has been reviewed and validated by the external auditors.
If an audit report discloses vulnerabilities without providing clear mitigation steps, the development team might attempt to fix the issues independently. However, without further review, there’s no guarantee that the new fix is free from additional bugs or security flaws. The mitigations suggested by the external auditors are designed with security in mind, and they have been analyzed to ensure they address the root of the problem without introducing new risks. By following these recommendations, the development team can confidently implement fixes knowing they have been reviewed by security experts.
That being said, the recommended solutions are just that—recommendations. If the proposed fix doesn't align with certain functionalities or goals of the development team, adjustments can be made. However, it’s crucial that any modifications to the suggested fix are reviewed once again, either by the external auditors or through an additional audit, to ensure the revised solution resolves the vulnerability without introducing new issues. This highlights the importance of ongoing collaboration between the development team and the external audit firm to ensure both functionality and security are maintained.
Addressing Post-Audit Changes
Once an audit is completed, the development team typically applies the recommended fixes to their codebase, often by pushing new versions to platforms like GitHub. However, it is crucial that these post-audit changes are reviewed again by the auditing team. This step ensures that the identified vulnerabilities have not only been addressed but also that the fixes were implemented correctly and without introducing new issues.
In many cases, developers may inadvertently introduce new vulnerabilities while making changes, especially if they opt for a custom solution instead of following the auditor’s recommendations precisely. Alternatively, the recommended fix may have been applied incorrectly, leaving the system exposed. A post-implementation review by the audit team provides a critical layer of verification, ensuring that the system is secure after all changes—whether additions or subtractions—are made.
This process helps maintain the integrity of the audit and reduces the likelihood of introducing further risks, ultimately ensuring the code is secure and ready for production. Regular re-evaluation after fixes is a best practice that prevents regression or the emergence of new security threats.
Once the audit is completed, it is considered best practice to make the audit report publicly available. Doing so increases transparency, allowing stakeholders and users to gain confidence in the security and overall health of the project. By sharing the audit findings, the project demonstrates a strong commitment to security and accountability, which can foster greater trust within the community.
Additionally, maintaining a close relationship with the audit firm is highly beneficial post-audit. As the project evolves and new changes are implemented, it’s important to ensure that these changes are thoroughly reviewed and rigorously tested. This relationship makes it easier to schedule follow-up audits when needed, ensuring that the code remains secure as the project grows.
Finally, implementing a bug bounty program is another excellent way to enhance security. By having previous audit reports publicly accessible, security researchers and white-hat hackers can better understand the system and identify potential vulnerabilities, contributing to ongoing security improvements. This proactive approach encourages collaboration with the wider security community and strengthens the project's resilience against threats.
Closing Thoughts and Contingency
Review this document carefully, and keep it handy while you’re going through your development life cycle. This document can also be used to educate your development team about the processes in addition to integrating security into your Software Development Life Cycle. Even assuming all bases are covered, this process is not a silver bullet and shouldn’t be treated as such. Some of the most elite development teams have also suffered breaches in their contracts due to errors in their code. One should always assume that they are either being targeted or about to be attacked in a security breach, and as a result, your team should always have a contingency plan in place (which should hopefully never need to be used).
Consider the case study of Conic Finance suffered a loss of approximately $4.2M in funds due to a read only reentrancy vulnerability followed by sandwich attacks due to imbalanced Omni pools after the reentrancy. Many in the community thought that they would never recover and come back from such a devastating attack. However, at the end of 2023 they vowed to make users whole again by creating a debt pool where the LP Token (cncDT) would have a total supply of 4,337,233 - the total amount lost in the breach. Users who were affected by the attack would be able to claim debt tokens which can freely be traded on the open market, as platform fees would be redirected to the debt pool to repay the users their losses which can be redeemed for stablecoin. CNC token surged following the announcement of this plan as they were trusted by the users, which is why it is important to be transparent and open with the community.
References:
https://www.immunebytes.com/blog/conic-finance-detailed-hack-analysis-july-21/
https://docs.conic.finance/conic-finance/liquidity-providers/conic-debt-pool
Ready to secure your project?
Zokyo is a leading web3 security firm dedicated to protecting blockchain organizations from cyber threats.
We specialize in identifying web3 cyber threats and ensuring the security of your code, advising clients across major ecosystems like EVM, Solana, Cosmos, Move, and more. Whether you're developing wallets, cross-chain infrastructure, or L1s/L2s, we provide tailored, comprehensive security solutions.
Trust Zokyo to safeguard your reputation and secure your code. Contact us now to schedule your audit.