5.3 示例:使用K近邻算法检测异常操作(一)

黑客入侵Web服务器以后,通常会通过系统漏洞进一步提权,获得root权限。我们试图通过搜集Linux服务器的bash操作日志,通过训练识别出特定用户的操作习惯,然后进一步识别出异常操作行为。完整演示代码请见本书GitHub上的5-2.py。

1.数据搜集和数据清洗

Schonlau在他的个人网站:http://www.schonlau.net/ 上发布了针对Linux操作的训练数据(见图5-3)。

图5-3 Schonlau个人网站

训练数据中包括50个用户的操作日志(见图5-4),每个日志包含15000条操作命令,其中前5000条都是正常操作,后面的10000条日志中随机包含有异常操作。为了便于分析,数据集每100条操作作为一个操作序列,同时进行了标注,每个操作序列只要有1条操作异常就认为这个操作序列异常(见图5-5)。

图5-4 操作日志的目录结构

其中以用户名作为文件名,每个用户的操作都记录在对应的文件里面。

每个文件中,按照操作顺序依次记录了操作命令。

图5-5 操作日志的内容

依次读取每行操作命令,每100个命令组成一个操作序列,保存在列表里面:


with open(filename) as f:
    i=0
    x=[]
    for line in f:
        line=line.strip('\n')
        x.append(line)
        dist.append(line)
        i+=1
        if i == 100:
            cmd_list.append(x)
            x=[]
            i=0

统计最频繁使用的前50个命令和最不频繁的前50个命令:


    fdist = FreqDist(dist).keys()
dist_max=set(fdist[0:50])
dist_min = set(fdist[-50:])

2.特征化

(1)去重操作命令的个数。

以100个命令为统计单元,作为一个操作序列,去重后的操作命令个数作为特征。


f1=len(set(cmd_block))

(2)最频繁使用的前10个命令。

最频繁使用的命令从某种程度上反映出当前用户的操作习惯。


fdist = FreqDist(cmd_block).keys()
f2=fdist[0:10]

(3)最不频繁使用的前10个命令。

最不频繁使用的命令也从某种程度上反映出当前用户的使用习惯。


f3=fdist[-10:]

以user3为例,在完整的数据集合上统计其操作命令,去重后获得一个操作命令的字典:


['more', 'emacs-20', 'netscape', 'netscape', 'netscape', 'netscape', 'netscape', 'netscape', 'netscape', 'netscape', 'netscape', 'netscape', 'aacdec', 'cat', 'aiffplay', 'sh', 'netscape', 'rm', 'sh', 'MediaMai', 'netscape', 'netscape', 'xcal', 'launchef', 'launchef', 'sh', 'gettxt', 'hostname', 'gettxt', 'gettxt', 'gettxt', 'xconfirm', 'endsessi', 'tellwm', 'tellwm', 'xprop', 'endsessi', 'xdm', '4Dwm', 'toolches', 'xwsh', 'csh', 'aiffplay', 'rlogin', 'xterm', 'aiffplay', 'rlogin', 'csh', 'sh', 'sh', 'netscape', 'xwsh', 'csh', 'netscape', 'cpp', 'sh', 'xrdb', 'cpp', 'sh', 'xrdb', 'mkpts', 'env', 'csh', 'csh', 'csh', 'userenv', 'sh', 'csh', 'kill', 'wait4wm', 'xhost', 'xsetroot', 'reaper', 'cat', 'mail', 'csh', 'launchef', 'launchef', 'sh', 'hostname', 'cat', 'mail', 'csh', 'launchef', 'launchef', 'sh', 'hostname', 'cat', 'mail', 'csh', 'launchef', 'launchef', 'sh', 'sh', 'launchef', 'launchef', 'sh', 'netstat', 'netscape', 'netscape']

对应的特征为:


[36, ['gettxt', 'xsetroot', 'xhost', 'kill', 'xrdb', 'wait4wm'], ['cat', 'sh', 'xprop', 'cpp', 'more', 'launchef']]

KNN只能以标量作为输入参数,所以需要将f2和f3标量化,最简单的方式就是和统计的最频繁使用的前50个命令以及最不频繁使用的前50个命令计算重合程度:


f2 = len(set(f2) & set(dist_max))
f3=len(set(f3)&set(dist_min))

3.训练模型

标识文件的内容:每行50列,分别代表每个用户的当前操作序列,正常操作标识为0,异常操作标识为1。

从标识文件中加载针对操作序列正常和异常的标识:


def get_label(filename,index=0):
x=[]
with open(filename) as f:
    for line in f:
        line=line.strip('\n')
        x.append( int(line.split()[index]))
return x

加载user3的操作数据,并将前120个操作序列作为训练序列,后30个操作序列作为测试序列。


user_cmd_list,user_cmd_dist_max,user_cmd_dist_min=load_user_cmd("../data/MasqueradeDat/User3")
user_cmd_feature=get_user_cmd_feature(user_cmd_list,user_cmd_dist_max,user_cmd_dist_min)
labels=get_label("../data/MasqueradeDat/label.txt",2)
y=[0]*50+labels
x_train=user_cmd_feature[0:N]
y_train=y[0:N]
x_test=user_cmd_feature[N:150]
y_test=y[N:150]

调用KNN函数进行训练:


neigh = KNeighborsClassifier(n_neighbors=3)
neigh.fit(x_train, y_train)
y_predict=neigh.predict(x_test)

4.效果验证

测试结果表明,准确率约80%,不是很理想。


score=np.mean(y_test==y_predict)*100