往年在年底我会写一篇年度总结的文章,历史文章文章列表可以见博客 “年度分类” tag,但是 2020 年过了几个月我才想起来没有写 2019 年的总结,也索性就先不写了。
所以本文其实是 2019 年和 2020 年两年的总结合并而来,不过不重要,技术和生活上的事情都是连贯的。
工作方面
xray 扫描器的工作
2019 年初,我还是在雷池 waf 团队。当时随着雷池的客户越来越多,部署规模越来越大,之前创业初期做的 waf 平台已经不能满足高性能、高可靠性的要求了,各种问题越来越多,所以整个团队决定进行一次大型重构升级工作,新增了大量的新技术。
2019 三四月份的时候,Monster 给我说,希望我去扫描器那边做一些安全策略的工作,我答应了,之后就是 大约 8 个月的 xray 扫描器开发工作了。
这个开发工作又是一次重构,当时整个洞鉴扫描器效果确实很一般,每天最头疼的问题就是为什么又卡住了。
为什么放弃动态语言
这不得不从开发语言 Python 说起。
我认为,下面这些问题能搞清楚才能写出一个高质量的模块不过分吧,但是 Python 能做到么?
- 一个函数接受几个参数?返回几个值?各自是什么类型?
- 一个对象有什么属性?一个属性什么情况下存在什么情况下不存在?
- 这个函数可能抛出什么异常?
有人会说 Python 的 type hint,但是那只是标记,并不是真的检查,何况很多三方库也没有 type hint 或者代码过于动态没办法 hint,实现完整的类型检查是很难的。
还有一些更高阶的工程问题,Python 同样解决方案很乱
- 内存和 cpu pprof
- 查看每个进程/线程的运行栈
- 方便的并发控制和调度
之前提到了卡住的问题,当时的引擎使用的是 Python 的 asyncio 框架,asyncio 可以简单理解为一个进程只有一个线程,在 io 操作的时候,可以切换到别的非 io 的代码执行。如果一个 async 函数就是不结束,那整个进程看上去就是卡死的状态,比如你的代码中有一个 input()
或者非 asyncio 版本的 sleep 等。
我们在卡住的时候,第一感觉是想找个办法查看下当前 Python 的进程在执行什么代码,发现竟然没啥通用的办法,我们大部分时候还是 strace 到 Python 进程看的,比如发现卡在 read(stdin)
上,然后就在某第三方的 poc 代码中找到一个 input()
。
一门工程语言不应该让人去猜测代码的行为,而是在人搞错的情况下,直接编译不通过。另外的需求就是尽可能简单,比如 C++ 和 Rust 就并不是一门非常通用的工程语言,只在特定的领域里面好使。
Golang
后来我们就换成了 Golang,也就是后面 xray 的开发语言,Golang 的优点很明显
- 强类型,包含简单的 interface 和 public、private 的 OOP 机制,比其他语言的 OOP 简单很多
- 明确的错误处理,
error
这东西我认为是利大于弊的,总比随随便便就可能抛异常好吧 - 简单代码实现安全高效的并发,goroutine、channel、defer 等真是神器
- 开发生态完善,比如
go fmt
、go vet
、跨平台编译的支持等 - 调试生态完善,比如 pprof、竞争检查等,卡住、数据竞争都 debug 非常的方便,能直接看到每一个 goroutine 在运行哪行代码
当然 Golang 也有自己的缺点
- 没有泛型导致代码冗余,这一点对我们偏业务方向的其实没太多的影响,又不是天天写 sort 库,直接使用
interface{}
的地方一般也不能靠泛型来解决。 error
处理确实繁琐了一些,因为大部分的时候,都是重复的三行代码
if err != nil {
return err
// 或者 return errors.Wraf(err, "do something")
}
xray 安全策略
在确定了语言之后,接下来就是整体架构和安全插件的编写了。那些就整理一下亮点吧
@koalr 做了整个扫描器的基础架构部分,比如基于 Google 的 martian 做了代理功能。还有流量的输出输出处理、HTTP 基础库、插件调度等,写了报告和反连平台的前端,写了非常强的 fastjson、weblogic 和 shiro 等检测插件,是非常有创新型的插件,有很多独家的 payload。后期也还有更加繁重的和业务对接的部分。
@phithon 写了基于语义分析的 xss 引擎,扫描 payload 被 waf 拦截的概率降低很多,扫描速度和检出率提高了很多,同样是一个非常有创新的插件,其他的还包括 struts 和 thinkphp 插件等。另外基于 Google 的 cel 做了一个基于表达式的 poc 框架,降低了写 poc 的灵活性,但是大大提高了可靠性和质量。这个具体在 安全人员的代码水平 一文中已经说过了。
我主要写了一些其他的传统插件和框架,比如命令注入、模板注入、报错 sql 注入等,这些都是基于回显检测的一个框架,因为它们的输入都是一个随机串或者随机表达式(比如 extractvalue(1,concat(char(126),md5([RandInt])))
),然后检测输出是否包含随机串的哈希或者表达式的计算结果。
其他的还包括 sql 盲注、jsonp、任意跳转、反连平台后端等,有些检测思路还是挺有意思的。
比如 jsonp 相关的漏洞在蜜罐溯源等场景下广泛使用,如何有效的去检测 jsonp 敏感信息泄露呢?
我们发现可以花式在 jsonp 中存储敏感信息
callback({"email":"xray@xray.cool"});
a={"email":"xray@xray.cool"}; cb({"s": a})
callback({"key":"email"})
callback({"key":"\u1234\u2345"})
如果使用正则匹配,可能会受到编码和文本格式的影响,而且容易出现误报,比如上面的 email
,很难去写一个正则去判断是在 key 还是在 value 的问题。
具体的思路已经在 阿里白帽大会的一个 ppt 里面公开,原理很清晰和简单,在后续的牧云产品的攻击检测方向,我们也有在做类似的事情。
在重定向插件中,除了常见的无任何限制的跳转以外,还考虑了一些组件的处理缺陷和 bug 来进行一些绕过,比如 Django CVE-2017-7233
CVE-2018-14574
,比如 PHP parseurl
的 bug(basic auth 的 @
和 #
导致了混淆),比如开发人员使用字符串包含或者前后缀匹配来防范 (比如 example.com
-> example.com.evil-example.com
)等等。这些思路同样可以用于 ssrf 插件。
在开发反连平台的功能的时候,除了服务于扫描器本身,还有一个目标就是可以独立部署,当一个私有化的 ceye 或者 dnslog 来用,那就需要多考虑一些问题,比如监听地址和访问地址不同、比如使用一个域名借助 glue record 搭建 dns server 等等,后面 @koalr 还给写个前端页面,易用性++。
长亭是一个 TO B 的公司,但是后续的 xray 的运营就像一个 TO C 的业务了。在 xray 运营的过程中,还去做了一些运营辅助的服务,比如使用函数计算搭建了更新检测服务和反馈系统,使用 sls 搭建了更新日志统计和分析系统等等。
xray 引擎和社区现在看上去是非常的成功的。对于安全社区来说,帮很多白帽子挣到了钱,帮很多安服仔挖到了漏洞,还输出了很多高质量的规范的 poc。对于公司来说,安全效果得到了很多人的肯定,也带来了一些洞鉴的项目线索。
牧云的工作
xray 在 6 月份放出去第一个版本,之后又快速迭代了几个月,发了十几个版本,进入到了功能和 bug 开始收敛的状态。快到年底的时候,Monster 又给我说,让我去牧云那边去做一些工作,我就翻过了 2019 年的 xray 翻到了 2020 年的 cloudwalker 那一页。
2020年一整年的工作重点都在牧云安全策略上,带来了一些以前踩的坑所得到的经验,也又踩了一些新坑。
Lua 和 teal
在 xray 抛弃了让让我们惶惶不安的 Python 之后,来到牧云,迎接我的是 Lua,一个比 Python 更加灵活更加动态,连数组和 map 都没有区分的语言,一个函数少传了参数也不会报错,接受少接收函数返回值也不会报错的语言。
首先介绍下整体的架构,主机安全产品主要由
- 管理平台,前后端数据库等
- 探针,提供基础平台功能,比如 Lua runtime、Lua 部分基础库、与管理平台通信、进程文件网络监控、插件调度等等。
- 安全策略插件,使用 Lua 编写,比如反弹 shell 检测、文件检测上传、暴力破解数据收集等等。
为啥是 Lua 呢?
在当前的场景下,我们其实是没有别的选择的,因为
- 探针需要控制 Lua 代码的 cpu 占用,Lua 虚拟机很轻量和纯净,非常好做,而其他的语言做起来就太复杂了
- 安全策略插件需要热更新,如果使用探针的原生语言 Golang 来编写,那更新策略插件的时候就需要更新整个探针,一个探针的大小(目前是几十兆)肯定是远大于一些 Lua 代码的,在大规模部署的情况下,对网络的要求会比较高,而且需要重启整个探针进程,对稳定性和在线时间指标等都不太有利。
在这种情况下,只能硬着头皮去写 Lua 了,有可以提高代码质量的方法么?
在前端界,大家也遇到了类似的问题,前端界的办法是发明了一门新语言,叫 TypeScript,是带类型的 JavaScript,最后可以由工具转换为 JavaScript 来支持在浏览器上执行。类似的思路,经过一番搜寻,我发现了 teal 这个语言,一种带类型的 Lua 方言,支持语法和类型检查和转换为 Lua。
跑个题,teal 和常用的 htop 工具是一个作者 Hisham H. Muhammad,这个人也在 Lua 其他的领域贡献了很多代码,是一个真正的大牛,这样让我对 teal 这个语言也充满了信心。
迁移 teal 是一个渐进的过程,大概是这样的过程
- 给所有的库写类型定义,因为插件调用的一些库还是 Lua 写的,那 teal 编译器怎么知道你的用法对不对呢?这个要靠类型定义文件来实现,也就是
xx.d.tl
,一个简单的样例如下
local file = require("file")
local record md5
sum: function(string): string
file_sum: function(string): string, err_t
-- 该函数不会关闭 fd
fd_sum: function(file.file_t): string, err_t
end
return md5
- 因为我们使用的库还是比较少的,写类型定义很快就结束了,接下来是使用 teal 重构部分简单的模块。因为项目刚起步不就,受限于一些基础设施和经验,很多安全插件中都存在复制第三方库代码和自己造轮子的情况,比如 json 库就有好几个。重构过程中,需要慢慢的去规范和统一。当然在这个过程中,新插件都直接是 teal 来写的。
- 完善 teal 开发体验,因为 teal 也是一个新事物,存在很多不完善的点,在使用过程中,我们需要经常的去和 teal 作者反馈 bug 和讨论需求。有一些小的不适合在主线上修改的点,我们在内部的 fork 版本里面做了修改。
团队
除了写代码能力以外,我觉得有想法和有创新能力非常重要的,前段时间做总结的时候,发现来到牧云之后一些大型项目架构几乎都是按照我自己的想法写的,比如换 teal 语言、新检测引擎和 file_server 的整体调整、远程基线检查工具、全量重点漏洞库等等,大家更像是被推着往前走,听工头的让怎么搬砖就怎么搬。对于积极主动有想法的同学,可能就也打消了热情,对于本来就不积极的同学,更不是一个良性的循环。
生活
平淡的流水账
现在回想 2019 年的生活,好像也没有太多的事情发生,每天和孙同学都是平平淡淡的幸福。
等到快年底的时候,RSA Conference 2020 就要召开了,公司的一些人想去学习下国外的先进安全产品和经验,顺便去美国玩一圈。一月初,一个下完大雪的早晨,在美国大使馆门口排了两个小时的队之后,顺利的拿到了签证。
后面的事情大家都知道了,美国肯定去不成了,大年初二我就从山东回了北京,因为担心后续疫情继续蔓延会增加旅途上的风险和进京的困难程度,接下来就是春节假期延长和居家办公了好久。
八月份,从公司附近搬家到了回龙观,房子大了很多,也不用天天爬六楼了,我和孙同学继续开启新生活。当时搬家和后续的打扫整理真的累,但是非常的快乐。过了几天,顺手回了趟山东,顺手领了结婚证(这里应该有一个微信上的狗头表情)。
结婚
因为疫情的原因,婚礼一拖再拖,在 11 月底,终于办了婚礼。下面是我在婚礼上的发言,全文搬运过来。
大家好,欢迎各位亲朋好友来参加我和孙同学的婚礼,来见证这美好的时刻。
我和孙同学是高中同学,在十多年前就认识了,但是深入的认识是在北京工作之后。2016 年我毕业去北京当程序员,她是医学类专业,要学五年,当年是大学五年级去北京的医院实习。在北京的这三四年里面,从最开始约饭,到后面去演唱会,去逛街逛公园,我们留下了很多美好的回忆,互相有了更深入的认识,我们今天我们终于结婚啦!
孙同学在工作和生活上都是一个非常出色的人。
在工作上,她就是是超人和大侠,在医院里,她可以脚踩风火轮忙碌一天,但同时保持着一丝不苟的态度,因为那关乎着病人的安危。在北京新发地新冠疫情爆发的时候,她也奋战在抗击疫情一线。当时正是酷暑六月,为了保证空气流通,采样点都是露天的,全密闭的防护服穿上去没多久,里面的衣服就会完全被汗水浸湿,手脚都会被汗水泡的脱皮。但是她没有任何怨言,因为她知道这就是她的使命所在,职责所在。
在生活上,她主要有两个角色,第一,是我的朋友,我们可以谈论各种感兴趣的事情,共同去做各种感兴趣的事情。第二,就是我们家庭的饲养员,她的厨艺非常好,同样的食材在我和她的手下做出来就完全是两种食物。在场的一些北京的同事去过我们家吃过饭,他们就可以证明。另外就是在生活上也非常关心我,不管我加班到多晚都会等着我,还比如这场婚礼的很多物资就是她在百忙之中准备的,我也没能帮上什么忙。
在这里,我想对孙同学说,我会一直默默支持你,不管你累了,或者遇到不开心的事情,我都会一直在你身边陪伴你,我们还要去一起去尝试更多生活中最开心和最美妙的东西。有个非常圆满的人生,这是我的梦想。希望到老之后,你会跟我说,认识我是正确的、快乐的。
从前,我真心觉得婚礼是一件特别烦琐,特别形式化的事情,直到此刻,看着宾朋满座,我们被祝福围绕,回想起这几天的点点滴滴,我才真正体会到,原来一直被感动,才是婚礼的感觉。当然此时此刻,除了感动,还有更多的是感谢。
感谢我的父母从小对我的教育,给我指引了人生道路。
感谢我的岳父岳母培养了这么优秀,这么有才华的女儿,我一定会全心全意的对她好。
感谢我和孙同学的高中老师宋老师,没有您当年严格的管理就没有我们今天的成绩。
感谢亲朋好友的光临,你们都是放弃假期休息的事件来参加婚礼,有的朋友还是在很远的地方赶过来的。
感谢主持人摄影师和其他工作人员的忙碌。
再一次感谢大家出席我们的婚礼,祝福大家一生都能幸福、健康、平安、喜乐。