def server(ctx, host, port, uwsgi, nginx): username = ctx.parent.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.apps.distributed" print "callable = app" print "uid =", username print "gid =", username print "env = CUCKOO_APP=dist" print "env = CUCKOO_CWD=%s" % cwd() return if nginx: print "upstream _uwsgi_cuckoo_distributed {" print " server unix:/run/uwsgi/app/cuckoo-distributed/socket;" print "}" print print "server {" print " listen %s:%d;" % (host, port) print print " # REST Distributed app" print " location / {" print " client_max_body_size 1G;" print " uwsgi_pass _uwsgi_cuckoo_distributed;" print " include uwsgi_params;" print " }" print "}" return cuckoo_distributed(host, port, ctx.parent.parent.level == logging.DEBUG)
def temppath(): """Return the true temporary directory.""" tmppath = config("cuckoo:cuckoo:tmppath") # Backwards compatibility with older configuration. if not tmppath or tmppath == "/tmp": return os.path.join(tempfile.gettempdir(), "cuckoo-tmp-%s" % getuser()) return tmppath
def temppath(): """Returns the true temporary directory.""" tmppath = config("cuckoo:cuckoo:tmppath") # Backwards compatibility with older configuration. if not tmppath or tmppath == "/tmp": return os.path.join( tempfile.gettempdir(), "cuckoo-tmp-%s" % getuser() ) return tmppath
def test_temppath(): set_cwd(tempfile.mkdtemp()) cuckoo_create() assert temppath() == os.path.join( tempfile.gettempdir(), "cuckoo-tmp-%s" % getuser() ) set_cwd(tempfile.mkdtemp()) cuckoo_create(cfg={ "cuckoo": { "cuckoo": { "tmppath": "", }, }, }) assert temppath() == os.path.join( tempfile.gettempdir(), "cuckoo-tmp-%s" % getuser() ) set_cwd(tempfile.mkdtemp()) cuckoo_create(cfg={ "cuckoo": { "cuckoo": { "tmppath": "/tmp", }, }, }) assert temppath() == os.path.join( tempfile.gettempdir(), "cuckoo-tmp-%s" % getuser() ) set_cwd(tempfile.mkdtemp()) cuckoo_create(cfg={ "cuckoo": { "cuckoo": { "tmppath": "/custom/directory", }, }, }) assert temppath() == "/custom/directory"
def init(ctx, conf): """Initializes Cuckoo and its configuration.""" if conf and os.path.exists(conf): cfg = read_kv_conf(conf) else: cfg = None # If this is a new install, also apply the provided configuration. cuckoo_init(logging.INFO, ctx.parent, cfg) # If this is an existing install, overwrite the supervisord.conf # configuration file (if needed) as well as the Cuckoo configuration. write_supervisor_conf(ctx.parent.user or getuser()) write_cuckoo_conf(cfg)
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 api(ctx, host, port, uwsgi, nginx): """Operate the Cuckoo REST API.""" 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.apps.api" print "callable = app" print "uid =", username print "gid =", username print "env = CUCKOO_APP=api" print "env = CUCKOO_CWD=%s" % cwd() return if nginx: print "upstream _uwsgi_cuckoo_api {" print " server unix:/run/uwsgi/app/cuckoo-api/socket;" print "}" print print "server {" print " listen %s:%d;" % (host, port) print print " # REST API app" print " location / {" print " client_max_body_size 1G;" print " uwsgi_pass _uwsgi_cuckoo_api;" print " include uwsgi_params;" print " }" print "}" return init_console_logging(level=ctx.parent.level) Database().connect() if not ensure_tmpdir(): sys.exit(1) cuckoo_api(host, port, ctx.parent.level == logging.DEBUG)
def api(ctx, host, port, uwsgi, nginx): """Operate the Cuckoo REST API.""" 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.apps.api" print "callable = app" print "uid =", username print "gid =", username print "env = CUCKOO_APP=api" print "env = CUCKOO_CWD=%s" % cwd() return if nginx: print "upstream _uwsgi_cuckoo_api {" print " server unix:/run/uwsgi/app/cuckoo-api/socket;" print "}" print print "server {" print " listen %s:%d;" % (host, port) print print " # REST API app" print " location / {" print " client_max_body_size 1G;" print " uwsgi_pass _uwsgi_cuckoo_api;" print " include uwsgi_params;" print " }" print "}" return init_console_logging(level=ctx.parent.level) Database().connect() cuckoo_api(host, port, ctx.parent.level == logging.DEBUG)
def start(self): if not self.machine.interface: log.error("Network interface not defined, network capture aborted") return False # Handle special pcap dumping options. if "nictrace" in self.machine.options: return True tcpdump = self.options["tcpdump"] bpf = self.options["bpf"] or "" file_path = cwd("storage", "analyses", "%s" % self.task.id, "dump.pcap") if not os.path.exists(tcpdump): log.error( "Tcpdump does not exist at path \"%s\", network " "capture aborted", tcpdump) return False # TODO: this isn't working. need to fix. # mode = os.stat(tcpdump)[stat.ST_MODE] # if (mode & stat.S_ISUID) == 0: # log.error("Tcpdump is not accessible from this user, " # "network capture aborted") # return pargs = [ tcpdump, "-U", "-q", "-s", "0", "-n", "-i", self.machine.interface, ] # Trying to save pcap with the same user which cuckoo is running. user = getuser() if user: pargs.extend(["-Z", user]) pargs.extend(["-w", file_path]) pargs.extend(["host", self.machine.ip]) if self.task.options.get("sniffer.debug") != "1": # Do not capture Agent traffic. pargs.extend([ "and", "not", "(", "dst", "host", self.machine.ip, "and", "dst", "port", "%s" % CUCKOO_GUEST_PORT, ")", "and", "not", "(", "src", "host", self.machine.ip, "and", "src", "port", "%s" % CUCKOO_GUEST_PORT, ")", ]) # Do not capture ResultServer traffic. pargs.extend([ "and", "not", "(", "dst", "host", self.machine.resultserver_ip, "and", "dst", "port", "%s" % self.machine.resultserver_port, ")", "and", "not", "(", "src", "host", self.machine.resultserver_ip, "and", "src", "port", "%s" % self.machine.resultserver_port, ")", ]) if bpf: pargs.extend(["and", "(", bpf, ")"]) try: self.proc = Popen(pargs, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) except (OSError, ValueError): log.exception( "Failed to start sniffer (interface=%s, host=%s, pcap=%s)", self.machine.interface, self.machine.ip, file_path, ) return False log.info( "Started sniffer with PID %d (interface=%s, host=%s, pcap=%s)", self.proc.pid, self.machine.interface, self.machine.ip, file_path, ) return True
def test_sniffer(): set_cwd(tempfile.mkdtemp()) s = Sniffer() s.set_task(task) s.set_machine(machine) s.set_options({ "tcpdump": __file__, "bpf": None, }) with mock.patch("subprocess.Popen") as p: p.return_value = BasePopen() assert s.start() is True user = getuser() if user: user = "******" % user # Test regular setup. command = ( "%s -U -q -s 0 -n -i interface %s-w %s " "host 1.2.3.4 and " "not ( dst host 1.2.3.4 and dst port 8000 ) and " "not ( src host 1.2.3.4 and src port 8000 ) and " "not ( dst host 1.1.1.1 and dst port 1234 ) and " "not ( src host 1.1.1.1 and src port 1234 )" % ( __file__, user or "", cwd("storage", "analyses", "42", "dump.pcap") ) ) if is_windows(): p.assert_called_once_with( command.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE ) else: p.assert_called_once_with( command.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True ) assert s.stop() is None # Test a bpf rule. s.options["bpf"] = "not arp" with mock.patch("subprocess.Popen") as p: p.return_value = BasePopen() assert s.start() is True if is_windows(): p.assert_called_once_with( command.split() + ["and", "(", "not arp", ")"], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) else: p.assert_called_once_with( command.split() + ["and", "(", "not arp", ")"], close_fds=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) assert s.stop() is None # Test an invalid executable path. with mock.patch("os.path.exists") as p: p.return_value = False assert s.start() is False # Test stdout output from tcpdump. with mock.patch("subprocess.Popen") as p: p.return_value = PopenStdout() assert s.start() is True with pytest.raises(CuckooOperationalError) as e: assert s.stop() e.match("did not expect standard output") # Test unknown stderr output from tcpdump. with mock.patch("subprocess.Popen") as p: p.return_value = PopenStderr() assert s.start() is True with pytest.raises(CuckooOperationalError) as e: assert s.stop() e.match("following standard error output") # Test OSError and/or ValueError exceptions. with mock.patch("subprocess.Popen") as p: p.side_effect = OSError("this is awkward") assert s.start() is False with mock.patch("subprocess.Popen") as p: p.side_effect = ValueError("this is awkward") assert s.start() is False
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)
from cuckoo.misc import is_windows, is_linux, is_macosx, getuser, mkdir # Note that collect_ignore is a parameter for pytest so that it knows which # unit tests to skip etc. In other words, perform platform-specific unit tests # (in terms of the Cuckoo Analyzer) depending on the current host machine. collect_ignore = [] if is_windows(): sys.path.insert(0, "cuckoo/data/analyzer/windows") collect_ignore.append("tests/linux") collect_ignore.append("tests/darwin") # Copy over the monitoring binaries as if we were in a real analysis. monitor = open("cuckoo/data/monitor/latest", "rb").read().strip() for filename in os.listdir("cuckoo/data/monitor/%s" % monitor): shutil.copy("cuckoo/data/monitor/%s/%s" % (monitor, filename), "cuckoo/data/analyzer/windows/bin/%s" % filename) if is_linux(): sys.path.insert(0, "cuckoo/data/analyzer/linux") collect_ignore.append("tests/windows") collect_ignore.append("tests/darwin") if is_macosx(): sys.path.insert(0, "cuckoo/data/analyzer/darwin") collect_ignore.append("tests/windows") collect_ignore.append("tests/linux") # Ensure the Cuckoo TMP dir exists, as some tests rely on it. mkdir(os.path.join(tempfile.gettempdir(), "cuckoo-tmp-%s" % getuser()))
def test_getuser(): # TODO This probably doesn't work on all platforms. assert getuser() == subprocess.check_output(["id", "-un"]).strip()
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. 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() if not args: execute_from_command_line( ("cuckoo", "runserver", "%s:%d" % (host, port)) ) else: execute_from_command_line(("cuckoo",) + args)
def start(self): if not self.machine.interface: log.error("Network interface not defined, network capture aborted") return False # Handle special pcap dumping options. if "nictrace" in self.machine.options: return True tcpdump = self.options["tcpdump"] bpf = self.options["bpf"] or "" file_path = cwd("storage", "analyses", "%s" % self.task.id, "dump.pcap") if not os.path.exists(tcpdump): log.error("Tcpdump does not exist at path \"%s\", network " "capture aborted", tcpdump) return False # TODO: this isn't working. need to fix. # mode = os.stat(tcpdump)[stat.ST_MODE] # if (mode & stat.S_ISUID) == 0: # log.error("Tcpdump is not accessible from this user, " # "network capture aborted") # return pargs = [ tcpdump, "-U", "-q", "-s", "0", "-n", "-i", self.machine.interface, ] # Trying to save pcap with the same user which cuckoo is running. user = getuser() if user: pargs.extend(["-Z", user]) pargs.extend(["-w", file_path]) pargs.extend(["host", self.machine.ip]) if self.task.options.get("sniffer.debug") != "1": # Do not capture Agent traffic. pargs.extend([ "and", "not", "(", "dst", "host", self.machine.ip, "and", "dst", "port", "%s" % CUCKOO_GUEST_PORT, ")", "and", "not", "(", "src", "host", self.machine.ip, "and", "src", "port", "%s" % CUCKOO_GUEST_PORT, ")", ]) # Do not capture ResultServer traffic. pargs.extend([ "and", "not", "(", "dst", "host", self.machine.resultserver_ip, "and", "dst", "port", "%s" % self.machine.resultserver_port, ")", "and", "not", "(", "src", "host", self.machine.resultserver_ip, "and", "src", "port", "%s" % self.machine.resultserver_port, ")", ]) if bpf: pargs.extend(["and", "(", bpf, ")"]) try: self.proc = Popen( pargs, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True ) except (OSError, ValueError): log.exception( "Failed to start sniffer (interface=%s, host=%s, pcap=%s)", self.machine.interface, self.machine.ip, file_path, ) return False log.info( "Started sniffer with PID %d (interface=%s, host=%s, pcap=%s)", self.proc.pid, self.machine.interface, self.machine.ip, file_path, ) return True
def test_sniffer(): set_cwd(tempfile.mkdtemp()) s = Sniffer() s.set_task(task) s.set_machine(machine) s.set_options({ "tcpdump": __file__, "bpf": None, }) with mock.patch("subprocess.Popen") as p: p.return_value = BasePopen() assert s.start() is True user = getuser() if user: user = "******" % user # Test regular setup. command = ( "%s -U -q -s 0 -n -i interface %s-w %s " "host 1.2.3.4 and " "not ( dst host 1.2.3.4 and dst port 8000 ) and " "not ( src host 1.2.3.4 and src port 8000 ) and " "not ( dst host 1.1.1.1 and dst port 1234 ) and " "not ( src host 1.1.1.1 and src port 1234 )" % (__file__, user or "", cwd("storage", "analyses", "42", "dump.pcap"))) if is_windows(): p.assert_called_once_with(command.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) else: p.assert_called_once_with(command.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) assert s.stop() is None # Test a bpf rule. s.options["bpf"] = "not arp" with mock.patch("subprocess.Popen") as p: p.return_value = BasePopen() assert s.start() is True if is_windows(): p.assert_called_once_with(command.split() + ["and", "(", "not arp", ")"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) else: p.assert_called_once_with(command.split() + ["and", "(", "not arp", ")"], close_fds=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) assert s.stop() is None # Test an invalid executable path. with mock.patch("os.path.exists") as p: p.return_value = False assert s.start() is False # Test permission denied on tcpdump. with mock.patch("subprocess.Popen") as p: p.return_value = PopenPermissionDenied() assert s.start() is True with pytest.raises(CuckooOperationalError) as e: assert s.stop() e.match("the network traffic during the") e.match("denied-for-tcpdump") # Test stdout output from tcpdump. with mock.patch("subprocess.Popen") as p: p.return_value = PopenStdout() assert s.start() is True with pytest.raises(CuckooOperationalError) as e: assert s.stop() e.match("did not expect standard output") # Test unknown stderr output from tcpdump. with mock.patch("subprocess.Popen") as p: p.return_value = PopenStderr() assert s.start() is True with pytest.raises(CuckooOperationalError) as e: assert s.stop() e.match("following standard error output") # Test OSError and/or ValueError exceptions. with mock.patch("subprocess.Popen") as p: p.side_effect = OSError("this is awkward") assert s.start() is False with mock.patch("subprocess.Popen") as p: p.side_effect = ValueError("this is awkward") assert s.start() is False