26.struct低版本漏洞
2023-06-23 20:22:31 # 00.security

struct低版本漏洞

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
pragma solidity 0.4.17;
// A locked name registrar
contract NameRegistrar {

bool public unlocked = false; // registrar locked, no name updates

struct NameRecord { // map hashes to addresses
bytes32 name;
address mappedAddress;
}

// records who registered names
mapping(address => NameRecord) public registeredNameRecord;
// resolves hashes to addresses
mapping(bytes32 => address) public resolve;

function register(bytes32 _name, address _mappedAddress) public {
// set up the new NameRecord
NameRecord newRecord ;
newRecord.name = _name;
newRecord.mappedAddress = _mappedAddress;

resolve[_name] = _mappedAddress;
registeredNameRecord[msg.sender] = newRecord;

require(unlocked); // only allow registrations if contract is unlocked
}
}

analyse

This simple name registrar has only one function. When the contract is unlocked, it allows anyone to register a name (as a bytes32 hash) and map that name to an address. The registrar is initially locked, and the require on line 25 prevents register from adding name records. It seems that the contract is unusable, as there is no way to unlock the registry! There is, however, a vulnerability that allows name registration regardless of the unlocked variable.

To discuss this vulnerability, first we need to understand how storage works in Solidity. As a high-level overview (without any proper technical detail—we suggest reading the Solidity docs for a proper review), state variables are stored sequentially in slots as they appear in the contract (they can be grouped together but aren’t in this example, so we won’t worry about that). Thus, unlocked exists in slot[0], registeredNameRecord in slot[1], and resolve in slot[2], etc. Each of these slots is 32 bytes in size (there are added complexities with mappings, which we’ll ignore for now). The Boolean unlocked will look like 0x000…0 (64 0s, excluding the 0x) for false or 0x000…1 (63 0s) for true. As you can see, there is a significant waste of storage in this particular example.

The next piece of the puzzle is that Solidity by default puts complex data types, such as structs, in storage when initializing them as local variables. Therefore, newRecord on line 18 defaults to storage. The vulnerability is caused by the fact that newRecord is not initialized. Because it defaults to storage, it is mapped to storage slot[0], which currently contains a pointer to unlocked. Notice that on lines 19 and 20 we then set newRecord.name to _name and newRecord.mappedAddress to _mappedAddress; this updates the storage locations of slot[0] and slot[1], which modifies both unlocked and the storage slot associated with registeredNameRecord.

attack

This means that unlocked can be directly modified, simply by the bytes32 _name parameter of the register function. Therefore, if the last byte of _name is nonzero, it will modify the last byte of storage slot[0] and directly change unlocked to true. Such _name values will cause the require call on line 25 to succeed, as we have set unlocked to true. Try this in Remix. Note the function will pass if you use a _name of the form:

1
0x0000000000000000000000000000000000000000000000000000000000000001

self-understand

注意:只要最低两位不是00就行:01/10/11都可以,因为只有全0(00)才被认为是false

漏洞理解:0.5.0版本之后,solidity强制需要在结构体写上memory / storage,否则会报错如下:

1
2
3
4
from solidity:
njkn.sol:19:9: TypeError: Data location must be "storage" or "memory" for variable, but none was given.
NameRecord newRecord ;
^------------------^
  • 写上storage:那么我们必须指向一个状态变量
  • 写上memory:无法影响状态变量

此漏洞是位于0.5.0版本以下:NameRecord newRecord ;默认是storage,然后未声明一个指针,因此它默认指向第一个slot,因此可以修改storage

Prev
2023-06-23 20:22:31 # 00.security
Next