关于solidity漏洞的基础知识

精度的计算

这个错误常常出现于计算过程当中,使用了先除后乘,就会导致精度的计算错误,比如

interest = principal / 3_333 * 10_000;

如果本金小于3_333,那么就会计算的利息为零
如果按一下的例子计算

interest = principal * 10_000 / 3_333;

那么就不会出现这种的错误,因为使先扩大的精度,再除的话,就能避免精度的损失
个人也是通过近几次的审计报告发现,大家都是很注意计算的地方

函数变量不用同一个变量声明

这是我再审计报告中,第一次了解到这个问题,但是学过Java的都知道,成员变量和方法的变量冲突的话就会使用this来区分,而我们solidity没有这种说法,所以只能用不同的命名来表示,比如

address owner;

function change owner(address owner) internal{
require(owner == msg.sender,"NOT CHANGE");
owner = owner;
}

在这个函数中,我们可以清楚的看见想要修改合约的所有者。但是owner还是owner没有改变过,所以这个是修改不成功的
这里的 owner 参数与状态变量 owner 同名,这就导致了一个作用域的问题。
在 Solidity 中,函数参数的作用域优先于状态变量,这意味着在函数内部,owner 会首先指代函数的参数 address owner,而不是合约的状态变量 owner
因此,owner = owner; 只是将函数的参数 owner 赋值给它自己,并没有对链上的状态变量owner做任何修改。
在 Solidity 中,owner = owner; 是一个 自我赋值,没有任何效果,除非你在这个赋值中显式地引用状态变量。

状态变量(State Variables):状态变量存储在区块链上,生命周期与合约相同,可以在整个合约中访问和修改。
函数参数(Function Arguments):这些变量仅在函数执行期间有效,当函数执行完后,它们就不再存在。
局部变量(Local Variables):在函数内部声明的变量,它们只在函数的执行期间有效。

真确的改法:

address owner;

function change owner(address newowner) internal{
require(owner == msg.sender,"NOT CHANGE");
owner = newowner;
}

慎用transfer或者send

我们大家都知道,这俩个都是发送eth使用的,起初是因为开发者害怕重入攻击设置的,因为这俩个都有2300 gas的限制,所以就导致攻击者会消耗这俩个的gas上限,然后使合约功能不能正常进行

tx.origin和msg.sender

这个也是一个非常经典的问题,由于中间合约的存在,验证调用者的时候就要谨慎使用tx.origin
tx.origin是签署交易的钱包。msg.sender是直接调用者。 如果一个钱包直接调用一个合约
钱包 → 合约
那么从合约的角度来看,钱包既是msg.sender也是tx.origin。现在考虑如果钱包调用一个中间合约,然后中间合约再调用最终合约:
钱包 → 中间合约 → 最终合约
从最终合约的角度来看,钱包是tx.origin,中间合约是msg.sender
使用tx.origin来识别调用者会带来安全漏洞。假设用户被钓鱼攻击,调用了一个恶意中间合约
钱包 → 恶意中间合约 → 最终合约
在这种情况下,恶意中间合约获得了钱包的所有权限,允许它执行钱包被授权执行的任何操作——例如转移资金。

控制访问的问题

一般是由于函数缺少权限的限制,然后攻击者就可以随意调用。
或者就是管理员的权限太大,然后就导致了,攻击者一旦成为管理者,那么整个合约就会出现严重的危害,所以一般就要去看,关键的函数,有没有实现public,让攻击者乘虚而入

无限循环的问题

一般出现在数组过长,那么历遍他们,就会消耗很多的gas,有些攻击者发现某个合约存在数组很长的函数,那么他就会恶意多次调用,就是恶意消耗gas

不遵循样式指南

以下是要点:

  • 构造函数是第一个函数
  • 然后是fallback()和receive()(如果合约有的话)
  • 然后是external函数,public函数,internal函数和pure函数
  • 在每个组内
    • payable函数优先
    • 接着是非payable非view函数
    • view函数最后

抢跑交易

这是一个很常见的漏洞,因为交易信息都是要被打包送进交易池的,等待矿工确认,但是这就相当于信息共享了。然后攻击者利用这些信息,就会发送一个gas费更高的交易,抢先在正常交易的前面,通常发生在defi,一些猜测的游戏

没有统一单位(10^18,10^8)往往会出现计算错误

这是solidity中最常见的单位,一些粗心的开发者就会混用这俩个单位,往往在那种计算很杂的地方容易犯错

FOT代币,在转账的时候会产生手续费,就会导致一些计算失败

这是我在审计报告中第一次遇到这个Fee-on-Transfer (FOT) 代币,通常发生在那些借贷协议中,使用的代币数量很多,然后计算金额的时候又没有考虑到有些代币存在手续费的问题,就会导致一些计算的不准确

预言机的相关问题

对于预言机这方面,我还是缺少了解,就是对于它不是很敏感。我目前已知这类的问题都是用于价格的计算
比如,使用getThePrice() 函数来预测价格,那么就必须满足以下的条件

合约是否暂停(isPaused)。//这个可以省略,具体看合约的功能
价格预言机是否存在。
L2序列器是否正常(对于L2链)。
返回的价格是否大于零。

价格更新的时间戳(updatedAt)。
回合是否完整(answeredInRound)。
回合是否按顺序回答(answeredInRound >= roundId)。

用预言机的话,就要注意到这几个细节
配置更新时间阈值: 为了应对不同的市场环境,可以让合约所有者设置一个自定义的价格数据有效时间(比如,10分钟或者更短)。
价格验证事件: 在每次价格验证时,触发事件记录价格数据的验证状态,这有助于监控和审计价格来源的健康状态。
公开数据接口: 提供一个视图函数,返回包括时间戳和回合 ID 在内的完整价格数据,供外部用户验证和分析。
时间戳验证缺失: 如果预言机数据的更新时间戳很久之前,那么这个价格数据就是“过时”的,应该拒绝使用。
回合完整性检查: 如果价格数据属于一个不完整的回合(例如,数据没有完全更新),则无法保证其准确性。
回合顺序验证: 需要确保返回的回合数据是按顺序的,否则有可能是过时的无效数据。