|
| 1 | +# This file is a part of GreedyBear https://github.com/honeynet/GreedyBear |
| 2 | +# See the file 'LICENSE' for copying permission. |
| 3 | +import itertools |
| 4 | +import logging |
| 5 | +import socket |
| 6 | + |
| 7 | +from api.views.utils import is_ip_address, is_sha256hash |
| 8 | +from certego_saas.apps.auth.backend import CookieTokenAuthentication |
| 9 | +from django.http import Http404, HttpResponseBadRequest |
| 10 | +from greedybear.consts import FEEDS_LICENSE, GET |
| 11 | +from greedybear.models import IOC, CommandSequence, CowrieSession, Statistics, viewType |
| 12 | +from rest_framework import status |
| 13 | +from rest_framework.decorators import api_view, authentication_classes, permission_classes |
| 14 | +from rest_framework.permissions import IsAuthenticated |
| 15 | +from rest_framework.response import Response |
| 16 | + |
| 17 | +logger = logging.getLogger(__name__) |
| 18 | + |
| 19 | + |
| 20 | +@api_view([GET]) |
| 21 | +@authentication_classes([CookieTokenAuthentication]) |
| 22 | +@permission_classes([IsAuthenticated]) |
| 23 | +def cowrie_session_view(request): |
| 24 | + """ |
| 25 | + Retrieve Cowrie honeypot session data including command sequences, credentials, and session details. |
| 26 | + Queries can be performed using either an IP address to find all sessions from that source, |
| 27 | + or a SHA-256 hash to find sessions containing a specific command sequence. |
| 28 | +
|
| 29 | + Args: |
| 30 | + request: The HTTP request object containing query parameters |
| 31 | + query (str, required): The search term, can be either an IP address or the SHA-256 hash of a command sequence. |
| 32 | + SHA-256 hashes should match command sequences generated using Python's "\\n".join(sequence) format. |
| 33 | + include_similar (bool, optional): When "true", expands the result to include all sessions that executed |
| 34 | + command sequences belonging to the same cluster(s) as command sequences found in the initial query result. |
| 35 | + Requires CLUSTER_COWRIE_COMMAND_SEQUENCES enabled in configuration. Default: false |
| 36 | + include_credentials (bool, optional): When "true", includes all credentials used across matching Cowrie sessions. |
| 37 | + Default: false |
| 38 | + include_session_data (bool, optional): When "true", includes detailed information about matching Cowrie sessions. |
| 39 | + Default: false |
| 40 | +
|
| 41 | + Returns: |
| 42 | + Response (200): JSON object containing: |
| 43 | + - query (str): The original query parameter |
| 44 | + - commands (list[str]): Unique command sequences (newline-delimited strings) |
| 45 | + - sources (list[str]): Unique source IP addresses |
| 46 | + - credentials (list[str], optional): Unique credentials if include_credentials=true |
| 47 | + - sessions (list[dict], optional): Session details if include_session_data=true |
| 48 | + - time (datetime): Session start time |
| 49 | + - duration (float): Session duration in seconds |
| 50 | + - source (str): Source IP address |
| 51 | + - interactions (int): Number of interactions in session |
| 52 | + - credentials (list[str]): Credentials used in this session |
| 53 | + - commands (str): Command sequence executed (newline-delimited) |
| 54 | + Response (400): Bad Request - Missing or invalid query parameter |
| 55 | + Response (404): Not Found - No matching sessions found |
| 56 | + Response (500): Internal Server Error - Unexpected error occurred |
| 57 | +
|
| 58 | + Example Queries: |
| 59 | + /api/cowrie_session?query=1.2.3.4 |
| 60 | + /api/cowrie_session?query=5120e94e366ec83a79ee80454e4d1c76c06499ab19032bcdc7f0b4523bdb37a6 |
| 61 | + /api/cowrie_session?query=1.2.3.4&include_credentials=true&include_session_data=true&include_similar=true |
| 62 | + """ |
| 63 | + observable = request.query_params.get("query") |
| 64 | + include_similar = request.query_params.get("include_similar", "false").lower() == "true" |
| 65 | + include_credentials = request.query_params.get("include_credentials", "false").lower() == "true" |
| 66 | + include_session_data = request.query_params.get("include_session_data", "false").lower() == "true" |
| 67 | + |
| 68 | + logger.info(f"Cowrie view requested by {request.user} for {observable}") |
| 69 | + source_ip = str(request.META["REMOTE_ADDR"]) |
| 70 | + request_source = Statistics(source=source_ip, view=viewType.COWRIE_SESSION_VIEW.value) |
| 71 | + request_source.save() |
| 72 | + |
| 73 | + if not observable: |
| 74 | + return HttpResponseBadRequest("Missing required 'query' parameter") |
| 75 | + |
| 76 | + if is_ip_address(observable): |
| 77 | + sessions = CowrieSession.objects.filter(source__name=observable, duration__gt=0).prefetch_related("source", "commands") |
| 78 | + if not sessions.exists(): |
| 79 | + raise Http404(f"No information found for IP: {observable}") |
| 80 | + |
| 81 | + elif is_sha256hash(observable): |
| 82 | + try: |
| 83 | + commands = CommandSequence.objects.get(commands_hash=observable.lower()) |
| 84 | + except CommandSequence.DoesNotExist as exc: |
| 85 | + raise Http404(f"No command sequences found with hash: {observable}") from exc |
| 86 | + sessions = CowrieSession.objects.filter(commands=commands, duration__gt=0).prefetch_related("source", "commands") |
| 87 | + else: |
| 88 | + return HttpResponseBadRequest("Query must be a valid IP address or SHA-256 hash") |
| 89 | + |
| 90 | + if include_similar: |
| 91 | + commands = set(s.commands for s in sessions if s.commands) |
| 92 | + clusters = set(cmd.cluster for cmd in commands if cmd.cluster is not None) |
| 93 | + related_sessions = CowrieSession.objects.filter(commands__cluster__in=clusters).prefetch_related("source", "commands") |
| 94 | + sessions = sessions.union(related_sessions) |
| 95 | + |
| 96 | + response_data = { |
| 97 | + "license": FEEDS_LICENSE, |
| 98 | + "query": observable, |
| 99 | + } |
| 100 | + |
| 101 | + unique_commands = set(s.commands for s in sessions if s.commands) |
| 102 | + response_data["commands"] = sorted("\n".join(cmd.commands) for cmd in unique_commands) |
| 103 | + response_data["sources"] = sorted(set(s.source.name for s in sessions), key=socket.inet_aton) |
| 104 | + if include_credentials: |
| 105 | + response_data["credentials"] = sorted(set(itertools.chain(*[s.credentials for s in sessions]))) |
| 106 | + if include_session_data: |
| 107 | + response_data["sessions"] = [ |
| 108 | + { |
| 109 | + "time": s.start_time, |
| 110 | + "duration": s.duration, |
| 111 | + "source": s.source.name, |
| 112 | + "interactions": s.interaction_count, |
| 113 | + "credentials": s.credentials if s.credentials else [], |
| 114 | + "commands": "\n".join(s.commands.commands) if s.commands else "", |
| 115 | + } |
| 116 | + for s in sessions |
| 117 | + ] |
| 118 | + |
| 119 | + return Response(response_data, status=status.HTTP_200_OK) |
0 commit comments