Skip to content

Commit 63870a1

Browse files
authored
Merge pull request GreedyBear-Project#603 from intelowlproject/develop
2.1.0
2 parents b7e0c80 + 0de05ea commit 63870a1

13 files changed

Lines changed: 362 additions & 15 deletions

File tree

.env_template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ COMPOSE_FILE=docker/default.yml:docker/local.override.yml
1313
#COMPOSE_FILE=docker/default.yml:docker/local.override.yml:docker/elasticsearch.yml
1414

1515
# If you want to run a specific version, populate this
16-
# REACT_APP_INTELOWL_VERSION="2.0.1"
16+
# REACT_APP_INTELOWL_VERSION="2.1.0"

.github/workflows/pull_request_automation.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,6 @@ jobs:
100100
"BROKER_URL": "amqp://guest:guest@rabbitmq:5672",
101101
}
102102
python_versions: >-
103-
["3.10"]
103+
["3.13"]
104104
max_timeout: 15
105-
ubuntu_version: 22.04
105+
ubuntu_version: 24.04

api/urls.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
# This file is a part of GreedyBear https://github.com/honeynet/GreedyBear
22
# See the file 'LICENSE' for copying permission.
3-
from api.views import StatisticsViewSet, command_sequence_view, enrichment_view, feeds, feeds_advanced, feeds_pagination, general_honeypot_list
3+
from api.views import (
4+
StatisticsViewSet,
5+
command_sequence_view,
6+
cowrie_session_view,
7+
enrichment_view,
8+
feeds,
9+
feeds_advanced,
10+
feeds_pagination,
11+
general_honeypot_list,
12+
)
413
from django.urls import include, path
514
from rest_framework import routers
615

@@ -14,6 +23,7 @@
1423
path("feeds/advanced/", feeds_advanced),
1524
path("feeds/<str:feed_type>/<str:attack_type>/<str:prioritize>.<str:format_>", feeds),
1625
path("enrichment", enrichment_view),
26+
path("cowrie_session", cowrie_session_view),
1727
path("command_sequence", command_sequence_view),
1828
path("general_honeypot", general_honeypot_list),
1929
# router viewsets

api/views/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from api.views.command_sequence import *
2+
from api.views.cowrie_session import *
23
from api.views.enrichment import *
34
from api.views.feeds import *
45
from api.views.general_honeypot import *

api/views/cowrie_session.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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)

docker/.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
REACT_APP_GREEDYBEAR_VERSION="2.0.1"
1+
REACT_APP_GREEDYBEAR_VERSION="2.1.0"

docker/Dockerfile_nginx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM library/nginx:1.29.1-alpine
1+
FROM library/nginx:1.29.3-alpine
22
RUN mkdir -p /var/cache/nginx /var/cache/nginx/feeds
33
RUN apk update && apk upgrade && apk add bash
44
ENV NGINX_LOG_DIR=/var/log/nginx

greedybear/cronjobs/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from datetime import datetime, timedelta
66

77
from django.conf import settings
8-
from elasticsearch_dsl import Q, Search
8+
from elasticsearch8.dsl import Q, Search
99
from greedybear.settings import EXTRACTION_INTERVAL, LEGACY_EXTRACTION
1010

1111

greedybear/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class viewType(models.TextChoices):
1010
FEEDS_VIEW = "feeds"
1111
ENRICHMENT_VIEW = "enrichment"
1212
COMMAND_SEQUENCE_VIEW = "command sequence"
13+
COWRIE_SESSION_VIEW = "cowrie session"
1314

1415

1516
class iocType(models.TextChoices):

greedybear/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from datetime import timedelta
77

88
from django.core.management.utils import get_random_secret_key
9-
from elasticsearch import Elasticsearch
9+
from elasticsearch8 import Elasticsearch
1010

1111
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
1212
BASE_STATIC_PATH = os.path.join(BASE_DIR, "static/")

0 commit comments

Comments
 (0)