mirror of https://github.com/midoks/mdserver-web
parent
952fcc2a1e
commit
6fb8a5bbe5
@ -1,139 +0,0 @@ |
||||
-- Copyright (C) midoks |
||||
|
||||
local _M = { _VERSION = '1.0' } |
||||
local mt = { __index = _M } |
||||
local setmetatable = setmetatable |
||||
|
||||
local obf_log = require "resty.obf.log" |
||||
local tpl = require "resty.obf.tpl" |
||||
local log_fmt = obf_log.fmt |
||||
local aes = require "resty.aes" |
||||
local ffi = require "ffi" |
||||
local ffi_str = ffi.string |
||||
local sha256 = require "resty.sha256" |
||||
local resty_str = require "resty.string" |
||||
local random = require "resty.random" |
||||
|
||||
local find = string.find |
||||
local byte = string.byte |
||||
local log = ngx.log |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function _M.new(self) |
||||
local self = { |
||||
} |
||||
return setmetatable(self, mt) |
||||
end |
||||
|
||||
|
||||
function _M.to_uint8array(content) |
||||
if not content then |
||||
content = "" |
||||
end |
||||
local arr = {} |
||||
for i = 1, #content do |
||||
arr[#arr + 1] = tostring(byte(content, i)) |
||||
end |
||||
return "new Uint8Array([" .. table.concat(arr, ",") .. "])" |
||||
end |
||||
|
||||
-- 响应处理函数 |
||||
function _M.process_response() |
||||
local ctx = ngx.ctx |
||||
local chunk, eof = ngx.arg[1], ngx.arg[2] |
||||
|
||||
if not ctx.obf_buffer then |
||||
ctx.obf_buffer = {} |
||||
end |
||||
|
||||
if chunk and chunk ~= "" then |
||||
ctx.obf_buffer[#ctx.obf_buffer + 1] = chunk |
||||
ngx.arg[1] = nil |
||||
end |
||||
|
||||
if eof then |
||||
local content = table.concat(ctx.obf_buffer) |
||||
local enc, tag, iv_bin |
||||
local content_type = ngx.header.content_type or "" |
||||
local upstream_ct = ngx.var.upstream_http_content_type or "" |
||||
local cl = ngx.header["Content-Length"] or "" |
||||
local upstream_cl = ngx.var.upstream_http_content_length or "" |
||||
|
||||
-- log(ngx.ERR, log_fmt("proxy content-type: %s, upstream_content_type: %s", tostring(content_type), tostring(upstream_ct))) |
||||
-- log(ngx.ERR, log_fmt("content-length: %s, upstream_content_length: %s", tostring(cl), tostring(upstream_cl))) |
||||
-- log(ngx.ERR, log_fmt("body len=%s", tostring(#content))) |
||||
|
||||
-- log(ngx.ERR, log_fmt("body: %s", content)) |
||||
|
||||
local password = random.bytes(8, true) |
||||
log(ngx.ERR, log_fmt("enc=aes-256-gcm, pass_b64=%s", ngx.encode_base64(password))) |
||||
local cipher = aes.cipher(256, "gcm") |
||||
local a, aerr = aes:new(password, nil, cipher, aes.hash.md5, 1, 12, true) |
||||
if not a then |
||||
log(ngx.ERR, log_fmt("aes init error: %s", tostring(aerr))) |
||||
else |
||||
local key_bin = ffi_str(a._key, 32) |
||||
iv_bin = ffi_str(a._iv, 12) |
||||
local sh1 = sha256:new(); sh1:update(key_bin); local key_fpr = resty_str.to_hex(sh1:final()) |
||||
local sh2 = sha256:new(); sh2:update(iv_bin); local iv_fpr = resty_str.to_hex(sh2:final()) |
||||
log(ngx.ERR, log_fmt("key_len=%d, key_sha256=%s", 32, key_fpr)) |
||||
log(ngx.ERR, log_fmt("iv_len=%d, iv_sha256=%s", 12, iv_fpr)) |
||||
local res, eerr = a:encrypt(content) |
||||
if not res then |
||||
log(ngx.ERR, log_fmt("aes encrypt error: %s", tostring(eerr))) |
||||
else |
||||
if type(res) == "table" then |
||||
enc = res[1] |
||||
tag = res[2] |
||||
else |
||||
enc = res |
||||
end |
||||
|
||||
content = enc .. (tag or "") |
||||
|
||||
local b64_ct = ngx.encode_base64(enc:sub(1, math.min(#enc, 128))) |
||||
local b64_tag = tag and ngx.encode_base64(tag) or "" |
||||
log(ngx.ERR, log_fmt("ciphertext len=%s, tag_len=%s, b64_ct_preview=%s, b64_tag=%s", tostring(#enc), tostring(tag and #tag or 0), b64_ct, b64_tag)) |
||||
end |
||||
end |
||||
|
||||
-- local preview = content |
||||
-- if #preview > 2048 then |
||||
-- preview = preview:sub(1, 2048) |
||||
-- end |
||||
-- log(ngx.ERR, log_fmt("body preview: %s", preview)) |
||||
|
||||
-- 根据内容类型进行混淆 |
||||
if find(content_type, "text/html") then |
||||
|
||||
-- content = obfuscate_html(content) |
||||
end |
||||
|
||||
local data = _M.to_uint8array(enc or content) |
||||
local iv_data = _M.to_uint8array(iv_bin or "") |
||||
local tag_data = _M.to_uint8array(tag or "") |
||||
local key_data = _M.to_uint8array(password or "") |
||||
-- content type will be set in header_filter phase |
||||
-- ngx.arg[1] = literal |
||||
|
||||
html_data = tpl.content(data, iv_data, tag_data, key_data) |
||||
|
||||
html_data = html_data:gsub("<script(.-)>(.-)</script>", function(attrs, body) |
||||
local b = body |
||||
b = b:gsub("^%s*//[^\n\r]*", "") |
||||
b = b:gsub("\n%s*//[^\n\r]*", "\n") |
||||
b = b:gsub("([^:])%s+//[^\n\r]*", "%1") |
||||
b = b:gsub("%s+", " ") |
||||
b = b:gsub("%s*([%(%),;:%{%}%[%]%+%-%*%/%=<>])%s*", "%1") |
||||
return "<script" .. attrs .. ">" .. b .. "</script>" |
||||
end) |
||||
|
||||
ngx.arg[1] = html_data:gsub("[\r\n]+", ""):gsub(">%s+<", "><") |
||||
ctx.obf_buffer = nil |
||||
end |
||||
end |
||||
|
||||
return _M |
||||
@ -1,292 +0,0 @@ |
||||
-- Copyright (C) midoks |
||||
|
||||
local _M = { _VERSION = '1.0' } |
||||
local mt = { __index = _M } |
||||
local setmetatable = setmetatable |
||||
|
||||
local obf_log = require "resty.obf.log" |
||||
local tpl = require "resty.obf.tpl" |
||||
local log_fmt = obf_log.fmt |
||||
local aes = require "resty.aes" |
||||
local ffi = require "ffi" |
||||
local ffi_str = ffi.string |
||||
local sha256 = require "resty.sha256" |
||||
local resty_str = require "resty.string" |
||||
local random = require "resty.random" |
||||
local lrucache = require "resty.lrucache" |
||||
local obf_cache = lrucache.new(1024) |
||||
|
||||
local find = string.find |
||||
local byte = string.byte |
||||
local log = ngx.log |
||||
|
||||
|
||||
function _M.new(self) |
||||
local self = { |
||||
} |
||||
return setmetatable(self, mt) |
||||
end |
||||
|
||||
|
||||
function _M.to_uint8array(content) |
||||
if not content then |
||||
content = "" |
||||
end |
||||
local arr = {} |
||||
for i = 1, #content do |
||||
arr[#arr + 1] = tostring(byte(content, i)) |
||||
end |
||||
return "new Uint8Array([" .. table.concat(arr, ",") .. "])" |
||||
end |
||||
|
||||
-- 数据过滤 |
||||
function _M.data_filter(content) |
||||
content = content:gsub("<script(.-)>(.-)</script>", function(attrs, body) |
||||
local b = body |
||||
b = b:gsub("^%s*//[^\n\r]*", "") |
||||
b = b:gsub("\n%s*//[^\n\r]*", "\n") |
||||
b = b:gsub("([^:])%s+//[^\n\r]*", "%1") |
||||
b = b:gsub("%s+", " ") |
||||
b = b:gsub("%s*([%(%),;:%{%}%[%]%+%-%*%=<>])%s*", "%1") |
||||
return "<script" .. attrs .. ">" .. b .. "</script>" |
||||
end) |
||||
|
||||
content = content:gsub("[\r\n]+", ""):gsub(">%s+<", "><") |
||||
return content |
||||
end |
||||
|
||||
-- 随机变量名 |
||||
function _M.obf_rand(content) |
||||
local function rand_ident(len) |
||||
local head = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_" |
||||
local tail = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$" |
||||
local seed = random.bytes(16, true) .. (ngx.var.request_id or "") .. tostring(ngx.time()) .. tostring(ngx.now()) |
||||
local h = sha256:new(); h:update(seed); local digest = resty_str.to_hex(h:final()) |
||||
local r = {} |
||||
local hi = (string.byte(digest, 1) or 65) % #head + 1 |
||||
r[1] = string.sub(head, hi, hi) |
||||
local pos, di = 2, 2 |
||||
while pos <= len do |
||||
local c = string.byte(digest, di) or 97 |
||||
local ti = c % #tail + 1 |
||||
r[pos] = string.sub(tail, ti, ti) |
||||
pos = pos + 1 |
||||
di = di + 1 |
||||
if di > #digest then |
||||
h = sha256:new(); h:update(digest .. random.bytes(8, true)); digest = resty_str.to_hex(h:final()); di = 1 |
||||
end |
||||
end |
||||
return table.concat(r) |
||||
end |
||||
local ids = {"encrypted","iv_data","key","tag_data","d","u8ToBytes","evpBytesToKey","startTime","dk","decipher","ok","newDoc","endTime"} |
||||
local map = {} |
||||
for _, id in ipairs(ids) do |
||||
map[id] = rand_ident(8) |
||||
end |
||||
for k, v in pairs(map) do |
||||
local pat = "%f[%w_]" .. k .. "%f[^%w_]" |
||||
content = content:gsub(pat, v) |
||||
end |
||||
return content |
||||
end |
||||
|
||||
-- 添加混淆代码 |
||||
function _M.obf_add_data(content) |
||||
content = content:gsub("<script(.-)>(.-)</script>", function(attrs, body) |
||||
local b = body |
||||
local function rand_ident(len) |
||||
local head = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_" |
||||
local tail = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$" |
||||
local seed = random.bytes(16, true) .. (ngx.var.request_id or "") .. tostring(ngx.time()) .. tostring(ngx.now()) |
||||
local h = sha256:new(); h:update(seed); local digest = resty_str.to_hex(h:final()) |
||||
local r = {} |
||||
local hi = (string.byte(digest, 1) or 65) % #head + 1 |
||||
r[1] = string.sub(head, hi, hi) |
||||
local pos, di = 2, 2 |
||||
while pos <= len do |
||||
local c = string.byte(digest, di) or 97 |
||||
local ti = c % #tail + 1 |
||||
r[pos] = string.sub(tail, ti, ti) |
||||
pos = pos + 1 |
||||
di = di + 1 |
||||
if di > #digest then |
||||
h = sha256:new(); h:update(digest .. random.bytes(8, true)); digest = resty_str.to_hex(h:final()); di = 1 |
||||
end |
||||
end |
||||
return table.concat(r) |
||||
end |
||||
local v1 = rand_ident(8) |
||||
local v2 = rand_ident(8) |
||||
local v3 = rand_ident(8) |
||||
local f1 = rand_ident(8) |
||||
local f2 = rand_ident(8) |
||||
local tmp = rand_ident(8) |
||||
local filler = "var "..v1.."=\""..ngx.encode_base64(random.bytes(8, true)).."\";" |
||||
.."var "..v2.."=".._M.to_uint8array(random.bytes(8, true))..";" |
||||
.."var "..v3.."="..tostring(#ngx.encode_base64(random.bytes(6, true)))..";" |
||||
.."function "..f1.."(x){return x}" |
||||
.."function "..f2.."(){return "..v3.."}" |
||||
.."(function(){var "..tmp.."="..v3.."; for(var i=0;i<1;i++){"..tmp.."="..tmp.."+i}})();" |
||||
b = filler..";"..b |
||||
b = b:gsub("%s+", " ") |
||||
b = b:gsub("%s*([%(%),;:%{%}%[%]%+%-%*%=<>])%s*", "%1") |
||||
return "<script" .. attrs .. ">" .. b .. "</script>" |
||||
end) |
||||
content = content:gsub("[\r\n]+", ""):gsub(">%s+<", "><") |
||||
return content |
||||
end |
||||
|
||||
|
||||
-- 编码 |
||||
function _M.obf_encode(content) |
||||
local enc, tag, iv_bin |
||||
local content_type = ngx.header.content_type or "" |
||||
local upstream_ct = ngx.var.upstream_http_content_type or "" |
||||
local cl = ngx.header["Content-Length"] or "" |
||||
local upstream_cl = ngx.var.upstream_http_content_length or "" |
||||
|
||||
local key = random.bytes(8, true) |
||||
-- log(ngx.ERR, log_fmt("enc=aes-256-gcm, pass_b64=%s", ngx.encode_base64(password))) |
||||
local cipher = aes.cipher(256, "gcm") |
||||
local a, aerr = aes:new(key, nil, cipher, aes.hash.md5, 1, 12, true) |
||||
|
||||
if not a then |
||||
log(ngx.ERR, log_fmt("aes init error: %s", tostring(aerr))) |
||||
else |
||||
local key_bin = ffi_str(a._key, 32) |
||||
iv_bin = ffi_str(a._iv, 12) |
||||
local sh1 = sha256:new(); sh1:update(key_bin); local key_fpr = resty_str.to_hex(sh1:final()) |
||||
local sh2 = sha256:new(); sh2:update(iv_bin); local iv_fpr = resty_str.to_hex(sh2:final()) |
||||
local res, eerr = a:encrypt(content) |
||||
if not res then |
||||
log(ngx.ERR, log_fmt("aes encrypt error: %s", tostring(eerr))) |
||||
else |
||||
if type(res) == "table" then |
||||
enc = res[1] |
||||
tag = res[2] |
||||
else |
||||
enc = res |
||||
end |
||||
content = enc .. (tag or "") |
||||
end |
||||
end |
||||
|
||||
return enc or content, key, iv_bin, tag |
||||
end |
||||
|
||||
function _M.get_cache_key() |
||||
local args = ngx.req.get_uri_args() |
||||
local parts = {} |
||||
for k, v in pairs(args or {}) do |
||||
if k ~= "obf" then |
||||
local val = v |
||||
if type(val) == "table" then val = val[1] end |
||||
parts[#parts + 1] = tostring(k) .. "=" .. tostring(val) |
||||
end |
||||
end |
||||
table.sort(parts) |
||||
local q = table.concat(parts, "&") |
||||
local cache_key = (ngx.var.scheme or "") .. "://" .. (ngx.var.host or "") .. (ngx.var.uri or "") .. (q ~= "" and ("?" .. q) or "") |
||||
return cache_key |
||||
end |
||||
|
||||
-- HTML标签混淆 |
||||
function _M.obf_html() |
||||
local content_type = ngx.header.content_type or "" |
||||
local ctx = ngx.ctx |
||||
local chunk, eof = ngx.arg[1], ngx.arg[2] |
||||
local html_debug = "false" |
||||
local cache_timeout = 300 |
||||
|
||||
local args = ngx.req.get_uri_args() |
||||
obf = args and args.obf or nil |
||||
if obf == "debug" then |
||||
html_debug = "true" |
||||
end |
||||
|
||||
local var_debug = ngx.var.close_debug |
||||
if var_debug == "true" then |
||||
html_debug = "false" |
||||
end |
||||
|
||||
local var_obf_timeout = ngx.var.obf_timeout |
||||
|
||||
if var_obf_timeout then |
||||
cache_timeout = var_obf_timeout |
||||
end |
||||
-- log(ngx.ERR, log_fmt("var_obf_timeout: %s", tostring(var_obf_timeout))) |
||||
|
||||
|
||||
if not ctx.obf_buffer then |
||||
ctx.obf_buffer = {} |
||||
end |
||||
|
||||
if chunk and chunk ~= "" then |
||||
ctx.obf_buffer[#ctx.obf_buffer + 1] = chunk |
||||
ngx.arg[1] = nil |
||||
end |
||||
|
||||
if eof then |
||||
local content = table.concat(ctx.obf_buffer) |
||||
|
||||
if find(content_type, "text/html") then |
||||
|
||||
local cache_key = _M.get_cache_key()..html_debug |
||||
local cached = obf_cache and obf_cache:get(cache_key) |
||||
if cached then |
||||
ngx.arg[1] = cached |
||||
ctx.obf_buffer = nil |
||||
return |
||||
end |
||||
|
||||
-- local t0 = ngx.now() |
||||
local content,key,iv,tag = _M.obf_encode(content) |
||||
-- local t1 = ngx.now() |
||||
|
||||
local content_data = _M.to_uint8array(content or "") |
||||
local iv_data = _M.to_uint8array(iv or "") |
||||
local tag_data = _M.to_uint8array(tag or "") |
||||
local key_data = _M.to_uint8array(key or "") |
||||
|
||||
local html_data = tpl.content(content_data, iv_data, tag_data, key_data,html_debug) |
||||
|
||||
html_data = _M.obf_rand(html_data) |
||||
html_data = _M.obf_add_data(html_data) |
||||
if obf_cache then |
||||
obf_cache:set(cache_key, html_data, cache_timeout) |
||||
end |
||||
ngx.arg[1] = html_data |
||||
end |
||||
|
||||
ctx.obf_buffer = nil |
||||
end |
||||
end |
||||
|
||||
|
||||
function _M.done() |
||||
local content_type = ngx.header.content_type or "" |
||||
if find(content_type, "text/html") then |
||||
_M.obf_html() |
||||
end |
||||
end |
||||
-- 响应处理函数 |
||||
function _M.process_response() |
||||
|
||||
local var_close = ngx.var.close_close |
||||
-- log(ngx.ERR, log_fmt("var_close: %s", tostring(var_close))) |
||||
if var_close == "true" then |
||||
_M.done() |
||||
return |
||||
else |
||||
local args = ngx.req.get_uri_args() |
||||
local obf = args and args.obf or nil |
||||
if obf == "close" then |
||||
return |
||||
end |
||||
end |
||||
|
||||
_M.done() |
||||
|
||||
end |
||||
|
||||
return _M |
||||
@ -1,199 +0,0 @@ |
||||
-- Copyright (C) midoks |
||||
|
||||
local _M = { _VERSION = '1.0' } |
||||
local mt = { __index = _M } |
||||
local setmetatable = setmetatable |
||||
|
||||
local obf_log = require "resty.obf.log" |
||||
local tpl = require "resty.obf.tpl" |
||||
local log_fmt = obf_log.fmt |
||||
local aes = require "resty.aes" |
||||
local ffi = require "ffi" |
||||
local ffi_str = ffi.string |
||||
local sha256 = require "resty.sha256" |
||||
local resty_str = require "resty.string" |
||||
local random = require "resty.random" |
||||
local lrucache = require "resty.lrucache" |
||||
local util = require "resty.obf.util" |
||||
local obf_cache = lrucache.new(4096) |
||||
|
||||
local find = string.find |
||||
local byte = string.byte |
||||
local log = ngx.log |
||||
|
||||
|
||||
function _M.new(self) |
||||
local self = { |
||||
} |
||||
return setmetatable(self, mt) |
||||
end |
||||
|
||||
-- 编码 |
||||
function _M.obf_encode(content) |
||||
local enc, tag, iv_bin |
||||
local content_type = ngx.header.content_type or "" |
||||
local upstream_ct = ngx.var.upstream_http_content_type or "" |
||||
local cl = ngx.header["Content-Length"] or "" |
||||
local upstream_cl = ngx.var.upstream_http_content_length or "" |
||||
|
||||
local key = random.bytes(8, true) |
||||
-- log(ngx.ERR, log_fmt("enc=aes-256-gcm, pass_b64=%s", ngx.encode_base64(password))) |
||||
local cipher = aes.cipher(256, "gcm") |
||||
local a, aerr = aes:new(key, nil, cipher, aes.hash.md5, 1, 12, true) |
||||
|
||||
if not a then |
||||
log(ngx.ERR, log_fmt("aes init error: %s", tostring(aerr))) |
||||
else |
||||
local key_bin = ffi_str(a._key, 32) |
||||
iv_bin = ffi_str(a._iv, 12) |
||||
local res, eerr = a:encrypt(content) |
||||
if not res then |
||||
log(ngx.ERR, log_fmt("aes encrypt error: %s", tostring(eerr))) |
||||
else |
||||
if type(res) == "table" then |
||||
enc = res[1] |
||||
tag = res[2] |
||||
else |
||||
enc = res |
||||
end |
||||
content = enc .. (tag or "") |
||||
end |
||||
end |
||||
|
||||
return enc or content, key, iv_bin, tag |
||||
end |
||||
|
||||
function _M.get_cache_key() |
||||
local args = ngx.req.get_uri_args() |
||||
local parts = {} |
||||
for k, v in pairs(args or {}) do |
||||
if k ~= "obf" then |
||||
local val = v |
||||
if type(val) == "table" then val = val[1] end |
||||
parts[#parts + 1] = tostring(k) .. "=" .. tostring(val) |
||||
end |
||||
end |
||||
table.sort(parts) |
||||
local q = table.concat(parts, "&") |
||||
local cache_key = (ngx.var.scheme or "") .. "://" .. (ngx.var.host or "") .. (ngx.var.uri or "") .. (q ~= "" and ("?" .. q) or "") |
||||
return cache_key |
||||
end |
||||
|
||||
-- HTML标签混淆 |
||||
function _M.obf_html() |
||||
local content_type = ngx.header.content_type or "" |
||||
local ctx = ngx.ctx |
||||
local chunk, eof = ngx.arg[1], ngx.arg[2] |
||||
local html_debug = "false" |
||||
local cache_timeout = 600 |
||||
|
||||
local args = ngx.req.get_uri_args() |
||||
obf = args and args.obf or nil |
||||
if obf == "debug" then |
||||
html_debug = "true" |
||||
end |
||||
|
||||
local var_debug = ngx.var.close_debug |
||||
if var_debug == "true" then |
||||
html_debug = "false" |
||||
end |
||||
|
||||
local var_obf_timeout = ngx.var.obf_timeout |
||||
if var_obf_timeout then |
||||
cache_timeout = var_obf_timeout |
||||
end |
||||
-- log(ngx.ERR, log_fmt("var_obf_timeout: %s", tostring(var_obf_timeout))) |
||||
|
||||
if not ctx.obf_buffer then |
||||
ctx.obf_buffer = {} |
||||
end |
||||
|
||||
if chunk and chunk ~= "" then |
||||
ctx.obf_buffer[#ctx.obf_buffer + 1] = chunk |
||||
ngx.arg[1] = nil |
||||
end |
||||
|
||||
if eof then |
||||
local content = table.concat(ctx.obf_buffer) |
||||
|
||||
if find(content_type, "text/html") then |
||||
|
||||
local var_rand = ngx.var.obf_rand |
||||
local var_b64 = ngx.var.obf_uint8_b64 |
||||
local var_skip_large = ngx.var.obf_skip_large or "" |
||||
local var_skip_small = ngx.var.obf_skip_small or "" |
||||
local cache_key = _M.get_cache_key()..html_debug..tostring(var_rand)..tostring(var_b64)..tostring(var_skip_large)..tostring(var_skip_small) |
||||
local cached = obf_cache and obf_cache:get(cache_key) |
||||
if cached then |
||||
ngx.arg[1] = cached |
||||
ctx.obf_buffer = nil |
||||
return |
||||
end |
||||
|
||||
local var_skip = ngx.var.obf_skip_large |
||||
if var_skip then |
||||
local n = tonumber(var_skip) or 0 |
||||
if n > 0 and #content > n then |
||||
ngx.arg[1] = content |
||||
ctx.obf_buffer = nil |
||||
return |
||||
end |
||||
end |
||||
local var_skip_s = ngx.var.obf_skip_small |
||||
if var_skip_s then |
||||
local ns = tonumber(var_skip_s) or 0 |
||||
if ns > 0 and #content < ns then |
||||
ngx.arg[1] = content |
||||
ctx.obf_buffer = nil |
||||
return |
||||
end |
||||
end |
||||
|
||||
local content,key,iv,tag = _M.obf_encode(content) |
||||
|
||||
local content_data = util.to_uint8array(content or "") |
||||
local iv_data = util.to_uint8array(iv or "") |
||||
local tag_data = util.to_uint8array(tag or "") |
||||
local key_data = util.to_uint8array(key or "") |
||||
|
||||
local html_data = tpl.content(content_data, iv_data, tag_data, key_data,html_debug) |
||||
html_data = util.obf_add_data(html_data) |
||||
|
||||
if obf_cache then |
||||
obf_cache:set(cache_key, html_data, cache_timeout) |
||||
end |
||||
ngx.arg[1] = html_data |
||||
end |
||||
|
||||
ctx.obf_buffer = nil |
||||
end |
||||
end |
||||
|
||||
|
||||
function _M.done() |
||||
local content_type = ngx.header.content_type or "" |
||||
if find(content_type, "text/html") then |
||||
_M.obf_html() |
||||
end |
||||
end |
||||
-- 响应处理函数 |
||||
function _M.process_response() |
||||
local content_type = ngx.header.content_type or "" |
||||
local var_close = ngx.var.close_close |
||||
-- log(ngx.ERR, log_fmt("var_close: %s", tostring(var_close))) |
||||
if var_close == "true" then |
||||
_M.done() |
||||
return |
||||
else |
||||
local args = ngx.req.get_uri_args() |
||||
local obf = args and args.obf or nil |
||||
if obf == "close" then |
||||
return |
||||
end |
||||
end |
||||
|
||||
_M.done() |
||||
|
||||
end |
||||
|
||||
return _M |
||||
@ -1,243 +0,0 @@ |
||||
-- Copyright (C) midoks |
||||
|
||||
local _M = { _VERSION = '1.0' } |
||||
local mt = { __index = _M } |
||||
local setmetatable = setmetatable |
||||
|
||||
local obf_log = require "resty.obf.log" |
||||
local tpl = require "resty.obf.tpl" |
||||
local log_fmt = obf_log.fmt |
||||
local aes = require "resty.aes" |
||||
local ffi = require "ffi" |
||||
local ffi_str = ffi.string |
||||
local sha256 = require "resty.sha256" |
||||
local resty_str = require "resty.string" |
||||
local random = require "resty.random" |
||||
local lrucache = require "resty.lrucache" |
||||
local util = require "resty.obf.util" |
||||
local obf_cache = nil |
||||
|
||||
local find = string.find |
||||
local byte = string.byte |
||||
local log = ngx.log |
||||
|
||||
|
||||
function _M.new(self) |
||||
local self = { |
||||
} |
||||
return setmetatable(self, mt) |
||||
end |
||||
|
||||
-- 编码 |
||||
function _M.obf_encode(content) |
||||
local enc, tag, iv_bin |
||||
local content_type = ngx.header.content_type or "" |
||||
local upstream_ct = ngx.var.upstream_http_content_type or "" |
||||
local cl = ngx.header["Content-Length"] or "" |
||||
local upstream_cl = ngx.var.upstream_http_content_length or "" |
||||
|
||||
local key = random.bytes(8, true) |
||||
-- log(ngx.ERR, log_fmt("enc=aes-256-gcm, pass_b64=%s", ngx.encode_base64(password))) |
||||
local cipher = aes.cipher(256, "gcm") |
||||
local a, aerr = aes:new(key, nil, cipher, aes.hash.md5, 1, 12, true) |
||||
|
||||
if not a then |
||||
log(ngx.ERR, log_fmt("aes init error: %s", tostring(aerr))) |
||||
else |
||||
local key_bin = ffi_str(a._key, 32) |
||||
iv_bin = ffi_str(a._iv, 12) |
||||
local res, eerr = a:encrypt(content) |
||||
if not res then |
||||
log(ngx.ERR, log_fmt("aes encrypt error: %s", tostring(eerr))) |
||||
else |
||||
if type(res) == "table" then |
||||
enc = res[1] |
||||
tag = res[2] |
||||
else |
||||
enc = res |
||||
end |
||||
content = enc .. (tag or "") |
||||
end |
||||
end |
||||
|
||||
return enc or content, key, iv_bin, tag |
||||
end |
||||
|
||||
function _M.get_cache_key() |
||||
local args = ngx.req.get_uri_args() |
||||
local parts = {} |
||||
for k, v in pairs(args or {}) do |
||||
if k ~= "obf" then |
||||
local val = v |
||||
if type(val) == "table" then val = val[1] end |
||||
parts[#parts + 1] = tostring(k) .. "=" .. tostring(val) |
||||
end |
||||
end |
||||
table.sort(parts) |
||||
local q = table.concat(parts, "&") |
||||
local cache_key = (ngx.var.scheme or "") .. "://" .. (ngx.var.host or "") .. (ngx.var.uri or "") .. (q ~= "" and ("?" .. q) or "") |
||||
return cache_key |
||||
end |
||||
|
||||
-- HTML标签混淆 |
||||
function _M.obf_html() |
||||
local content_type = ngx.header.content_type or "" |
||||
local ctx = ngx.ctx |
||||
local chunk, eof = ngx.arg[1], ngx.arg[2] |
||||
local html_debug = "false" |
||||
local cache_timeout = 600 |
||||
|
||||
local args = ngx.req.get_uri_args() |
||||
obf = args and args.obf or nil |
||||
if obf == "debug" then |
||||
html_debug = "true" |
||||
end |
||||
|
||||
local var_debug = ngx.var.close_debug |
||||
if var_debug == "true" then |
||||
html_debug = "false" |
||||
end |
||||
|
||||
local var_obf_timeout = ngx.var.obf_timeout |
||||
if var_obf_timeout then |
||||
cache_timeout = var_obf_timeout |
||||
end |
||||
-- log(ngx.ERR, log_fmt("var_obf_timeout: %s", tostring(var_obf_timeout))) |
||||
|
||||
if not ctx.obf_buffer then |
||||
ctx.obf_buffer = {} |
||||
ctx.obf_size = 0 |
||||
ctx.obf_passthrough = false |
||||
end |
||||
|
||||
if chunk and chunk ~= "" then |
||||
if ctx.obf_passthrough then |
||||
ngx.arg[1] = chunk |
||||
else |
||||
ctx.obf_buffer[#ctx.obf_buffer + 1] = chunk |
||||
ctx.obf_size = ctx.obf_size + #chunk |
||||
local sl = ngx.var.obf_skip_large |
||||
if sl then |
||||
local n = tonumber(sl) or 0 |
||||
if n > 0 and ctx.obf_size > n then |
||||
ctx.obf_passthrough = true |
||||
ngx.arg[1] = table.concat(ctx.obf_buffer) |
||||
ctx.obf_buffer = nil |
||||
return |
||||
end |
||||
end |
||||
ngx.arg[1] = nil |
||||
end |
||||
end |
||||
|
||||
if eof then |
||||
if ctx.obf_passthrough then |
||||
return |
||||
end |
||||
local prof = ngx.var.obf_prof |
||||
local t_all0 = ngx.now() |
||||
local content = table.concat(ctx.obf_buffer) |
||||
|
||||
if find(content_type, "text/html", 1, true) then |
||||
|
||||
if not obf_cache then |
||||
local entries = tonumber(ngx.var.obf_cache_entries) or 1024 |
||||
obf_cache = lrucache.new(entries) |
||||
end |
||||
local var_rand = ngx.var.obf_rand |
||||
local var_b64 = ngx.var.obf_uint8_b64 |
||||
local var_skip_large = ngx.var.obf_skip_large or "" |
||||
local var_skip_small = ngx.var.obf_skip_small or "" |
||||
local cache_key = _M.get_cache_key()..html_debug..tostring(var_rand)..tostring(var_b64)..tostring(var_skip_large)..tostring(var_skip_small) |
||||
local cached = obf_cache and obf_cache:get(cache_key) |
||||
if cached then |
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof cache_hit=1 size=%d total_ms=%.2f", #cached, (ngx.now()-t_all0)*1000)) |
||||
end |
||||
ngx.arg[1] = cached |
||||
ctx.obf_buffer = nil |
||||
return |
||||
end |
||||
|
||||
local var_skip = ngx.var.obf_skip_large |
||||
if var_skip then |
||||
local n = tonumber(var_skip) or 0 |
||||
if n > 0 and #content > n then |
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof skip_large=1 size=%d total_ms=%.2f", #content, (ngx.now()-t_all0)*1000)) |
||||
end |
||||
ngx.arg[1] = content |
||||
ctx.obf_buffer = nil |
||||
return |
||||
end |
||||
end |
||||
|
||||
local t_enc0 = ngx.now() |
||||
local content,key,iv,tag = _M.obf_encode(content) |
||||
local t_enc1 = ngx.now() |
||||
|
||||
local t_ser0 = ngx.now() |
||||
local content_data = util.to_uint8array(content or "") |
||||
local iv_data = util.to_uint8array(iv or "") |
||||
local tag_data = util.to_uint8array(tag or "") |
||||
local key_data = util.to_uint8array(key or "") |
||||
local t_ser1 = ngx.now() |
||||
|
||||
local t_tpl0 = ngx.now() |
||||
local html_data = tpl.content(content_data, iv_data, tag_data, key_data,html_debug) |
||||
local t_tpl1 = ngx.now() |
||||
if not (ngx.var.obf_rand == "false") then |
||||
local t_add0 = ngx.now() |
||||
html_data = util.obf_add_data(html_data) |
||||
local t_add1 = ngx.now() |
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof add_ms=%.2f", (t_add1-t_add0)*1000)) |
||||
end |
||||
end |
||||
|
||||
local max_item = tonumber(ngx.var.obf_cache_item_max) or 0 |
||||
if obf_cache then |
||||
if max_item <= 0 or #html_data <= max_item then |
||||
obf_cache:set(cache_key, html_data, cache_timeout) |
||||
end |
||||
end |
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof size=%d enc_ms=%.2f ser_ms=%.2f tpl_ms=%.2f total_ms=%.2f", #content, (t_enc1-t_enc0)*1000, (t_ser1-t_ser0)*1000, (t_tpl1-t_tpl0)*1000, (ngx.now()-t_all0)*1000)) |
||||
end |
||||
ngx.arg[1] = html_data |
||||
end |
||||
|
||||
ctx.obf_buffer = nil |
||||
ctx.obf_size = nil |
||||
ctx.obf_passthrough = nil |
||||
end |
||||
end |
||||
|
||||
|
||||
function _M.done() |
||||
local content_type = ngx.header.content_type or "" |
||||
if find(content_type, "text/html") then |
||||
_M.obf_html() |
||||
end |
||||
end |
||||
-- 响应处理函数 |
||||
function _M.process_response() |
||||
local content_type = ngx.header.content_type or "" |
||||
local var_close = ngx.var.close_close |
||||
-- log(ngx.ERR, log_fmt("var_close: %s", tostring(var_close))) |
||||
if var_close == "true" then |
||||
_M.done() |
||||
return |
||||
else |
||||
local args = ngx.req.get_uri_args() |
||||
local obf = args and args.obf or nil |
||||
if obf == "close" then |
||||
return |
||||
end |
||||
end |
||||
|
||||
_M.done() |
||||
|
||||
end |
||||
|
||||
return _M |
||||
@ -1,232 +0,0 @@ |
||||
-- Copyright (C) midoks |
||||
|
||||
local _M = { _VERSION = '1.0' } |
||||
local mt = { __index = _M } |
||||
local setmetatable = setmetatable |
||||
|
||||
local obf_log = require "resty.obf.log" |
||||
local tpl = require "resty.obf.tpl" |
||||
local log_fmt = obf_log.fmt |
||||
local aes = require "resty.aes" |
||||
local ffi = require "ffi" |
||||
local ffi_str = ffi.string |
||||
local random = require "resty.random" |
||||
local util = require "resty.obf.util" |
||||
local obf_cache = ngx.shared.obf_cache |
||||
local obf_cache_bytes = 0 |
||||
|
||||
local find = string.find |
||||
local log = ngx.log |
||||
|
||||
|
||||
function _M.new(self) |
||||
local self = { |
||||
} |
||||
return setmetatable(self, mt) |
||||
end |
||||
|
||||
-- 编码 |
||||
function _M.obf_encode(content) |
||||
local enc, tag, iv_bin |
||||
local content_type = ngx.header.content_type or "" |
||||
local upstream_ct = ngx.var.upstream_http_content_type or "" |
||||
local cl = ngx.header["Content-Length"] or "" |
||||
local upstream_cl = ngx.var.upstream_http_content_length or "" |
||||
|
||||
local key = random.bytes(8, true) |
||||
-- log(ngx.ERR, log_fmt("enc=aes-256-gcm, pass_b64=%s", ngx.encode_base64(password))) |
||||
local cipher = aes.cipher(256, "gcm") |
||||
local a, aerr = aes:new(key, nil, cipher, aes.hash.md5, 1, 12, true) |
||||
|
||||
if not a then |
||||
log(ngx.ERR, log_fmt("aes init error: %s", tostring(aerr))) |
||||
else |
||||
iv_bin = ffi_str(a._iv, 12) |
||||
local res, eerr = a:encrypt(content) |
||||
if not res then |
||||
log(ngx.ERR, log_fmt("aes encrypt error: %s", tostring(eerr))) |
||||
else |
||||
if type(res) == "table" then |
||||
enc = res[1] |
||||
tag = res[2] |
||||
else |
||||
enc = res |
||||
end |
||||
content = enc .. (tag or "") |
||||
end |
||||
end |
||||
|
||||
return enc or content, key, iv_bin, tag |
||||
end |
||||
|
||||
function _M.get_cache_key() |
||||
local args = ngx.req.get_uri_args() |
||||
local parts = {} |
||||
for k, v in pairs(args or {}) do |
||||
if k ~= "obf" then |
||||
local val = v |
||||
if type(val) == "table" then val = val[1] end |
||||
parts[#parts + 1] = tostring(k) .. "=" .. tostring(val) |
||||
end |
||||
end |
||||
table.sort(parts) |
||||
local q = table.concat(parts, "&") |
||||
local cache_key = (ngx.var.scheme or "") .. "://" .. (ngx.var.host or "") .. (ngx.var.uri or "") .. (q ~= "" and ("?" .. q) or "") |
||||
return cache_key |
||||
end |
||||
|
||||
-- HTML标签混淆 |
||||
function _M.obf_html() |
||||
local content_type = ngx.header.content_type or "" |
||||
local ctx = ngx.ctx |
||||
|
||||
local html_debug = "false" |
||||
local cache_timeout = 600 |
||||
|
||||
local args = ngx.req.get_uri_args() |
||||
obf = args and args.obf or nil |
||||
if obf == "debug" then |
||||
html_debug = "true" |
||||
end |
||||
|
||||
local close_debug = ngx.var.close_debug |
||||
if close_debug == "true" then |
||||
html_debug = "false" |
||||
end |
||||
|
||||
local var_obf_timeout = ngx.var.obf_timeout |
||||
if var_obf_timeout then |
||||
cache_timeout = var_obf_timeout |
||||
end |
||||
-- log(ngx.ERR, log_fmt("var_obf_timeout: %s", tostring(var_obf_timeout))) |
||||
|
||||
if not ctx.obf_buffer then |
||||
ctx.obf_buffer = {} |
||||
ctx.obf_passthrough = false |
||||
end |
||||
|
||||
local chunk, eof = ngx.arg[1], ngx.arg[2] |
||||
if chunk and chunk ~= "" then |
||||
if not ctx.obf_first_t then |
||||
ctx.obf_first_t = util.tmark() |
||||
end |
||||
if ctx.obf_passthrough then |
||||
ngx.arg[1] = chunk |
||||
else |
||||
ctx.obf_buffer[#ctx.obf_buffer + 1] = chunk |
||||
ngx.arg[1] = nil |
||||
end |
||||
end |
||||
|
||||
|
||||
if eof then |
||||
if ctx.obf_passthrough then |
||||
return |
||||
end |
||||
local prof = ngx.var.obf_prof |
||||
local t_all0 = util.tmark() |
||||
local content = table.concat(ctx.obf_buffer) |
||||
local obf_cache = ngx.shared.obf_cache |
||||
|
||||
if find(content_type, "text/html", 1, true) then |
||||
|
||||
local var_rand_var = ngx.var.obf_rand_var |
||||
local var_rand_extra = ngx.var.obf_rand_extra |
||||
local var_b64 = ngx.var.obf_uint8_b64 |
||||
local js_mode = ngx.var.obf_js_mode |
||||
local js_url = ngx.var.obf_js_url |
||||
local cache_key = _M.get_cache_key()..tostring(html_debug)..tostring(var_rand_var)..tostring(var_rand_extra)..tostring(var_b64)..tostring(js_mode)..tostring(js_url) |
||||
local cached = obf_cache and obf_cache:get(cache_key) |
||||
if cached then |
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof cache_hit=1 size=%d total_ms=%.2f wait_ms=%.2f", #cached, util.dt_ms(t_all0), ctx.obf_first_t and util.dt_ms(ctx.obf_first_t) or 0)) |
||||
end |
||||
ngx.arg[1] = cached |
||||
ctx.obf_buffer = nil |
||||
return |
||||
else |
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof cache_miss=1")) |
||||
end |
||||
end |
||||
|
||||
|
||||
|
||||
local t_enc0 = util.tmark() |
||||
local content,key,iv,tag = _M.obf_encode(content) |
||||
local enc_ms = util.dt_ms(t_enc0) |
||||
|
||||
local t_ser0 = util.tmark() |
||||
local content_data = util.to_uint8array(content or "") |
||||
local iv_data = util.to_uint8array(iv or "") |
||||
local tag_data = util.to_uint8array(tag or "") |
||||
local key_data = util.to_uint8array(key or "") |
||||
local ser_ms = util.dt_ms(t_ser0) |
||||
|
||||
local t_tpl0 = util.tmark() |
||||
local html_data = tpl.content(content_data, iv_data, tag_data, key_data,tostring(html_debug)) |
||||
local tpl_ms = util.dt_ms(t_tpl0) |
||||
|
||||
local max_item = tonumber(ngx.var.obf_cache_item_max) or 0 |
||||
local max_bytes = tonumber(ngx.var.obf_cache_max_bytes) or 0 |
||||
local exptime = tonumber(cache_timeout) or 600 |
||||
if obf_cache then |
||||
if max_item <= 0 or #html_data <= max_item then |
||||
if max_bytes > 0 then |
||||
local free = obf_cache:free_space() |
||||
if not free or free >= #html_data then |
||||
obf_cache:set(cache_key, html_data, exptime) |
||||
end |
||||
else |
||||
obf_cache:set(cache_key, html_data, exptime) |
||||
end |
||||
end |
||||
end |
||||
|
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof size=%d enc_ms=%.2f ser_ms=%.2f tpl_ms=%.2f total_ms=%.2f wait_ms=%.2f", #content, enc_ms, ser_ms, tpl_ms, util.dt_ms(t_all0), ctx.obf_first_t and util.dt_ms(ctx.obf_first_t) or 0)) |
||||
end |
||||
ngx.arg[1] = html_data |
||||
end |
||||
|
||||
ctx.obf_buffer = nil |
||||
ctx.obf_passthrough = nil |
||||
end |
||||
end |
||||
|
||||
|
||||
function _M.done() |
||||
local content_type = ngx.header.content_type or "" |
||||
if find(content_type, "text/html") then |
||||
local prof = ngx.var.obf_prof |
||||
local t_start = util.tmark() |
||||
_M.obf_html() |
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("total_ms=%.2f", util.dt_ms(t_start))) |
||||
end |
||||
else |
||||
if ngx.var.obf_prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof skip_ct=1 ct=%s", tostring(content_type))) |
||||
end |
||||
end |
||||
end |
||||
-- 响应处理函数 |
||||
function _M.process_response() |
||||
local content_type = ngx.header.content_type or "" |
||||
local var_close = ngx.var.close_close |
||||
if var_close == "true" then |
||||
_M.done() |
||||
return |
||||
else |
||||
local args = ngx.req.get_uri_args() |
||||
local obf = args and args.obf or nil |
||||
if obf == "close" then |
||||
return |
||||
end |
||||
end |
||||
|
||||
_M.done() |
||||
|
||||
end |
||||
|
||||
return _M |
||||
@ -1,83 +0,0 @@ |
||||
-- Copyright (C) midoks |
||||
|
||||
local _M = { _VERSION = '1.0' } |
||||
local mt = { __index = _M } |
||||
local setmetatable = setmetatable |
||||
|
||||
local forgejs = require "resty.obf.forgejs" |
||||
|
||||
function _M.new(self) |
||||
local self = { |
||||
} |
||||
return setmetatable(self, mt) |
||||
end |
||||
|
||||
function _M.content(data, iv, tag, key, debug_data) |
||||
local cc = [[ |
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
</head> |
||||
<body></body> |
||||
</html> |
||||
<script type="text/javascript">{{FORGEJS}}</script> |
||||
<script>var encrypted={{SOURCE_DATA}}; var iv_data={{IV_DATA}}; var tag_data={{TAG_DATA}}; var key={{KEY_DATA}};var d={{DEBUG_DATA}};</script> |
||||
<script> |
||||
function u8ToBytes(u8){var s="";for(var i=0;i<u8.length;i++){s+=String.fromCharCode(u8[i]);}return s;} |
||||
function evpBytesToKey(pass, keyLen, ivLen){ |
||||
var m=[]; var i=0; var md=forge.md.md5.create(); |
||||
function concatLen(arr){var n=0; for(var j=0;j<arr.length;j++){n+=arr[j].length;} return n;} |
||||
while(concatLen(m) < (keyLen+ivLen)){ |
||||
md.start(); if(i>0){ md.update(m[i-1]); } |
||||
md.update(pass); |
||||
var d = md.digest().getBytes(); m.push(d); i++; |
||||
} |
||||
var ms = m.join(""); var key = ms.substring(0,keyLen); var iv = ms.substring(keyLen,keyLen+ivLen); |
||||
return {key:key, iv:iv}; |
||||
} |
||||
|
||||
window.onload = function(){ |
||||
var startTime = Date.now(); |
||||
|
||||
var dk = evpBytesToKey(u8ToBytes(key), 32, 32); |
||||
var decipher = forge.cipher.createDecipher("AES-GCM", dk.key); |
||||
decipher.start({iv: u8ToBytes(iv_data), tag: forge.util.createBuffer(u8ToBytes(tag_data))}); |
||||
decipher.update(forge.util.createBuffer(u8ToBytes(encrypted))); |
||||
var ok = decipher.finish(); |
||||
|
||||
if (ok) { |
||||
var newDoc = new DOMParser().parseFromString(decipher.output, "text/html"); |
||||
|
||||
if (d){ |
||||
console.log(newDoc); |
||||
console.log(decipher.output); |
||||
} |
||||
document.head.innerHTML = newDoc.head.innerHTML; |
||||
document.open(); |
||||
document.write(decipher.output); |
||||
document.close(); |
||||
} |
||||
var endTime = Date.now(); |
||||
if (d){ |
||||
console.log("dec cos(ms):",endTime - startTime); |
||||
} |
||||
} |
||||
</script> |
||||
]] |
||||
|
||||
local fj_content = forgejs.content() |
||||
cc = cc:gsub("{{FORGEJS}}", function() return fj_content end) |
||||
cc = cc:gsub("{{SOURCE_DATA}}", function() return data end) |
||||
cc = cc:gsub("{{IV_DATA}}", function() return iv end) |
||||
cc = cc:gsub("{{TAG_DATA}}", function() return tag end) |
||||
cc = cc:gsub("{{KEY_DATA}}", function() return key end) |
||||
cc = cc:gsub("{{DEBUG_DATA}}", function() return debug_data end) |
||||
|
||||
return cc |
||||
end |
||||
|
||||
|
||||
|
||||
return _M |
||||
@ -1,86 +0,0 @@ |
||||
-- Copyright (C) midoks |
||||
|
||||
local _M = { _VERSION = '1.0' } |
||||
local mt = { __index = _M } |
||||
local setmetatable = setmetatable |
||||
|
||||
local forgejs = require "resty.obf.forgejs" |
||||
local util = require "resty.obf.util" |
||||
local FJ = forgejs.content() |
||||
|
||||
function _M.new(self) |
||||
local self = { |
||||
} |
||||
return setmetatable(self, mt) |
||||
end |
||||
|
||||
function _M.content(data, iv, tag, key, debug_data) |
||||
local cc = [[ |
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
</head> |
||||
<body></body> |
||||
</html> |
||||
<script type="text/javascript">{{FORGEJS}}</script> |
||||
<script>var encrypted={{SOURCE_DATA}}; var iv_data={{IV_DATA}}; var tag_data={{TAG_DATA}}; var key={{KEY_DATA}};var d={{DEBUG_DATA}};</script> |
||||
<script> |
||||
function u8ToBytes(u8){var s="";for(var i=0;i<u8.length;i++){s+=String.fromCharCode(u8[i]);}return s;} |
||||
function evpBytesToKey(pass, keyLen, ivLen){ |
||||
var m=[]; var i=0; var md=forge.md.md5.create(); |
||||
function concatLen(arr){var n=0; for(var j=0;j<arr.length;j++){n+=arr[j].length;} return n;} |
||||
while(concatLen(m) < (keyLen+ivLen)){ |
||||
md.start(); if(i>0){ md.update(m[i-1]); } |
||||
md.update(pass); |
||||
var d = md.digest().getBytes(); m.push(d); i++; |
||||
} |
||||
var ms = m.join(""); var key = ms.substring(0,keyLen); var iv = ms.substring(keyLen,keyLen+ivLen); |
||||
return {key:key, iv:iv}; |
||||
} |
||||
|
||||
window.onload = function(){ |
||||
var startTime = Date.now(); |
||||
|
||||
var dk = evpBytesToKey(u8ToBytes(key), 32, 32); |
||||
var decipher = forge.cipher.createDecipher("AES-GCM", dk.key); |
||||
decipher.start({iv: u8ToBytes(iv_data), tag: forge.util.createBuffer(u8ToBytes(tag_data))}); |
||||
decipher.update(forge.util.createBuffer(u8ToBytes(encrypted))); |
||||
var ok = decipher.finish(); |
||||
|
||||
if (ok) { |
||||
var newDoc = new DOMParser().parseFromString(decipher.output, "text/html"); |
||||
|
||||
if (d){ |
||||
console.log(newDoc); |
||||
console.log(decipher.output); |
||||
} |
||||
document.head.innerHTML = newDoc.head.innerHTML; |
||||
document.open(); |
||||
document.write(decipher.output); |
||||
document.close(); |
||||
} |
||||
var endTime = Date.now(); |
||||
if (d){ |
||||
console.log("dec cos(ms):",endTime - startTime); |
||||
} |
||||
} |
||||
</script> |
||||
]] |
||||
-- 先随机变量名 |
||||
cc = util.obf_rand(cc) |
||||
|
||||
cc = cc:gsub("{{FORGEJS}}", function() return FJ end) |
||||
cc = cc:gsub("{{SOURCE_DATA}}", function() return data end) |
||||
cc = cc:gsub("{{IV_DATA}}", function() return iv end) |
||||
cc = cc:gsub("{{TAG_DATA}}", function() return tag end) |
||||
cc = cc:gsub("{{KEY_DATA}}", function() return key end) |
||||
cc = cc:gsub("{{DEBUG_DATA}}", function() return debug_data end) |
||||
|
||||
return cc |
||||
end |
||||
|
||||
|
||||
|
||||
return _M |
||||
@ -1,76 +0,0 @@ |
||||
-- Copyright (C) midoks |
||||
|
||||
local _M = { _VERSION = '1.0' } |
||||
local mt = { __index = _M } |
||||
local setmetatable = setmetatable |
||||
|
||||
local forgejs = require "resty.obf.forgejs" |
||||
local util = require "resty.obf.util" |
||||
local FJ = forgejs.content() |
||||
|
||||
function _M.new(self) |
||||
local self = { |
||||
} |
||||
return setmetatable(self, mt) |
||||
end |
||||
|
||||
function _M.content(data, iv, tag, key, debug_data) |
||||
local head_html = "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n </head>\n <body></body>\n</html>\n" |
||||
local fj_open = "<script type=\"text/javascript\">" |
||||
local fj_close = "</script>\n" |
||||
|
||||
local decode_script = "var encrypted="..data.."; var iv_data="..iv.."; var tag_data="..tag.."; var key="..key..";var d="..debug_data..";\n".. |
||||
"function u8ToBytes(u8){var s=\"\";for(var i=0;i<u8.length;i++){s+=String.fromCharCode(u8[i]);}return s;}\n".. |
||||
"function evpBytesToKey(pass, keyLen, ivLen){\n".. |
||||
" var m=[]; var i=0; var md=forge.md.md5.create();\n".. |
||||
" function concatLen(arr){var n=0; for(var j=0;j<arr.length;j++){n+=arr[j].length;} return n;}\n".. |
||||
" while(concatLen(m) < (keyLen+ivLen)){\n".. |
||||
" md.start(); if(i>0){ md.update(m[i-1]); }\n".. |
||||
" md.update(pass);\n".. |
||||
" var d = md.digest().getBytes(); m.push(d); i++;\n".. |
||||
" }\n".. |
||||
" var ms = m.join(\"\"); var key = ms.substring(0,keyLen); var iv = ms.substring(keyLen,keyLen+ivLen);\n".. |
||||
" return {key:key, iv:iv};\n".. |
||||
"}\n\n".. |
||||
"window.onload = function(){\n".. |
||||
" var startTime = Date.now();\n\n".. |
||||
" var dk = evpBytesToKey(u8ToBytes(key), 32, 32);\n".. |
||||
" var decipher = forge.cipher.createDecipher(\"AES-GCM\", dk.key);\n".. |
||||
" decipher.start({iv: u8ToBytes(iv_data), tag: forge.util.createBuffer(u8ToBytes(tag_data))});\n".. |
||||
" decipher.update(forge.util.createBuffer(u8ToBytes(encrypted)));\n".. |
||||
" var ok = decipher.finish();\n\n".. |
||||
" if (ok) {\n".. |
||||
" var newDoc = new DOMParser().parseFromString(decipher.output, \"text/html\");\n\n".. |
||||
" if (d){\n".. |
||||
" console.log(newDoc);\n".. |
||||
" console.log(decipher.output);\n".. |
||||
" }\n".. |
||||
" document.head.innerHTML = newDoc.head.innerHTML;\n".. |
||||
" document.open();\n".. |
||||
" document.write(decipher.output);\n".. |
||||
" document.close();\n".. |
||||
" }\n".. |
||||
" var endTime = Date.now();\n".. |
||||
" if (d){\n".. |
||||
" console.log(\"dec cos(ms):\",endTime - startTime);\n".. |
||||
" }\n".. |
||||
"}\n" |
||||
|
||||
if not (ngx.var.obf_rand == "false") then |
||||
decode_script = util.obf_rand(decode_script) |
||||
end |
||||
|
||||
return table.concat({ |
||||
head_html, |
||||
fj_open, |
||||
FJ, |
||||
fj_close, |
||||
"<script>\n", |
||||
decode_script, |
||||
"</script>\n", |
||||
}) |
||||
end |
||||
|
||||
|
||||
|
||||
return _M |
||||
@ -1,96 +0,0 @@ |
||||
-- Copyright (C) midoks |
||||
|
||||
local _M = { _VERSION = '1.0' } |
||||
local mt = { __index = _M } |
||||
local setmetatable = setmetatable |
||||
|
||||
local forgejs = require "resty.obf.forgejs" |
||||
local util = require "resty.obf.util" |
||||
local FJ = forgejs.content() |
||||
|
||||
local obf_log = require "resty.obf.log" |
||||
local log_fmt = obf_log.fmt |
||||
local log = ngx.log |
||||
|
||||
|
||||
function _M.new(self) |
||||
local self = { |
||||
} |
||||
return setmetatable(self, mt) |
||||
end |
||||
|
||||
function _M.content(data, iv, tag, key, debug_data) |
||||
local head_html = "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n </head>\n <body></body>\n</html>\n" |
||||
local fj_open = "<script type=\"text/javascript\">" |
||||
local fj_close = "</script>\n" |
||||
|
||||
local data_script = "<script>\nvar encrypted={{__HOLD_1__}}; var iv_data="..iv.."; var tag_data="..tag.."; var key="..key..";var d="..debug_data..";function u8ToBytes(u8){var s=\"\";for(var i=0;i<u8.length;i++){s+=String.fromCharCode(u8[i]);}return s;}\n".. |
||||
"function evpBytesToKey(pass, keyLen, ivLen){\n".. |
||||
" var m=[]; var i=0; var md=forge.md.md5.create();\n".. |
||||
" function concatLen(arr){var n=0; for(var j=0;j<arr.length;j++){n+=arr[j].length;} return n;}\n".. |
||||
" while(concatLen(m) < (keyLen+ivLen)){\n".. |
||||
" md.start(); if(i>0){ md.update(m[i-1]); }\n".. |
||||
" md.update(pass);\n".. |
||||
" var d = md.digest().getBytes(); m.push(d); i++;\n".. |
||||
" }\n".. |
||||
" var ms = m.join(\"\"); var key = ms.substring(0,keyLen); var iv = ms.substring(keyLen,keyLen+ivLen);\n".. |
||||
" return {key:key, iv:iv};\n".. |
||||
"}\n\n".. |
||||
"window.onload = function(){\n".. |
||||
" var startTime = Date.now();\n\n".. |
||||
" var dk = evpBytesToKey(u8ToBytes(key), 32, 32);\n".. |
||||
" var decipher = forge.cipher.createDecipher(\"AES-GCM\", dk.key);\n".. |
||||
" decipher.start({iv: u8ToBytes(iv_data), tag: forge.util.createBuffer(u8ToBytes(tag_data))});\n".. |
||||
" decipher.update(forge.util.createBuffer(u8ToBytes(encrypted)));\n".. |
||||
" var ok = decipher.finish();\n\n".. |
||||
" if (ok) {\n".. |
||||
" var newDoc = new DOMParser().parseFromString(decipher.output, \"text/html\");\n\n".. |
||||
" if (d){\n".. |
||||
" console.log(newDoc);\n".. |
||||
" console.log(decipher.output);\n".. |
||||
" }\n".. |
||||
" document.head.innerHTML = newDoc.head.innerHTML;\n".. |
||||
" document.open();\n".. |
||||
" document.write(decipher.output);\n".. |
||||
" document.close();\n".. |
||||
" }\n".. |
||||
" var endTime = Date.now();\n".. |
||||
" if (d){\n".. |
||||
" console.log(\"dec cos(ms):\",endTime - startTime);\n".. |
||||
" }\n".. |
||||
"}\n</script>\n" |
||||
|
||||
|
||||
if not (ngx.var.obf_rand_var == "false") then |
||||
data_script = util.obf_rand(data_script) |
||||
end |
||||
|
||||
local prof = ngx.var.obf_prof |
||||
if not (ngx.var.obf_rand_extra == "false") then |
||||
local t_add0 = util.tmark() |
||||
data_script = util.obf_rand_data(data_script) |
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof obf_rand_data add_ms=%.2f", util.dt_ms(t_add0))) |
||||
end |
||||
end |
||||
|
||||
local t_df0 = util.tmark() |
||||
data_script = util.data_filter(data_script) |
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof data_filter ms=%.2f", util.dt_ms(t_df0))) |
||||
end |
||||
|
||||
data_script = data_script:gsub("{{__HOLD_1__}}", data) |
||||
|
||||
return table.concat({ |
||||
head_html, |
||||
fj_open, |
||||
FJ, |
||||
fj_close, |
||||
data_script, |
||||
}) |
||||
end |
||||
|
||||
|
||||
|
||||
return _M |
||||
@ -1,113 +0,0 @@ |
||||
-- Copyright (C) midoks |
||||
|
||||
local _M = { _VERSION = '1.0' } |
||||
local mt = { __index = _M } |
||||
local setmetatable = setmetatable |
||||
|
||||
local util = require "resty.obf.util" |
||||
|
||||
local forgejs = require "resty.obf.forgejs" |
||||
|
||||
local obf_log = require "resty.obf.log" |
||||
local log_fmt = obf_log.fmt |
||||
local log = ngx.log |
||||
|
||||
|
||||
function _M.new(self) |
||||
local self = { |
||||
} |
||||
return setmetatable(self, mt) |
||||
end |
||||
|
||||
function _M.content(data, iv, tag, key, debug_data) |
||||
local head_html = "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><link rel=\"icon\" href=\"data:,\"></head><body></body></html>" |
||||
|
||||
local fj_open = "<script type=\"text/javascript\">" |
||||
local fj_close = "</script>\n" |
||||
|
||||
local data_script = "<script>\nvar encrypted={{__HOLD_1__}}; var iv_data="..iv.."; var tag_data="..tag.."; var key="..key..";var d="..debug_data..";function u8ToBytes(u8){var s=\"\";for(var i=0;i<u8.length;i++){s+=String.fromCharCode(u8[i]);}return s;}\n".. |
||||
"function evpBytesToKey(pass, keyLen, ivLen){\n".. |
||||
" var m=[]; var i=0; var md=forge.md.md5.create();\n".. |
||||
" function concatLen(arr){var n=0; for(var j=0;j<arr.length;j++){n+=arr[j].length;} return n;}\n".. |
||||
" while(concatLen(m) < (keyLen+ivLen)){\n".. |
||||
" md.start(); if(i>0){ md.update(m[i-1]); }\n".. |
||||
" md.update(pass);\n".. |
||||
" var d = md.digest().getBytes(); m.push(d); i++;\n".. |
||||
" }\n".. |
||||
" var ms = m.join(\"\"); var key = ms.substring(0,keyLen); var iv = ms.substring(keyLen,keyLen+ivLen);\n".. |
||||
" return {key:key, iv:iv};\n".. |
||||
"}\n\n".. |
||||
"window.onload = function(){\n".. |
||||
" var startTime = Date.now();\n\n".. |
||||
" var dk = evpBytesToKey(u8ToBytes(key), 32, 32);\n".. |
||||
" var decipher = forge.cipher.createDecipher(\"AES-GCM\", dk.key);\n".. |
||||
" decipher.start({iv: u8ToBytes(iv_data), tag: forge.util.createBuffer(u8ToBytes(tag_data))});\n".. |
||||
" decipher.update(forge.util.createBuffer(u8ToBytes(encrypted)));\n".. |
||||
" var ok = decipher.finish();\n\n".. |
||||
" if (ok) {\n".. |
||||
" var newDoc = new DOMParser().parseFromString(decipher.output, \"text/html\");\n\n".. |
||||
" if (d){\n".. |
||||
" console.log(newDoc);\n".. |
||||
" console.log(decipher.output);\n".. |
||||
" }\n".. |
||||
" document.head.innerHTML = newDoc.head.innerHTML;\n".. |
||||
" document.open();\n".. |
||||
" document.write(decipher.output);\n".. |
||||
" document.close();\n".. |
||||
" }\n".. |
||||
" var endTime = Date.now();\n".. |
||||
" if (d){\n".. |
||||
" console.log(\"dec cos(ms):\",endTime - startTime);\n".. |
||||
" }\n".. |
||||
"}\n</script>\n" |
||||
|
||||
|
||||
if not (ngx.var.obf_rand_var == "false") then |
||||
data_script = util.obf_rand(data_script) |
||||
end |
||||
|
||||
local prof = ngx.var.obf_prof |
||||
if not (ngx.var.obf_rand_extra == "false") then |
||||
local t_add0 = util.tmark() |
||||
data_script = util.obf_rand_data(data_script) |
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof obf_rand_data add_ms=%.2f", util.dt_ms(t_add0))) |
||||
end |
||||
end |
||||
|
||||
local t_df0 = util.tmark() |
||||
data_script = util.data_filter(data_script) |
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof data_filter ms=%.2f", util.dt_ms(t_df0))) |
||||
end |
||||
|
||||
data_script = data_script:gsub("{{__HOLD_1__}}", data) |
||||
|
||||
local forge_part = "" |
||||
local mode = ngx.var.obf_js_mode or "link" |
||||
local url = ngx.var.obf_js_url or "" |
||||
|
||||
if mode == "link" then |
||||
if url ~= '' then |
||||
forge_part = "<script src=\""..url.."\"></script>\n" |
||||
else |
||||
forge_part = "<script src=\"https://cdn.jsdelivr.net/npm/node-forge@1.3.1/dist/forge.min.js\"></script>\n" |
||||
end |
||||
elseif mode == "inline" then |
||||
if url ~= '' then |
||||
forge_part = "<script src=\""..url.."\"></script>\n" |
||||
else |
||||
forge_part = fj_open .. forgejs.content() .. fj_close |
||||
end |
||||
end |
||||
|
||||
return table.concat({ |
||||
head_html, |
||||
forge_part, |
||||
data_script, |
||||
}) |
||||
end |
||||
|
||||
|
||||
|
||||
return _M |
||||
File diff suppressed because one or more lines are too long
@ -1,29 +0,0 @@ |
||||
-- Copyright (C) midoks |
||||
|
||||
local _M = { |
||||
_VERSION = '1.0' |
||||
} |
||||
|
||||
local fmt = string.format |
||||
|
||||
local ERR = ngx.ERR |
||||
local WARN = ngx.WARN |
||||
local DEBUG = ngx.DEBUG |
||||
|
||||
function _M.fmt(formatstring, ...) |
||||
return fmt("obf: " .. formatstring, ...) |
||||
end |
||||
|
||||
function _M.err_fmt(formatstring, ...) |
||||
return ERR, _M.fmt(formatstring, ...) |
||||
end |
||||
|
||||
function _M.warn_fmt(formatstring, ...) |
||||
return WARN, _M.fmt(formatstring, ...) |
||||
end |
||||
|
||||
function _M.debug_fmt(formatstring, ...) |
||||
return DEBUG, _M.fmt(formatstring, ...) |
||||
end |
||||
|
||||
return _M |
||||
@ -1,242 +0,0 @@ |
||||
-- Copyright (C) midoks |
||||
|
||||
local _M = { _VERSION = '1.0' } |
||||
local mt = { __index = _M } |
||||
local setmetatable = setmetatable |
||||
|
||||
local obf_log = require "resty.obf.log" |
||||
local tpl = require "resty.obf.tpl" |
||||
local log_fmt = obf_log.fmt |
||||
local aes = require "resty.aes" |
||||
local ffi = require "ffi" |
||||
local ffi_str = ffi.string |
||||
local random = require "resty.random" |
||||
local util = require "resty.obf.util" |
||||
local obf_cache = ngx.shared.obf_cache |
||||
local obf_cache_bytes = 0 |
||||
|
||||
local find = string.find |
||||
local log = ngx.log |
||||
|
||||
|
||||
function _M.new(self) |
||||
local self = { |
||||
} |
||||
return setmetatable(self, mt) |
||||
end |
||||
|
||||
-- 编码 |
||||
function _M.obf_encode(content) |
||||
local enc, tag, iv_bin |
||||
local content_type = ngx.header.content_type or "" |
||||
local upstream_ct = ngx.var.upstream_http_content_type or "" |
||||
local cl = ngx.header["Content-Length"] or "" |
||||
local upstream_cl = ngx.var.upstream_http_content_length or "" |
||||
|
||||
local cipher = aes.cipher(256, "gcm") |
||||
local kdf_raw = ngx.var.obf_kdf_raw == "true" |
||||
local key |
||||
local a, aerr |
||||
if kdf_raw then |
||||
key = random.bytes(32, true) |
||||
iv_bin = random.bytes(12, true) |
||||
a, aerr = aes:new(key, nil, cipher, { iv = iv_bin }, nil, 12, true) |
||||
else |
||||
key = random.bytes(8, true) |
||||
a, aerr = aes:new(key, nil, cipher, aes.hash.md5, 1, 12, true) |
||||
end |
||||
|
||||
if not a then |
||||
log(ngx.ERR, log_fmt("aes init error: %s", tostring(aerr))) |
||||
else |
||||
if not kdf_raw then |
||||
iv_bin = ffi_str(a._iv, 12) |
||||
end |
||||
local res, eerr = a:encrypt(content) |
||||
if not res then |
||||
log(ngx.ERR, log_fmt("aes encrypt error: %s", tostring(eerr))) |
||||
else |
||||
if type(res) == "table" then |
||||
enc = res[1] |
||||
tag = res[2] |
||||
else |
||||
enc = res |
||||
end |
||||
content = enc .. (tag or "") |
||||
end |
||||
end |
||||
|
||||
return enc or content, key, iv_bin, tag |
||||
end |
||||
|
||||
function _M.get_cache_key() |
||||
local args = ngx.req.get_uri_args() |
||||
local parts = {} |
||||
for k, v in pairs(args or {}) do |
||||
if k ~= "obf" then |
||||
local val = v |
||||
if type(val) == "table" then val = val[1] end |
||||
parts[#parts + 1] = tostring(k) .. "=" .. tostring(val) |
||||
end |
||||
end |
||||
table.sort(parts) |
||||
local q = table.concat(parts, "&") |
||||
local cache_key = (ngx.var.scheme or "") .. "://" .. (ngx.var.host or "") .. (ngx.var.uri or "") .. (q ~= "" and ("?" .. q) or "") |
||||
return cache_key |
||||
end |
||||
|
||||
-- HTML标签混淆 |
||||
function _M.obf_html() |
||||
local content_type = ngx.header.content_type or "" |
||||
local ctx = ngx.ctx |
||||
|
||||
local html_debug = "false" |
||||
local cache_timeout = 600 |
||||
|
||||
local args = ngx.req.get_uri_args() |
||||
obf = args and args.obf or nil |
||||
if obf == "debug" then |
||||
html_debug = "true" |
||||
end |
||||
|
||||
local close_debug = ngx.var.close_debug |
||||
if close_debug == "true" then |
||||
html_debug = "false" |
||||
end |
||||
|
||||
local var_obf_timeout = ngx.var.obf_timeout |
||||
if var_obf_timeout then |
||||
cache_timeout = var_obf_timeout |
||||
end |
||||
-- log(ngx.ERR, log_fmt("var_obf_timeout: %s", tostring(var_obf_timeout))) |
||||
|
||||
if not ctx.obf_buffer then |
||||
ctx.obf_buffer = {} |
||||
ctx.obf_passthrough = false |
||||
end |
||||
|
||||
local chunk, eof = ngx.arg[1], ngx.arg[2] |
||||
if chunk and chunk ~= "" then |
||||
if not ctx.obf_first_t then |
||||
ctx.obf_first_t = util.tmark() |
||||
end |
||||
if ctx.obf_passthrough then |
||||
ngx.arg[1] = chunk |
||||
else |
||||
ctx.obf_buffer[#ctx.obf_buffer + 1] = chunk |
||||
ngx.arg[1] = nil |
||||
end |
||||
end |
||||
|
||||
|
||||
if eof then |
||||
if ctx.obf_passthrough then |
||||
return |
||||
end |
||||
local prof = ngx.var.obf_prof |
||||
local t_all0 = util.tmark() |
||||
local content = table.concat(ctx.obf_buffer) |
||||
local obf_cache = ngx.shared.obf_cache |
||||
|
||||
if find(content_type, "text/html", 1, true) then |
||||
|
||||
local var_rand_var = ngx.var.obf_rand_var |
||||
local var_rand_extra = ngx.var.obf_rand_extra |
||||
local var_b64 = ngx.var.obf_uint8_b64 |
||||
local js_mode = ngx.var.obf_js_mode |
||||
local js_url = ngx.var.obf_js_url |
||||
local cache_key = _M.get_cache_key()..tostring(html_debug)..tostring(var_rand_var)..tostring(var_rand_extra)..tostring(var_b64)..tostring(js_mode)..tostring(js_url) |
||||
local cached = obf_cache and obf_cache:get(cache_key) |
||||
if cached then |
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof cache_hit=1 size=%d total_ms=%.2f wait_ms=%.2f", #cached, util.dt_ms(t_all0), ctx.obf_first_t and util.dt_ms(ctx.obf_first_t) or 0)) |
||||
end |
||||
ngx.arg[1] = cached |
||||
ctx.obf_buffer = nil |
||||
return |
||||
else |
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof cache_miss=1")) |
||||
end |
||||
end |
||||
|
||||
|
||||
|
||||
local t_enc0 = util.tmark() |
||||
local content,key,iv,tag = _M.obf_encode(content) |
||||
local enc_ms = util.dt_ms(t_enc0) |
||||
|
||||
local t_ser0 = util.tmark() |
||||
local content_data = util.to_uint8array(content or "") |
||||
local iv_data = util.to_uint8array(iv or "") |
||||
local tag_data = util.to_uint8array(tag or "") |
||||
local key_data = util.to_uint8array(key or "") |
||||
local ser_ms = util.dt_ms(t_ser0) |
||||
|
||||
local t_tpl0 = util.tmark() |
||||
local html_data = tpl.content(content_data, iv_data, tag_data, key_data,tostring(html_debug)) |
||||
local tpl_ms = util.dt_ms(t_tpl0) |
||||
|
||||
local max_item = tonumber(ngx.var.obf_cache_item_max) or 0 |
||||
local max_bytes = tonumber(ngx.var.obf_cache_max_bytes) or 0 |
||||
local exptime = tonumber(cache_timeout) or 600 |
||||
if obf_cache then |
||||
if max_item <= 0 or #html_data <= max_item then |
||||
if max_bytes > 0 then |
||||
local free = obf_cache:free_space() |
||||
if not free or free >= #html_data then |
||||
obf_cache:set(cache_key, html_data, exptime) |
||||
end |
||||
else |
||||
obf_cache:set(cache_key, html_data, exptime) |
||||
end |
||||
end |
||||
end |
||||
|
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof size=%d enc_ms=%.2f ser_ms=%.2f tpl_ms=%.2f total_ms=%.2f wait_ms=%.2f", #content, enc_ms, ser_ms, tpl_ms, util.dt_ms(t_all0), ctx.obf_first_t and util.dt_ms(ctx.obf_first_t) or 0)) |
||||
end |
||||
ngx.arg[1] = html_data |
||||
end |
||||
|
||||
ctx.obf_buffer = nil |
||||
ctx.obf_passthrough = nil |
||||
end |
||||
end |
||||
|
||||
|
||||
function _M.done() |
||||
local content_type = ngx.header.content_type or "" |
||||
if find(content_type, "text/html") then |
||||
local prof = ngx.var.obf_prof |
||||
local t_start = util.tmark() |
||||
_M.obf_html() |
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("total_ms=%.2f", util.dt_ms(t_start))) |
||||
end |
||||
else |
||||
if ngx.var.obf_prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof skip_ct=1 ct=%s", tostring(content_type))) |
||||
end |
||||
end |
||||
end |
||||
-- 响应处理函数 |
||||
function _M.process_response() |
||||
local content_type = ngx.header.content_type or "" |
||||
local var_close = ngx.var.close_close |
||||
if var_close == "true" then |
||||
_M.done() |
||||
return |
||||
else |
||||
local args = ngx.req.get_uri_args() |
||||
local obf = args and args.obf or nil |
||||
if obf == "close" then |
||||
return |
||||
end |
||||
end |
||||
|
||||
_M.done() |
||||
|
||||
end |
||||
|
||||
return _M |
||||
@ -1,141 +0,0 @@ |
||||
-- Copyright (C) midoks |
||||
|
||||
local _M = { _VERSION = '1.0' } |
||||
local mt = { __index = _M } |
||||
local setmetatable = setmetatable |
||||
|
||||
local util = require "resty.obf.util" |
||||
|
||||
local forgejs = require "resty.obf.forgejs" |
||||
|
||||
local obf_log = require "resty.obf.log" |
||||
local log_fmt = obf_log.fmt |
||||
local log = ngx.log |
||||
|
||||
|
||||
function _M.new(self) |
||||
local self = { |
||||
} |
||||
return setmetatable(self, mt) |
||||
end |
||||
|
||||
function _M.content(data, iv, tag, key, debug_data) |
||||
local head_html = "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><link rel=\"icon\" href=\"data:,\"></head><body></body></html>" |
||||
|
||||
local fj_open = "<script type=\"text/javascript\">" |
||||
local fj_close = "</script>\n" |
||||
|
||||
local use_raw = ngx.var.obf_kdf_raw == "true" |
||||
local data_script |
||||
if use_raw then |
||||
data_script = "<script>\nvar encrypted={{__HOLD_1__}}; var iv_data="..iv.."; var tag_data="..tag.."; var key="..key..";var d="..debug_data..";function u8ToBytes(u8){var s=\"\";for(var i=0;i<u8.length;i++){s+=String.fromCharCode(u8[i]);}return s;}\n".. |
||||
"window.onload = function(){\n".. |
||||
" var startTime = Date.now();\n\n".. |
||||
" var decipher = forge.cipher.createDecipher(\"AES-GCM\", u8ToBytes(key));\n".. |
||||
" decipher.start({iv: u8ToBytes(iv_data), tag: forge.util.createBuffer(u8ToBytes(tag_data))});\n".. |
||||
" decipher.update(forge.util.createBuffer(u8ToBytes(encrypted)));\n".. |
||||
" var ok = decipher.finish();\n\n".. |
||||
" if (ok) {\n".. |
||||
" var newDoc = new DOMParser().parseFromString(decipher.output, \"text/html\");\n\n".. |
||||
" if (d){\n".. |
||||
" console.log(newDoc);\n".. |
||||
" console.log(decipher.output);\n".. |
||||
" }\n".. |
||||
" document.head.innerHTML = newDoc.head.innerHTML;\n".. |
||||
" document.open();\n".. |
||||
" document.write(decipher.output);\n".. |
||||
" document.close();\n".. |
||||
" }\n".. |
||||
" var endTime = Date.now();\n".. |
||||
" if (d){\n".. |
||||
" console.log(\"dec cos(ms):\",endTime - startTime);\n".. |
||||
" }\n".. |
||||
"}\n</script>\n" |
||||
else |
||||
data_script = "<script>\nvar encrypted={{__HOLD_1__}}; var iv_data="..iv.."; var tag_data="..tag.."; var key="..key..";var d="..debug_data..";function u8ToBytes(u8){var s=\"\";for(var i=0;i<u8.length;i++){s+=String.fromCharCode(u8[i]);}return s;}\n".. |
||||
"function evpBytesToKey(pass, keyLen, ivLen){\n".. |
||||
" var m=[]; var i=0; var md=forge.md.md5.create();\n".. |
||||
" function concatLen(arr){var n=0; for(var j=0;j<arr.length;j++){n+=arr[j].length;} return n;}\n".. |
||||
" while(concatLen(m) < (keyLen+ivLen)){\n".. |
||||
" md.start(); if(i>0){ md.update(m[i-1]); }\n".. |
||||
" md.update(pass);\n".. |
||||
" var d = md.digest().getBytes(); m.push(d); i++;\n".. |
||||
" }\n".. |
||||
" var ms = m.join(\"\"); var key = ms.substring(0,keyLen); var iv = ms.substring(keyLen,keyLen+ivLen);\n".. |
||||
" return {key:key, iv:iv};\n".. |
||||
"}\n\n".. |
||||
"window.onload = function(){\n".. |
||||
" var startTime = Date.now();\n\n".. |
||||
" var dk = evpBytesToKey(u8ToBytes(key), 32, 32);\n".. |
||||
" var decipher = forge.cipher.createDecipher(\"AES-GCM\", dk.key);\n".. |
||||
" decipher.start({iv: u8ToBytes(iv_data), tag: forge.util.createBuffer(u8ToBytes(tag_data))});\n".. |
||||
" decipher.update(forge.util.createBuffer(u8ToBytes(encrypted)));\n".. |
||||
" var ok = decipher.finish();\n\n".. |
||||
" if (ok) {\n".. |
||||
" var newDoc = new DOMParser().parseFromString(decipher.output, \"text/html\");\n\n".. |
||||
" if (d){\n".. |
||||
" console.log(newDoc);\n".. |
||||
" console.log(decipher.output);\n".. |
||||
" }\n".. |
||||
" document.head.innerHTML = newDoc.head.innerHTML;\n".. |
||||
" document.open();\n".. |
||||
" document.write(decipher.output);\n".. |
||||
" document.close();\n".. |
||||
" }\n".. |
||||
" var endTime = Date.now();\n".. |
||||
" if (d){\n".. |
||||
" console.log(\"dec cos(ms):\",endTime - startTime);\n".. |
||||
" }\n".. |
||||
"}\n</script>\n" |
||||
end |
||||
|
||||
|
||||
if not (ngx.var.obf_rand_var == "false") then |
||||
data_script = util.obf_rand(data_script) |
||||
end |
||||
|
||||
local prof = ngx.var.obf_prof |
||||
if not (ngx.var.obf_rand_extra == "false") then |
||||
local t_add0 = util.tmark() |
||||
data_script = util.obf_rand_data(data_script) |
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof obf_rand_data add_ms=%.2f", util.dt_ms(t_add0))) |
||||
end |
||||
end |
||||
|
||||
local t_df0 = util.tmark() |
||||
data_script = util.data_filter(data_script) |
||||
if prof == "true" then |
||||
log(ngx.ERR, log_fmt("obf_prof data_filter ms=%.2f", util.dt_ms(t_df0))) |
||||
end |
||||
|
||||
data_script = data_script:gsub("{{__HOLD_1__}}", data) |
||||
|
||||
local forge_part = "" |
||||
local mode = ngx.var.obf_js_mode or "link" |
||||
local url = ngx.var.obf_js_url or "" |
||||
|
||||
if mode == "link" then |
||||
if url ~= '' then |
||||
forge_part = "<script src=\""..url.."\"></script>\n" |
||||
else |
||||
forge_part = "<script src=\"https://cdn.jsdelivr.net/npm/node-forge@1.3.1/dist/forge.min.js\"></script>\n" |
||||
end |
||||
elseif mode == "inline" then |
||||
if url ~= '' then |
||||
forge_part = "<script src=\""..url.."\"></script>\n" |
||||
else |
||||
forge_part = fj_open .. forgejs.content() .. fj_close |
||||
end |
||||
end |
||||
|
||||
return table.concat({ |
||||
head_html, |
||||
forge_part, |
||||
data_script, |
||||
}) |
||||
end |
||||
|
||||
|
||||
|
||||
return _M |
||||
@ -1,166 +0,0 @@ |
||||
-- Copyright (C) midoks |
||||
|
||||
local _M = {_VERSION = '1.0'} |
||||
local random = require "resty.random" |
||||
local sha256 = require "resty.sha256" |
||||
local resty_str = require "resty.string" |
||||
local byte = string.byte |
||||
|
||||
|
||||
function _M.tmark() |
||||
if ngx.hrtime then |
||||
return ngx.hrtime() |
||||
end |
||||
ngx.update_time() |
||||
return ngx.now() * 1000000 |
||||
end |
||||
|
||||
function _M.dt_ms(start) |
||||
if ngx.hrtime then |
||||
return (ngx.hrtime() - start) / 1000000 |
||||
end |
||||
ngx.update_time() |
||||
return (ngx.now() * 1000000 - start) / 1000 |
||||
end |
||||
|
||||
|
||||
function _M.to_uint8array(content) |
||||
if not content then |
||||
content = "" |
||||
end |
||||
local len = #content |
||||
if len == 0 then |
||||
return "new Uint8Array([])" |
||||
end |
||||
local mode = ngx.var.obf_uint8_b64 |
||||
if mode == "true" or (not mode and len >= 4096) then |
||||
local b64 = ngx.encode_base64(content) |
||||
return "(function(){var s='"..b64.."';var b=atob(s);var a=new Uint8Array(b.length);for(var i=0;i<b.length;i++){a[i]=b.charCodeAt(i)};return a;})()" |
||||
end |
||||
local arr = {} |
||||
for i = 1, len do |
||||
arr[i] = byte(content, i) |
||||
end |
||||
return "new Uint8Array([" .. table.concat(arr, ",") .. "])" |
||||
end |
||||
|
||||
|
||||
function _M.to_b64(content) |
||||
if not content then |
||||
content = "" |
||||
end |
||||
return ngx.encode_base64(content) |
||||
end |
||||
|
||||
-- 数据过滤 |
||||
function _M.data_filter(content) |
||||
if content == nil then |
||||
return "" |
||||
end |
||||
if type(content) ~= "string" then |
||||
content = tostring(content) |
||||
end |
||||
-- if not string.find(content, "<script", 1, true) then |
||||
-- return content:gsub("[\r\n]+", ""):gsub(">%s+<", "><") |
||||
-- end |
||||
-- content = content:gsub("<script(.-)>(.-)</script>", function(attrs, body) |
||||
-- local b = body |
||||
-- b = ("\n"..b):gsub("\n%s*//[^\n\r]*", "\n") |
||||
-- b = b:gsub("([^:])%s+//[^\n\r]*", "%1") |
||||
-- b = b:gsub("%s*([%(%),;:%{%}%[%]%+%-%*%=<>])%s*", "%1") |
||||
-- b = b:gsub("%s+", " ") |
||||
-- return "<script" .. attrs .. ">" .. b .. "</script>" |
||||
-- end) |
||||
return content:gsub(">[%s\r\n]+<", "><") |
||||
end |
||||
|
||||
|
||||
-- 随机变量名 |
||||
function _M.obf_rand(content) |
||||
local function rand_ident(len) |
||||
local head = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_" |
||||
local tail = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$" |
||||
local seed = random.bytes(16, true) .. (ngx.var.request_id or "") .. tostring(ngx.time()) .. tostring(ngx.now()) |
||||
local h = sha256:new(); h:update(seed); local digest = resty_str.to_hex(h:final()) |
||||
local r = {} |
||||
local hi = (string.byte(digest, 1) or 65) % #head + 1 |
||||
r[1] = string.sub(head, hi, hi) |
||||
local pos, di = 2, 2 |
||||
while pos <= len do |
||||
local c = string.byte(digest, di) or 97 |
||||
local ti = c % #tail + 1 |
||||
r[pos] = string.sub(tail, ti, ti) |
||||
pos = pos + 1 |
||||
di = di + 1 |
||||
if di > #digest then |
||||
h = sha256:new(); h:update(digest .. random.bytes(8, true)); digest = resty_str.to_hex(h:final()); di = 1 |
||||
end |
||||
end |
||||
return table.concat(r) |
||||
end |
||||
local ids = {"encrypted","iv_data","key","tag_data","d","u8ToBytes","evpBytesToKey","startTime","dk","decipher","ok","newDoc","endTime"} |
||||
local map = {} |
||||
for _, id in ipairs(ids) do |
||||
map[id] = rand_ident(8) |
||||
end |
||||
for k, v in pairs(map) do |
||||
local pat = "%f[%w_]" .. k .. "%f[^%w_]" |
||||
content = content:gsub(pat, v) |
||||
end |
||||
return content |
||||
end |
||||
|
||||
|
||||
|
||||
-- 添加混淆代码 |
||||
function _M.obf_rand_data(content) |
||||
if content == nil then |
||||
return "" |
||||
end |
||||
if type(content) ~= "string" then |
||||
content = tostring(content) |
||||
end |
||||
content = content:gsub("<script(.-)>(.-)</script>", function(attrs, body) |
||||
local b = body |
||||
local function rand_ident(len) |
||||
local head = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_" |
||||
local tail = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$" |
||||
local seed = random.bytes(16, true) .. (ngx.var.request_id or "") .. tostring(ngx.time()) .. tostring(ngx.now()) |
||||
local h = sha256:new(); h:update(seed); local digest = resty_str.to_hex(h:final()) |
||||
local r = {} |
||||
local hi = (string.byte(digest, 1) or 65) % #head + 1 |
||||
r[1] = string.sub(head, hi, hi) |
||||
local pos, di = 2, 2 |
||||
while pos <= len do |
||||
local c = string.byte(digest, di) or 97 |
||||
local ti = c % #tail + 1 |
||||
r[pos] = string.sub(tail, ti, ti) |
||||
pos = pos + 1 |
||||
di = di + 1 |
||||
if di > #digest then |
||||
h = sha256:new(); h:update(digest .. random.bytes(8, true)); digest = resty_str.to_hex(h:final()); di = 1 |
||||
end |
||||
end |
||||
return table.concat(r) |
||||
end |
||||
local v1 = rand_ident(8) |
||||
local v2 = rand_ident(8) |
||||
local v3 = rand_ident(8) |
||||
local f1 = rand_ident(8) |
||||
local f2 = rand_ident(8) |
||||
local tmp = rand_ident(8) |
||||
local filler = "var "..v1.."=\""..ngx.encode_base64(random.bytes(8, true)).."\";" |
||||
.."var "..v2.."=".._M.to_uint8array(random.bytes(8, true))..";" |
||||
.."var "..v3.."="..tostring(#ngx.encode_base64(random.bytes(6, true)))..";" |
||||
.."function "..f1.."(x){return x}" |
||||
.."function "..f2.."(){return "..v3.."}" |
||||
.."(function(){var "..tmp.."="..v3.."; for(var i=0;i<1;i++){"..tmp.."="..tmp.."+i}})();" |
||||
b = filler..";"..b |
||||
b = b:gsub("%s+", " ") |
||||
b = b:gsub("%s*([%(%),;:%{%}%[%]%+%-%*%=<>])%s*", "%1") |
||||
return "<script" .. attrs .. ">" .. b .. "</script>" |
||||
end) |
||||
return content |
||||
end |
||||
|
||||
return _M |
||||
Loading…
Reference in new issue