Docs
Checking for TON drain vulnerability

Checking for TON drain vulnerability

Learn how to check if anyone can drain TONs from your contract.

This tutorial walks you through detecting and preventing one of the most critical smart contract vulnerabilities: unauthorized fund withdrawals.

We'll intentionally create a vulnerable TON smart contract, then use the TSA Blueprint plugin to identify the security flaw. This hands-on approach helps developers understand how vulnerabilities emerge and how tools can catch them before deployment.

By the end, you'll know how to:

  • Set up TSA security analysis in your Blueprint project
  • Interpret vulnerability reports and understand what they mean
  • Reproduce the found vulnerability on an actual blockchain network

1. Create a Project with a Vulnerable Contract

To demonstrate how the TON drain checker works, we'll start by deliberately creating a smart contract with a known vulnerability — specifically, one that could allow unauthorized withdrawal of funds.

Follow the steps below to set up the project and write the vulnerable contract.

Create a Blueprint Project

Before you begin, make sure you meet all the requirements for working with Blueprint. You can find the details in the Blueprint documentation.

Once everything is set up, create a new project by running:

npm create ton@latest

You'll be prompted to enter:

  • Project name – enter TonDrainDemo
  • First contract name – enter VulnerableContract
  • Contract template – choose An empty contract (Tolk)

We'll use this empty contract as a starting point to build our intentionally vulnerable example.

Enter the generated directory:

cd TonDrainDemo

Implement the Vulnerable Contract

The generated empty contract is located at contracts/vulnerable_contract.tolk.

Open this file and replace its contents with the following:

struct (0x12345678) Withdraw {
    amount: coins
    to: address
}
 
fun onInternalMessage(in: InMessage) {
    if (in.body.isEmpty()) {
        // just receive TONs
        return;
    }
 
    val msg = lazy Withdraw.fromSlice(in.body);
    val outMsg = createMessage({
        dest: msg.to,
        bounce: false,
        value: msg.amount,
    });
    outMsg.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE);
}

Security Issue: This contract contains a critical vulnerability — it lacks authorization checks. Any user can send a Withdraw message to transfer TONs from the contract to any address. There's no verification that the sender is the contract owner or has permission to withdraw funds.

2. Set Up the TSA Plugin

To detect and analyze security vulnerabilities like the one in our contract, we'll use the TSA plugin.

Refer to TSA Blueprint Plugin Installation Guide for instructions.

3. Run the TON Drain Checker

Now let's analyze our vulnerable contract. Run the TSA drain checker with the following command:

yarn blueprint tsa drain-check -c VulnerableContract

By default, built-in checkers are interactive: TSA asks for confirmation twice — first before preparation and timeout calculation, and then again before the analysis starts. If you want to skip both prompts, add --no-interactive.

When you execute this command, TSA performs symbolic analysis of your contract. This means it systematically explores different execution paths to identify any that could lead to vulnerabilities.

Here's what the command options mean:

  • drain-check refers specifically to the TON drain checker — a specialized analyzer that searches only for execution paths where unauthorized fund withdrawal could occur.
  • -c VulnerableContract specifies the name of the contract to analyze.

You can customize the analysis with additional options. To see all available parameters, run:

yarn blueprint tsa drain-check --help

The analysis process includes:

  • Path exploration - Examining various ways the contract could be called
  • Constraint solving - Determining what inputs would trigger vulnerable execution paths
  • Vulnerability detection - Flagging paths that allow unauthorized withdrawals

If vulnerabilities are detected, the tool will report the findings and provide detailed information about how to reproduce the vulnerable execution paths.

After a moment, you'll receive a vulnerability report similar to this:

⚠️ Vulnerability found!
Summary path: tsa/reports/run-<id>/summary.txt
Typed input: tsa/reports/run-<id>/typed-input.yaml
SARIF with full information: tsa/reports/run-<id>/report.sarif

The generated files are stored in tsa/reports/run-<id>:

tsa
└── reports
    └── run-<id>
        ├── contract-data.boc
        ├── message-body.boc
        ├── report.sarif
        ├── summary.txt
        ├── tsa-reproduce-config.json
        └── typed-input.yaml

Next Steps

There are two options for proceeding:

  • Manual investigation - Examine the generated files to understand the vulnerability in detail
  • Automated reproduction - Use TSA's built-in functionality to reproduce the issue on an actual blockchain

We'll explore both approaches starting with manual investigation. The automated reproduction will be described in Section 4 of this tutorial.

Manual Investigation

Let's break down what each component of the report means:

  • Summary (summary.txt): A concise overview of the vulnerability — this is what was displayed in your terminal.
  • Typed input (typed-input.yaml): The recommended file for investigation. It contains the typed representation of the reproducing message body and contract data in one place.
  • Raw BoC inputs (message-body.boc, contract-data.boc): The exact message body and contract data that reproduce the vulnerability.
  • SARIF report (report.sarif): A standardized format for static analysis results. This file contains comprehensive details about analyzed execution paths. For most use cases, you won't need to examine this file directly.

The checker has successfully identified our missing authorization check — anyone can trigger the withdrawal function.

4. Reproduce the Vulnerability on the Blockchain

Prerequisites

Before proceeding, ensure you have:

  1. A TestNet wallet - Create one with Tonkeeper
  2. TestNet TONs - Get TONs from the Testgiver bot

For this tutorial, you'll need at least 1.5 TestNet TONs to cover deployment and transaction fees.

Starting the Reproduction

The vulnerability report includes a command to reproduce the issue on-chain:

To reproduce the vulnerability on the blockchain, run:
> yarn blueprint tsa reproduce --config tsa/reports/run-[id]/tsa-reproduce-config.json

This configuration file (generated by tsa drain-check) contains all necessary data for the reproduction process.

Run the Reproduction Command

yarn blueprint tsa reproduce --config tsa/reports/run-[id]/tsa-reproduce-config.json

Configure Network and Wallet

You'll be prompted to:

  • Choose a network - Select testnet
  • Connect your wallet - Follow the on-screen instructions to link your TestNet wallet

Deploy the Vulnerable Contract

Next, you'll deploy the contract to TestNet. When asked:

  • Reuse of an already deployed contract: Answer no

Answer no issues a fresh deployment of a contract under test that will receive a message that triggers the found vulnerability. This is outside of the scope for this tutorial, but you can also send a reproduction message to an already deployed contract. To do this, answer "yes" to this question and proceed according to prompts. The data of the input contract will be checked, so don't be afraid to type the wrong address — the plugin will report the wrong contract state.

  • Contract funding amount: Enter 0.5 TONs. This is the money you are about to withdraw
  • Confirm deployment: Approve the transaction in your wallet app

Fund the Reproduction Attempt

You'll then specify how much you're willing to spend on reproducing the vulnerability:

  • Reproduction budget: Enter 0.5 TONs

Execute and Confirm

TSA will now:

  1. Re-analyze the contract with your wallet as the sender
  2. Generate a reproduction transaction if the vulnerability persists
  3. Prompt you to confirm this transaction in your wallet

Once confirmed, you'll receive a transaction ID.

Verify the Results

You can use the following explorers to view the transaction (TonViewer is recommended for better debugging tools):

TonViewer:

https://testnet.tonviewer.com/transaction/<transaction_id>

TonScan:

https://testnet.tonscan.org/tx/<transaction_id>

You can verify that more TONs were received than sent. This transaction is an evidence of the unauthorized withdrawal.

Further Investigation

For deeper analysis, you can use:

  • TxTracer and ReTracer - Tools for transaction tracing
  • Typed data files - Generated earlier for detailed cell analysis

Since we already understand this vulnerability (missing authorization), extensive investigation isn't necessary — but these tools are useful for analyzing more complex issues.

5. Fix the Vulnerability and Re-run Analysis

Understanding the Fix

The vulnerability stems from missing authorization checks. To fix it, we need to:

  1. Store the contract owner's address in persistent storage
  2. Validate message senders against this owner address before processing withdrawals

The Fixed Contract

Update contracts/vulnerable_contract.tolk with the following corrected code:

struct (0x12345678) Withdraw {
    amount: coins
    to: address
}
 
struct Storage {
    owner: address
}
 
fun Storage.load() {
    return Storage.fromCell(contract.getData())
}
 
fun checkOwner(sender: address) {
    val storage = lazy Storage.load();
    assert(sender == storage.owner) throw 501;
}
 
fun onInternalMessage(in: InMessage) {
    if (in.body.isEmpty()) {
        return;  // just receive TONs
    }
 
    checkOwner(in.senderAddress);
 
    val msg = lazy Withdraw.fromSlice(in.body);
    val outMsg = createMessage({
        dest: msg.to,
        bounce: false,
        value: msg.amount,
    });
    outMsg.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE);
}

Verify the Fix

Re-run the TSA drain checker to confirm the vulnerability is resolved:

yarn blueprint tsa drain-check -c VulnerableContract

Expected Result: The tool should report that no vulnerabilities were found, confirming our security fix is effective.

Conclusion

In this tutorial, you've learned how to use the TSA TON drain checker to identify and fix a critical smart contract vulnerability. You've successfully:

  • Set up TSA security analysis in a Blueprint project
  • Detected unauthorized withdrawal vulnerabilities
  • Reproduced issues on TestNet

The TSA plugin offers additional security checkers beyond drain analysis — explore them with yarn blueprint tsa --help to strengthen your contract security further.

Keep building securely!