Note: The following information was originally presented by Peiyu Wang, a security engineer from CertiK, at the Appsec Village at DEF CON 32 on August 10, 2024.
The integration of Web2 and blockchain components within decentralized applications (Dapps) introduces a new frontier of security challenges that demand close attention. As Dapps continue to evolve and experience growing adoption, they face a wide range of vulnerabilities and attack vectors unique to their hybrid architecture. Developers and security professionals must therefore contend with risks orchestrated by malicious actors, such as client-side attacks that steal users’ crypto assets, and server-side attacks that lead to system compromise and total financial loss.
This blog will explore these complexities through real-world examples, offering insights into the differences between traditional Web2 applications and Web3 Dapps, Dapp threat modeling, and unique attack vectors enabled by the integration of blockchain components.
The following diagram illustrates the simplified architecture of a standard Web2 application. In this model, the front-end application runs in the client’s browser, while the server hosts the webpage files and server-side APIs. The client and server communicate via a protocol, most commonly the HTTPS protocol.
Web3 Dapp architecture can be categorized into three types:
As the name suggests, this type of Dapp does not have server-side APIs. The server’s sole purpose is to serve the webpage files. The primary logic of this application resides within the smart contract, and the client-side application functions as a UI to display data and handle contract transactions based on user input.
An example of such a Dapp is the 2020 version of Uniswap. From an application security perspective, the attack surface for this type of application is relatively limited; if exploited, it is often due to vulnerabilities within the smart contract itself. The client-side app often uses frameworks such as React, which includes built-in XSS filters.
This type of Dapp includes a server-side API, but its backend does not communicate with the blockchain. Its front-end serves the same purpose as in the “API-less” Dapp.
An example of an “API-Enabled” Dapp is an NFT minting application, where the server provides an API for users to upload images and metadata, returning a URL for the image and metadata to the user. The user can then interact with the smart contract to mint an NFT using these URLs. In this case, the server functions as centralized storage for NFTs.
The final type is a “Full-Scale” Dapp, where the backend not only serves web content, but also communicates with the blockchain and interacts with the project’s smart contracts. In these Dapps, smart contracts often contain privileged functions that can only be executed by a role known as the “owner.” The private key associated with the owner role is stored on the backend server. If this server is compromised, the consequences are severe, as the attacker could obtain the private key and gain complete control over the smart contract and its associated crypto assets. Attackers can also exploit business logic errors in the API to cause financial damage to the protocol.
An example of this type of Dapp is a cross-chain bridge. Since smart contracts on different blockchains cannot communicate directly with each other, an off-chain component is required to facilitate the communication and operation. This off-chain component is often critical to the security and functionality of the bridge, making it a high-value target for attackers.
From a threat modeling standpoint, the most sought-after “assets” in Web3 are the actual crypto assets, such as Bitcoin or Ether. When attempting to acquire these assets, malicious actors seek seed phrases/private keys, signatures (signed “permit” messages), and API keys — all of which provide access to assets and custodians, and enable contract state manipulation. It is possible to acquire this information due to vulnerabilities on both the client and server sides, emphasizing the need for higher Web2 security within Web3 applications.
Below are some common Web2 vulnerabilities on both sides.
When examining client-side attack vectors, it is crucial to first understand the concept of a “site with already-connected wallets.” Users who visit Dapps for the first time are prompted to directly connect their wallets to the Dapps. This connection is often permanent, unless the user manually disconnects, and only a connected Dapp can send transactions to a wallet for its user to sign.
Unfortunately, malicious actors can exploit this feature through the following methods:
To reduce the impact of the above attack vectors, users should only connect to sites they trust. Oftentimes, users who trust sites will automatically sign transaction requests without much consideration. Consequently, if a client-side vulnerability exists in a popular Dapp with many connected users wallets, the severity of the vulnerability will dramatically increase.
Attackers also aim to steal crypto assets or cause negative financial impact to the project by exploiting vulnerabilities on the server side. The following server side attack vectors are common:
To summarize, when testing a Web3 Dapp, once a Web2 vulnerability is identified, consider how it could be exploited in a Web3 context. This approach often allows you to escalate the severity of the identified vulnerability.
The integration of Web3 components into various applications has introduced unique attack vectors. Based on past findings when testing Dapps, we have broken down these attack vectors into three primary categories:
The mishandling of blockchain transaction characteristics:
Initiating attacks against Web2 backend components via smart contracts:
The mishandling of crypto asset operations:
The following case studies demonstrate what might happen if the integration of Web3 components is incorrectly handled.
The first example is a token airdrop Dapp, which requires users to register their walled addresses for participation. First, a user would call the “/register” API, providing their on-chain address. The backend would then call the “register()” function in the smart contract, which can only be called by the smart contract admin address.
During the pentest, the auditor found that the API accepts any Ethereum address, as long as the address data is in the correct format. Every time the API is called, the backend sends a transaction to the smart contract.
Consequently, a malicious actor might repeatedly call the API with randomly-generated addresses to consume all the ETH tokens in the admin's wallet as gas for the transactions.
This vulnerability may exist in any Dapp that has APIs triggering the backend to send an on-chain transaction. In the vulnerable implementation, the HTTP server keeps the connection open while waiting for the transaction execution result before returning the HTTP response, as shown in the GPT generated example code below.
We discovered that, for blockchains with long block times (e.g., 12 seconds for Ethereum), the HTTP connection will remain open for a considerable amount of time. The API server can be easily subjected to a DoS attack with concurrent requests sent from a single machine.
In the following GameFi Dapp case, users can purchase in-game items with in-game currency. To obtain the in-game currency, users need to deposit crypto assets into a smart contract, after which the backend updates their in-game balance. To withdraw and convert the in-game currency to crypto assets, the process works as follows:
During the pentest of the application, the auditor discovered that the backend updates the user’s in-game balance, only after confirming that the transaction is successfully executed on the blockchain. However, the backend didn’t “lock” the user’s balance before sending the on-chain transaction, enabling users to purchase items during the transaction execution time and receive crypto assets, essentially obtaining in-game items for free.
A cross-chain bridge works as follows:
A token bridge backend needs to extract and validate a lot of information from the deposit transaction, such as the token amount, the token type, and the sender’s and receiver’s addresses. The auditor discovered that the backend failed to validate that the smart contract receiving the token deposit was the one operated by the project team.
To exploit the validation vulnerability, an attacker can deploy a fake bridge contract, perform a deposit into the self-deployed contract, submit the transaction hash to the API, and wait for the backend to misprocess it and release the token on the destination chain.
For more details on this finding, view the pentest report here.
Our final example involves a lightweight crypto trading application where users can deposit crypto assets and perform token swap activities. The backend of this application uses Fireblocks as its crypto custodian service, and its deposit flow is as follows:
When we deposited 0.1 MATIC into the deposit address, the balance of our account increased by “1.1.” To investigate this odd result, we viewed the on-chain address data in a blockchain explorer and confirmed that the token indeed arrived at the address with 0.1 MATIC. Upon further verification, we found a record of a scam NFT (ERC1155) airdropped into the address.
The results showed that the backend recognized the receipt of one scam NFT as a 1 MATIC token deposit. When reviewing the source code, we then discover the root cause: The backend fails to distinguish the token type when processing transaction information returned from Fireblocks. To create a PoC for this finding, the auditor deployed an ERC-1155 contract and minted 99 valueless NFTs to the address, which caused the account balance to increase by 99 MATIC.
As Dapps evolve and integrate more complex Web2 backend systems, new security challenges emerge. Developers who are not well-versed in both Web2 and Web3 security may inadvertently introduce vulnerabilities, which can have severe consequences. While Web3 exploits, such as those involving smart contracts, are publicly visible, Dapps exploited through Web2 components might have less exposure. To mitigate these risks, it is essential to perform rigorous penetration testing in addition to smart contract audits, ensuring a robust security posture across both Web2 and Web3 elements of the application.
At CertiK, we have conducted extensive research on various vulnerabilities common in the ecosystem. For thorough penetration testing of applications, audits of your smart contract code, or consultations with our team of experienced auditors and security experts, please get in touch with us at CertiK.com.