┌─────────────────────────────────────┐
│ Layer 3: Lua Script Engine │ ← 業務規則:SQL 白名單、注入特徵、風險評分
│ - 註冊 C 函數/變數到 Lua 狀態機 │
│ - 熱載入 .lua 腳本,無需重啟主程序 │
└─────────────▲───────────────────────┘
│ API 呼叫 / 事件回調
┌─────────────┴───────────────────────┐
│ Layer 2: Protocol Parser & Queue │ ← MySQL 協議解析、Command 識別、SQL 還原
│ - 連結佇列緩衝 Sniffer 原始封包 │
│ - 有限狀態機解析 Handshake/Query │
│ - 提取:Command Code、SQL 文本、DB名、用戶 │
└─────────────▲───────────────────────┘
│ 原始封包餵入
┌─────────────┴───────────────────────┐
│ Layer 1: Packet Capture (libpcap) │ ← 高效能抓包、BPF 過濾 Port 3306
│ - pcap_loop / pcap_dispatch │
│ - 零拷貝傳遞給解析層 │
└─────────────────────────────────────┘
// 1. 佇列節點
typedef struct pkt_node {
u_char *data; int len; struct timeval ts;
struct pkt_node *next;
} pkt_node_t;
// 2. 無鎖環形佇列 (生產者-消費者)
pkt_node_t *ring[RING_SIZE];
volatile int head=0, tail=0;
// 3. Lua 狀態機初始化與註冊
lua_State *L = luaL_newstate();
luaL_openlibs(L);
// 註冊 C 函數供 Lua 調用
lua_pushcfunction(L, l_get_sql_text); lua_setglobal(L, "get_sql");
lua_pushcfunction(L, l_get_command); lua_setglobal(L, "get_cmd");
lua_pushcfunction(L, l_get_user); lua_setglobal(L, "get_user");
lua_pushcfunction(L, l_get_db); lua_setglobal(L, "get_db");
// 載入腳本
if (luaL_dofile(L, "rules.lua")) { fprintf(stderr, "%s\n", lua_tostring(L, -1)); }
// 4. 消費者主迴圈
while (running) {
pkt_node_t *node = dequeue();
if (!node) { usleep(1000); continue; }
parse_mysql_packet(node->data, node->len); // 解析填充全域緩衝
// 呼叫 Lua 入口函數
lua_getglobal(L, "on_packet");
if (lua_pcall(L, 0, 1, 0)) { log_error(lua_tostring(L, -1)); }
int verdict = lua_tointeger(L, -1); lua_pop(L, 1);
if (verdict == BLOCK) { send_rst_packet(node); }
free_node(node);
}
-- rules.lua
local sql_inject_patterns = {
"union%s+select", "or%s+1=1", "sleep%(%d+%)", "benchmark%(%d+,%d+%)",
"load_file%(", "into%s+outfile", "information_schema",
"';--", "';#", "';/*"
}
function on_packet()
local sql = get_sql()
local cmd = get_cmd()
local user = get_user()
local db = get_db()
-- 僅分析 COM_QUERY (3) 和 COM_STMT_EXECUTE (23)
if cmd ~= 3 and cmd ~= 23 then return PASS end
sql = sql:lower()
for _, pat in ipairs(sql_inject_patterns) do
if sql:match(pat) then
log_alert("SQL_INJECTION", user, db, sql)
return BLOCK -- 或 ALERT 僅告警
end
end
-- 白名單機制
if sql_whitelist[sql] then return PASS end
return PASS
end
| 階段 | 方向 | 關鍵欄位 | 備註 |
|---|---|---|---|
| Handshake | S→C | Protocol Version, Server Version, Thread ID, Salt, Capabilities | 驗證握手、取得鹽值 |
| Auth | C→S | Username, Auth-Response (scramble), DB Name | 明文/加密密碼、預設 DB |
| Query | C→S | COM_QUERY (0x03) + SQL Text | 核心分析點,明文傳輸 |
| Stmt Prepare | C→S | COM_STMT_PREPARE (0x16) + SQL | 預處理,參數標記 ? |
| Stmt Execute | C→S | COM_STMT_EXECUTE (0x17) + Param Values | 需還原參數化 SQL |
| Resultset | S→C | Column Count, Column Defs, Rows, EOF | 可選擇性解析 |
// 訊號處理:SIGHUP 觸發腳本重載
void sighup_handler(int sig) { reload_lua_script = 1; }
// 主迴圈檢查
if (reload_lua_script) {
lua_close(L);
L = luaL_newstate(); luaL_openlibs(L);
register_c_functions(L);
if (luaL_dofile(L, "rules.lua")) log_error(...);
reload_lua_script = 0;
}
生產環境建議:Lua 沙箱(luaL_sandbox 或自定義環境表)限制腳本權限,防止惡意腳本執行 os.execute、io.open 等危險操作。
C 負責「快」,Lua 負責「靈」。協議解析、封包處理、高併發 IO 在 C 層極致優化;業務規則、風險模型、白名單維護在 Lua 層秒級迭代。這就是「高擴展性」的工程化落地。