中、小企業如何自建免費的雲WAF

來源:互聯網
上載者:User

概述

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;

openresty在init階段會根據設定檔指定的位置導入json格式的規則到全域的lua table中,不同的規則放在不同的table中,以加快正則匹配的速度

waf = require("waf") waf_rules = waf.load_rules()

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

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

每個請求進來時,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 els eif _M.url_args_attack_check() then elseif _M.post_attack_check() then else return end

end

對每個請求的每種參數類型的判斷都是先獲取到參數內容,然後再迴圈與該類參數的正則規則進行匹配,如果匹配到則認為是攻擊請求,以下為對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

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 ├── READM E.md ├── routers │ ├── admin.go │ ├── index.go │ ├── rules.go │ ├── site.go │ └── user.go ├── server ├── server.go ├── set ting │ └── 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) no tnull 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 }

後端網站管理的路由實現

首先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/") } }

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) } }

配置路由

當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))

互動問題

從前有座山,山裡有個廟,廟裡有個灰帽子小明同學,他有次通過一些不可描述的手段,得到了某個網站的反彈Shell,雖是root許可權,但利用方法不穩定。

此時小明發現伺服器的內網網卡上跑著一個有root許可權的redis,但加了密碼,只見小明虎軀一震,頓時有了思路:留個webshell,以後通過webshell來執行redis反彈shell的exp。

但當他看完nginx的配置後又菊花一緊,因為這個網站只跑了lua的web應用:

# 省略部分配置內容

HTTP { include mime.types; default_type application/octet-stream; lua_package_path "/data0/www/a.b.com/?. lua/?. lua;/usr/local/lib/lua/?. lua;;"; lua_code_cache off; init_by_lua_file /data0/www/a.b.com/init.lua; sendfile on; keepalive_timeout 65; server { listen 80; server_name a.b.com; location /api/ { content_by_lua_file /data0/www/a.b.com/web.lua; } } }

請聽題

▼▼▼

樹上7只猴,地上1只猴,小明如何留一個lua的webshell,請大家貼一下思路及代碼。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.