本篇为webshell检测的第三篇,主要讲的是基于黑白样本的webshell预测,从样本收集、特征提取、模型训练,最后模型评估这四步,实现一个简单的黑白样本预测模型。
若有误之处,望大佬们指出
Ⅰ 基本实现步骤
- 样本收集:首先,你需要收集大量的黑样本(恶意的webshell)和白样本(正常的web文件)。这些样本将用于训练和测试你的检测模型。
- 特征提取:对于每一个样本,你需要提取出对于webshell检测有用的特征。这些特征可能包括文件的大小、文件的修改时间、文件中包含的特定字符串等等。
- 模型训练:使用你收集的样本和提取的特征,你可以训练一个机器学习模型来进行webshell检测。这个模型可能是一个决策树、随机森林、支持向量机、神经网络等等。
- 模型评估:在模型训练完成后,你需要对模型的性能进行评估。这通常包括计算模型的准确率、召回率、F1值等指标。
关于样本收集过程,需要注意的地方
- 选取合适的正常样本和恶意样本进行降噪、向量化与随机抽样
- 其优点是具备不错的未知威胁检测能力,其缺点是对样本要求较高。
Ⅱ 样本收集
黑样本
直接搜github,搜集近2000+webshell文件,进行数据清洗,最后只得到了525个php的webshell,样本有点少了
白样本
常见的CMS框架:WP、帝国、极致、TP等等
注意:在这里,收集的白样本,小版本迭代的CMS可以不收集,里面存在大量的重复,我是直接收集大版本的源码
下面是我收集的白样本以及下载地址,近4w个左右
- https://github.com/phpmyadmin/phpmyadmin/tree/MAINT_4_0_10
- https://github.com/smarty-php/smarty/tree/v2.6.32
- https://github.com/yiisoft/yii2/tree/2.0.32
接下来就是——样本清洗
白样本清洗大致思路
- 删除无关文件:你可以删除那些与webshell检测无关的文件,例如图片、音频、视频等非代码文件。
- 删除重复文件:如果你的白样本中存在大量的重复文件,那么这些文件可能会对你的模型产生过拟合的影响。因此,你需要删除这些重复的文件。
- 代码格式化:为了使得你的模型能够更好地理解代码的结构,你可以对代码进行格式化,例如删除多余的空格和注释,统一代码的缩进和换行等。
- 提取代码特征:你可以提取代码的一些特征,例如函数的数量、变量的数量、代码的长度等,这些特征可以帮助你的模型更好地理解代码。
- 标记样本:为了训练你的模型,你需要给你的白样本打上标签,表示这些样本是正常的web文件。
黑样本清洗大致思路,样本有点少,清洗流程主要都是去空格、去注释、去重、去空白文件。
Ⅲ 特征提取
通过两种方式:词袋模型或者opcode模型训练,再配合上TF-IDF
TF-IDF是啥?
这是一种统计方法,用来评估一个词对于一个文件集或语料库中的其中一份文件的重要程度,字词的重要性随着它在文件中出现的次数成正比增加, 但是同时也会随着它在语料库中出现的频率成反比下降。
- 词袋模型 + TF-IDF模型训练
基本流程
- 合并黑白样本
- 计算TF-IDF值,并保存模型
def calculate_tfidf(self):
# 合并黑样本和白样本
samples = self.black_samples + self.white_samples
# 计算TF-IDF值
vectorizer = TfidfVectorizer(max_features=1000)
self.features = vectorizer.fit_transform(samples)
# 保存TF-IDF值模型
joblib.dump(vectorizer, os.path.join(self.config_dir, "tfidf_model.pkl"))
# 为黑样本和白样本生成标签
self.labels = [1]*len(self.black_samples) + [0]*len(self.white_samples)
# 保存特征和标签
np.savez(os.path.join(self.config_dir, 'features_labels.npz'), features=self.features.toarray(), labels=self.labels)
print(f"有效的样本总数:{len(samples)}")
最终需要形成一个特征文件,作为后续预测使用
采用词袋模型算法,计算出的样本数量 1w个
【留下疑问】
- 这个特征文件有什么用
- 最后需要怎么进行评分模型处理
- 如何提高一个预测的正确率
Ⅳ 预测模型生成
拿到这个特征文件后,需要将黑样本标记为1,白样本标记为0,然后用以下算法,生成预测模型
- 第一种是朴素贝叶斯算法,
- 第二种是随机森林
用python写了两个算法的实现逻辑
【随机森林实现】
# 加载特征和标签
data = np.load('config/features_labels.npz')
features = data['features']
labels = data['labels']
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(features, labels, test_size=0.2, random_state=42)
# 输出矩阵数量
print(f"训练集的矩阵形状:{X_train.shape}")
print(f"测试集的矩阵形状:{X_test.shape}")
# 创建并训练随机森林模型
clf = RandomForestClassifier(n_estimators=100, random_state=42)
clf.fit(X_train, y_train)
# 保存随机森林预测模型
joblib.dump(clf, "config/rf_model.pkl")
# 预测测试集
y_pred = clf.predict(X_test)
# 计算并打印检出率(召回率)和精确率
recall = recall_score(y_test, y_pred)
precision = precision_score(y_test, y_pred)
朴素贝叶斯生成预测模型
data = np.load('config/features_labels.npz')
features = data['features']
labels = data['labels']
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(features, labels, test_size=0.2, random_state=42)
# 输出训练集和测试集的矩阵形状
print(f"训练集的矩阵形状:{X_train.shape}")
print(f"测试集的矩阵形状:{X_test.shape}")
# 创建并训练朴素贝叶斯模型
clf = MultinomialNB()
clf.fit(X_train, y_train)
# 预测测试集
y_pred = clf.predict(X_test)
# 计算并打印检出率(召回率)和精确率
recall = recall_score(y_test, y_pred)
precision = precision_score(y_test, y_pred)
以下是其预测结果
Ⅴ 实战预测
利用训练好的模型,进行预测处理
# 词袋模型
tfidf_model = joblib.load('config/tfidf_model.pkl')
# 随机森林预测模型
rf_model = joblib.load('config/rf_model.pkl')
# 预处理代码
with open(file_path, 'r', errors='ignore') as f:
code = f.read()
code = preprocess_code(code)
# 提取特征
features = tfidf_model.transform([code])
# 预测
pred = rf_model.predict(features)
效果没预期的那么好
这里会有一个问题,如果内容上不是携带有php文件的标签的,如"<?php>",就会默认评判会webshell
如下面所示
效果很一般,需要调整下
- 使用MLP算法,特征提取使用词袋&TF-IDF模型
- 特征提取使用opcode&n-gram,降低样本的噪点
十分感谢能看到这里,这个预测还有好多好多需要完善的,貌似只使用一种方式,效果都是很一般的,下版本试一下优化下,尽可能是降低黑白样本噪点 + 其余辅助检测手段,进行文件研判。
参考文献
- https://m.freebuf.com/articles/web/254913.html