From f5f44f2e0345235f2577203dffcd415e17feed7c Mon Sep 17 00:00:00 2001
From: CoCo ainrm- <137618329@qq.com>
Date: Fri, 11 Feb 2022 17:06:16 +0800
Subject: [PATCH] Add files via upload
---
403.lua | 9 ++++
aes.lua | 36 +++++++++++++
b64.lua | 86 ++++++++++++++++++++++++++++++
config.lua | 51 ++++++++++++++++++
fileio.lua | 21 ++++++++
init.lua | 135 +++++++++++++++++++++++++++++++++++++++++++++++
log.lua | 41 ++++++++++++++
randomStr.lua | 14 +++++
readme.md | 76 ++++++++++++++++++++++++++
req.lua | 3 ++
rsp_body.lua | 2 +
rsp_header.lua | 2 +
tableXstring.lua | 73 +++++++++++++++++++++++++
whiteList.lua | 9 ++++
14 files changed, 558 insertions(+)
create mode 100644 403.lua
create mode 100644 aes.lua
create mode 100644 b64.lua
create mode 100644 config.lua
create mode 100644 fileio.lua
create mode 100644 init.lua
create mode 100644 log.lua
create mode 100644 randomStr.lua
create mode 100644 readme.md
create mode 100644 req.lua
create mode 100644 rsp_body.lua
create mode 100644 rsp_header.lua
create mode 100644 tableXstring.lua
create mode 100644 whiteList.lua
diff --git a/403.lua b/403.lua
new file mode 100644
index 0000000..d4855a6
--- /dev/null
+++ b/403.lua
@@ -0,0 +1,9 @@
+function emptyPrint()
+end
+
+function say_html()
+ ngx.header.content_type = "text/html"
+ ngx.status = ngx.HTTP_FORBIDDEN
+ ngx.say(html)
+ ngx.exit(ngx.status)
+end
\ No newline at end of file
diff --git a/aes.lua b/aes.lua
new file mode 100644
index 0000000..5ac6346
--- /dev/null
+++ b/aes.lua
@@ -0,0 +1,36 @@
+local aes = require "resty.aes"
+local str = require "resty.string"
+local iv = 'ABCDEF1234123412'
+
+
+-- 需要自己写一个函数将16进制转2进制
+function hex2bin(hexstr)
+ local str = ""
+ for i = 1, string.len(hexstr) - 1, 2 do
+ local doublebytestr = string.sub(hexstr, i, i+1);
+ local n = tonumber(doublebytestr, 16);
+ if 0 == n then
+ str = str .. '\00'
+ else
+ str = str .. string.format("%c", n)
+ end
+ end
+ return str
+end
+
+-- 加密函数,返回16进制
+function encrypT(content, key)
+ local aes_128_cbc_with_iv = assert(aes:new(key, nil, aes.cipher(128,"cbc"), {iv=iv}))
+ local encrypted = aes_128_cbc_with_iv:encrypt(content)
+ return str.to_hex(encrypted)
+end
+
+-- 解密函数 返回解密字符串
+function dencrypT(content, key)
+ local aes_128_cbc_with_iv = assert(aes:new(key, nil, aes.cipher(128,"cbc"), {iv=iv}))
+ local dencrypted = aes_128_cbc_with_iv:decrypt(hex2bin(content))
+ return dencrypted
+end
+
+--ngx.say(encrypt('123456'))
+--ngx.say(dencrypt('32e29ba66134e3d8f2c149a2b93006c7'))
\ No newline at end of file
diff --git a/b64.lua b/b64.lua
new file mode 100644
index 0000000..a9329f3
--- /dev/null
+++ b/b64.lua
@@ -0,0 +1,86 @@
+function encodeBase64(source_str)
+ local b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+ local s64 = ''
+ local str = source_str
+
+ while #str > 0 do
+ local bytes_num = 0
+ local buf = 0
+
+ for byte_cnt=1,3 do
+ buf = (buf * 256)
+ if #str > 0 then
+ buf = buf + string.byte(str, 1, 1)
+ str = string.sub(str, 2)
+ bytes_num = bytes_num + 1
+ end
+ end
+
+ for group_cnt=1,(bytes_num+1) do
+ local b64char = math.fmod(math.floor(buf/262144), 64) + 1
+ s64 = s64 .. string.sub(b64chars, b64char, b64char)
+ buf = buf * 64
+ end
+
+ for fill_cnt=1,(3-bytes_num) do
+ s64 = s64 .. '='
+ end
+ end
+
+ return s64
+end
+
+function decodeBase64(str64)
+ local b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+ local temp={}
+ for i=1,64 do
+ temp[string.sub(b64chars,i,i)] = i
+ end
+ temp['=']=0
+ local str=""
+ for i=1,#str64,4 do
+ if i>#str64 then
+ break
+ end
+ local data = 0
+ local str_count=0
+ for j=0,3 do
+ local str1=string.sub(str64,i+j,i+j)
+ if not temp[str1] then
+ return
+ end
+ if temp[str1] < 1 then
+ data = data * 64
+ else
+ data = data * 64 + temp[str1]-1
+ str_count = str_count + 1
+ end
+ end
+ for j=16,0,-8 do
+ if str_count > 0 then
+ str=str..string.char(math.floor(data/math.pow(2,j)))
+ data=math.fmod(data,math.pow(2,j))
+ str_count = str_count - 1
+ end
+ end
+ end
+
+ local last = tonumber(string.byte(str, string.len(str), string.len(str)))
+ if last == 0 then
+ str = string.sub(str, 1, string.len(str) - 1)
+ end
+ return str
+end
+
+
+--url解码
+local function decodeURI(s)
+ s = string.gsub(s, '%%(%x%x)', function(h) return string.char(tonumber(h, 16)) end)
+ return s
+end
+
+--url加码
+local function encodeURI(s)
+ s = string.gsub(s, "([^%w%.%- ])", function(c) return string.format("%%%02X", string.byte(c)) end)
+ return string.gsub(s, " ", "+")
+end
\ No newline at end of file
diff --git a/config.lua b/config.lua
new file mode 100644
index 0000000..2566b7f
--- /dev/null
+++ b/config.lua
@@ -0,0 +1,51 @@
+attacklog = "off"
+shiroProtect = "off"
+toolsProtect = "off"
+jsProtect = "off"
+sensitiveProtect = "on"
+
+
+logdir = "/gate/log" -- log文件夹需要有写权限
+aesKey = '1231231231234567' -- 16位
+cookieA = 'h0yGbdRv'
+cookieB = 'kQpFHdoh'
+cookieC = 'aLoFjX4v'
+cookieD = 'x9i7RDYX23'
+jsPath = 'zE48AHvK/index.html'
+whiteExt = {'js', 'css', 'png', 'jpg'}
+
+
+
+html=[[
+
+
+网站防火墙
+
+
+
+
+
+
+
+
+
+
网站防火墙
+
+
您的请求带有不合法参数,已被网站管理员设置拦截!
+
可能原因:您提交的内容包含危险的攻击请求
+
如何解决:
+
- 1)检查提交内容;
+- 2)如网站托管,请联系空间提供商;
+- 3)普通网站访客,请联系网站管理员;
+
+
+
+
+]]
+
diff --git a/fileio.lua b/fileio.lua
new file mode 100644
index 0000000..ed5094c
--- /dev/null
+++ b/fileio.lua
@@ -0,0 +1,21 @@
+function readfile(path)
+ local file = io.open(path, "r")
+ if file then
+ local content = file:read("*a")
+ io.close(file)
+ return content
+ end
+ return nil
+end
+
+function writefile(path, content, mode)
+ mode = mode or "w+b"
+ local file = io.open(path, mode)
+ if file then
+ if file:write(content) == nil then return false end
+ io.close(file)
+ return true
+ else
+ return false
+ end
+end
\ No newline at end of file
diff --git a/init.lua b/init.lua
new file mode 100644
index 0000000..599feb4
--- /dev/null
+++ b/init.lua
@@ -0,0 +1,135 @@
+require 'config'
+require 'b64'
+require 'aes'
+require 'log'
+require '403'
+require 'tableXstring'
+require 'fileio'
+require 'randomStr'
+require 'whiteList'
+
+local optionIsOn = function (options) return options == "on" and true or false end
+ToolsProtect = optionIsOn(toolsProtect)
+ShiroProtect = optionIsOn(shiroProtect)
+JsProtect = optionIsOn(jsProtect)
+JsConfuse = false
+SensitiveProtect = optionIsOn(sensitiveProtect)
+
+
+-- cookie加密
+function reqCookieParse()
+ if ShiroProtect then
+ local userCookieX9 = ngx.var.cookie_x9i7RDYX23
+ if not userCookieX9 then -- 没有cookie
+ log('0-cookie 无cookie', '')
+ ngx.req.set_header('Cookie', '') -- 移除其他cookie
+ elseif #userCookieX9 < 32 then -- 判断cookie长度
+ log('1-cookie 不符合要求', userCookieX9)
+ ngx.say('4')
+ say_html()
+ else --有cookie
+ local result = xpcall(dencrypT, emptyPrint, userCookieX9, aesKey)
+ if not result then --解密失败
+ log('2-cookie 无法解密', userCookieX9)
+ ngx.say('5')
+ say_html()
+ else --解密成功
+ local originCookie = StrToTable(dencrypT(userCookieX9, aesKey))
+ ngx.req.set_header('Cookie', transTable(originCookie))
+ log('3-cookie 解密成功', userCookieX9)
+ end
+ end
+ end
+end
+
+function respCookieEncrypt()
+ if ShiroProtect then
+ local value = ngx.resp.get_headers()["Set-Cookie"]
+ if value then
+ local encryptedCookie = cookieD.."="..encrypT(TableToStr(value), aesKey)
+ ngx.header["Set-Cookie"] = encryptedCookie
+ log('4-cookie 加密成功',encryptedCookie)
+ end
+ end
+end
+
+-- reload机制
+function toolsInfoSpider()
+ if ToolsProtect and not whiteExtCheck() then
+ local clientCookieA = ngx.var.cookie_h0yGbdRv
+ local clientCookieB = ngx.var.cookie_kQpFHdoh
+ if not (clientCookieA and clientCookieB) then --没有cookieA进入reload,302至html生成cookie后再请求原地址
+ local ip = 'xxx'
+ local finalPath = 'http://'..ip..'/'..jsPath..'?origin='..encodeBase64(ngx.var.request_uri)
+ log('1-tools 无cookieA/B', '')
+ ngx.redirect(finalPath, 302)
+ else
+ local result = xpcall(dencrypT, emptyPrint, clientCookieB, clientCookieA)
+ if not result then
+ log('2-tools 解密失败', clientCookieA..', '..clientCookieB)
+ ngx.say('1')
+ say_html() -- 解密失败
+ else-- 可以解密,提取数据
+ local result2 = dencrypT(clientCookieB, clientCookieA)
+ if #result2 < 1 then
+ log('3-tools 解密失败', result2)
+ else
+ local srs = split(result2, ',')
+ local _,e = string.find(srs[1], '0')
+ if e ~= nil then
+ log('4-tools 工具请求', result2)
+ ngx.say('2')
+ say_html()
+ else
+ log('0-tools 工具验证通过, 记录浏览器指纹', '', srs[2])
+ end
+ end
+ end
+ end
+ end
+end
+
+-- js文件混淆
+function jsExtDetect()
+ if JsProtect then
+ local ext = string.match(ngx.var.uri, ".+%.(%w+)$")
+ if ext == 'js' then -- 加入检查,js文件是否存在
+ JsConfuse = true
+ end
+ end
+end
+
+function jsConfuse()
+ if JsConfuse then
+ local originBody = ngx.arg[1]
+ if #originBody > 200 then -- 筛选空js
+ local s = getRandom(8)
+ local path = '/tmp/'..s
+ writefile(path, originBody, 'w+')
+ local t = io.popen('export NODE_PATH=/usr/lib/node_modules && node /gate/node/js_confuse.js '..path)
+ local a = t:read("*all")
+ ngx.arg[1] = a
+ os.execute('rm -f '..path)
+ end
+ JsConfuse = false
+ end
+end
+
+-- 响应包过滤
+function dateReplace()
+ if SensitiveProtect then
+ local replaceTelephone = string.gsub(ngx.arg[1], "[1][3,4,5,7,8]%d%d%d%d%d%d%d%d%d", "******")
+ ngx.arg[1] = replaceTelephone
+ end
+end
+
+
+
+
+
+
+
+
+
+
+
diff --git a/log.lua b/log.lua
new file mode 100644
index 0000000..f5ba796
--- /dev/null
+++ b/log.lua
@@ -0,0 +1,41 @@
+require 'config'
+
+
+local optionIsOn = function (options) return options == "on" and true or false end
+local Attacklog = optionIsOn(attacklog)
+local logpath = logdir
+
+local function getClientIp()
+ IP = ngx.var.remote_addr
+ if IP == nil then
+ IP = "unknown"
+ end
+ return IP
+end
+
+local function write(logfile,msg)
+ local fd = io.open(logfile,"ab")
+ if fd == nil then return end
+ fd:write(msg)
+ fd:flush()
+ fd:close()
+end
+
+function log(data, ruletag, fp)
+ if Attacklog then
+ local fingerprint = fp or ''
+ local realIp = getClientIp()
+ local method = ngx.var.request_method
+ local ua = ngx.var.http_user_agent
+ local servername=ngx.var.server_name
+ local url = ngx.var.request_uri
+ local time=ngx.localtime()
+ if ua then
+ line = realIp.." ["..time.."] \""..method.." "..servername..url.."\" \""..ruletag.."\" \""..ua.."\" \""..data.."\" \""..fingerprint.."\"\n"
+ else
+ line = realIp.." ["..time.."] \""..method.." "..servername..url.."\" \""..ruletag.."\" - \""..data.."\" \""..fingerprint.."\"\n"
+ end
+ local filename = logpath..'/'..servername.."_"..ngx.today().."_sec.log"
+ write(filename,line)
+ end
+end
\ No newline at end of file
diff --git a/randomStr.lua b/randomStr.lua
new file mode 100644
index 0000000..fd0ce14
--- /dev/null
+++ b/randomStr.lua
@@ -0,0 +1,14 @@
+math.randomseed(os.time())
+
+function getRandom(n)
+ local t = {
+ "0","1","2","3","4","5","6","7","8","9",
+ "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
+ "A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
+ }
+ local s = ""
+ for i =1, n do
+ s = s .. t[math.random(#t)]
+ end;
+ return 't'..s
+end
\ No newline at end of file
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..33f09d4
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,76 @@
+### 0x01 简介
+
+这是一个基于openresty的安全网关,使用提供的指令和接口将lua代码插入至nginx处理http请求的不同阶段来实现包过滤效果,相较于传统waf该网关侧重于业务方面的防护,特点之一是具备防自动化工具请求能力,但目前还只是一个demo项目,具体的开发过程记录在[这里](https://ainrm.cn/2022/safegate.html),后续可能会增加如文中所规划的功能
+
+
+
+### 0x02 文件说明
+
+核心文件是`init.lua`,包含了具体的处理逻辑,`config.lua`为配置文件,决定是否启用某些功能,然后在nginx中配置`access_by_lua_file`、`header_filter_by_lua_file`、`body_filter_by_lua_file`来调用具体函数
+
+```bash
+.
+├── 403.lua # 403页面
+├── aes.lua # aes加解密
+├── b64.lua # base64转码
+├── config.lua # 配置文件
+├── fileio.lua # 文件io相关
+├── init.lua # 处理请求的具体逻辑
+├── log.lua # 日志相关
+├── log # 保存日志的路径
+│ ├── error.log
+│ └── localhost_2022-02-11_sec.log
+├── nginx
+│ ├── nginx.conf # 示例配置
+│ └── zE48AHvK # 下发cookie相关文件
+│ ├── crypto-js.min.js
+│ ├── index.html
+│ ├── info.html
+│ ├── info.js
+│ ├── jump.js
+│ └── webdriver.js
+├── node # babel混淆规则
+│ └── js_confuse.js
+├── randomStr.lua # 产生随机字符串
+├── req.lua # 处理发来的请求,由access_by_lua_file调用
+├── resty -> /usr/local/openresty/lualib/resty # 软链接过来的库文件
+├── rsp_body.lua # 处理返回包体内容,由body_filter_by_lua_file调用
+├── rsp_header.lua # 处理返回包头内容,header_filter_by_lua_file调用
+├── tableXstring.lua # table与string转换
+└── whiteList.lua # 白名单相关
+```
+
+### 0x03 使用说明
+
+首先在http块中引入lua文件:
+
+- lua_package_path
+- lua_shared_dict
+- init_by_lua_file
+
+然后在server块中引用具体lua文件:
+
+- access_by_lua_file
+- header_filter_by_lua_block
+- body_filter_by_lua_file
+
+示例:
+
+```lua
+http {
+ lua_package_path "/gate/?.lua";
+ lua_shared_dict limit 10m;
+ init_by_lua_file /gate/init.lua;
+
+ server {
+ location /test {
+ access_by_lua_file /gate/req.lua;
+ proxy_pass http://127.0.0.1:8000/req;
+ proxy_connect_timeout 2s;
+ header_filter_by_lua_file /gate/rsp_header.lua;
+ body_filter_by_lua_file /gate/rsp_body.lua;
+ }
+ }
+}
+```
+
diff --git a/req.lua b/req.lua
new file mode 100644
index 0000000..aa8ee09
--- /dev/null
+++ b/req.lua
@@ -0,0 +1,3 @@
+toolsInfoSpider()
+reqCookieParse()
+jsExtDetect()
\ No newline at end of file
diff --git a/rsp_body.lua b/rsp_body.lua
new file mode 100644
index 0000000..e43b051
--- /dev/null
+++ b/rsp_body.lua
@@ -0,0 +1,2 @@
+jsConfuse()
+dateReplace()
\ No newline at end of file
diff --git a/rsp_header.lua b/rsp_header.lua
new file mode 100644
index 0000000..36b5d7b
--- /dev/null
+++ b/rsp_header.lua
@@ -0,0 +1,2 @@
+ngx.header.content_length = nil
+respCookieEncrypt()
diff --git a/tableXstring.lua b/tableXstring.lua
new file mode 100644
index 0000000..3b5b700
--- /dev/null
+++ b/tableXstring.lua
@@ -0,0 +1,73 @@
+
+function ToStringEx(value)
+ if type(value)=='table' then
+ return TableToStr(value)
+ elseif type(value)=='string' then
+ return "\'"..value.."\'"
+ else
+ return tostring(value)
+ end
+end
+
+
+function TableToStr(t)
+ if t == nil then return "" end
+ local retstr= "{"
+
+ local i = 1
+ for key,value in pairs(t) do
+ local signal = ","
+ if i==1 then
+ signal = ""
+ end
+
+ if key == i then
+ retstr = retstr..signal..ToStringEx(value)
+ else
+ if type(key)=='number' or type(key) == 'string' then
+ retstr = retstr..signal..'['..ToStringEx(key).."]="..ToStringEx(value)
+ else
+ if type(key)=='userdata' then
+ retstr = retstr..signal.."*s"..TableToStr(getmetatable(key)).."*e".."="..ToStringEx(value)
+ else
+ retstr = retstr..signal..key.."="..ToStringEx(value)
+ end
+ end
+ end
+
+ i = i+1
+ end
+
+ retstr = retstr.."}"
+ return retstr
+end
+
+
+function StrToTable(str)
+ if str == nil or type(str) ~= "string" then
+ return
+ end
+
+ return loadstring("return " .. str)()
+end
+
+function transTable(xxx)
+ local yyy = {}
+ for i=#xxx,1,-1 do
+ if #yyy ~= 0 then
+ yyy = xxx[i].."; "..yyy
+ else
+ yyy = xxx[i]
+ end
+ end
+ return yyy
+end
+
+
+function split( str,reps )
+ local resultStrList = {}
+ string.gsub(str,'[^'..reps..']+',function ( w )
+ table.insert(resultStrList,w)
+ end)
+ return resultStrList
+end
\ No newline at end of file
diff --git a/whiteList.lua b/whiteList.lua
new file mode 100644
index 0000000..d88a65b
--- /dev/null
+++ b/whiteList.lua
@@ -0,0 +1,9 @@
+function whiteExtCheck()
+ local reqExt = string.match(ngx.var.uri, ".+%.(%w+)$") --js
+ for _,e in ipairs(whiteExt) do -- js、css、png
+ if reqExt == e then -- 在白名单里
+ return true
+ end
+ end
+ return false
+end
\ No newline at end of file