diff --git a/scenarios/generate_index.py b/scenarios/generate_index.py new file mode 100644 index 0000000..0adfbe8 --- /dev/null +++ b/scenarios/generate_index.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import json +import re +from pathlib import Path + +ALLOWED_TAGS = [ + "advent2025", + "apache", + "bash", + "c", + "caddy", + "clickhouse", + "csv", + "data processing", + "disk volumes", + "dns", + "docker", + "envoy", + "etcd", + "git", + "golang", + "gunicorn", + "hack", + "haproxy", + "harbor", + "hashicorp vault", + "helm", + "java", + "jenkins", + "json", + "kubernetes", + "linux-other", + "mysql", + "nginx", + "node.js", + "php", + "podman", + "postgres", + "prometheus", + "python", + "rabbitmq", + "redis", + "sql", + "sqlite", + "ssh", + "ssl", + "supervisord", + "systemd", + "realistic", + "interviews", + "new", + "pro", + "business", +] +ALLOWED_SET = {tag.lower(): tag for tag in ALLOWED_TAGS} + + +ROOT = Path(__file__).resolve().parent +OUTPUT_FILE = ROOT / "scenario-index.json" + + +def extract_title(text: str) -> str: + match = re.search(r"^#\s+(.*)", text, flags=re.MULTILINE) + return match.group(1).strip() if match else "Untitled" + + +def extract_description(text: str) -> str: + match = re.search( + r"##\s+Description[: ]*\n+(.*?)(?:\n##|\Z)", + text, + flags=re.DOTALL | re.IGNORECASE, + ) + if match: + description = match.group(1) + else: + fallback = re.search(r"^#.*?\n+(.*?)(?:\n##|\Z)", text, flags=re.DOTALL | re.MULTILINE) + description = fallback.group(1) if fallback else "" + description = re.sub(r"`([^`]*)`", r"\1", description) + description = re.sub(r"\s+", " ", description) + return description.strip() + + +def extract_meta(text: str, label: str) -> str: + pattern = rf"(?mi)^[>\*\s-]*{label}\s*:?\s*([^\n<]+)" + inline = re.search(pattern, text) + if inline: + return inline.group(1).strip() + bold = re.search(rf"(?i){label}\s*:\s*([^<\n]+)", text) + if bold: + return bold.group(1).strip() + return "" + + +def extract_tags(text: str, slug: str, title: str) -> list[str]: + tags: list[str] = [] + heading = re.search( + r"##\s+Tags\s*\n+(.*?)(?:\n##|\Z)", text, flags=re.DOTALL | re.IGNORECASE + ) + if heading: + for line in heading.group(1).splitlines(): + line = line.strip(" -*\t") + if line: + tags.append(line) + else: + line = re.search(r"^tags?:\s*(.+)$", text, flags=re.MULTILINE | re.IGNORECASE) + if line: + raw = line.group(1) + tags.extend([part.strip() for part in re.split(r"[;,]", raw) if part.strip()]) + blob = f"{slug} {title} {text}".lower() + keyword_map = [ + (["advent"], "advent2025"), + (["apache"], "apache"), + ([" bash", "shell", "command"], "bash"), + ([" c "], "c"), + (["caddy"], "caddy"), + (["clickhouse"], "clickhouse"), + (["csv"], "csv"), + (["data", "processing"], "data processing"), + (["disk", "volume", "lvm"], "disk volumes"), + (["dns"], "dns"), + (["docker", "container", "compose"], "docker"), + (["envoy"], "envoy"), + (["etcd"], "etcd"), + (["git"], "git"), + ([" golang", " go "], "golang"), + (["gunicorn"], "gunicorn"), + (["hack", "ctf", "exploit"], "hack"), + (["haproxy"], "haproxy"), + (["harbor"], "harbor"), + (["vault"], "hashicorp vault"), + (["helm"], "helm"), + (["java"], "java"), + (["jenkins"], "jenkins"), + (["json"], "json"), + (["kubernetes", "k8s", "kubectl", "namespace"], "kubernetes"), + (["linux", "bash", "shell"], "linux-other"), + (["mysql"], "mysql"), + (["nginx"], "nginx"), + (["node.js", "nodejs", "node "], "node.js"), + (["php"], "php"), + (["podman"], "podman"), + (["postgres", "postgresql"], "postgres"), + (["prometheus"], "prometheus"), + (["python"], "python"), + (["rabbitmq"], "rabbitmq"), + (["redis"], "redis"), + ([" sql", "query"], "sql"), + (["sqlite"], "sqlite"), + (["ssh"], "ssh"), + ([" tls", "ssl"], "ssl"), + (["supervisor"], "supervisord"), + (["systemd"], "systemd"), + (["interview"], "interviews"), + (["realistic"], "realistic"), + ([" new "], "new"), + (["pro"], "pro"), + (["business"], "business"), + ] + for keywords, mapped in keyword_map: + if any(word in blob for word in keywords): + tags.append(mapped) + seen = set() + deduped = [] + for tag in tags: + key = tag.lower() + if key not in seen: + seen.add(key) + deduped.append(tag) + filtered: list[str] = [] + for tag in deduped: + canonical = ALLOWED_SET.get(tag.lower()) + if canonical and canonical not in filtered: + filtered.append(canonical) + priority = [tag for tag in ALLOWED_TAGS if tag in filtered] + return priority[:3] + + +def to_excerpt(text: str, limit: int = 220) -> str: + if len(text) <= limit: + return text + truncated = text[:limit].rsplit(" ", 1)[0].rstrip() + return f"{truncated}..." + + +def build_index() -> list[dict]: + entries: list[dict] = [] + for readme in sorted(ROOT.glob("*/README.md")): + slug = readme.parent.name + text = readme.read_text(encoding="utf-8") + title = extract_title(text) + description = extract_description(text) + tags = extract_tags(text, slug, title) + level = extract_meta(text, "Level") + scenario_type = extract_meta(text, "Type") + access = extract_meta(text, "Access") + time_to_solve = extract_meta(text, "Time to Solve") or extract_meta(text, "Time") + entries.append( + { + "slug": slug, + "title": title, + "description": description, + "excerpt": to_excerpt(description), + "tags": tags, + "level": level, + "type": scenario_type, + "access": access, + "time": time_to_solve, + "readme": f"{slug}/README.md", + } + ) + return entries + + +def main() -> None: + entries = build_index() + OUTPUT_FILE.write_text(json.dumps(entries, indent=2), encoding="utf-8") + print(f"Wrote {len(entries)} entries to {OUTPUT_FILE.relative_to(ROOT)}") + + +if __name__ == "__main__": + main() diff --git a/scenarios/index.html b/scenarios/index.html new file mode 100644 index 0000000..ba024ef --- /dev/null +++ b/scenarios/index.html @@ -0,0 +1,340 @@ + + + + + + SadServers - Scenarios + + + + + + + +
+
+

SadServers Troubleshooting Scenarios

+
+
+
+
+
Total Scenarios
+
0
+
+
+
+
+
+
+
Fix
+
0
+
+
+
+
+
+
+
Build
+
0
+
+
+
+
+
+
+
Learn
+
0
+
+
+
+
+
+ +
+
+
+
+
+
+ 🔍 +
+ +
+
+
+ +
+
+ + +
+
+
+
+
+ +
Loading scenarios...
+ +
+ + + + + + + + + + + + + + +
#ScenarioTimeLevelTypeAccessTagsInfo
+
+ + + + +
+ + + + + + + diff --git a/scenarios/scenario-index.json b/scenarios/scenario-index.json new file mode 100644 index 0000000..fd3cc9c --- /dev/null +++ b/scenarios/scenario-index.json @@ -0,0 +1,967 @@ +[ + { + "slug": "abaokoro", + "title": "\"Abaokoro\": Restore MySQL Databases Spooked by a Ghost", + "description": "There are three databases that need to be restored. You need to create three databases called \"first\", \"second\" and \"third\" and restore the databases using the file \"/home/admin/dbs_to_restore.tar.gz\". If you encounter an issue while restoring the database, fix it. Credit: [Sebastian Segovia](https://www.linkedin.com/in/sebastian-segovia-a7518a228/)", + "excerpt": "There are three databases that need to be restored. You need to create three databases called \"first\", \"second\" and \"third\" and restore the databases using the file \"/home/admin/dbs_to_restore.tar.gz\". If you encounter...", + "tags": [ + "data processing", + "linux-other", + "mysql" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "abaokoro/README.md" + }, + { + "slug": "apia", + "title": "\"Apia\": Needle in a Haystack", + "description": "In a directory _/home/admin/data-, there are multiple files, all of them with same content. One of these files has been modified, a word was added. You need to identify which word it is and put it in the solution file (both newline terminated or not are accepted).", + "excerpt": "In a directory _/home/admin/data-, there are multiple files, all of them with same content. One of these files has been modified, a word was added. You need to identify which word it is and put it in the solution file...", + "tags": [ + "data processing", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "apia/README.md" + }, + { + "slug": "atrani", + "title": "\"Atrani\": Modify a SQlite3 Database", + "description": "A developer created a script _/home/admin/readdb.py_ that tests access to a database. Without modifying the readdb.py file, change the database so that running the script returns the string \"John Karmack\".", + "excerpt": "A developer created a script _/home/admin/readdb.py_ that tests access to a database. Without modifying the readdb.py file, change the database so that running the script returns the string \"John Karmack\".", + "tags": [ + "data processing", + "linux-other", + "sql" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "atrani/README.md" + }, + { + "slug": "bangalore", + "title": "Bangalore: Envoy Panics", + "description": "There is an Envoy proxy routing traffic to two unhealthy backend services but the Envoy container won't start. Your first task is to fix that. When the number of healthy backends falls under a panic threshold (default 50%), Envoy enters a _panic mode_ and it will either send traffic to all upstream hosts or to none at all. We are simulating this condition by returning an HTTP 503 status code from the _/health_ endpoint in both backends. In our case Envoy is sending traffic to all upstream services. Your second task is to change the panic Envoy behaviour so that it does not route any traffic to unhealthy services and instead Envoy returns \"no healthy upstream\". (Do not change anything in the backend services). There can also be other Envoy configuration issues you need to fix.", + "excerpt": "There is an Envoy proxy routing traffic to two unhealthy backend services but the Envoy container won't start. Your first task is to fix that. When the number of healthy backends falls under a panic threshold (default...", + "tags": [ + "docker", + "envoy", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "bangalore/README.md" + }, + { + "slug": "bata", + "title": "\"Bata\": Find in /proc", + "description": "A spy has left a password in a file in _/proc/sys_ . The contents of the file start with _\"secret:\"_ (without the quotes). Find the file and save the word after \"secret:\" to the file _/home/admin/secret.txt_ with a newline at the end (e.g. if the file contents were \"secret:password\" do: echo \"password\" > /home/admin/secret.txt). (Note there's no root/sudo access in this scenario).", + "excerpt": "A spy has left a password in a file in _/proc/sys_ . The contents of the file start with _\"secret:\"_ (without the quotes). Find the file and save the word after \"secret:\" to the file _/home/admin/secret.txt_ with a...", + "tags": [ + "linux-other", + "pro" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "bata/README.md" + }, + { + "slug": "batumi", + "title": "\"Batumi\": Troubleshoot \"A\" cannot connect to \"B\"", + "description": "There is a web server (Caddy) on HTTP port :80 but curl http://127.0.0.1 doesn't work. Find out what's wrong and make the necessary fixes so the web server returns a URL. Note: as a limitation, the file _/home/admin/db_connector.py_ must not be modified so that the challenge is considered solved properly. The web server has to respond on the IP address 127.0.0.1; not only on \"localhost\".", + "excerpt": "There is a web server (Caddy) on HTTP port :80 but curl http://127.0.0.1 doesn't work. Find out what's wrong and make the necessary fixes so the web server returns a URL. Note: as a limitation, the file...", + "tags": [ + "bash", + "caddy", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "batumi/README.md" + }, + { + "slug": "bekasi", + "title": "\"Bekasi\": Supervisor your variables", + "description": "There is an nginx service running on port 443, it is the main web server for the company and looks like a new employee has deployed some changes to the configuration of supervisor and now it is not working as expected. if you try to access curl -k https://bekasi it should return Hello SadServers! but for reason it is not. You cannot modify files from the /home/admin/bekasi folder in order to pass the check.sh You must find out why and fix it.", + "excerpt": "There is an nginx service running on port 443, it is the main web server for the company and looks like a new employee has deployed some changes to the configuration of supervisor and now it is not working as expected....", + "tags": [ + "linux-other", + "nginx", + "supervisord" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "bekasi/README.md" + }, + { + "slug": "belo-horizonte", + "title": "\"Belo-Horizonte\": A Java Enigma", + "description": "(Credit for the idea: fuero)

There is a one-class Java application in your /home/admin directory. Running the program will print out a secret code, or you may be able to extract the secret from the class file without executing it but I'm not providing any special tools for that.

Put the secret code in a /home/admin/solution file, eg echo \"code\" > /home/admin/solution.", + "excerpt": "(Credit for the idea: fuero)

There is a one-class Java application in your /home/admin directory. Running the program will print out a secret code, or you may be able to extract the secret from the class...", + "tags": [ + "disk volumes", + "java", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "belo-horizonte/README.md" + }, + { + "slug": "bengaluru", + "title": "\"Bengaluru\": Kubernetes StatefulSet least known characteristic", + "description": "There's a Kubernetes cluster (created with \"k3d\") with two worker nodes and two pods on the node _k3d-cluster-agent-0_: a Deployment _demo-deployment-..._ and a StatefulSet _demo-statefulset-0_. Their manifests are identical except for the different kind of K8s resource. Make the node hosting the pods unavailable (it \"goes down\" or \"crashes\" without being deleted from k8s), for example with: docker stop k3d-mycluster-agent-0. After waiting for about a minute (_tolerationSeconds_ in the manifest is 30s, we shorten the K8S 5 minutes default so you don't have to wait so much, plus a grace period), both pods are marked as Terminating. While the Deployment pod is evicted and deployed onto the remaining available node _k3d-cluster-agent-1_, the StatefulSet _demo-statefulset-0_ is not (Why?). Make the StatefulSet pod _demo-statefulset-0_ run on the available node. Note: you can use k as a shortcut for kubectl.", + "excerpt": "There's a Kubernetes cluster (created with \"k3d\") with two worker nodes and two pods on the node _k3d-cluster-agent-0_: a Deployment _demo-deployment-..._ and a StatefulSet _demo-statefulset-0_. Their manifests are...", + "tags": [ + "data processing", + "docker", + "json" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "bengaluru/README.md" + }, + { + "slug": "bern", + "title": "\"Bern\": Docker web container can't connect to db container.", + "description": "There are two Docker containers running, a web application (Wordpress or WP) and a database (MariaDB) as back-end, but if we look at the web page, we see that it cannot connect to the database. curl -s localhost:80 |tail -4 returns:

<body id=\"error-page\"> <div class=\"wp-die-message\"><h1>Error establishing a database connection</h1></div></body> </html>

This is not a Wordpress code issue (the image is :latest with some network utilities added). What you need to know is that WP uses \"WORDPRESS_DB_\" environment variables to create the MySQL connection string. See the ./html/wp-config.php WP config file for example (from /home/admin).

", + "excerpt": "There are two Docker containers running, a web application (Wordpress or WP) and a database (MariaDB) as back-end, but if we look at the web page, we see that it cannot connect to the database. curl -s localhost:80...", + "tags": [ + "data processing", + "docker", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "bern/README.md" + }, + { + "slug": "bharuch", + "title": "Bharuch: \"Lost in Translation\"", + "description": "There's a Docker container that runs a web server on port 3000, but it's not working. Using the tooling and resources provided in the server, make the container run correctly.", + "excerpt": "There's a Docker container that runs a web server on port 3000, but it's not working. Using the tooling and resources provided in the server, make the container run correctly.", + "tags": [ + "docker", + "linux-other", + "pro" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "bharuch/README.md" + }, + { + "slug": "bilbao", + "title": "\"Bilbao\": Basic Kubernetes Problems", + "description": "There's a Kubernetes Deployment with an Nginx pod and a Load Balancer declared in the manifest.yml file. The pod is not coming up. Fix it so that you can access the Nginx container through the Load Balancer.

There's no \"sudo\" (root) access.", + "excerpt": "There's a Kubernetes Deployment with an Nginx pod and a Load Balancer declared in the manifest.yml file. The pod is not coming up. Fix it so that you can access the Nginx container through the Load Balancer.

...", + "tags": [ + "bash", + "disk volumes", + "docker" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "bilbao/README.md" + }, + { + "slug": "bucharest", + "title": "\"Bucharest\": Connecting to Postgres", + "description": "A web application relies on the PostgreSQL 13 database present on this server. However, the connection to the database is not working. Your task is to identify and resolve the issue causing this connection failure. The application connects to a database named app1 with the user app1user and the password app1user.

Credit PykPyky", + "excerpt": "A web application relies on the PostgreSQL 13 database present on this server. However, the connection to the database is not working. Your task is to identify and resolve the issue causing this connection failure. The...", + "tags": [ + "data processing", + "linux-other", + "postgres" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "bucharest/README.md" + }, + { + "slug": "buenos-aires", + "title": "\"Buenos Aires\": Kubernetes Pod Crashing", + "description": "There are two pods: \"logger\" and \"logshipper\" living in the default namespace. Unfortunately, logshipper has an issue (crashlooping) and is forbidden to see what logger is trying to say. Could you help fix Logshipper?

Do not change the K8S definition of the logshipper pod. Use \"sudo\".

Because k8s takes a minute or two to change the pod state initially, the check for the scenario is made to fail in the first two minutes.

Credit Srivatsav Kondragunta", + "excerpt": "There are two pods: \"logger\" and \"logshipper\" living in the default namespace. Unfortunately, logshipper has an issue (crashlooping) and is forbidden to see what logger is trying to say. Could you help fix Logshipper?...", + "tags": [ + "bash", + "docker", + "json" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "buenos-aires/README.md" + }, + { + "slug": "capetown", + "title": "\"Cape Town\": Nginx borked", + "description": "There's an Nginx web server installed and managed by systemd. Running curl -I 127.0.0.1:80 returns curl: (7) Failed to connect to localhost port 80: Connection refused , fix it so when you curl you get the default Nginx page.", + "excerpt": "There's an Nginx web server installed and managed by systemd. Running curl -I 127.0.0.1:80 returns curl: (7) Failed to connect to localhost port 80: Connection refused , fix it so when you curl you...", + "tags": [ + "linux-other", + "nginx", + "systemd" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "capetown/README.md" + }, + { + "slug": "chennai", + "title": "\"Chennai\": Pull a Rabbit from a Hat", + "description": "There is a RabbitMQ (RMQ) cluster defined in a docker-compose.yml file.

Bring this system up and then run the producer.py script in such a way that is able to send messages to RMQ. In particular you have to send the message \"hello-lwc\".

- RMQ is a queuing system: messages are put in the queue with a \"producer\" and they are taken out from the other side by a \"consumer\". The queue name has to be the same for both.

- To send the message \"hello-lwc\": python3 ~/producer.py hello-lwc. Should return Message sent to RabbitMQ. \"IncompatibleProtocolError\" means RMQ is not working properly.

- To test consuming it: python3 ~/consumer.py, this will retrieve the next message from the queue and print it. Once everything is working send more than one message so there's at least one in the queue when the validation runs.

- Do not change the consumer.py and producer.py files; if you do the Check My Solution will fail.", + "excerpt": "There is a RabbitMQ (RMQ) cluster defined in a docker-compose.yml file.

Bring this system up and then run the producer.py script in such a way that is able to send messages to RMQ. In particular you have to...", + "tags": [ + "bash", + "docker", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "chennai/README.md" + }, + { + "slug": "command-line-murders", + "title": "\"The Command Line Murders\"", + "description": "This is the Command Line Murders with a small twist as in the solution is different

Enter the name of the murderer in the file /home/admin/mysolution, for example echo \"John Smith\" > ~/mysolution

Hints are at the base of the /home/admin/clmystery directory. Enjoy the investigation!", + "excerpt": "This is the Command Line Murders with a small twist as in the solution is different

Enter the name of the murderer in the file...", + "tags": [ + "bash", + "git", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "command-line-murders/README.md" + }, + { + "slug": "constanta", + "title": "\"Constanta\": Jumping Frog", + "description": "This is a \"hacking\" or Capture The Flag challenge. You need to copy the message at /home/user3/secret.txt into the /home/admin/solution.txt file.", + "excerpt": "This is a \"hacking\" or Capture The Flag challenge. You need to copy the message at /home/user3/secret.txt into the /home/admin/solution.txt file.", + "tags": [ + "hack", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "constanta/README.md" + }, + { + "slug": "depok", + "title": "\"Depok\": Nginx with Brotli", + "description": "As a devops engineer, you're tasked to add compression to the company website, actually a gaming shop. The website is running on an Nginx server, and you decide to add Brotli compression to it. Brotli has became very popular these days because of its high compression ratio. It's a generic-purpose lossless compression algorithm that compresses data using a combination of a modern variant of the LZ77 algorithm, Huffman coding, and 2nd order context modeling. For this purpose, you decided to compile the brotli modules yourself and add them to the Nginx server. The location of the Brotli source code is at /home/admin/ngx_brotli, and nginx source code, which is needed to compile the modules, is located at /home/admin/nginx-1.18.0. You've seen from the official ngx_brotli repository that first you need to compile the brotli dependencies and then configure and make modules for Nginx, once you've done that, you need to add the modules to the Nginx configuration. After installing the modules, you need to make sure the responses from the server are being served with compression. Create a port-forward to port 80 from the server and check the header Content-Encoding, responses must return br for Brotli compression. You could also use curl -H \"Accept-Encoding: br, gzip\" -I http://localhost to check the header. PD: something nice about Brotli is that it fails over to gzip if the client doesn't support Brotli, so curl -H \"Accept-Encoding: gzip\" -I http://localhost should return gzip instead.", + "excerpt": "As a devops engineer, you're tasked to add compression to the company website, actually a gaming shop. The website is running on an Nginx server, and you decide to add Brotli compression to it. Brotli has became very...", + "tags": [ + "data processing", + "linux-other", + "nginx" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "depok/README.md" + }, + { + "slug": "florence", + "title": "\"Florence\": Database Migration Hell", + "description": "You are working as a DevOps Engineer in a company and another devops left the company and left docker-compose unfinished. Generally, the problem revolves around migration and docker compose. You need to deploy application with database Postgresql via docker-compose. Additionally on front of the application there is an Nginx server and you need to fix it as well. You can test it with curl --cacert /etc/nginx/certs/sadserver.crt https://sadserver.local The source of code is in /home/admin/app", + "excerpt": "You are working as a DevOps Engineer in a company and another devops left the company and left docker-compose unfinished. Generally, the problem revolves around migration and docker compose. You need to deploy...", + "tags": [ + "data processing", + "docker", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "florence/README.md" + }, + { + "slug": "geneva", + "title": "\"Geneva\": Renew an SSL Certificate", + "description": "There's an Nginx web server running on this machine, configured to serve a simple \"Hello, World!\" page over HTTPS. However, the SSL certificate is expired. Create a new SSL certificate for the Nginx web server with the same Issuer and Subject (same domain and company information).", + "excerpt": "There's an Nginx web server running on this machine, configured to serve a simple \"Hello, World!\" page over HTTPS. However, the SSL certificate is expired. Create a new SSL certificate for the Nginx web server with the...", + "tags": [ + "linux-other", + "nginx", + "ssl" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "geneva/README.md" + }, + { + "slug": "gitega", + "title": "\"Gitega\": Find the Bad Git Commit", + "description": "The directory at _/home/admin/git_ has a Git repository with a Golang program and a test for it. To execute the test, from this \"git\" directory run: go test. The last (current HEAD) commit fails the test. Suppose the first commit passed the test. Find the (long hash) commit that first broke the test and enter it in the _/home/admin/solution_ file. For example: echo 9e80a7eb1b09385e93ab4a76cb2c93beec48fd9f > /home/admin/solution", + "excerpt": "The directory at _/home/admin/git_ has a Git repository with a Golang program and a test for it. To execute the test, from this \"git\" directory run: go test. The last (current HEAD) commit fails the test. Suppose the...", + "tags": [ + "git", + "golang", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "gitega/README.md" + }, + { + "slug": "hanoi", + "title": "\"Hanoi\": Find the Multitasking Users", + "description": "The Hanoi office has a Linux server with a large number of user accounts and groups. The system administrators need to identify which users belong to multiple groups for better access management. Given two files, users.txt and groups.txt, create a new file /home/admin/multi-group-users.txt containing the usernames of users who belong to more than one group, one username per line, sorted alphabetically. The users.txt file contains a list of usernames, one per line. The groups.txt file contains group names and their members, in the format group_name:user1,user2,user3.", + "excerpt": "The Hanoi office has a Linux server with a large number of user accounts and groups. The system administrators need to identify which users belong to multiple groups for better access management. Given two files,...", + "tags": [ + "linux-other", + "new" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "hanoi/README.md" + }, + { + "slug": "helsingor", + "title": "\"Helsing\u00f8r\": The first walls of postgres physical replication", + "description": "You're setting up a PostgreSQL database with replication, you decided to use Docker along with Docker Compose to make it easier to manage and test, after a few hours of work you finished the job and the master database is up and running, but you're having trouble with the replica. You need to figure out what's wrong with the replica and fix it. Since you are using Docker Compose, you can check the status of the running containers using docker compose ps (docker ps will do the job too). you may also want to check the logs of the containers. This is the file structure for this project: md /home/admin \u251c\u2500\u2500 postgres \u2502 \u251c\u2500\u2500 master \u2502 \u2502 \u251c\u2500\u2500 initdb.d \u2502 \u2502 \u2502 \u251c\u2500\u2500 01-roles.sql | | | |\u2500\u2500 02-replication.sql \u2502 \u2502 \u2502 \u2514\u2500\u2500 03-dataset.sql \u2502 \u2502 \u2514\u2500\u2500 postgres.conf \u2502 \u2514\u2500\u2500 replica \u2502 \u2502 \u2514\u2500\u2500 postgres.conf \u251c\u2500\u2500 docker-compose.yml \u2514\u2500\u2500 README.md All definition for the containers are inside docker-compose.yml file. You can get up the environment by running docker compose up -d and set it down by running docker compose down. If you make any change to the docker-compose.yml file, you can restart the containers by running docker compose up -d --force-recreate.", + "excerpt": "You're setting up a PostgreSQL database with replication, you decided to use Docker along with Docker Compose to make it easier to manage and test, after a few hours of work you finished the job and the master database...", + "tags": [ + "data processing", + "docker", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "helsingor/README.md" + }, + { + "slug": "hongkong", + "title": "\"Hong-Kong\": can't write data into database.", + "description": "(Similar to \"Manhattan\" scenario but harder). Your objective is to be able to insert a row in an existing Postgres database. The issue is not specific to Postgres and you don't need to know details about it (although it may help).

Postgres information: it's a service that listens to a port (:5432) and writes to disk in a data directory, the location of which is defined in the data_directory parameter of the configuration file /etc/postgresql/14/main/postgresql.conf. In our case Postgres is managed by systemd as a unit with name postgresql.", + "excerpt": "(Similar to \"Manhattan\" scenario but harder). Your objective is to be able to insert a row in an existing Postgres database. The issue is not specific to Postgres and you don't need to know details about it (although it...", + "tags": [ + "bash", + "data processing", + "disk volumes" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "hongkong/README.md" + }, + { + "slug": "ivujivik", + "title": "\"Ivujivik\": Parlez-vous Fran\u00e7ais?", + "description": "Given the CSV file /home/admin/table_tableau11.csv, find the Electoral District Name/Nom de circonscription that has the largest number of Rejected Ballots/Bulletins rejet\u00e9s and also has a population of less than 100,000.

The initial CSV file may be corrupted or invalid in a way that can be fixed without changing its data.

Installed in the VM are: Python3, Go, sqlite3, miller directly and PostgreSQL, MySQL in Docker images.

Save the solution in the /home/admin/mysolution , with the name as it is in the file, for example: echo \"Trois-Rivi\u00e8res\" > ~/mysolution", + "excerpt": "Given the CSV file /home/admin/table_tableau11.csv, find the Electoral District Name/Nom de circonscription that has the largest number of Rejected Ballots/Bulletins rejet\u00e9s and also has a...", + "tags": [ + "csv", + "data processing", + "docker" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "ivujivik/README.md" + }, + { + "slug": "jakarta", + "title": "\"Jakarta\": it's always DNS.", + "description": "Can't ping google.com. It returns ping: google.com: Name or service not known. Expected is being able to resolve the hostname. (Note: currently the VMs can't ping outside so there's no automated check for the solution).", + "excerpt": "Can't ping google.com. It returns ping: google.com: Name or service not known. Expected is being able to resolve the hostname. (Note: currently the VMs can't ping outside so there's no automated...", + "tags": [ + "data processing", + "dns", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "jakarta/README.md" + }, + { + "slug": "karakorum", + "title": "\"Karakorum\": WTFIT \u2013 What The Fun Is This?", + "description": "There's a binary at /home/admin/wtfit that nobody knows how it works or what it does (\"what the fun is this\"). Someone remembers something about wtfit needing to communicate to a service in order to start. Run this wtfit program so it doesn't exit with an error, fixing or working around things that you need but are broken in this server. (Note that you can open more than one web \"terminal\").", + "excerpt": "There's a binary at /home/admin/wtfit that nobody knows how it works or what it does (\"what the fun is this\"). Someone remembers something about wtfit needing to communicate to a service in...", + "tags": [ + "apache", + "linux-other", + "nginx" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "karakorum/README.md" + }, + { + "slug": "kihei", + "title": "\"Kihei\": Surely Not Another Disk Space Scenario", + "description": "There is a /home/admin/kihei program. Make the changes necessary so it runs succesfully, without deleting the /home/admin/datafile file.", + "excerpt": "There is a /home/admin/kihei program. Make the changes necessary so it runs succesfully, without deleting the /home/admin/datafile file.", + "tags": [ + "data processing", + "disk volumes", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "kihei/README.md" + }, + { + "slug": "lhasa", + "title": "\"Lhasa\": Easy Math", + "description": "There's a file /home/admin/scores.txt with two columns (imagine the first number is a counter and the second one is a test score for example).

Find the average (more precisely; the arithmetic mean: sum of numbers divided by how many numbers are there) of the numbers in the second column (find the average score).

Use exaclty two digits to the right of the decimal point. i. e., use exaclty two \"decimal digits\" without any rounding. Eg: if average = 21.349 , the solution is 21.34. If average = 33.1 , the solution is 33.10.

Save the solution in the /home/admin/solution file, for example: echo \"123.45\" > ~/solution

Tip: There's bc, Python3, Golang and sqlite3 installed in this VM.", + "excerpt": "There's a file /home/admin/scores.txt with two columns (imagine the first number is a counter and the second one is a test score for example).

Find the average (more precisely; the arithmetic mean: sum of...", + "tags": [ + "bash", + "csv", + "data processing" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "lhasa/README.md" + }, + { + "slug": "lisbon", + "title": "\"Lisbon\": etcd SSL cert troubles", + "description": "There's an etcd server running on https://localhost:2379 , get the value for the key \"foo\", ie etcdctl get foo or curl https://localhost:2379/v2/keys/foo", + "excerpt": "There's an etcd server running on https://localhost:2379 , get the value for the key \"foo\", ie etcdctl get foo or curl https://localhost:2379/v2/keys/foo", + "tags": [ + "etcd", + "linux-other", + "nginx" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "lisbon/README.md" + }, + { + "slug": "manado", + "title": "\"Manado\": How much do you press?", + "description": "You have been tasked with compressing the file _/home/admin/names_, which is 35147 bytes, to a size smaller than 9400 bytes. You can use any compressing tool at your disposal (there are many available in the server), also you can modify the file without deleting anything in it. Put the solution (compressed file) in the _/home/user/admin/solution_ directory with the default extension used by the compression tool (example: ~/solution/names.gzip). Test: The size of the compressed file is smaller than 9400 bytes. The \"Check My Solution\" button runs the script _/home/admin/agent/check.sh_, which you can see and execute. **check.sh** bash #!/usr/bin/bash # Directory path solution_folder=\"/home/admin/solution\" # List of compression tools to try compression_tools=(\"xz\" \"gzip\" \"lbzip2\" \"lz4\" \"lzip\" \"lzop\" \"zstd\") # Loop through each compression tool for tool in \"${compression_tools[@]}\"; do # Find compressed files for the current tool compressed_files=$(find \"$solution_folder\" -maxdepth 1 -type f -name \"*.${tool}\" 2>/dev/null) # Loop through each compressed file found for compressed_file in $compressed_files; do # Check if the compressed file size is less than 9400 bytes if [ $(stat -c %s \"$compressed_file\") -lt 9400 ]; then # Attempt to decompress the file if \"$tool\" -d \"$compressed_file\" -c > \"$solution_folder/temp_uncompressed.txt\"; then # Check if the size of the uncompressed file is at least 35000 bytes if [ $(stat -c %s \"$solution_folder/temp_uncompressed.txt\") -ge 35000 ]; then # Count the number of words in the uncompressed file word_count=$(wc -w < \"$solution_folder/temp_uncompressed.txt\") # Check if the word count is 4950 if [ \"$word_count\" -eq 4950 ]; then echo -n \"OK\" exit 0 # Exit script with success status fi fi fi fi done done # If none of the compressed files meet all conditions echo -n \"NO\"", + "excerpt": "You have been tasked with compressing the file _/home/admin/names_, which is 35147 bytes, to a size smaller than 9400 bytes. You can use any compressing tool at your disposal (there are many available in the server),...", + "tags": [ + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "manado/README.md" + }, + { + "slug": "manhattan", + "title": "\"Manhattan\": can't write data into database.", + "description": "Your objective is to be able to insert a row in an existing Postgres database. The issue is not specific to Postgres and you don't need to know details about it (although it may help).

Helpful Postgres information: it's a service that listens to a port (:5432) and writes to disk in a data directory, the location of which is defined in the data_directory parameter of the configuration file /etc/postgresql/14/main/postgresql.conf. In our case Postgres is managed by systemd as a unit with name postgresql.", + "excerpt": "Your objective is to be able to insert a row in an existing Postgres database. The issue is not specific to Postgres and you don't need to know details about it (although it may help).

Helpful Postgres...", + "tags": [ + "bash", + "data processing", + "disk volumes" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "manhattan/README.md" + }, + { + "slug": "marrakech", + "title": "\"Marrakech\": Word Histogram", + "description": "Find in the file frankestein.txt the second most frequent word and save in UPPER (capital) case in the /home/admin/mysolution file.

A word is a string of characters separated by space or newlines or punctuation symbols .,:; . Disregard case ('The', 'the' and 'THE' is the same word) and for simplification consider the apostrophe as another character not as punctuation (\"it's\" would be a word, distinct from \"it\" and \"is\"). Also disregard plurals (\"car\" and \"cars\" are different words) and other word variations (don't do \"stemming\").

We are providing a shorter test.txt file where the second most common word in upper case is \"WORLD\", so we could save this solution as: echo \"WORLD\" > /home/admin/mysolution

This problem can be done with a programming language (Python, Golang and sqlite3) or with common Linux utilities.", + "excerpt": "Find in the file frankestein.txt the second most frequent word and save in UPPER (capital) case in the /home/admin/mysolution file.

A word is a string of...", + "tags": [ + "bash", + "golang", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "marrakech/README.md" + }, + { + "slug": "melbourne", + "title": "\"Melbourne\": WSGI with Gunicorn", + "description": "There is a Python WSGI web application file at /home/admin/wsgi.py , the purpose of which is to serve the string \"Hello, world!\". This file is served by a Gunicorn server which is fronted by an nginx server (both servers managed by systemd). So the flow of an HTTP request is: Web Client (curl) -> Nginx -> Gunicorn -> wsgi.py . The objective is to be able to curl the localhost (on default port :80) and get back \"Hello, world!\", using the current setup.", + "excerpt": "There is a Python WSGI web application file at /home/admin/wsgi.py , the purpose of which is to serve the string \"Hello, world!\". This file...", + "tags": [ + "bash", + "gunicorn", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "melbourne/README.md" + }, + { + "slug": "minneapolis", + "title": "\"Minneapolis\": Break a CSV file", + "description": "Break the Comma Separated Valued (CSV) file _data.csv_ in the _/home/admin/_ directory into exactly 10 smaller files of about the same size named _data-00.csv_, _data-01.csv_, ... , _data-09.csv_ files in the same directory. All the files should have the same header (first line with column names) as _data.csv_. None of the smaller files should be bigger than 32KB. Note: to simplify, disregard broken lines in your files (ie, you can break a file at any point, not just at a newline). The resulting files don't have to be proper CSV files.", + "excerpt": "Break the Comma Separated Valued (CSV) file _data.csv_ in the _/home/admin/_ directory into exactly 10 smaller files of about the same size named _data-00.csv_, _data-01.csv_, ... , _data-09.csv_ files in the same...", + "tags": [ + "csv", + "data processing", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "minneapolis/README.md" + }, + { + "slug": "minneapolis-2", + "title": "\"Minneapolis with a Vengeance\": Break a CSV file", + "description": "Break the Comma Separated Valued (CSV) file _data.csv_ in the _/home/admin/_ directory into exactly 10 smaller files of about the same size named _data-00.csv_, _data-01.csv_, ... , _data-09.csv_ files in the same directory. All the files should have the same header (first line with column names) as _data.csv_. None of the smaller files should be bigger than 32KB. Note: unlike the original [Minneapolis scenario](https://sadservers.com/scenario/minneapolis), here the resulting files have to be proper CSV files. As a helper tool, you can run the program check_csv.py to check if your data-??.cs files look like proper CSV files.", + "excerpt": "Break the Comma Separated Valued (CSV) file _data.csv_ in the _/home/admin/_ directory into exactly 10 smaller files of about the same size named _data-00.csv_, _data-01.csv_, ... , _data-09.csv_ files in the same...", + "tags": [ + "csv", + "data processing", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "minneapolis-2/README.md" + }, + { + "slug": "monaco", + "title": "\"Monaco\": Disappearing Trick", + "description": "There is a web server on :5000 with a form. POSTing the correct form password into this web service will return a secret.

Save this secret provided by the web page (not the password you sent to it) to /home/admin/mysolution, for example: echo \"SecretFromWebSite\" > ~/mysolution

TIP: a developer worked on the web server code in this VM, using the same 'admin' account.

Scenario credit: PuppiestDoggo", + "excerpt": "There is a web server on :5000 with a form. POSTing the correct form password into this web service will return a secret.

Save this secret provided by the web page (not the password you sent to it) to...", + "tags": [ + "disk volumes", + "git", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "monaco/README.md" + }, + { + "slug": "moyogalpa", + "title": "\"Monapaya\": Security Snag. The Trials of John and Mike", + "description": "John and Mike are working on a Golang web application, and the security team has asked them to implement security measures. They have been tasked with the following: - The application must communicate only over HTTPS. - The application should only have access to the necessary files. Certificate and static files - The application should be rate-limited to 10 requests per second. - The application should run with a non-root user. John and Mike tried their best; unfortunately, they have broken the application while adding security measures, and it no longer functions. They need your help to fix it. The fixed application should be able to: - Allow clients to communicate with the application over HTTPS without ignoring any checks. - Allow clients to make at most 10 requests per second. - Serve static files.", + "excerpt": "John and Mike are working on a Golang web application, and the security team has asked them to implement security measures. They have been tasked with the following: - The application must communicate only over HTTPS. -...", + "tags": [ + "bash", + "golang", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "moyogalpa/README.md" + }, + { + "slug": "oaxaca", + "title": "\"Oaxaca\": Close an Open File", + "description": "The file /home/admin/somefile is open for writing by some process. Close this file without killing the process.", + "excerpt": "The file /home/admin/somefile is open for writing by some process. Close this file without killing the process.", + "tags": [ + "bash", + "linux-other", + "pro" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "oaxaca/README.md" + }, + { + "slug": "paris", + "title": "\"Paris\": Where is my webserver?", + "description": "A developer put an important password on his webserver localhost:5000 . However, he can't find a way to recover it. This scenario is easy to to once you realize the one \"trick\".

Find the password and save it in /home/admin/mysolution , for example: echo \"somepassword\" > ~/mysolution

Scenario credit: PuppiestDoggo", + "excerpt": "A developer put an important password on his webserver localhost:5000 . However, he can't find a way to recover it. This scenario is easy to to once you realize the one \"trick\".

Find the password and save it in...", + "tags": [ + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "paris/README.md" + }, + { + "slug": "pokhara", + "title": "\"Pokhara\": SSH and other sshenanigans", + "description": "A user client was added to the server, as well as their SSH public key.
The objective is to be able to SSH locally (there's only one server) as this user client using their ssh keys. This is, if as root you change to this user sudo su; su client, you should be able to login with ssh: ssh localhost.

", + "excerpt": "A user client was added to the server, as well as their SSH public key.
The objective is to be able to SSH locally (there's only one server) as this user client using their ssh keys. This is, if as...", + "tags": [ + "bash", + "linux-other", + "ssh" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "pokhara/README.md" + }, + { + "slug": "poznan", + "title": "\"Pozna\u0144\": Helm Chart Issue in Kubernetes", + "description": "A DevOps engineer created a Helm Chart web_chart with a custom nginx site, however he still gets the default nginx _index.html_. You can check for example with POD_IP=$(kubectl get pods -n default -o jsonpath='{.items[0].status.podIP}') and curl -s \"${POD_IP}\">. In addition he should set replicas to 3. The chart is not working as expected. Fix the configurations so you get the custom HTML page from any nginx pod. Root access is not needed (\"admin\" user cannot sudo). Credit [Kamil B\u0142a\u017c](https://www.devkblaz.com/)", + "excerpt": "A DevOps engineer created a Helm Chart web_chart with a custom nginx site, however he still gets the default nginx _index.html_. You can check for example with POD_IP=$(kubectl get pods -n default -o...", + "tags": [ + "helm", + "json", + "kubernetes" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "poznan/README.md" + }, + { + "slug": "quito", + "title": "\"Quito\": Control One Container from Another", + "description": "You have a running container named _docker-access_. Another container _nginx_ is present but in a stopped state. Your goal is to start the nginx container from inside the docker-access container. You must not start the nginx container from the host system or any other container that is not _docker-access_. You can restart this _docker-access_ container.", + "excerpt": "You have a running container named _docker-access_. Another container _nginx_ is present but in a stopped state. Your goal is to start the nginx container from inside the docker-access container. You must not start the...", + "tags": [ + "bash", + "docker", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "quito/README.md" + }, + { + "slug": "rosario", + "title": "\"Rosario\": Restore a MySQL database.", + "description": "A developer created a database named 'main' but now some data is missing in the database. You need to restore the database using the the dump \"/home/admin/backup.sql\".
The issue is that the developer forgot the root password for the MariaDB server.
If you encounter an issue while restoring the database, fix it. Credit: [Sebastian Segovia](https://www.linkedin.com/in/sebastian-segovia-a7518a228/)", + "excerpt": "A developer created a database named 'main' but now some data is missing in the database. You need to restore the database using the the dump \"/home/admin/backup.sql\".
The issue is that the developer forgot the root...", + "tags": [ + "data processing", + "linux-other", + "mysql" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "rosario/README.md" + }, + { + "slug": "roseau", + "title": "\"Roseau\": Hack a Web Server", + "description": "There is a secret stored in a file that the local Apache web server can provide. Find this secret and have it as a /home/admin/secret.txt file.

Note that in this server the admin user is not a sudoer.

Also note that the password crackers Hashcat and Hydra are installed from packages and John the Ripper binaries have been built from source in /home/admin/john/run", + "excerpt": "There is a secret stored in a file that the local Apache web server can provide. Find this secret and have it as a /home/admin/secret.txt file.

Note that in this server the admin user is not a...", + "tags": [ + "apache", + "hack", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "roseau/README.md" + }, + { + "slug": "saintjohn", + "title": "\"Saint John\": what is writing to this log file?", + "description": "A developer created a testing program that is continuously writing to a log file /var/log/bad.log and filling up disk. You can check for example with tail -f /var/log/bad.log.
This program is no longer needed. Find it and terminate it. Do not delete the log file.", + "excerpt": "A developer created a testing program that is continuously writing to a log file /var/log/bad.log and filling up disk. You can check for example with tail -f /var/log/bad.log.
This program is no...", + "tags": [ + "bash", + "disk volumes", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "saintjohn/README.md" + }, + { + "slug": "salta", + "title": "\"Salta\": Docker container won't start.", + "description": "There's a \"dockerized\" Node.js web application in the /home/admin/app directory. Create a Docker container so you get a web app on port :8888 and can curl to it. For the solution to be valid, there should be only one running Docker container.", + "excerpt": "There's a \"dockerized\" Node.js web application in the /home/admin/app directory. Create a Docker container so you get a web app on port :8888 and can curl to it. For the solution to be valid,...", + "tags": [ + "bash", + "docker", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "salta/README.md" + }, + { + "slug": "santiago", + "title": "\"Santiago\": Find the secret combination", + "description": "Alice the spy has hidden a secret number combination, find it using these instructions:

1) Find the number of lines with occurrences of the string Alice (case sensitive) in the *.txt files in the /home/admin directory
2) There's a file where Alice appears exactly once. In that file, in the line after that \"Alice\" occurrence there's a number.
Write both numbers consecutively as one (no new line or spaces) to the solution file. For example if the first number from 1) is 11 and the second 22, you can do echo -n 11 > /home/admin/solution; echo 22 >> /home/admin/solution or echo \"1122\" > /home/admin/solution.", + "excerpt": "Alice the spy has hidden a secret number combination, find it using these instructions:

1) Find the number of lines with occurrences of the string Alice (case sensitive) in the *.txt files...", + "tags": [ + "linux-other", + "new" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "santiago/README.md" + }, + { + "slug": "saskatoon", + "title": "\"Saskatoon\": counting IPs.", + "description": "There's a web server access log file at /home/admin/access.log. The file consists of one line per HTTP request, with the requester's IP address at the beginning of each line.

Find what's the IP address that has the most requests in this file (there's no tie; the IP is unique). Write the solution into a file /home/admin/highestip.txt. For example, if your solution is \"1.2.3.4\", you can do echo \"1.2.3.4\" > /home/admin/highestip.txt", + "excerpt": "There's a web server access log file at /home/admin/access.log. The file consists of one line per HTTP request, with the requester's IP address at the beginning of each line.

Find what's the IP...", + "tags": [ + "bash", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "saskatoon/README.md" + }, + { + "slug": "singara", + "title": "\"Singara\": Docker and Kubernetes web app not working", + "description": "There's a k3s Kubernetes install you can access with kubectl. The Kubernetes YAML manifests under /home/admin have been applied. The objective is to access from the host the \"webapp\" web server deployed and find what message it serves (it's a name of a town or city btw). In order to pass the check, the webapp Docker container should not be run separately outside Kubernetes as a shortcut.", + "excerpt": "There's a k3s Kubernetes install you can access with kubectl. The Kubernetes YAML manifests under /home/admin have been applied. The objective is to access from the host the \"webapp\" web server...", + "tags": [ + "docker", + "kubernetes", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "singara/README.md" + }, + { + "slug": "st-paul", + "title": "\"Saint Paul\": Merge Many CSVs files", + "description": "Join (merge) all the 338 files in _/home/admin/polldayregistrations_enregistjourduscrutin?????.csv_ into one single _/home/admin/all.csv_ file with the contents of all the CSV files in any order. There should be only one line with the names of the columns as a header.", + "excerpt": "Join (merge) all the 338 files in _/home/admin/polldayregistrations_enregistjourduscrutin?????.csv_ into one single _/home/admin/all.csv_ file with the contents of all the CSV files in any order. There should be only...", + "tags": [ + "csv", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "st-paul/README.md" + }, + { + "slug": "taipei", + "title": "\"Taipei\": Come a-knocking", + "description": "There is a web server on port :80 protected with Port Knocking. Find the one \"knock\" needed (sending a SYN to a single port, not a sequence) so you can curl localhost.", + "excerpt": "There is a web server on port :80 protected with Port Knocking. Find the one \"knock\" needed (sending a SYN to a single port, not a sequence) so you...", + "tags": [ + "bash", + "linux-other", + "new" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "taipei/README.md" + }, + { + "slug": "tarifa", + "title": "\"Tarifa\": Between Two Seas", + "description": "There are three Docker containers defined in the docker-compose.yml file: an HAProxy accepting connetions on port :5000 of the host, and two nginx containers, not exposed to the host.

The person who tried to set this up wanted to have HAProxy in front of the (backend or upstream) nginx containers load-balancing them but something is not working.", + "excerpt": "There are three Docker containers defined in the docker-compose.yml file: an HAProxy accepting connetions on port :5000 of the host, and two nginx containers, not exposed to the host.

The person who tried...", + "tags": [ + "docker", + "haproxy", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "tarifa/README.md" + }, + { + "slug": "tokamachi", + "title": "\"Tokamachi\": Troubleshooting a Named Pipe", + "description": "There's a process reading from the named pipe _/home/admin/namedpipe_. If you run this command that writes to that pipe: /bin/bash -c 'while true; do echo \"this is a test message being sent to the pipe\" > /home/admin/namedpipe; done' & And check the reader log with tail -f reader.log You'll see that after a minute or so it works for a while (the reader receives some messages) and then it stops working (no more received messages are printed to the reader log or it takes a long time to process one). Troubleshoot and fix (for example changing the writer command) so that the writer keeps sending the messages and the reader is able to read all of them.", + "excerpt": "There's a process reading from the named pipe _/home/admin/namedpipe_. If you run this command that writes to that pipe: /bin/bash -c 'while true; do echo \"this is a test message being sent to the pipe\" >...", + "tags": [ + "bash", + "linux-other", + "pro" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "tokamachi/README.md" + }, + { + "slug": "tokyo", + "title": "\"Tokyo\": can't serve web file", + "description": "There's a web server serving a file /var/www/html/index.html with content \"hello sadserver\" but when we try to check it locally with an HTTP client like curl 127.0.0.1:80, nothing is returned. This scenario is not about the particular web server configuration and you only need to have general knowledge about how web servers work.", + "excerpt": "There's a web server serving a file /var/www/html/index.html with content \"hello sadserver\" but when we try to check it locally with an HTTP client like curl 127.0.0.1:80, nothing is returned. This...", + "tags": [ + "apache", + "data processing", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "tokyo/README.md" + }, + { + "slug": "tukaani", + "title": "\"Tukaani\": XZ LZMA Library Compromised", + "description": "This is a scenario where a Linux shared library liblzma.so has been compromised (the real compromised XZ Utils liblzma has not been used). Find all instances of this malicious liblzma library and make it so none of the running processes use it and all the applications (\"webapp\", \"jobapp\") still run properly (eg, stopping those applications is not a solution). This liblzma.so at the path /usr/lib/x86_64-linux-gnu/liblzma.so.5.2.5 is the good one. Consider the same library liblzma.so.5.2.5 at other paths as compromised (ideally we would have used other real versions with different checksums but we were not able to). The \"webapp\" responds to curl http://127.0.0.1:8000 and the docker version of it \"user-service\" responds to curl http://127.0.0.1:8001 with \"Hello, We are using lzma library to compress our requests.\" Find all the \"compromised\" libraries paths that are used by the hijacked processes. Save their paths in the /home/admin/paths file (one path per line), for example: echo '/path/to/liblzma.so.5' >> /home/admin/paths, one per path.", + "excerpt": "This is a scenario where a Linux shared library liblzma.so has been compromised (the real compromised XZ Utils liblzma has not been used). Find all instances of this malicious liblzma library and make it so none of the...", + "tags": [ + "bash", + "docker", + "linux-other" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "tukaani/README.md" + }, + { + "slug": "unimak", + "title": "\"Unimak Island\": Fun with Mr Jason", + "description": "Using the file station_information.json , find the station_id where \"has_kiosk\" is false and \"capacity\" is greater than 30.

Save the station_id of the solution in the /home/admin/mysolution file, for example: echo \"ec040a94-4de7-4fb3-aea0-ec5892034a69\" > ~/mysolution

You can use the installed utilities jq, gron, jid as well as Python3 and Golang.", + "excerpt": "Using the file station_information.json , find the station_id where \"has_kiosk\" is false and \"capacity\" is greater than 30.

Save the station_id of the solution in the /home/admin/mysolution file, for...", + "tags": [ + "data processing", + "git", + "json" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "unimak/README.md" + }, + { + "slug": "venice", + "title": "\"Venice\": Am I in a container?", + "description": "Try and figure out if you are inside a container (like a Docker one for example) or inside a Virtual Machine (like in the other scenarios).", + "excerpt": "Try and figure out if you are inside a container (like a Docker one for example) or inside a Virtual Machine (like in the other scenarios).", + "tags": [ + "docker", + "podman", + "pro" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "venice/README.md" + }, + { + "slug": "warsaw", + "title": "\"Warsaw\": Promethues can't scrape the webserver", + "description": "A developer created a golang application that is exposing the _/metrics_ endpoint. They have a problem with scraping the metrics from the application. They asked you to help find the problem. Full source code of the application is available at the _/home/admin/app_ directory. Credit [Kamil B\u0142a\u017c](https://www.devkblaz.com/)", + "excerpt": "A developer created a golang application that is exposing the _/metrics_ endpoint. They have a problem with scraping the metrics from the application. They asked you to help find the problem. Full source code of the...", + "tags": [ + "golang", + "linux-other", + "pro" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "warsaw/README.md" + }, + { + "slug": "yokohama", + "title": "\"Yokohama\": Linux Users Working Together", + "description": "There are four Linux users working together in a project in this server: abe, betty, carlos, debora. First, they have asked you as the sysadmin, to make it so each of these four users can read the project files of the other users in the /home/admin/shared directory, but none of them can modify a file that belongs to another user. Users should be able modify their own files. Secondly, they have asked you to modify the file shared/ALL so that any of these four users can write more content to it, but previous (existing) content cannot be altered.", + "excerpt": "There are four Linux users working together in a project in this server: abe, betty, carlos, debora. First, they have asked you as the sysadmin, to make it so each of these four users can read the project files of the...", + "tags": [ + "bash", + "linux-other", + "pro" + ], + "level": "", + "type": "", + "access": "", + "time": "", + "readme": "yokohama/README.md" + } +] \ No newline at end of file