Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8934

【Node.js+Express+MongoDB on Docker】環境構築 2021 (CTFのNoSQLi練習用サーバー)

$
0
0
これは? MongoDBに対するNoSQL Injectionを題材としたCTFの問題サーバーを用意したく作成したので実用には向いてません。 m1z0r3というCTFチームの勉強会用に作成したので所々m1z0r3とかmizoreとかあります。 https://qiita.com/sho_U/items/43f6483aac8ca45a12f6 の記事を参考に作らせていただきました。 用意するファイルたち 全体像 ├── .env ├── .gitignore ├── Dockerfile ├── challenge │   ├── controller │   │   └── initUserController.js │   ├── index.js │   ├── models │   │   └── User.js │   ├── package.json │   ├── routes │   │   └── index.js │   └── views │   ├── index.ejs │   └── js │   └── main.js ├── data │   └── db (空ディレクトリ) ├── docker-compose.yml ├── secret_file │   ├── db.env │   └── db_init │   └── mongo_init_user.js ├── setup.sh └── src (空ディレクトリ) 各ファイルの中身 .env MONGO_INITDB_ROOT_USERNAME=<mongoDBのrootのユーザー名> MONGO_INITDB_ROOT_PASSWORD=<mongoDBのrootのパスワード> MONGO_INITDB_DATABASE=<mongoDBのデータベース名> .gitignore node_modules/ data/ secret_file/ Dockerfile FROM node:12 WORKDIR /app RUN apt-get update && apt-get install -y vim RUN npm install docker-compose.yml version: '3' services: app: build: ./ container_name: nosqli-web ports: - "3004:3000" restart: always working_dir: /app tty: true volumes: - ./src:/app env_file: - ./secret_file/db.env command: bash networks: - mizore-network depends_on: - mongo mongo: image: mongo:latest container_name: nosqli-db ports: - "3005:27017" restart: always environment: MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME} MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD} MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE} volumes: - ./data/db:/data/db - ./secret_file/db_init/:/docker-entrypoint-initdb.d env_file: - ./secret_file/db.env command: - mongod networks: - mizore-network networks: mizore-network: (このネットワーク名は適当に変える) external: true secret_file/db.env DB_USER=<mongoDBのユーザー名(自分は.envと同じにした)> DB_PASS=<mongoDBのパスワード(自分は.envと同じにした)> DB_NAME=<mongoDBのデータベース名(自分は.envと同じにした)> secret_file/db_init/mongo_init_user.js let users = [ { user: "<mongoDBのユーザー名(これも自分は.envと同じにした)>", pwd: "<mongoDBのパスワード(これも自分は.envと同じにした)>", roles: [ { role: "dbOwner", db: "<mongoDBのデータベース名(これも自分は.envと同じにした)>" } ] } ]; for (let i = 0, length = users.length; i < length; ++i) { db.createUser(users[i]); } challenge/controller/initUserController.js const InitUser = require('../models/User'); const user = () => { let initUser = new InitUser({ username: "admin", password: "m1z0r3{...flag....}" }) initUser.save((error, data) => { if (error) { console.log(error); } console.log(data); }) let initUser2 = new InitUser({ username: "admin", password: "mmmmmmimmmmmmm_mm_mmmmmi" }) initUser2.save((error, data) => { if (error) { console.log(error); } console.log(data); }) let initUser3 = new InitUser({ username: "test", password: "passwd" }) initUser3.save((error, data) => { if (error) { console.log(error); } console.log(data); }) } module.exports = { user }; challenge/index.js const express = require("express"); const app = express(); const bodyParser = require("body-parser"); const routes = require("./routes"); const mongoose = require("mongoose"); mongoose.connect( `mongodb://${process.env.DB_USER}:${process.env.DB_PASS}@mongo:27017/<先程のmongoDBのデータベース名>`, { useNewUrlParser: true, useUnifiedTopology: true } ); // "@mongo" のmongoはdocker-compose.ymlの "mongo:" に対応しているのでlocalhostとかじゃできないので注意 // 後ポートの27017はコンテナ側のポート(":"で区切った時の右の方) // { useNewUrlParser: true, useUnifiedTopology: true } の部分はこれを丸コピ(他のだとうまく行かないという記事をみた) app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.set('view engine', "ejs"); app.use(routes); // 最初 /initUser にアクセスしてmongoDBにユーザー(データとしてのユーザー、mongoDBの認証関連のユーザーじゃない)のデータを入れる。 const initUserController = require("./controller/initUserController"); app.get("/initUser", initUserController.user); app.all("*", (req, res) => { return res.status(404).send({ message: '404 page not found' }); }); app.listen(3000, () => console.log("Listening on port 3004")); // 3000はコンテナの方のポートで、3004はホストで実際に開いてるポート challenge/package.json { "name": "mizore-app", "version": "0.0.0", "private": true, "scripts": { "start": "nodemon ./bin/www" }, "dependencies": { "bcrypt": "^5.0.0", "body-parser": "^1.19.0", "connect-flash": "^0.1.1", "cookie-parser": "~1.4.4", "debug": "~2.6.9", "ejs": "^3.1.5", "express": "~4.16.1", "express-ejs-layouts": "^2.5.0", "express-generator": "^4.16.1", "express-session": "^1.17.1", "express-validator": "^6.7.0", "http-errors": "~1.6.3", "http-status-codes": "^2.1.4", "method-override": "^3.0.0", "mongoose": "^5.11.9", "morgan": "~1.9.1", "nodemon": "^2.0.6", "passport": "^0.4.1", "passport-local-mongoose": "^6.0.1" } } challenge/models/User.js const mongoose = require("mongoose"); const Schema = mongoose.Schema; let User = new Schema({ username: { type: String }, password: { type: String } }, { collection: 'users' }); module.exports = mongoose.model("User", User); challenge/routes/index.js var express = require('express'); var router = express.Router(); var User = require("../models/User"); /* GET home page. */ router.get('/', function(req, res, next) { res.render('index', { title: 'Express' }); }); router.post("/login", (req, res) => { let { username, password } = req.body; if(username && password) { return User.find({ username, password }) .then((user) => { if(user.length == 1) { return res.json({logged: 1, message: `Login Successful, welcome back ${user[0].username} : ${user[0].password}` }); } else { return res.json({logged: 0, message: `Login Failed`}); } }) .catch(() => res.json({ message: "Something went wrong" })); } return res.json({ message: "Invalid username or password" }); }); module.exports = router; challenge/views/index.ejs <!DOCTYPE html> <html> <head> <title>NoSQLi Practice</title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <h1>NoSQLi Practice</h1> <p>Search User here</p> <form action="/login" method="post"> <label for="username">username:</label> <input type="text" id="username" name="username"><br/> <label for="password">password:</label> <input type="text" id="password" name="password"><br/> <input type="submit" value="login"> </form> </body> </html> challenge/views/js/main.js const login = document.getElementById("login"); const response = document.getElementById("response"); login.addEventListener("submit", e => { e.preventDefault(); fetch("/login", { method: "POST", body: new URLSearchParams(new FormData(e.target)) }) .then(resp => resp.json()) .then(data => { if(data.logged) { login.remove(); response.innerHTML = data.message; } else { response.innerHTML = data.message; } }); }); 構築する とりあえず以下のsetup.shを作る。 setup.sh # usage: ./setup.sh <containerID> # sudo rm -rf data/db/* && sudo rm -rf src/* # dc build # docker network create mizore-network # dc run app /bin/bash docker restart $1 && \ docker exec $1 npx express-generator -f --view=ejs && \ docker cp ./challenge/index.js $1:/app/ && echo "[OK] index.js" && \ docker cp ./challenge/package.json $1:/app/ && echo "[OK] package.json" && \ docker exec $1 mkdir /app/models && \ docker cp ./challenge/models/User.js $1:/app/models/ && echo "[OK] models/User.js" && \ docker cp ./challenge/routes/index.js $1:/app/routes/ && echo "[OK] routes/index.js" && \ docker cp ./challenge/views/index.ejs $1:/app/views/ && echo "[OK] views/index.ejs" && \ docker exec $1 mkdir /app/views/js && \ docker cp ./challenge/views/js/main.js $1:/app/views/js/ && echo "[OK] views/js/main.js" && \ docker exec $1 mkdir /app/controller && \ docker cp ./challenge/controller/initUserController.js $1:/app/controller/ && echo "[OK] controller/initUserController.js" && \ docker exec $1 npm install && \ docker-compose up -d && \ docker stop $1 && docker rm $1 && \ docker-compose exec app bash # docker-compose exec app node /app/index.js (コメントアウトしてるやつはコメントアウトされたままでいい) chmod +x setup.sh をした後、まずはdocker networkを以下のコマンドで作成する。 docker network create mizore-network # ネットワーク名はdocker-compose.ymlで定義したやつ 次に、以下のコマンドを実行する。 docker-compose build 最後らへんちょろっと数行赤いエラーが出るが気にしない。そのまま以下のコマンドを実行。 docker-compose run app /bin/bash これをするとコンテナが作成されてそのコンテナにbashで入ると思うので、exitで抜け出し、以下のコマンドを実行してそのコンテナIDをコピーする。 docker ps -a コンテナIDがコピーできたら、先程の setup.sh を以下のようにして実行する。 ./setup.sh <コピーしたコンテナID> うまく行けば、最後の docker-compose exec app bashでbashに入れる。 mongoDBにデータをセット + Webサーバー立ち上げ bashに入ったあと、/app ディレクトリにいると思うので、そのまま以下を実行。 node index.js そうするとエラーがなければ、console.log した Listening on 3004 みたいに表示されるはずなので、http://localhost:3004 にアクセスしてちゃんとサイトが表示されてるか確認する。 この段階ではまだmongoDBの中に何もデータが入っていない状態なので、どんなusername/passwordを入れても "Login Failed" になるはず。ちゃんとサイトが表示されたら、今度は http://localhost:3004/initUser にアクセスしてmongoDBにフラグがパスワードのadminとかのユーザーのデータを入れる(node index.jsしたコンソールにユーザーのデータが表示されたらちゃんとデータが入ったということ)。 その後、/initUserにまたアクセスしちゃうと重複してデータが追加されちゃうので、一回データが入ってることを確認したら、challenge/index.jsのapp.get("/initUser")みたいな所をコメントアウトする。 一通りちゃんと動きそうなら、docker-compose upの際に自動でnode index.jsをしてWebサーバーを起動してほしいので、docker-compose.ymlのcommand:のところを以下のように変更する。 docker-compose.yml app: # ... <略> ... command: bash # 上記を下記に変更!! app: # ... <略> ... command: node /app/index.js mongoDBの中の覗き方 mongoDBとうまく連携できなかったので詰まったので、実際にデータがちゃんと入ってるのか確認するため、mongoDBの中を確認する方法が下記。 まず、mongoの方のコンテナに以下のようにして入る。 docker-compose exec mongo bash これでmongoの方のコンテナのbashに入れるので、その後以下を実行する。 mongo <.envに書いたデータベース名> -u <.envに書いたユーザー名> -p これを実行すると "Enter Password" 聞かれるので、.envで書いたパスワードを入れる。 無事入れたら、以下のコマンド実行すれば代々できてるかどうかわかる。 > show collections # usersとか表示される > db.users.find() # これでフラグがパスワードのadminとか出てきたらちゃんと連携されてる

Viewing all articles
Browse latest Browse all 8934

Trending Articles