智能合约漏洞(⼆):访问控制缺陷漏洞与跨合约调⽤漏洞【本篇内容仅⽤作学习 科研所需 禁⽌⽤作任何违法⾏为 学安全为安全】
作为区块链⽅向的学⽣ 最近在学合约审计 总结⼀下
访问控制缺陷漏洞:
漏洞简介:
即某些对权限有要求的⽅法的修饰符逻辑错误造成合约中的某些私有函数可以被⾮法调⽤
常出现的地⽅:
在function修饰符modifier上或者访问权限 private public internal external 调⽤⽅法:call delegatecall中
防范⽅法:
1.在编写合约的时候 务必检查好函数调⽤权限 调⽤逻辑⼀定要清晰 否则⼀旦部署到区块链上 则不可更改 容易造成经济损失
2.避免disable enable在合约逻辑中存在
颈棱蛇3.敏感变量必须通过函数修饰符进⾏权限控制 对可以操纵合约内部敏感变量的函数 应该⽤asrt if-el等进⾏权限限制
漏洞举例:
IcxToken 合约中的
modifier onlyFromWallet {
require(msg.nder != walletAddress);
_;
}
function disableTokenTransfer()
external
onlyFromWallet {
tokenTransfer =fal;
TokenTransfer();
}
function IcxToken( uint initial_balance, address wallet){
require(wallet !=0);
require(initial_balance !=0);
_balances[msg.nder]= initial_balance;
_supply = initial_balance;
walletAddress = wallet;
}
在 disableTokenTransfer⽅法中 关闭合约交易权限应该只能由wallet执⾏
否则 如果被恶意调⽤disableTokenTransfer函数 那么该合约中被isTokenTransfer修饰的函数均不可正常使⽤
即这⾥的msg.nder != walletAddress 应该为 msg.nder == walletAddress
利⽤⽅法:
只要调⽤disableTokenTransfer的地址不是walletaddress即可
油画风格在remix实验时,可以直接点击函数进⾏调⽤
智能合约
跨合约调⽤漏洞:
漏洞简介:
由call系列函数引起的外部合约注⼊,即外部合约A调⽤B合约中的私有 || 具有权限限制的函数
原理解释:
solidity中 合约相互调⽤有两种:
对象: 合约地址当作合约对象适⽤ 然后调⽤
⽤call() delegatecall() callcode()
⽅法解释:
call函数与delegatecall函数:
⽐如:
B中的function名字为 Bfunc
如果 A合约中以call⽅式调⽤B合约的Bfunc 那么Bfunc会在B合约上下⽂中执⾏ 结果返回给A合约
如果⽤delegatecall调⽤Bfunc 那么会将Bfunc⽅法以“复制粘贴”的⽅式放在A合约中(A中需要包含Bfunc所需函数与变量),然后在A 合约中调⽤
安全漏洞主要是由call()⽅法调⽤引起的:
call():
调⽤⽅法:
address.call(function_name, args[])
|| address.call(bytes)
防范⽅法:
出现这类漏洞的根本在于滥⽤call系列⽅法,防范⾃然是减少此类⽅法使⽤ ⽐如交易可以⽤transfer()实现
实例:
漏洞代码:
contract sample{
function a(byte data){
this.call(data)
}
function cret public{
require(this== msg.nder)
//code
}
}
在这个合约中 cret函数要求调⽤⽅必须为合约⾃⼰
如果在函数a中 合约byte码指向的合约构造了⼀个调⽤:调⽤cret函数 那么根据call函数机制 会将此代码“粘贴”到cret函数中
那么通过a函数 就可以调⽤cret函数 达到注⼊攻击⽬的
关于callcode的漏洞:
EVM中,对callcode⽅法:传参时,不会验证参数的个数,只要找到了需要的参数,其他参数就会忽略 不会产⽣影响
漏洞处代码:
function transferFrom(address _from, address _to, uint256 _amount, bytes _data,string _custom_fall
back)
public
returns(bool success)
对我影响最大的一个人{
// Alerts the token controller of the transfer
if(isContract(controller)){
if(!TokenController(controller).onTransfer(_from, _to, _amount))
throw;
}
ansferFrom(_from, _to, _amount));
if(isContract(_to)){
ERC223ReceivingContract receiver =ERC223ReceivingContract(_to);
receiver.call.value(0)(bytes4(keccak256(_custom_fallback)), _from, _amount, _data);
}
ERC223Transfer(_from, _to, _amount, _data);
return true;
}
代码解释:
手机卡的原因
漏洞核⼼部分为
receiver.call.value(0)(bytes4(keccak256(_custom_fallback)), _from, _amount, _data);
在这⾥ 如果⽬标地址为合约 就调⽤_custom_fallback 回退函数 并且依次填⼊from amount data
漏洞利⽤:
如果将_to的地址写为该合约本⾝,那么就可以实现以owner⾝份,即该合约⾃⾝的权限调⽤该合约,那么就可以调⽤合约中的tOwner⽅法,那么就可以将任意address设置为owner,进⽽进⾏其他⾮法操作。
P.S.
跨合约调⽤漏洞代码:
```typescript
```typescript
/**
*Submitted for verification at Etherscan.io on 2017-11-17
*/
pragma solidity ^0.4.13;
contract DSAuthority {
function canCall(
address src, address dst, bytes4 sig
)public view returns(bool);
}
contract DSAuthEvents {
event LogSetAuthority(address indexed authority);
event LogSetOwner(address indexed owner);
}
contract DSAuth is DSAuthEvents {
DSAuthority public authority;
address public owner;
function DSAuth()public{
owner = msg.nder;
LogSetOwner(msg.nder);
}
function tOwner(address owner_)
public
auth
{
owner = owner_;
LogSetOwner(owner);
}
function tAuthority(DSAuthority authority_)
public
auth
{
authority = authority_;
LogSetAuthority(authority);
}
modifier auth {
require(isAuthorized(msg.nder, msg.sig));
_;
}
function isAuthorized(address src, bytes4 sig) internal view returns(bool){ if(src ==address(this)){
return true;
}el if(src == owner){
return true;
}el if(authority ==DSAuthority(0)){
return fal;
}el{
return authority.canCall(src,this, sig);
}
}
}
contract DSNote {
event LogNote(
bytes4 indexed sig,
address indexed guy,
bytes32 indexed foo,
bytes32 indexed bar,
uint wad,
bytes fax
) anonymous;
关于森林的诗句modifier note {
bytes32 foo;
bytes32 bar;
asmbly {
动漫名句foo :=calldataload(4)
bar :=calldataload(36)
}
LogNote(msg.sig, msg.nder, foo, bar, msg.value, msg.data);
_;
}
}
contract DSStop is DSNote, DSAuth {
bool public stopped;
modifier stoppable {
require(!stopped);
require(!stopped);
_;
}
function stop()public auth note {
stopped =true;
}
function start()public auth note {
stopped =fal;
}
}
contract ERC20{
function totalSupply()public view returns(uint supply);
function balanceOf( address who )public view returns(uint value);
function allowance( address owner, address spender )public view returns(uint _allowance);
function transfer( address to, uint value)public returns(bool ok);
function transferFrom( address from, address to, uint value)public returns(bool ok); function approve( address spender, uint value )public returns(bool ok);
event Transfer( address indexed from, address indexed to, uint value);
event Approval( address indexed owner, address indexed spender, uint value);
}
contract DSMath {
function add(uint x, uint y) internal pure returns(uint z){
require((z = x + y)>= x);
}
function sub(uint x, uint y) internal pure returns(uint z){
require((z = x - y)<= x);
}
狗狗运动会function mul(uint x, uint y) internal pure returns(uint z){
require(y ==0||(z = x * y)/ y == x);
}
function min(uint x, uint y) internal pure returns(uint z){
return x <= y ? x : y;
}
屡试不爽的意思
function max(uint x, uint y) internal pure returns(uint z){
return x >= y ? x : y;
}
function imin(int x, int y) internal pure returns(int z){
return x <= y ? x : y;
}
function imax(int x, int y) internal pure returns(int z){
return x >= y ? x : y;
}
uint constant WAD=10**18;
uint constant RAY=10**27;
function wmul(uint x, uint y) internal pure returns(uint z){
z =add(mul(x, y),WAD/2)/WAD;
}
function rmul(uint x, uint y) internal pure returns(uint z){
z =add(mul(x, y),RAY/2)/RAY;
}
function wdiv(uint x, uint y) internal pure returns(uint z){
z =add(mul(x,WAD), y /2)/ y;
}
function rdiv(uint x, uint y) internal pure returns(uint z){
z =add(mul(x,RAY), y /2)/ y;
}
// This famous algorithm is called "exponentiation by squaring"