摘要: 这篇先简单说明了nslookup和dig的用法和如何进行区域传送,之后用shell编写了一个简单的批量抓取脚本,涉及到多线程、显示控制、进度控制等等。

DNS工具的使用

nslookup

对于windows平台来说nslookup是集成在系统内部的工具,简单好用。nslookup有两种工作模式:命令行模式交互式模式

命令行模式

用法:nslookup [-qt=type] target-domain dns-server

示例: 请求tyr.so的nameserver: - nslookup -qt=ns tyr.so

使用谷歌公共DNS解析gfwli.st: - nslookup gfwli.st 8.8.8.8

获得TTL过期时间等详细信息(debug): - nslookup -d tyr.so

交互式模式

交互式模式的使用也很简单。

查询SOA类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
C:\Users\conan>nslookup
默认服务器:  c.cc        	//默认dns服务器反向解析
Address:  192.168.1.1  		//默认dns服务器ip地址

> set type=soa         		//设置类型为SOA(起始授权机构)
> njit.edu.cn		   	//解析njit.edu.cn
服务器:  c.cc
Address:  192.168.1.1

非权威应答:
njit.edu.cn
        primary name server = dns1.njit.edu.cn
        responsible mail addr = lilanyou.njit.edu.cn
        serial  = 2014123102
        refresh = 30 (30 secs)
        retry   = 30 (30 secs)
        expire  = 360 (6 mins)
        default TTL = 3600 (1 hour)

进行一次区域传送

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
> set type=ns  			//查询ns记录
> njit.edu.cn
服务器:  c.cc
Address:  192.168.1.1

非权威应答:
njit.edu.cn     nameserver = dns1.njit.edu.cn
njit.edu.cn     nameserver = dns2.njit.edu.cn

> server dns1.njit.edu.cn 	//设置默认dns服务器
默认服务器:  dns1.njit.edu.cn
Address:  202.119.160.11


>ls -d njit.edu.cn  		//ls列出可获得的dns信息,-d 等同于 -t ANY ,列出域中所有记录

这条命令执行完可以看出学校dns服务器是开启了区域传送的,瞬间获得所有dns记录: 内容太多,大体如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 dns                            CNAME  dns1.njit.edu.cn
 dns1                           A      202.119.160.11
 dns2                           A      202.119.160.12
 doc                            A      210.29.25.20
 dpstar                         A      202.119.160.64
 dx                             A      202.119.160.148
 dzsw                           CNAME  emlab.njit.edu.cn
 dzxx                           CNAME  emlab.njit.edu.cn
 ecard                          A      202.119.160.102
 eeblc                          A      202.119.160.149
 eeec                           A      202.119.160.156
 egate                          A      10.0.1.1

共有306条记录,通过抓包可以看到进行了一次完全区域传送(AXFR,full zone transfer),相对的还有增量区域传送(IXFR,incremental zone transfer request)。

区域传送的本意是同步主DNS服务器和备用DNS服务器之间的dns记录而存在的,DNS基于UDP协议,而在区域传送时工作在TCP协议上。正确的配置是对允许区域传送的服务器进行过滤,采用白名单模式而非允许未授权主机与自己进行区域传送,既泄露了dns信息,又浪费了资源。

乌云上已经有区域传送的漏洞提交,不过这个漏洞太广泛了,是一个常见的DNS配置失误。 如果禁止了区域传送,ls -d会提示我们

1
2
3
4
> ls -d nju.edu.cn
[ns.nju.edu.cn]
*** 无法列出域 nju.edu.cn: Query refused
DNS 服务器拒绝将区域 nju.edu.cn 传送到您的计算机。如果这不正确,请检查 IP 地址 202.119.32.12 的 DNS 服务器上 nju.edu.cn 的区域传送安全设置。

dig

linux最常用功能也最给力的dns调试工具。用法很多,最简单的格式为: dig domain @dns-server type

例如我们进行一次AXFR区域传送: dig njit.edu.cn @dns1.njit.edu.cn axfr

如果存在区域传送的漏洞就可以得到我们想要的结果。

用shell写一个自动检测脚本

写到这里想起之前看到一个人用python写了一个程序去批量判断存在区域传送漏洞的dns服务器,于是打算也用shell脚本来写一个。

脚本写完一直在后悔没有直接使用c语言写,因为多线程这里用shell实在是麻烦又不理想。

此脚本支持多线程处理,并且尝试了一下光标的控制,此前一直不知道这个功能应该叫什么。

先上代码然后我来解释一下。

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# !/bin/bash
# description:according to the domain list to find the dns zone transfer vulnerability.
# Tyr Chen
# 2015-2-6
if [ "$# " -ne "1" ];then				# 判断命令行参数
	echo "usage: $0 doman-list-file"
	exit 1
fi
THREAD=200						# 线程数量
tmp_fifo="/tmp/$$.fifo"
tmp_fifo2="/tmp/$$.$$.fifo"
mkfifo $tmp_fifo 					# 创建FIFO管道
mkfifo $tmp_fifo2
exec 6<>$tmp_fifo 					# 定义文件描述符6为指定管道
exec 7<>$tmp_fifo2
echo 1 >&7						# 将数字1压入管道(描述符7)
rm $tmp_fifo 							
rm $tmp_fifo2
for((j=0;j<$THREAD;j++))				# 压入指定数量的换行符到管道(描述符6)
do
    echo >&6
done
count=`cat $1|wc -l`					# 统计域名数量
declare -x pro=1
echo "共有域名$count个"
test -d dnslist || mkdir dnslist
test -e host.txt && rm host.txt
test -e log.txt  && rm log.txt
test -e log.err  && rm log.err
log="./log.txt"
errlog="./log.err"

dig_domain(){

        line=${1# *.}					# 这里给定的域名都是都有www的,因此去除最前面的'www.'
        nsserver=`dig +short ns $line`			# dig ns记录 解析指定域名的namserver
		echo "$line" >> $log
        echo -e "发现DNS服务器:" >> $log
        echo -e "\033[33m$nsserver\033[0m" >>$log
        for ns in $nsserver
        do  
                echo -e "\t正在与服务器$ns尝试区域传送..." >>$log
 				echo -e "\tdomain=$line   nsserver=@$ns" >>$log
                if dig +time=1 axfr $line @$ns 2>>$errlog | tee "./dnslist/$line.txt" | grep -q "SERVER:";then # 尝试区域传送
                        flag=success
						echo -e "\t服务器$ns传送成功" >> $log
						echo $ns >> host.txt
                else
						flag=fail
						echo -e "\t服务器$ns传送失败" >> $log
                        test -e "./dnslist/$line.txt" && rm "./dnslist/$line.txt" 2>>$errlog
                fi  
        done
 	echo -ne "\033\033[100D" 			# 光标移到行首
 	read -u7 pro
 	echo $((pro+1)) >&7
 	pec=`expr $((pro)) \* 100 / $count`		# 计算当前进度
 	echo -ne "当前进度: $pec%\t"
 	printf "%-20s" $line
	if [ "$flag" == "success" ];then  		# 最后显示是防止多线程输出不同步的问题
		printf "\033[31m%s\033[0m" "传送成功"	# 中间的\033是控制字符,后面指定文字颜色
	else
		printf "\033[36m%s\033[0m" "传送失败"
	fi
	echo -ne "\033[36m\033[K"			# 清除从光标到行尾的内容
	echo -ne "\033\033[100D" 			# 光标移到行首
}

while read line    					# while循环使用read来读取文件每一行的内容
do
	read -u6					# 从管道中读取一个字符
	{
		dig_domain $line	# 执行测试函数
		echo >&6 			# 写入一个字符
	}&						# 放入后台运行
	pid=$!						# 此处的$!为最后运行的后台process ID
done <$1          					# 接收来自文件的输入
wait							# wait等待所有后台程序全部执行完毕
exec 6>&-						# 关闭文件描述符
exec 7>&-
echo
echo "done"
echo "`wc -l host.txt| cut -d ' ' -f1`个dns服务器存在区域传送漏洞.列表见 host.txt"
echo "`ls -l dnslist | grep -v totol | wc -l`个域名存在风险.区域传送记录见 dnslist目录"
echo "ps:log.txt记录运行过程中产生的正常日志,log.err记录错误解析日志"
exit 0

讲解

多线程

shell实现多线程的方式其实很简单,就是把需要运行的命令放到后台(&)就可以,shell脚本本身是无法实现真正的多线程处理的,只能通过这种伪多线程的方式来实现。但是当需要给定线程数来并发处理时shell无法直接做到,这时候可以使用命名管道这个小技巧来实现,通过创建一个管道,我们可以可以得到一个FIFO(先入先出)结构的队列,队列深度即为线程数量,以此来控制并发任务数。

上面的代码中,先通过如下语句创建了一个管道文件并和文件描述符6关联,文件描述符可以选用3~9中的一个,其中0,1,2,5已有定义。

创建管道

1
2
3
4
tmp_fifo="/tmp/$$.fifo"
mkfifo $tmp_fifo 						# 创建FIFO管道
exec 6<>$tmp_fifo 						# 把描述符6指向管道文件
rm $tmp_fifo 							# 删除管道文件

写入数据

这里的THREAD即为线程数量,也为队列深度。初始深度为200,即脚本执行时将保证有200个子程序于后台执行。

1
2
3
4
for((j=0;j<$THREAD;j++))				# 压入指定数量的换行符到管道(描述符6)
do
    echo >&6
done

这里输入无论是什么字符都可以,保证管道不为空即可。

多线程主程序

这里的while循环相当于main函数,外层的while循环读取域名列表,循环执行的第一条语句就是从管道中读取一次内容,后执行dig_domain()为检测区域传送的函数,再向队列中写入一个字符,保证队列深度不变,放入后台执行。

这样当脚本执行时,管道初始深度为200,每执行一次循环会产生三个动作

  • 读取管道
  • 执行函数
  • 写入管道

由于写入管道在函数执行结束之后才会触发,于是在脚本执行初期,将会快速循环200次,将所有的任务放在后台执行,然后管道为空,深度为0,此时管道会阻塞,程序停在read -u6这里等待执行,只有当管道中又有数据深度不为空时才会继续执行。

而当函数运行结束时会向管道中写入一条数据,保持着管道深度的动态平衡,以此达到并发线程数量的控制。由于并不是真正的多线程处理而只是放到了后台,所以存在诸多不便,比如这样的脚本在并发处理的过程中异常退出是一件很蛋疼的事情。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
while read line    				# while循环使用read来读取文件每一行的内容
do
	read -u6				# 从管道中读取一次内容
	{
		dig_domain $line		# 执行测试函数
		echo >&6 			# 写入一个字符
	}&					# 放入后台运行
	pid=$!					# 此处的$!为最后运行的后台process ID
done <$1          				# 接收来自文件的输入
wait						# wait等待所有后台程序全部执行完毕
exec 6>&-					# 关闭文件描述符

其中 read -u是从指定的文件描述符读取数据。也使用了read来读取重定向过来的文件流,读取的每一行都保存在变量line中。最后的wait会阻塞到所有的子进程都全部退出为止。

区域传送探测

思路很简单,先使用dig解析指定域名的nameserver,然后使用该nameserver作为dig的dns server来尝试是否允许区域传送。

去掉边边角角,最终的程序就是这样的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
dig_domain(){

	nsserver=`dig +short ns $line`	# dig ns记录 解析指定域名的namserver
    for ns in $nsserver
    do  
	    if dig +time=1 axfr $line @$ns 2>>$errlog | tee "./dnslist/$line.txt" | grep -q "SERVER:";then # 尝试区域传送
	        flag=success
        else
			flag=fail
        fi  
	done

dig +short将直接返回A记录的IP地址而没有其他任何多余内容,此结果不唯一,对于返回的每一个nameserver都将探测是否存在区域传送的漏洞,如果寻找到一个dns服务器存在漏洞就不再探测其他dns服务器,可以在flag=success后面加上一个break。对于dig axfr的结果先做保存,如果传送成功,采取的匹配模式为SERVER:,这里把传送失败和传送成功对比一下可以看到有诸多不同:

传送成功

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#  dig axfr njit.edu.cn @dns1.njit.edu.cn

; <<>> DiG 9.8.4-rpz2+rl005.12-P1 <<>> axfr njit.edu.cn @dns1.njit.edu.cn
;; global options: +cmd
njit.edu.cn.		3600	IN	SOA	dns1.njit.edu.cn. 2014123102 30 30 360 3600
njit.edu.cn.		3600	IN	TXT	"v=spf1 ip4:218.2.192.13/32 ~all"
....
njit.edu.cn.		3600	IN	NS	dns1.njit.edu.cn.
njit.edu.cn.		3600	IN	NS	dns2.njit.edu.cn.
njit.edu.cn.		3600	IN	SOA	dns1.njit.edu.cn. 2014123102 30 30 360 3600
;; Query time: 40 msec
;; SERVER: 202.119.160.11# 53(202.119.160.11)
;; WHEN: Mon Feb 16 21:48:00 2015
>;; XFR size: 297 records (messages 1, bytes 6163)

传送失败

1
2
3
4
5
root@ali:~#  dig axfr ncu.edu.cn @dns1.ncu.edu.cn

; <<>> DiG 9.8.4-rpz2+rl005.12-P1 <<>> axfr ncu.edu.cn @dns1.ncu.edu.cn
;; global options: +cmd
; Transfer failed.
显示控制

以前一直想实现这样的效果:

单行显示

但是不知道如何具体操作,在看到光标控制的时候发现这样可以,也不知道是不是别人都是这么实现的,感觉这样好low…不过面前可以用了。 上面这个程序的代码就是这样的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# !/bin/bash

echo -n "this is :"
for((i=0;i<=100;i++))
do
	if [ $i -lt 10 ];then
		echo -ne "\033[36m0\033[0m"
	fi
	echo -ne "\033[36m$i%\033[0m"
	sleep 0.1s
	echo -ne "\033[36m\033[3D"
done
echo

上面echo的选项使用了 -n-e作用分别是不换行和使用扩展选项。光标的默认位置在行末,于是每次输出完把光标向左移动两格即可。if语句是给数字小于10时十位补0用的。

这里echo的 格式:echo -e "\033 颜色属性 字符串 \033 控制属性" 注意: -e,双引号,\033的0,每一部分都不能少,\033使用反斜杠转义并加了前置0表示这是一个八进制数。 颜色属性可以控制前景色和背景色,控制属性可以控制光标的显示、闪烁、位置等。

上面脚本中的echo -ne "\033[36m\033[3D"最后的控制属性\033[3D"就是将光标向左平移3个单位。

参数

字颜色:30—–37

1
2
3
4
5
6
7
8
   echo -e “\033[30m 黑色字 \033[0m”
  echo -e “\033[31m 红色字 \033[0m”
  echo -e “\033[32m 绿色字 \033[0m”
  echo -e “\033[33m 黄色字 \033[0m”
  echo -e “\033[34m 蓝色字 \033[0m”
  echo -e “\033[35m 紫色字 \033[0m”
  echo -e “\033[36m 天蓝字 \033[0m”
  echo -e “\033[37m 白色字 \033[0m”

字背景颜色范围:40—–47

1
2
3
4
5
6
7
8
   echo -e “\033[40;37m 黑底白字 \033[0m”
  echo -e “\033[41;37m 红底白字 \033[0m”
  echo -e “\033[42;37m 绿底白字 \033[0m”
  echo -e “\033[43;37m 黄底白字 \033[0m”
  echo -e “\033[44;37m 蓝底白字 \033[0m”
  echo -e “\033[45;37m 紫底白字 \033[0m”
  echo -e “\033[46;37m 天蓝底白字 \033[0m”
  echo -e “\033[47;30m 白底黑字 \033[0m”

最后的控制属性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
  \033[0m 关闭所有属性
  \033[1m 设置高亮度
  \033[4m 下划线
  \033[5m 闪烁
  \033[7m 反显
  \033[8m 消隐
  \033[30m — \33[37m 设置前景色
  \033[40m — \33[47m 设置背景色
  \033[nA 光标上移n行
  \033[nB 光标下移n行
  \033[nC 光标右移n行
  \033[nD 光标左移n行
  \033[y;xH 设置光标位置
  \033[2J 清屏
  \033[K 清除从光标到行尾的内容
  \033[s 保存光标位置
  \033[u 恢复光标位置
  \033[?25l 隐藏光标
  \033[?25h 显示光标

上面的示例脚本中使用了光标的移动,dns脚本中的代码是这样的

1
2
3
4
5
6
7
8
9
	echo -ne "\033\033[100D" 					
 	printf "%-20s" $line
	if [ "$flag" == "success" ];then  			
		printf "\033[31m%s\033[0m" "传送成功"
	else
		printf "\033[36m%s\033[0m" "传送失败"
	fi
	echo -ne "\033[36m\033[K"					
	echo -ne "\033\033[100D" 				

第一句使用\033[100D来将光标移到行首,字体使用红色字和青色字体显示,显示完毕使用\033[K清楚从光标当行尾的内容,之所以这么做是因为在多线程的情况下,terminal上的输出存在交叉显示的情况,之后光标再回到行首。

如果不是在多线程的情况下,那么保存光标的位置再恢复是更简单和稳定的实现。

进度控制

就是显示当前的进度百分比,最初的想法很简单很单纯,设置一个变量作为基数并export为环境变量,每处理完一个任务则基数++,但是悲剧的发现子进程中改变环境变量对父进程的变量是不起作用的,想想也是。

后来想了一些其他的办法,但由于在200线程的并发处理下始终存在各种各样的问题,最后干脆也继续使用管道来实现了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
tmp_fifo2="/tmp/$$.$$.fifo"
mkfifo $tmp_fifo2
exec 7<>$tmp_fifo2
echo 1 >&7
declare -x pro=1

dig_domain
{
	read -u7 pro
	echo $((pro+1)) >&7
	pec=`expr $((pro)) \* 100 / $count`			
	echo -ne "当前进度: $pec%\t"
}

刚才使用描述符6,这里使用描述符7。管道深度为1且存在基数为1,declare -x声明一个环境变量。每处理完一个任务时,会从管道中读取这个基数,此时管道为空,其他线程若亦在尝试读取该管道会阻塞。读取到该基数后程序会将该基数+1并写入管道,其他线程会继续做相似处理。应该还有更好的处理方式,比如wait,waitpid什么的,当时我就想到这种处理方式了,后来打算拿C语言重写一个,然后就没有后来了…

这里使用expr 来进行百分比的运算,像 bc,let.$(()),expr都能实现运算的功能。

运行

使用方法: ./getAXFR.sh domain.list 运行结束后会产生如下文件:

1
2
3
4
5
6
7
folder/
├── colleges.list
├── dnslist
├── getAXFR.sh
├── host.txt
├── log.err
└── log.txt
  • colleges.list 提供检测的域名,以www开头,每行一个
  • dnslist 该文件夹存放区域传送得到的dns列表
  • getAXFR.sh shell脚本程序
  • host.txt 所有存在漏洞的dns服务器
  • log.err 错误日志
  • log.txt 运行过程的正常日志

下载