用go实现web日志分析及网页挂马关键字检测

本程序主要实现网页挂马关键字检测,原理是这样的,通过定时分析用户访问的IIS web日志,对日志的里的URL进行访问,然后检索是否包含相关的关键字,有就入库,这只是go实现自动检索及入库,可以加个前端,实现加关键及报警功能

package main


import (

    "bufio"

    "code.google.com/p/mahonia"

    "fmt"

    "io"

    "io/ioutil"

    "labix.org/v2/mgo"

    "labix.org/v2/mgo/bson"

    "log"

    "net"

    "net/http"

    "os"

    "path/filepath"

    "strconv"

    "strings"

    "time"

    //qqwry为IP库

    //ftp "github.com/jlaffaye/ftp"

    //"github.com/yinheli/qqwry"

)


const Version = "CDN1.0"


//var ServerList []ServerMember

//PageKeyWord用来保存可疑Url及这个页面包含有哪些关键字,以及日志记录于哪台服务器及日志文件,并最终插入数据库

type PageHasKeyWord struct {

    Url string

    KeyWords []string

    //    UserIp string

    LogFile string

    ServerIp string

    Audit bool

    Auditor string //记录谁审核

    AuditTime string //记录审核时间

    Level int //可疑级别

}

type DoubtfulKeyWord struct {

    Id bson.ObjectId "_id"

    KeyWord string

    //Level string

}


type UrlsToAnalysit struct {

    Urls []string

    LogFile string

    ServerIp string

    Domain string

}


//定义MonthTOstr映射,用来将月份换为数字string,go里的月份用英文单词表示,要注意的go赋值要在函数体里赋值

var MonthTOstr = make(map[string]string)


func main() {


    //fmt.Printf("%s", getLogFileName())


    if len(os.Args) != 4 {

        log.Fatal("Usage:" + filepath.Base(os.Args[0]) + " log_dir " + "db_server_ip_address test")

        os.Exit(1)

    }


    logFileName, _, _ := getLogFileName()


    fmt.Println(time.Now())


    logDbServer := os.Args[2]

    dir := os.Args[1]

    dbname := "webmonitordb"

    //doubtfulKW为string数组,从数据库中获取可疑检索的URL,注意的是go是区分大小写的,因此要注意mongodb里的库名及collection名大小写

    //doubtfulKW := []string{}

    keyWords := getdoubtfulkeyword(logDbServer, dbname, "DangeroursWords")

    //wordlist := []string{"百家乐", "博彩网", "网上赌场", "娱乐城", "赌博网站", "皇冠投注", "皇冠开户", "真龙娱乐城", "三亚娱乐城"}

    /*for n, item := range keyWords {

        fmt.Println(n, item.KeyWord)

    }*/

    if os.Args[3] == "test" {

        fmt.Println("wait!")

        time.Sleep(time.Duration(90) * time.Second)

    }


    fmt.Println("start!")

    filepath.Walk(dir, func(path string, f os.FileInfo, err error) error {

        if f == nil {

            return err

        }

        if f.IsDir() {

            return nil

        }

        if f.Name() == logFileName {


            //fmt.Println(path)


            //fmt.Println(time.Now())

            urls := logFileAnalysit(path)

            //fmt.Println(urls)

            for _, v := range urls.Urls {

                //fmt.Println(n, ":", v)

                url := "http://" + urls.Domain + v

                //fmt.Println(n, url)

                pagestr := getPage(url)

                findWord(pagestr, url, urls.LogFile, urls.ServerIp, keyWords)


                //fmt.Println(n)

            }


        }


        return nil

    })


}

func logFileAnalysit(logFile string) UrlsToAnalysit {

    readLogFile, err := os.Open(logFile)

    if err != nil {

        log.Fatal(err)

    }

    defer readLogFile.Close()

    //pathFields的作用是将日志path解析成一个数据,从而可以得到日志的域名

    pathFields := strings.Split(logFile, "\\")

    var domainName string

    if len(pathFields) > 3 {

        domainName = pathFields[len(pathFields)-3]

    }

    var Urls UrlsToAnalysit

    Urls.Domain = domainName

    Urls.LogFile = logFile

    Urls.ServerIp = getLocalIpAddr()


    Urls.Urls = append(Urls.Urls, "/") //监控站点首页

    //    analysitTime := time.Now()

    bfLogFile := bufio.NewReader(readLogFile)

    //定义一个gbk转utf8解码器

    //enc := mahonia.NewDecoder("gbk")

    for {

        logRecordStr, err := bfLogFile.ReadString('\n')


        if err == io.EOF {

            //注意,这里要使用break,而不是return,return会使整个个程序退出了,break只是中断当前的for循环

            break

        }

        //以"#"开头的要跳过,iiS 日志前几行是做了注释的

        if strings.HasPrefix(logRecordStr, "#") {

            continue

        }

        //recordItems 是个string数组,用来临时保存每行记录里的字段

        //#Fields: date time s-sitename s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) sc-status sc-substatus sc-win32-status time-taken

        fields := strings.Split(logRecordStr, " ")

        //scstatus为服务器状态码的index

        if len(fields) < 13 { //不满16个字段的跳过

            continue

        }

        url := strings.ToLower(fields[5])

        //fields[9] != Urls.ServerIp 过滤掉自己访问的IP记录

        if (strings.HasSuffix(url, "htm") || strings.HasSuffix(url, "html")) && fields[12] == "200" && fields[9] != Urls.ServerIp {

            flag := true

            for _, v := range Urls.Urls {

                if v == url {

                    flag = false

                    break

                }

            }

            if flag {

                Urls.Urls = append(Urls.Urls, url)


            }


        }

    }

    return Urls

}


//getLogFileName()根据当前的时间,生成一个将要被分析日志文件名,因为不同IIS站点每小时时生成一个日志文件,命名格式不exyymmddhh.log

func getLogFileName() (string, string, string) {

    MonthTOstr := map[string]string{"January": "01",

        "February": "02",

        "March": "03",

        "April": "04",

        "May": "05",

        "June": "06",

        "July": "07",

        "August": "08",

        "September": "09",

        "October": "10",

        "November": "11",

        "December": "12"}

    timenow := time.Now()

    year, month, day := timenow.Date()

    //monthStr := month.String()


    hour, _, _ := timenow.Clock()

    yearStr := strings.TrimLeft(strconv.Itoa(year), "20") //去掉前面的四位数年份如"2014"年的“20”

    dayStr, hourStr := strconv.Itoa(day), strconv.Itoa(hour)

    if day < 10 {

        dayStr = "0" + dayStr

    }

    if hour < 10 {

        hourStr = "0" + hourStr

    }


    fileName := "ex" + yearStr + MonthTOstr[month.String()] + dayStr + hourStr + ".log"

    logDay := yearStr + MonthTOstr[month.String()] + dayStr

    logMonth := yearStr + MonthTOstr[month.String()]


    //monthSrt := strconv.Itoa(timenow.Month())

    //fmt.Println(fileName, logDay)

    return fileName, logDay, logMonth

    //fmt.Println(fileName)


}

func getPage(page string) string {

    resp, err := http.Get(page)

    if err != nil {

        log.Fatal(err)

    }

    defer resp.Body.Close()

    enc := mahonia.NewDecoder("gb2312")

    pageData, _ := ioutil.ReadAll(resp.Body)

    //return string(pageData)

    PageStr := enc.ConvertString(string(pageData))

    return PageStr


}

func findWord(str, url, logfile, serverip string, wordlist []DoubtfulKeyWord) {

    var phkw PageHasKeyWord

    flag := false

    for _, item := range wordlist {


        if strings.Contains(str, item.KeyWord) {

            //fmt.Println("the page contains the word:", item.KeyWord, url)


            phkw.KeyWords = append(phkw.KeyWords, item.KeyWord)

            flag = true


        }

    }

    if flag {

        phkw.Url = url

        phkw.LogFile = logfile

        phkw.ServerIp = serverip

        phkw.Audit = false

        phkw.Level = len(phkw.KeyWords)

        interdatadb("192.168.111.28", "webmonitordb", "dangerPage", phkw)


    }

}


func interdatadb(dbserver, dbname, celection string, items PageHasKeyWord) {

    session, err := mgo.Dial(dbserver)

    if err != nil {

        panic(err)

    }

    defer session.Close()


    // Optional. Switch the session to a monotonic behavior.

    session.SetMode(mgo.Monotonic, true)


    c := session.DB(dbname).C(celection)

    //fmt.Println(items)

    //time.Sleep(time.Duration(90) * time.Second)

    err = c.Insert(&PageHasKeyWord{items.Url, items.KeyWords, items.LogFile, items.ServerIp, items.Audit, items.Auditor, items.AuditTime, items.Level})

    //err = c.Insert(&LogItems(logItem))

    if err != nil {

        panic(err)

    }


}


func getdoubtfulkeyword(dbserver, dbname, collection string) []DoubtfulKeyWord {

    // 连接数据库

    session, err := mgo.Dial(dbserver)

    if err != nil {

        panic(err)

    }

    defer session.Close()


    // 获取数据库,获取集合

    c := session.DB(dbname).C(collection)


    kws := []DoubtfulKeyWord{}

    //kw := []string{}

    //var ta task

    err = c.Find(bson.M{}).All(&kws)


    if err != nil {

        panic(err)

    }

    /*for n, item := range kws {

        fmt.Println(n, item.Id, item.KeyWord)

    }*/

    return kws

}

func getLocalIpAddr() string {

    //这里使用一个合法的IP就行了,端口随便,即使没有打开也行,也许因为使用UDP,如果用TCP的话,对端不打开就会有问题

    conn, err := net.Dial("udp", "192.168.18.51:80")

    if err != nil {

        //fmt.Println(err.Error())

        return "127.0.0.1"

    }

    defer conn.Close()

    //fmt.Println(conn.LocalAddr().String())

    //conn.

    //fmt.Println(strings.Split(conn.LocalAddr().String(), ":")[0])

    return strings.Split(conn.LocalAddr().String(), ":")[0]

}


你可能感兴趣的:(日志分析,自动,检测,iis日志,木马,web日志分析,木马检测)