XCTF攻防世界 Web Writeup (持续更新中)

新手练习区

view-source

F12得到flag。

get_post

HackBar一把梭。

robots

访问robots.txt发现隐藏文件,进入该文件获得flag。

backup

index.php的备份文件应该是index.php.bak,访问一下发现果然是,用记事本打开下载的文件得到flag。

cookie

看一下请求头发现cookie.php

访问cookie.php,在响应头中有flag。

disabled_button

删掉那个disabled,再点击按钮就能拿到flag。

simple_js

查看源码发现js脚本,直觉感觉下面十六进制的才是flag(后来发现我的直觉果然很准)

把这个十六进制转换一下得到几个数字,数字ASCII转码为字符串,加上格式得到flag。

xff_referer

放进BurpSuite的Repeater,按照题目要求修改一下请求头就行。

weak_auth

weak_auth,应该是弱密码。

用户名admin,密码123456被我一下就试出来了……

webshell

直接上菜刀:

当前目录下有个flag.txt,打开就是flag。

command_execution

题目就叫命令执行,于是采用管道符连接ip地址和命令。

一番搜索之后在家目录下发现了flag的文件,cat命令读取一下得到flag。

simple_php

代码:

<?php 
show_source(__FILE__); 
include("config.php"); 
$a=@$_GET['a']; 
$b=@$_GET['b']; 
if($a==0 and $a){ 
    echo $flag1; 
} 
if(is_numeric($b)){ 
    exit(); 
} 
if($b>1234){ 
    echo $flag2; 
} 
?> 

要求a不等于0并且转换后等于0,那么根据PHP弱类型,a可以等于0a。b大于1234数字又不能是数字,b可以等于12345%00(%00是空字符)。

payload:?a=0a&b=12345%00

高手进阶区

ics-06

很简单的题,考察使用burpsuite爆破

进去之后随便点了几下发现这么一个页面:

送分题?(手动滑稽)

发现url里有个id,那么应该不是SQL注入就是爆破或者是伪协议读源码之类的。一番尝试之后觉得应该是爆破。

于是用BurpSuite抓包之后爆破,payload为2333时回显有变化,查看响应信息得到flag。

News Center

用sqlmap就能跑出来的SQL注入题

进去之后看到一个搜索页面,前端还是用的MDUI,233333

一般遇到这种都是SQL注入,看了一下network标签发现是POST方式提交的search参数,于是上sqlmap

sqlmap -u http://111.198.29.45:34382/ --data="search=1"

接下来就是sqlmap一把梭的事情了。

最后的过程有点慢,但是可以跑出来:

mfw

也是比较简单的题,考察的是git源码泄露和php assert任意命令执行

在about页面找到了这些:

Git?那肯定要上githack了。

看了一下flag.php发现啥也没有:

翻了翻源码,在index.php里发现了一些可以利用的东西——熟悉的assert()函数:

我在总结里写过关于这个函数的知识:

https://www.jianshu.com/p/5fd1b624fd7a

assert函数的参数为字符串时,会将字符串当做PHP命令来执行。

例如:assert('phpinfo()')相当于执行<?php phpinfo() ?>

所以只要用一个不存在的文件名闭合掉assert函数并令其等于false,再用system()和strpos()函数读一下./templates/flag.php就可以了。

payload:?/page=about.php', '123') === false and system('cat templates/flag.php') and strpos('templates/flag

flag在网页源代码里,F12就看到了。

NaNNaNNaNNaN-Batman

前端题,主要是考察对JavaScript的熟悉程度

下载附件,用VSCode打开,发现是乱码,但能看到HTML标签和类似于JavaScript的代码:

把这个文件的扩展名改为.html,保存,可以稍微看清楚一点(需要VSCode装插件,就是保存的时候自动把代码格式化的那种插件,想不起来叫什么了):

从上面的代码中可以看出“_”是一个字符串变量,会用后面的eval()函数执行,其他的因为都是乱码所以看不出来了。

卡了一段时间之后忽然想到为什么不能把这个字符串输出一下呢?于是把eval改为console.log,可以在console里得到没有乱码的字符串:

得到的字符串是标准的JavaScript代码,丢进VSCode看一下:

简单来说就是经过一系列正则匹配之后执行一部分代码,但是既然我们有了要执行的代码,那还管正则匹配干什么呢,直接把需要执行的那部分扔到console里执行一下就可以了。

flag到手。

PHP2

考察.phps源码泄露、URL二次编码绕过

一开始我也没明白这个题是什么意思,看了大佬的wp才知道是有.phps源码泄露:

phps文件就是php的源代码文件,通常用于提供给用户(访问者)查看php代码,因为用户无法直接通过Web浏览器看到php文件的内容,所以需要用phps文件代替。其实,只要不用php等已经在服务器中注册过的MIME类型为文件即可,但为了国际通用,所以才用了phps文件类型。

访问index.phps得到源码:

看源码就可以知道这个是很简单的URL二次编码绕过,所以payload:?id=ad%256din

flag到手。

unserialize3

绕过魔法函数sleep()和wakeup()的反序列化漏洞

题目名叫unserialize3,那应该是跟反序列化有关的题目。

看一下源码,出现了__wakeup()这个魔法函数:

unserialize()执行时会检查是否存在一个wakeup()方法。如果存在则先调用wakeup()方法,预先准备对象需要的资源。wakeup()经常用在反序列化操作中。sleep()则相反,是在序列化一个对象的时候被调用。

这个漏洞的核心是:序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup()的执行。

将题目中的类序列化得到结果:O:4:"xctf":1:{s:4:"flag";s:3:"111";}

简单解释一下这个字符串:

O代表结构类型为4表示类名长度,然后是类名成员个数

大括号内的值是:属性名类型长度名称值类型长度

如果我们把传入序列化字符串的属性个数改成比1更大的值,就不会触发__wakeup()方法,进而得到flag。

payload:?code=O:4:"xctf":2:{s:4:"flag";s:3:"111";}

Cat

个人感觉比较综合也比较难的题目,考察的是url编码和django的知识

打开网页发现是一个ping功能,但是输入正常的用户名没有反应,输入ip地址有反应:

本来因为题目名叫cat,又是ping,以为是命令执行,但是尝试了|&等都报错,提示Invalid URL。看来题目的本意并不是命令执行。

看了网上一个大佬的wp才知道这个是跟Django有关的。网站本身是用PHP写的,但是可能有Django的组成部分。

在url里传参%80报错(URL编码是0~127,80的十六进制是128自然报错),从报错信息的目录结构可以知道这个是Django的项目:

其他的没什么信息,又去看了看大佬的wp,发现这个原来还需要用PHP的@前缀:

全部数据协议使用HTTP协议中的POST操作来发送。要发送文件,在文件名前加上@前缀并使用完整路径……

根据Django的目录特性,用@进行文件传递,对文件进行读取之后将内容传给url参数,如果有错误信息就可以得到回显,进而取得更多错误信息、帮助我们拿到flag。

先看看settings.py:

payload: ?url=@/opt/api/api/settings.py

找到数据库文件的存放位置:

看看这个文件:

payload: ?url=@/opt/api/database.sqlite3

搜索CTF得到flag。

ics-05

进去之后随便点点发现了这个:

page=index,那应该可以用伪协议读出源码。

payload:page=php://filter/read=convert.base64-encode/resource=index.php

源码到手,base64解码一下:

<?php
error_reporting(0);

@session_start();
posix_setuid(1000);


?>
<!DOCTYPE HTML>
<html>

<head>
    <meta charset="utf-8">
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="layui/css/layui.css" media="all">
    <title>设备维护中心</title>
    <meta charset="utf-8">
</head>

<body>
    <ul class="layui-nav">
        <li class="layui-nav-item layui-this"><a href="?page=index">云平�设备维护中心</a></li>
    </ul>
    <fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
        <legend>设备列表</legend>
    </fieldset>
    <table class="layui-hide" id="test"></table>
    <script type="text/html" id="switchTpl">
        <!-- 这里的 checked 的状��是演示 -->
        <input type="checkbox" name="sex" value="{{d.id}}" lay-skin="switch" lay-text="å¼€|å
³" lay-filter="checkDemo" {{ d.id==1 0003 ? 'checked' : '' }}>
    </script>
    <script src="layui/layui.js" charset="utf-8"></script>
    <script>
    layui.use('table', function() {
        var table = layui.table,
            form = layui.form;

        table.render({
            elem: '#test',
            url: '/somrthing.json',
            cellMinWidth: 80,
            cols: [
                [
                    { type: 'numbers' },
                     { type: 'checkbox' },
                     { field: 'id', title: 'ID', width: 100, unresize: true, sort: true },
                     { field: 'name', title: '设备�', templet: '#nameTpl' },
                     { field: 'area', title: '区域' },
                     { field: 'status', title: '维护状�', minWidth: 120, sort: true },
                     { field: 'check', title: '设备开å
³', width: 85, templet: '#switchTpl', unresize: true }
                ]
            ],
            page: true
        });
    });
    </script>
    <script>
    layui.use('element', function() {
        var element = layui.element; //导航的hover效果�二级��等功能,需��赖element模�
        //监�导航点击
        element.on('nav(demo)', function(elem) {
            //console.log(elem)
            layer.msg(elem.text());
        });
    });
    </script>

<?php

$page = $_GET[page];

if (isset($page)) {



if (ctype_alnum($page)) {
?>

    <br /><br /><br /><br />
    <div style="text-align:center">
        <p class="lead"><?php echo $page; die();?></p>
    <br /><br /><br /><br />

<?php

}else{

?>
        <br /><br /><br /><br />
        <div style="text-align:center">
            <p class="lead">
                <?php

                if (strpos($page, 'input') > 0) {
                    die();
                }

                if (strpos($page, 'ta:text') > 0) {
                    die();
                }

                if (strpos($page, 'text') > 0) {
                    die();
                }

                if ($page === 'index.php') {
                    die('Ok');
                }
                    include($page);
                    die();
                ?>
        </p>
        <br /><br /><br /><br />

<?php
}}


//方便的实现输å
¥è¾“出的功能,正在开å�‘中的功能,å�ªèƒ½å†
部人员测试

if ($_SERVER['HTTP_X_FORWARDED_FOR'] === '127.0.0.1') {

    echo "<br >Welcome My Admin ! <br >";

    $pattern = $_GET[pat];
    $replacement = $_GET[rep];
    $subject = $_GET[sub];

    if (isset($pattern) && isset($replacement) && isset($subject)) {
        preg_replace($pattern, $replacement, $subject);
    }else{
        die();
    }

}





?>

</body>

</html>

发现了危险函数preg_replace(),存在命令执行漏洞。

preg_replace( pattern , replacement , subject ) : 当pattern指明/e标志时 ,preg_replace()会将replacement部分的代码当作PHP代码执行 (简单的说就是将replacement参数值放入eval()结构中)

payload:/index.php?pat=/test/e&rep=phpinfo()&sub=test,这里还需要一个XFF绕过(至于为什么请看源码),可以用BurpSuite的Repeater来测试:

最终拿到flag的payload:/index.php?pat=/test/e&rep=system('cat%20./s3chahahaDir/flag/flag.php')&sub=test

Triangle

进去之后先读一波JavaScript源码,源码的格式比较乱,可以用Chrome自带的代码格式化功能格式化一下:

util.js

function test_pw(e, _) {
    var t = stoh(atob(getBase64Image("eye")))
      , r = 4096
      , m = 8192
      , R = 12288
      , a = new uc.Unicorn(uc.ARCH_ARM,uc.MODE_ARM);
    a.reg_write_i32(uc.ARM_REG_R9, m),
    a.reg_write_i32(uc.ARM_REG_R10, R),
    a.reg_write_i32(uc.ARM_REG_R8, _.length),
    a.mem_map(r, 4096, uc.PROT_ALL);
    for (var o = 0; o < o1.length; o++)
        a.mem_write(r + o, [t[o1[o]]]);
    a.mem_map(m, 4096, uc.PROT_ALL),
    a.mem_write(m, stoh(_)),
    a.mem_map(R, 4096, uc.PROT_ALL),
    a.mem_write(R, stoh(e));
    var u = r
      , c = r + o1.length;
    return a.emu_start(u, c, 0, 0),
    a.reg_read_i32(uc.ARM_REG_R5)
}
function enc_pw(e) {
    var _ = stoh(atob(getBase64Image("frei")))
      , t = 4096
      , r = 8192
      , m = 12288
      , R = new uc.Unicorn(uc.ARCH_ARM,uc.MODE_ARM);
    R.reg_write_i32(uc.ARM_REG_R8, r),
    R.reg_write_i32(uc.ARM_REG_R9, m),
    R.reg_write_i32(uc.ARM_REG_R10, e.length),
    R.mem_map(t, 4096, uc.PROT_ALL);
    for (var a = 0; a < o2.length; a++)
        R.mem_write(t + a, [_[o2[a]]]);
    R.mem_map(r, 4096, uc.PROT_ALL),
    R.mem_write(r, stoh(e)),
    R.mem_map(m, 4096, uc.PROT_ALL);
    var o = t
      , u = t + o2.length;
    return R.emu_start(o, u, 0, 0),
    htos(R.mem_read(m, e.length))
}
function get_pw() {
    for (var e = stoh(atob(getBase64Image("templar"))), _ = "", t = 0; t < o3.length; t++)
        _ += String.fromCharCode(e[o3[t]]);
    return _
}

secret.js

function test_pw(e, _) {
    var t = stoh(atob(getBase64Image("eye")))
      , r = 4096
      , m = 8192
      , R = 12288
      , a = new uc.Unicorn(uc.ARCH_ARM,uc.MODE_ARM);
    a.reg_write_i32(uc.ARM_REG_R9, m),
    a.reg_write_i32(uc.ARM_REG_R10, R),
    a.reg_write_i32(uc.ARM_REG_R8, _.length),
    a.mem_map(r, 4096, uc.PROT_ALL);
    for (var o = 0; o < o1.length; o++)
        a.mem_write(r + o, [t[o1[o]]]);
    a.mem_map(m, 4096, uc.PROT_ALL),
    a.mem_write(m, stoh(_)),
    a.mem_map(R, 4096, uc.PROT_ALL),
    a.mem_write(R, stoh(e));
    var u = r
      , c = r + o1.length;
    return a.emu_start(u, c, 0, 0),
    a.reg_read_i32(uc.ARM_REG_R5)
}
function enc_pw(e) {
    var _ = stoh(atob(getBase64Image("frei")))
      , t = 4096
      , r = 8192
      , m = 12288
      , R = new uc.Unicorn(uc.ARCH_ARM,uc.MODE_ARM);
    R.reg_write_i32(uc.ARM_REG_R8, r),
    R.reg_write_i32(uc.ARM_REG_R9, m),
    R.reg_write_i32(uc.ARM_REG_R10, e.length),
    R.mem_map(t, 4096, uc.PROT_ALL);
    for (var a = 0; a < o2.length; a++)
        R.mem_write(t + a, [_[o2[a]]]);
    R.mem_map(r, 4096, uc.PROT_ALL),
    R.mem_write(r, stoh(e)),
    R.mem_map(m, 4096, uc.PROT_ALL);
    var o = t
      , u = t + o2.length;
    return R.emu_start(o, u, 0, 0),
    htos(R.mem_read(m, e.length))
}
function get_pw() {
    for (var e = stoh(atob(getBase64Image("templar"))), _ = "", t = 0; t < o3.length; t++)
        _ += String.fromCharCode(e[o3[t]]);
    return _
}

unicorn.js是一个JavaScript框架的源码,暂时不去管它。

我们把get_pw()在console里执行一下得到返回值:

执行enc_pw和test_pw得到返回值:

enc_pw: \x08\x00\xa0\xe1\x09\x10\xa0\xe1\x0a\x20\xa0\xe1\x00\x30\xa0\xe3\x00\x50\xa0\xe3\x00\x40\xd0\xe5\x01\x00\x55\xe3\x01\x00\x00\x1a\x03\x60\x03\xe2\x06\x40\x84\xe0\x06\x40\x84\xe2\x01\x50\x04\xe2\x00\x40\xc1\xe5\x01\x00\x80\xe2\x01\x10\x81\xe2\x01\x30\x83\xe2\x02\x00\x53\xe1\xf2\xff\xff\xba\x00\x00\xa0\xe3\x00\x10\xa0\xe3\x00\x20\xa0\xe3\x00\x30\xa0\xe3\x00\x40\xa0\xe3\x00\x50\xa0\xe3\x00\x60\xa0\xe3\x00\x70\xa0\xe3\x00\x90\xa0\xe3\x00\xa0\xa0\xe3

test_pw: \x09\x00\xa0\xe1\x0a\x10\xa0\xe1\x08\x30\xa0\xe1\x00\x40\xa0\xe3\x00\x50\xa0\xe3\x00\xc0\xa0\xe3\x00\x20\xd0\xe5\x00\x60\xd1\xe5\x05\x60\x86\xe2\x01\xc0\x04\xe2\x00\x00\x5c\xe3\x00\x00\x00\x0a\x03\x60\x46\xe2\x06\x00\x52\xe1\x05\x00\x00\x1a\x01\x00\x80\xe2\x01\x10\x81\xe2\x01\x40\x84\xe2\x03\x00\x54\xe1\xf1\xff\xff\xba\x01\x50\xa0\xe3\x00\x00\xa0\xe3\x00\x10\xa0\xe3\x00\x20\xa0\xe3\x00\x30\xa0\xe3\x00\x40\xa0\xe3\x00\x60\xa0\xe3\x00\x70\xa0\xe3\x00\x80\xa0\xe3\x00\x90\xa0\xe3\x00\xa0\xa0\xe3\x00\xc0\xa0\xe3

直接转换得到的是乱码,卡了一会儿后才发现js源码中出现了“ARM”,同时Unicorn.js里也有不少“ARM”:

百度了一下发现是一种CPU,并找到一个十六进制与ARM代码的转换器:http://armconverter.com/hextoarm/

把text_pw和enc_pw得到的十六进制字符串去掉前面的\x并用转换器转换一下,得到以下汇编代码:

enc_pw:

MOV R0, R8
MOV R1, SB
MOV R2, SL
MOV R3, #0
MOV R5, #0
LDRB    R4, [R0]
CMP R5, #1
BNE #0x28
AND R6, R3, #3
ADD R4, R4, R6
ADD R4, R4, #6
AND R5, R4, #1
STRB    R4, [R1]
ADD R0, R0, #1
ADD R1, R1, #1
ADD R3, R3, #1
CMP R3, R2
BLT #0x14
MOV R0, #0
MOV R1, #0
MOV R2, #0
MOV R3, #0
MOV R4, #0
MOV R5, #0
MOV R6, #0
MOV R7, #0
MOV SB, #0
MOV SL, #0

test_pw:

    MOV R0, SB
    MOV R1, SL
    MOV R3, R8
    MOV R4, #0
    MOV R5, #0
    MOV IP, #0
    LDRB  R2, [R0]
    LDRB  R6, [R1]
    ADD R6, R6, #5
    AND IP, R4, #1
    CMP IP, #0           
    BEQ #0x34
    SUB R6, R6, #3
    CMP R2, R6
    BNE #0x54
    ADD R0, R0, #1
    ADD R1, R1, #1
    ADD R4, R4, #1
    CMP R4, R3
    BLT #0x18
    MOV R5, #1
    MOV R0, #0
    MOV R1, #0
    MOV R2, #0
    MOV R3, #0
    MOV R4, #0
    MOV R6, #0
    MOV R7, #0
    MOV R8, #0
    MOV SB, #0
    MOV SL, #0
    MOV IP, #0

完全不懂汇编的本渣渣表示彻底懵了,难道Web和逆向都精通才是未来的趋势吗???

这其实是道逆向题。(雾)

于是只好去搜了wp,发现上面的汇编码用Python写是这样的:

enc_pw:

def enc_pw(s):
  res = ''
  f = 0
  for i, c in enumerate(s):
    c = ord(c)
    if f == 1:
      c += i & 3
    c += 6
    f = c & 1
    res += chr(c)
  return res

test_pw:

def test_pw(s, t):
  for i, (c, d) in enumerate(zip(s, t)):
    c, d = ord(c), ord(d)
    c += 5
    if i & 1:
      c -= 3
    if c != d:
      return 0
  return 1

解密脚本:

import string

def enc_pw(s):
  res = ''
  f = 0
  for i, c in enumerate(s):
    c = ord(c)
    if f == 1:
      c += i & 3
    c += 6
    f = c & 1
    res += chr(c)
  return res

encrypted = 'XYzaSAAX_PBssisodjsal_sSUVWZYYYb'
flag = ''
for i, c in enumerate(encrypted):
  c = ord(c)
  c -= 5
  if i & 1 != 0:
    c += 3
  for d in string.printable:
    if enc_pw(flag + d)[i] == chr(c):
      flag += d
      break
  print flag

跑一下这个脚本得到flag。flag没有格式。

大佬的wp:https://st98.github.io/diary/posts/2017-10-25-hacklu-ctf-2017.html

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

推荐阅读更多精彩内容