Created
December 5, 2018 08:31
-
-
Save cstayyab/4ca0ac623d38de0d1527c749cbbf1d1d to your computer and use it in GitHub Desktop.
A Sysdig Chisel written in lua to capture all the activites done in SSH sessions separately on a linux machine.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Chisel description | |
description = "Track SSH Session activity"; | |
short_description = "Track SSH SESSIONS activity"; | |
category = "Security"; | |
-- Chisel argument list | |
args = | |
{ | |
{ | |
name = "max_depth", | |
description = "the maximum depth to show in the hierarchy of processes", | |
argtype = "int", | |
optional = true | |
}, | |
{ | |
name = "disable_color", | |
description = "Set to 'disable_colors' if you want to disable color output", | |
argtype = "string", | |
optional = true | |
}, | |
} | |
require "common" | |
terminal = require "ansiterminal" | |
terminal.enable_color(true) | |
MAX_ANCESTOR_NAVIGATION = 16 | |
max_depth = -1 | |
-- Argument notification callback | |
function on_set_arg(name, val) | |
if name == "max_depth" then | |
max_depth = parse_numeric_input(val, name) | |
elseif name == "disable_color" and val == "disable_color" then | |
terminal.enable_color(false) | |
end | |
return true | |
end | |
-- Initialize Callback | |
function on_init() | |
sysdig.set_snaplen(2000) | |
-- Request the fields needed for this chisel | |
fetype = chisel.request_field("evt.type") | |
fexe = chisel.request_field("proc.exe") | |
fproc = chisel.request_field("proc.name") | |
fargs = chisel.request_field("proc.args") | |
fdir = chisel.request_field("evt.arg.path") | |
fuser = chisel.request_field("user.name") | |
fdtime = chisel.request_field("evt.time.s") | |
fpid = chisel.request_field("proc.pid") | |
fppid = chisel.request_field("proc.ppid") | |
fppname = chisel.request_field("proc.pname"); | |
fsid = chisel.request_field("proc.sid") | |
ffname = chisel.request_field("fd.name") | |
ffilename = chisel.request_field("fd.filename") | |
fargdata = chisel.request_field("evt.arg.data") | |
fapid = chisel.request_field("proc.apid[2]") | |
fanames = {} | |
fapids = {} | |
sfilter = "(fd.name startswith /run/systemd/sessions/ and not fd.filename startswith \".#\" and not fd.filename startswith c and evt.arg.data contains \"REMOTE=1\")" | |
bfilter = "(evt.type=execve)" | |
cfilter = "(((evt.type=execve and evt.dir=<) or (evt.type=chdir and evt.dir=< and proc.name contains sh and not proc.name contains sshd)) and evt.failed=false)" | |
chisel.set_filter(sfilter .. " or " .. bfilter .. " or " .. cfilter) | |
for j = 0, MAX_ANCESTOR_NAVIGATION do | |
fanames[j] = chisel.request_field("proc.aname[" .. j .. "]") | |
fapids[j] = chisel.request_field("proc.apid[" .. j .. "]") | |
end | |
return true | |
end | |
process_tree = {} | |
sessions = {} | |
files = {} | |
function table.print(t) | |
print("{\n") | |
for k,v in pairs(t) do | |
print(k .. " : " .. v .. "\n") | |
end | |
print("}\n") | |
end | |
-- Event parsing callback | |
function on_event() | |
local color = "" | |
local etype = evt.field(fetype) | |
local user = evt.field(fuser) | |
local dtime = evt.field(fdtime) | |
local pid = evt.field(fpid) | |
local ppid = evt.field(fppid) | |
local ischdir = evt.field(fetype) == "chdir" | |
local containername = evt.field(fcontainername) | |
local containerid = evt.field(fcontainerid) | |
local sid=evt.field(fsid) | |
local dir=evt.field(fdir) | |
local proc = evt.field(fproc) | |
local fname = evt.field(ffname) | |
local filename = evt.field(ffilename) | |
local argdata = evt.field(fargdata) | |
local exe = evt.field(fexe) | |
local apid = evt.field(fapid) | |
local ppname = evt.field(fppname); | |
local aname | |
local icorr = 1 | |
if(etype=="execve" and exe == "-bash" and ppname == "sshd") then | |
for k,v in pairs(sessions) do | |
--print(v["LEADER"] .. " " .. apid) | |
if(v["LEADER"]==tonumber(apid)) then | |
sessions[k]["BASH"] = tonumber(pid) | |
--print("Session " .. k .. " User=" .. v["USER"] .. " IP=" .. v["REMOTE_HOST"]) | |
print(terminal.red .. "New session " .. k .. " started for user (".. sessions[k]["USER"].. ") from IP " .. sessions[k]["REMOTE_HOST"] .. " SHELL ID: " .. sessions[k]["BASH"] .. terminal.reset) | |
break | |
end | |
end | |
else | |
if (sessions[filename] == nil and tonumber(filename) ~= nil and io.open(fname,"r") ~= nil) then | |
local session = get_session_attr(fname) | |
sessions[filename] = session | |
--[[elseif (sessions[filename] ~= nil and tonumber(filename) ~= nil and io.open(fname,"r") ~= nil) then | |
local update = get_session_attr(fname) | |
if(update["STATE"]=="CLOSING") then | |
sessions[filename]=nil | |
print("Session " .. filename .. " closed by user(".. update["USER"] ..") from IP " .. update["REMOTE_HOST"] .. " SHELL ID: " .. sessions[k]["BASH"]) | |
end--]] | |
end | |
end | |
-- Traking user sessions activity | |
if ischdir then | |
ppid = pid | |
table.insert(fanames, 0, 0) | |
table.insert(fapids, 0, 0) | |
icorr = 0 | |
end | |
local belongsto | |
for k,v in pairs(sessions) do | |
if v["BASH"]~=nil and tonumber(sid)==tonumber(v["BASH"]) then | |
belongsto = v | |
break | |
end | |
end | |
local remoteip | |
if belongsto~=nil then | |
user=belongsto["USER"] | |
remoteip=belongsto["REMOTE_HOST"] | |
if not process_tree[ppid] then | |
-- No parent pid in the table yet. | |
-- Add one and make sure that there's a shell among the ancestors | |
process_tree[ppid] = {-1} | |
for j = 1, MAX_ANCESTOR_NAVIGATION do | |
aname = evt.field(fanames[j]) | |
if aname == nil then | |
if evt.field(fapids[j]) == nil then | |
-- no shell in the ancestor list, hide this command | |
break | |
end | |
elseif string.len(aname) >= 2 and aname:sub(-2) == "sh" then | |
apid = evt.field(fapids[j]) | |
if process_tree[apid] then | |
process_tree[ppid] = {j - 1, apid} | |
else | |
process_tree[ppid] = {0, apid} | |
end | |
end | |
end | |
end | |
if process_tree[ppid][1] == -1 then | |
-- the parent process has already been detected as NOT having a shell ancestor | |
return true | |
end | |
if not process_tree[pid] then | |
process_tree[pid] = {1 + process_tree[ppid][1], process_tree[ppid][2]} | |
end | |
if ischdir then | |
if max_depth ~= -1 then | |
if process_tree[pid][1] - icorr > max_depth then | |
return true | |
end | |
end | |
-- The -pc or -pcontainer options was supplied on the cmd line | |
if print_container then | |
-- No support of containers added yet | |
else | |
print(color .. | |
extend_string("", 4 * (process_tree[pid][1] - icorr)) .. process_tree[pid][2] .. " " .. | |
dtime .. " " .. remoteip .. "(" .. | |
user .. ") cd " .. | |
evt.field(fdir)) | |
end | |
else | |
if max_depth ~= -1 then | |
if process_tree[pid][1] - 1 > max_depth then | |
return true | |
end | |
end | |
-- The -pc or -pcontainer options was supplied on the cmd line | |
if print_container then | |
-- No support for container added yet | |
else | |
print(color .. | |
extend_string("", 3 * (process_tree[pid][1] - 1)) .. process_tree[pid][2] .. " " .. dtime .. " " .. remoteip .. " (" .. user ..") " .. evt.field(fexe) .. " " .. evt.field(fargs)) | |
end | |
end | |
end | |
-- Tracking activity ends here | |
return true | |
end | |
-- Get an attribute of ssh session | |
function get_session_attr(filename) | |
attrs = {} | |
local f = io.open(filename) | |
while true do | |
line = f:read() | |
if line == nil then break end | |
if not line:match "^#" then | |
local attr = split(line,"=") | |
if(tonumber(attr[2])~=nil) then | |
attrs[attr[1]]=tonumber(attr[2]) | |
else | |
attrs[attr[1]]=attr[2] | |
end | |
end | |
end | |
return attrs | |
end | |
-- Called by the engine at the end of the capture (Ctrl-C) | |
function on_capture_end() | |
print(terminal.reset) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment