nazo game for web
Excerpt from the web project report, to be open-sourced at an appropriate time.
To ensure security, on December 23, 2022, I redeployed babyweb.py
using Docker, replacing the original Nginx server.
Project Overview
This project is a simple web puzzle game where players read the requirements of each level, deduce the corresponding answers, input them into the URL, and proceed to the next level. If all levels are completed, players are redirected to a success page. The reasons for choosing this project are:
1. To serve as an important component of my personal homepage.
2. To allow players to review web knowledge from this semester while solving puzzles.
3. To experience the entire process from conceptualizing puzzles, designing the frontend, developing the backend, deploying to a server, and maintaining the project.
4. To create a project from scratch, 100% hand-coded.
System Analysis
Business Analysis
Frontend: Utilizes the Bootstrap framework, with jQuery and AJAX employed in Level 10 for submitting user input.
Backend: Web routing and POST requests in Level 8 are implemented using the Express framework for Node.js, while the backend for Level 10 is built with Flask, using MySQL to store routing data.
Deployment: Source code is synchronized to GitHub, deployed and reverse-proxied using Vercel, with Nginx used locally on the server for reverse proxying.
Business Process
Frontend

Backend and Deployment

Nginx should now be replaced with Docker.
System Design
Overall Design
The project is themed as a challenge-based game that requires web knowledge. It will be deployed on a personal server and utilize technologies such as Bootstrap, jQuery, AJAX, Flask, Node.js, MySQL, Nginx, and Vercel.
Interface Design

Input/Output Design
For levels 1-7 and level 9, submit the answers via the website.
Level 8 will utilize a POST request, and level 10 will involve interaction with the shell.
Module Design

The game is divided into three parts: first, the welcome screen, which provides the game rules and introduction; then the game interface, containing levels 1 to 10 and error messages; and finally, the success screen, displaying a success message and about information.
Specific Implementation
Personal Homepage Module - junyu33.me
Static webpage, implemented with pure HTML + CSS:
Use Bootstrap to reserve the central 1/2 area for content. When the height is set to 90vh, centering the content vertically works perfectly. The upper section contains the title, and the middle image uses the border-radius: 50%
property. Then use an <hr>
as a divider, and the lower section includes 4 href subdomains implemented with Bootstrap buttons.
Puzzle Game Module——nazo.junyu33.me
The following content contains partial solution analysis. Proceed with caution!!
The Nazo game involves solving a problem presented on the page and entering the answer after the domain name to proceed to the next level. After completing all the levels designed by the creator, players can access a congratulatory page indicating they have cleared the game.
This is a comprehensive full-stack project and the main component of this course design. It utilizes technologies such as HTML, CSS, JavaScript, Bootstrap, jQuery, AJAX, Node.js, Flask, Nginx, and MySQL.
Welcome Interface
Primarily introduces the game rules. The page layout is similar to the personal homepage, with Bootstrap-styled buttons that use routing to link to the first level.
Level 1
This level is a simple test of HTML5 history and the player's Googling skills, with nothing particularly special. Here, the Bootstrap card element is used to distinguish between the level name and the content. Subsequent questions will follow this template.
Level 2
Testing the player's basic knowledge of HTML5 keywords. Here, Bootstrap's list elements are used to present the four provided options, and the code element is utilized to display inline code snippets.
Level 3
A simple test of the player's understanding of CSS content, requiring them to check the CSS source code to identify the font used.
Level 4
This level tests your understanding of CSS hex and RGB color codes as well as your ability to distinguish colors by eye. You may use the color picker in the developer tools to complete this task. JavaScript pop-up windows are used here to provide corresponding hints.
Level 5
This level tests the player's understanding of CSS positioning, still using a multiple-choice format with four options to choose from. There is nothing particularly special about this stage.
Level 6
This level tests the user's understanding of JavaScript data types. To limit possible answers, the question specifies the starting characters (while also hinting at the character length to reduce difficulty making brute-forcing easier).
There are three correct answers for this level. By storing the answers in a database, inconsistencies caused by URL encoding are avoided, allowing multiple routes to point to a single file.
Level 7
This level tests the understanding of JavaScript syntactic sugar reduce
and Bootstrap's responsive image feature. The correct solution involves solving it through brute-force iteration.
Level 8
This level tests the understanding of basic backend operations. Initially, it's clear that all four languages can be used for backend development, so simply inputting these options directly won't work. After inspecting the webpage source code, a comment is found instructing to POST one of the options. Tools such as curl, Hackbar, Postman, or even Burp Suite can be used to achieve this.
Level 9
This level tests the player's understanding of web technologies and information gathering skills. First, by inspecting the webpage source code, it can be observed that each page uses Bootstrap 5. Further examination of Bootstrap 5's CSS reveals numerous instances of the term "flex," which allows option C to be eliminated.
Additionally, by reviewing the source code of the answer to Level 8, a comment can be found: my_server: node.js
. This allows option B to be eliminated.
Since the server in the response header is Vercel and the ping result also points to Vercel's IP address, it can be concluded that the website uses a reverse proxy, thus eliminating option D.
Level 10
This level tests the player's basic penetration testing skills. It might be challenging without a public IP address. (The specific process is detailed in the Flask backend section.)
The input box here uses AJAX and jQuery technology to combine the user's command with the domain name into a GET request sent to the server, and uses the alert
function to receive the server's response content.
Answer Error Page
If the answer is not present in the RUOTE field of the database, the backend will write a 404 in the header and return err.html
.
Clearance Page
If the answer is not found in the RUOTE field of the database, the backend will write a 404 in the header and return err.html.
Node.js Backend
Built using the Express framework, it extracts user input routes through the body-parser
middleware and connects to a database storing routes and corresponding files to determine if the user's input is the correct answer. If it exists, the corresponding HTML is retrieved and written to the response; otherwise, a 404 status is written to the header and err.html
is returned. A dedicated route is used to handle the POST request for the eighth level.
const { readFileSync } = require("fs");
const http = require("http");
const url = require("url");
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser());
var mysql = require('mysql');
var connection = mysql.createConnection({
host : '47.114.45.27',
user : 'root',
password : '******',
database : 'nazo_answer'
});
connection.connect();
app.post('/31337', function(req, res) {
if ( req.body.Go != undefined) {
var data = readFileSync("./answer.html");
res.end(data);
} else {
var data = readFileSync("./level8.html");
res.end(data);
}
});
app.get('*', function(req, res) {
var route = req.params[0];
connection.query('SELECT `PATH` FROM `ANSWER` WHERE `ROUTE` = "' + route + '"', function (error, results, fields) {
if (error) throw error;
if (results.length > 0) {
var data = readFileSync("./" + results[0].PATH);
res.end(data);
} else {
res.writeHead(404, {'Content-Type': 'text/html'})
var data = readFileSync("./err.html");
res.end(data);
}
});
});
app.listen("80",()=>{
console.log("serv running on http://127.0.0.1:80");
});
Flask Backend
This demonstrates an application with an SSRF vulnerability. First, by examining the source code, it can be seen that user input is not filtered, allowing the construction of specific strings to gain shell access to the server. It can also be observed that the behavior of the date
command causes the shell output to filter letters and numbers shorter than 3 characters, making it impossible to obtain the key using the cat
command. Even attempts to convert it to other encodings like base64 and combine it with head
or tail
commands make it difficult to piece together the complete encoded string.
At the same time, the shell user is not root
and lacks sufficient permissions to modify HTML files, so appending the key to the end of an HTML file is also not feasible.
Now that it's running in Docker, the HTML is not inside the container at all, making this method completely ineffective.
One feasible approach is to reverse the shell to a public IP (usually requiring your own server). The specific process is omitted here.
import os
import pathlib
import tempfile
import contextlib
import urllib
import subprocess
import ctypes
from flask import Flask, request, session, redirect
from flask_cors import CORS
level = 10
challenge_host = "localhost"
hacker_host = "localhost"
app = Flask(__name__)
CORS(app)
def level10():
timezone = request.args.get("timezone", "UTC")
return subprocess.check_output(f"TZ={timezone} date", shell=True, encoding="latin")
@app.route("/", methods=["GET", "POST"])
@app.route("/<path:path>", methods=["GET", "POST"])
def catch_all(path=""):
challenge = globals()[f"level{level}"]
return challenge()
def challenge():
app.run(challenge_host, 8080)
challenge()
Deploying to the Server and Implementing Domain with HTTPS
First, upload the code to a private GitHub repository, then pull it down on the server. For the Node.js backend, since npm install xxx --save
was executed earlier, the server only needs to run npm install
. The Python backend only requires installing Flask and Flask-CORS. Finally, use sudo chmod
to set all files to read-only permissions to prevent writing keys into HTML. Since the Python Flask backend does not require root privileges to start, this will not affect the players' gaming experience later.
Deploying the domain and implementing HTTPS is relatively more challenging. Since my domain was obtained for free (for one year) through the GitHub Student Pack on Namecheap and is not registered with an ICP license, I cannot directly map the DNS A record to the server's IP address. The only solution here is to use reverse proxy.
There are two methods for reverse proxy: one is using Nginx, but Nginx requires an intermediate server that must either be ICP-licensed or located overseas (which creates a circular dependency issue, making it unfeasible). The other method is to use a third-party service for reverse proxy, such as Vercel. The specific steps are as follows:
-
Install Vercel's client locally using npm and log in.
-
Write the reverse proxy configuration file
nazo.json
:
{
"version": 2,
"routes": [
{"src": "/(.*)","dest": "http://<server ip>"}
]
}
- Run
vercel -A nazo.json --prod
.
This method allows access to websites hosted on domestic servers without an ICP license. The drawback is that since domestic DNS may pollute Vercel's domain, the access speed will certainly be slower than that of domestic websites.
For safety, I eventually set up an Nginx server on my own server and reverse-proxied the Flask port.
As for adding subdomains, Vercel allows you to add a custom subdomain under your top-level domain for your frontend and even provides a free SSL certificate to upgrade to HTTPS. I originally intended to use Nginx + Certbot + Crontab to apply for an SSL certificate and renew it periodically, but Vercel's approach is incredibly user-friendly.
Docker Environment Configuration
After finishing my regular school classes, I found time to further optimize my project. With the help of Copilot, I successfully wrote my first Dockerfile:
# python with flask and flask-cors
FROM python:3.10.7
RUN pip install flask flask-cors
COPY ./babyweb.py /
COPY ./key /
WORKDIR /
EXPOSE 5000
CMD ["python", "babyweb.py"]
However, this Dockerfile failed to run on Docker for Windows due to path-related errors. Perhaps Docker downloaded a Windows-based Python environment?
For convenience, I reran the command in an Ubuntu environment. Not only did it run successfully, but the exported image size also decreased from 5GB to 900MB. Linux is indeed more efficient!
Finally, I started the container on the server, tested the exploit, and after confirming everything worked, I changed the reverse proxy JSON to a different port, deployed it on Vercel, and then destroyed the original port.
Course Design Experience and Insights
As a project built entirely from scratch and 100% hand-coded, going through the entire process—from conception, frontend, and backend to deployment and maintenance—was quite challenging. The deployment phase, in particular, was tricky. Initially, I tried to host both the Flask backend and the Node.js backend under the same domain but on different ports. However, I spent a day or two struggling with cross-origin and HTTPS issues without making any progress. Eventually, I had to split the two services into separate domains. Over time, more and more technologies were integrated into the website. From the initial simple setup of Bootstrap + Node.js + a local server, it has evolved into Bootstrap + Express + Flask + Nginx + MySQL + full-site HTTPS + server permission management. This shows that web development is a process of continuous refinement. I hope students playing the Nazo Game will enjoy it and solidify the web knowledge they've acquired.
Project Improvement Notes
-
Not deploying the service using Docker, making it difficult to migrate and posing certain security risks. -
No cookie design to prevent level skipping and record the number of incorrect attempts.
-
Relatively slow access speeds within China.