logging attckers try and add location to admin

This commit is contained in:
Parsa Nazer
2025-02-21 01:08:46 +03:30
parent 7679e82fd3
commit c7524e9e63
12 changed files with 395 additions and 16 deletions
+40 -1
View File
@@ -10,6 +10,12 @@ from django.contrib.auth.models import Group
from unfold.forms import AdminPasswordChangeForm from unfold.forms import AdminPasswordChangeForm
from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm
from utils.admin import ModelAdmin from utils.admin import ModelAdmin
from django.template.loader import render_to_string
from folium import Map, Marker
from unfold.decorators import action, display
from django.utils.html import format_html
class UserAddressInLine(TabularInline): class UserAddressInLine(TabularInline):
model = UserAddressModel model = UserAddressModel
extra = 0 extra = 0
@@ -95,4 +101,37 @@ class PushSubscription(ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm import_form_class = ImportForm
export_form_class = ExportForm export_form_class = ExportForm
compressed_fields = True compressed_fields = True
warn_unsaved_form = True warn_unsaved_form = True
@admin.register(SecurityBreachAttemptModel)
class SecurityBreachAttemptAdmin(ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm
export_form_class = ExportForm
compressed_fields = True
warn_unsaved_form = True
change_form_template = 'loction_chagne_form.html'
list_display = ['ip', 'country', 'region_name', 'city', 'zip_code', 'isp', 'created_at', 'trys', 'display_viewd']
def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = extra_context or {}
obj = self.get_object(request, object_id)
if obj and obj.lat and obj.lon:
m = Map(location=[obj.lat, obj.lon], zoom_start=10)
Marker([obj.lat, obj.lon], popup=f"Location: {obj.ip}").add_to(m)
map_html = m._repr_html_()
extra_context['map_html'] = map_html
return super().change_view(request, object_id, form_url, extra_context)
@display(description='دیده شده')
def display_viewd(self, instance):
if instance.viewd:
svg = f'<a href="/accounts/attack/view/{instance.id}"><svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 24 24"><path fill="green" d="M12 16q1.875 0 3.188-1.312T16.5 11.5t-1.312-3.187T12 7T8.813 8.313T7.5 11.5t1.313 3.188T12 16m0-1.8q-1.125 0-1.912-.788T9.3 11.5t.788-1.912T12 8.8t1.913.788t.787 1.912t-.787 1.913T12 14.2m0 4.8q-3.65 0-6.65-2.037T1 11.5q1.35-3.425 4.35-5.462T12 4t6.65 2.038T23 11.5q-1.35 3.425-4.35 5.463T12 19m0-2q2.825 0 5.188-1.487T20.8 11.5q-1.25-2.525-3.613-4.012T12 6T6.813 7.488T3.2 11.5q1.25 2.525 3.613 4.013T12 17"/></svg></a>'
else:
svg = f'<a href="/accounts/attack/view/{instance.id}"><svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 24 24"><path fill="#c30009" d="m19.1 21.9l-3.5-3.45q-.875.275-1.775.413T12 19q-3.35 0-6.125-1.8t-4.35-4.75q-.125-.225-.187-.462t-.063-.488t.063-.488t.187-.462q.55-.975 1.175-1.9T4.15 7L2.075 4.9Q1.8 4.625 1.8 4.213t.3-.713q.275-.275.7-.275t.7.275l17 17q.275.275.288.688t-.288.712q-.275.275-.7.275t-.7-.275M12 16q.275 0 .525-.025t.5-.1l-5.4-5.4q-.075.25-.1.5T7.5 11.5q0 1.875 1.313 3.188T12 16m0-12q3.35 0 6.138 1.813t4.362 4.762q.125.2.188.438t.062.487t-.05.488t-.175.437q-.475.925-1.062 1.75t-1.313 1.55q-.35.35-.825.325t-.825-.375l-2-2q-.175-.175-.225-.413t.025-.487q.1-.325.15-.625t.05-.65q0-1.875-1.312-3.187T12 7q-.35 0-.65.05t-.625.15q-.25.075-.5.025T9.8 7l-.825-.825q-.475-.475-.312-1.1t.787-.8q.625-.125 1.263-.2T12 4m1.975 5.65q.275.325.462.713t.238.812q.025.2-.15.275t-.325-.075l-2.05-2.05Q12 9.175 12.088 9t.287-.175q.475.05.875.263t.725.562"/></svg></a>'
return format_html(
svg
)
@@ -0,0 +1,31 @@
# Generated by Django 5.1.2 on 2025-02-20 20:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0014_alter_pushsubscription_created_at_and_more'),
]
operations = [
migrations.CreateModel(
name='SecurityBreachAttemptModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ip', models.CharField(max_length=40, unique=True, verbose_name='آدرس آی\u200cپی')),
('country', models.CharField(blank=True, max_length=40, null=True, verbose_name='کشور')),
('region_name', models.CharField(blank=True, max_length=40, null=True, verbose_name='منطقه')),
('city', models.CharField(blank=True, max_length=40, null=True, verbose_name='شهر')),
('zip_code', models.CharField(blank=True, max_length=40, null=True, verbose_name='کد پستی')),
('lon', models.CharField(blank=True, max_length=40, null=True, verbose_name='طول جغرافیایی')),
('lat', models.CharField(blank=True, max_length=40, null=True, verbose_name='عرض جغرافیایی')),
('isp', models.CharField(blank=True, max_length=40, null=True, verbose_name='ارائه\u200cدهنده اینترنت (ISP)')),
],
options={
'verbose_name': 'تلاش نفوذ',
'verbose_name_plural': 'تلاش\u200cهای نفوذ',
},
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2025-02-20 21:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0015_securitybreachattemptmodel'),
]
operations = [
migrations.AddField(
model_name='securitybreachattemptmodel',
name='viewd',
field=models.BooleanField(default=False, verbose_name='تماشا شده'),
),
]
@@ -0,0 +1,25 @@
# Generated by Django 5.1.2 on 2025-02-20 21:35
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0016_securitybreachattemptmodel_viewd'),
]
operations = [
migrations.AddField(
model_name='securitybreachattemptmodel',
name='created_at',
field=models.DateField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='securitybreachattemptmodel',
name='trys',
field=models.IntegerField(default=0),
),
]
@@ -0,0 +1,23 @@
# Generated by Django 5.1.2 on 2025-02-20 21:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0017_securitybreachattemptmodel_created_at_and_more'),
]
operations = [
migrations.AlterField(
model_name='securitybreachattemptmodel',
name='created_at',
field=models.DateTimeField(auto_now_add=True, verbose_name='شروع حمله'),
),
migrations.AlterField(
model_name='securitybreachattemptmodel',
name='trys',
field=models.IntegerField(default=0, verbose_name='تعداد تلاش ها'),
),
]
+43 -1
View File
@@ -198,4 +198,46 @@ class PushSubscription(models.Model):
} }
) )
except WebPushException as ex: except WebPushException as ex:
print(f"Failed to send notification to {sub.user}:", ex) print(f"Failed to send notification to {sub.user}:", ex)
def get_location_from_ip(ip_address):
try:
response = requests.get(f"http://ip-api.com/json/{ip_address}")
data = response.json()
if data["status"] == "success":
return data['country'], data['regionName'], data['city'], data.get('zip', 'ناموجود'), data['lat'], data['lon'], data['isp']
else:
print("Error fetching data: ", data["message"])
return None
except Exception as e:
print(f"An error occurred: {e}")
return None
class SecurityBreachAttemptModel(models.Model):
ip = models.CharField(max_length=40, unique=True, verbose_name="آدرس آی‌پی")
country = models.CharField(max_length=40, verbose_name="کشور", blank=True, null=True)
region_name = models.CharField(max_length=40, verbose_name="منطقه", blank=True, null=True)
city = models.CharField(max_length=40, verbose_name="شهر", blank=True, null=True)
zip_code = models.CharField(max_length=40, verbose_name="کد پستی", blank=True, null=True)
lon = models.CharField(max_length=40, verbose_name="طول جغرافیایی", blank=True, null=True)
lat = models.CharField(max_length=40, verbose_name="عرض جغرافیایی", blank=True, null=True)
isp = models.CharField(max_length=40, verbose_name="ارائه‌دهنده اینترنت (ISP)", blank=True, null=True)
viewd = models.BooleanField(default=False, verbose_name='تماشا شده')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='شروع حمله')
trys = models.IntegerField(default=0, verbose_name='تعداد تلاش ها')
def save(self, *args, **kwargs):
if not self.id:
location_data = get_location_from_ip(self.ip)
if location_data:
self.country, self.region_name, self.city, self.zip_code, self.lat, self.lon, self.isp = location_data
super().save(*args, **kwargs)
def __str__(self):
return f'تلاش نفوذ از {self.ip} در {self.city}, {self.country}'
class Meta:
verbose_name = "تلاش نفوذ"
verbose_name_plural = "تلاش‌های نفوذ"
+2 -1
View File
@@ -12,5 +12,6 @@ urlpatterns = [
path('address/delete/<int:pk>', views.DeleteAddressView.as_view(), name='delete-address'), path('address/delete/<int:pk>', views.DeleteAddressView.as_view(), name='delete-address'),
path('address/list', views.GetUserAddressesView.as_view(), name='list-addresses'), path('address/list', views.GetUserAddressesView.as_view(), name='list-addresses'),
path('address/<int:pk>', views.GetIDUserAddressView.as_view(), name='get-ID-address'), path('address/<int:pk>', views.GetIDUserAddressView.as_view(), name='get-ID-address'),
path('subscribe', views.SubscribeView.as_view(), name='subscibe') path('subscribe', views.SubscribeView.as_view(), name='subscibe'),
path('attack/view/<int:pk>', views.ChangeViewAttack.as_view(), name='attack-view'),
] ]
+12 -3
View File
@@ -3,13 +3,14 @@ from rest_framework.views import APIView
from rest_framework import generics, permissions, status from rest_framework import generics, permissions, status
from rest_framework.response import Response from rest_framework.response import Response
from .serializers import * from .serializers import *
from .models import UserAddressModel, User from .models import UserAddressModel, User, SecurityBreachAttemptModel
from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.permissions import IsAuthenticated, AllowAny
from drf_spectacular.utils import extend_schema, OpenApiParameter from drf_spectacular.utils import extend_schema, OpenApiParameter
from rest_framework_simplejwt.views import TokenObtainPairView from rest_framework_simplejwt.views import TokenObtainPairView
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404, redirect
from rest_framework_simplejwt.tokens import RefreshToken from rest_framework_simplejwt.tokens import RefreshToken
import ghasedak_sms import ghasedak_sms
from django.views import View
# this works only need to be used # this works only need to be used
# class APIView(APIView): # class APIView(APIView):
# def __init__(self, *args, **kwargs): # def __init__(self, *args, **kwargs):
@@ -185,4 +186,12 @@ class SubscribeView(APIView):
defaults=(push_ser.validated_data) defaults=(push_ser.validated_data)
) )
return Response(status=status.HTTP_201_CREATED) return Response(status=status.HTTP_201_CREATED)
return Response(status=status.HTTP_400_BAD_REQUEST) return Response(status=status.HTTP_400_BAD_REQUEST)
class ChangeViewAttack(View):
def get(self, request, pk):
attack = get_object_or_404(SecurityBreachAttemptModel, pk=pk)
attack.viewd = not attack.viewd
attack.save()
return redirect('admin:account_securitybreachattemptmodel_changelist')
+6
View File
@@ -209,6 +209,12 @@ UNFOLD = {
"icon": "contact_mail", "icon": "contact_mail",
"link": reverse_lazy("admin:account_useraddressmodel_changelist"), "link": reverse_lazy("admin:account_useraddressmodel_changelist"),
}, },
{
"title": _("تلاش‌های نفوذ"),
"icon": "gpp_maybe",
"link": reverse_lazy("admin:account_securitybreachattemptmodel_changelist"),
"badge": 'utils.admin.new_attck_count'
},
], ],
}, },
+5 -10
View File
@@ -9,7 +9,7 @@ from django.views.generic import RedirectView, TemplateView
from unfold.views import UnfoldModelAdminViewMixin from unfold.views import UnfoldModelAdminViewMixin
from order.models import OrderModel from order.models import OrderModel
from ticket.models import Ticket from ticket.models import Ticket
from account.models import SecurityBreachAttemptModel
import json import json
@@ -267,20 +267,15 @@ class FakeAdminLoginView(View):
return context return context
def get(self, request): def get(self, request):
# Log empty attempt (optional)
ip = request.META.get("REMOTE_ADDR") ip = request.META.get("REMOTE_ADDR")
print(f"Honeypot page accessed from IP: {ip}") hacker, created = SecurityBreachAttemptModel.objects.get_or_create(ip=ip)
return render(request, 'admin/fake_login.html', self.get_context(request)) return render(request, 'admin/fake_login.html', self.get_context(request))
def post(self, request): def post(self, request):
username = request.POST.get("username")
password = request.POST.get("password") # Never actually used
ip = request.META.get("REMOTE_ADDR") ip = request.META.get("REMOTE_ADDR")
hacker, created = SecurityBreachAttemptModel.objects.get_or_create(ip=ip)
print(f"Honeypot triggered! IP: {ip}, Username: {username}") hacker.trys += 1
hacker.save()
messages.error(request, "Please correct the error below.") messages.error(request, "Please correct the error below.")
messages.error(request, "Please enter the correct شماره تماس and password for a staff account. Note that both fields may be case-sensitive.") messages.error(request, "Please enter the correct شماره تماس and password for a staff account. Note that both fields may be case-sensitive.")
# Redirect back to fake login page with context
return render(request, 'admin/fake_login.html', self.get_context(request)) return render(request, 'admin/fake_login.html', self.get_context(request))
+187
View File
@@ -0,0 +1,187 @@
{% extends "admin/base_site.html" %}
{% load unfold %}
{% load i18n admin_urls static admin_modify %}
{% block extrahead %}{{ block.super }}
<script src="{% url 'admin:jsi18n' %}"></script>
{{ media }}
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
{% endblock %}
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-form{% endblock %}
{% if not is_popup %}
{% block breadcrumbs %}
<div class="px-4 lg:px-8">
<div class="container mb-6 mx-auto -my-3 lg:mb-12">
<ul class="flex flex-wrap">
{% url 'admin:index' as link %}
{% trans 'Home' as name %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=name %}
{% url 'admin:app_list' app_label=opts.app_label as link %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=opts.app_config.verbose_name %}
{% if has_view_permission %}
{% url opts|admin_urlname:'changelist' as link %}
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=opts.verbose_name_plural|capfirst %}
{% else %}
{% include 'unfold/helpers/breadcrumb_item.html' with link='' name=opts.verbose_name_plural|capfirst %}
{% endif %}
{% if add %}
{% blocktranslate trimmed with name=opts.verbose_name asvar breadcrumb_name %}
Add {{ name }}
{% endblocktranslate %}
{% include 'unfold/helpers/breadcrumb_item.html' with link='' name=breadcrumb_name %}
{% else %}
{% include 'unfold/helpers/breadcrumb_item.html' with link='' name=original|truncatewords:'18' %}
{% endif %}
</ul>
</div>
</div>
{% endblock %}
{% endif %}
{% block nav-global-side %}
{% if has_add_permission %}
{% include "unfold/helpers/add_link.html" %}
{% endif %}
{% endblock %}
{% block content %}
<div id="content-main" class="flex flex-col gap-4">
{% block form_before %}{% endblock %}
{% if adminform.model_admin.change_form_outer_before_template %}
{% include adminform.model_admin.change_form_outer_before_template %}
{% endif %}
<div class="border border-base-200 border-dashed mb-4 p-3 rounded dark:border-base-700 gap-1.5">
{% if original.lon and original.lat %}
<div class="location-details" style="margin-bottom: 0rem; padding: 0.5rem 1rem; background-color: rgba(var(--color-primary-100), 0.3); border-radius: var(--border-radius,6px);">
<div style="display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; align-items: center; font-size: 0.9em; color: rgb(var(--color-primary-700));" dir="rtl">
{% if original.country %}
<span class="badge" style="padding: 0.25rem 0.75rem; background-color: rgb(var(--color-primary-500)); color: white; border-radius: 20px;">
🇺🇳 {{ original.country }}
</span>
{% endif %}
{% if original.region_name %}
<span style="display: inline-flex; align-items: center;">
<i class="fas fa-mountain mr-1"></i>
{{ original.region_name }}
</span>
{% endif %}
{% if original.city %}
<span style="display: inline-flex; align-items: center;">
<i class="fas fa-city mr-1"></i>
{{ original.city }}
</span>
{% endif %}
{% if original.zip_code %}
<span style="display: inline-flex; align-items: center;">
<i class="fas fa-mail-bulk mr-1"></i>
{{ original.zip_code }}
</span>
{% endif %}
{% if original.isp %}
<span style="display: inline-flex; align-items: center;">
<i class="fas fa-network-wired mr-1"></i>
{{ original.isp }}
</span>
{% endif %}
{% if original.ip %}
<span style="display: inline-flex; align-items: center;">
<i class="fas fa-network-wired mr-1"></i>
{{ original.ip }}
</span>
{% endif %}
</div>
</div>
{% if map_html %}
<!-- Render the Folium map with a fixed height -->
<div style="display: flex;justify-content: center;align-items: center;width: 100%;padding: 25px 0px;">
<div style="border-radius: var(--border-radius,6px);width: 100%;max-width: 800px;">
{{ map_html|safe }}
</div>
</div>
{% else %}
<p>نقشه در دسترس نیست</p>
{% endif %}
{% else %}
<p>موقعیت جغرافیایی موجود نیست</p>
{% endif %}
</div>
<!--
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}{% if form_url %}action="{{ form_url }}" {% endif %}method="post" id="{{ opts.model_name }}_form" {% if adminform.model_admin.warn_unsaved_form %}class="warn-unsaved-form"{% endif %} novalidate>
{% csrf_token %}
{% if adminform.model_admin.change_form_before_template %}
{% include adminform.model_admin.change_form_before_template %}
{% endif %}
{% block form_top %}{% endblock %}
<div>
{% if is_popup %}
<input type="hidden" name="{{ is_popup_var }}" value="1">
{% endif %}
{% if to_field %}
<input type="hidden" name="{{ to_field_var }}" value="{{ to_field }}">
{% endif %}
{% include "unfold/helpers/messages/errornote.html" with errors=errors %}
{% include "unfold/helpers/messages/error.html" with errors=adminform.form.non_field_errors %}
{% block field_sets %}
{% for fieldset in adminform %}
{% if "tab" not in fieldset.classes %}
{% include 'admin/includes/fieldset.html' %}
{% endif %}
{% endfor %}
{% include "unfold/helpers/fieldsets_tabs.html" %}
{% endblock %}
{% block after_field_sets %}{% endblock %}
{% block inline_field_sets %}
{% for inline_admin_formset in inline_admin_formsets %}
{% include inline_admin_formset.opts.template %}
{% endfor %}
{% endblock %}
{% block after_related_objects %}{% endblock %}
{% if adminform.model_admin.change_form_after_template %}
{% include adminform.model_admin.change_form_after_template %}
{% endif %}
{% block submit_buttons_bottom %}{% submit_row %}{% endblock %}
{% block admin_change_form_document_ready %}
<script id="django-admin-form-add-constants" src="{% static 'admin/js/change_form.js' %}"{% if adminform and add %} data-model-name="{{ opts.model_name }}"{% endif %} async></script>
{% endblock %}
{% prepopulated_fields_js %}
</div>
</form> -->
{% if adminform.model_admin.change_form_outer_after_template %}
{% include adminform.model_admin.change_form_outer_after_template %}
{% endif %}
{% block form_after %}{% endblock %}
</div>
{% endblock %}
{% block extrajs %}
<!-- Add Leaflet JS for Folium -->
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
{% endblock %}
+3
View File
@@ -2,6 +2,7 @@ from order.models import OrderModel
from product.models import DollorModel, CommentModel from product.models import DollorModel, CommentModel
from ticket.models import Ticket from ticket.models import Ticket
from home.models import LearnVideoModel from home.models import LearnVideoModel
from account.models import SecurityBreachAttemptModel
def admin_pending_count(request): def admin_pending_count(request):
pending_count = OrderModel.objects.filter(status='ADMIN_PENDING').count() pending_count = OrderModel.objects.filter(status='ADMIN_PENDING').count()
@@ -20,6 +21,8 @@ def new_ticket_count(request):
def new_learn_video_count(request): def new_learn_video_count(request):
return LearnVideoModel.objects.filter(viewd=False).count() return LearnVideoModel.objects.filter(viewd=False).count()
def new_attck_count(request):
return SecurityBreachAttemptModel.objects.filter(viewd=False).count()
from django.contrib import admin, messages from django.contrib import admin, messages
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin