基于openresty的安全网关开发记录
字数 854 2025-08-29 08:31:35

OpenResty安全网关开发教学文档

一、前言

本教学文档基于OpenResty开发安全网关的实现过程,主要功能包括:

  • 防御Shiro反序列化攻击
  • 防御自动化工具请求
  • 保护网站前端代码
  • 防御多源代理请求
  • 防止敏感信息泄漏

二、环境搭建

2.1 OpenResty安装

  1. 配置yum源:
wget https://openresty.org/package/centos/openresty.repo
sudo mv openresty.repo /etc/yum.repos.d/
sudo yum check-update
  1. 安装OpenResty:
sudo yum install -y openresty
sudo yum install -y openresty-resty
  1. 验证安装:
openresty -V
  1. 文件结构:
/usr/local/openresty/
├── bin
├── COPYRIGHT
├── luajit
├── lualib
├── nginx
├── openssl111
├── pcre
├── site
└── zlib
  1. 常用命令:
openresty -t # 检查配置文件
openresty -s reload # 重新加载配置
openresty -s reopen # 重新打开日志文件
openresty -s stop # 快速停止
openresty -s quit # 优雅停止

2.2 Node.js安装

  1. 安装Node.js:
curl -fsSL https://rpm.nodesource.com/setup_17.x | bash -
sudo yum install -y nodejs
  1. 安装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
  1. 设置环境变量:
export NODE_PATH=/usr/lib/node_modules

三、功能实现

3.1 防御Shiro反序列化攻击

实现原理

  1. 对服务器下发的Cookie进行加密
  2. 对客户端传来的Cookie进行解密
  3. 对不认识的Cookie直接丢弃

关键代码

  1. 请求处理:
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
  1. 响应处理:
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
  1. 日志记录:
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 防御自动化工具请求

实现原理

  1. 首次请求无Cookie时返回302跳转至JS生成页面
  2. JS代码收集环境数据并生成Cookie
  3. 后续请求验证Cookie有效性

关键代码

  1. 请求处理:
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
  1. 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 保护网站前端代码

实现原理

  1. 检测请求的JS文件
  2. 使用Babel对JS代码进行混淆
  3. 返回混淆后的代码

关键代码

  1. 检测JS文件:
function jsExtDetect()
    if JsProtect then
        local ext = string.match(ngx.var.uri, ".+%.(%w+)$")
        if ext == 'js' then
            JsConfuse = true
        end
    end
end
  1. 混淆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 防御多源代理请求

实现原理

  1. 使用fingerprintjs生成浏览器指纹
  2. 将指纹信息存入Cookie
  3. 验证请求的指纹一致性

关键代码

  1. 指纹生成:
function finalCookie(){
    arr.push(get_info())
    let fp = new Fingerprint();
    arr.push(fp.get());
    return arr
}
setCookie('kQpFHdoh', aesEncrypt(finalCookie(), tt));
  1. 指纹验证:
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 防止敏感信息泄漏

实现原理

  1. 在响应体过滤阶段处理
  2. 使用正则匹配敏感信息
  3. 替换为星号

关键代码

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 功能特点

  1. 业务层面防护,降低原始站点设计缺陷风险
  2. 无需对已上线系统进行二次开发
  3. 基于Nginx扩展性,灵活处理请求和响应

5.2 注意事项

  1. JS安全是关键点,需要加强混淆强度
  2. 存在Cookie可被重放使用的问题
  3. 部分处理逻辑可能返回500状态码

5.3 后续规划

  1. 增强JS混淆能力和反逆向能力
  2. 防御XSS、SQL、XXE等传统Web攻击
  3. 加入图形化日志分析功能
  4. 将日志接入Splunk、ELK等数据处理平台
OpenResty安全网关开发教学文档 一、前言 本教学文档基于OpenResty开发安全网关的实现过程,主要功能包括: 防御Shiro反序列化攻击 防御自动化工具请求 保护网站前端代码 防御多源代理请求 防止敏感信息泄漏 二、环境搭建 2.1 OpenResty安装 配置yum源: 安装OpenResty: 验证安装: 文件结构: 常用命令: 2.2 Node.js安装 安装Node.js: 安装Babel相关包: 设置环境变量: 三、功能实现 3.1 防御Shiro反序列化攻击 实现原理 对服务器下发的Cookie进行加密 对客户端传来的Cookie进行解密 对不认识的Cookie直接丢弃 关键代码 请求处理: 响应处理: 日志记录: 3.2 防御自动化工具请求 实现原理 首次请求无Cookie时返回302跳转至JS生成页面 JS代码收集环境数据并生成Cookie 后续请求验证Cookie有效性 关键代码 请求处理: JS检测代码: 3.3 保护网站前端代码 实现原理 检测请求的JS文件 使用Babel对JS代码进行混淆 返回混淆后的代码 关键代码 检测JS文件: 混淆JS代码: 3.4 防御多源代理请求 实现原理 使用fingerprintjs生成浏览器指纹 将指纹信息存入Cookie 验证请求的指纹一致性 关键代码 指纹生成: 指纹验证: 3.5 防止敏感信息泄漏 实现原理 在响应体过滤阶段处理 使用正则匹配敏感信息 替换为星号 关键代码 四、项目结构 五、总结 5.1 功能特点 业务层面防护,降低原始站点设计缺陷风险 无需对已上线系统进行二次开发 基于Nginx扩展性,灵活处理请求和响应 5.2 注意事项 JS安全是关键点,需要加强混淆强度 存在Cookie可被重放使用的问题 部分处理逻辑可能返回500状态码 5.3 后续规划 增强JS混淆能力和反逆向能力 防御XSS、SQL、XXE等传统Web攻击 加入图形化日志分析功能 将日志接入Splunk、ELK等数据处理平台