用户浏览器行为分析引入机器学习k-means算法初探

《WestWorld》第一季第二集有一句很有意思的台词:游客William来到西部世界公园,遇到一个美女接待员,但无法区分对方是否是人类,于是产生了如下对话:


对于很多编写机器行为脚本的攻击者而言,这可能是他们非法行为背后的逻辑——当你无法识别的时候,我还算非法吗?
其实非法永远是非法,作为一个网站维护人员,你需要做的就是将它识别出来

以浏览器行为为例,基于哪些特征量,用何种技术,如何准确识别,都不是那么容易的事情,因为机器行为是变化且持续的。唯一容易的事情,可能是处理方式上——不论是弹验证码还是直接阻断请求,处理掉就好了,而实在不必像剧中William对待Dolores那样,“此情无计可消除”。

笔者公司在浏览器人机行为识别上,目前的做法是基于鼠标&键盘事件分析:例如在浏览器不刷新的前提下,捕获鼠标历史坐标点、移动线路情况、鼠标键盘点击的次数等,看这些指标:

  • 是否不满足设定阈值
  • 是否在黑名单中

但是各有各的问题:

  • 阈值无法动态变化,只能人工检测到异常,再手工去后台调整
  • 脚本更换数据,可能就不在黑名单了

可以说是非常尴尬了。

但这种做法依然有它的合理之处:每次登录的鼠标轨迹完全一样,可以说100%是机器;从开始捕获到验证通过时间小得惊人(例如<0.1s),是机器的概率,也非常之大(也许98%?)...总而言之,这种做法可以概括为:通过开发人员的“拍脑袋”,来想象一些“不像人类操作”的场景,给出一个“自己认为的人类极限值”,从而“描述并阻止机器行为”。

这种做法,绝对是可以杜绝部分机器行为渗入的——因为正常人确实干不出这事。但是一旦你的脑袋拍歪了,或者“想象力枯竭”,那么这便是漏洞所在。

因此笔者在思考,能否利用机器学习中的聚类分析,对用户的浏览器行为进行一定程度辅助判定?

上图来自极验官网“产品特性”截图。很明显,人机行为在一定数据量级下,是存在边界的。从图的分类效果上看,似乎是用到了支持向量机(SVM)

当然,不排除这只是一张产品宣传效果图。但笔者更倾向于不是。因为很久之前在InfoQ看过一个新浪安全团队的视频:反机器人行为系统漫谈,在16:38秒左右有提到一个“幂律分布”的概念,有兴趣的读者可以一看。是否可以证明大部分非法的请求是存在一些特征,从而区别于其他请求的。

所以,通过机器学习/对人的行为特征进行大量的分析,建立安全模型去区分人与机器程序理论可行:利用深度学习构建的神经网络是可以不断地自主学习的,在不断的验证过程中不断的学习新的特征分析。

在此笔者以K-means聚类算法做了一些测试。

聚类和分类在机器学习中是两个概念。

  • 分类是我们根据已知的一些样本(包括属性与类标号)来得到分类模型(即得到样本属性与类标号之间的函数),然后通过此目标函数来对只包含属性的样本数据进行分类。属于supervised learning(监督学习)。
  • 聚类则事先并不知道任何样本的类别标号,希望通过某种算法来把一组未知类别的样本划分成若干类别,聚类的时候,我们并不关心某一类是什么,我们需要实现的目标只是把相似的东西聚到一起,这在机器学习中被称作 unsupervised learning (无监督学习)

以本文讨论的案例为例。如果我们的训练样本里面,包含人机识别的结果,且正确率100%,那么可以基于分类算法,如SVM,得到一个分类器函数,对未来的新数据进行人机判定;而如果这个训练样本里,不包含人机识别结果,或者结果并不准确呢?那我们可以通过选取对行为最有影响的特征量做聚类分析

公司目前的人机识别系统并非100%正确率,但80%还是能保证。因此正好可以用其结果和聚类结果做一个对比,关注两点即可:

  • 各自识别出的人类行为和机器行为数量
  • 聚类结果和公司人机识别结果的一致率是多少

算法选择##

以人机识别为例,红色是恶意程序,绿色是正常用户。我们用肉眼可以一眼看出,有两个分类。但如何让机器看出?


而这就是K-means要解决的问题:
输入:聚类个数k,以及包含 n个数据对象的数据库。
输出:满足方差最小标准的k个聚类。


K-means算法是很典型的基于距离的聚类算法,采用距离作为相似性的评价指标,即认为两个对象的距离越近,其相似度就越大。该算法认为簇是由距离靠近的对象组成的,因此把得到紧凑且独立的簇作为最终目标。

直接上图更容易理解:

K-Means 算法概要

从上图中可以看到,A,B,C,D,E是五个在图中点。而灰色的点是种子点,也就是用来找点群的点。以人机识别为例。假定结果只有人和机器,那么就有两个种子点,所以K=2。则:

  • 随机在图中取K(这里K=2)个种子点
  • 然后对图中的所有点求到这K个种子点的距离,假如点Pi离种子点Si最近,那么Pi属于Si点群。(上图中,可以看到A,B属于上面的种子点,C,D,E属于下面中部的种子点)
  • 接下来,移动种子点到属于他的“点群”的中心。(见图上的第三步)
  • 然后重复第2)和第3)步,直到,种子点没有移动(可以看到图中的第四步上面的种子点聚合了A,B,C,下面的种子点聚合了D,E)

环境准备##

  • Anaconda3(包含机器学习常用lib的python sdk,这里直接用python3)
  • JetBrains PyCharm Community Edition (python ide)
  • 测试数据
    测试数据选用了4个维度,共4w条数据:
    1.鼠标点击次数,以下记为mc
    2.键盘点击次数,以下记为kc
    3.鼠标移动次数,以下记为mmc
    4.最近三次鼠标的坐标点(x1,y1),(x2,y2),(x3,y3),以下记为m3p。这三个点各自独立作为维度,笔者认为意义不大(单个x or y无法反映其特征),因此取了其构成三角形之面积(最近一次提交上送的轨迹大多差不多,则面积不该过于突兀)。

另,由于数据来源于公司,出于保密考虑,不贴出具体数据和格式,只贴笔者原创的测试代码。

测试案例##

笔者对维度4的引入效果持疑,因此做有无维度4两类测试,看哪种更接近公司人机识别系统结果。

  1. 只选前3个维度

# -*- coding: utf-8 -*-
# 导入相应的包
from pyexcel_xls import get_data
import re
from sklearn.cluster import KMeans
import scipy.io as sio
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt


xls_data = get_data(r"test.xlsx")
# print ("Get data type:", type(xls_data))
# print (xls_data['ub'])

all_num = len(xls_data['ub']);
ub_dim_list = [[] for i in range(all_num)]
# print(ub_dim_list)
ub_res_list = []
count = 0

for row_data in xls_data['ub']:
    # print (row_data[0])
    ub_data = eval(row_data[0])

    ub_res_list.append(row_data[1])
    # ub_dim_list[count].append(polyArea(mp_dim_list))
    ub_dim_list[count].append(ub_data['kc'])
    ub_dim_list[count].append(ub_data['mc'])
    ub_dim_list[count].append(ub_data['mmc'])

    count += 1
# print (ub_dim_list)
# print('人机识别结果:', ub_res_list)

# 聚类,2个聚簇
clf = KMeans(n_clusters=2)
y_pred = clf.fit_predict(ub_dim_list)
y_pred = y_pred.tolist()
# print('机器学习结果:', y_pred)


import numpy as np
from matplotlib import pyplot as plt
from scipy import io as spio
from sklearn.decomposition import pca
from sklearn.preprocessing import StandardScaler

# '''归一化数据并作图'''
scaler = StandardScaler()
scaler.fit(ub_dim_list)
x_train = scaler.transform(ub_dim_list)
print(x_train)
# '''拟合数据'''
K=3 # 要降的维度
model = pca.PCA(n_components=K).fit(x_train)   # 拟合数据,n_components定义要降的维度
print(model)
Z = model.transform(x_train)    # transform就会执行降维操作
print(Z)
# print(Z[:,0])

fig = plt.figure()
ax_pca = fig.add_subplot(1, 1, 1, projection='3d')
ax_pca.view_init(elev=10., azim=11)
ax_pca.scatter(Z[:,0], Z[:,1], Z[:,2], c=y_pred)

# '''拟合数据'''
K=2 # 要降的维度
model = pca.PCA(n_components=K).fit(x_train)   # 拟合数据,n_components定义要降的维度
print(model)
Z = model.transform(x_train)    # transform就会执行降维操作
print(Z)
# print(Z[:,0])

fig = plt.figure()
ax_pca = fig.add_subplot(1, 1, 1)
ax_pca.scatter(Z[:,0], Z[:,1], c=y_pred)

rjsb_num = [0, 0]
for i in ub_res_list:
    if (i == 0):
        rjsb_num[0] += 1
    else:
        rjsb_num[1] += 1
print('人机识别结果: 人类:' + str(rjsb_num[0]) + ',机器:' + str(rjsb_num[1]) + ',机器行为占比:' + '%.2f%%' % (
    rjsb_num[1] / all_num * 100))

ml_num = [0, 0]
for i in y_pred:
    if (i == 0):
        ml_num[0] += 1
    else:
        ml_num[1] += 1
print('机器学习结果: 人类:' + str(ml_num[0]) + ',机器:' + str(ml_num[1]) + ',机器行为占比:' + '%.2f%%' % (ml_num[1] / all_num * 100))

right_num = 0
for i in range(0, all_num):
    if ((ub_res_list[i] == y_pred[i] and ub_res_list[i] == 0) or (ub_res_list[i] != 0 and y_pred[i] == 1)):
       right_num += 1

print('以人机识别为参考系,机器学习正确率:%.2f%%' % (right_num / all_num * 100))
plt.show()

其输出为:

人机识别结果: 人类:37903,机器:2097,机器行为占比:5.24%
机器学习结果: 人类:39999,机器:1,机器行为占比:0.00%
以人机识别为参考系,机器学习正确率:94.75%
测试1降维后3D图
测试1降维后2D图

这里有点令人惊讶:假定人机识别的结果全部正确,那么聚类的“正确率”高达94.75%!——而实际上它判定出4w条行为样本中,只有1个机器行为。
造成这一情况的主要原因是,样本中机器行为的数量,可能本来就偏少——因此就算认为这4w条全部是人类行为,正确率都可以高于94.75%

不能说一定不可能,只能说难以令人信服(数据是完全随机的)。因此决定加入维度4测试

  1. 加入维度4


# -*- coding: utf-8 -*-
# 导入相应的包
from pyexcel_xls import get_data
import re
from sklearn.cluster import KMeans
import scipy.io as sio
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt


# 计算多边形面积
def getArea(pts):
    s = 0
    _len = len(pts)
    x, y = zip(*pts)
    j = _len - 1
    for i in range(_len):
        s = s + (x[j] + x[i]) * (y[j] - y[i])
        j = i
    return abs(s / 2.)


xls_data = get_data(r"test.xlsx")
# print ("Get data type:", type(xls_data))
# print (xls_data['ub'])

all_num = len(xls_data['ub']);
ub_dim_list = [[] for i in range(all_num)]
# print(ub_dim_list)
ub_res_list = []
count = 0

for row_data in xls_data['ub']:
    # print (row_data[0])
    ub_data = eval(row_data[0])
    # print(ub_data['mp'])
    mp_dim_split = re.split(';|,', ub_data['m3p'])
    mp_dim_list = [[] for i in range(3)]
    mp_dim = [0.0, 0.0]
    for i in range(0, len(mp_dim_split)):
        if (i % 2 == 0):
            mp_dim[0] = float(mp_dim_split[i])
            mp_dim[1] = float(mp_dim_split[i + 1])
            mp_dim_list[int(i / 2)].extend(mp_dim)
    # print(mp_dim_list)
    # print(polyArea(mp_dim_list))
    ub_res_list.append(row_data[1])
    ub_dim_list[count].append(getArea(mp_dim_list))
    ub_dim_list[count].append(ub_data['kc'])
    ub_dim_list[count].append(ub_data['mc'])
    ub_dim_list[count].append(ub_data['mmc'])
    count += 1
# print (ub_dim_list)
# print('人机识别结果:', ub_res_list)

# 聚类,2个聚簇
clf = KMeans(n_clusters=2)
y_pred = clf.fit_predict(ub_dim_list)
y_pred = y_pred.tolist()
# print('机器学习结果:', y_pred)


import numpy as np
from matplotlib import pyplot as plt
from scipy import io as spio
from sklearn.decomposition import pca
from sklearn.preprocessing import StandardScaler

'''归一化数据并作图'''
scaler = StandardScaler()
scaler.fit(ub_dim_list)
x_train = scaler.transform(ub_dim_list)
print(x_train)
'''拟合数据'''
K=3 # 要降的维度
model = pca.PCA(n_components=K).fit(x_train)   # 拟合数据,n_components定义要降的维度
print(model)
Z = model.transform(x_train)    # transform就会执行降维操作
print(Z)
# print(Z[:,0])

fig = plt.figure()
ax_pca = fig.add_subplot(1, 1, 1, projection='3d')
ax_pca.view_init(elev=10., azim=11)
ax_pca.scatter(Z[:,0], Z[:,1], Z[:,2], c=y_pred)

'''拟合数据'''
K=2 # 要降的维度
model = pca.PCA(n_components=K).fit(x_train)   # 拟合数据,n_components定义要降的维度
print(model)
Z = model.transform(x_train)    # transform就会执行降维操作
print(Z)
# print(Z[:,0])

fig = plt.figure()
ax_pca = fig.add_subplot(1, 1, 1)
ax_pca.scatter(Z[:,0], Z[:,1], c=y_pred)



rjsb_num = [0, 0]
for i in ub_res_list:
    if (i == 0):
        rjsb_num[0] += 1
    else:
        rjsb_num[1] += 1
print('人机识别结果: 人类:' + str(rjsb_num[0]) + ',机器:' + str(rjsb_num[1]) + ',机器行为占比:' + '%.2f%%' % (
    rjsb_num[1] / all_num * 100))

ml_num = [0, 0]
for i in y_pred:
    if (i == 0):
        ml_num[0] += 1
    else:
        ml_num[1] += 1
print('机器学习结果: 人类:' + str(ml_num[0]) + ',机器:' + str(ml_num[1]) + ',机器行为占比:' + '%.2f%%' % (ml_num[1] / all_num * 100))

right_num = 0
for i in range(0, all_num):
    if ((ub_res_list[i] == y_pred[i] and ub_res_list[i] == 0) or (ub_res_list[i] != 0 and y_pred[i] == 1)):
       right_num += 1

print('以人机识别为参考系,机器学习正确率:%.2f%%' % (right_num / all_num * 100))
plt.show()

其输出为:

人机识别结果: 人类:37903,机器:2097,机器行为占比:5.24%
机器学习结果: 人类:37799,机器:2201,机器行为占比:5.50%
以人机识别为参考系,机器学习正确率:89.79%
测试2降维后3D图
测试2降维后2D图

可以看出明显的人机界限。
在对机器行为的识别比例上,两者相差无几;只是有10%左右(大约4k条)的数据判定,两者看法是不一致的。

那么究竟是谁错了?目前还不得而知——这些数据来自浏览器的另一端,本身不会自带label说i'm from robot。但今后可以在测试环境,基于人的行为,同时基于自己编写的机器脚本,来做一次label确凿的比较。而如果是这样,就可以不仅于局限聚类分析了,一些有监督学习的分类算法也可以引入尝试。

另外单靠这种算法也并非万能的——攻击者也可以利用机器学习,模拟出“更合法的非法行为”。
未来势必是机器与机器的战争。

结语

笔者入坑机器学习时间较短,对一些算法的具体应用场景可能理解不够透彻。但思路上,希望能利用现有行为数据,动态的打造一个“用户行为模型”来强化公司人机识别产品。此文纯属抛砖引玉。希望有经验的朋友多多指正,谢谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容