本文作者:常箭@涂鸦智能安全实验室
一、简介
envoy : Envoy Proxy是专为大型现代 SOA(面向服务架构)架构设计的 L7 代理和通信总线。
wasm : WebAssembly的缩写。是一种用于基于堆栈的虚拟机的二进制指令格式。Wasm 被设计为编程语言的可移植编译目标,支持在 Web 上部署客户端和服务器应用程序。
TinyGo : 一个Go编译器,旨在用于微控制器,WebAssembly(WASM)和命令行工具等小型场景。
envoy代理项目本身并未设计WAF项目,但是根据其HTTP filters阶段,可以编写wasm插件,获取http流量,进行过滤操作。
二 、envoy配置
根据官方文档(https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/wasm_filter),wasm插件应按照如下配置:
http_filters: - name: envoy.filters.http.wasm typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm config: name: "my_plugin" root_id: "my_root_id" configuration: "@type": "type.googleapis.com/google.protobuf.StringValue" value: | {} vm_config: runtime: "envoy.wasm.runtime.v8" vm_id: "my_vm_id" code: local: filename: "lib/envoy_filter_http_wasm_example.wasm" - name: envoy.filters.http.router
filename参数即为编译好的wasm插件路径。
三、wasm插件开发
根据其官网介绍,wasm_filter目前是实验性的,正在积极开发,且不能在Windows机器中使用。官网给出了envoy下wasm C++ SDK (https://github.com/proxy-wasm/proxy-wasm-cpp-sdk),由于笔者对C有天生恐惧,所以使用了针对其C++ SDK的go版本实现(https://github.com/tetratelabs/proxy-wasm-go-sdk)。
根据go SDK的demo样例(https://github.com/tetratelabs/proxy-wasm-go-sdk/blob/main/examples/helloworld/main.go),可以看到go wasm SDK导出了以下http阶段函数供我们使用:
func (*DefaultHttpContext) OnHttpRequestHeaders(int, bool) Action
接收到http headers阶段,此阶段还未开始接受POST数据。
func (*DefaultHttpContext) OnHttpRequestBody(int, bool) Action
接收到http POST数据阶段,第一个参数为bodySize,即接收到的数据大小,第二个参数为endOfStream,即是否接收完毕。若未接收完毕,可直接返回types.ActionPause继续接收,亦可判断总的bodySize是否超过限制要求,超过限制的请求可以忽略过滤,也可以选择直接阻断防止攻击者发送超大封包绕过。
func (*DefaultHttpContext) OnHttpRequestTrailers(int) Action
分块消息
func (*DefaultHttpContext) OnHttpResponseHeaders(int, bool) Action
返回headers阶段
func (*DefaultHttpContext) OnHttpResponseBody(int, bool) Action
返回体阶段。此阶段可用作检测敏感数据(例如身*证号,手*号等)是否有明文返回的不规范场景。
func (*DefaultHttpContext) OnHttpResponseTrailers(int) Action
分块消息
func (*DefaultHttpContext) OnHttpStreamDone()
完成请求阶段。
其他可用的导出函数:
func (*DefaultPluginContext) OnTick()
定时任务函数,此过程可用于定时规则更新、定时日志输出等场景。使用此函数需要在OnPluginStart阶段进行注册 。
SDK提供了proxywasm包,用于获取请求的各种信息:
proxywasm.SetTickPeriodMilliSeconds(millSec uint32) //用于注册定时任务
proxywasm.GetHttpRequestHeaders() ([][2]string, error) //用于获取http headers,返回header map
proxywasm.GetHttpRequestHeader(key string) (string, error) //获取某个header key的值
proxywasm.GetHttpRequestBody(start, maxSize int) ([]byte, error) //获取POST数据
proxywasm.SendHttpResponse(statusCode uint32, headers [][2]string, body []byte) error //主动发送返回 ,可以用于阻断攻击请求。
proxywasm.DispatchHttpCall //非阻塞的http请求,可以用于请求API,获取规则集。
更多的示例插件代码,读者可以进行参考。
四、插件编译
根据SDK文档,编译需要使用者安装go以及TinyGo,编译过程需要使用TinyGo的wasi方式进行编译。
tinygo build -o ./envoy.wasm -target=wasi ./main.go
漏洞悬赏计划:涂鸦智能安全响应中心(https://src.tuya.com)欢迎白帽子来探索。
招聘内推计划:涵盖安全开发、安全测试、代码审计、安全合规等所有方面的岗位,简历投递sec@tuya.com,请注明来源。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)