import (
"reflect"
"regexp"
"runtime"
"strconv"
"strings"
"time"
"github.com/fsnotify/fsnotify"
)
const (
userInfoEvent = "user_info"
userInfoMonitorEvent = "UserInfo"
bashPidContentRegexp = "(useradd|password|usermod)[[0-9]{2,8}]"
bashPidRegexp = "[0-9]{2,8}"
)
var (
userInfoRepository = repository.NewUserInfoRepository()
UserInfoMonitorProcessor = newUserInfoProcessor()
)
type UserInfoProcessor struct {
fileName string
passwdFileName string
}
func newUserInfoProcessor() *UserInfoProcessor {
return &UserInfoProcessor{
conf.GlobalMonitorConfig.RootPath + "/etc/passwd",
conf.GlobalMonitorConfig.RootPath + "/etc/shadow",
}
}
func (fpp *UserInfoProcessor) HandleUserInfoEvent(notifyEvent fsnotify.Event) {
monitorFileName := notifyEvent.Name
if strings.ToLower(notifyEvent.Op.String()) == "remove" {
fpp.restartMonitorUserInfo(monitorFileName)
runtime.Goexit()
return
}
//获取数据库数据
userInfoList := userInfoRepository.SelectUserInfo()
var userInfoDetailArr []event.Detail
//获取变化后的数据map
newUserInfoMap := util.GetCurUserInfoData(fpp.fileName, fpp.passwdFileName)
for _, oldUserInfo := range userInfoList {
value, isFind := newUserInfoMap[oldUserInfo.UserName]
if isFind {
var newUserInfo userInfo.UserInfo
util.MapToStruct(value, &newUserInfo)
if !reflect.DeepEqual(oldUserInfo, newUserInfo) {
userInfoRepository.UpdateUserInfo(newUserInfo)
isHiddenModCommandOpt, _ := fpp.isCommandOpt(newUserInfo, "usermod")
isHiddenPasswdCommandOpt, currentBashUserName := fpp.isCommandOpt(newUserInfo, "passwd")
if currentBashUserName != "" {
newUserInfo.CurrentBashUserName = currentBashUserName
}
newUserInfo.IsHiddenUser = !isHiddenModCommandOpt && !isHiddenPasswdCommandOpt
if newUserInfo.PassWdMd5 != oldUserInfo.PassWdMd5 {
newUserInfo.IsPasswdChange = true
}
userInfoDetailArr = append(userInfoDetailArr, event.NewDetail(oldUserInfo, newUserInfo))
fpp.updateUserStatus(fpp.generateUserStatusInfo(newUserInfo))
}
delete(newUserInfoMap, oldUserInfo.UserName)
} else {
userInfoRepository.DeleteUserInfo(oldUserInfo.UserName)
userInfoDetailArr = append(userInfoDetailArr, event.NewDetail(oldUserInfo, struct{}{}))
}
}
for _, addItem := range newUserInfoMap {
var newUserInfo userInfo.UserInfo
util.MapToStruct(addItem, &newUserInfo)
userInfoRepository.InsertUserInfo(newUserInfo)
isHiddenCommandOpt, currentBashUserName := fpp.isCommandOpt(newUserInfo, "useradd")
if currentBashUserName != "" {
newUserInfo.CurrentBashUserName = currentBashUserName
}
newUserInfo.IsHiddenUser = !isHiddenCommandOpt
var oldUserInfo userInfo.UserInfo
userInfoDetailArr = append(userInfoDetailArr, event.NewDetail(oldUserInfo, newUserInfo))
fpp.insertUserStatus(fpp.generateUserStatusInfo(newUserInfo))
}
if len(userInfoDetailArr) > 0 {
EventReporter.HandleEventReporter(userInfoEvent, userInfoDetailArr)
}
if notifyEvent.Name == fpp.passwdFileName || notifyEvent.Name == fpp.fileName {
fpp.restartMonitorUserInfo(monitorFileName)
runtime.Goexit()
return
}
}
func (fpp *UserInfoProcessor) generateUserStatusInfo(userInfo userInfo.UserInfo) userLoginInfo.UserStatusInfo {
var userStatusInfo userLoginInfo.UserStatusInfo
userStatusInfo.UserName = userInfo.UserName
userStatusInfo.IsLocked = userInfo.IsLockUser
userStatusInfo.IsUnauthorized = util.GetAuthorizedStatus(userInfo.Shell)
return userStatusInfo
}
func (fpp *UserInfoProcessor) updateUserStatus(newUserStatusInfo userLoginInfo.UserStatusInfo) {
beforeUserStatusInfo := newUserStatusRepository.GetUserStatusByUser(newUserStatusInfo.UserName)
if newUserStatusInfo.IsLocked == true && beforeUserStatusInfo.IsLocked == false {
newUserStatusInfo.IsUnauthorized = beforeUserStatusInfo.IsUnauthorized
newUserStatusRepository.UpdateUserStatus(newUserStatusInfo)
}
if newUserStatusInfo.IsUnauthorized == true && beforeUserStatusInfo.IsUnauthorized == false {
newUserStatusInfo.IsLocked = beforeUserStatusInfo.IsLocked
newUserStatusRepository.UpdateUserStatus(newUserStatusInfo)
}
}
func (fpp *UserInfoProcessor) insertUserStatus(newUserStatusInfo userLoginInfo.UserStatusInfo) {
if newUserStatusInfo.IsLocked == true || newUserStatusInfo.IsUnauthorized == true {
repository.NewUserStatusRepository().InsertUserStatus(newUserStatusInfo)
}
}
func (fpp *UserInfoProcessor) InitUserInfoData() {
userInfoList := userInfoRepository.SelectUserInfo()
listLen := len(userInfoList)
if listLen == 0 {
fpp.saveUserInfoToDB()
}
}
func (fpp *UserInfoProcessor) saveUserInfoToDB() {
newUserInfoMap := util.GetCurUserInfoData(fpp.fileName, fpp.passwdFileName)
for _, newUserInfoMap := range newUserInfoMap {
var newUserInfo userInfo.UserInfo
util.MapToStruct(newUserInfoMap, &newUserInfo)
userInfoRepository.InsertUserInfo(newUserInfo)
}
}
//可能的操作类型 useradd usermod userdel
func (fpp *UserInfoProcessor) isCommandOpt(changedUserInfo userInfo.UserInfo, optType string) (bool, string) {
time.Sleep(500 * time.Millisecond)
matchString := "new user: name=" + changedUserInfo.UserName
if optType == "usermod" {
matchString = "change user '" + changedUserInfo.UserName + "'"
}
if optType == "userdel" {
matchString = "delete user '" + changedUserInfo.UserName + "'"
}
if optType == "passwd" {
matchString = "password changed for " + changedUserInfo.UserName
}
commandString := "grep -a \"" + matchString + "\" " + conf.GlobalMonitorConfig.RootPath + "/var/log/secure"
if conf.GlobalMonitorConfig.RootName == "itran" {
commandString = "grep -a \"" + matchString + "\" " + conf.GlobalMonitorConfig.RootPath + conf.RanSecureFilePath
}
outPut, _, _ := util.ExecShellCommand(commandString)
if outPut != "" && fpp.checkTriggeringTime(outPut) {
if optType == "passwd" || optType == "useradd" {
currentBashUserName := fpp.getBashNameByPidThreeTimes(outPut, optType)
return true, currentBashUserName
} else {
return true, ""
}
}
return false, ""
}
func (fpp *UserInfoProcessor) restartMonitorUserInfo(monitorFileName string) {
policy.CurrentMonitor.Notify(monitorFileName)
}
func (fpp *UserInfoProcessor) checkTriggeringTime(outPut string) bool {
triggeringTime := time.Now().Format("15:04:05")
triggeringTimeLast5 := time.Now().Add(-7 * time.Second).Format("15:04:05")
recordTime := ""
arr := strings.Split(outPut, "\n")
if conf.GlobalMonitorConfig.RootName == "itran" {
subArr := strings.Fields(arr[len(arr)-1])
itranTimeDate := util.FormatRanTime(subArr[0])
itranTime := strings.Fields(itranTimeDate)
recordTime = itranTime[1]
} else {
subArr := strings.Fields(arr[len(arr)-1])
recordTime = subArr[2]
}
if recordTime >= triggeringTimeLast5 && recordTime <= triggeringTime {
return true
} else {
return false
}
}
func (fpp *UserInfoProcessor) getBashNameByPidThreeTimes(outPut string, optType string) string {
bashPid := fpp.getCurrentPid(outPut)
for count := 0; count < 3; count++ {
bashName := fpp.getBashNameByPid(bashPid, optType)
if bashName != "" {
return bashName
}
time.Sleep(2 * time.Second)
}
return ""
}
func (fpp *UserInfoProcessor) getBashNameByPid(bashPid int, optType string) string {
if optType == "useradd" {
_, isFind := monitor.BashUserNameMap.Load(int32(bashPid))
if isFind {
value, isOK := monitor.BashUserNameMap.LoadAndDelete(int32(bashPid))
if isOK {
return value.(string)
} else {
return ""
}
}
} else if optType == "passwd" {
_, isFind := monitor.BashUserNameMap.Load("passwd")
if isFind {
value, isOK := monitor.BashUserNameMap.LoadAndDelete("passwd")
if isOK {
return value.(string)
} else {
return ""
}
}
}
return ""
}
func (fpp *UserInfoProcessor) getCurrentPid(outPut string) int {
bashPidRegexpContentPattern, _ := regexp.Compile(bashPidContentRegexp)
bashPidList := bashPidRegexpContentPattern.FindAllString(outPut, -1)
if len(bashPidList) >= 1 {
bashPidContent := bashPidList[len(bashPidList)-1]
bashPidRegexpPattern, _ := regexp.Compile(bashPidRegexp)
bashPidString := bashPidRegexpPattern.FindString(bashPidContent)
bashPid, _ := strconv.Atoi(bashPidString)
return bashPid
}
return -1
}
webshell检测规则
func (fpp *WebShellProcessor) matchWebShellRegx(fileContent string) bool {
regx1 := "((eval|assert)[\\s|\n]{0,30}\\([\\s|\n]{0,30}(\\\\{0,1}\\$((_(GET|POST|REQUEST|SESSION|SERVER)(\\[[\\'\"]{0,1})[\\w\\(\\)]{0,15}([\\'\"]{0,1}\\]))|\\w{1,10}))\\s{0,5}\\))"
regx2 := "((eval|assert)[\\s|\n]{0,30}\\((gzuncompress|gzinflate\\(){0,1}[\\s|\n]{0,30}base64_decode.{0,100})"
regx3 := "\\s{0,10}=\\s{0,10}([{@]{0,2}\\\\{0,1}\\$_(GET|POST|REQUEST)|file_get_contents|[\"\\']a[\"\\']\\.[\"\\']s[\"\\']\\.|[\"\\']e[\"\\']\\.[\"\\']v[\"\\']\\.|[\"\\']ass[\"\\']\\.).{0,20}"
var regxPhpEvalAssertLis = []string{regx1, regx2, regx3}
regx4 := "(\\$_(GET|POST|REQUEST)\\[.{0,15}\\]\\s{0,10}\\(\\s{0,10}\\$_(GET|POST|REQUEST).{0,15})"
regx5 := "((\\$(_(GET|POST|REQUEST|SESSION|SERVER)(\\[[\\'\"]{0,1})\\w{1,12}([\\'\"]{0,1}\\])|\\w{1,10}))[\\s\n]{0,20}\\([\\s\n]{0,20}(@{0,1}\\$(_(GET|POST|REQUEST|SESSION|SERVER)(\\[[\\'\"]{0,1})\\w{1,12}([\\'\"]{0,1}\\])|\\w{1,10}))[\\s\n]{0,5}\\))"
regx6 := "\\s{0,10}=\\s{0,10}[{@]{0,2}(\\$_(GET|POST|REQUEST)|file_get_contents|str_replace|[\"\\']a[\"\\']\\.[\"\\']s[\"\\']\\.|[\"\\']e[\"\\']\\.[\"\\']v[\"\\']\\.|[\"\\']ass[\"\\']\\.).{0,10}"
var regxPhpDynamicFunction = []string{regx4, regx5, regx6}
phpDdosCcKeywords := []string{"启动自动攻击", "xxddos", "phpddos", "fsockopen(\"udp:\"", "fsockopen(\"tcp:", "$_get[\"moshi\"]==\"udp\""}
regx7 := "([^\\'\"](include|require)(_once){0,1}\\s{0,5}(\\s{0,5}|\\(\\s{0,5})[\"\\']([\\.\\w\\,/\\\\+-_]{1,60})[\"\\']\\s*\\){0,1})"
regx8 := "((include|require)(_once){0,1}(\\s{0,5}|\\s{0,5}\\(\\s{0,5})[\\'\"]{0,1}(\\$(_(GET|POST|REQUEST|SERVER)(\\[[\\'\"]{0,1})\\w{0,8}([\\'\"]{0,1}\\])|[\\w]{1,15}))[\\'\"]{0,1})"
regx9 := "\\s{0,10}=\\s{0,10}([{@]{0,2}\\$_(GET|POST|REQUEST)|[\\'\"]{0,1}php:\\/\\/input[\\'\"]{0,1}|file_get_contents).{0,20}"
var regxPhpIncludeFile = []string{regx7, regx8, regx9}
var regxPhpPackShell = []string{"gzdeflate|gzcompress|gzencode"}
regx10 := "(array_map[\\s\n]{0,20}\\(.{1,5}(eval|assert|ass\\x65rt).{1,20}\\$_(GET|POST|REQUEST).{0,15})"
regx11 := "(call_user_func[\\s\n]{0,25}\\(.{0,25}\\$_(GET|POST|REQUEST).{0,15})"
regx12 := "(\\$_(GET|POST|REQUEST)\\[.{0,15}\\]\\s{0,10}\\(\\s{0,10}\\$_(GET|POST|REQUEST).{0,15})"
regx13 := "(include|require)(_once){0,1}[\\s*]+[\"|\\']+[0-9A-Za-z_]*\\://"
regx14 := "(preg_replace[\\s\n]{0,10}\\([\\s\n]{0,10}(([\"\\'].{0,15}[/@\\'][is]{0,2}e[is]{0,2}[\"\\'])|\\$[a-zA-Z_][\\w\"\\'\\[\\]]{0,15})\\s{0,5},\\s{0,5}.{0,40}(\\$_(GET|POST|REQUEST|SESSION|SERVER)|str_rot13|urldecode).{0,30})"
var regxWebShell = []string{regx10, regx11, regx12, regx13, regx14}
phpDpJmKeywords := []string{"www.phpdp.org", "www.phpjm.net", "www.phpdp.com", "Zend"}
if fpp.matchRegxLis(fileContent, regxPhpEvalAssertLis, "regx") ||
fpp.matchRegxLis(fileContent, regxPhpDynamicFunction, "regx") ||
fpp.matchRegxLis(fileContent, phpDdosCcKeywords, "keyword") ||
fpp.matchRegxLis(fileContent, phpDpJmKeywords, "keyword") ||
fpp.matchRegxLis(fileContent, regxPhpIncludeFile, "regx") ||
fpp.matchRegxLis(fileContent, regxPhpPackShell, "regx") || fpp.matchRegxLis(fileContent, regxWebShell, "regx") {
return true
}
return false
}
流式读取文件内容 防止读取大文件 CPU冲高
func (fpp *WebShellProcessor) DetectFileContentByFlow(fileName string) bool {
txtFile, err := os.OpenFile(filepath.Clean(fileName), os.O_RDONLY, 0600)
if err != nil {
util.HandleErr(err)
return false
}
defer func() {
if err := txtFile.Close(); err != nil {
util.HandleErr(err)
}
}()
bufReader := bufio.NewReader(txtFile)
var fileContent strings.Builder
step := 0
for {
data, _, err := bufReader.ReadLine() // 读一行日志
if err == io.EOF { // 如果列表读完了,退出
if fpp.matchWebShellRegx(fileContent.String()){
return true
}
return false
}
fileContent.WriteString(string(data))
step = step + 1
if step == 50 {
if fpp.matchWebShellRegx(fileContent.String()){
return true
}
time.Sleep(500*time.Millisecond)
step = 0
fileContent.Reset()
}
}
}
爆破测试方法
hydra工具爆破命令: hydra -l 用户名 -P passlist.txt ssh://ip
性能测试命令
测试命令
ps -ef |grep "proName" 查看名称还有proName的进程
top -p pid 查看对应pid进程的cpu
cat /proc/20705/status 查看对应pid进程占用的内存
进入容器命令
docker exec -it 容器ID /bin/bash
linux系统相关:
查看登录记录 /var/log/secure
查看网络接口状态 每次网络接口状态变化都会在 /var/log/messages 中记录
记录sftp登陆信息
Subsystem sftp /usr/libexec/openssh/sftp-server -l VERBOSE
Subsystem sftp internal-sftp -l VERBOSE
重启服务:systemctl restart sshd.service
复制宿主机文件到虚拟机
scp /home/admin/main admin@ip:/home/admin/main
cp /home/admin/ls admin@ip:/home/admin/ls
查看二进制文件的可打印字符串
"strings " + filePath + " | grep -w " + feature
容器目录
/var/lib/docker/containers