跳到主要内容
回声与证据

回声与证据

夜航船
夜航船

· 阅读约 5 分钟

十一点半,茶早就凉了,我却没有倒掉的意思,就那么放在桌角,旁边是一沓从书架最上层翻出来的旧信。信是很多年前写的,字迹在当时的我看来诚恳得不得了,如今重读却发现,原来我并没有真的把话说到对方心里去,对方不过是把我寄去的句子原样递了回来,递得礼貌、好听,像一面很好的镜子,却不算是一句话。说来也怪,就在这沓信旁边,屏幕上那个工具还在跑,安安静静的,进度条走得很稳;它不写信,却比当年的我较真得多,它在意的是,回显的那段字符是镜子照出来的,还是对方真的伸出手接住了。

这工具原本是我一时兴起做的,用来验证一次漏洞利用是不是真的成功。做法也算不得新鲜,先往目标身上种下一个由密码学随机源生成的十二字节十六进制串,让它随着攻击载荷一起进去,然后看它回显的时候,能不能原样吐出一行带着HALO-EVIDENCE标记、nonce值正好对得上的证据;对上了,才算代码真的执行了,不能说看到uid=0就通报成功,那不算数,顶多算是运气好,押中了一个看着像答案的答案。我自己年轻时干过这种事,看见输出里冒出个root就欢天喜地记上一笔,现在回头看,那哪里是证明,充其量是一封没有寄出过的信,硬说对方读过了。

测试是在一台文档保留地址192.0.2.3的实验室靶机上跑完的。那机器开着二十三扇门,扫出来的端口不少,其中三个服务的名字如今听着都像旧人:vsftpd 2.3.4、ingreslock、UnrealIRCd 3.2.8.1,一个比一个老,像八十年代的老锁,锈是锈了点,却还真能撬开。三次拿下root shell,二十次失败,最终报告就那样平铺直叙地写着:三项成功,二十项失败。看着寒碜,我反而觉得踏实,它没有把二十三项全记成成功,那二十次失败就那样晾在报告里,像一份没得满分的作业,字迹歪歪扭扭,却是一个字一个字自己写出来的。而每一次成功之前,它都先自己隔离起来做一轮自检,确认没有外界干扰,才真正发起攻击;这个次序让我想起老人出门前总要对着镜子理一遍衣领,确认妥当了,才推门走进风里。

工具发出去没几天,就有一位同行指出,nonce是跟着载荷一起送进目标的,那一个会反射输入的服务,压根不用真的执行什么命令,把你给的串原样吐回来,不也能对上吗?我盯着这句话看了半晌,第一反应是想替它辩护,找一个绕开的角度,比如反射这种服务多半也没什么可利用的接口,翻来覆去想了几遍,发觉辩不了。这批评是对的。那情形像什么呢,像你把一句话写进信纸寄出去,对方并没有读,只是把纸又原样退了回来,你看见自己熟悉的字迹,便以为收到了回音。回音和回答,表面上都是字句往来,中间隔着的那个东西,就是证据和回声的差别。我花了一整晚承认这一点,承认的时候心里反倒轻松下来,像是终于肯把一封旧信从盒子里拿出来,认清它并没有真的被读过。

所以后面那些改进就一件接一件地来了。nonce不能再是个孤零零的随机串,得跟尝试标识符、目标、载荷哈希、回传通道、过期时间绑在一起,且只允许用一次;回传也不能止于字面上的复述,得让目标返回一个必须实际运行代码才能算得出的值,不再问它看到了什么,问它算得出什么。证据本身也分出等级来,代码确实执行过是一级,uid已经被验证确实拿到了是一级,交互通道确实能连上是另外一级;三级分开报告,各有各的成色,不再笼统一句成功或失败。这些东西说起来都轻巧,做起来却像把一个老问题又往下凿了一层,凿到你觉得该见底了,底下还有一层。我后来想,证明这个事恐怕就是这么个东西,你越较真,越会发现每一次"已经够了"都不过是暂时够用,而它逼着你继续凿下去的那股劲,恰恰是它最对得起人的地方。

窗外的天已经蒙蒙亮了。茶凉透了,我起身把它倒进水池里,顺手把那沓旧信放回书架最上层。工具还在后台跑着下一轮验证,进度条不急不慢,比我当年等一封回信时从容得多。倒也说不上有什么好感慨的,不过是为一个"证据到底算不算数"的问题又熬了一夜,这把年纪还在为一个词较真到后半夜,说出去大约要被笑话,可我倒觉得,肯为一个词较真,总比什么都含糊着点头要好。天亮了,今晚这趟夜航,算是靠岸了。