struct低版本漏洞
code
1 | pragma solidity 0.4.17; |
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 | from solidity: |
- 写上storage:那么我们必须指向一个状态变量
- 写上memory:无法影响状态变量
此漏洞是位于0.5.0版本以下:NameRecord newRecord ;
默认是storage,然后未声明一个指针,因此它默认指向第一个slot,因此可以修改storage