Skip to content

Commit 3b56d69

Browse files
committed
Add fake jetton contract in the TON not so smart contracts list
1 parent 9b477fe commit 3b56d69

File tree

3 files changed

+63
-0
lines changed

3 files changed

+63
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Brought to you by [Trail of Bits](https://www.trailofbits.com/), this repository
2727
- [Cosmos](./not-so-smart-contracts/cosmos)
2828
- [Substrate](./not-so-smart-contracts/substrate)
2929
- [Solana](./not-so-smart-contracts/solana)
30+
- [TON](./not-so-smart-contracts/ton)
3031
- [Program Analysis](./program-analysis): Using automated tools to secure contracts
3132
- [Echidna](./program-analysis/echidna): A fuzzer that checks your contract's properties
3233
- [Medusa](./program-analysis/medusa/docs/src): A next-gen fuzzer that checks your contract's properties

not-so-smart-contracts/ton/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Each _Not So Smart Contract_ consists of a standard set of information:
1717
| Not So Smart Contract | Description |
1818
| ---------------------------------------------------------------------------- | ------------------------------------------------------------ |
1919
| [Int as Boolean](int_as_boolean) | Unexpected result of logical operations on the int type |
20+
| [Fake Jetton contract](fake_jetton_contract) | Any contract can send a `transfer_notification` message |
2021

2122
## Credits
2223

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Fake Jetton Contract
2+
3+
TON smart contracts use the `transfer_notification` message sent by the receiver's Jetton wallet contract to specify and process a user request along with the transfer of a Jetton. Users add a `forward_payload` to the Jetton `transfer` message when transferring their Jettons, this `forward_payload` is forwarded by the receiver's Jetton wallet contract to the receiver in the `transfer_notification` message. The `transfer_notification` message has the following TL-B schema:
4+
5+
```
6+
transfer_notification#7362d09c query_id:uint64 amount:(VarUInteger 16)
7+
sender:MsgAddress forward_payload:(Either Cell ^Cell)
8+
= InternalMsgBody;
9+
```
10+
11+
The `amount` and `sender` are added by the receiver's Jetton wallet contract as the amount of Jettons transferred and the sender of Jettons (owner of the Jetton wallet that sent of the `internal_transfer` message). However, all the other values specified by the user in the `forward_payload` are not parsed or validated by the Jetton wallet contract, they are added as it and sent in the `transfer_notification` message. Therefore, the receiver of the `transfer_notification` message must consider malicious values in the `forward_payload` and validate them properly to prevent any contract state manipulation.
12+
13+
## Example
14+
15+
The following simplified code highlights the lack of token_id validation in the `transfer_notification` message. This contract tracks user deposits by updating the `token0_balances` dictionary entry for the user's address. However, the `transfer_notification` message handler does not verify that the `sender_address` is equal to one of the `token0` or `token1` Jetton wallets owned by this contract. This allows users to manipulate their deposit values by sending the `transfer_notification` message from a fake Jetton wallet contract.
16+
17+
```FunC
18+
#include "imports/stdlib.fc";
19+
20+
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
21+
slice cs = in_msg_full.begin_parse();
22+
int flags = cs~load_uint(4);
23+
;; ignore all bounced messages
24+
if (is_bounced?(flags)) {
25+
return ();
26+
}
27+
slice sender_address = cs~load_msg_addr(); ;; incorrectly assumed to be Jetton wallet contract owned by this contract
28+
29+
(cell token0_balances, cell token1_balances) = load_data(); ;; balances dictionaries
30+
31+
(int op, int query_id) = in_msg_body~load_op_and_query_id();
32+
33+
if (op == op::transfer_notification) {
34+
(int amount, slice from_address) = (in_msg_body~load_coins(), in_msg_body~load_msg_addr());
35+
cell forward_payload_ref = in_msg_body~load_ref();
36+
slice forward_payload = forward_payload_ref.begin_parse();
37+
38+
int is_token0? = forward_payload.load_int(1);
39+
40+
if (is_token0?) {
41+
slice balance_before = token0_balances.dict_get?(267, from_address);
42+
int balance = balance_before.load_coins();
43+
balance = balance + amount;
44+
token0_balances~dict_set(267, from_address, balance);
45+
} else {
46+
slice balance_before = token1_balances.dict_get?(267, from_address);
47+
int balance = balance_before.load_coins();
48+
balance = balance + amount;
49+
token1_balances~dict_set(267, from_address, balance);
50+
}
51+
52+
save_data();
53+
return ();
54+
}
55+
}
56+
```
57+
58+
## Mitigations
59+
60+
- Store the address of Jetton wallet contract owned by the current contract at the time of contract initialization and use this stored value to verify the sender of the `transfer_notification` message.
61+
- Validate all the user provided values in the `forward_payload` instead of trusting users to send correct values.

0 commit comments

Comments
 (0)