卓航论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 1046|回复: 6
打印 上一主题 下一主题

中、小企业如何自建免费的云WAF

[复制链接]
[未点亮至尊红钻]发帖数量不足10篇 [未点亮至尊黄钻]威望不足10点 [未点亮至尊蓝钻]在线时间不足10小时 [未点亮至尊绿钻]贡献度不足10点 [未点亮至尊紫钻]金币不足100个 [未点亮至尊粉钻]精华贴数不足10贴 [未点亮至尊黑钻]活跃不足8个
 等级: 
 级别: 注册会员
 UID:  152   [未点亮普号显示]钻石不足3个
 阁 分: 52
 阁 望: 6
 阁 献: 9
 活 跃: 0
 发 贴: 7 (0)
 阁 币: 24  
性 别: I'm Boy
阅读权限: 20
在线时长: 0 小时
注册时间: 2011-1-6
最后登录: 2013-6-14
go
楼主
发表于 2016-10-19 16:43:28 |只看该作者 |倒序浏览
本帖发表于 2016-10-19 16:43:28...阅读 1047 人...加油,亲爱的楼主:浮生若成梦?╮彼

WEB攻击是十几年来黑客攻击的主流技术,国内的大厂们早已把WAF作为安全基础设施的标配,市面上也有很多安全厂商提供了WAF产品或云WAF服务。
对于没有自己安全团队,却又饱受sql注入、xss、cc等WEB攻击的中、小企业,对WAF的需求也是非常迫切的。
目前获取WAF的途径有以下几种:

   
  • 购买安全厂商的WAF产品
       
  • 使用云waf服务,将自己域名的DNS服务器设为云waf厂商提供的,或者将需要接入云waf的域名cname过去
       
  • 或者从网上找一些免费或开源的waf使用
       
  • 自制WAF

    对于收入不错的公司使用收费的产品或服务无可厚非,但是有些公司会因预算、数据私密性(云waf可以捕获所有流量的请求和响应的内容)等原因,不打算使用收费的产品或服务。
    这种情况下只能使用免费的waf了,或者按业务需求自制一款适合自己的云WAF。
    笔者会通过本文详细阐述如何用一周的时间自制一款简单易用的云WAF,以下为已经完成的云WAF的文档及github地址:
    项目站点:https://waf.xsec.io/
    Github地址:https://github.com/xsec-lab
    云WAF架构设计
    物理架构
    根据业务场景或需求的不同,WAF也有不同的架构,比如:
    以模块的形式集成到本地WEB容器中,如mod_security、Naxsi
    反向代理模式
    硬件产品WAF
    Agent+检测云模式
    本文实现的云WAF采用了反向代理模式的架构。

    waf可以部署一台或者多台服务器中,如果业务规模较大,一台waf的性能已经无法满足业务需求,可以在waf前面使用LVS、haproxy、nginx等搭建负载均衡,通过VIP将前端的请求分发到后端的waf中。
    后端的app server为提供正常业务的web  server,用户的请求会先经过waf进行过滤,如果是恶意的攻击请求,则会在waf层面阻断,如果是正常的请求才会转发到后端服务器。
    逻辑架构

    x-waf由x-waf本身以及web管理后台x-waf-admin组成,其中:
    x-waf基于openresty + lua开发
    waf管理后台:采用golang + xorm + macrom开发的,支持二进制的形式部署
    x-waf的实现
    笔者呆过的2家公司都自主研发过云waf,架构一开始就设计成了适合大规模业务系统的,安装、部署、运维都比较复杂,不方便小企业快速部署,所以在参考了github中现有的开源的几款waf后,重新设计了一款轻量级的。
    x-waf的执行流程
    openresty默认不会执行lua脚本,需要在nginx.conf中进行配置,如下所示:
    # 指定lua文件的查找路径
  • lua_package_path "/usr/local/openresty/nginx/conf/x-waf/?.lua;/usr/local/lib/lua/?.lua;;";
  • # 定义2个lua shared dict变量分别为limit和badGuys,分配的内存大小为100M
  • lua_shared_dict limit 100m; lua_shared_dict badGuys 100m;
  • # 开启lua代码缓存功能
  • lua_code_cache on;
  • # 让nginx在init阶段执行init.lua文件中的lua代码
  • init_by_lua_file /usr/local/openresty/nginx/conf/x-waf/init.lua;
  • # 让nginx在每个http请求的access阶段执行access.lua文件中的lua代码
  • access_by_lua_file /usr/local/openresty/nginx/conf/x-waf/access.lua; [/ol]
    openresty在init阶段会根据配置文件指定的位置导入json格式的规则到全局的lua  table中,不同的规则放在不同的table中,以加快正则匹配的速度
    waf = require("waf") wafwaf_rules = waf.load_rules() [/ol]
    waf.load_rules会根据配置文件中指定的路径加载读取所有json格式的规则,并加载到不同的table中,然后封装一个get_rule的函数,方便在每个http进来时可以直接从lua  table中获取对应类型的规则:
    local _M = { RULES = {} }
  • function _M.load_rules() _M.RULES = util.get_rules(config.config_rule_dir)
  • return _M.RULES end
  • function _M.get_rule(rule_file_name) ngx.log(ngx.DEBUG, rule_file_name)
  • return _M.RULES[rule_file_name] end [/ol]
    util.get_rules会将指定文件中的规则按规则名保存到lua table中供waf.get_rule函数在需要的时候获取规则:
    function _M.get_rules(rules_path)
  • local rule_files = _M.get_rule_files(rules_path)
  • if rule_files == {} then return nil end
  • for rule_name, rule_file in pairs(rule_files) do local t_rule = {}
  • local file_rule_name = io.open(rule_file)
  • local json_rules = file_rule_name:read("*a") file_rule_name:close()
  • local table_rules = cjson.decode(json_rules)
  • if table_rules ~= nil then
  • for _, table_name in pairs(table_rules) do table.insert(t_rule, table_name["RuleItem"]) end end _M.RULE_TABLE[rule_name] = t_rule end
  • return(_M.RULE_TABLE) end [/ol]
    每个请求进来时,waf会按ip白名单、ip黑名单、user_agent、是否cc攻击、url白名单、url黑名单、是否cc攻击、cookies、get和post参数的顺序进行过滤,如果匹配到其中任一种就会进行相应的处理(输出提示或跳转后),之后就不会继续判断是否为其他类型的攻击了。
    function _M.check()
  • if _M.white_ip_check() then elseif _M.black_ip_check() then elseif _M.user_agent_attack_check() then elseif _M.white_url_check() then elseif _M.url_attack_check() then elseif _M.cc_attack_check() then elseif _M.cookie_attack_check() then elseif _M.url_args_attack_check() then elseif _M.post_attack_check() then else return end
  • end [/ol]
    对每个请求的每种参数类型的判断都是先获取到参数内容,然后再循环与该类参数的正则规则进行匹配,如果匹配到则认为是攻击请求,以下为对post参数进行过滤的函数:
    -- deny post function _M.post_attack_check()
  • if config.config_post_check == "on" then ngx.req.read_body() local POST_RULES = _M.get_rule('post.rule')
  • for _, rule in pairs(POST_RULES) do local POST_ARGS = ngx.req.get_post_args() or {}
  • for _, v in pairs(POST_ARGS) do local post_data = "" if type(v) == "table" then post_data = table.concat(v, ", ")
  • else post_data = v
  • end if rule ~= "" and rulematch(post_data, rule, "jo") then util.log_record('Deny_USER_POST_DATA', post_data, "-", rule)
  • if config.config_waf_enable == "on" then util.waf_output()
  • return true end end end end end return false
  • end [/ol]
    waf管理后台x-waf-admin的实现
    waf的规则是以json格式的字符串,人工维护起来容量出错,另外云waf会有多台waf同时工作,如果人工做waf的后端主机的管理、规则同步与主机配置的同步等这些运维工作的话,非常容易出错或者疏漏,所以有必要提供一个自动化管理、同步配置的管理后台。
    waf管理后台的功能需求
    方便部署,启动前只需做简单的配置即可,第一次启动时,x-waf-admin会在mysql中生成默认管理员以及默认的waf规则;
    用户管理,支持管理员账户的增、改、删;
    waf规则管理,支持waf规则的增、改、删除以及策略同步到所有waf服务器的功能;
    后端站点管理,支持接入waf的站点的增、改、删除,以及单独同步或全部同步接入的后端站点的功能。
    程序结构
    为了方便部署,x-waf-admin没有采用python、php等需要搭建运行环境或依赖第3方包的语言,而是用可以直接编译为可执行文件的go语言写的,具体的技术栈为go语言  + macron + xorm。
    项目结构如下:
    hartnett at hartnett-notebook in /data/code/golang/src/xsec-waf/x-waf-admin  (master●) $ tree -L 2 ├── conf │ └── app.ini ├── models │ ├── models.go │ ├──  rules.go │ ├── site.go │ └── user.go ├── modules │ └── util ├── public │ ├── css  ├── README.md ├── routers │ ├── admin.go │ ├── index.go │ ├── rules.go │ ├──  site.go │ └── user.go ├── server ├── server.go ├── setting │ └── setting.go └──  templates
    conf为配置文件目录
    models目录下为orm文件
    modules为功能模块组件
    public和templates分别为静态资源及模板文件所在的目录
    routers目录下的为各路由文件
    setting目录下为配置文件处理的文件
    server.go为程序入口
    规则管理功能的实现
    用户管理、后端站点管理与规则管理功能的实现大同小异,都是类似flask、martini、tornado、django等MTV  WEB框架的应用,为了减少篇幅,本文只写后端站点管理功能如何实现,完整的代码请参见github。
    后端站点管理的ORM实现
    先用xorm定义site的struct,然后再提供增、改、删、查看等方法,这些方法会被routers模块中的site文件调用:
    // 因篇幅太长,省略部分代码,详细代码请查看github
  • // debuglevel: debug, info, notice, warn, error, crit, alert, emerg
  • // ssl: on, off
  • type Site struct { Id int64 SiteName string `xorm:"unique"` Port int BackendAddr []string Ssl string `xorm:"varchar(10) notnull default 'off'"` DebugLevel string `xorm:"varchar(10) notnull default 'error'"` LastChange time.Time `xorm:"updated"` Version int `xorm:"version"` // 乐观锁
  • }
  • func ListSite() (sites []Site, err error) { sites = make([]Site, 0) err = Engine.Find(&sites)
  • log.Println(err, sites)
  • return sites, err }
  • func NewSite(siteName string, Port int, BackendAddr []string, SSL string, DebugLevel string) (err error) {
  • if SSL == "" { SSL = "off" }
  • if DebugLevel == "" { DebugLevel = "error" } _, err = Engine.Insert(&Site{SiteName: siteName, Port: Port, BackendAddr: BackendAddr, Ssl: SSL, DebugLevel: DebugLevel})
  • return err } [/ol]
    后端站点管理的路由实现
    首先import相应的包,然后分别编写以下处理器:
    增加站点的get与post请求的处理器(NewSite、DoNewSite)
    修改站点的get与post请求的处理器(EditSite、DoEditSite)
    根据ID删除站点的get处理器(DelSite)
    同步站点配置的处理器(SyncSite)
    同步站点配置的API的处理器以及根据ID同步站点配置的API的处理器(SyncSiteApi、SyncSiteById)
    // 因篇幅太长,省略部分代码,详细代码请查看github
  • func NewSite(ctx *macaron.Context, sess session.Store, x csrf.CSRF) { if sess.Get("uid") != "" { ctx.Data["csrf_token"] = x.GetToken() ctx.HTML(200, "newSite") } else { ctx.Redirect("/login/") } }
  • func DoNewSite(ctx *macaron.Context, sess session.Store) {
  • if sess.Get("uid") != nil {
  • log.Println(sess.Get("uid")) siteName := ctx.Req.Form.Get("sitename") port := ctx.Req.Form.Get("port") Port, _ := strconv.Atoi(port) backaddr := ctx.Req.Form.Get("backendaddr") backendaddr := strings.Split(backaddr, "\r\n") BackendAddr := make([]string, 0)
  • for _, v := range backendaddr {
  • if v == "" {
  • continue } v = strings.TrimSpace(v) BackendAddr = append(BackendAddr, v) } ssl := ctx.Req.Form.Get("ssl") debugLevel := ctx.Req.Form.Get("debuglevel")
  • log.Println(siteName, BackendAddr, ssl, debugLevel) models.NewSite(siteName, Port, BackendAddr, ssl, debugLevel) ctx.Redirect("/admin/site/list/") } else { ctx.Redirect("/login/") } } [/ol]
    model的初始化
    大家一定注意到了,虽然用了mysql,但是没有要求在使用前手工去导入建表或插入初始化值的sql脚本,这是为神马呢?
    因为我们使用了ORM,ORM会帮我们自动完成上面所说的操作,如下代码所示:
    // 因篇幅太长,省略部分代码,详细代码请查看github
  • var ( Engine *xorm.Engine err error )
  • func init() {
  • // 从conf/app.ini获取数据库的配置信息 sec := setting.Cfg.Section("database")
  • // 连接数据库 Engine, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8", sec.Key("USER").String(), sec.Key("PASSWD").String(), sec.Key("HOST").String(), sec.Key("NAME").String()))
  • if err != nil {
  • log.Panicf("Faild to connect to database, err:%v", err) }
  • // 新建site、user和rules表 Engine.Sync2(new(Site)) Engine.Sync2(new(User)) Engine.Sync2(new(Rules))
  • // 如果user表为空,则新建一个默认账户, ret, err := Engine.IsTableEmpty(new(User))
  • if err == nil && ret {
  • log.Printf("create new user:%v, password:%v\n", "admin", "x@xsec.io") NewUser("admin", "x@xsec.io") }
  • // 如果规则为空,则插入默认的初始化规则 ret, err = Engine.IsTableEmpty(new(Rules))
  • if err == nil && ret {
  • log.Println("Insert default waf rules") Engine.Exec(DefaultRules) } } [/ol]
    配置路由
    当ORM、路由处理相关的代码写完后就可以在程序入口中配置路由了,将URL与路由处理的控制器对应起来,如下所示:
    // 因篇幅太长,省略部分代码,详细代码请查看github
  • m.Group("/admin", func() {
  • m.Get("/index/", routers.Admin)
  • m.Group("/site/", func() {
  • m.Get("", routers.Admin)
  • m.Get("/list/", routers.Admin)
  • m.Get("/new/", routers.NewSite)
  • m.Post("/new/", csrf.Validate, routers.DoNewSite)
  • m.Get("/edit/:id", routers.EditSite)
  • m.Post("/edit/:id", csrf.Validate, routers.DoEditSite)
  • m.Get("/del/:id", routers.DelSite)
  • m.Get("/sync/", routers.SyncSite)
  • m.Get("/sync/:id", routers.SyncSiteById)
  • m.Get("/json/", routers.SiteJSON)
  • }) })
  • m.Group("/api", func() {
  • m.Get("/site/sync/", routers.SyncSiteApi)
  • m.Get("/rule/sync/", routers.SyncRuleApi) })
  • log.Printf("xsec waf admin %s", setting.AppVer)
  • log.Printf("Run mode %s", strings.Title(macaron.Env))
  • log.Printf("Server is running on %s", fmt.Sprintf("0.0.0.0:%v", setting.HTTPPort))
  • log.Println(http.ListenAndServe(fmt.Sprintf("0.0.0.0:%v", setting.HTTPPort), m)) [/ol]
    互动问题
    从前有座山,山里有个庙,庙里有个灰帽子小明同学,他有次通过一些不可描述的手段,得到了某个网站的反弹Shell,虽是root权限,但利用方法不稳定。
    此时小明发现服务器的内网网卡上跑着一个有root权限的redis,但加了密码,只见小明虎躯一震,顿时有了思路:留个webshell,以后通过webshell来执行redis反弹shell的exp。
    但当他看完nginx的配置后又菊花一紧,因为这个站点只跑了lua的web应用.。
  • 分享到: QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏0 支持支持0 反对反对0

    使用道具 举报

    [至尊红钻2级]发帖数量≥100篇 [未点亮至尊黄钻]威望不足10点 [未点亮至尊蓝钻]在线时间不足10小时 [未点亮至尊绿钻]贡献度不足10点 [未点亮至尊紫钻]金币不足100个 [未点亮至尊粉钻]精华贴数不足10贴 [未点亮至尊黑钻]活跃不足8个
     等级: 
     级别: 注册会员
     UID:  33   [未点亮普号显示]钻石不足3个
     阁 分: 127
     阁 望: 6
     阁 献: 3
     活 跃: 0
     发 贴: 111 (0)
     阁 币: 1  
    性 别: I'm Boy
    阅读权限: 20
    在线时长: 3 小时
    注册时间: 2011-1-6
    最后登录: 2016-10-21
    沙发
    发表于 2016-10-19 16:44:38 |只看该作者
    本回复帖发表于 2016-10-19 16:44:38,感谢nzwennet2011对本帖的认真回复,你的回复是对楼主莫大的鼓舞
    没看完~~~~~~ 先顶,好同志

    使用道具 举报

    [至尊红钻2级]发帖数量≥100篇 [未点亮至尊黄钻]威望不足10点 [未点亮至尊蓝钻]在线时间不足10小时 [至尊绿钻1级]贡献度≥10点 [未点亮至尊紫钻]金币不足100个 [未点亮至尊粉钻]精华贴数不足10贴 [未点亮至尊黑钻]活跃不足8个
     等级: 
     级别: 注册会员
     UID:  69   [未点亮普号显示]钻石不足3个
     阁 分: 163
     阁 望: 4
     阁 献: 10
     活 跃: 0
     发 贴: 140 (0)
     阁 币: 5  
    性 别: I'm Boy
    阅读权限: 20
    在线时长: 5 小时
    注册时间: 2011-1-6
    最后登录: 2016-10-21
    板凳
    发表于 2016-10-19 20:19:36 |只看该作者
    本回复帖发表于 2016-10-19 20:19:36,感谢xmvista对本帖的认真回复,你的回复是对楼主莫大的鼓舞
    相当不错,感谢无私分享精神!

    使用道具 举报

    [至尊红钻2级]发帖数量≥100篇 [至尊黄钻1级]威望≥10点 [未点亮至尊蓝钻]在线时间不足10小时 [未点亮至尊绿钻]贡献度不足10点 [未点亮至尊紫钻]金币不足100个 [未点亮至尊粉钻]精华贴数不足10贴 [未点亮至尊黑钻]活跃不足8个
     等级: 
     级别: 注册会员
     UID:  48   [未点亮普号显示]钻石不足3个
     阁 分: 147
     阁 望: 10
     阁 献: 4
     活 跃: 0
     发 贴: 114 (0)
     阁 币: 9  
    性 别: I'm 火星人!
    阅读权限: 20
    在线时长: 0 小时
    注册时间: 2011-1-6
    最后登录: 2016-10-21
    地板
    发表于 2016-10-19 22:55:31 |只看该作者
    本回复帖发表于 2016-10-19 22:55:31,感谢liyunde对本帖的认真回复,你的回复是对楼主莫大的鼓舞
    有道理。。。

    使用道具 举报

    [至尊红钻2级]发帖数量≥100篇 [未点亮至尊黄钻]威望不足10点 [未点亮至尊蓝钻]在线时间不足10小时 [至尊绿钻1级]贡献度≥10点 [未点亮至尊紫钻]金币不足100个 [未点亮至尊粉钻]精华贴数不足10贴 [未点亮至尊黑钻]活跃不足8个
     等级: 
     级别: 中级会员
     UID:  100   [未点亮普号显示]钻石不足3个
     阁 分: 237
     阁 望: 4
     阁 献: 10
     活 跃: 0
     发 贴: 215 (0)
     阁 币: 4  
    性 别: I'm 火星人!
    阅读权限: 30
    在线时长: 2 小时
    注册时间: 2011-1-6
    最后登录: 2016-10-21
    5#
    发表于 2016-10-19 23:14:24 |只看该作者
    本回复帖发表于 2016-10-19 23:14:24,感谢uy8jj5tm7tu对本帖的认真回复,你的回复是对楼主莫大的鼓舞
    学习了,谢谢分享、、、

    使用道具 举报

    [至尊红钻2级]发帖数量≥100篇 [未点亮至尊黄钻]威望不足10点 [未点亮至尊蓝钻]在线时间不足10小时 [未点亮至尊绿钻]贡献度不足10点 [未点亮至尊紫钻]金币不足100个 [未点亮至尊粉钻]精华贴数不足10贴 [未点亮至尊黑钻]活跃不足8个
     等级: 
     级别: 注册会员
     UID:  26   [未点亮普号显示]钻石不足3个
     阁 分: 185
     阁 望: 4
     阁 献: 9
     活 跃: 0
     发 贴: 158 (0)
     阁 币: 10  
    性 别: I'm 火星人!
    阅读权限: 20
    在线时长: 2 小时
    注册时间: 2011-1-6
    最后登录: 2016-10-21
    6#
    发表于 2016-10-20 09:59:31 |只看该作者
    本回复帖发表于 2016-10-20 09:59:31,感谢sulee对本帖的认真回复,你的回复是对楼主莫大的鼓舞
    帮你顶下哈!!

    使用道具 举报

    [至尊红钻2级]发帖数量≥100篇 [未点亮至尊黄钻]威望不足10点 [未点亮至尊蓝钻]在线时间不足10小时 [未点亮至尊绿钻]贡献度不足10点 [未点亮至尊紫钻]金币不足100个 [未点亮至尊粉钻]精华贴数不足10贴 [未点亮至尊黑钻]活跃不足8个
     等级: 
     级别: 注册会员
     UID:  44   [未点亮普号显示]钻石不足3个
     阁 分: 127
     阁 望: 5
     阁 献: 2
     活 跃: 0
     发 贴: 105 (0)
     阁 币: 10  
    性 别: I'm 火星人!
    阅读权限: 20
    在线时长: 4 小时
    注册时间: 2011-1-6
    最后登录: 2016-10-21
    7#
    发表于 2016-10-20 10:00:24 |只看该作者
    本回复帖发表于 2016-10-20 10:00:24,感谢shlinpin对本帖的认真回复,你的回复是对楼主莫大的鼓舞
    真是 收益 匪浅

    使用道具 举报

    高级模式
    B Color Image Link Quote Code Smilies
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    1、请认真发帖,禁止回复纯表情,纯数字等无意义的内容!帖子内容不要太简单!
    2、提倡文明上网,净化网络环境!抵制低俗不良违法有害信息。
    3、每个贴内连续回复请勿多余3贴,每个版面回复请勿多余10贴!
    4、如果你对主帖作者的帖子不屑一顾的话,请勿回帖。谢谢合作!

    手机版| 百度搜索:vkee.pw

    2012-2015 卓航旗下 GMT+8, 2024-6-16 08:30 , Processed in 0.495584 second(s), 31 queries . Powered by Discuz! X3.2  

    禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.如遇版权问题,请及时联系站长(QQ:5213513)

    今天是: | 本站已经安全运行: //这个地方可以改颜色

    快速回复 返回顶部 返回列表