Skip to content

Commit

Permalink
Merge branch 'master' into staging
Browse files Browse the repository at this point in the history
  • Loading branch information
doomedraven committed Feb 7, 2025
2 parents 462fb0c + ad650c5 commit 8db0425
Show file tree
Hide file tree
Showing 12 changed files with 167 additions and 12 deletions.
2 changes: 2 additions & 0 deletions conf/default/web.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ reports_dl_allowed_to_all = yes
expose_process_log = no
# Show button to reprocess the task
reprocess_tasks = no
# Allows you to define URL splitter, "," is default
url_splitter = ,

# ratelimit for anon users
[ratelimit]
Expand Down
23 changes: 19 additions & 4 deletions docs/book/src/installation/host/routing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,16 @@ Following is the list of available routing options.
+-------------------------+--------------------------------------------------+
| :ref:`routing_tor` | Routes all traffic through Tor. |
+-------------------------+--------------------------------------------------+
| :ref:`routing_tun` | Route traffic though any "tun" interface |
+-------------------------+--------------------------------------------------+
| :ref:`routing_vpn` | Routes all traffic through one of perhaps |
| | multiple pre-defined VPN endpoints. |
+-------------------------+--------------------------------------------------+
| :ref:`routing_socks` | Routes all traffic through one of perhaps |
| | multiple pre-defined VPN endpoints. |
+-------------------------+--------------------------------------------------+


Using Per-Analysis Network Routing
==================================

Expand Down Expand Up @@ -358,6 +361,18 @@ correctly.

.. _`latest stable version of Tor here`: https://www.torproject.org/docs/debian.html.en


.. _routing_tun:

Tun Routing
^^^^^^^^^^^
This allows you to route via any ``tun`` interface. You can pass the tun
interface name on demand per analysis. The interface name can be ``tunX``
or ``tun_foo``. This assumes you create the tunnel inferface outside of CAPE.

Then you set the ``route=tun_foo`` on the ``/apiv2/tasks/create/file/``
API call.

.. _routing_vpn:

VPN Routing
Expand Down Expand Up @@ -454,13 +469,13 @@ VPN persistence & auto-restart `source`_::
6. Reload the daemons:
# sudo systemctl daemon-reload

1. Start the OpenVPN service:
7. Start the OpenVPN service:
# sudo systemctl start openvpn

2. Test if it is working by checking the external IP:
8. Test if it is working by checking the external IP:
# curl ifconfig.co

3. If curl is not installed:
9. If curl is not installed:
# sudo apt install curl

.. _`source`: https://www.ivpn.net/knowledgebase/linux/linux-autostart-openvpn-in-systemd-ubuntu/
Expand Down Expand Up @@ -568,7 +583,7 @@ Assuming you already have any VM running, to test the internet connection using
$ sudo python3 router_manager.py -r internet -e --vm-name win1 --verbose
$ sudo python3 router_manager.py -r internet -d --vm-name win1 --verbose

The ``-e`` flag is used to enable a route and ``-d`` is used to disable it. You can read more about all the options the utility has by running::
The ``-e`` flag is used to enable a route and ``-d`` is used to disable it. You can read more about all the options the utility has by running::

$ sudo python3 router_manager.py -h

Expand Down
16 changes: 16 additions & 0 deletions lib/cuckoo/core/analysis_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

# os.listdir('/sys/class/net/')
HAVE_NETWORKIFACES = False

try:
import psutil

Expand All @@ -43,6 +44,12 @@
latest_symlink_lock = threading.Lock()


def is_network_interface(intf: str):
global network_interfaces
network_interfaces = list(psutil.net_if_addrs().keys())
return intf in network_interfaces


class CuckooDeadMachine(Exception):
"""Exception thrown when a machine turns dead.
Expand Down Expand Up @@ -536,6 +543,9 @@ def route_network(self):
self.rt_table = vpns[self.route].rt_table
elif self.route in self.socks5s:
self.interface = ""
elif self.route[:3] == "tun" and is_network_interface(self.route):
# tunnel interface starts with "tun" and interface exists on machine
self.interface = self.route
else:
self.log.warning("Unknown network routing destination specified, ignoring routing for this analysis: %s", self.route)
self.interface = None
Expand Down Expand Up @@ -583,6 +593,9 @@ def route_network(self):

elif self.route in ("none", "None", "drop"):
self.rooter_response = rooter("drop_enable", self.machine.ip, str(self.cfg.resultserver.port))
elif self.route[:3] == "tun" and is_network_interface(self.route):
self.log.info("Network interface {} is tunnel", self.interface)
self.rooter_response = rooter("interface_route_tun_enable", self.machine.ip, self.route, str(self.task.id))

self._rooter_response_check()

Expand Down Expand Up @@ -714,6 +727,9 @@ def unroute_network(self):

elif self.route in ("none", "None", "drop"):
self.rooter_response = rooter("drop_disable", self.machine.ip, str(self.cfg.resultserver.port))
elif self.route[:3] == "tun":
self.log.info("Disable tunnel interface {}", self.interface)
self.rooter_response = rooter("interface_route_tun_disable", self.machine.ip, self.route, str(self.task.id))

self._rooter_response_check()

Expand Down
2 changes: 1 addition & 1 deletion lib/cuckoo/core/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ def is_short_on_disk_space(self):
# Resolve the full base path to the analysis folder, just in
# case somebody decides to make a symbolic link out of it.
dir_path = os.path.join(CUCKOO_ROOT, "storage", "analyses")
need_space, space_available = free_space_monitor(dir_path, return_value=True, analysis=True)
need_space, space_available = free_space_monitor(dir_path, analysis=True)
if need_space:
log.error(
"Not enough free disk space! (Only %d MB!). You can change limits it in cuckoo.conf -> freespace", space_available
Expand Down
15 changes: 14 additions & 1 deletion modules/processing/analysisinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,31 @@
import time
from contextlib import suppress
from datetime import datetime
from pathlib import Path

from lib.cuckoo.common.abstracts import Processing
from lib.cuckoo.common.constants import CUCKOO_VERSION
from lib.cuckoo.common.constants import CUCKOO_ROOT, CUCKOO_VERSION
from lib.cuckoo.common.exceptions import CuckooProcessingError
from lib.cuckoo.common.path_utils import path_exists
from lib.cuckoo.common.utils import get_options
from lib.cuckoo.core.database import Database

# https://stackoverflow.com/questions/14989858/get-the-current-git-hash-in-a-python-script/68215738#68215738

log = logging.getLogger(__name__)

db = Database()


def get_running_commit() -> str:
git_folder = Path(CUCKOO_ROOT, ".git")
head_name = Path(git_folder, "HEAD").read_text().split("\n")[0].split(" ")[-1]
return Path(git_folder, head_name).read_text().replace("\n", "")


CAPE_CURRENT_COMMIT_HASH = get_running_commit()


class AnalysisInfo(Processing):
"""General information about analysis session."""

Expand Down Expand Up @@ -111,4 +123,5 @@ def run(self):
"source_url": source_url,
"route": self.task.get("route"),
"user_id": self.task.get("user_id"),
"CAPE_current_commit": CAPE_CURRENT_COMMIT_HASH,
}
18 changes: 17 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ pyzipper = "0.3.6"
flare-capa = "9.0.0"

Cython = "3.0.11"
# pyre2 = "0.3.6" # Dead for python3.11
Django = ">=4.2.18"
SQLAlchemy = "1.4.50"
SQLAlchemy-Utils = "0.41.1"
Expand All @@ -53,6 +52,7 @@ bs4 = "0.0.1"
pydeep2 = "0.5.1"
django-recaptcha = "4.0.0" # https://pypi.org/project/django-recaptcha/
django-crispy-forms = "2.3"
crispy-bootstrap4 = "2024.10"
django-settings-export = "1.2.1"
django-csp = "3.8"
django-extensions = "3.2.3"
Expand Down
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@ colorclass==2.2.2 ; python_version >= "3.10" and python_version < "4.0" \
constantly==23.10.4 ; python_version >= "3.10" and python_version < "4.0" \
--hash=sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9 \
--hash=sha256:aa92b70a33e2ac0bb33cd745eb61776594dc48764b06c35e0efd050b7f1c7cbd
crispy-bootstrap4==2024.10 ; python_version >= "3.10" and python_version < "4.0" \
--hash=sha256:138a97884044ae4c4799c80595b36c42066e4e933431e2e971611e251c84f96c \
--hash=sha256:503e8922b0f3b5262a6fdf303a3a94eb2a07514812f1ca130b88f7c02dd25e2b
crudini==0.9.5 ; python_version >= "3.10" and python_version < "4.0" \
--hash=sha256:59ae650f45af82a64afc33eb876909ee0c4888dc4e8711ef59731c1edfda5e24 \
--hash=sha256:84bc208dc7d89571bdc3c99274259d0b32d6b3a692d4255524f2eb4b64e9195c
Expand Down
2 changes: 1 addition & 1 deletion utils/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ def autoprocess(
# If not enough free disk space is available, then we print an
# error message and wait another round (this check is ignored
# when the freespace configuration variable is set to zero).
if cfg.cuckoo.freespace:
if cfg.cuckoo.freespace_processing:
# Resolve the full base path to the analysis folder, just in
# case somebody decides to make a symbolic link out of it.
dir_path = os.path.join(CUCKOO_ROOT, "storage", "analyses")
Expand Down
90 changes: 90 additions & 0 deletions utils/rooter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import argparse
import errno
import grp
import ipaddress
import json
import logging.handlers
import os
Expand Down Expand Up @@ -47,6 +48,49 @@ def run(*args):
return stdout, stderr


def get_tun_peer_address(interface_name):
"""Gets the peer address of a tun interface.
Args:
interface_name: The name of the tun interface (e.g., "tun0").
Returns:
The peer IP address as a string, or None if an error occurs. Returns None if the interface does not exist, or does not have a peer.
"""
try:
result = subprocess.run(["ip", "addr", "show", interface_name], capture_output=True, text=True, check=True)
output = result.stdout

for line in output.splitlines():
if "peer" in line:
parts = line.split()
if len(parts) > 1: # Check if there's a second element to avoid IndexError
peer_with_cidr = parts[1]
try:
# Handle CIDR notation using ipaddress library
peer_ip = ipaddress.ip_interface(peer_with_cidr).ip.exploded
return peer_ip
except ValueError: # Handle invalid CIDR notations
try:
peer_ip = peer_with_cidr.split("/")[0] # Try just splitting by /
return peer_ip
except IndexError:
return None # Invalid format - give up.
else:
return None # No peer address found on the line.
return None # "peer" not found in the output

except subprocess.CalledProcessError as e:
if e.returncode == 1: # Interface not found
return None
else:
print(f"Error executing ip command: {e}")
return None
except FileNotFoundError:
print("ip command not found. Is iproute2 installed?")
return None


def enable_ip_forwarding(sysctl="/usr/sbin/sysctl"):
log.debug("Enabling IPv4 forwarding")
run(sysctl, "-w" "net.ipv4.ip_forward=1")
Expand Down Expand Up @@ -641,6 +685,50 @@ def inetsim_disable(ipaddr, inetsim_ip, dns_port, resultserver_port, ports):
run_iptables("-D", "OUTPUT", "--source", ipaddr, "-j", "DROP")


def interface_route_tun_enable(ipaddr: str, out_interface: str, task_id: str):
"""Enable routing and NAT via tun output_interface."""
log.info(f"Enabling interface routing via: {out_interface} for task: {task_id}")

Check failure on line 690 in utils/rooter.py

View workflow job for this annotation

GitHub Actions / test (3.10)

Ruff (G004)

utils/rooter.py:690:14: G004 Logging statement uses f-string

Check failure on line 690 in utils/rooter.py

View workflow job for this annotation

GitHub Actions / test (3.10)

Ruff (G004)

utils/rooter.py:690:14: G004 Logging statement uses f-string

# mark packets from analysis VM
run_iptables("-t", "mangle", "-I", "PREROUTING", "--source", ipaddr, "-j", "MARK", "--set-mark", task_id)

run_iptables("-t", "nat", "-I", "POSTROUTING", "--source", ipaddr, "-o", out_interface, "-j", "MASQUERADE")
# ACCEPT forward
run_iptables("-t", "filter", "-I", "FORWARD", "--source", ipaddr, "-o", out_interface, "-j", "ACCEPT")

# in routing table add route table task_id
run(s.ip, "rule", "add", "fwmark", task_id, "lookup", task_id)

peer_ip = get_tun_peer_address(out_interface)
if peer_ip:
log.info(f"interface_route_enable {out_interface} has peer {peer_ip}")

Check failure on line 704 in utils/rooter.py

View workflow job for this annotation

GitHub Actions / test (3.10)

Ruff (G004)

utils/rooter.py:704:18: G004 Logging statement uses f-string

Check failure on line 704 in utils/rooter.py

View workflow job for this annotation

GitHub Actions / test (3.10)

Ruff (G004)

utils/rooter.py:704:18: G004 Logging statement uses f-string
run(s.ip, "route", "add", "default", "via", peer_ip, "table", task_id)
else:
log.error("interface_route_enable missing peer IP ")


def interface_route_tun_disable(ipaddr: str, out_interface: str, task_id: str):
"""Disable routing and NAT via tun output_interface."""
log.info(f"Disable interface routing via: {out_interface} for task: {task_id}")

Check failure on line 712 in utils/rooter.py

View workflow job for this annotation

GitHub Actions / test (3.10)

Ruff (G004)

utils/rooter.py:712:14: G004 Logging statement uses f-string

Check failure on line 712 in utils/rooter.py

View workflow job for this annotation

GitHub Actions / test (3.10)

Ruff (G004)

utils/rooter.py:712:14: G004 Logging statement uses f-string

# mark packets from analysis VM
run_iptables("-t", "mangle", "-D", "PREROUTING", "--source", ipaddr, "-j", "MARK", "--set-mark", task_id)

run_iptables("-t", "nat", "-D", "POSTROUTING", "--source", ipaddr, "-o", out_interface, "-j", "MASQUERADE")
# ACCEPT forward
run_iptables("-t", "filter", "-D", "FORWARD", "--source", ipaddr, "-o", out_interface, "-j", "ACCEPT")

# in routing table add route table task_id
run(s.ip, "rule", "del", "fwmark", task_id, "lookup", task_id)

peer_ip = get_tun_peer_address(out_interface)
if peer_ip:
log.info(f"interface_route_disable {out_interface} has peer {peer_ip}")

Check failure on line 726 in utils/rooter.py

View workflow job for this annotation

GitHub Actions / test (3.10)

Ruff (G004)

utils/rooter.py:726:18: G004 Logging statement uses f-string

Check failure on line 726 in utils/rooter.py

View workflow job for this annotation

GitHub Actions / test (3.10)

Ruff (G004)

utils/rooter.py:726:18: G004 Logging statement uses f-string
run(s.ip, "route", "del", "default", "via", peer_ip, "table", task_id)
else:
log.error("interface_route_disable missing peer IP ")


def socks5_enable(ipaddr, resultserver_port, dns_port, proxy_port):
"""Enable hijacking of all traffic and send it to socks5."""
log.info("Enabling socks route.")
Expand Down Expand Up @@ -750,6 +838,8 @@ def drop_disable(ipaddr, resultserver_port):
"srcroute_disable": srcroute_disable,
"inetsim_enable": inetsim_enable,
"inetsim_disable": inetsim_disable,
"interface_route_tun_enable": interface_route_tun_enable,
"interface_route_tun_disable": interface_route_tun_disable,
"socks5_enable": socks5_enable,
"socks5_disable": socks5_disable,
"drop_enable": drop_enable,
Expand Down
3 changes: 1 addition & 2 deletions web/submission/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,8 +422,7 @@ def index(request, task_id=None, resubmit_hash=None):
if task_category in ("url", "dlnexec"):
if not samples:
return render(request, "error.html", {"error": "You specified an invalid URL!"})

for url in samples.split(","):
for url in samples.split(web_conf.general.url_splitter):
url = url.replace("hxxps://", "https://").replace("hxxp://", "http://").replace("[.]", ".")
if task_category == "dlnexec":
path, content, sha256 = process_new_dlnexec_task(url, route, options, custom)
Expand Down
3 changes: 2 additions & 1 deletion web/web/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@
# "allauth.socialaccount.providers.google",
# "allauth.socialaccount.providers.microsoft",
"crispy_forms",
"crispy_bootstrap4",
"django_recaptcha", # https://pypi.org/project/django-recaptcha/
"rest_framework",
"rest_framework.authtoken",
Expand Down Expand Up @@ -340,7 +341,7 @@

ACCOUNT_EMAIL_REQUIRED = web_cfg.registration.get("email_required", False)
ACCOUNT_EMAIL_SUBJECT_PREFIX = web_cfg.registration.get("email_prefix_subject", False)
ACCOUNT_RATE_LIMITS = {"login_failed": 3}
ACCOUNT_RATE_LIMITS = {"login_failed": "3/m"}
LOGIN_REDIRECT_URL = "/"
ACCOUNT_LOGOUT_REDIRECT_URL = "/accounts/login/"
MANUAL_APPROVE = web_cfg.registration.get("manual_approve", False)
Expand Down

0 comments on commit 8db0425

Please sign in to comment.