-
-
Notifications
You must be signed in to change notification settings - Fork 49
Allow to query for passwords in CowrieSession API - Closes #607 #641
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,3 +7,4 @@ __pycache__/ | |
| mlmodels/ | ||
| # JetBrains IDEs (PyCharm, IntelliJ, etc.) | ||
| .idea/ | ||
| .DS_Store | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ | |
| from django.conf import settings | ||
| from django.http import Http404, HttpResponseBadRequest | ||
| from greedybear.consts import GET | ||
| from greedybear.models import IOC, CommandSequence, CowrieSession, Statistics, viewType | ||
| from greedybear.models import CommandSequence, CowrieSession, Statistics, viewType | ||
| from rest_framework import status | ||
| from rest_framework.decorators import api_view, authentication_classes, permission_classes | ||
| from rest_framework.permissions import IsAuthenticated | ||
|
|
@@ -24,13 +24,17 @@ | |
| def cowrie_session_view(request): | ||
| """ | ||
| Retrieve Cowrie honeypot session data including command sequences, credentials, and session details. | ||
| Queries can be performed using either an IP address to find all sessions from that source, | ||
| or a SHA-256 hash to find sessions containing a specific command sequence. | ||
| Queries can be performed using an IP address, SHA-256 hash or password. | ||
|
|
||
| Args: | ||
| request: The HTTP request object containing query parameters | ||
| query (str, required): The search term, can be either an IP address or the SHA-256 hash of a command sequence. | ||
| SHA-256 hashes should match command sequences generated using Python's "\\n".join(sequence) format. | ||
| query (str, required): The search term, can be: | ||
| - An IP address to find all sessions from that | ||
| source | ||
| - A SHA-256 hash of a command sequence | ||
| (generated using Python's "\\n".join(sequence) format) | ||
| - A password string to find all sessions where | ||
| that password was used | ||
| include_similar (bool, optional): When "true", expands the result to include all sessions that executed | ||
| command sequences belonging to the same cluster(s) as command sequences found in the initial query result. | ||
| Requires CLUSTER_COWRIE_COMMAND_SEQUENCES enabled in configuration. Default: false | ||
|
|
@@ -74,19 +78,32 @@ def cowrie_session_view(request): | |
| if not observable: | ||
| return HttpResponseBadRequest("Missing required 'query' parameter") | ||
|
|
||
| if "<" in observable or ">" in observable or observable.count("}") > observable.count("{"): | ||
| return HttpResponseBadRequest("Invalid query parameter: contains invalid characters") | ||
| looks_like_ip = "." in observable or ":" in observable or "/" in observable | ||
regulartim marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if looks_like_ip and not is_ip_address(observable): | ||
| return HttpResponseBadRequest(f"Invalid IP address format: {observable}") | ||
|
|
||
| if len(observable) == 64 and not is_sha256hash(observable): | ||
| return HttpResponseBadRequest("Invalid hash format: must be 64 hexadecimal characters") | ||
|
Comment on lines
+87
to
+88
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, so I is not possible to query passwords of length 64, right? |
||
|
|
||
| if is_ip_address(observable): | ||
| sessions = CowrieSession.objects.filter(source__name=observable, duration__gt=0).prefetch_related("source", "commands") | ||
| if not sessions.exists(): | ||
| raise Http404(f"No information found for IP: {observable}") | ||
|
|
||
| elif is_sha256hash(observable): | ||
| elif len(observable) == 64 and is_sha256hash(observable): | ||
| try: | ||
| commands = CommandSequence.objects.get(commands_hash=observable.lower()) | ||
| except CommandSequence.DoesNotExist as exc: | ||
| raise Http404(f"No command sequences found with hash: {observable}") from exc | ||
| sessions = CowrieSession.objects.filter(commands=commands, duration__gt=0).prefetch_related("source", "commands") | ||
| else: | ||
| return HttpResponseBadRequest("Query must be a valid IP address or SHA-256 hash") | ||
| password_pattern = f" | {observable}" | ||
| sessions = CowrieSession.objects.filter(duration__gt=0, credentials__icontains=password_pattern).prefetch_related("source", "commands") | ||
regulartim marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if not sessions.exists(): | ||
| raise Http404(f"No sessions found with password: {observable}") | ||
|
|
||
| if include_similar: | ||
| commands = set(s.commands for s in sessions if s.commands) | ||
|
|
||
IshaanXCoder marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| from datetime import datetime | ||
|
|
||
| from django.contrib.postgres import fields as pg_fields | ||
| from django.contrib.postgres.indexes import GinIndex | ||
| from django.db import models | ||
|
|
||
|
|
||
|
|
@@ -67,7 +68,12 @@ def __str__(self): | |
| class CommandSequence(models.Model): | ||
| first_seen = models.DateTimeField(blank=False, default=datetime.now) | ||
| last_seen = models.DateTimeField(blank=False, default=datetime.now) | ||
| commands = pg_fields.ArrayField(models.CharField(max_length=1024, blank=True), blank=False, null=False, default=list) | ||
| commands = pg_fields.ArrayField( | ||
| models.CharField(max_length=1024, blank=True), | ||
| blank=False, | ||
| null=False, | ||
| default=list, | ||
| ) | ||
| commands_hash = models.CharField(max_length=64, unique=True, blank=True, null=True) | ||
| cluster = models.IntegerField(blank=True, null=True) | ||
|
|
||
|
|
@@ -81,7 +87,12 @@ class CowrieSession(models.Model): | |
| start_time = models.DateTimeField(blank=True, null=True) | ||
| duration = models.FloatField(blank=True, null=True) | ||
| login_attempt = models.BooleanField(blank=False, null=False, default=False) | ||
| credentials = pg_fields.ArrayField(models.CharField(max_length=256, blank=True), blank=False, null=False, default=list) | ||
| credentials = pg_fields.ArrayField( | ||
| models.CharField(max_length=256, blank=True), | ||
| blank=False, | ||
| null=False, | ||
| default=list, | ||
| ) | ||
| command_execution = models.BooleanField(blank=False, null=False, default=False) | ||
| interaction_count = models.IntegerField(blank=False, null=False, default=0) | ||
| source = models.ForeignKey(IOC, on_delete=models.CASCADE, blank=False, null=False) | ||
|
|
@@ -90,6 +101,7 @@ class CowrieSession(models.Model): | |
| class Meta: | ||
| indexes = [ | ||
| models.Index(fields=["source"]), | ||
| GinIndex(fields=["credentials"], name="greedybear_credentials_gin_idx"), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure that this index helps in our specific use case? I looked it up and that's really advanced database stuff! At least for me it is hard to understand what this type of index actually does. |
||
| ] | ||
|
|
||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.