def migrate(revision): """Perform database migrations.""" if not migrate_database(revision): print red(">>> Error migrating your database..") exit(1) print yellow(">>> Your database migration was successful!")
def check_version(): """Checks version of Cuckoo.""" if not config("cuckoo:cuckoo:version_check"): return print(" Checking for updates...") try: r = requests.get("https://cuckoosandbox.org/updates.json", params={"version": version}, timeout=6) r.raise_for_status() r = r.json() except (requests.RequestException, ValueError) as e: print(red(" Error checking for the latest Cuckoo version: %s!" % e)) return try: old = StrictVersion(version) < StrictVersion(r["version"]) except ValueError: old = True if old: msg = "Cuckoo Sandbox version %s is available now." % r["version"] print(red(" Outdated! ") + msg) else: print(green(" You're good to go!")) print("\n Our latest blogposts:") for blogpost in r["blogposts"]: print(" * %s, %s." % (yellow(blogpost["title"]), blogpost["date"])) print(" %s" % red(blogpost["oneline"])) print(" More at %s" % blogpost["url"]) print("") return r
def main(ctx, debug, quiet, nolog, maxcount, user, cwd): """Invokes the Cuckoo daemon or one of its subcommands. To be able to use different Cuckoo configurations on the same machine with the same Cuckoo installation, we use the so-called Cuckoo Working Directory (aka "CWD"). A default CWD is available, but may be overridden through the following options - listed in order of precedence. \b * Command-line option (--cwd) * Environment option ("CUCKOO_CWD") * Environment option ("CUCKOO") * Current directory (if the ".cwd" file exists) * Default value ("~/.cuckoo") """ decide_cwd(cwd) # Drop privileges. user and drop_privileges(user) ctx.user = user ctx.log = not nolog if quiet: level = logging.WARN elif debug: level = logging.DEBUG else: level = logging.INFO ctx.level = level # Update the Config.configuration object to have configuration info # from plugins. update_config() # A subcommand will be invoked, so don't run Cuckoo itself. if ctx.invoked_subcommand: return try: cuckoo_init(level, ctx) cuckoo_main(maxcount) except CuckooCriticalError as e: log.critical(red("{0}: {1}".format(e.__class__.__name__, e))) sys.exit(1) except SystemExit as e: if e.code: print e except Exception as e: # Deal with an unhandled exception. sys.stderr.write(exception_message()) log.exception(red("{0}: {1}".format(e.__class__.__name__, e))) sys.exit(1)
def dist_migrate(): args = [ "alembic", "-x", "cwd=%s" % cwd(), "upgrade", "head", ] try: subprocess.check_call( args, cwd=cwd("distributed", "migration", private=True) ) except subprocess.CalledProcessError: print red(">>> Error migrating your database..") exit(1) print yellow(">>> Your database migration was successful!")
def process(ctx, instance, report, maxcount): """Process raw task data into reports.""" init_console_logging(level=ctx.parent.level) if instance: pidfile = Pidfile(instance) if pidfile.exists(): log.error(red( "Cuckoo process instance '%s' already exists. PID: %s\n" ), instance, pidfile.pid) sys.exit(1) pidfile.create() init_logfile("process-%s.json" % instance) Database().connect() # Load additional Signatures. load_signatures() try: # Initialize all modules & Yara rules. init_modules() init_yara() except CuckooCriticalError as e: message = red("{0}: {1}".format(e.__class__.__name__, e)) if len(log.handlers): log.critical(message) else: sys.stderr.write("{0}\n".format(message)) sys.exit(1) try: # Regenerate one or more reports. if report: process_task_range(report) elif not instance: print ctx.get_help(), "\n" sys.exit("In automated mode an instance name is required!") else: log.info( "Initialized instance=%s, ready to process some tasks", instance ) process_tasks(instance, maxcount) except KeyboardInterrupt: print(red("Aborting (re-)processing of your analyses..")) if instance: Pidfile(instance).remove()
def main(ctx, debug, quiet, nolog, maxcount, user, cwd): """Invokes the Cuckoo daemon or one of its subcommands. To be able to use different Cuckoo configurations on the same machine with the same Cuckoo installation, we use the so-called Cuckoo Working Directory (aka "CWD"). A default CWD is available, but may be overridden through the following options - listed in order of precedence. \b * Command-line option (--cwd) * Environment option ("CUCKOO_CWD") * Environment option ("CUCKOO") * Current directory (if the ".cwd" file exists) * Default value ("~/.cuckoo") """ decide_cwd(cwd) # Drop privileges. user and drop_privileges(user) ctx.user = user ctx.log = not nolog if quiet: level = logging.WARN elif debug: level = logging.DEBUG else: level = logging.INFO ctx.level = level # A subcommand will be invoked, so don't run Cuckoo itself. if ctx.invoked_subcommand: return try: cuckoo_init(level, ctx) cuckoo_main(maxcount) except CuckooCriticalError as e: log.critical(red("{0}: {1}".format(e.__class__.__name__, e))) sys.exit(1) except SystemExit as e: if e.code: print e except Exception as e: # Deal with an unhandled exception. sys.stderr.write(exception_message()) log.exception(red("{0}: {1}".format(e.__class__.__name__, e))) sys.exit(1)
def cuckoo_create(username=None, cfg=None, quiet=False): """Create a new Cuckoo Working Directory.""" if not quiet: print jinja2.Environment().from_string( open(cwd("cwd", "init-pre.jinja2", private=True), "rb").read() ).render(cwd=cwd, yellow=yellow, red=red) if not os.path.exists(cwd(".cwd", private=True)): print red( "The cuckoo/private/.cwd file is missing. Please run " "'python setup.py sdist' before 'pip install ...'!" ) return if not os.path.isdir(cwd()): os.mkdir(cwd()) def _ignore_pyc(src, names): """Don't copy .pyc files.""" return [name for name in names if name.endswith(".pyc")] # The following effectively nops the first os.makedirs() call that # shutil.copytree() does as we've already created the destination # directory ourselves (assuming it didn't exist already). orig_makedirs = shutil.os.makedirs def _ignore_first_makedirs(dst): shutil.os.makedirs = orig_makedirs shutil.os.makedirs = _ignore_first_makedirs shutil.copytree( os.path.join(cuckoo.__path__[0], "data"), cwd(), symlinks=True, ignore=_ignore_pyc ) # Drop our version of the CWD. our_version = open(cwd(".cwd", private=True), "rb").read() open(cwd(".cwd"), "wb").write(our_version) # Write the supervisord.conf configuration file. write_supervisor_conf(username or getuser()) write_cuckoo_conf(cfg=cfg) if not quiet: print print jinja2.Environment().from_string( open(cwd("cwd", "init-post.jinja2", private=True), "rb").read() ).render()
def dnsserve(ctx, host, port, nxdomain, hardcode): """Custom DNS server.""" init_console_logging(ctx.parent.level) try: cuckoo_dnsserve(host, port, nxdomain, hardcode) except KeyboardInterrupt: print(red("Aborting Cuckoo DNS Serve.."))
def process(ctx, instance, report, maxcount): """Process raw task data into reports.""" init_console_logging(level=ctx.parent.level) if instance: init_logfile("process-%s.json" % instance) Database().connect() # Load additional Signatures. load_signatures() # Initialize all modules & Yara rules. init_modules() init_yara(False) try: # Regenerate one or more reports. if report: process_task_range(report) elif not instance: print ctx.get_help(), "\n" sys.exit("In automated mode an instance name is required!") else: log.info( "Initialized instance=%s, ready to process some tasks", instance ) process_tasks(instance, maxcount) except KeyboardInterrupt: print(red("Aborting (re-)processing of your analyses.."))
def rooter(ctx, socket, group, ifconfig, service, iptables, ip, sudo): """Instantiates the Cuckoo Rooter.""" init_console_logging(level=ctx.parent.level) if sudo: args = [ "sudo", sys.argv[0], "rooter", socket, "--group", group, "--ifconfig", ifconfig, "--service", service, "--iptables", iptables, "--ip", ip, ] if ctx.parent.level == logging.DEBUG: args.insert(2, "--debug") try: subprocess.call(args) except KeyboardInterrupt: pass else: try: cuckoo_rooter(socket, group, ifconfig, service, iptables, ip) except KeyboardInterrupt: print(red("Aborting the Cuckoo Rooter.."))
def dnsserve(ctx, host, port, nxdomain, hardcode, sudo): """Custom DNS server.""" init_console_logging(ctx.parent.level) if sudo: args = [ "sudo", sys.argv[0], "dnsserve", "--host", host, "--port", "%d" % port, ] if ctx.parent.level == logging.DEBUG: args.insert(2, "--debug") if nxdomain: args.extend(("--nxdomain", nxdomain)) if hardcode: args.extend(("--hardcode", hardcode)) try: subprocess.call(args) except KeyboardInterrupt: pass else: try: cuckoo_dnsserve(host, port, nxdomain, hardcode) except KeyboardInterrupt: print(red("Aborting Cuckoo DNS Serve.."))
def submit(ctx, target, url, options, package, custom, owner, timeout, priority, machine, platform, memory, enforce_timeout, clock, tags, baseline, remote, shuffle, pattern, max, unique): """Submit one or more files or URLs to Cuckoo.""" init_console_logging(level=ctx.parent.level) Database().connect() try: l = submit_tasks( target, options, package, custom, owner, timeout, priority, machine, platform, memory, enforce_timeout, clock, tags, remote, pattern, max, unique, url, baseline, shuffle ) for category, target, task_id in l: if task_id: print "%s: %s \"%s\" added as task with ID #%s" % ( bold(green("Success")), category, target, task_id ) else: print "%s: %s \"%s\" as it has already been analyzed" % ( bold(yellow("Skipped")), category, target ) except KeyboardInterrupt: print(red("Aborting submission of samples.."))
def rooter(ctx, socket, group, service, iptables, ip, sudo): """Instantiates the Cuckoo Rooter.""" init_console_logging(level=ctx.parent.level) if sudo: args = [ "sudo", sys.argv[0], "rooter", socket, "--group", group, "--service", service, "--iptables", iptables, "--ip", ip, ] if ctx.parent.level == logging.DEBUG: args.insert(2, "--debug") try: subprocess.call(args) except KeyboardInterrupt: pass else: try: cuckoo_rooter(socket, group, service, iptables, ip) except KeyboardInterrupt: print(red("Aborting the Cuckoo Rooter.."))
def ensure_tmpdir(): """Verify if the current user can read and create files in the cuckoo temporary directory (and creates it, if needed).""" try: if not os.path.isdir(temppath()): mkdir(temppath()) except OSError as e: # Currently we only handle EACCES. if e.errno != errno.EACCES: raise if os.path.isdir(temppath()) and os.access(temppath(), os.R_OK | os.W_OK): return True print red( "Cuckoo cannot read or write files into the temporary directory '%s'," " please make sure the user running Cuckoo has the ability to do so. " "If the directory does not yet exist and the parent directory is " "owned by root, then please create and chown the directory with root." % temppath()) return False
def check_version(): """Checks version of Cuckoo.""" if not config("cuckoo:cuckoo:version_check"): return print(" Checking for updates...") try: r = requests.post( "http://api.cuckoosandbox.org/checkversion.php", data={"version": version} ) r.raise_for_status() r = r.json() except (requests.RequestException, ValueError) as e: print(red(" Error checking for the latest Cuckoo version: %s!" % e)) return if not isinstance(r, dict) or r.get("error"): print(red(" Error checking for the latest Cuckoo version:")) print(yellow(" Response: %s" % r)) return rc1_responses = "NEW_VERSION", "NO_UPDATES" # Deprecated response. if r.get("response") in rc1_responses and r.get("current") == "2.0-rc1": print(green(" You're good to go!")) return try: old = StrictVersion(version) < StrictVersion(r.get("current")) except ValueError: old = True if old: msg = "Cuckoo Sandbox version %s is available now." % r.get("current") print(red(" Outdated! ") + msg), else: print(green(" You're good to go!"))
def ensure_tmpdir(): """Verifies if the current user can read and create files in the cuckoo temporary directory (and creates it, if needed).""" try: if not os.path.isdir(temppath()): mkdir(temppath()) except OSError as e: # Currently we only handle EACCES. if e.errno != errno.EACCES: raise if os.path.isdir(temppath()) and os.access(temppath(), os.R_OK | os.W_OK): return True print red( "Cuckoo cannot read or write files into the temporary directory '%s'," " please make sure the user running Cuckoo has the ability to do so. " "If the directory does not yet exist and the parent directory is " "owned by root, then please create and chown the directory with root." % temppath() ) return False
def emit(self, record): colored = copy.copy(record) if record.levelname == "WARNING": colored.msg = yellow(record.msg) elif record.levelname == "ERROR" or record.levelname == "CRITICAL": colored.msg = red(record.msg) else: if "analysis procedure completed" in record.msg: colored.msg = cyan(record.msg) else: colored.msg = record.msg logging.StreamHandler.emit(self, colored)
def check_version(): """Checks version of Cuckoo.""" if not config("cuckoo:cuckoo:version_check"): return print(" Checking for updates...") try: r = requests.get( "https://cuckoosandbox.org/updates.json", params={"version": version}, timeout=6 ) r.raise_for_status() r = r.json() except (requests.RequestException, ValueError) as e: print(red(" Error checking for the latest Cuckoo version: %s!" % e)) return try: old = StrictVersion(version) < StrictVersion(r["version"]) except ValueError: old = True if old: msg = "Cuckoo Sandbox version %s is available now." % r["version"] print(red(" Outdated! ") + msg) else: print(green(" You're good to go!")) print("\n Our latest blogposts:") for blogpost in r["blogposts"]: print(" * %s, %s." % (yellow(blogpost["title"]), blogpost["date"])) print(" %s" % red(blogpost["oneline"])) print(" More at %s" % blogpost["url"]) print("") return r
def submit_tasks(target, options, package, custom, owner, timeout, priority, machine, platform, memory, enforce_timeout, clock, tags, remote, pattern, maxcount, is_unique, is_url, is_baseline, is_shuffle): db = Database() data = dict( package=package or "", timeout=timeout, options=options, priority=priority, machine=machine, platform=platform, custom=custom, owner=owner, tags=tags, memory="1" if memory else "0", enforce_timeout="1" if enforce_timeout else "0", clock=clock, unique="1" if is_unique else "0", ) if is_baseline: if remote: print "Remote baseline support has not yet been implemented." return task_id = db.add_baseline(timeout, owner, machine, memory) yield "Baseline", machine, task_id return if is_url and is_unique: print "URL doesn't have --unique support yet." return if is_url: for url in target: if not remote: data.pop("unique", None) task_id = db.add_url(to_unicode(url), **data) yield "URL", url, task_id continue data["url"] = to_unicode(url) try: r = requests.post( "http://%s/tasks/create/url" % remote, data=data ) yield "URL", url, r.json()["task_id"] except Exception as e: print "%s: unable to submit URL: %s" % ( bold(red("Error")), e ) else: files = [] for path in target: files.extend(enumerate_files(os.path.abspath(path), pattern)) if is_shuffle: random.shuffle(files) for filepath in files: if not os.path.getsize(filepath): print "%s: sample %s (skipping file)" % ( bold(yellow("Empty")), filepath ) continue if maxcount is not None: if not maxcount: break maxcount -= 1 if not remote: if is_unique: sha256 = File(filepath).get_sha256() if db.find_sample(sha256=sha256): yield "File", filepath, None continue data.pop("unique", None) task_id = db.add_path(file_path=filepath, **data) yield "File", filepath, task_id continue files = { "file": (os.path.basename(filepath), open(filepath, "rb")), } try: r = requests.post( "http://%s/tasks/create/file" % remote, data=data, files=files ) yield "File", filepath, r.json()["task_id"] except Exception as e: print "%s: unable to submit file: %s" % ( bold(red("Error")), e ) continue
from django.template.base import TemplateSyntaxError from cuckoo.common.colors import red from cuckoo.common.elastic import elastic from cuckoo.common.mongo import mongo from cuckoo.core.startup import init_rooter, init_routing from cuckoo.misc import cwd, decide_cwd if cwd(root=True) is None: decide_cwd(exists=True) # Connect to MongoDB (mandatory). if not mongo.init(): sys.exit( red("In order to use the Cuckoo Web Interface it is required to have " "MongoDB up-and-running and enabled in Cuckoo. Please refer to our " "official documentation as well as the $CWD/conf/reporting.conf file." )) mongo.connect() # Connect to ElasticSearch (optional). elastic.init() elastic.connect() # In case we have VPNs enabled we need to initialize through the following # two methods as they verify the interaction with VPNs as well as gather # which VPNs are available (for representation upon File/URL submission). init_rooter() init_routing() DEBUG = False
def import_(ctx, mode, path): """Imports an older Cuckoo setup into a new CWD. The old setup should be identified by PATH and the new CWD may be specified with the --cwd parameter, e.g., "cuckoo --cwd /tmp/cwd import old-cuckoo".""" if mode == "symlink" and is_windows(): sys.exit(red( "You can only use the 'symlink' mode on non-Windows platforms." )) print yellow("You are importing an existing Cuckoo setup. Please") print yellow("understand that, depending on the mode taken, if ") print yellow("you remove the old Cuckoo setup after this import ") print yellow("you may still"), red("lose ALL of your data!") print print yellow("Additionally, database migrations will be performed ") print yellow("in-place*. You won't be able to use your old Cuckoo ") print yellow("setup anymore afterwards! However, we'll provide ") print yellow("you with the option to create a SQL backup beforehand.") print print red("TL;DR Cleaning the old setup after the import may") print red("corrupt your new setup: its SQL, MongoDB, and ") print red("ElasticSearch database may be dropped and, in 'symlink'") print red("mode, the analyses removed.") print print yellow("*: Except for sqlite3 databases in combination with") print yellow(" the import 'copy' approach.") print value = click.confirm( "... I've read the above and understand the consequences", False ) if not value: sys.exit(red("Aborting operation.. please try again!")) try: import_cuckoo(ctx.parent.user, mode, path) except KeyboardInterrupt: print(red("Aborting import of Cuckoo instance.."))
def web(ctx, args, host, port, uwsgi, nginx): """Operate the Cuckoo Web Interface. Use "--help" to get this help message and "help" to find Django's manage.py potential subcommands. """ username = ctx.parent.user or getuser() if uwsgi: print "[uwsgi]" print "plugins = python" if os.environ.get("VIRTUAL_ENV"): print "virtualenv =", os.environ["VIRTUAL_ENV"] print "module = cuckoo.web.web.wsgi" print "uid =", username print "gid =", username dirpath = os.path.join(cuckoo.__path__[0], "web", "static") print "static-map = /static=%s" % dirpath print "# If you're getting errors about the PYTHON_EGG_CACHE, then" print "# uncomment the following line and add some path that is" print "# writable from the defined user." print "# env = PYTHON_EGG_CACHE=" print "env = CUCKOO_APP=web" print "env = CUCKOO_CWD=%s" % cwd() return if nginx: print "upstream _uwsgi_cuckoo_web {" print " server unix:/run/uwsgi/app/cuckoo-web/socket;" print "}" print print "server {" print " listen %s:%d;" % (host, port) print print " # Cuckoo Web Interface" print " location / {" print " client_max_body_size 1G;" print " proxy_redirect off;" print " proxy_set_header X-Forwarded-Proto $scheme;" print " uwsgi_pass _uwsgi_cuckoo_web;" print " include uwsgi_params;" print " }" print "}" return # Switch to cuckoo/web and add the current path to sys.path as the Web # Interface is using local imports here and there. # TODO Rename local imports to either cuckoo.web.* or relative imports. sys.argv[0] = os.path.abspath(sys.argv[0]) os.chdir(os.path.join(cuckoo.__path__[0], "web")) sys.path.insert(0, ".") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cuckoo.web.web.settings") # The Django HTTP server also imports the WSGI module for some reason, so # ensure that WSGI is able to load. os.environ["CUCKOO_APP"] = "web" os.environ["CUCKOO_CWD"] = cwd() from django.core.management import execute_from_command_line init_console_logging(level=ctx.parent.level) Database().connect() try: execute_from_command_line( ("cuckoo", "runserver", "%s:%d" % (host, port), "--noreload") if not args else ("cuckoo",) + args ) except CuckooCriticalError as e: message = red("{0}: {1}".format(e.__class__.__name__, e)) if len(log.handlers): log.critical(message) else: sys.stderr.write("{0}\n".format(message)) sys.exit(1)
def import_cuckoo(username, mode, dirpath): version = identify(dirpath) if not version: raise CuckooOperationalError( "The path that you specified is not a proper Cuckoo setup. Please " "point the path to the root of your older Cuckoo setup, i.e., to " "the directory containing the cuckoo.py script!" ) # TODO Copy over the configuration and ignore the database. if version in ("0.4", "0.4.1", "0.4.2"): raise CuckooOperationalError( "Importing from version 0.4, 0.4.1, or 0.4.2 is not supported as " "there are no database migrations for that version. Please start " "from scratch, your configuration would have been obsolete anyway!" ) print "We've identified a Cuckoo Sandbox %s installation!" % version if os.path.isdir(cwd()) and os.listdir(cwd()): raise CuckooOperationalError( "This Cuckoo Working Directory already exists. Please import to " "a new/clean Cuckoo Working Directory." ) # Following are various recursive imports. from cuckoo.apps import migrate_database from cuckoo.main import cuckoo_create print "Reading in the old configuration.." # Port the older configuration. cfg = Config.from_confdir(os.path.join(dirpath, "conf"), loose=True) cfg = migrate_conf(cfg, version) print " configuration has been migrated to the latest version!" print # Create a fresh Cuckoo Working Directory. cuckoo_create(username, cfg, quiet=True) dburi = cfg["cuckoo"]["database"]["connection"] # Ask if the user would like to make a backup of the SQL database and in # the case of sqlite3, copy/move/symlink cuckoo.db to the CWD. sqldump(dburi, dirpath) movesql(dburi, mode, dirpath) # Run database migrations. if not migrate_database(): raise CuckooOperationalError( "Error migrating your old Cuckoo database!" ) # Link or copy all of the older results to the new CWD. import_legacy_analyses(mode, dirpath) # Urge the user to run the community command. print print "You have successfully imported your old version of Cuckoo!" print "However, in order to get up-to-date, you'll probably want to" print yellow("run the community command"), print "by running", red("'cuckoo community'"), "manually." print "The community command will fetch the latest monitoring updates" print "and Cuckoo Signatures."
def cuckoo_rooter(socket_path, group, service, iptables, ip): try: import grp except ImportError: sys.exit(red( "Could not find the `grp` module, the Cuckoo Rooter is only " "supported under Linux operating systems." )) if not service or not os.path.exists(service): sys.exit(red( "The service binary is not available, please configure it!\n" "Note that on CentOS you should provide --service /sbin/service, " "rather than using the Ubuntu/Debian default /usr/sbin/service." )) if not iptables or not os.path.exists(iptables): sys.exit(red("The `iptables` binary is not available, eh?!")) if not ip or not os.path.exists(ip): sys.exit(red("The `ip` binary is not available, eh?!")) if os.getuid(): sys.exit(red( "This utility is supposed to be ran as root user. Please invoke " "it with the --sudo flag (e.g., 'cuckoo rooter --sudo') so it " "will automatically prompt for your password (this naturally only " "works for users with sudo capabilities)." )) if os.path.exists(socket_path): os.remove(socket_path) server = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) server.bind(socket_path) # Provide the correct file ownership and permission so Cuckoo can use it # from an unprivileged process, based on Sean Whalen's routetor. try: gr = grp.getgrnam(group) except KeyError: sys.exit(red( "The group ('%s') does not exist. Please define the group / user " "through which Cuckoo will connect to the rooter, e.g., " "'cuckoo rooter -g myuser'." % group )) os.chown(socket_path, 0, gr.gr_gid) os.chmod(socket_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IWGRP) # Initialize global variables. s.service = service s.iptables = iptables s.ip = ip while True: try: command, addr = server.recvfrom(4096) except socket.error as e: if e.errno == errno.EINTR: continue raise e try: obj = json.loads(command) except: log.info("Received invalid request: %r", command) continue command = obj.get("command") args = obj.get("args", []) kwargs = obj.get("kwargs", {}) if not isinstance(command, basestring) or command not in handlers: log.info("Received incorrect command: %r", command) continue if not isinstance(args, (tuple, list)): log.info("Invalid arguments type: %r", args) continue if not isinstance(kwargs, dict): log.info("Invalid keyword arguments: %r", kwargs) continue for arg in args + kwargs.keys() + kwargs.values(): if not isinstance(arg, basestring): log.info("Invalid argument detected: %r", arg) break else: log.debug( "Processing command: %s %s %s", command, " ".join(args), " ".join("%s=%s" % (k, v) for k, v in kwargs.items()) ) output = e = None try: output = handlers[command](*args, **kwargs) except Exception as e: log.exception("Error executing command: %s", e) server.sendto(json.dumps({ "output": output, "exception": str(e) if e else None, }), addr)
from django.template.base import TemplateSyntaxError from cuckoo.common.colors import red from cuckoo.common.elastic import elastic from cuckoo.common.mongo import mongo from cuckoo.core.startup import init_rooter, init_routing from cuckoo.misc import cwd, decide_cwd if cwd(root=True) is None: decide_cwd(exists=True) # Connect to MongoDB (mandatory). if not mongo.init(): sys.exit(red( "In order to use the Cuckoo Web Interface it is required to have " "MongoDB up-and-running and enabled in Cuckoo. Please refer to our " "official documentation as well as the $CWD/conf/reporting.conf file." )) mongo.connect() # Connect to ElasticSearch (optional). elastic.init() elastic.connect() # In case we have VPNs enabled we need to initialize through the following # two methods as they verify the interaction with VPNs as well as gather # which VPNs are available (for representation upon File/URL submission). init_rooter() init_routing()
def cuckoo_init(level, ctx, cfg=None): """Initialize Cuckoo configuration. @param quiet: enable quiet mode. """ logo() # It would appear this is the first time Cuckoo is being run (on this # Cuckoo Working Directory anyway). if not os.path.isdir(cwd()) or not os.listdir(cwd()): cuckoo_create(ctx.user, cfg) sys.exit(0) # Determine if this is a proper CWD. if not os.path.exists(cwd(".cwd")): sys.exit( "No proper Cuckoo Working Directory was identified, did you pass " "along the correct directory? For new installations please use a " "non-existant directory to build up the CWD! You can craft a CWD " "manually, but keep in mind that the CWD layout may change along " "with Cuckoo releases (and don't forget to fill out '$CWD/.cwd')!") init_console_logging(level) # Only one Cuckoo process should exist per CWD. Run this check before any # files are possibly modified. Note that we mkdir $CWD/pidfiles/ here as # its CWD migration rules only kick in after the pidfile check. mkdir(cwd("pidfiles")) pidfile = Pidfile("cuckoo") if pidfile.exists(): log.error(red("Cuckoo is already running. PID: %s"), pidfile.pid) sys.exit(1) pidfile.create() check_configs() check_version() ctx.log and init_logging(level) # Determine if any CWD updates are required and if so, do them. current = open(cwd(".cwd"), "rb").read().strip() latest = open(cwd(".cwd", private=True), "rb").read().strip() if current != latest: migrate_cwd() open(cwd(".cwd"), "wb").write(latest) # Ensure the user is able to create and read temporary files. if not ensure_tmpdir(): sys.exit(1) Database().connect() # Load additional Signatures. load_signatures() init_modules() init_tasks() init_yara() init_binaries() init_rooter() init_routing() signatures = 0 for sig in cuckoo.signatures: if not sig.enabled: continue signatures += 1 if not signatures: log.warning( "It appears that you haven't loaded any Cuckoo Signatures. " "Signatures are highly recommended and improve & enrich the " "information extracted during an analysis. They also make up " "for the analysis score that you see in the Web Interface - so, " "pretty important!") log.warning( "You'll be able to fetch all the latest Cuckoo Signaturs, Yara " "rules, and more goodies by running the following command:") log.info("$ %s", green(format_command("community")))
def web(ctx, args, host, port, uwsgi, nginx): """Operate the Cuckoo Web Interface. Use "--help" to get this help message and "help" to find Django's manage.py potential subcommands. """ username = ctx.parent.user or getuser() if uwsgi: print "[uwsgi]" print "plugins = python" if os.environ.get("VIRTUAL_ENV"): print "virtualenv =", os.environ["VIRTUAL_ENV"] print "module = cuckoo.web.web.wsgi" print "uid =", username print "gid =", username dirpath = os.path.join(cuckoo.__path__[0], "web", "static") print "static-map = /static=%s" % dirpath print "# If you're getting errors about the PYTHON_EGG_CACHE, then" print "# uncomment the following line and add some path that is" print "# writable from the defined user." print "# env = PYTHON_EGG_CACHE=" print "env = CUCKOO_APP=web" print "env = CUCKOO_CWD=%s" % cwd() return if nginx: print "upstream _uwsgi_cuckoo_web {" print " server unix:/run/uwsgi/app/cuckoo-web/socket;" print "}" print print "server {" print " listen %s:%d;" % (host, port) print print " # Cuckoo Web Interface" print " location / {" print " client_max_body_size 1G;" print " proxy_redirect off;" print " proxy_set_header X-Forwarded-Proto $scheme;" print " uwsgi_pass _uwsgi_cuckoo_web;" print " include uwsgi_params;" print " }" print "}" return # Switch to cuckoo/web and add the current path to sys.path as the Web # Interface is using local imports here and there. # TODO Rename local imports to either cuckoo.web.* or relative imports. sys.argv[0] = os.path.abspath(sys.argv[0]) os.chdir(os.path.join(cuckoo.__path__[0], "web")) sys.path.insert(0, ".") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cuckoo.web.web.settings") # The Django HTTP server also imports the WSGI module for some reason, so # ensure that WSGI is able to load. os.environ["CUCKOO_APP"] = "web" os.environ["CUCKOO_CWD"] = cwd() from django.core.management import execute_from_command_line init_console_logging(level=ctx.parent.level) Database().connect() try: execute_from_command_line( ("cuckoo", "runserver", "%s:%d" % (host, port)) if not args else ("cuckoo",) + args ) except CuckooCriticalError as e: message = red("{0}: {1}".format(e.__class__.__name__, e)) if len(log.handlers): log.critical(message) else: sys.stderr.write("{0}\n".format(message)) sys.exit(1)
def cuckoo_init(level, ctx, cfg=None): """Initialize Cuckoo configuration. @param quiet: enable quiet mode. """ logo() # It would appear this is the first time Cuckoo is being run (on this # Cuckoo Working Directory anyway). if not os.path.isdir(cwd()) or not os.listdir(cwd()): cuckoo_create(ctx.user, cfg) sys.exit(0) # Determine if this is a proper CWD. if not os.path.exists(cwd(".cwd")): sys.exit( "No proper Cuckoo Working Directory was identified, did you pass " "along the correct directory? For new installations please use a " "non-existant directory to build up the CWD! You can craft a CWD " "manually, but keep in mind that the CWD layout may change along " "with Cuckoo releases (and don't forget to fill out '$CWD/.cwd')!" ) init_console_logging(level) # Only one Cuckoo process should exist per CWD. Run this check before any # files are possibly modified. Note that we mkdir $CWD/pidfiles/ here as # its CWD migration rules only kick in after the pidfile check. mkdir(cwd("pidfiles")) pidfile = Pidfile("cuckoo") if pidfile.exists(): log.error(red("Cuckoo is already running. PID: %s"), pidfile.pid) sys.exit(1) pidfile.create() check_configs() check_version() ctx.log and init_logging(level) # Determine if any CWD updates are required and if so, do them. current = open(cwd(".cwd"), "rb").read().strip() latest = open(cwd(".cwd", private=True), "rb").read().strip() if current != latest: migrate_cwd() open(cwd(".cwd"), "wb").write(latest) Database().connect() # Load additional Signatures. load_signatures() init_modules() init_tasks() init_yara() init_binaries() init_rooter() init_routing() signatures = 0 for sig in cuckoo.signatures: if not sig.enabled: continue signatures += 1 if not signatures: log.warning( "It appears that you haven't loaded any Cuckoo Signatures. " "Signatures are highly recommended and improve & enrich the " "information extracted during an analysis. They also make up " "for the analysis score that you see in the Web Interface - so, " "pretty important!" ) log.warning( "You'll be able to fetch all the latest Cuckoo Signaturs, Yara " "rules, and more goodies by running the following command:" ) raw = cwd(raw=True) if raw == "." or raw == "~/.cuckoo": command = "cuckoo community" elif " " in raw or "'" in raw: command = 'cuckoo --cwd "%s" community' % raw else: command = "cuckoo --cwd %s community" % raw log.info("$ %s", green(command))
def submit_tasks(target, options, package, custom, owner, timeout, priority, machine, platform, memory, enforce_timeout, clock, tags, remote, pattern, maxcount, is_unique, is_url, is_baseline, is_shuffle): db = Database() data = dict( package=package or "", timeout=timeout, options=options, priority=priority, machine=machine, platform=platform, custom=custom, owner=owner, tags=tags, memory="1" if memory else "0", enforce_timeout="1" if enforce_timeout else "0", clock=clock, unique="1" if is_unique else "0", ) if is_baseline: if remote: print "Remote baseline support has not yet been implemented." return task_id = db.add_baseline(timeout, owner, machine, memory) yield "Baseline", machine, task_id return if is_url and is_unique: print "URL doesn't have --unique support yet." return if is_url: for url in target: if not remote: data.pop("unique", None) task_id = db.add_url(to_unicode(url), **data) yield "URL", url, task_id continue data["url"] = to_unicode(url) try: r = requests.post("http://%s/tasks/create/url" % remote, data=data) yield "URL", url, r.json()["task_id"] except Exception as e: print "%s: unable to submit URL: %s" % (bold(red("Error")), e) else: files = [] for path in target: files.extend(enumerate_files(os.path.abspath(path), pattern)) if is_shuffle: random.shuffle(files) for filepath in files: if not os.path.getsize(filepath): print "%s: sample %s (skipping file)" % (bold( yellow("Empty")), filepath) continue if maxcount is not None: if not maxcount: break maxcount -= 1 if not remote: if is_unique: sha256 = File(filepath).get_sha256() if db.find_sample(sha256=sha256): yield "File", filepath, None continue data.pop("unique", None) task_id = db.add_path(file_path=filepath, **data) yield "File", filepath, task_id continue files = { "file": (os.path.basename(filepath), open(filepath, "rb")), } try: r = requests.post("http://%s/tasks/create/file" % remote, data=data, files=files) yield "File", filepath, r.json()["task_id"] except Exception as e: print "%s: unable to submit file: %s" % (bold(red("Error")), e) continue
def check_version(ignore_vuln=False): """Check version of Cuckoo.""" if not config("cuckoo:cuckoo:version_check"): return ignore_vuln = ignore_vuln or config("cuckoo:cuckoo:ignore_vulnerabilities") import pkg_resources print(" Checking for updates...") try: r = requests.get("https://cuckoosandbox.org/updates.json", params={"version": version}, timeout=6) r.raise_for_status() r = r.json() except (requests.RequestException, ValueError) as e: print(red(" Error checking for the latest Cuckoo version: %s!" % e)) return try: old = StrictVersion(version) < StrictVersion(r["version"]) except ValueError: old = True warnings = [] for deptype, vulns in r.get("vulnerable", {}).iteritems(): for dep in vulns: compare = dep.get("highest") or dep.get("lowest") # Check if any of the mentioned Python dependencies are installed if deptype == "pydep": try: v = pkg_resources.get_distribution( dep["name"]).parsed_version except (pkg_resources.DistributionNotFound, ValueError): continue # See if the mentioned virtualization software is used elif deptype == "machinery": if config("cuckoo:cuckoo:machinery") != dep["name"]: continue # If the version number cannot be determined, raise a warning # to be sure. Virtualization vulnerabilities can potentially # cause a lot of damage v = cuckoo.machinery.plugins[dep["name"]].version() if not v: warnings.append( bold( red("Potentially vulnerable %s version installed. " "Failed to retrieve its version. Update if version" " is: %s" % (dep["name"], compare)))) continue else: continue warn = False # If a range is specified, check if the current version falls # within the range. if dep.get("highest") and dep.get("lowest"): lv = LooseVersion(str(v)) if (lv >= LooseVersion(dep["lowest"]) and lv <= LooseVersion(dep["highest"])): warn = True # If no range is specified, use the specified operator to see if # the installed version is # 'if <operator> highest/lowest specified' elif cmp_version(str(v), compare, dep["op"]): warn = True # Warn the user the dependency must be updated/ if warn: info = dep.get("info") message = "Vulnerable version of %s installed (%s). It is " \ "highly recommended to update. Please update and " \ "restart Cuckoo." % (dep["name"], v) if deptype == "pydep": message += " 'pip install %s%s'" % (dep["name"], dep["recommended"]) else: message += " Recommended version: %s" % dep["recommended"] message = bold(red(message)) if info: message += yellow("\nAdditional information: %s" % info) warnings.append(message) if warnings: print(color(bold(red("Vulnerable dependencies found\n")), 5)) for warning in warnings: print("--> %s\n" % color(warning, 4)) if warnings and not ignore_vuln: print( "This check can be disabled by enabling " "'ignore_vulnerabilities' in cuckoo.conf under the " "[cuckoo] section") sys.exit(1) if old: msg = "Cuckoo Sandbox version %s is available now." % r["version"] print(red(" Outdated! ") + msg) else: print(green(" You're good to go!")) print("\n Our latest blogposts:") for blogpost in r["blogposts"]: print(" * %s, %s." % (yellow(blogpost["title"]), blogpost["date"])) print(" %s" % red(blogpost["oneline"])) print(" More at %s" % blogpost["url"]) print("") return r
def log_error(message, *args): """Prints to stderr if no logging has been initialized yet.""" if not logging.getLogger().handlers: print >> sys.stderr, red("Configuration error: " + message % args) else: log.error(message, *args)
def import_cuckoo(username, mode, dirpath): version = identify(dirpath) if not version: raise CuckooOperationalError( "The path that you specified is not a proper Cuckoo setup. Please " "point the path to the root of your older Cuckoo setup, i.e., to " "the directory containing the cuckoo.py script!") # TODO Copy over the configuration and ignore the database. if version in ("0.4", "0.4.1", "0.4.2"): raise CuckooOperationalError( "Importing from version 0.4, 0.4.1, or 0.4.2 is not supported as " "there are no database migrations for that version. Please start " "from scratch, your configuration would have been obsolete anyway!" ) print "We've identified a Cuckoo Sandbox %s installation!" % version if os.path.isdir(cwd()) and os.listdir(cwd()): raise CuckooOperationalError( "This Cuckoo Working Directory already exists. Please import to " "a new/clean Cuckoo Working Directory.") # Following are various recursive imports. from cuckoo.apps import migrate_database from cuckoo.main import cuckoo_create print "Reading in the old configuration.." # Port the older configuration. cfg = Config.from_confdir(os.path.join(dirpath, "conf"), loose=True) cfg = migrate_conf(cfg, version) print " configuration has been migrated to the latest version!" print # Create a fresh Cuckoo Working Directory. cuckoo_create(username, cfg, quiet=True) dburi = cfg["cuckoo"]["database"]["connection"] # Ask if the user would like to make a backup of the SQL database and in # the case of sqlite3, copy/move/symlink cuckoo.db to the CWD. sqldump(dburi, dirpath) movesql(dburi, mode, dirpath) # Run database migrations. if not migrate_database(): raise CuckooOperationalError( "Error migrating your old Cuckoo database!") # Link or copy all of the older results to the new CWD. import_legacy_analyses(mode, dirpath) # Urge the user to run the community command. print print "You have successfully imported your old version of Cuckoo!" print "However, in order to get up-to-date, you'll probably want to" print yellow("run the community command"), print "by running", red("'cuckoo community'"), "manually." print "The community command will fetch the latest monitoring updates" print "and Cuckoo Signatures."
def cuckoo_rooter(socket_path, group, service, iptables, ip): try: import grp except ImportError: sys.exit(red( "Could not find the `grp` module, the Cuckoo Rooter is only " "supported under Linux operating systems." )) if not service or not os.path.exists(service): sys.exit(red( "The service binary is not available, please configure it!\n" "Note that on CentOS you should provide --service /sbin/service, " "rather than using the Ubuntu/Debian default /usr/sbin/service." )) if not iptables or not os.path.exists(iptables): sys.exit(red("The `iptables` binary is not available, eh?!")) if not ip or not os.path.exists(ip): sys.exit(red("The `ip` binary is not available, eh?!")) if os.getuid(): sys.exit(red( "This utility is supposed to be ran as root user. Please invoke " "it with the --sudo flag (e.g., 'cuckoo rooter --sudo') so it " "will automatically prompt for your password (this naturally only " "works for users with sudo capabilities)." )) if os.path.exists(socket_path): os.remove(socket_path) server = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) server.bind(socket_path) # Provide the correct file ownership and permission so Cuckoo can use it # from an unprivileged process, based on Sean Whalen's routetor. try: gr = grp.getgrnam(group) except KeyError: sys.exit(red( "The group ('%s') does not exist. Please define the group / user " "through which Cuckoo will connect to the rooter, e.g., " "'cuckoo rooter -g myuser'." % group )) os.chown(socket_path, 0, gr.gr_gid) os.chmod(socket_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IWGRP) # Initialize global variables. s.service = service s.iptables = iptables s.iptables_save = "/sbin/iptables-save" s.iptables_restore = "/sbin/iptables-restore" s.ip = ip # Simple object to allow a signal handler to stop the rooter loop class Run(object): def __init__(self): self.run = True do = Run() def handle_sigterm(sig, f): do.run = False server.shutdown(socket.SHUT_RDWR) server.close() cleanup_rooter() signal.signal(signal.SIGTERM, handle_sigterm) while do.run: try: command, addr = server.recvfrom(4096) except socket.error as e: if e.errno == errno.EINTR: continue elif e.errno == errno.EBADF and not do.run: continue raise e try: obj = json.loads(command) except: log.info("Received invalid request: %r", command) continue command = obj.get("command") args = obj.get("args", []) kwargs = obj.get("kwargs", {}) if not isinstance(command, basestring) or command not in handlers: log.info("Received incorrect command: %r", command) continue if not isinstance(args, (tuple, list)): log.info("Invalid arguments type: %r", args) continue if not isinstance(kwargs, dict): log.info("Invalid keyword arguments: %r", kwargs) continue for arg in args + kwargs.keys() + kwargs.values(): if not isinstance(arg, basestring): log.info("Invalid argument detected: %r", arg) break else: log.info( "Processing command: %s %s %s", command, " ".join(args), " ".join("%s=%s" % (k, v) for k, v in kwargs.items()) ) output = e = None try: output = handlers[command](*args, **kwargs) except Exception as e: log.exception("Error executing command: %s", e) server.sendto(json.dumps({ "output": output, "exception": str(e) if e else None, }), addr)