Add files via upload

This commit is contained in:
CoCo ainrm-
2022-02-11 17:06:16 +08:00
committed by GitHub
commit f5f44f2e03
14 changed files with 558 additions and 0 deletions

9
403.lua Normal file
View File

@@ -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

36
aes.lua Normal file
View File

@@ -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'))

86
b64.lua Normal file
View File

@@ -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

51
config.lua Normal file
View File

@@ -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=[[
<html xmlns="http://www.w3.org/1999/xhtml"><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>网站防火墙</title>
<style>
p {
line-height:20px;
}
ul{ list-style-type:none;}
li{ list-style-type:none;}
</style>
</head>
<body style=" padding:0; margin:0; font:14px/1.5 Microsoft Yahei, 宋体,sans-serif; color:#555;">
<div style="margin: 0 auto; width:1000px; padding-top:70px; overflow:hidden;">
<div style="width:600px; float:left;">
<div style=" height:40px; line-height:40px; color:#fff; font-size:16px; overflow:hidden; background:#6bb3f6; padding-left:20px;">网站防火墙 </div>
<div style="border:1px dashed #cdcece; border-top:none; font-size:14px; background:#fff; color:#555; line-height:24px; height:220px; padding:20px 20px 0 20px; overflow-y:auto;background:#f3f7f9;">
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600; color:#fc4f03;">您的请求带有不合法参数,已被网站管理员设置拦截!</span></p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">可能原因:您提交的内容包含危险的攻击请求</p>
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:1; text-indent:0px;">如何解决:</p>
<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">1检查提交内容</li>
<li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">2如网站托管请联系空间提供商</li>
<li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">3普通网站访客请联系网站管理员</li></ul>
</div>
</div>
</div>
</body></html>
]]

21
fileio.lua Normal file
View File

@@ -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

135
init.lua Normal file
View File

@@ -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进入reload302至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

41
log.lua Normal file
View File

@@ -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

14
randomStr.lua Normal file
View File

@@ -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

76
readme.md Normal file
View File

@@ -0,0 +1,76 @@
### 0x01 简介
这是一个基于openresty的安全网关使用提供的指令和接口将lua代码插入至nginx处理http请求的不同阶段来实现包过滤效果相较于传统waf该网关侧重于业务方面的防护特点之一是具备防自动化工具请求能力但目前还只是一个demo项目具体的开发过程记录在[这里](https://ainrm.cn/2022/safegate.html),后续可能会增加如文中所规划的功能
![ainrm@20220211170151](./tu/ainrm@20220211170151.webp)
### 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;
}
}
}
```

3
req.lua Normal file
View File

@@ -0,0 +1,3 @@
toolsInfoSpider()
reqCookieParse()
jsExtDetect()

2
rsp_body.lua Normal file
View File

@@ -0,0 +1,2 @@
jsConfuse()
dateReplace()

2
rsp_header.lua Normal file
View File

@@ -0,0 +1,2 @@
ngx.header.content_length = nil
respCookieEncrypt()

73
tableXstring.lua Normal file
View File

@@ -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

9
whiteList.lua Normal file
View File

@@ -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