当前位置:首页 > 知识

任意代码执行

一个输入框,任意一个没过滤的代码分号,服务器就姓别人的执行了。这不是任意电影桥段。2017年Equifax泄露案,代码攻击者利用Apache Struts2的执行一个远程代码执行漏洞,拿走了1.47亿人的任意社保号、驾照、代码信用卡信息。执行整个入口,任意就是代码某个Web应用里一段没做输入校验的代码。

任意代码执行(Arbitrary Code Execution),执行说白了就是任意:攻击者能让目标机器跑起他自己写的代码。注意,代码不是执行"读取某个文件"或"越权访问某个页面"——那些是权限问题。这是更底层的东西:你的CPU在执行谁的指令。一旦达到这一步,这台机器在逻辑上已经不属于原主人了。

它是怎么发生的

最常见的通道是内存。C/C++程序里,缓冲区溢出是个老话题,但老不代表消失。程序往一个固定大小的栈空间里拷贝用户输入,输入超长,多出来的部分覆盖了函数返回地址。攻击者精心构造这段溢出数据,把返回地址改成自己shellcode的入口。函数一返回,CPU就跳进了攻击者的代码。

另一种路径是解释器。Python的eval()、PHP的system()、Java的反序列化——这些功能本身是给开发者用的,但如果参数能被外部控制,就等于把执行权交出去了。Log4j那个漏洞(CVE-2021-44228)就是典型:日志里写个${ jndi:ldap://...},Log4j会去加载远程类并执行。一个日志库,变成了后门。

还有一类更隐蔽的:反序列化。程序把一段字节流还原成对象,攻击者在字节流里塞入恶意对象,还原过程本身就触发了代码执行。Java生态里这种漏洞多到安全研究员都懒得编号了。

为什么它比别的漏洞更致命

SQL注入能拖库,XSS能偷cookie,但任意代码执行意味着攻击者拿到了和应用程序相同的系统权限。如果这个应用以root或SYSTEM运行——太多生产环境确实如此——那整台服务器就是他的了。装挖矿程序、建反向shell、横向移动进内网,都是顺理成章的事。

更麻烦的是持久化。攻击者可以写一个cron job、改一个启动脚本、甚至patch掉系统里某个常用命令。你重装应用没用,得重装系统。有些APT组织会在固件层面植入后门,那连重装都不够。

防御的核心思路

没有银弹。但有几件事必须做。

内存安全语言是方向。Rust的所有权模型在编译期就堵死了大部分溢出可能。但现实是,大量基础设施还是C/C++写的,不可能一夜重写。所以ASLR、DEP、Stack Canary这些缓解措施仍然重要——它们不阻止漏洞存在,但让利用难度陡增。

输入校验是基本功。任何来自外部的数据——HTTP参数、文件内容、环境变量——都不能直接喂给eval、exec、system。如果业务逻辑非要动态执行,用沙箱。但沙箱本身也经常被逃逸,所以这只能算纵深防御的一层。

最小权限原则被说烂了,但真正做到的服务没几个。Web应用就不该以root跑。容器里加个--read-only和--cap-drop=ALL,能挡掉一大类后续利用。

补丁管理听着无聊,但Log4j和Struts2的惨案反复证明:已知漏洞不修,等于把门开着。攻击者不需要0day,他只需要你懒。

任意代码执行不是某一种具体漏洞,而是一类后果的统称。它的可怕之处在于不可逆——代码一旦跑起来,你永远不知道它做了什么。日志可以删,后门可以藏,时间线可以伪造。等你发现的时候,攻击者可能已经在内网里住了三个月。

分享到: