• 随着社会越来越信息化,网络已经成为人们生活中的重要部分。在网络迅猛发展的同时,产生的数据量也越来越大,如何有效地对这些数据进行处理分析,以及使用分析结果对公司的生产实践进行指导,已经成为当前的热点话题。在公司网络环境中,经常存在局域网络滥用公司带宽、网络攻击溯源困难等情况。
  • 如果能对网络中的流量数据进行记录、分析、总结用户行为特征,那么局域网络的管理者就可以根据这些结果进行有效的管理,合理地配置网络资源,有效地定位IP流量的地理位置,发现伪装网络扫描等。本章将带领大家一起学习如何通过Python脚本获取网络中的流量数据并进行有效分析。

    一、流量嗅探

  • 不少人存在这样的观点:只要计算机安装各种专业的安全软件,系统及时更新补丁,密码尽可能复杂,那么计算机就会避免遭到入侵。当然这样的确不容易被入侵,但那也只是针对传统的病毒、木马而言,在流量攻击面前,这些防护就会显得无能为力。

  • 无论如何,当你与其他设备进行通信时就会产生流量,当这些流量脱离了你的计算机后,其安全就不能得到有效的保障,然而这些流量中却包含着你的敏感数据,攻击者完全可以在不入侵你计算机的情况下获得你的敏感数据,这个过程叫流量嗅探

    1、工作原理

  • 互联网中的流量都是以数据包的形式传送的,流量嗅探是对数据包中的流量进行数据分析的一种手段。

  • 通过网络嗅探工具可以捕获到目标计算机网络的数据包,数据包中的数据是根据所采用协议的要求来组织的,只要能够掌握协议的格式,就能够分析出这些数据所表示的意义。例如下图是HTTP数据包

image.png

  • 互联网中的大部分数据都没有采用加密的方式进行传输。例如,我们经常接触的HTTP、FTP、Telnet等协议所传输的数据都是明文传输的,如图所示。这也就意味着,一旦攻击者捕获了数据包,并用协议分析软件对数据包进行分析,那么就可以截获这些数据。

image.png
image.png

  • 早期的局域网(LAN)是由集线器(HUB)构建的。因为HUB不具备交换机的MAC地址表,所以它用广播的方式来发送数据。也就是说,HUB发送的数据,局域网内的每台计算机都是能接收到的。如果把网络接口设置成“混杂”模式,就可以实现不管是不是我的数据,我照单全收的情况,从而可以窃取到他人的流量数据。
  • 交换机的出现逐渐淘汰了HUB。交换机会绑定MAC地址和接口,数据包最终只发往一个终端主机,不会出现HUB的广播式方法数据。如果事先配置MAC地址与对应的接口,理论上非常安全。但是很多人为了偷懒,直接使用了设备默认的模式“自动学习”,使得交换机成了非常容易被欺骗的对象。攻击者只要伪造一个源MAC地址数据包,就能将这个地址的流量关联到自己的接口上,以此获得他人的流量数据。

    2、工具编写

    (1)sniff函数的常用参数

  • 本节我们使用Scapy模块来编写一个流量嗅探工具来嗅探本机网卡上的流量。本次工具的编写需要使用到Scapy中的**sniff()**函数,该函数提供了多个参数,下面我们先了解其中几个比较重要的参数的含义:

    1. [+] iface:指定在哪个网络接口上抓包,不指定则代表所有网卡
    2. [+] count:表示要捕获数据包的数量。默认值为0,表示不限制数量。
    3. [+] filter:流量的过滤规则。使用的是BPFBerkeley Packet Filter,柏克莱封包过滤器)的语法。
    4. [+] prn:定义回调函数,通常使用lambda表达式来写回调函数。当符合filter的流量被捕获时,就会执行回调函数。
    5. [+] store:保存抓取的数据包或者丢弃,1保存,0丢弃
    6. [+] offline:从pcap文件中读取数据包,而不进行嗅探,默认为None
    7. [+] timeout:在给定的事件后停止嗅探,默认为None
  • 其中filter是最常用的参数。因为如果直接使用sniff()函数,会捕获到大量的流量数据,如果不进行过滤,我们很难从里面找到需要的数据库。filter采用的是BPF,利用它来匹配符合我们要求的流量并进行捕获。

    (2)过滤规则

  • BPF的过滤规则(表达式)由一个或多个原语组成。每个原语通常由一个标识(ID、名称或数字)和一个或多个限定词组成。表达式主要有以下三种限定词:

    1. [+] Type:类型限定词,指明ID或数字所代表的含义,例如hostnetport等,若不指定,则默认为host
    2. [+] Dir:方向限定词,指明数据包的传输方向,例如srcdstsrcdst等。
    3. [+] Proto:协议限定词,限定所要匹配的协议,例如tcpudpiparp等。
  • 表达式还可以使用逻辑运算符对原语进行组合,从而创建出更高级的表达式,逻辑运算符主要有以下三种:

    1. [+] &&: 连接运算符。
    2. [+] ||: 选择运算符。
    3. [+] !: 否定运算符。

    (3)例子

  • 下面举几个常见用例,帮助读者理解BPF语法:

    1. [+] 只捕获与网络中某一IP的主机进行交互的流量:host 192.168.10.1
    2. [+] 只捕获与网络中某一MAC地址的主机的交互流量:ether src host 00:88:ca:86:f8:od
    3. [+] 只捕获来源于网络中某一IP的主机流量:src host 192.168.10.1
    4. [+] 只捕获去往网站中某一IP的主机的流量:dst host 192.168.10.1
    5. [+] 只捕获80端口的流量:port 80
    6. [+] 只捕获除80端口以外的其他端口的流量:! port 80
    7. [+] 只捕获ICMP流量:ICMP
    8. [+] 只捕获源地址为192.168.10.1且目的端口为80的流量:src host 192.168.10.1&&dst port 80
  • 下面使用sniff()来进行数据包的捕获。例如,我们捕获目的地址为112.80.248.76的流量,如下所示: ```python from scapy.all import *

sniff(filter=”dst 112.80.248.76”)

  1. - 这时Scapy就已经在开始捕获符合filter表达式的数据包,但是这个时候捕获到数据是不会实时显示出来的,只有取消捕获时才会出现结果,如下所示:
  2. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2893488/1637336243005-6024845f-027e-4544-b7b9-a672e44fbd39.png#clientId=ude985730-bf95-4&from=paste&id=u415dcac6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=401&originWidth=1638&originalType=binary&ratio=1&size=116812&status=done&style=none&taskId=uf85206fd-0c30-468c-add5-c54fc4890a7)
  3. - 如果想要实时显示捕获到的数据包,就要加上prn选项,这里prn的内容我们用lambda表达式来编写,具体内容为`**prn=lambda x.x.summary()**`,如下所示:
  4. ```python
  5. from scapy.all import *
  6. sniff(filter="dst 112.80.248.76", prn=lambda x:x.summary())

image.png

  • 也可以进一步细化打印的内容。我们更改一下lambda表达式,让**sniff()**打印出源IP和目的IP,如下所示: ```python from scapy.all import *

sniff(filter=”dst 112.80.248.76”, prn=lambda x:x[IP].src+”—->”+x[IP].dst)

  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2893488/1637338277247-309f9746-5149-4737-8e26-bdc0282a05cb.png#clientId=ude985730-bf95-4&from=paste&id=uf9f3ed6a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=648&originWidth=1557&originalType=binary&ratio=1&size=230582&status=done&style=none&taskId=u4cad084f-ace1-4dae-a7d6-a867f99bb3c)
  2. - 如果需要更翔实的输出,则会需要更多的代码,那么`**sniff()**`语句整体就会很冗长。我们可以定义一个回调函数,然后让prn调用即可。定义一个`**CallBack()**`函数,代码如下:
  3. ```python
  4. def CallBack(packet):
  5. # 打印源地址和目标地址
  6. print("Source:%s--->Target:%s"%(packet[IP].src,packet[IP].dst))
  7. # 打印TTL值
  8. print("TTL:%s"%packet[IP].ttl)
  9. # 使用内置函数show()打印数据包的内容
  10. print(packet.show())
  • 效果如下所示

image.png

  • 除了显示这些数据包,我们还可以将这些数据包保存,用专业的工具查看、分析这些数据包。保存数据包的格式有很多种,目前最为通用的格式为pcap。可以借助**wrpcap()**函数进行数据包的保存:

    1. packet = sniff(filter="dst 112.80.248.76", count=4)
    2. wrpcap("ms08067.pcap",packet)
  • 同样,我们先通过**sniff()**进行捕获数据包,同时我们在增加一个count选项,表明我们需要捕获数据包的数量,当捕获到规定数量的数据包时,sniff就停止捕获。如下所示: