《WestWorld》第一季第二集有一句很有意思的臺詞:游客William來到西部世界公園,遇到一個美女接待員答朋,但無法區(qū)分對方是否是人類蕊蝗,于是產(chǎn)生了如下對話:
對于很多編寫機(jī)器行為腳本的攻擊者而言,這可能是他們非法行為背后的邏輯——當(dāng)你無法識別的時候,我還算非法嗎攻走?
其實(shí)非法永遠(yuǎn)是非法殷勘,作為一個網(wǎng)站維護(hù)人員,你需要做的就是將它識別出來
以瀏覽器行為為例昔搂,基于哪些特征量玲销,用何種技術(shù),如何準(zhǔn)確識別摘符,都不是那么容易的事情贤斜,因?yàn)闄C(jī)器行為是變化且持續(xù)的。唯一容易的事情逛裤,可能是處理方式上——不論是彈驗(yàn)證碼還是直接阻斷請求瘩绒,處理掉就好了,而實(shí)在不必像劇中William對待Dolores那樣带族,“此情無計可消除”锁荔。
筆者公司在瀏覽器人機(jī)行為識別上,目前的做法是基于鼠標(biāo)&鍵盤事件分析:例如在瀏覽器不刷新的前提下蝙砌,捕獲鼠標(biāo)歷史坐標(biāo)點(diǎn)堕战、移動線路情況、鼠標(biāo)鍵盤點(diǎn)擊的次數(shù)等拍霜,看這些指標(biāo):
- 是否不滿足設(shè)定閾值
- 是否在黑名單中
但是各有各的問題:
- 閾值無法動態(tài)變化,只能人工檢測到異常薪介,再手工去后臺調(diào)整
- 腳本更換數(shù)據(jù)祠饺,可能就不在黑名單了
可以說是非常尷尬了。
但這種做法依然有它的合理之處:每次登錄的鼠標(biāo)軌跡完全一樣汁政,可以說100%是機(jī)器道偷;從開始捕獲到驗(yàn)證通過時間小得驚人(例如<0.1s),是機(jī)器的概率记劈,也非常之大(也許98%勺鸦?)...總而言之,這種做法可以概括為:通過開發(fā)人員的“拍腦袋”目木,來想象一些“不像人類操作”的場景换途,給出一個“自己認(rèn)為的人類極限值”,從而“描述并阻止機(jī)器行為”刽射。
這種做法军拟,絕對是可以杜絕部分機(jī)器行為滲入的——因?yàn)檎H舜_實(shí)干不出這事。但是一旦你的腦袋拍歪了誓禁,或者“想象力枯竭”懈息,那么這便是漏洞所在。
因此筆者在思考摹恰,能否利用機(jī)器學(xué)習(xí)中的聚類分析辫继,對用戶的瀏覽器行為進(jìn)行一定程度輔助判定怒见?
上圖來自極驗(yàn)官網(wǎng)“產(chǎn)品特性”截圖。很明顯姑宽,人機(jī)行為在一定數(shù)據(jù)量級下遣耍,是存在邊界的。從圖的分類效果上看低千,似乎是用到了支持向量機(jī)(SVM)配阵。
當(dāng)然,不排除這只是一張產(chǎn)品宣傳效果圖示血。但筆者更傾向于不是棋傍。因?yàn)楹芫弥霸贗nfoQ看過一個新浪安全團(tuán)隊的視頻:反機(jī)器人行為系統(tǒng)漫談,在16:38秒左右有提到一個“冪律分布”的概念难审,有興趣的讀者可以一看瘫拣。是否可以證明大部分非法的請求是存在一些特征,從而區(qū)別于其他請求的告喊。
所以麸拄,通過機(jī)器學(xué)習(xí)/對人的行為特征進(jìn)行大量的分析,建立安全模型去區(qū)分人與機(jī)器程序理論可行:利用深度學(xué)習(xí)構(gòu)建的神經(jīng)網(wǎng)絡(luò)是可以不斷地自主學(xué)習(xí)的黔姜,在不斷的驗(yàn)證過程中不斷的學(xué)習(xí)新的特征分析拢切。
在此筆者以K-means聚類算法做了一些測試。
聚類和分類在機(jī)器學(xué)習(xí)中是兩個概念秆吵。
- 分類是我們根據(jù)已知的一些樣本(包括屬性與類標(biāo)號)來得到分類模型(即得到樣本屬性與類標(biāo)號之間的函數(shù))淮椰,然后通過此目標(biāo)函數(shù)來對只包含屬性的樣本數(shù)據(jù)進(jìn)行分類。屬于supervised learning(監(jiān)督學(xué)習(xí))纳寂。
- 聚類則事先并不知道任何樣本的類別標(biāo)號主穗,希望通過某種算法來把一組未知類別的樣本劃分成若干類別,聚類的時候毙芜,我們并不關(guān)心某一類是什么忽媒,我們需要實(shí)現(xiàn)的目標(biāo)只是把相似的東西聚到一起,這在機(jī)器學(xué)習(xí)中被稱作 unsupervised learning (無監(jiān)督學(xué)習(xí))
以本文討論的案例為例腋粥。如果我們的訓(xùn)練樣本里面晦雨,包含人機(jī)識別的結(jié)果,且正確率100%隘冲,那么可以基于分類算法金赦,如SVM,得到一個分類器函數(shù)对嚼,對未來的新數(shù)據(jù)進(jìn)行人機(jī)判定夹抗;而如果這個訓(xùn)練樣本里,不包含人機(jī)識別結(jié)果纵竖,或者結(jié)果并不準(zhǔn)確呢漠烧?那我們可以通過選取對行為最有影響的特征量做聚類分析杏愤。
公司目前的人機(jī)識別系統(tǒng)并非100%正確率,但80%還是能保證已脓。因此正好可以用其結(jié)果和聚類結(jié)果做一個對比珊楼,關(guān)注兩點(diǎn)即可:
- 各自識別出的人類行為和機(jī)器行為數(shù)量
- 聚類結(jié)果和公司人機(jī)識別結(jié)果的一致率是多少
算法選擇##
以人機(jī)識別為例,紅色是惡意程序度液,綠色是正常用戶厕宗。我們用肉眼可以一眼看出,有兩個分類堕担。但如何讓機(jī)器看出已慢?
而這就是K-means要解決的問題:
輸入:聚類個數(shù)k,以及包含 n個數(shù)據(jù)對象的數(shù)據(jù)庫霹购。
輸出:滿足方差最小標(biāo)準(zhǔn)的k個聚類佑惠。
K-means算法是很典型的基于距離的聚類算法,采用距離作為相似性的評價指標(biāo)齐疙,即認(rèn)為兩個對象的距離越近膜楷,其相似度就越大。該算法認(rèn)為簇是由距離靠近的對象組成的贞奋,因此把得到緊湊且獨(dú)立的簇作為最終目標(biāo)赌厅。
直接上圖更容易理解:
從上圖中可以看到,A轿塔,B特愿,C,D催训,E是五個在圖中點(diǎn)。而灰色的點(diǎn)是種子點(diǎn)宗收,也就是用來找點(diǎn)群的點(diǎn)漫拭。以人機(jī)識別為例。假定結(jié)果只有人和機(jī)器混稽,那么就有兩個種子點(diǎn)采驻,所以K=2。則:
- 隨機(jī)在圖中取K(這里K=2)個種子點(diǎn)
- 然后對圖中的所有點(diǎn)求到這K個種子點(diǎn)的距離匈勋,假如點(diǎn)Pi離種子點(diǎn)Si最近礼旅,那么Pi屬于Si點(diǎn)群。(上圖中洽洁,可以看到A痘系,B屬于上面的種子點(diǎn),C饿自,D汰翠,E屬于下面中部的種子點(diǎn))
- 接下來龄坪,移動種子點(diǎn)到屬于他的“點(diǎn)群”的中心。(見圖上的第三步)
- 然后重復(fù)第2)和第3)步复唤,直到健田,種子點(diǎn)沒有移動(可以看到圖中的第四步上面的種子點(diǎn)聚合了A,B佛纫,C妓局,下面的種子點(diǎn)聚合了D,E)
環(huán)境準(zhǔn)備##
- Anaconda3(包含機(jī)器學(xué)習(xí)常用lib的python sdk呈宇,這里直接用python3)
- JetBrains PyCharm Community Edition (python ide)
- 測試數(shù)據(jù)
測試數(shù)據(jù)選用了4個維度好爬,共4w條數(shù)據(jù):
1.鼠標(biāo)點(diǎn)擊次數(shù),以下記為mc
2.鍵盤點(diǎn)擊次數(shù)攒盈,以下記為kc
3.鼠標(biāo)移動次數(shù)抵拘,以下記為mmc
4.最近三次鼠標(biāo)的坐標(biāo)點(diǎn)(x1,y1)型豁,(x2僵蛛,y2),(x3迎变,y3)充尉,以下記為m3p驼侠。這三個點(diǎn)各自獨(dú)立作為維度句狼,筆者認(rèn)為意義不大(單個x or y無法反映其特征)胳螟,因此取了其構(gòu)成三角形之面積(最近一次提交上送的軌跡大多差不多丘薛,則面積不該過于突兀)。
另,由于數(shù)據(jù)來源于公司审丘,出于保密考慮播急,不貼出具體數(shù)據(jù)和格式,只貼筆者原創(chuàng)的測試代碼。
測試案例##
筆者對維度4的引入效果持疑,因此做有無維度4兩類測試,看哪種更接近公司人機(jī)識別系統(tǒng)結(jié)果。
- 只選前3個維度
# -*- coding: utf-8 -*-
# 導(dǎo)入相應(yīng)的包
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('人機(jī)識別結(jié)果:', ub_res_list)
# 聚類氓侧,2個聚簇
clf = KMeans(n_clusters=2)
y_pred = clf.fit_predict(ub_dim_list)
y_pred = y_pred.tolist()
# print('機(jī)器學(xué)習(xí)結(jié)果:', 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
# '''歸一化數(shù)據(jù)并作圖'''
scaler = StandardScaler()
scaler.fit(ub_dim_list)
x_train = scaler.transform(ub_dim_list)
print(x_train)
# '''擬合數(shù)據(jù)'''
K=3 # 要降的維度
model = pca.PCA(n_components=K).fit(x_train) # 擬合數(shù)據(jù),n_components定義要降的維度
print(model)
Z = model.transform(x_train) # transform就會執(zhí)行降維操作
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)
# '''擬合數(shù)據(jù)'''
K=2 # 要降的維度
model = pca.PCA(n_components=K).fit(x_train) # 擬合數(shù)據(jù)氓癌,n_components定義要降的維度
print(model)
Z = model.transform(x_train) # transform就會執(zhí)行降維操作
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('人機(jī)識別結(jié)果: 人類:' + str(rjsb_num[0]) + '疲迂,機(jī)器:' + str(rjsb_num[1]) + ',機(jī)器行為占比:' + '%.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('機(jī)器學(xué)習(xí)結(jié)果: 人類:' + str(ml_num[0]) + '忙芒,機(jī)器:' + str(ml_num[1]) + ',機(jī)器行為占比:' + '%.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('以人機(jī)識別為參考系跑杭,機(jī)器學(xué)習(xí)正確率:%.2f%%' % (right_num / all_num * 100))
plt.show()
其輸出為:
人機(jī)識別結(jié)果: 人類:37903窄做,機(jī)器:2097,機(jī)器行為占比:5.24%
機(jī)器學(xué)習(xí)結(jié)果: 人類:39999椭盏,機(jī)器:1组砚,機(jī)器行為占比:0.00%
以人機(jī)識別為參考系,機(jī)器學(xué)習(xí)正確率:94.75%
這里有點(diǎn)令人驚訝:假定人機(jī)識別的結(jié)果全部正確掏颊,那么聚類的“正確率”高達(dá)94.75%糟红!——而實(shí)際上它判定出4w條行為樣本中艾帐,只有1個機(jī)器行為。
造成這一情況的主要原因是盆偿,樣本中機(jī)器行為的數(shù)量柒爸,可能本來就偏少——因此就算認(rèn)為這4w條全部是人類行為,正確率都可以高于94.75%
不能說一定不可能事扭,只能說難以令人信服(數(shù)據(jù)是完全隨機(jī)的)捎稚。因此決定加入維度4測試
- 加入維度4
# -*- coding: utf-8 -*-
# 導(dǎo)入相應(yīng)的包
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('人機(jī)識別結(jié)果:', ub_res_list)
# 聚類,2個聚簇
clf = KMeans(n_clusters=2)
y_pred = clf.fit_predict(ub_dim_list)
y_pred = y_pred.tolist()
# print('機(jī)器學(xué)習(xí)結(jié)果:', 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
'''歸一化數(shù)據(jù)并作圖'''
scaler = StandardScaler()
scaler.fit(ub_dim_list)
x_train = scaler.transform(ub_dim_list)
print(x_train)
'''擬合數(shù)據(jù)'''
K=3 # 要降的維度
model = pca.PCA(n_components=K).fit(x_train) # 擬合數(shù)據(jù)求橄,n_components定義要降的維度
print(model)
Z = model.transform(x_train) # transform就會執(zhí)行降維操作
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)
'''擬合數(shù)據(jù)'''
K=2 # 要降的維度
model = pca.PCA(n_components=K).fit(x_train) # 擬合數(shù)據(jù)阳藻,n_components定義要降的維度
print(model)
Z = model.transform(x_train) # transform就會執(zhí)行降維操作
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('人機(jī)識別結(jié)果: 人類:' + str(rjsb_num[0]) + ',機(jī)器:' + str(rjsb_num[1]) + '谈撒,機(jī)器行為占比:' + '%.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('機(jī)器學(xué)習(xí)結(jié)果: 人類:' + str(ml_num[0]) + '腥泥,機(jī)器:' + str(ml_num[1]) + ',機(jī)器行為占比:' + '%.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('以人機(jī)識別為參考系啃匿,機(jī)器學(xué)習(xí)正確率:%.2f%%' % (right_num / all_num * 100))
plt.show()
其輸出為:
人機(jī)識別結(jié)果: 人類:37903蛔外,機(jī)器:2097,機(jī)器行為占比:5.24%
機(jī)器學(xué)習(xí)結(jié)果: 人類:37799溯乒,機(jī)器:2201夹厌,機(jī)器行為占比:5.50%
以人機(jī)識別為參考系,機(jī)器學(xué)習(xí)正確率:89.79%
可以看出明顯的人機(jī)界限裆悄。
在對機(jī)器行為的識別比例上矛纹,兩者相差無幾;只是有10%左右(大約4k條)的數(shù)據(jù)判定光稼,兩者看法是不一致的或南。
那么究竟是誰錯了?目前還不得而知——這些數(shù)據(jù)來自瀏覽器的另一端艾君,本身不會自帶label說i'm from robot采够。但今后可以在測試環(huán)境,基于人的行為冰垄,同時基于自己編寫的機(jī)器腳本蹬癌,來做一次label確鑿的比較。而如果是這樣虹茶,就可以不僅于局限聚類分析了逝薪,一些有監(jiān)督學(xué)習(xí)的分類算法也可以引入嘗試。
另外單靠這種算法也并非萬能的——攻擊者也可以利用機(jī)器學(xué)習(xí)蝴罪,模擬出“更合法的非法行為”董济。
未來勢必是機(jī)器與機(jī)器的戰(zhàn)爭。
結(jié)語
筆者入坑機(jī)器學(xué)習(xí)時間較短洲炊,對一些算法的具體應(yīng)用場景可能理解不夠透徹感局。但思路上,希望能利用現(xiàn)有行為數(shù)據(jù)暂衡,動態(tài)的打造一個“用戶行為模型”來強(qiáng)化公司人機(jī)識別產(chǎn)品询微。此文純屬拋磚引玉。希望有經(jīng)驗(yàn)的朋友多多指正狂巢,謝謝撑毛。