RSS Feed
更好更安全的互联网
  • Code Breaking 挑战赛 Writeup

    2018-12-10
    作者:LoRexxar'@知道创宇404实验室
    时间:2018年12月7日
    @phith0n 在代码审计小密圈二周年的时候发起了Code-Breaking Puzzles挑战赛,其中包含了php、java、js、python各种硬核的代码审计技巧。在研究复现the js的过程中,我花费了大量的精力,也逐渐找到代码审计的一些技巧,这里主要分享了5道ez题目和1道hard的the js这道题目的writeup,希望阅读本文的你可以从题目中学习到属于代码审计的思考逻辑和技巧。

    easy - function

    思路还算是比较清晰,正则很明显,就是要想办法在函数名的头或者尾找一个字符,不影响函数调用。

    简单实验了一下没找到,那就直接fuzz起来吧

    很容易就fuzz到了就是\这个符号

    后来稍微翻了翻别人的writeup,才知道原因,在PHP的命名空间默认为\,所有的函数和类都在\这个命名空间中,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。

    紧接着就到了如何只控制第二个参数来执行命令的问题了,后来找到可以用create_function来完成,create_function的第一个参数是参数,第二个参数是内容。

    函数结构形似

    然后执行,如果我们想要执行任意代码,就首先需要跳出这个函数定义。

    这样一来,我们想要执行的代码就会执行

    exp

    easy pcrewaf

    这题自己研究的时候没想到怎么做,不过思路很清楚,文件名不可控,唯一能控制的就是文件内容。

    所以问题的症结就在于如何绕过这个正则表达式。

    简单来说就是<后面不能有问号,<?后面不能有(;?>反引号,但很显然,这是不可能的,最少执行函数也需要括号才行。从常规的思路肯定不行

    https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html

    之后看ph师傅的文章我们看到了问题所在,pcre.backtrack_limit这个配置决定了在php中,正则引擎回溯的层数。而这个值默认是1000000.

    而什么是正则引擎回溯呢?

    在正则中.*表示匹配任意字符任意位,也就是说他会匹配所有的字符,而正则引擎在解析正则的时候必然是逐位匹配的,对于

    这段代码来说

    但很显然,服务端不可能不做任何限制,不然如果post一个无限长的数据,那么服务端就会浪费太多的资源在这里,所以就有了pcre.backtrack_limit,如果回溯次数超过100万次,那么匹配就会结束,然后跳过这句语句。

    回到题目来看,如果能够跳过这句语句,我们就能上传任意文件内容了!

    所以最终post就是传一个内容为

    对于任何一种引擎来说都涉及到这个问题,尤其对于文件内容来说,没办法控制文件的长度,也就不可避免的会出现这样的问题。

    对于PHP来说,有这样一个解决办法,在php的正则文档中提到这样一个问题

    preg_match返回的是匹配到的次数,如果匹配不到会返回0,如果报错就会返回false

    所以,对preg_match来说,只要对返回结果有判断,就可以避免这样的问题。

    easy - phpmagic

    题目代码简化之后如下

    稍微阅读一下代码不难发现问题有几个核心点

    1、没办法完全控制dig的返回,由于没办法命令注入,所以这里只能执行dig命令,唯一能控制的就是dig的目标,而且返回在显示之前还转义了尖括号,所以

    2、in_array(pathinfo($log_name, PATHINFO_EXTENSION), ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'], true)这句过滤真的很严格,实在的讲没有什么直白的绕过办法。

    3、log前面会加上$_SERVER['SERVER_NAME']

    第一点真的是想不到,是看了别人的wp才想明白这个关键点 http://f1sh.site/2018/11/25/code-breaking-puzzles%E5%81%9A%E9%A2%98%E8%AE%B0%E5%BD%95/

    之前做题的时候曾经遇到过类似的问题,可以通过解base64来隐藏自己要写入的内容绕过过滤,然后php在解析的时候会忽略各种乱码,只会从<?php开始,所以其他的乱码都不会影响到内容,唯一要注意的就是base64是4位一解的,主要不要把第一位打乱掉。

    简单测试一下

    这样一来我们就能控制文件内容了,而且可以注入<?php

    接下来就是第二步,怎么才能控制logname为调用php伪协议呢?

    问题就在于我们如何控制$_SERVER['SERVER_NAME'],而这个值怎么定是不一定的,这里在这个题目中是取自了头中的host。

    这样一来头我们可以控制了,我们就能调用php伪协议了,那么怎么绕过后缀限制呢?

    这里用了之前曾经遇到过的一个技巧(老了记性不好,翻了半天也没找到是啥比赛遇到的),test.php/.就会直接调用到test.php

    通过这个办法可以绕过根据.来分割后缀的各种限制条件,同样也适用于当前环境下。

    最终poc:

    easy - phplimit

    这个代码就简单多了,简单来说就是只能执行一个函数,但不能设置参数,这题最早出现是在RCTF2018中

    https://lorexxar.cn/2018/05/23/rctf2018/

    在原来的题目中是用next(getallheaders())绕过这个限制的。

    但这里getallheaders是apache中的函数,这里是nginx环境,所以目标就是找一个函数其返回的内容是可以控制的就可以了。

    问题就在于这种函数还不太好找,首先nginx中并没有能获取all header的函数。

    所以目标基本就锁定在会不会有获取cookie,或者所有变量这种函数。在看别人writeup的时候知道了get_defined_vars这个函数

    http://php.net/manual/zh/function.get-defined-vars.php

    他会打印所有已定义的变量(包括全局变量GET等)。简单翻了翻PHP的文档也没找到其他会涉及到可控变量的

    在原wp中有一个很厉害的操作,直接reset所有的变量。

    http://f1sh.site/2018/11/25/code-breaking-puzzles%E5%81%9A%E9%A2%98%E8%AE%B0%E5%BD%95/

    然后只有当前get赋值,那么就只剩下get请求的变量了

    后面就简单了拼接就好了

    然后...直接列目录好像也是个不错的办法2333

    easy - nodechr

    nodejs的一个小问题,关键代码如下

    这里的注入应该是比较清楚的,直接拼接进查询语句没什么可说的。

    然后safekeyword过滤了select union -- ;这四个,下面的逻辑其实说简单的就一句

    如何构造这句来查询flag,开始看到题一味着去想盲注的办法了,后来想明白一点,在注入里,没有select是不可能去别的表里拿数据的,而题目一开始很明确的表明flag在flag表中。

    所以问题就又回到了最初的地方,如何绕过safekeyword的限制。

    ph师傅曾经写过一篇文章 https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html

    在js中部分字符会在toLowerCase和toUpperCase处理的时候发生难以想象的变化

    用在这里刚好合适不过了。

    hard - thejs

    javascript真难....

    关键代码以及注释如下

    由于对node不熟,初看代码的时候简单研究了一下各个部分都是干嘛的。然后就发现整个站几乎没什么功能,就是获取输入然后取其中固定的输出,起码就自己写的代码来说不可能有问题。

    再三思考下觉得可能问题在引入的包中...比较明显的就是lodash.merge这句,这句代码在这里非常刻意,于是就顺着这个思路去想,简单翻了一下代码发现没什么收获。后来@spine给了我一个链接

    https://github.com/HoLyVieR/prototype-pollution-nsec18/blob/master/paper/JavaScript_prototype_pollution_attack_in_NodeJS.pdf

    js特性

    首先我们可以先回顾一下js的一部分特性。

    由于js非常面向对象的编程特性,js有很多神奇的操作。

    在js中你可以用各种方式操作自己的对象。

    在js中,所有的对象都是从各种基础对象继承下来的,所以每个对象都有他的父类,通过prototype可以直接操作修改父类的对象。

    而且子类会继承父类的所有方法

    在js中,每个对象都有两个魔术方法,一个是constructor另一个是__proto__

    对于实例来说,constructor代表其构造函数,像前面说的一样,函数可以通过prototype获取其父对象

    而另一个魔术方法__proto__就等价于.constructor.prototype

    由于子类会继承父类的所有方法,所以如果在当前对象中找不到该方法,就会到父类中去找,直到找不到才会爆错

    在复习了上面的特性之后,我们回到这个漏洞

    回到漏洞

    在漏洞分析文中提到了这样一种方式

    假设对于语句

    如果我们控制a为constructor,b为prototype,c为某个key,我们是不是就可以为这个对象父类初始化某个值,这个值会被继承到当前对象。同理如果a为__proto__,b也为__proto__,那么我们就可以为基类Object定义某个值。

    当然这种代码不会随时都出现,所以在实际场景下,这种攻击方式会影响什么样的操作呢。

    首先我们需要理解的就是,我们想办法赋值的__proto__对象并不是真正的这个对象,如图

    所以想要写到真正的__proto__中,我们需要一层赋值,就如同原文范例代码中的那样

    通过这样的操作,我们就可以给Object基类定义一个变量名。

    由于子类会继承父类的所有方法,但首先需要保证子类没有定义这个变量,因为只有当前类没有定义这个变量,才会去父类寻找

    在js代码中,经常能遇到这样的代码

    这种情况下,js会去调用obj的aaa方法,如果aaa方法undefined,那么就会跟入到obj的父类中(js不会直接报该变量未定义并终止)。

    这种情况下,我们通过定义obj的基类Object的aaa方法,就能操作这个变量,改变原来的代码走向。

    最后让我们回到题目中来。

    回到题目

    回到题目中,这下代码的问题点很清楚了。整个代码有且只有1个输入点也就是req.body,这个变量刚好通过lodash.merge合并.

    这里的lodash.merge刚好也就是用于将两个对象合并,成功定义了__proto__对象的变量。

    我们也可以通过上面的技巧去覆盖某个值,但问题来了,我们怎么才能getshell呢?

    顺着这个思路,我需要在整个代码中寻找一个,在影响Object之后,且可以执行命令的地方。

    很幸运的是,虽然我没有特别研究明白nodejs,但我还是发现模板是动态生成的。

    这里的代码是在请求后完成的(动态渲染?)

    跟入到template函数中,可以很清楚的看到

    接下来就是这一大串代码中寻找一个可以影响的变量,我们的目标是找一个未定义的变量,且后面有判断调用它

    这里的sourceURL刚好符合这个条件,我们直接跟入前面的options定义处,进入函数一直跟下去,直到lodash.js的3515行。

    可以看到object本身没有这个方法,但仍然遍历到了,成功注入了这个变量,紧接着渲染模板就成功执行代码了。

    完成攻击

    其实发现可以注入代码之后就简单了,我朋友说他不能用child_process来执行命令,我测试了一下发现是可以的,只是不能弹shell回来不知道怎么回事。思考了一下决定直接wget外带数据出来吧。

    poc

    需要注意一定要是json格式,否则__proto__会解成字符串,开始坑了很久。

    直接偷懒用ceye接请求,其实用什么都行


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/755/

    作者:Elfinx | Categories:安全研究技术分享 | Tags:
  • Ethereum Smart Contract Audit CheckList

    2018-12-10
    Author: Knownsec 404 Blockchain Security Research Team
    Time: 2018.12.05
    Chinese Version: https://paper.seebug.org/741/
    Project link: https://github.com/knownsec/Ethereum-Smart-Contracts-Security-CheckList
    In the Ethereum contract audit CheckList, I divided the 29 issues encountered in Ethereum contract auditing into five major categories, including coding specification issues, design defect issues, coding security issues, coding design issues, and coding security issues. This will help smart contract developers and security workers get started quickly with smart contract security.This CheckList refers to and collates with the research results of the major blockchain security research teams in the completion process. Once imperfections/errors occurred, welcome to submit issues.Because this article is mainly a CheckList, the article will not contain too detailed vulnerability/hazard information, and most of the vulnerability analysis will be mentioned in the scanning report.

    1. Coding specification issue

    (1) Compiler version

    In the contract code, the compiler version should be specified. It is recommended to use the latest compiler version.

    Compilers of older versions may cause various known security issues, such as https://paper.seebug.org/631/#44-dividenddistributor

    V0.4.23 updates a compiler vulnerability. In this version, if both constructors are used, i.e.,

    one of the constructors will be ignored, which only affects v0.4.22. V0.4.25 fixes the uninitialized storage pointer problem mentioned below.

    https://etherscan.io/solcbuginfo

    (2) Constructor writing issue

    The correct constructor should be used for different compiler versions, otherwise the contract owner may change.

    In the solidify compiler syntax requirements of versions less than 0.4.22, the contract constructor must be equal to the contract name, and the name is affected by the case, e.g.,

    After version 0.4.22, the constructor keyword was introduced as a constructor declaration. But no function is required.

    If you don't follow the corresponding method, the constructor will be compiled into a normal function, which can be called arbitrarily, leading to more serious consequences such as owner permission.

    (3) Return standard

    Following the ERC20 specification, the transfer and approve functions should return a bool value, and a return value code needs to be added.

    The result of transferFrom should be consistent with the result returned by transfer.

    (4) Event standard

    Follow the ERC20 specification and require the transfer and approve functions to trigger the corresponding event.

    (5) Fake recharge issue

    In the transfer function, the judgment of the balance and the transfer amount needs to use the require function to throw an error, otherwise it will judge that the transaction is successful mistakingly.

    The above code may cause false recharge.

    The correct code is as follows:

    2. Design defect issue

    (1) Approve authorization function conditional competition

    Conditional competition should be avoided in the approve function. Before modifying the allowance, you should change it to 0 first and then to _value.

    The reason for this vulnerability is that in order to encourage miners to mine in the underlying miners' agreement, the miners can decide what to pack for themselves. In order to make more profits, the miners generally choose to package the deals with larger gas prices, rather than relying on the order of transactions.

    By setting 0, the hazards arising from the conditional competition can be alleviated to some extent. The contract manager can check the log to determine if there is a conditional competition. The greater significance of this fix is to remind users who use the approve function. The operation of this function is irreversible to some extent.

    The above code may lead to conditional competition.

    So add the following in the approve function:

    Change the allowance to 0 and then the corresponding number.

    (2) Loop dos issue

    [1] Loop consumption issue

    It is not recommended to use too many loops in contracts.

    In Ethereum, each transaction consumes a certain amount of gas, and the actual consumption is determined by the complexity of the transaction. The larger the number of loops, the higher the complexity of the transaction. When the maximum allowable gas consumption is exceeded, the transaction will fail.

    Real world event

    Simoleon (SIM)

    Pandemica

    [2] Loop security issue

    In the contract, the number of loops should be prevented from being controlled by the user. And the attacker may use an excessive loop to complete the Dos attack.

    When a user needs to transfer money to multiple accounts at the same time, we need to traverse the transfer of the target account list, which may lead to DoS attacks.

    In the above situation, it is recommended to use withdrawFunds to let the user retrieve their token instead of sending it to the corresponding account. This can reduce the hazard to a certain extent.

    If the above code controls a function call, then it can construct a huge loop to consume gas, causing a Dos problem.

    3. Coding security issue

    (1) Overflow issue

    [1] Arithmetic overflow

    When calling addition, subtraction, multiplication and division, you should use the safeMath library instead, otherwise it will easily lead to calculation overflow, resulting in inevitable loss.

    balances[msg.sender] - _value >= 0

    You can bypass the judgment by underflow.

    The usual fix is to use openzeppelin-safeMath, but it may also be limited by judging different variables. However, it is difficult to impose restrictions on multiplication and exponential multiplication.

    The correct writing:

    Real world event

    Hexagon

    SMT/BEC

    [2] Coin/destroy overflow issue

    In the coin/destroy function, the upper limit should be set for totalSupply to avoid the increase in malicious coinage events due to vulnerabilities such as arithmetic overflow.

    There is no limit to totalSupply in the above code, which may cause the exponential arithmetic overflow.

    The correct writing:

    Real world event

    (2) Reentrancy vulnerability

    Avoid using call to trade in smart contracts to avoid reentrancy vulnerabilities.

    In the smart contract, call, send, and transfer are provided to trade eth. The biggest difference for call is that there is no limit for gas. The other two, when the gas is not enough, will report out of gas.

    There are several characteristics of reentrancy vulnerability.

    1. Using the call function.
    2. There is no limit for the call function gas.
    3. Deducting the balance after the transfer.
    4. Adding () to execute the fallback

    The above code is a simple demo of reentrancy vulnerability. A large number of contract tokens are recursively transferred by reentrancy vulnerabilities.

    For possible reentrancy issues, use the transfer function to complete the transfer as much as possible, or limit the gas execution of the call. These can effectively reduce the harm.

    The above code is a way to use mutex lock to avoid recursive protection.

    Real world event

    The Dao

    (3) Call injection

    When the call function is invoked, you should do strict permission control, or write the function invoked to hardcode directly.

    In the design of EVM, if the parameter data of the call is 0xdeadbeef (assumed function name) + 0x0000000000.....01, then it is the invoke function.

    Call function injection can lead to token stealing and permission bypass. Private functions and even partially high-privilege functions can be called through call injection.

    For example, when the delegatecall function must call another contract within the contract, the keyword library can be used to ensure that the contract is static and indestructible. By forcing the contract to be static, the storage environment can be simple to a certain extent and preventing the attacker from attacking the contract by modifying the state.

    Real world events

    call injection

    (4) Permission control

    Different functions in the contract should have reasonable permission settings.

    Check whether the functions in the contract use public, private and other keywords correctly for visibility modification. Check whether the contract is correctly defined and use the modifier to restrict access to key functions to avoid unauthorized control.

    The above code should not be a public function.

    Real world event

    Parity Multi-sig bug 1

    Parity Multi-sig bug 2

    Rubixi

    (5) Replay attack

    If the contract involves the demands for entrusted management, attention should be paid to the non-reusability of verification to avoid replay attacks.

    In the asset management system, there are often cases of entrusted management. The principal gives the assets to the trustee for management and pays a certain fee to the trustee. This business scenario is also common in smart contracts.

    Here is an example of the transferProxy function, which is used when user1 transfers token to user3 but does not have eth to pay for gas price. In this case, user2 is delegated for payment by calling transferProxy.

    The problem with this function is that the nonce value is predictable. Replay attacks can be performed with other variables unchanged which lead to multiple transfers.

    The vulnerability stems from the DEF CON 2018 topics.

    Replay Attacks on Ethereum Smart Contracts Replay Attacks on Ethereum Smart Contracts pdf

    4. Coding design issue

    (1) Address initialization issue

    When the address is involved in a function, it is recommended to add the verification of require(_to!=address(0)) to effectively avoid unnecessary loss caused by user misuse or unknown errors.

    The address that EVM initializes when compiling the contract code is 0. If the developer initializes an address variable in the code without setting an initial value, or the user does not initialize the address variable upon any mis-operation, and this variable is called in the following code, unnecessary security risks may rise.

    This type of check can be used in the simplest way to avoid issues such as unknown errors or short address attacks.

    (2) Judgment function issue

    When the conditional judgment is involved, the require function instead of the assert function is used. Because assert will cause the remaining gas to be consumed, but they are consistent in other aspects.

    It is worth noting that the assert has mandatory consistency. For static variables, assert can be used to avoid some unknown problems, because it will force the termination of the contract and make it invalid. And in some conditions, assert may be more suitable.

    (3) Balance judgment issue

    Don't assume that the contract is created with a balance of 0 and the transfer can be forced.

    Be cautious to write invariants for checking account balances, because an attacker can send wei to any account forcibly, even if the fallback function throws.

    The attacker can create a contract with 1wei and then call selfdestruct(victimAddress) to destroy it. This balance is forcibly transferred to the target, and the target contract has no code to execute and cannot be blocked.

    It is worth noting that during the packaging process, the attacker can transfer before the contract is created through race condition so that the balance is not 0 when the contract is created.

    (4) Transfer function issue

    Upon completing a transaction, it is recommended to use transfer instead of send by default.

    When the target of the transfer or send function is a contract, the contract's fallback function will be invoked. But if the fallback function failed to execute, transfer will throw an error and automatically roll back, and send will return false. Therefore, you need to judge the return type when using send. Otherwise, the transfer may fail and the balance will decrease.

    The above code use the send() function to transfer, because there is no check for the returned value of the send() function.

    If msg.sender fail to call the contract account fallback(), send() returns false, which eventually results in a reduction in the account balance with money loss.

    (5) External call design issue

    For external contracts, pull instead of push is preferred.

    In the case of external calls, unpredictable failure happens. In order to avoid unknown loss, the external operations should be changed into user's own disposal.

    Error example:

    When a transfer to a party is required, the transfer is changed to define the withdraw function, allowing the user to execute the function by himself and withdraw the balance. This will avoid unknown losses to the greatest extent.

    Example code:

    (6) Error handling

    When the contract involves a call or other methods that operates at the base level of the address function, make reasonable error handling.

    If such an operation encounters an error, it will not throw an exception but return false and continue the execution.

    The above code does not verify the return value of send. If msg.sender is a contract account, send returns false when the fallback call fails.

    So when using the above method, you need to check the return value and make error handling.

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

    It's worth noting that as a part of the EVM design, the following functions will return True if the contract being called does not exist.

    Before calling such functions, you need to check the validity of the address.

    (7) Weak random number issue

    The method of generating random numbers on smart contracts requires more considerations.

    The Fomo3D contract introduces the block information as a parameter for generating the random number seed in the airdrop reward, which causes the random number seed to be affected only by the contract address and cannot be completely random.

    The above code directly led to the Fomo3D incident causing more than a few thousands eth loss.

    So when it comes to such applications in a contract, it is important to consider a more appropriate generation method and a reasonable order of use.

    Here is a reasonable random number generation method hash-commit-reveal, i.e., the player submits the action plan and the hash to the back end, which then generates the corresponding hash value as well as the random number to reveal and returns the corresponding random number to commit. In this way, the server can't get the action plan, and the client can't get the random number.

    One great implementation code is the random number generation code for dice2win.(https://etherscan.io/address/0xD1CEeeefA68a6aF0A5f6046132D986066c7f9426)

    But the biggest problem with hash-commit-reveal is that the server will get all the data in the process briefly after user submits. Maliciously suspending the attack will destroy the fairness to some extent. Detailed analysis can be found in the smart contract game - Dice2win security analysis.

    Of course, hash-commit is also a good implementation in some simple scenarios, i.e., the player submits the action plan hash before generating a random number and submitting the action plan.

    Real world event

    Fomo3D Incident

    Last Winner

    (8) Variable coverage vulnerability

    Avoid the key of the array variable in contract being controlled.

    In EVM, arrays are different from other types. As arrays are dynamically sized, array data is calculated as

    The key is the position defined by the map variable, i.e., 1. The offset refers to the offset in the array, e.g., for map[2], the offset is 2.

    The address of map[2] is sha3(1)+2. Assuming map[2]=2333, storage[sha3(1)+2]=2333.

    This is a problem because offset is controllable so that we can write values to any address of the storage.

    This may overwrite the value of any address in the storage, affecting the logic of the code itself, leading to even more serious problems.

    For detailed principles, please refer to - 以太坊智能合约 OPCODE 逆向之理论基础篇 - https://paper.seebug.org/739

    5. Code hidden danger

    (1) Grammatical property issue

    Be careful with the rounding down of integer division in smart contracts.

    In smart contracts, all integer divisions are rounded down to the nearest integer. For higher precision, a multiplier is needed to increase this number.

    If the problem occurs explicitly in the code, the compiler will raise an error and cannot continue compiling. However, if it appears implicitly, the round-down approach will be taken.

    Error example:

    Correct code:

    (2) Data privacy

    note that all data in the chain is public.

    In the contract, all data including private variables are public. Privacy data cannot be stored on the chain.

    (3) Data reliability

    In the contract, the timestamp should not be allowed to appear in the code to avoid interference by the miners. Instead, the constant data such as block.height should be used.

    (4) Gas consumption optimization

    For some functions and variables that do not involve state changes, you can add constant to avoid gas consumption.

    (5) Contract users

    In the contract, we should try to consider the situation when the trading target is the contract and avoid the various malicious uses incurred thereby.

    The above contract is a typical case when the contract is not considered as a user. This is a simple bidding code to compete for the king. When the trade ether is bigger than the highestBid in the contract, the current user will become the current "king" of the contract, and his trading amount will become the new highestBid.

    However, when a new user tries to become the new "king" and the code executes to require(currentLeader.send(highestBid));, the fallback function in the contract is triggered. If the attacker adds a revert() function to the fallback function, the transaction will return false and the transaction will never be completed. Then the current contract will always be the current "king" of the contract.

    (6) Log records

    Key events should have an Event record. In order to facilitate operation, maintenance and monitoring, in addition to functions such as transfer and authorization, other operations also need to add detailed event records such as administrator permission transfer and other special main functions.

    (7) Fallback function

    Define the Fallback function in the contract and make the Fallback function as simple as possible.

    The fallback will be called when there is a problem with the execution of the contract (if there is no matching function). When the send or transfer function is called, only 2300 gas is used to execute the fallback function after the failure. The 2300 gas only allows a set of bytecode instructions to be executed and needs to be carefully written to avoid the use of gas.

    Some examples:

    (8) Owner permission issue

    Avoiding the owner permission is too large.

    For contract owner permissions that are too large, the owner can freely modify various data in the contract, including modification rules, arbitrary transfer, and any coinage. Once a safety issue occurs, it may lead to serious results.

    Regarding the owner permission issue, several requirements should be followed:

    1. After the contract is created, no one can change the contract rules, including the size of the rule parameters.
    2. Only the owner is allowed to withdraw the balance from the contract

    (9) User authentication issue

    Don't use tx.origin for authentication in the contract.

    Tx.origin represents the initial address. If user a invokes contract c through contract b, for contract c, tx.origin is user a, and msg.sender is contract b. This represents a possible phishing attack, which is extremely dangerous for authentication.

    Here's an example:

    We can construct an attack contract:

    When the user is spoofed and invokes the attack contract, it will bypass the authentication directly and transfer the account successfully. Here you should use msg.sender to do permission judgment.

    https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin

    (10) Race condition issue

    Try to avoid relying on the order of transactions in the contract.

    In smart contracts, there is often a reliance on the order of transactions. Such as the rule of occupying a hill to act as a lord or the last winner rule. These are rules that are designed because of the strong dependence on the order of transactions. But the bottom rule of Ethernet is based on the law of maximum interests of miners. Within a certain degree of limit, as long as the attackers pay enough costs, he can control the order of the transactions to a certain extent. Developers should avoid this problem.

    Real world event

    Fomo3D Incident

    (11) Uninitialized storage pointer

    Avoiding initializing struct variables in functions.

    A special data structure is allowed to be a struct structure in solidity, and local variables in the function are stored by default using storage or memory.

    Storage and memory are two different concepts. Solidity allows pointers to point to an uninitialized reference, and uninitialized local storage causes variables to point to other stored variables. This can lead to variable coverage and even more serious consequences.

    After the above code is compiled, s.x and s.y will point incorrectly to owner and a.

    After the attacker executes fake_foo, the owner will be changed to himself.

    The above issue was fixed in the latest version of 0.4.25.

    CheckList audit reports

    REF


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/754/

    作者:Elfinx | Categories:技术分享 | Tags:
  • LCTF2018 ggbank 薅羊毛实战

    2018-11-21
    作者:LoRexxar'@知道创宇404区块链安全研究团队
    时间:2018年11月20日
    11.18号结束的LCTF2018中有一个很有趣的智能合约题目叫做ggbank,题目的原意是考察弱随机数问题,但在题目的设定上挺有意思的,加入了一个对地址的验证,导致弱随机的难度高了许多,反倒是薅羊毛更快乐了,下面就借这个题聊聊关于薅羊毛的实战操作。

    分析

    源代码
    https://ropsten.etherscan.io/address/0x7caa18d765e5b4c3bf0831137923841fe3e7258a#code

    首先我们照例来分析一下源代码

    和之前我出的题风格一致,首先是发行了一种token,然后基于token的挑战代码,主要有几个点

    跟着看checkfriend函数

    checkfriend就是整个挑战最大的难点,也大幅度影响了思考的方向,这个稍后再谈。

    空投函数没看有什么太可说的,就是对每一个新用户都发一次空投。

    然后就是goodluck函数

    然后只要余额大于200000就可以拿到flag。

    其实代码特别简单,漏洞也不难,就是非常常见的弱随机数问题。

    随机数的生成方式为

    另一个的生成方式为

    其实非常简单,这两个数字都是已知的,msg.sender可以直接控制已知的地址,那么左值就是已知的,剩下的就是要等待一个右值出现,由于block.number是自增的,我们可以通过提前计算出一个block.number,然后写脚本监控这个值出现,提前开始发起交易抢打包,就ok了。具体我就不详细提了。可以看看出题人的wp。

    https://github.com/LCTF/LCTF2018/tree/master/Writeup/gg%20bank

    但问题就在于,这种操作要等block.number出现,而且还要抢打包,毕竟还是不稳定的。所以在做题的时候我们关注到另一条路,薅羊毛,这里重点说说这个。

    合约薅羊毛

    在想到原来的思路过于复杂之后,我就顺理成章的想到薅羊毛这条路,然后第一反正就是直接通过合约建合约的方式来碰这个概率。

    思路来自于最早发现的薅羊毛合约https://paper.seebug.org/646/

    这个合约有几个很精巧的点。

    首先我们需要有基本的概念,在以太坊上发起交易是需要支付gas的,如果我们不通过合约来交易,那么这笔gas就必须先转账过去eth,然后再发起交易,整个过程困难了好几倍不止。

    然后就有了新的问题,在合约中新建合约在EVM中,是属于高消费的操作之一,在以太坊中,每一次交易都会打包进一个区块中,而每一个区块都有gas消费的上限,如果超过了上限,就会爆gas out,然后交易回滚,交易就失败了。

    上述的poc中,有一个很特别的点就是我加入了checkfriend的判断,因为我发现循环中如果新建合约的函数调用revert会导致整个交易报错,所以我干脆把整个判断放上来,在判断后再发起交易。

    可问题来了,我尝试跑了几波之后发现完全不行,我忽略了一个问题。

    让我们回到checkfriend

    checkfriend只接受地址中带有7d7ec的地址交易,光是这几个字母出现的概率就只有1/36*1/36*1/36*1/36*1/36这个几率在每次随机生成50个合约上计算的话,概率就太小了。

    必须要找新的办法来解决才行。

    python脚本解决方案

    既然在合约上没办法,那么我直接换用python写脚本来解决。

    这个挑战最大的问题就在于checkfriend这里,那么我们直接换一种思路,如果我们去爆破私钥去恢复地址,是不是更有效一点儿?

    其实爆破的方式非常多,但有的恢复特别慢,也不知道瓶颈在哪,在换了几种方式之后呢,我终于找到了一个特别快的恢复方式。

    我们拿到了地址之后就简单了,首先先转0.01eth给它,然后用私钥发起交易,获得空投、转账回来。

    需要注意的是,转账之后需要先等到转账这个交易打包成功,之后才能继续下一步交易,需要多设置一步等待。

    有个更快的方案是,先跑出200个地址,然后再批量转账,最后直接跑起来,不过想了一下感觉其实差不太多,因为整个脚本跑下来也就不到半小时,速度还是很可观的。

    脚本如下

    最终效果显著


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/747/

    作者:Elfinx | Categories:安全研究技术分享 | Tags:
  • 以太坊合约审计 CheckList 之变量覆盖问题

    2018-11-16
    作者:LoRexxar'@知道创宇404区块链安全研究团队
    时间:2018年11月16日
    系列文章: