Commit 5b619e2e authored by liming6's avatar liming6
Browse files

feature 添加文件监控部分功能

parent 095e09e6
This source diff could not be displayed because it is too large. You can view the blob instead.
# 监控scp创建文件动作 # 监控scp创建文件动作
-a always,exit -F arch=b64 -S openat -F exe=/usr/bin/scp -F 'a2&0x40' -F key=scp_create_file -a always,exit -F arch=b64 -S openat -F exe=/usr/bin/scp -F a2&0x40 -F key=scp_create_file
-a always,exit -F arch=b64 -S open -F exe=/usr/bin/scp -F 'a1&0x40' -F key=scp_create_file -a always,exit -F arch=b64 -S open -F exe=/usr/bin/scp -F a1&0x40 -F key=scp_create_file
# 监控scp关闭文件动作,通常表示文件已经写入完成 # 监控scp关闭文件动作
-a always,exit -F arch=b64 -S close -F exe=/usr/bin/scp -F key=scp_close_file -a always,exit -F arch=b64 -S close -F exe=/usr/bin/scp -F key=scp_close_file
# 监控sftp创建文件动作 # 监控sftp创建文件动作
-a always,exit -F arch=b64 -S openat -F exe=/usr/libexec/openssh/sftp-server -F 'a2&0x40' -F key=sftp_create_file -a always,exit -F arch=b64 -S openat -F exe=/usr/libexec/openssh/sftp-server -F a2&0x40 -F key=sftp_create_file
-a always,exit -F arch=b64 -S open -F exe=/usr/libexec/openssh/sftp-server -F 'a1&0x40' -F key=sftp_create_file -a always,exit -F arch=b64 -S open -F exe=/usr/libexec/openssh/sftp-server -F a1&0x40 -F key=sftp_create_file
# 监控sftp关闭文件动作,通常表示文件已经写入完成
# 监控sftp关闭文件动作
-a always,exit -F arch=b64 -S close -F exe=/usr/libexec/openssh/sftp-server -F key=sftp_close_file -a always,exit -F arch=b64 -S close -F exe=/usr/libexec/openssh/sftp-server -F key=sftp_close_file
# 监控sftp修改文件名动作 # 监控sftp修改文件名动作
......
package logic
import (
"errors"
"fmt"
"os"
"os/exec"
"regexp"
"sshd-tool/utils"
"strconv"
"strings"
)
var (
// 需要的二进制文件
NeedExec = []string{"chattr", "lsattr", "clamscan", "clamdscan"}
ReInfectedFile = regexp.MustCompile(`(?i)^infected\s+files:\s+([0-9]+)\s*$`)
ErrNoLineMatch = errors.New("no line match")
)
func CheckExec() error {
for _, v := range NeedExec {
p := utils.FindCmd(v)
if p == nil {
return fmt.Errorf("%s not found", v)
}
}
return nil
}
// ScanFile 扫描文件,返回是否有毒,如果有毒会删除
func ScanFile(f string) (bool, error) {
finfo, err := os.Stat(f)
if err != nil {
return false, err
}
if finfo.IsDir() {
return false, errors.New("error: dest is directory")
}
sf := fmt.Sprintf("%s.scanning", f)
err = os.Rename(f, sf)
if err != nil {
return false, err
}
cmd := exec.Command("clamdscan", "-i", sf)
output, err := cmd.CombinedOutput()
if err != nil {
if cmd.ProcessState.ExitCode() != 1 {
return false, fmt.Errorf("run clamdscan failed, output: %s", string(output))
}
}
if cmd.ProcessState.ExitCode() == 1 {
// 返回值不为0,有异常
str := string(output)
num, err := parseClamOutput(str)
if err != nil {
return false, fmt.Errorf("parse clamdscan output error, output: %s", str)
}
if num > 0 {
// todo:可以在这里插入日志
os.Remove(sf)
}
return true, nil
} else {
// 无异常,直接返回
err = os.Rename(sf, f)
return false, err
}
}
// parseClamOutput 解析clamdscan输出
func parseClamOutput(str string) (int, error) {
for _, line := range strings.Split(strings.Trim(str, "\n"), "\n") {
if ReInfectedFile.MatchString(line) {
f := ReInfectedFile.FindStringSubmatch(line)
return strconv.Atoi(f[1])
}
}
return 0, ErrNoLineMatch
}
package logic
import (
"strings"
"testing"
"time"
)
const (
Output = `----------- SCAN SUMMARY -----------
Infected files: 0
Time: 0.981 sec (0 m 0 s)
Start Date: 2026:02:14 09:39:02
End Date: 2026:02:14 09:39:03`
)
func TestRegext(t *testing.T) {
lines := strings.Split(Output, "\n")
for _, i := range lines {
if ReInfectedFile.MatchString(i) {
f := ReInfectedFile.FindStringSubmatch(i)
for _, ff := range f {
t.Log(ff)
}
}
}
}
func TestScanFile(t *testing.T) {
target := "./hycusmutool"
start := time.Now()
have, err := ScanFile(target)
dua := time.Since(start)
if have {
t.Logf("%s is bd", target)
}
t.Logf("used time: %dms", dua.Milliseconds())
if err != nil {
t.Error(err)
}
}
func TestParseInt(t *testing.T) {
i, err := ParseInt("abc")
if err != nil {
t.Error(err)
}
t.Logf("%d", i)
}
package logic
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"sync"
"github.com/alphadose/haxmap"
"github.com/elastic/go-libaudit/v2/aucoalesce"
"github.com/elastic/go-libaudit/v2/auparse"
)
var (
// 接收审计事件的管道
EventChan = make(chan *aucoalesce.Event, 1024)
// 记录有效事件的map
EventMap = haxmap.New[string, []*TaggedEvent]()
// 记录需要注意的可执行文件路径
ExecPath = map[string]bool{
"/usr/bin/scp": true,
"/usr/libexec/openssh/sftp-server": true,
}
// 记录需要注意的系统调用
Syscalls = map[string]FileAction{
"open": FA_Open,
"openat": FA_Open,
"close": FA_Close,
"rename": FA_Rename,
"renameat": FA_Rename,
"renameat2": FA_Rename,
"link": FA_Rename,
"unlink": FA_Rename,
"linkat": FA_Rename,
"unlinkat": FA_Rename,
}
SCP = "/usr/bin/scp"
SFTP = "/usr/libexec/openssh/sftp-server"
)
// FileTransformExec 传输文件的程序类型
type FileTransformExec uint8
const (
FTE_UNKNOWN FileTransformExec = iota
FTE_SFTP // sftp
FTE_SCP // scp
)
type FileAction uint8
const (
FA_Open FileAction = iota
FA_Close
FA_Rename
FA_Delete
)
type TaggedEvent struct {
Event *aucoalesce.Event
Exec FileTransformExec
IsTabby bool // 标记是否为tabby客户端
Fd *int // 记录文件描述符
}
func FromEvent(event *aucoalesce.Event) *TaggedEvent {
if event == nil {
return nil
}
result := TaggedEvent{
Event: event,
}
switch event.Process.Exe {
case SCP:
result.Exec = FTE_SCP
case SFTP:
result.Exec = FTE_SFTP
default:
result.Exec = FTE_UNKNOWN
}
// 如果是sftp,检查是否为
if result.Exec == FTE_SFTP {
}
// 如果是open、openat、close,会有文件描述符
return &result
}
// EventSet 事件集,一个事件集记录了同一个进程的审计事件
type EventSet struct {
Pid, PPID int // pid和ppid
Events []*aucoalesce.Event // 记录所有事件
Fds map[int64][]*aucoalesce.Event // 记录可以获取文件描述符的事件
IsTabby *bool // 记录是否为tabby客户端
Exec FileTransformExec // 记录文件传输工具
Lock sync.RWMutex // 锁,用于保护Fds和Events的读写
}
func NewEventSet() *EventSet {
return &EventSet{
Events: make([]*aucoalesce.Event, 0, 16),
Fds: make(map[int64][]*aucoalesce.Event),
IsTabby: nil,
Exec: FTE_UNKNOWN,
Lock: sync.RWMutex{},
}
}
// func (es *EventSet) AddEvent(e *aucoalesce.Event) error {
// if e == nil {
// return nil
// }
// es.Events = append(es.Events, e)
// sc := e.Data["syscall"]
// switch sc {
// case "close":
// case "openat":
// case "":
// }
// if sc == "openat" {
// i, err := ParseInt(e.Data["exit"])
// if err != nil {
// return nil
// }
// }
// return nil
// }
// GenKey 为事件生成字符串id,格式为 pid-ppid
func GenKey(event *aucoalesce.Event) string {
return fmt.Sprintf("%s-%s", event.Process.PID, event.Process.PPID)
}
// FiltMsg 过滤出有用的事件,存放到map中,并触发处理程序
func FiltMsg() {
for i := range EventChan {
v, have := ExecPath[i.Process.Exe]
if !(v && have) {
// 不是指定的exe跳过
continue
}
if i.Category != aucoalesce.EventTypeAuditRule {
// 不是由审计规则触发的事件跳过
continue
}
if i.Type != auparse.AUDIT_SYSCALL {
// 不是系统调用,跳过
continue
}
if i.Data == nil {
// Data里没有数据的跳过
continue
}
sc, have := i.Data["syscall"]
if !have {
continue
}
// 检查是不是需要关注的系统调用
_, have = Syscalls[sc]
if !have {
continue
}
// 失败
if i.Result != "success" {
continue
}
printEvent(i)
}
}
// HandleEvent 处理事件
func HaneldEvent(events []*TaggedEvent) {
// todo
panic("unimplemented")
}
// needHandle 判断是否需要进入处理流程
func needHandle(events []*TaggedEvent) bool {
// todo
panic("unimplemented")
}
func printEvent(e *aucoalesce.Event) {
// pid,ppid,syscall,
// open/openat:file,fd
// close:fd
if e == nil {
return
}
switch e.Data["syscall"] {
case "open", "openat":
fmt.Printf("open(at) pid: %s, ppid: %s, open %s, fd: %s \n", e.Process.PID, e.Process.PPID, e.File.Path, e.Data["exit"])
case "close":
fmt.Printf("close pid: %s, ppid: %s, fd: %s \n", e.Process.PID, e.Process.PPID, e.Data["a0"])
case "rename", "renameat", "renameat2":
fmt.Println(json.MarshalIndent(e, "", " "))
case "link", "linkat":
fmt.Printf("link(at): paths: %v\n", e.Paths)
case "unlink", "unlinkat":
fmt.Printf("unlink(at): paths: %v\n", e.Paths)
case "dup2", "dup3":
fmt.Println("dup")
default:
return
}
}
func ParseInt(str string) (int64, error) {
if strings.ContainsAny(str, "abcdefABCDEFxX") {
// 是16进制数
s := strings.ToLower(str)
if strings.HasPrefix(s, "0x") {
return strconv.ParseInt(s, 0, 64)
} else {
return strconv.ParseInt(fmt.Sprintf("0x%s", s), 0, 64)
}
} else {
// 不是16进制数
n, err := strconv.Atoi(str)
if err != nil {
return 0, err
}
return int64(n), err
}
}
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"log" "log"
"os" "os"
"sshd-tool/cmd/file-monitor/logic"
"time" "time"
"github.com/elastic/go-libaudit/v2" "github.com/elastic/go-libaudit/v2"
...@@ -19,10 +19,7 @@ func (h *EventHandler) ReassemblyComplete(msgs []*auparse.AuditMessage) { ...@@ -19,10 +19,7 @@ func (h *EventHandler) ReassemblyComplete(msgs []*auparse.AuditMessage) {
if err != nil { if err != nil {
fmt.Printf("coalesce messages error: %v", err) fmt.Printf("coalesce messages error: %v", err)
} }
fmt.Println("---------------------------") logic.EventChan <- event
j, _ := json.MarshalIndent(event, "", " ")
fmt.Printf("%s\n", string(j))
fmt.Println("---------------------------")
} }
func (h *EventHandler) EventsLost(count int) { func (h *EventHandler) EventsLost(count int) {
...@@ -37,7 +34,7 @@ func main() { ...@@ -37,7 +34,7 @@ func main() {
defer cli.Close() defer cli.Close()
handler := &EventHandler{} handler := &EventHandler{}
rea, err := libaudit.NewReassembler(256, time.Second*2, handler) rea, err := libaudit.NewReassembler(1024, time.Second*60, handler)
if err != nil { if err != nil {
log.Printf("%v", err) log.Printf("%v", err)
return return
...@@ -45,7 +42,7 @@ func main() { ...@@ -45,7 +42,7 @@ func main() {
defer rea.Close() defer rea.Close()
go func() { go func() {
ticker := time.NewTicker(time.Second) ticker := time.NewTicker(time.Second * 15)
defer ticker.Stop() defer ticker.Stop()
for range ticker.C { for range ticker.C {
if rea.Maintain() != nil { if rea.Maintain() != nil {
...@@ -61,4 +58,6 @@ func main() { ...@@ -61,4 +58,6 @@ func main() {
} }
_ = rea.Push(rawMsg.Type, rawMsg.Data) _ = rea.Push(rawMsg.Type, rawMsg.Data)
} }
close(logic.EventChan)
} }
# readme
sftp:
- 对于tabby,openat开始,close,unlink、link、unlink,unlink表示写入完成
- 普通sftp,openat开始,close表示写入完成
scp:
-
对于sftp,重命名文件:
-
<86>Jan 27 16:00:36 admin02 sftp-server[3761842]: opendir "/tmp"
<86>Jan 27 16:00:36 admin02 sftp-server[3761842]: closedir "/tmp"
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: open "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" flags WRITE,CREATE mode 0666
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: close "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" bytes read 0 written 1412082
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: remove name "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar"
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: sent status No such file
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: rename old "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" new "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar"
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: opendir "/tmp"
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: closedir "/tmp"
<86>Jan 27 16:48:22 admin02 sftp-server[3965704]: open "/tmp/id_ed25519" flags WRITE,CREATE,TRUNCATE mode 0666
<86>Jan 27 16:48:22 admin02 sftp-server[3965704]: close "/tmp/id_ed25519" bytes read 0 written 419
<86>Jan 27 16:48:22 admin02 sftp-server[3965052]: opendir "/tmp"
<86>Jan 27 16:48:23 admin02 sftp-server[3965052]: closedir "/tmp"
<86>Jan 27 16:48:53 admin02 sftp-server[3965052]: remove name "/tmp/id_ed25519"
<86>Jan 27 16:48:53 admin02 sftp-server[3965052]: opendir "/tmp"
<86>Jan 27 16:48:53 admin02 sftp-server[3965052]: closedir "/tmp"
\ No newline at end of file
package logic
import (
"fmt"
"os"
"sshd-tool/utils"
)
var (
NeedExec = []string{"chattr", "lsattr", "clamscan", "clamdscan"}
)
// CheckExec 检查必要的命令是否存在
func CheckExec() error {
for _, v := range NeedExec {
if p := utils.FindCmd(v); p == nil {
return fmt.Errorf("command %s not found", v)
}
}
return nil
}
// CheckRoot 检查是否以root的身份执行
func CheckRoot() bool {
return os.Getuid() == 0
}
package logic
import (
"testing"
"time"
)
func TestReg1(t *testing.T) {
itmes := []string{
`<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: rename old "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" new "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar"`,
}
for _, v := range itmes {
if ReMatch.MatchString(v) {
t.Logf("match: %s", v)
fields := ReMatch.FindStringSubmatch(v)
tt, err := time.Parse("Jan _2 15:04:05", fields[1])
if err != nil {
t.Log("parse time error!")
} else {
tn := time.Date(time.Now().Year(), tt.Month(), tt.Day(), tt.Hour(), tt.Minute(), tt.Second(), 0, time.Local)
t.Logf("time is %v", tn)
}
for ki, vi := range fields {
t.Logf(" %d: %v", ki, vi)
}
} else {
t.Logf("not match: %s", v)
}
}
}
func TestParseItem(t *testing.T) {
itmes := []string{
`<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: rename old "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" new "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar"`,
`<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: open "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" flags WRITE,CREATE mode 0666`,
`<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: close "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" bytes read 0 written 1412082`,
}
for _, v := range itmes {
i, err := ParseItem(v)
if err != nil {
t.Error(err)
}
if i != nil {
t.Logf("%+v", i)
} else {
t.Logf("not match: %s", v)
}
}
}
package logic
import (
"fmt"
"log"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/shirou/gopsutil/v4/process"
)
/*
思路:由于tabby比较特殊,所以分两类:普通sftp客户端日志和tabby客户端日志
普通:
<86>Jan 27 16:48:22 admin02 sftp-server[3965704]: open "/tmp/id_ed25519" flags WRITE,CREATE,TRUNCATE mode 0666
<86>Jan 27 16:48:22 admin02 sftp-server[3965704]: close "/tmp/id_ed25519" bytes read 0 written 419
解析到open动作以及CREATE标志,记录文件位置;等到解析到同文件的close动作表示写文件完成,立即扫描该文件
tabby:
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: open "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" flags WRITE,CREATE mode 0666
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: close "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" bytes read 0 written 1412082
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: remove name "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar"
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: sent status No such file
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: rename old "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" new "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar"
解析到open动作以及CREATE标志,记录文件位置;等到解析到同文件的rename动作表示写文件完成,立即扫描该文件
*/
var (
ReMatch = regexp.MustCompile(`<\d+>([A-Z][a-z]+\s+\d+\s+\d+:\d+:\d+)\s+[a-zA-Z0-9]+\s+sftp-server\[(\d+)\]:\s+([a-z]+)\s+(.*)$`)
LogItemMap = make(map[string][]*SftpLogItem) // key为文件名,value为两个对象指针,一个记录文件上传开始,一个记录文件上传结束
LogItemMapLock = sync.RWMutex{}
)
type Action int
const (
ActionOpenDir Action = iota
ActionCloseDir
ActionOpenFile
ActionCloseFile
ActionRemove
ActionRename
)
const (
TabbySuffix = ".tabby-upload"
ScanSuffix = ".scanning"
)
// SftpLogItem sftp日志类型,Flag的key可以为:name(remove) old(rename) new(rename) flags(open) mode(open) read(close) written(close)
type SftpLogItem struct {
Pid int
UserInfo string // uid或用户名
Time time.Time
Act Action
Target string // 动作的目标,针对open、close,是文件路径,针对rename,是文件原名
Flag map[string]any // 动作的参数,针对open、close、rename才有
IsTabby bool // 针对tabby需要特殊处理
}
// FinalFile 获取最终文件路径
func (sli *SftpLogItem) FinalFile() (string, error) {
if !sli.IsTabby {
return sli.Target, nil
}
switch sli.Act {
case ActionOpenFile, ActionCloseFile:
return strings.TrimSuffix(sli.Target, TabbySuffix), nil
case ActionRename:
if sli.Flag == nil {
return strings.TrimSuffix(sli.Target, TabbySuffix), nil
}
nname, have := sli.Flag["new"]
if !have {
return strings.TrimSuffix(sli.Target, TabbySuffix), nil
}
return nname.(string), nil
default:
return "", fmt.Errorf("unexcept action: %d", sli.Act)
}
}
func ParseItem(str string) (*SftpLogItem, error) {
str = strings.Trim(strings.Trim(str, "\n"), " ")
fields := ReMatch.FindStringSubmatch(str)
if len(fields) == 0 {
return nil, nil
}
var act Action
switch fields[3] {
case "open":
act = ActionOpenFile
case "close":
act = ActionCloseFile
case "rename":
act = ActionRename
default:
return nil, nil
}
time_raw, err := time.Parse("Jan _2 15:04:05", fields[1])
if err != nil {
return nil, err
}
time_raw = time_raw.AddDate(time.Now().Year()-time_raw.Year(), 0, 0)
pid, err := strconv.Atoi(fields[2])
if err != nil {
return nil, err
}
proc, err := process.NewProcess(int32(pid))
if err != nil {
return nil, err
}
uinfo, err := proc.Username()
if err != nil {
uinfo = fmt.Sprintf("%d",proc.Uids())
} else {
}
result := SftpLogItem{Act: act, Time: time_raw, Pid: pid}
switch result.Act {
case ActionOpenFile:
err = parseOpen(fields[4], &result)
case ActionCloseFile:
err = parseClose(fields[4], &result)
case ActionRename:
err = parseRename(fields[4], &result)
}
return &result, err
}
// parseClose
func parseClose(str string, target *SftpLogItem) error {
str = strings.Trim(strings.Trim(str, "\n"), " ")
fields := strings.Fields(str)
last := ""
for _, v := range fields {
if strings.HasPrefix(v, `"`) && strings.HasSuffix(v, `"`) {
target.Target = strings.Trim(v, `"`)
target.IsTabby = strings.HasSuffix(target.Target, TabbySuffix)
last = v
continue
}
switch v {
case "bytes", "read", "written":
last = v
default:
i, err := strconv.Atoi(v)
if err != nil {
last = v
break
}
if last != "" {
if target.Flag == nil {
target.Flag = make(map[string]any)
}
target.Flag[last] = i
last = ""
} else {
return fmt.Errorf("unknown parse close log: %s", str)
}
}
}
return nil
}
func parseOpen(str string, target *SftpLogItem) error {
str = strings.Trim(strings.Trim(str, "\n"), " ")
fields := strings.Fields(str)
last := ""
for _, v := range fields {
if strings.HasPrefix(v, `"`) && strings.HasSuffix(v, `"`) {
target.Target = strings.Trim(v, `"`)
target.IsTabby = strings.HasSuffix(target.Target, TabbySuffix)
last = v
continue
}
switch v {
case "flags", "mode":
last = v
default:
if last != "" {
if target.Flag == nil {
target.Flag = make(map[string]any)
}
target.Flag[last] = v
last = ""
} else {
return fmt.Errorf("unknown parse open log: %s", str)
}
}
}
return nil
}
func parseRename(str string, target *SftpLogItem) error {
str = strings.Trim(strings.Trim(str, "\n"), " ")
fields := strings.Fields(str)
last := ""
for _, v := range fields {
switch v {
case "old", "new":
last = v
default:
if strings.HasPrefix(v, `"`) && strings.HasSuffix(v, `"`) {
if last != "" {
if target.Flag == nil {
target.Flag = make(map[string]any)
}
target.Flag[last] = strings.Trim(v, `"`)
if last == "old" {
target.Target = strings.Trim(v, `"`)
target.IsTabby = strings.HasSuffix(target.Target, TabbySuffix)
}
last = ""
} else {
return fmt.Errorf("unknown parse rename log: %s", str)
}
last = v
} else {
return fmt.Errorf("unknown parse rename log: %s", str)
}
}
}
return nil
}
// HandleSftpLog 处理sftp日志
func HandleSftpLog(str string) {
item, err := ParseItem(str)
if err != nil {
log.Printf("parse sftp log error,log: %s, err: %v", str, err)
}
if item == nil {
return
}
switch item.Act {
case ActionOpenFile:
if item.Flag == nil {
return
}
flag, have := item.Flag["flags"]
if !have {
return
}
f := flag.(string)
if strings.Contains(f, "CREATE") {
LogItemMapLock.Lock()
ii := make([]*SftpLogItem, 0, 2)
ii = append(ii, item)
LogItemMap[item.Target] = ii
LogItemMapLock.Unlock()
}
case ActionCloseFile:
if item.IsTabby {
return
}
rl := LogItemMapLock.RLocker()
rl.Lock()
i, have := LogItemMap[item.Target]
rl.Unlock()
if !have {
log.Printf("close file but not found open: %s", item.Target)
return
}
i[1] = item
scanFile(i)
case ActionRename:
if !item.IsTabby {
return
}
rl := LogItemMapLock.RLocker()
rl.Lock()
i, have := LogItemMap[item.Target]
rl.Unlock()
if !have {
log.Printf("rename file but not found open: %s", item.Target)
return
}
i[1] = item
scanFile(i)
}
}
func scanFile(logItems []*SftpLogItem) {
if len(logItems) < 2 {
return
}
// // 记录权限
filePath, err := logItems[0].FinalFile()
if err != nil {
log.Printf("get final file path error: %v", err)
return
}
fileStat, err := os.Stat(filePath)
fileMode := fileStat.Mode()
os.Chmod(filePath, 0000)
// 修改名称
reFilePath := fmt.Sprintf("%s%s", filePath, ScanSuffix)
os.Rename(filePath, reFilePath)
// 扫描文件
cmd := exec.Command("clamdscan", "-i", "--no-summary", reFilePath)
output, err := cmd.CombinedOutput()
if cmd.ProcessState.ExitCode() != 0 {
// 有问题
content := string(output)
log.Printf("find virus file: %s", content)
} else {
// 没问题
os.Rename(reFilePath, filePath)
os.Chmod(filePath, fileMode)
}
}
package main
import (
"fmt"
"log"
"net"
"os/exec"
"sshd-tool/cmd/sftp-monitor/logic"
"strings"
"github.com/gofrs/flock"
)
const (
SocketPath = "/var/run/authpriv.sock"
FileLockPath = "/tmp/sftp-monitor.pid"
)
func main() {
fileLock := flock.New(FileLockPath, flock.SetPermissions(0644))
l, err := fileLock.TryLock()
if err != nil {
log.Fatalf("error lock file %s, %v", fileLock, err)
}
if !l {
log.Fatalf("can't lock %s, Perhaps an instance is already running.", FileLockPath)
}
// 删除旧
_ = exec.Command("rm", "-f", SocketPath).Run()
conn, err := net.ListenPacket("unixgram", SocketPath)
if err != nil {
log.Fatalf("can't listen unix socket: %v", err)
}
buff := make([]byte, 16384)
for {
n, _, err := conn.ReadFrom(buff)
if err != nil {
log.Fatalf("read error: %v", err)
break
}
content := string(buff[:n])
if strings.Contains(content, "sftp-server") {
fmt.Println(content)
go logic.HandleSftpLog(content)
}
}
}
...@@ -8,6 +8,7 @@ require ( ...@@ -8,6 +8,7 @@ require (
) )
require ( require (
github.com/alphadose/haxmap v1.4.1 // indirect
github.com/ebitengine/purego v0.9.1 // indirect github.com/ebitengine/purego v0.9.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
...@@ -17,6 +18,7 @@ require ( ...@@ -17,6 +18,7 @@ require (
github.com/tklauser/go-sysconf v0.3.16 // indirect github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
......
github.com/alphadose/haxmap v1.4.1 h1:VtD6VCxUkjNIfJk/aWdYFfOzrRddDFjmvmRmILg7x8Q=
github.com/alphadose/haxmap v1.4.1/go.mod h1:rjHw1IAqbxm0S3U5tD16GoKsiAd8FWx5BJ2IYqXwgmM=
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
...@@ -101,6 +103,8 @@ golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= ...@@ -101,6 +103,8 @@ golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE=
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
......
package utils
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment