基于openresty的安全网关开发记录
字数 854 2025-08-29 08:31:35
OpenResty安全网关开发教学文档
一、前言
本教学文档基于OpenResty开发安全网关的实现过程,主要功能包括:
- 防御Shiro反序列化攻击
- 防御自动化工具请求
- 保护网站前端代码
- 防御多源代理请求
- 防止敏感信息泄漏
二、环境搭建
2.1 OpenResty安装
- 配置yum源:
wget https://openresty.org/package/centos/openresty.repo
sudo mv openresty.repo /etc/yum.repos.d/
sudo yum check-update
- 安装OpenResty:
sudo yum install -y openresty
sudo yum install -y openresty-resty
- 验证安装:
openresty -V
- 文件结构:
/usr/local/openresty/
├── bin
├── COPYRIGHT
├── luajit
├── lualib
├── nginx
├── openssl111
├── pcre
├── site
└── zlib
- 常用命令:
openresty -t # 检查配置文件
openresty -s reload # 重新加载配置
openresty -s reopen # 重新打开日志文件
openresty -s stop # 快速停止
openresty -s quit # 优雅停止
2.2 Node.js安装
- 安装Node.js:
curl -fsSL https://rpm.nodesource.com/setup_17.x | bash -
sudo yum install -y nodejs
- 安装Babel相关包:
npm -g install @babel/generator@7.16.8
npm -g install @babel/parser@7.16.8
npm -g install @babel/traverse@7.16.8
npm -g install @babel/types@7.16.8
- 设置环境变量:
export NODE_PATH=/usr/lib/node_modules
三、功能实现
3.1 防御Shiro反序列化攻击
实现原理
- 对服务器下发的Cookie进行加密
- 对客户端传来的Cookie进行解密
- 对不认识的Cookie直接丢弃
关键代码
- 请求处理:
function reqCookieParse()
if ShiroProtect then
local userCookie = ngx.var.cookie_x9i7RDYX23
if not userCookie then
log('0-cookie 无cookie', '')
elseif #userCookie < 32 then
log('1-cookie 不符合要求', userCookie)
say_html()
else
local result = xpcall(dencrypT, errPrint, userCookie)
if not result then
log('3-cookie 无法解密', userCookie)
say_html()
else
local originCookie = StrToTable(dencrypT(userCookie))
ngx.req.set_header('Cookie', transTable(originCookie))
log('4-cookie 解密成功', userCookie)
end
end
end
end
- 响应处理:
function respCookieEncrypt()
if ShiroProtect then
local value = ngx.resp.get_headers()["Set-Cookie"]
if value then
local encryptedCookie = cookieKey.."="..encrypT(TableToStr(value))
ngx.header["Set-Cookie"] = encryptedCookie
log('5-cookie 加密成功', encryptedCookie)
end
end
end
- 日志记录:
function log(data, ruletag)
if Attacklog then
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()
local line = realIp.." ["..time.."] \""..method.." "..servername..url.."\" \""..ruletag.."\" \""..ua.."\" \""..data.."\"\n"
local filename = logpath..'/'..servername.."_"..ngx.today().."_sec.log"
write(filename, line)
end
end
3.2 防御自动化工具请求
实现原理
- 首次请求无Cookie时返回302跳转至JS生成页面
- JS代码收集环境数据并生成Cookie
- 后续请求验证Cookie有效性
关键代码
- 请求处理:
function toolsInfoSpider()
if ToolsProtect then
local clientCookieA = ngx.var.cookie_h0yGbdRv
local clientCookieB = ngx.var.cookie_kQpFHdoh
if not (clientCookieA and clientCookieB) then
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)
say_html()
else
local result2 = dencrypT(clientCookieB, clientCookieA)
local _, e = string.find(result2, '0')
if e ~= nil then
log('3-tools 工具请求', result2)
say_html()
else
log('0-tools 工具验证通过', '')
end
end
end
end
end
- JS检测代码:
// webdriver.js
function get_webdriver() {
try {
return !0 === _navigator.webdriver ? 0 : +!window.document.documentElement.getAttribute('webdriver')
} catch(e) {
return 1
}
}
function get_awvs() {
for(var e=['SimpleDOMXSSClass','MarvinHooks','MarvinPageExplorer','HashDOMXSSClass'],t=e.length,r=window.$hook$,n=0;n<t;n++)
if(window[e[n]]&&r) return 0;
return 1
}
function get_appscan() {
for(var e=['appScanSendReplacement','appScanOnReadyStateChangeReplacement','appScanLoadHandler','appScanSetPageLoaded'],t=e.length,r=0;r<t;r++)
if(window[e[r]]) return 0;
return 1
}
function get_info(arr){
arr = '' + get_webdriver() + get_awvs() + get_appscan();
return arr;
}
function setCookie(cname, date) {
var d = new Date();
d.setTime(d.getTime() + (1*24*60*60*1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + '=' + date + '; ' + expires + '; Path=/';
}
function aesEncrypt(word, tt) {
let key = CryptoJS.enc.Utf8.parse(tt);
const iv = CryptoJS.enc.Utf8.parse('ABCDEF1234123412');
let srcs = CryptoJS.enc.Utf8.parse(word);
let encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.ciphertext.toString().toUpperCase();
}
tt = '000' + tt;
setCookie('h0yGbdRv', tt);
setCookie('kQpFHdoh', aesEncrypt(get_info(arr), tt));
3.3 保护网站前端代码
实现原理
- 检测请求的JS文件
- 使用Babel对JS代码进行混淆
- 返回混淆后的代码
关键代码
- 检测JS文件:
function jsExtDetect()
if JsProtect then
local ext = string.match(ngx.var.uri, ".+%.(%w+)$")
if ext == 'js' then
JsConfuse = true
end
end
end
- 混淆JS代码:
function jsConfuse()
if JsConfuse then
local originBody = ngx.arg[1]
if #originBody > 200 then
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
3.4 防御多源代理请求
实现原理
- 使用fingerprintjs生成浏览器指纹
- 将指纹信息存入Cookie
- 验证请求的指纹一致性
关键代码
- 指纹生成:
function finalCookie(){
arr.push(get_info())
let fp = new Fingerprint();
arr.push(fp.get());
return arr
}
setCookie('kQpFHdoh', aesEncrypt(finalCookie(), tt));
- 指纹验证:
function toolsInfoSpider()
-- ...
local srs = split(result2, ',')
local _, e = string.find(srs[1], '0')
if e ~= nil then
log('4-tools 工具请求', result2)
say_html()
else
log('0-tools 工具验证通过, 记录浏览器指纹', '', srs[2])
end
-- ...
end
3.5 防止敏感信息泄漏
实现原理
- 在响应体过滤阶段处理
- 使用正则匹配敏感信息
- 替换为星号
关键代码
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
四、项目结构
.
├── 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 # 处理发来的请求
├── resty/ # OpenResty库文件
├── rsp_body.lua # 处理返回包体内容
├── rsp_header.lua # 处理返回包头内容
├── tableXstring.lua # table与string转换
└── whiteList.lua # 白名单相关
五、总结
5.1 功能特点
- 业务层面防护,降低原始站点设计缺陷风险
- 无需对已上线系统进行二次开发
- 基于Nginx扩展性,灵活处理请求和响应
5.2 注意事项
- JS安全是关键点,需要加强混淆强度
- 存在Cookie可被重放使用的问题
- 部分处理逻辑可能返回500状态码
5.3 后续规划
- 增强JS混淆能力和反逆向能力
- 防御XSS、SQL、XXE等传统Web攻击
- 加入图形化日志分析功能
- 将日志接入Splunk、ELK等数据处理平台