
地址投毒:只需要你复制错东西的攻击
针对加密用户的绝大多数攻击都需要你先犯一个错误——点开一个链接,把助记词敲进一个表单,批准一件你没读过的事。地址投毒需要的比这还少。它只需要你从自己的交易记录里复制一个地址。
没有恶意软件,没有钓鱼网站,也没有需要骗你签下的签名。攻击者的全部工作,就是把一串看起来说得过去的字符,放在一个你日后会去取用的地方。
它是怎么运作的
先从使它成为可能的东西说起:没有人读地址。一个比特币或以太坊地址,是三十来个毫无意义的 base58 或十六进制字符,没有肉眼可判的校验和,也没有任何理由记在脑子里。于是每一个钱包、区块浏览器和交易所都会把它缩短显示——0x8f3C…9A063——而每一个用户都学会了核对看得见的两端、跳过中间。
那个习惯,就是全部的攻击面。
攻击者用暴力枚举不断生成地址,直到有一个与你真正用过的地址共享开头和结尾的若干字符——通常是你最近转过账的那一个。这很便宜。生成密钥对很快,而要凑齐比如说四个开头字符和四个结尾字符,在普通硬件上只需几秒。没有人在破解什么;他们只是在掷骰子,直到某个数字看起来眼熟。
接着他们要把那个地址送进你的历史记录。常见的手法有:
粉尘转账。 他们从那个相似地址给你转来一笔微不足道的金额——几个聪,或者某个代币不到一分钱的零头。这是一笔普通的入账交易,因此会像其他任何一笔那样落进你的记录。在 UTXO 链上,这笔粉尘还会变成你钱包里一枚可花费的币,这一点稍后要紧。
零值转账。 在 EVM 链上,一笔转账数量为零的 ERC-20 transfer 是有效交易。攻击者从你的地址向他们的仿冒地址发送 0 个代币,而某些界面会把它渲染成一笔由你发出的付款。你的历史记录从此显示:你似乎向一个自己从未选择过的地址转过账。
伪造事件。 一个恶意的代币合约可以发出一个从未真正发生过的 Transfer 事件。那些信任事件日志的浏览器和钱包就会把它显示出来。什么都没有移动;这条记录纯粹是一句谎言。
然后他们等待。几天,有时几个月。这次攻击的收成发生在你下一次要给同一个对手方付款、在历史里翻找「上次用的那个地址」、然后复制错了一行的时候。两个都长得像 0x8f3C…9A063。你核对了两端。两端一致。
为什么惯常的忠告靠不住
「务必再核对一遍地址」是标准建议,而它单独拿出来是会失效的,因为核对恰恰是这次攻击早已预料到的动作。你确实核对了。你把钱包展示给你的六个字符,和你记忆中的六个字符做了比对,它们完全相同——因为攻击者让它们相同。
只有当被比对的东西伪造起来很贵时,核验才有意义。四个或六个相同字符只值几秒钟。十六个相同字符的代价则会超过这次盗窃本身的价值。整场博弈,就在于你的习惯落在这条曲线的哪一端。
UTXO 链上还有第二个陷阱。那笔粉尘付款如今是你钱包里的一枚币,而如果你的钱包会自动合并输入,它可能被卷进日后的某笔交易——把那个被撒粉的地址与你其余的币关联起来。这是隐私问题而非盗窃问题,但也正因如此,粉尘值得被忽略,而不是被「收拾干净」。
真正管用的防御
永远不要从交易记录里取地址。 这是唯一真正重要的改变。你的历史记录是一份「发生过什么」的记载,而不是一份「你信任谁」的名单——而且世界上任何人都能往里面写东西。要从对手方那里拿地址,走一条你能察觉冒名者的渠道,或者从一份你自己填好的通讯录里拿。
比对完整地址,或者比对某个能概括整个地址的东西。 读完四十二个字符并不现实。比对一个 identicon——一张由整串字符确定性生成的小图——则很现实,因为一个在中间任何位置有所不同的仿冒地址,会产出一张一眼可辨的不同图案。你的眼睛替你完成了耐心不会去做的工作。
使用保存好的联系人。 一个你曾经保存过、且来源曾经核验过的地址,日后无法被投毒。通讯录正是为此而存在,也正因如此,SSP 把联系人保存在你的设备上而不是服务器上:一份联系人列表只有在别人无法编辑它时才值得信任。
大额或首次转账,先发一笔试探性付款。 小额,通过另一条渠道确认收到,然后再发其余部分。手续费让这件事变得麻烦,而对于一笔你输不起的付款,它依然是对的。
忽略粉尘。 不要花它,不要去「清理」它;如果你在意隐私,也要小心自动合并。
SSP 为此做了什么
地址投毒既是安全问题,也是一个渲染问题,所以答案的一部分,就住在地址被画出来的方式里。
SSP 对地址采用开头六位与结尾六位的截断——0x8f3C4b…9A0631——而不是许多界面所用的三位或四位。这不是审美选择。每多一个字符,凑出一个匹配所需的暴力枚举成本就翻上一截,而十二个可见字符,足以把一个有说服力的仿冒地址推到随手生成的范围之外。
中间部分是被弱化,而不是被删除。在展开形态下,钱包会显示完整字符串,两端加粗、中间用较浅的颜色,于是攻击者指望你跳过的那部分仍然实实在在地留在屏幕上,而不是被一个可能藏着任何东西的省略号取而代之。
两个应用跑的是同一份代码。SSP Wallet 里的 splitAddressForDisplay 是从 SSP Key 直接移植过来的,被刻意保持完全一致,好让一个地址在你的浏览器上和手机上长得一模一样。这一点很要紧,因为 SSP 会在两台设备上把交易给你看两遍——而第二眼只有在两个界面以同样方式呈现同样内容时,才算得上一次真正的核对。两种不同的截断方式,会让差异藏进那点不同里。
在 SSP Key 的批准界面上,收款方会与截断文本并排显示一个由完整地址生成的 identicon,轻触即可展开完整地址、作为可选中的文本呈现。identicon 正是击败「中段替换」的那一环:攻击者可以凑齐你的开头和结尾字符,但要凑出一张由每一个字符共同推导出的图案,那是一个难得多的问题。
以上没有一条是保证。如果你粘贴了一个被投毒的地址并在两台设备上都批准了它,SSP 就会签名——这正是「一个由你掌控的钱包」而非「一个替你质疑决定的托管方」的含义。二取二多签阻止别人花掉你的币;它无法阻止你有意付给错误的人。就像所有依靠让你自己动手才能得逞的攻击一样,防御必须落在签名之前,而不是签名之中。
简短版
地址投毒之所以奏效,是因为你比对的是六个字符而不是四十二个,而攻击者知道是哪六个。防御不是更加细心——而是永远不从攻击者能写入的地方取地址,并使用那些把你会跳过的部分重新摆回你眼前的工具。


