1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-20 00:39:17 +08:00
1Panel/plugins/openresty/waf/lib/resty/libinjection.lua
2024-02-28 06:34:09 +00:00

326 lines
7.6 KiB
Lua

local _M = {}
local bit = require "bit"
local ffi = require "ffi"
local ffi_new = ffi.new
local ffi_string = ffi.string
-- enum sqli_flags
local FLAG_NONE = 0
local FLAG_QUOTE_NONE = 1
local FLAG_QUOTE_SINGLE = 2
local FLAG_QUOTE_DOUBLE = 4
local FLAG_SQL_ANSI = 8
local FLAG_SQL_MYSQL = 16
-- enum lookup_type
local LOOKUP_FINGERPRINT = 4
-- enum html5_flags
local DATA_STATE = 0
local VALUE_NO_QUOTE = 1
local VALUE_SINGLE_QUOTE = 2
local VALUE_DOUBLE_QUOTE = 3
local VALUE_BACK_QUOTE = 4
-- cached ORs
local QUOTE_NONE_SQL_ANSI = bit.bor(FLAG_QUOTE_NONE, FLAG_SQL_ANSI)
local QUOTE_NONE_SQL_MYSQL = bit.bor(FLAG_QUOTE_NONE, FLAG_SQL_MYSQL)
local QUOTE_SINGLE_SQL_ANSI = bit.bor(FLAG_QUOTE_SINGLE, FLAG_SQL_ANSI)
local QUOTE_SINGLE_SQL_MYSQL = bit.bor(FLAG_QUOTE_SINGLE, FLAG_SQL_MYSQL)
local QUOTE_DOUBLE_SQL_MYSQL = bit.bor(FLAG_QUOTE_DOUBLE, FLAG_SQL_MYSQL)
-- libibjection.so
ffi.cdef [[
const char* libinjection_sqli_fingerprint(struct libinjection_sqli_state* sql_state, int flags);
struct libinjection_sqli_token {
char type;
char str_open;
char str_close;
size_t pos;
size_t len;
int count;
char val[32];
};
typedef char (*ptr_lookup_fn)(struct libinjection_sqli_state*, int lookuptype, const char* word, size_t len);
struct libinjection_sqli_state {
const char *s;
size_t slen;
ptr_lookup_fn lookup;
void* userdata;
int flags;
size_t pos;
struct libinjection_sqli_token tokenvec[8];
struct libinjection_sqli_token *current;
char fingerprint[8];
int reason;
int stats_comment_ddw;
int stats_comment_ddx;
int stats_comment_c;
int stats_comment_hash;
int stats_folds;
int stats_tokens;
};
void libinjection_sqli_init(struct libinjection_sqli_state * sf, const char *s, size_t len, int flags);
int libinjection_is_sqli(struct libinjection_sqli_state* sql_state);
int libinjection_sqli(const char* s, size_t slen, char fingerprint[]);
int libinjection_is_xss(const char* s, size_t len, int flags);
int libinjection_xss(const char* s, size_t slen);
]]
_M.version = "0.1.1"
local state_type = ffi.typeof("struct libinjection_sqli_state[1]")
local lib, loaded
-- "borrowed" from CF aho-corasick lib
local function _loadlib()
if (not loaded) then
local path, so_path
local libname = "libinjection.so"
for k, v in string.gmatch(package.cpath, "[^;]+") do
so_path = string.match(k, "(.*/)")
if so_path then
-- "so_path" could be nil. e.g, the dir path component is "."
so_path = so_path .. libname
-- Don't get me wrong, the only way to know if a file exist is
-- trying to open it.
local f = io.open(so_path)
if f ~= nil then
io.close(f)
path = so_path
break
end
end
end
path = "/usr/local/openresty/1pwaf/data/libinjection.so"
lib = ffi.load(path)
if (lib) then
loaded = true
return true
else
return false
end
else
return true
end
end
-- this function is not publicly exposed so we need to emulate it here. not great but not a measurable perf hit
local function _reparse_as_mysql(sqli_state)
return sqli_state[0].stats_comment_ddx ~= 0 or sqli_state[0].stats_comment_hash ~= 0
end
--[[
Secondary API: detects SQLi in a string, given a context. Given a string, returns a list of
* boolean indicating a match
* SQLi fingerprint
--]]
local function _sqli_contextwrapper(string, char, flag1, flag2)
if (char and not string.find(string, char, 1, true)) then
return false, nil
end
if (not loaded) then
if (not _loadlib()) then
return false, nil
end
end
local issqli, lookup, sqli_state
-- allocate a new libinjection_sqli_state struct
sqli_state = ffi_new(state_type)
-- init the state
lib.libinjection_sqli_init(
sqli_state,
string,
#string,
FLAG_NONE
)
-- initial fingerprint
lib.libinjection_sqli_fingerprint(
sqli_state,
flag1
)
-- lookup
lookup = sqli_state[0].lookup(
sqli_state,
LOOKUP_FINGERPRINT,
sqli_state[0].fingerprint,
#ffi.string(sqli_state[0].fingerprint)
)
-- match? great, we're done
if (lookup > 0) then
return true, ffi_string(sqli_state[0].fingerprint)
end
-- no? reparse, fingerprint and lookup again
if (flag2 and _reparse_as_mysql(sqli_state)) then
lib.libinjection_sqli_fingerprint(
sqli_state,
flag2
)
lookup = sqli_state[0].lookup(
sqli_state,
LOOKUP_FINGERPRINT,
sqli_state[0].fingerprint,
#ffi.string(sqli_state[0].fingerprint)
)
if (lookup > 0) then
return true, ffi_string(sqli_state[0].fingerprint)
end
end
return false, nil
end
--[[
Wrapper for second-level API with no char context
--]]
function _M.sqli_noquote(string)
return _sqli_contextwrapper(
string,
nil,
QUOTE_NONE_SQL_ANSI,
QUOTE_NONE_SQL_MYSQL
)
end
--[[
Wrapper for second-level API with CHAR_SINGLE context
--]]
function _M.sqli_singlequote(string)
return _sqli_contextwrapper(
string,
"'",
QUOTE_SINGLE_SQL_ANSI,
QUOTE_SINGLE_SQL_MYSQL
)
end
--[[
Wrapper for second-level API with CHAR_DOUBLE context
--]]
function _M.sqli_doublequote(string)
return _sqli_contextwrapper(
string,
'"',
QUOTE_DOUBLE_SQL_MYSQL
)
end
--[[
Simple API. Given a string, returns a list of
* boolean indicating a match
* SQLi fingerprint
--]]
function _M.sqli(string)
if (not loaded) then
if (not _loadlib()) then
return false, nil
end
end
local fingerprint = ffi_new("char [8]")
return lib.libinjection_sqli(string, #string, fingerprint) == 1, ffi_string(fingerprint)
end
--[[
Secondary API: detects XSS in a string, given a context. Given a string, returns a boolean denoting if XSS was detected
--]]
local function _xss_contextwrapper(string, flag)
if (not loaded) then
if (not _loadlib()) then
return false
end
end
return lib.libinjection_is_xss(string, #string, flag) == 1
end
--[[
Wrapper for second-level API with DATA_STATE flag
--]]
function _M.xss_data_state(string)
return _xss_contextwrapper(
string,
DATA_STATE
)
end
--[[
Wrapper for second-level API with VALUE_NO_QUOTE flag
--]]
function _M.xss_noquote(string)
return _xss_contextwrapper(
string,
VALUE_NO_QUOTE
)
end
--[[
Wrapper for second-level API with VALUE_SINGLE_QUOTE flag
--]]
function _M.xss_singlequote(string)
return _xss_contextwrapper(
string,
VALUE_SINGLE_QUOTE
)
end
--[[
Wrapper for second-level API with VALUE_DOUBLE_QUOTE flag
--]]
function _M.xss_doublequote(string)
return _xss_contextwrapper(
string,
VALUE_DOUBLE_QUOTE
)
end
--[[
Wrapper for second-level API with VALUE_BACK_QUOTE flag
--]]
function _M.xss_backquote(string)
return _xss_contextwrapper(
string,
VALUE_BACK_QUOTE
)
end
--[[
ALPHA version of XSS detector. Given a string, returns a boolean denoting if XSS was detected
--]]
function _M.xss(string)
if (not loaded) then
if (not _loadlib()) then
return false
end
end
return lib.libinjection_xss(string, #string) == 1
end
return _M