freeBuf
主站

分類

漏洞 工具 極客 Web安全 系統安全 網絡安全 無線安全 設備/客戶端安全 數據安全 安全管理 企業安全 工控安全

特色

頭條 人物志 活動 視頻 觀點 招聘 報告 資訊 區塊鏈安全 標準與合規 容器安全 公開課

官方公眾號企業安全新浪微博

FreeBuf.COM網絡安全行業門戶,每日發布專業的安全資訊、技術剖析。

FreeBuf+小程序

FreeBuf+小程序

WAF代碼剖析之初識openresty
2020-10-07 15:39:22

為什么會有這一系列的文章?

自從這幾年信息安全的大力發展,信息安全的建設是逐步發展起來,作為甲方安全工程師,一個人的安全部,使用開源的WAF部署防御攻擊,仿佛是件很平常的事情,但是開源的壞處就是沒有人能夠及時提供技術支撐,出現問題就只能自己維護,我相信維護WAF的代碼和規則是一件很耗費精力的事情,還不如用商用的香。如果沒安全預算的朋友,不妨跟我一起走向WAF開發的世界。

之前對WAF維護和加載新功能,只是單純在openresty的access階段進行處理,沒有整體去了解整個流程是怎么運作的,接下來的文章我會針對于openresty處理階段,WAF代碼編寫等內容進行說明,可能有些內容會顯得啰嗦,因為這是我對于技術細節思考的結果。

為什么openresty能夠成為WAF開發的核心選擇?對于業務系統來說,一般放在最外層就是nginx,作為負載均衡,轉發流量,并且官方也提供modsecurity規則用于WAF防御攻擊,但是用過的人也知道,代碼是用C寫的,可寫性差并且防御規則不容易維護,最重要的一點是性能損耗,我之前寫過一篇WAF性能測試報告,雖然可能存在場景偏差,但是nginx性能不如openresty這是可以明顯看得到。而且有一點重要的是,openresty本質還是nginx,但是加上了lua腳本語言進行嵌入,性能處理提高和能夠編寫復雜處理場景。

開源社區流行的主要是這些WAF:jxwaf,openstar,ngx_lua_waf排名不分先后),我這邊主要會以jxwaf的代碼進行剖析,當然也會找其他WAF的代碼進行思路碰撞和學習。

廢話不多說,let‘s go~~

openresty處理流程

openresty有11個處理流程階段,如下圖所示

具體階段作用域和功能作用如下表,來源地址:https://openresty-reference.readthedocs.io/en/latest/Directives/

階段作用域(nginx.conf)功能作用
init_by_lua*http初始化 nginx 和預加載 lua(nginx 啟動和 reload 時執行)
init_worker_by_lua*http每個工作進程(worker_processes)被創建時執行,用于啟動一些定時任務,
比如心跳檢查,后端服務的健康檢查,定時拉取服務器配置等;
ssl_certificate_by_lua*server對HTTPS請求的處理
set_by_lua*server, server if, location, location if流程分支處理判斷變量初始化
rewrite_by_lua*http, server, location, location if轉發、重定向、緩存等功能
access_by_lua*http, server, location, location if內容處理(WAF規則處理)
content_by_lua*location, location if內容生成,相當于response
balancer_by_lua*upstream負載均衡
header_filter_by_lua*http, server, location, location if對響應頭(headers)進行處理
body_filter_by_lua*http, server, location, location if對響應體(body)進行處理
log_by_lua*http, server, location, location if日志記錄

為了讓大家更加直觀認識這些階段,我拿jxwaf的nginx配置文件進行說明,當然不懂的朋友就要下去自我補習。

http {
    include       mime.types;
    default_type  application/octet-stream;

    client_body_buffer_size  100m;
    client_max_body_size 10m;
    sendfile        on;
    #tcp_nopush     on;
	resolver  114.114.114.114;
  resolver_timeout 5s;
    #keepalive_timeout  0;
    keepalive_timeout  65;
    lua_ssl_trusted_certificate  /etc/pki/tls/certs/ca-bundle.crt;
    lua_ssl_verify_depth 3;
lua_shared_dict limit_req 100m;
lua_shared_dict limit_req_count 100m;
lua_shared_dict limit_attack_ip 100m;
lua_shared_dict limit_bot 100m;
lua_shared_dict waf_common_conf 100m;
lua_shared_dict black_attack_ip 100m;
init_by_lua_file /opt/jxwaf/lualib/resty/jxwaf/init.lua;
init_worker_by_lua_file /opt/jxwaf/lualib/resty/jxwaf/init_worker.lua;
rewrite_by_lua_file /opt/jxwaf/lualib/resty/jxwaf/rewrite.lua;
access_by_lua_file /opt/jxwaf/lualib/resty/jxwaf/access.lua;
header_filter_by_lua_file /opt/jxwaf/lualib/resty/jxwaf/header_filter.lua;
#body_filter_by_lua_file /opt/jxwaf/lualib/resty/jxwaf/body_filter.lua;
log_by_lua_file /opt/jxwaf/lualib/resty/jxwaf/log.lua;
rewrite_by_lua_no_postpone on;
    #gzip  on;
	upstream jxwaf {
	server www.jxwaf.com;
  balancer_by_lua_file /opt/jxwaf/lualib/resty/jxwaf/balancer.lua;
}
lua_code_cache on;
    server {
        listen       80;
        server_name  localhost;
        set $proxy_pass_https_flag "false";
        location / {
            #root   html;
           # index  index.html index.htm;
          if ($proxy_pass_https_flag = "true"){
            proxy_pass https://jxwaf;
          }
          if ($proxy_pass_https_flag = "false"){
            proxy_pass http://jxwaf;
          }
           proxy_set_header Host  $http_host;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }

    server {
        listen       443 ssl;
        server_name  localhost;
        ssl_certificate      full_chain.pem;
        ssl_certificate_key  private.key;
        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;
        ssl_session_tickets off;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:ECDHE-RSA-AES128-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA128:DHE-RSA-AES128-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA128:ECDHE-RSA-AES128-SHA384:ECDHE-RSA-AES128-SHA128:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA128:DHE-RSA-AES128-SHA128:DHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA384:AES128-GCM-SHA128:AES128-SHA128:AES128-SHA128:AES128-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!R";
        ssl_prefer_server_ciphers  on;
        ssl_certificate_by_lua_file /opt/jxwaf/lualib/resty/jxwaf/ssl.lua;
        set $proxy_pass_https_flag "false";
        location / {
            root   html;
            index  index.html index.htm;
          if ($proxy_pass_https_flag = "true"){
            proxy_pass https://jxwaf;
          }
          if ($proxy_pass_https_flag = "false"){
            proxy_pass http://jxwaf;
          }
            proxy_set_header Host  $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }

}

對openresty自帶屬性進行說明:

lua_ssl_trusted_certificate/etc/pki/tls/certs/ca-bundle.crt;

CA bundle是包含根證書和中間證書的文件。使用temsock:sslhandshake方法以PEM格式指定具有受信任CA證書的文件路徑,該證書路徑用于驗證SSL / TLS服務器的證書。

lua_ssl_verify_depth3;

設置服務器證書鏈中的驗證深度。證書鏈就是Root CA簽發二級Intermediate CA,二級Intermediate CA可以簽發三級Intermediate CA,也可以直接簽發用戶證書。從Root CA到用戶證書之間構成了一個信任鏈:信任Root CA,就應該信任它所信任的二級Intermediate CA,從而就應該信任三級Intermediate CA直至信任用戶證書。驗證深度設置為3就是說如果驗證Root CA下面的第三級的證書是否可信。

lua_shared_dict limit_req 100m;

聲明一塊共享內存區域,里面放著是變量字典數據,在nginx啟動的時候,就是讀取字典變量的數據。這樣的好處是比如說有很多檢測規則,SQL注入、XSS、命令注入等規則可以預先放進內存里面,openresty就可以直接去內存數據,而不是進來一個數據包就open一下文件然后close一下,減少對磁盤讀寫操作。

rewrite_by_lua_no_postpone on;

是否讓“rewrite_by_lua”在rewrite階段的最后執行,默認值是off, 即“rewrite_by_lua”里的Lua代碼將在其他Nginx rewrite功能模塊之后執行。

lua_code_cache on;

參考這里:https://linux.ci/201.html,在開發調試過程中,可以把它給關閉掉,因為可以實時更新,不需要重啟nginx,在生產就要開啟,不然性能損耗就非常大。

數了一下openresty11個執行階段jxwaf就用了8個階段,接下來講述這8個階段的代碼將會是本系列的重點。

安裝配置

根據目前jxwaf需要的openresty版本為1.15.8.3,使用centos7系統進行安裝:

yum install -y readline-devel pcre pcre-devel openssl openssl-devel gcc curl GeoIP-devel  wget 
wget https://openresty.org/download/openresty-1.15.8.3.tar.gz
tar -xvf openresty-1.15.8.3.tar.gz
cd openresty-1.15.8.3
./configure -j2
make -j2
make install

或者單純windows系統就可以下載Windows的openresty版本

https://openresty.org/download/openresty-1.15.8.3-win64.zip

開發WAF是采用LUA編程語言,LUA還屬于小眾語言,沒有像pycharm這種完整的IDE,LUA IDE的選擇可以看自己需要,可以使用vscode+插件進行開發。

lua插件,用于語法高亮和語法說明

1602050219_5f7d58abd89d29714ff9f.png!small

Lua Debug用于調試和運行lua代碼

1602050257_5f7d58d1b2c8f499ffa1b.png!small

按F5啟動調試,可以看到lua運行成功的代碼

1602050318_5f7d590ee5fc1ae81b3ad.png!small

測試

nginx.conf配置文件,我把lua_code_cache給關閉,這樣做是因為在測試代碼的時候,不用reload nginx,nginx會直接熱更新,然后我在content_by_lua_file階段調用test.lua文件輸出內容

worker_processes  1;
events {
    worker_connections  1024;
}
http {
lua_code_cache off;
    server {
        location /test {
           default_type 'text/plain';
           content_by_lua_file 'D:/waf/test.lua';
        }
    }
}
#test.lua
local name = "Anonymous" ngx.say("Hello, ", name, "!") ngx.say("test")

效果如下

1602056263_5f7d7047381647bca634c.png!small

本文作者:, 屬于FreeBuf原創獎勵計劃,未經許可禁止轉載

# WAF開發 # waf
被以下專輯收錄,發現更多精彩內容
+ 收入我的專輯
評論 按時間排序

登錄/注冊后在FreeBuf發布內容哦

相關推薦
  • 0 文章數
  • 0 評論數
  • 0 關注者
登錄 / 注冊后在FreeBuf發布內容哦
收入專輯
777766香港开奖结果 小说 股票指数基金代码 今天广西快乐双彩开奖结果 快乐8玩法介绍 青海十一选五走势图 青海十一选五开奖图 甘肃快三推荐豹子 好彩1选码的最佳方法 秒速时时彩 最准7尾中特公式规律 明天涨停股票推荐骗局