add source
@@ -6,4 +6,5 @@
|
||||
__pycache__/
|
||||
venv_py
|
||||
dnslog/settings_dev.py
|
||||
logs/dnslog_debug.log
|
||||
logs/dnslog_debug.log
|
||||
logview/migrations/
|
||||
@@ -0,0 +1,133 @@
|
||||
# coding:utf-8
|
||||
|
||||
import os
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = '#ma=s-l!2obwj%h-6uu^sbw+4%i2w79%v3^ill62k3&7tjf5dc'
|
||||
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
# SECURE_SSL_REDIRECT = True
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'logview',
|
||||
)
|
||||
|
||||
MIDDLEWARE = (
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'dnslog.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'dnslog.wsgi.application'
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'DB_NAME',
|
||||
'USER': 'DB_USER',
|
||||
'PASSWORD': 'DB_PASSWORD',
|
||||
'HOST': 'DB_HOST',
|
||||
'PORT': '3306'
|
||||
}
|
||||
}
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
TIME_ZONE = 'Asia/Shanghai'
|
||||
USE_I18N = True
|
||||
USE_L10N = True
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
if DEBUG:
|
||||
STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static'),)
|
||||
else:
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
||||
|
||||
# 用于DNS记录的域名
|
||||
DNS_DOMAIN = 'eyes.sh'
|
||||
|
||||
# 管理后台域名
|
||||
ADMIN_DOMAIN = ['eyes.sh', 'www.eyes.sh']
|
||||
|
||||
# NS域名
|
||||
NS1_DOMAIN = 'eyes_dns1.lijiejie.com'
|
||||
NS2_DOMAIN = 'eyes_dns2.lijiejie.com'
|
||||
|
||||
# 服务器外网地址
|
||||
SERVER_IP = '123.123.123.123'
|
||||
|
||||
if not DEBUG:
|
||||
LOGGING = {}
|
||||
else:
|
||||
LOG_DIR = os.path.join(BASE_DIR, 'logs')
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': True,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': "[%(asctime)s] %(levelname)s [ %(filename)s] [line %(lineno)s] %(message)s",
|
||||
'datefmt': "%d/%b/%Y %H:%M:%S"
|
||||
},
|
||||
'simple': {
|
||||
'format': '%(levelname)s %(message)s'
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'file': {
|
||||
'level': 'DEBUG' if DEBUG else 'INFO',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'filename': os.path.join(LOG_DIR, 'dnslog_debug.log'),
|
||||
'formatter': 'verbose',
|
||||
'maxBytes': 1024 * 1024 * 1024 * 2, # 2GB
|
||||
'backupCount': 5,
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'django': {
|
||||
'handlers': ['file'],
|
||||
'level': 'DEBUG' if DEBUG else 'INFO',
|
||||
'propagate': True,
|
||||
},
|
||||
'django.utils.autoreload': {
|
||||
'level': 'CRITICAL',
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
# coding=utf-8
|
||||
|
||||
from django.urls import path, re_path
|
||||
from django.contrib import admin
|
||||
from logview import views
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r'^admin/login', views.index),
|
||||
re_path(r'^admin/', admin.site.urls),
|
||||
re_path(r'^login', views.do_login, name='login'),
|
||||
re_path(r'^random_id_login', views.random_id_login, name='random_id_login'),
|
||||
re_path(r'^register', views.register, name='register'),
|
||||
re_path(r'^logout/', views.do_logout, name='logout'),
|
||||
re_path(r'^api/group/dns/(.+?)/(.+?)/$', views.group_api, name='group_api'),
|
||||
re_path(r'^api/(.+?)/(.+?)/(.+?)/$', views.api, name='api'),
|
||||
re_path(r'^dns/$', views.dns_view, name='dns_view'),
|
||||
re_path(r'^dns/delete$', views.dns_delete, name='dns_delete'),
|
||||
re_path(r'^web/$', views.web_view, name='web_view'),
|
||||
re_path(r'^web/delete$', views.web_delete, name='web_delete'),
|
||||
re_path(r'^config/$', views.config_view, name='config_view'),
|
||||
re_path(r'^rebind/$', views.rebind_view, name='rebind_view'),
|
||||
re_path(r'^rebind/gen$', views.rebind_gen, name='rebind_gen'),
|
||||
re_path(r'^as_admin/$', views.as_admin, name='as_admin'),
|
||||
re_path(r'^config/update$', views.config_update, name='config_update'),
|
||||
re_path(r'^.*$', views.index, name='index'),
|
||||
]
|
||||
@@ -0,0 +1,7 @@
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dnslog.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
@@ -0,0 +1,18 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name eyes.sh;
|
||||
uwsgi_pass_request_headers on;
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
location /static {
|
||||
alias /dnslog/static;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -0,0 +1,89 @@
|
||||
from django.db import models
|
||||
from django.contrib import admin
|
||||
|
||||
|
||||
class User(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
username = models.CharField(max_length=128, db_index=True)
|
||||
email = models.CharField(max_length=128, db_index=True)
|
||||
password = models.CharField(max_length=128, db_index=True)
|
||||
user_domain = models.CharField(max_length=128, db_index=True)
|
||||
token = models.CharField(max_length=32, db_index=True)
|
||||
is_admin = models.BooleanField(default=0)
|
||||
try_login_counter = models.IntegerField(default=0)
|
||||
last_try_login_time = models.DateTimeField(auto_now=True)
|
||||
login_ip = models.GenericIPAddressField(db_index=True, default='0.0.0.0')
|
||||
is_random_user = models.BooleanField(default=0, db_index=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.username
|
||||
|
||||
|
||||
class UserAdmin(admin.ModelAdmin):
|
||||
list_display = ('username', 'password', 'user_domain')
|
||||
|
||||
|
||||
admin.site.register(User, UserAdmin)
|
||||
|
||||
|
||||
class WebLog(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
path = models.CharField(max_length=256, db_index=True)
|
||||
remote_addr = models.GenericIPAddressField('remote_addr', db_index=True)
|
||||
city = models.CharField(max_length=64, null=True, db_index=True)
|
||||
user_agent = models.CharField(max_length=256, null=True, db_index=True)
|
||||
created_time = models.DateTimeField(auto_now_add=True)
|
||||
headers = models.TextField()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.remote_addr
|
||||
|
||||
class Meta:
|
||||
ordering = ['-id']
|
||||
|
||||
|
||||
class WebLogAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'path', 'remote_addr', 'user_agent', 'created_time')
|
||||
|
||||
|
||||
admin.site.register(WebLog, WebLogAdmin)
|
||||
|
||||
|
||||
class DNSLog(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
host = models.CharField(max_length=255, db_index=True, null=False)
|
||||
type = models.CharField(max_length=8, db_index=True, null=False)
|
||||
created_time = models.DateTimeField(auto_now_add=True)
|
||||
ip = models.GenericIPAddressField(null=False, db_index=True)
|
||||
city = models.CharField(max_length=255, null=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.host
|
||||
|
||||
class Meta:
|
||||
ordering = ['-id']
|
||||
|
||||
|
||||
class DNSLogAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'host', 'type', 'created_time')
|
||||
|
||||
|
||||
admin.site.register(DNSLog, DNSLogAdmin)
|
||||
|
||||
|
||||
class Config(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=128, db_index=True)
|
||||
value = models.CharField(max_length=128, db_index=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class ConfigAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'value')
|
||||
|
||||
|
||||
admin.site.register(Config, ConfigAdmin)
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,456 @@
|
||||
# coding=utf-8
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import requests
|
||||
import struct
|
||||
import socket
|
||||
import random
|
||||
import string
|
||||
from django.utils import timezone
|
||||
from django.shortcuts import render, redirect
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.core.paginator import Paginator, InvalidPage, EmptyPage, PageNotAnInteger
|
||||
from django import forms
|
||||
from .models import User, WebLog, DNSLog, Config
|
||||
from django.contrib.auth.models import User as SysUser
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import logout
|
||||
|
||||
|
||||
def get_city_by_ip(ip):
|
||||
try:
|
||||
doc = requests.get('http://ip.ws.126.net/ipquery?ip=%s' % ip).text
|
||||
doc = doc[doc.find('var localAddress=')+17:].strip()
|
||||
doc = doc.replace('city', '"city"').replace('province', '"province"')
|
||||
_doc = json.loads(doc)
|
||||
city = _doc['province'] + ' ' + _doc['city']
|
||||
except Exception as e:
|
||||
city = ''
|
||||
return city
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def index(request):
|
||||
http_host = request.get_host().split(':')[0]
|
||||
if http_host in settings.ADMIN_DOMAIN:
|
||||
return redirect('/login')
|
||||
|
||||
user_agent = request.META.get('HTTP_USER_AGENT') or ''
|
||||
user_agent = user_agent[:250]
|
||||
remote_addr = request.META.get('HTTP_X_REAL_IP') or request.META.get('REMOTE_ADDR')
|
||||
path = http_host + request.get_full_path()
|
||||
path = path[:250]
|
||||
|
||||
if not http_host.endswith(settings.DNS_DOMAIN):
|
||||
return HttpResponse("Server Gone", status=502)
|
||||
|
||||
subdomain = http_host.replace(settings.DNS_DOMAIN, '')
|
||||
if subdomain:
|
||||
items = subdomain.split('.')
|
||||
if len(items) >= 2:
|
||||
user_domain = items[-2]
|
||||
user = User.objects.filter(user_domain=user_domain)
|
||||
if user:
|
||||
city = get_city_by_ip(remote_addr)
|
||||
request_headers = ''
|
||||
for key in request.META:
|
||||
if key.startswith('REQUEST_') or key.startswith('HTTP_') or key.startswith('CONTENT_'):
|
||||
request_headers += key + ': ' + str(request.META[key]) + '\n'
|
||||
|
||||
if request.META.get('REQUEST_METHOD') == 'POST':
|
||||
request_headers += '\n' + request.body.decode()
|
||||
|
||||
weblog = WebLog(user=user[0], path=path, remote_addr=remote_addr,
|
||||
user_agent=user_agent, city=city, headers=request_headers)
|
||||
weblog.save()
|
||||
if path.find('/rpb.png') >= 0: # this is for AWVS Scanner
|
||||
return HttpResponse('39a6ea3246b507782676a6d79812fa1d29e12e9c')
|
||||
return HttpResponse('OK')
|
||||
return HttpResponse('Failed')
|
||||
|
||||
|
||||
class UserForm(forms.Form):
|
||||
username = forms.CharField(label='用户名', max_length=128)
|
||||
password = forms.CharField(label='密码', widget=forms.PasswordInput())
|
||||
|
||||
|
||||
def do_login(request):
|
||||
userid = request.session.get('userid', None)
|
||||
if userid:
|
||||
return HttpResponseRedirect('/dns/')
|
||||
|
||||
context = {'title': '登录', 'enable_register': 'yes', 'enable_create_random_user': 'yes'}
|
||||
c = Config.objects.filter(name='enable_register')
|
||||
if c and 'no' == c[0].value:
|
||||
context['enable_register'] = 'no'
|
||||
c = Config.objects.filter(name='enable_create_random_user')
|
||||
if c and 'no' == c[0].value:
|
||||
context['enable_create_random_user'] = 'no'
|
||||
|
||||
if request.method == 'POST':
|
||||
username = request.POST.get('username', '')
|
||||
password = request.POST.get('password', '')
|
||||
user = User.objects.filter(username=username)
|
||||
if user:
|
||||
user = user[0]
|
||||
if user.try_login_counter > 10 and (timezone.now() - user.last_try_login_time).seconds < 600:
|
||||
user.try_login_counter += 1
|
||||
user.save()
|
||||
context['error_msg'] = '登录失败次数太多,锁定10分钟'
|
||||
return render(request, 'login.html', context)
|
||||
if user.password == hashlib.md5((password + username[:3] + '@dnslog').encode('utf-8')).hexdigest():
|
||||
request.session['userid'] = user.id
|
||||
request.session['username'] = user.username
|
||||
user.try_login_counter = 0
|
||||
user.login_ip = request.META.get('HTTP_X_REAL_IP') or request.META.get('REMOTE_ADDR')
|
||||
user.save()
|
||||
return redirect('/dns/')
|
||||
else:
|
||||
user.try_login_counter += 1
|
||||
user.save()
|
||||
context['error_msg'] = '用户名或密码错误'
|
||||
return render(request, 'login.html', context)
|
||||
|
||||
if User.objects.count() < 1:
|
||||
return redirect('/register')
|
||||
context['error_msg'] = request.GET.get('msg', '')
|
||||
return render(request, 'login.html', context)
|
||||
|
||||
|
||||
def random_id_login(request):
|
||||
c = Config.objects.filter(name='enable_create_random_user')
|
||||
if c and 'no' == c[0].value:
|
||||
return redirect('/login?msg=随机账号功能已关闭')
|
||||
|
||||
user_agent = request.META.get('HTTP_USER_AGENT').lower()
|
||||
if user_agent.find('bot') > 0 or user_agent.find('spider') > 0:
|
||||
return redirect('/login?msg=爬虫禁止登录')
|
||||
|
||||
user_ip = request.META.get('HTTP_X_REAL_IP') or request.META.get('REMOTE_ADDR')
|
||||
if User.objects.filter(login_ip=user_ip).count() > 20:
|
||||
return redirect('/login?msg=随机账号超限,请注册登录')
|
||||
for _ in range(30):
|
||||
username = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
|
||||
if not User.objects.filter(username=username):
|
||||
token = hashlib.md5((username + '@random_token').encode('utf-8')).hexdigest()[:8]
|
||||
user = User.objects.create(username=username, password='NO_LOGIN', email='', user_domain=username,
|
||||
token=token, try_login_counter=0, login_ip=user_ip, is_random_user=1)
|
||||
request.session['userid'] = user.id
|
||||
request.session['username'] = username
|
||||
return redirect('/dns/')
|
||||
return redirect('/login?msg=随机账号创建失败')
|
||||
|
||||
|
||||
def register(request):
|
||||
c = Config.objects.filter(name='enable_register')
|
||||
if c and 'no' == c[0].value:
|
||||
return redirect('/login?msg=注册功能已关闭')
|
||||
username = request.POST.get('username', '')[:100]
|
||||
password = request.POST.get('password', '')[:100]
|
||||
email = request.POST.get('email', '')[:100]
|
||||
context = {'title': '注册', 'reg': 'true', 'error_msg': '',
|
||||
'username': username, 'password': password, 'email': email}
|
||||
if User.objects.count() < 1:
|
||||
context['init'] = 'true'
|
||||
|
||||
if request.method == 'POST':
|
||||
if User.objects.filter(username=username):
|
||||
context['error_msg'] = '无法创建该账号,已被占用'
|
||||
return render(request, 'login.html', context)
|
||||
else:
|
||||
password = hashlib.md5((password + username[:3] + '@dnslog').encode('utf-8')).hexdigest()
|
||||
token = hashlib.md5((password + username[:3] + '@token').encode('utf-8')).hexdigest()[:8]
|
||||
user = User.objects.create(username=username, password=password,
|
||||
email=email, user_domain=username, token=token, try_login_counter=0)
|
||||
if context.get('init', ''):
|
||||
user.is_admin = 1
|
||||
SysUser.objects.create_user(username=username, email=email, password=password,
|
||||
is_staff=1, is_superuser=1)
|
||||
user.save()
|
||||
return redirect('/login?msg=注册成功,请登录以确保密码有效')
|
||||
|
||||
return render(request, 'login.html', context)
|
||||
|
||||
|
||||
def do_logout(request):
|
||||
logout(request)
|
||||
return HttpResponseRedirect('/')
|
||||
|
||||
|
||||
def get_page(p):
|
||||
try:
|
||||
page = int(p)
|
||||
if page < 1:
|
||||
page = 1
|
||||
except ValueError:
|
||||
page = 1
|
||||
return page
|
||||
|
||||
|
||||
def dns_view(request):
|
||||
return log_view(request, 'dns')
|
||||
|
||||
|
||||
def dns_delete(request):
|
||||
userid = request.session.get('userid', None)
|
||||
if not userid:
|
||||
return redirect('/login')
|
||||
dns_id = request.GET.get("id")
|
||||
ip = request.GET.get("ip")
|
||||
domain = request.GET.get("domain")
|
||||
user = User.objects.filter(id=userid)[0]
|
||||
obj = DNSLog.objects.filter(user=user)
|
||||
if dns_id:
|
||||
obj = obj.filter(id=dns_id)
|
||||
if ip:
|
||||
obj = obj.filter(ip=ip)
|
||||
if domain:
|
||||
obj = obj.filter(host__icontains=domain)
|
||||
url = '/dns/?page=' + request.GET.get("page") if request.GET.get("page") else '/dns/?'
|
||||
if obj:
|
||||
if user.username != 'demo':
|
||||
obj.delete()
|
||||
return redirect(url+'&suc=删除成功')
|
||||
else:
|
||||
if request.GET.get("domain"):
|
||||
url += "&domain=" + request.GET.get("domain")
|
||||
if request.GET.get("ip"):
|
||||
url += "&ip=" + request.GET.get("ip")
|
||||
return redirect(url+"&fail=删除失败")
|
||||
|
||||
|
||||
def web_delete(request):
|
||||
userid = request.session.get('userid', None)
|
||||
if not userid:
|
||||
return redirect('/login')
|
||||
web_id = request.GET.get("id")
|
||||
ip = request.GET.get("ip")
|
||||
path = request.GET.get("path")
|
||||
headers = request.GET.get("headers")
|
||||
user = User.objects.filter(id=userid)[0]
|
||||
obj = WebLog.objects.filter(user=user)
|
||||
if web_id:
|
||||
obj = obj.filter(id=web_id)
|
||||
if ip:
|
||||
obj = obj.filter(remote_addr=ip)
|
||||
if path:
|
||||
obj = obj.filter(path__icontains=path)
|
||||
if headers:
|
||||
obj = obj.filter(headers__icontains=headers)
|
||||
url = '/web/?page=' + request.GET.get("page") if request.GET.get("page") else '/web/?'
|
||||
if obj:
|
||||
if user.username != 'demo':
|
||||
obj.delete()
|
||||
return redirect(url+'&suc=删除成功')
|
||||
else:
|
||||
if request.GET.get("domain"):
|
||||
url += "&domain=" + request.GET.get("domain")
|
||||
if request.GET.get("ip"):
|
||||
url += "&ip=" + request.GET.get("ip")
|
||||
return redirect(url+"&fail=删除失败")
|
||||
|
||||
|
||||
def web_view(request):
|
||||
return log_view(request, 'web')
|
||||
|
||||
|
||||
def config_view(request):
|
||||
return log_view(request, 'config')
|
||||
|
||||
|
||||
def rebind_view(request):
|
||||
return log_view(request, 'rebind')
|
||||
|
||||
|
||||
def log_view(request, type):
|
||||
userid = request.session.get('userid', None)
|
||||
if not userid:
|
||||
return redirect('/login')
|
||||
user = User.objects.filter(id=userid)[0]
|
||||
context = {}
|
||||
context['is_admin'] = user.is_admin
|
||||
context['type'] = type
|
||||
for var_name in ['domain', 'suc', 'fail', 'monitor', 'path', 'headers']:
|
||||
context[var_name] = request.GET.get(var_name, '')
|
||||
page = get_page(request.GET.get("page", 1))
|
||||
|
||||
if type == 'dns':
|
||||
objects = DNSLog.objects.filter(user=user)
|
||||
if request.GET.get('ip'):
|
||||
objects = objects.filter(ip=request.GET.get('ip'))
|
||||
if request.GET.get('domain'):
|
||||
objects = objects.filter(host__icontains=request.GET.get('domain'))
|
||||
paginator = Paginator(objects.order_by('-id'), 10)
|
||||
try:
|
||||
logs = paginator.page(page)
|
||||
except(EmptyPage, InvalidPage, PageNotAnInteger):
|
||||
page = paginator.num_pages
|
||||
logs = paginator.page(paginator.num_pages)
|
||||
if request.GET.get('check_update', '') == 'true':
|
||||
if request.GET.get('last_id') == str(logs[0].id):
|
||||
return HttpResponse('noChange')
|
||||
else:
|
||||
return HttpResponse('Changed')
|
||||
context['title'] = 'DNSLog管理'
|
||||
context['page'] = page
|
||||
context['logs'] = logs
|
||||
context['numpages'] = paginator.num_pages
|
||||
context['total'] = paginator.count
|
||||
|
||||
query_prefix = ''
|
||||
if request.GET.get('ip'):
|
||||
query_prefix += 'ip=' + request.GET.get('ip') + '&'
|
||||
if request.GET.get('domain'):
|
||||
query_prefix += 'domain=' + request.GET.get('domain') + '&'
|
||||
context['query_prefix'] = query_prefix
|
||||
context['last_id'] = logs[0].id if logs else 0
|
||||
|
||||
elif type == 'web':
|
||||
objects = WebLog.objects.filter(user=user)
|
||||
if request.GET.get('ip'):
|
||||
objects = objects.filter(remote_addr=request.GET.get('ip'))
|
||||
if request.GET.get('path'):
|
||||
objects = objects.filter(path__icontains=request.GET.get('path'))
|
||||
if request.GET.get('headers'):
|
||||
objects = objects.filter(headers__icontains=request.GET.get('headers'))
|
||||
paginator = Paginator(objects.order_by('-id'), 10)
|
||||
try:
|
||||
logs = paginator.page(page)
|
||||
except(EmptyPage, InvalidPage, PageNotAnInteger):
|
||||
page = paginator.num_pages
|
||||
logs = paginator.page(paginator.num_pages)
|
||||
if request.GET.get('check_update', '') == 'true':
|
||||
if request.GET.get('last_id') == str(logs[0].id):
|
||||
return HttpResponse('noChange')
|
||||
else:
|
||||
return HttpResponse('Changed')
|
||||
context['title'] = 'WebLog管理'
|
||||
context['page'] = page
|
||||
context['logs'] = logs
|
||||
context['numpages'] = paginator.num_pages
|
||||
context['total'] = paginator.count
|
||||
|
||||
query_prefix = ''
|
||||
if request.GET.get('ip', ''):
|
||||
query_prefix += 'ip=' + request.GET.get('ip') + '&'
|
||||
if request.GET.get('path', ''):
|
||||
query_prefix += 'path=' + request.GET.get('path') + '&'
|
||||
if request.GET.get('headers', ''):
|
||||
query_prefix += 'headers=' + request.GET.get('headers') + '&'
|
||||
context['query_prefix'] = query_prefix
|
||||
context['last_id'] = logs[0].id if logs else 0
|
||||
elif type == 'config':
|
||||
context['enable_register'] = 'yes'
|
||||
context['enable_create_random_user'] = 'yes'
|
||||
if user.is_admin:
|
||||
for c_name in ['enable_register', 'enable_create_random_user']:
|
||||
c = Config.objects.filter(name=c_name)
|
||||
if c and 'no' == c[0].value:
|
||||
context[c_name] = 'no'
|
||||
context['title'] = 'API配置'
|
||||
context['last_id'] = context['total'] = context['page'] = 0
|
||||
context['query_prefix'] = ''
|
||||
context['host'] = request.scheme + '://' + request.get_host()
|
||||
elif type == 'rebind':
|
||||
context['title'] = 'DNS重绑定'
|
||||
context['last_id'] = 0
|
||||
context['total'] = 0
|
||||
context['page'] = 0
|
||||
context['query_prefix'] = ''
|
||||
context['host'] = request.scheme + '://' + request.get_host()
|
||||
else:
|
||||
return HttpResponseRedirect('/')
|
||||
|
||||
context['userdomain'] = user.user_domain + '.' + settings.DNS_DOMAIN
|
||||
context['token'] = user.token
|
||||
context['username'] = user.username
|
||||
context['admin_domain'] = str(settings.ADMIN_DOMAIN)
|
||||
|
||||
return render(request, 'views.html', context)
|
||||
|
||||
|
||||
def api(request, type, username, prefix):
|
||||
result = False
|
||||
token = request.GET.get('token', '')
|
||||
if not User.objects.filter(username=username, token=token):
|
||||
return HttpResponse('Invalid token')
|
||||
|
||||
host = "%s.%s.%s" % (prefix, username, settings.DNS_DOMAIN)
|
||||
if type == 'dns':
|
||||
res = DNSLog.objects.filter(host=host)
|
||||
if len(res) > 0:
|
||||
result = True
|
||||
elif type == 'web':
|
||||
res = WebLog.objects.filter(path__contains=host)
|
||||
if len(res) > 0:
|
||||
result = True
|
||||
else:
|
||||
return HttpResponseRedirect('/')
|
||||
return HttpResponse(result)
|
||||
|
||||
|
||||
def group_api(request, username, prefix):
|
||||
token = request.GET.get('token', '')
|
||||
if not User.objects.filter(username=username, token=token):
|
||||
return HttpResponse('Invalid token')
|
||||
postfix = ".%s.%s.%s" % (prefix, username, settings.DNS_DOMAIN)
|
||||
res = DNSLog.objects.filter(host__endswith=postfix).order_by('host').values('host').distinct()
|
||||
if res:
|
||||
res = res[:50]
|
||||
data = [item['host'].replace(postfix, '') for item in res]
|
||||
text = json.dumps({"success": "true", "data": data})
|
||||
return HttpResponse(text, content_type="application/json")
|
||||
else:
|
||||
return HttpResponse("False")
|
||||
|
||||
|
||||
def as_admin(request):
|
||||
userid = request.session.get('userid', None)
|
||||
if not userid:
|
||||
return redirect('/login')
|
||||
user = User.objects.filter(id=userid)[0]
|
||||
if user.is_admin:
|
||||
su = SysUser.objects.filter(username=user.username, is_superuser=1)
|
||||
if su:
|
||||
request.user = su[0]
|
||||
login(request, su[0])
|
||||
return redirect('/admin/')
|
||||
return redirect('/')
|
||||
|
||||
|
||||
def config_update(request):
|
||||
userid = request.session.get('userid', None)
|
||||
config_name = request.GET.get('name', '')
|
||||
config_value = request.GET.get('value', '')
|
||||
if not userid or config_value not in ('yes', 'no') or \
|
||||
config_name not in ['enable_register', 'enable_create_random_user']:
|
||||
return HttpResponse('Fail', status=403)
|
||||
user = User.objects.filter(id=userid)[0]
|
||||
if user.is_admin:
|
||||
c = Config.objects.filter(name=config_name)
|
||||
if c:
|
||||
c[0].value = config_value
|
||||
c[0].save()
|
||||
else:
|
||||
Config.objects.create(name=config_name, value=config_value)
|
||||
return HttpResponse('Updated')
|
||||
return HttpResponse('Fail', status=403)
|
||||
|
||||
|
||||
def rebind_gen(request):
|
||||
userid = request.session.get('userid', None)
|
||||
if not userid:
|
||||
return HttpResponse('Fail', status=403)
|
||||
|
||||
valid_ip = request.GET.get('valid_ip', '')
|
||||
invalid_ip = request.GET.get('invalid_ip', '')
|
||||
try:
|
||||
valid_ip = hex(struct.unpack("!I", socket.inet_aton(valid_ip))[0])[2:]
|
||||
invalid_ip = hex(struct.unpack("!I", socket.inet_aton(invalid_ip))[0])[2:]
|
||||
user_domain = request.session.get('username') + '.' + settings.DNS_DOMAIN
|
||||
return HttpResponse('%s.%s.r.%s' % (valid_ip, invalid_ip, user_domain))
|
||||
except Exception as e:
|
||||
return HttpResponse('Fail')
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
import multiprocessing
|
||||
import zoneresolver
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dnslog.settings")
|
||||
from django.core.management import execute_from_command_line
|
||||
if len(sys.argv) >= 2 and sys.argv[1] == 'runserver':
|
||||
p = multiprocessing.Process(target=zoneresolver.main)
|
||||
p.daemon = True
|
||||
p.start()
|
||||
execute_from_command_line(sys.argv)
|
||||
@@ -0,0 +1,5 @@
|
||||
Django==4.0.3
|
||||
mysqlclient==2.1.0
|
||||
dnslib==0.9.19
|
||||
requests
|
||||
gunicorn
|
||||
@@ -0,0 +1,325 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v4.6.1 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2021 The Bootstrap Authors
|
||||
* Copyright 2011-2021 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||
*/
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
[tabindex="-1"]:focus:not(:focus-visible) {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title],
|
||||
abbr[data-original-title] {
|
||||
text-decoration: underline;
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
border-bottom: 0;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: .5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #0056b3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:not([href]):not([class]) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
-ms-overflow-style: scrollbar;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
color: #6c757d;
|
||||
text-align: left;
|
||||
caption-side: bottom;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
button:not(:disabled),
|
||||
[type="button"]:not(:disabled),
|
||||
[type="reset"]:not(:disabled),
|
||||
[type="submit"]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
input[type="radio"],
|
||||
input[type="checkbox"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: .5rem;
|
||||
font-size: 1.5rem;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type="search"] {
|
||||
outline-offset: -2px;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
||||
@@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v4.6.1 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2021 The Bootstrap Authors
|
||||
* Copyright 2011-2021 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
|
||||
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
|
||||
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 882 B |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,42 @@
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.form-signin {
|
||||
width: 100%;
|
||||
max-width: 430px;
|
||||
padding: 15px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.form-signin .form-control {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: auto;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.form-signin .form-control:focus {
|
||||
z-index: 2;
|
||||
}
|
||||
.form-signin input[type="text"] {
|
||||
margin-bottom: -1px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
.form-signin input[type="password"] {
|
||||
margin-bottom: 10px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<div style="margin-left: auto;">
|
||||
<label><input type="checkbox" class="form-control ml-5 mr-2" id="auto_refresh" {% if monitor == 'true' %} checked {% endif %}>监视刷新</label>
|
||||
</div>
|
||||
<div class="mr-5 ml-2">
|
||||
<img src="/static/help.png" width="17%" height="17%" data-toggle="popover" data-trigger="hover" data-title="自动刷新" data-content="监视频率为20秒,一旦发现新域名,本页面将自动刷新。如果希望监视特定关键词,请在搜索结果页开启"/>
|
||||
</div>
|
||||
@@ -0,0 +1,76 @@
|
||||
{% load static %}
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>{{ title }}</title>
|
||||
<link rel="shortcut icon" href="/static/favicon.png"/>
|
||||
<link href="{% static 'bootstrap-4.6.1/css/bootstrap.min.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'signin.css' %}" rel="stylesheet">
|
||||
</head>
|
||||
<body class="text-center">
|
||||
<form class="form-signin" method="post"{% if reg == 'true' %} oninput='password_check.setCustomValidity(password_check.value != password.value ? "两次输入的密码不一致" : "")'{% endif %}>
|
||||
<img class="mb-4" src="{% static "pentester.png" %}" width="400">
|
||||
<div class="alert-warning mb-2">{{ error_msg }}</div>
|
||||
{% if reg == 'true' %}
|
||||
<p class="alert alert-info">{% if init == 'true' %}<span style="color: red">请创建第一个账户,默认将成为管理员</span><br />{% endif %}用户名即域名前缀,越短,越有利于绕过限制</p>
|
||||
|
||||
<input type="text" id="username" name="username" class="form-control mt-3" placeholder="用户名" value="{{ username }}" required autofocus>
|
||||
<input type="email" id="email" name="email" class="form-control" placeholder="邮箱" value="{{ email }}" style="border-radius: 0;margin-bottom: -1px;" required autofocus>
|
||||
<input type="password" id="password" name="password" class="form-control" placeholder="密码" minlength="6" value="{{ password }}" style="border-radius: 0;margin-bottom: -1px;" required>
|
||||
<input type="password" id="password_check" name="password_check" class="form-control" placeholder="再次输入密码" required>
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">创建用户</button>
|
||||
<div class="mt-4"><a href="/login">已有账号?登录</a></div>
|
||||
{% else %}
|
||||
<input type="text" id="username" name="username" class="form-control" placeholder="用户名" required autofocus>
|
||||
<input type="password" id="password" name="password" class="form-control" placeholder="密码" minlength="6" required>
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">登录</button>
|
||||
<div class="mt-4">
|
||||
{% if enable_register == 'yes' %}<a href="/register">注册新账号</a> {% endif %}
|
||||
{% if enable_create_random_user == 'yes' %}
|
||||
<a href="#" class="ml-5" onclick="gen_random_id(event, false)">创建随机ID并登录</a>
|
||||
{% endif %}</div>
|
||||
|
||||
{% endif %}
|
||||
<div class="alert alert-info mt-5" role="alert">
|
||||
基于<a href="https://github.com/bugscanteam/dnslog/" class="alert-link" target="_blank">bugscan dnslog</a>开发<br/>
|
||||
</div>
|
||||
<p class="mt-5 mb-3 text-muted">© 2022-2022 <a href="https://github.com/lijiejie/eyes.sh" target="_blank">LiJieJie</a></p>
|
||||
</form>
|
||||
|
||||
<div class="modal fade" id="confirmModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">提示</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>随机账号创建后,无法再次登录,其数据可能被定期清理 <br />
|
||||
如果你希望长期保存日志,可注册个人账号</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="button btn-sm btn-primary" style="padding-top: 7px;" href="#"
|
||||
onclick="gen_random_id(event, true)">确认创建</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{% static 'jquery-3.6.0.min.js' %}"></script>
|
||||
<script src="{% static 'bootstrap-4.6.1/js/bootstrap.bundle.js' %}"></script>
|
||||
<script>
|
||||
function gen_random_id(e, confirm) {
|
||||
e.preventDefault();
|
||||
if (confirm){
|
||||
window.location.replace('/random' + '_id' + '_login');
|
||||
}
|
||||
$('#confirmModal').modal();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,38 @@
|
||||
<nav>
|
||||
<ul class="pagination">
|
||||
|
||||
{% if logs.has_previous %}
|
||||
<li class="page-item"><a class="page-link" href="?page={{ logs.previous_page_number }}">
|
||||
<span aria-hidden="true">«</span></a></li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link" aria-hidden="true">«</span></li>
|
||||
{% endif %}
|
||||
|
||||
{% if logs.number|add:'-4' > 1 %}
|
||||
<li class="page-item"><a class="page-link" href="?{{ query_prefix }}page={{ logs.number|add:'-5' }}">…</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% for i in logs.paginator.page_range %}
|
||||
{% if logs.number == i %}
|
||||
<li class="page-item active"><span class="page-link">{{ i }} <span class="sr-only">(current)</span></span></li>
|
||||
{% elif i > logs.number|add:'-5' and i < logs.number|add:'5' %}
|
||||
<li class="page-item"><a class="page-link" href="?{{ query_prefix }}page={{ i }}">{{ i }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if logs.paginator.num_pages > logs.number|add:'4' %}
|
||||
<li class="page-item"><a class="page-link" href="?{{ query_prefix }}page={{ logs.number|add:'5' }}">…</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% if logs.has_next %}
|
||||
<li class="page-item"><a class="page-link" href="?{{ query_prefix }}page={{ logs.next_page_number }}"><span aria-hidden="true">»</span></a></li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link" aria-hidden="true">»</span></li>
|
||||
{% endif %}
|
||||
|
||||
<span class="page-link ml-4" style="border: none">第{{ page }}页 / 共{{ numpages }}页, 共{{ total }}条记录</span>
|
||||
<a class="button btn-sm btn-danger" style="padding-top: 7px;" href="delete?{{ query_prefix }}{% if page != 1 %}&page={{ page }}{% endif %}"
|
||||
onclick="delete_all(event, false)">删除所有记录</a>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -0,0 +1,389 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ title }}</title>
|
||||
<link rel="shortcut icon" href="/static/favicon.png"/>
|
||||
<link href="{% static 'bootstrap-4.6.1/css/bootstrap.min.css' %}" rel="stylesheet">
|
||||
<style>
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
white-space: -moz-pre-wrap;
|
||||
white-space: -pre-wrap;
|
||||
white-space: -o-pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
mark {
|
||||
background-color: #e3f2fd;
|
||||
color: green;
|
||||
}
|
||||
.card-header {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="padding-top:10px;">
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-lg navbar-light mb-3" style="background-color: #e3f2fd;">
|
||||
<ul class="nav nav-pills mr-auto">
|
||||
<li class="nav-item mr-3" ><img src="/static/icon.png" height="40"/></li>
|
||||
<li class="nav-item" ><a class="nav-link{% if type == 'dns' %} active{% endif %}" href="/dns/">DNSLog</a></li>
|
||||
<li class="nav-item"><a class="nav-link{% if type == 'web' %} active{% endif %}" href="/web/">WebLog</a></li>
|
||||
<li class="nav-item"><a class="nav-link{% if type == 'config' %} active{% endif %}" href="/config/">API</a></li>
|
||||
<li class="nav-item"><a class="nav-link{% if type == 'rebind' %} active{% endif %}" href="/rebind/">Rebind</a></li>
|
||||
{% if is_admin %}<li class="nav-item"><a class="nav-link" href="/as_admin/">Admin</a></li>{% endif %}
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
<span class="mr-3" style="color: cornflowerblue">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-circle" viewBox="0 0 16 16">
|
||||
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
|
||||
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
|
||||
</svg>
|
||||
{{ username }}</span>
|
||||
<a href="/logout/">退出</a>
|
||||
</ul>
|
||||
</nav>
|
||||
{% if fail %}<div class="alert alert-danger">{{ fail }}</div>{% endif %}
|
||||
{% if suc %}<div class="alert alert-success">{{ suc }}</div>{% endif %}
|
||||
{% if type == 'dns'%}
|
||||
<form class="form-inline my-2 mt-md-0 input-group-sm" style="display: flex;justify-content: space-between">
|
||||
<div>
|
||||
<input class="form-control mr-sm-2 input-sm" size="30" type="text" placeholder="域名" name="domain" value="{{ domain }}">
|
||||
<button class="btn btn-sm btn-primary my-2 my-sm-0" type="submit">搜索</button>
|
||||
</div>
|
||||
{% include 'auto_reload.html' %}
|
||||
</form>
|
||||
|
||||
<div data-example-id="hoverable-table">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>域名</th>
|
||||
<th>Type</th>
|
||||
<th>IP</th>
|
||||
<th>位置</th>
|
||||
<th>时间</th>
|
||||
<th style="width: 100px;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in logs %}
|
||||
<tr>
|
||||
<th scope="row">{{log.id}}</th>
|
||||
<td style="word-break:break-all">{{ log.host }}</td>
|
||||
<td>{{ log.type }}</td>
|
||||
<td><a href="?ip={{ log.ip }}">{% if log.ip %} {{ log.ip }}</a> {% else %} {% endif %} </td>
|
||||
<td>{% if log.city %} {{ log.city }} {% else %} {% endif %}</td>
|
||||
<td>{{ log.created_time|date:'Y-m-d H:i:s' }}</td>
|
||||
<td>
|
||||
<a class="button btn-sm btn-danger" href="delete?{{ query_prefix }}id={{ log.id }}{% if page != 1 %}&page={{ page }}{% endif %}">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'pager.html' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if type == 'web'%}
|
||||
<form class="form-inline my-2 mt-md-0 input-group-sm" style="display: flex;justify-content: space-between">
|
||||
<div>
|
||||
<select class="form-control mr-2">
|
||||
<option {% if path != '' or headers == '' %}selected{% endif %}>URL</option>
|
||||
<option {% if headers != '' and path == '' %}selected{% endif %}>Headers</option>
|
||||
</select>
|
||||
<input class="form-control mr-sm-2 input-sm {% if headers != '' and path == '' %}d-none{% endif %}" size="30" type="text" placeholder="URL" name="path" id="inputURL" value="{{ path }}">
|
||||
<input class="form-control mr-sm-2 input-sm {% if path != '' or headers == '' %}d-none{% endif %}" size="30" type="text" placeholder="headers" name="headers" id="inputHeaders" value="{{ headers }}">
|
||||
<button class="btn btn-sm btn-primary my-2 my-sm-0" type="submit">搜索</button>
|
||||
</div>
|
||||
{% include 'auto_reload.html' %}
|
||||
</form>
|
||||
<div data-example-id="hoverable-table">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th style="width: 350px;">URL</th>
|
||||
<th>IP</th>
|
||||
<th>位置</th>
|
||||
<th>时间</th>
|
||||
<th style="width: 150px;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in logs %}
|
||||
<tr>
|
||||
<th scope="row">{{log.id}}</th>
|
||||
<td> <span style="word-break:break-all">{{ log.path }} </span></td>
|
||||
<td><a href="?ip={{ log.remote_addr }}">{% if log.remote_addr %} {{ log.remote_addr }}</a> {% else %} {% endif %}</td>
|
||||
<td> {% if log.city %} {{ log.city }} {% else %} {% endif %} </td>
|
||||
<td>{{ log.created_time|date:'Y-m-d H:i:s' }}</td>
|
||||
<td style="width: 150px;">
|
||||
<a type="button" class="button btn-sm btn-primary" onclick="show_web_headers({{log.id}})" id="showButton_{{log.id}}">查看</a>
|
||||
<a type="button" class="button btn-sm btn-danger" href="delete?{{ query_prefix }}id={{ log.id }}{% if page != 1 %}&page={{ page }}{% endif %}">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="d-none" id="web_headers_{{log.id}}">
|
||||
<td colspan="6" style="word-break:break-all">
|
||||
<div>
|
||||
<pre class="alert alert-info">{% if log.city %}{{ log.headers }}{% else %}{{ log.remote_addr }}{% endif %}</pre>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'pager.html' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if type == 'config'%}
|
||||
<div class="mt-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card" >
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
<b>子域名:</b> <mark>{{userdomain}}</mark>
|
||||
<b class="ml-4">Token:</b> <mark>{{token}}</mark>
|
||||
{% if is_admin %}
|
||||
<b class="ml-4 config"><input type="checkbox" id="enable_register" {% if enable_register == 'yes' %}checked{% endif %}> 开启用户注册功能</b>
|
||||
<b class="ml-4 config"><input type="checkbox" id="enable_create_random_user" {% if enable_create_random_user == 'yes' %}checked{% endif %}> 开启产生随机账号</b>
|
||||
{% endif %}
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-2">
|
||||
<div class="card-header">
|
||||
单个请求命中查询
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
{{host}}/api/{type}/{{username}}/{prefix}/?token={{ token }}</p>
|
||||
</p>
|
||||
<ul>
|
||||
<li>type: 设置为 <mark>dns</mark> 或者 <mark>web</mark></li>
|
||||
<li>prefix: 三级域名前缀</li>
|
||||
</ul>
|
||||
<p>检查是否触发 <mark>test.{{userdomain}}</mark>,访问:<a href="{{host}}/api/dns/{{username}}/test/?token={{ token }}" target="_blank">{{host}}/api/dns/{{username}}/test/?token={{ token }}</a></p>
|
||||
<ul>
|
||||
<li>返回 <mark>True</mark>,请求被触发</li>
|
||||
<li>返回 <mark>False</mark>,请求未触发</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-2">
|
||||
<div class="card-header">
|
||||
多个请求命中查询
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
{{host}}/api/group/dns/{{username}}/{prefix}/?token={{ token }}</p>
|
||||
</p>
|
||||
<ul>
|
||||
<li>prefix: 三级域名前缀</li>
|
||||
</ul>
|
||||
<p>对于一个较大的Payload集合,首先批量投递payload,再通过单个请求,来检查哪些payload触发执行成功。</p>
|
||||
<ul>
|
||||
<li>扫描器投递 <mark>payload1</mark>,使用域名:<mark>payload1.xxx-rce.{{userdomain}}</mark></li>
|
||||
<li>扫描器投递 <mark>payload2</mark>,使用域名:<mark>payload2.xxx-rce.{{userdomain}}</mark></li>
|
||||
<li>扫描器投递 <mark>payload3</mark>,使用域名:<mark>payload3.xxx-rce.{{userdomain}}</mark></li>
|
||||
<li>...</li>
|
||||
</ul>
|
||||
<p>检查是否存在漏洞,访问:<a href="{{host}}/api/group/dns/{{username}}/xxx-rce/?token={{ token }}" target="_blank">{{host}}/api/group/dns/{{user_domain}}/xxx-rce/?token={{ token }}</a></p>
|
||||
<ul>
|
||||
<li>返回 <mark>False</mark>,漏洞未触发</li>
|
||||
<li>返回 <mark>{"success": "true", "data": ["payload3", "payload2", "payload1"]}</mark>,漏洞被触发,data字段包含成功触发的payload清单,至多返回50个</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if type == 'rebind'%}
|
||||
<div class="mt-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
生成域名
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
<form class="form-inline my-2 mt-md-0 input-group-sm" style="display: flex;justify-content: space-between">
|
||||
<div>
|
||||
<input class="form-control mr-sm-2 input-sm" size="20" type="text" placeholder="合法IP" id="valid_ip" value="123.123.123.123">
|
||||
<input class="form-control mr-sm-2 input-sm" size="20" type="text" placeholder="非法IP" id="invalid_ip" value="10.1.1.1">
|
||||
<button class="btn btn-primary my-2 my-sm-0" type="button" onclick="generate_domain(event)">生成域名</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-4" id="result" style="color: red"></div>
|
||||
<div class="mt-4"><a href="/dns/?domain=.r.{{ userdomain }}&monitor=true" target="_blank" class="mr-4">DNS监控</a>
|
||||
<a href="/web/?path=.r.{{ userdomain }}&monitor=true" target="_blank">HTTP监控</a></div>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mt-2">
|
||||
<div class="card-header">
|
||||
什么是DNS重绑定
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
应用程序在请求用户可控的URL/域名资源时,有安全经验的程序员,往往会先解析域名,判断域名指向的IP是否在允许的范围中</p>
|
||||
<p class="card-text">
|
||||
例如,为了防御SSRF攻击,开发会首先检测URL中的域名指向的是一个非内网IP,才会在应用中发起后续请求。但是,由于第二步HTTP请求并不直接依赖第一步DNS请求的结果,黑客可以控制第二步请求中的目标IP,导致绕过检测。
|
||||
</p>
|
||||
<p class="card-text">
|
||||
攻击的方法是:<br />
|
||||
<mark>在程序做合法性校验时,DNS服务器首先返回一个预期的合法IP。由于DNS记录TTL为0,当应用真正发起HTTP请求资源时,再次DNS查询,此时DNS服务器立即返回一个非法的IP。</mark>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="confirmModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">提示</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
是否清空全部 <b>{{ total }}</b> 条记录?删除后数据无法恢复
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
|
||||
<a class="button btn-sm btn-danger" style="padding-top: 7px;" href="delete?{{ query_prefix }}{% if page != 1 %}&page={{ page }}{% endif %}"
|
||||
onclick="delete_all(event, true)">删除</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="{% static 'jquery-3.6.0.min.js' %}"></script>
|
||||
<script src="{% static 'bootstrap-4.6.1/js/bootstrap.bundle.js' %}"></script>
|
||||
|
||||
<script>
|
||||
function show_web_headers(id) {
|
||||
if ( $('#web_headers_' + id)[0].className == 'd-none'){
|
||||
$('#web_headers_' + id).removeClass('d-none');
|
||||
$('#showButton_' + id).text('收起');
|
||||
} else {
|
||||
$('#web_headers_' + id).addClass('d-none');
|
||||
$('#showButton_' + id).text('查看');
|
||||
}
|
||||
}
|
||||
/* var getUrlParameter = function getUrlParameter(sParam) {
|
||||
var sPageURL = window.location.search.substring(1),
|
||||
sURLVariables = sPageURL.split('&'),
|
||||
sParameterName,
|
||||
i;
|
||||
|
||||
for (i = 0; i < sURLVariables.length; i++) {
|
||||
sParameterName = sURLVariables[i].split('=');
|
||||
|
||||
if (sParameterName[0] === sParam) {
|
||||
return sParameterName[1] === undefined ? true : decodeURIComponent(sParameterName[1]);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
*/
|
||||
|
||||
function delete_all(e, confirm) {
|
||||
if (confirm)
|
||||
return true;
|
||||
e.preventDefault();
|
||||
$('#confirmModal').modal();
|
||||
/* if (!getUrlParameter('ip') && !getUrlParameter('domain') && !getUrlParameter('path') && !getUrlParameter('headers')) {
|
||||
e.preventDefault();
|
||||
$('#confirmModal').modal();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
$('select').on('change', function() {
|
||||
if (this.value=='URL') {
|
||||
$('#inputHeaders').prop('value', '');
|
||||
$('#inputHeaders').addClass('d-none');
|
||||
$('#inputURL').removeClass('d-none');
|
||||
} else {
|
||||
$('#inputHeaders').removeClass('d-none');
|
||||
$('#inputURL').prop('value', '');
|
||||
$('#inputURL').addClass('d-none');
|
||||
}
|
||||
});
|
||||
{% if monitor %}
|
||||
window.reload_interval = setInterval(data_changed, 10000);
|
||||
{% endif %}
|
||||
$('#auto_refresh').change(function () {
|
||||
if ($('#auto_refresh').is(':checked')){
|
||||
window.reload_interval = setInterval(data_changed, 10000);
|
||||
} else {
|
||||
clearInterval(window.reload_interval);
|
||||
}
|
||||
})
|
||||
function data_changed() {
|
||||
let base_url = $(location).attr('href');
|
||||
if (base_url.indexOf('?') < 0){
|
||||
base_url += '?'
|
||||
}
|
||||
url = base_url + '&check_update=true&last_id=' + {{ last_id }};
|
||||
$.ajax({url:url, success:function(resp){
|
||||
if (resp === 'Changed'){
|
||||
if (base_url.indexOf('monitor=true') < 0){
|
||||
base_url += '&monitor=true'
|
||||
}
|
||||
window.location.replace(base_url);
|
||||
}
|
||||
}});
|
||||
}
|
||||
|
||||
function generate_domain(e){
|
||||
e.preventDefault();
|
||||
url = '/rebind/gen?valid_ip=' + $('#valid_ip').prop('value') + '&invalid_ip=' + $('#invalid_ip').prop('value') ;
|
||||
$.ajax({url:url, success:function(resp){
|
||||
$('#result').html(resp);
|
||||
}});
|
||||
}
|
||||
|
||||
$('.config').change(function (e) {
|
||||
url = '/config/update?name=' + e.target.id + '&value=';
|
||||
if ($('#' + e.target.id).is(':checked')){
|
||||
$.ajax({url:url+'yes', success:function(resp){
|
||||
location.reload();
|
||||
}});
|
||||
} else {
|
||||
$.ajax({url:url+'no', success:function(resp){
|
||||
location.reload();
|
||||
}});
|
||||
}
|
||||
})
|
||||
|
||||
$(function () {
|
||||
$('[data-toggle="popover"]').popover();
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,162 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dnslog.settings")
|
||||
import django
|
||||
django.setup()
|
||||
import copy
|
||||
import re
|
||||
import json
|
||||
import requests
|
||||
import struct
|
||||
import socket
|
||||
import random
|
||||
from dnslib import RR, QTYPE, RCODE, TXT, A
|
||||
from dnslib.server import DNSServer, DNSHandler, BaseResolver, DNSLogger
|
||||
from logview.models import *
|
||||
from dnslog import settings
|
||||
|
||||
|
||||
class MySQLLogger:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def log_data(self, dnsobj):
|
||||
pass
|
||||
|
||||
def log_error(self, handler, e):
|
||||
pass
|
||||
|
||||
def log_pass(self, *args):
|
||||
pass
|
||||
|
||||
def log_prefix(self, handler):
|
||||
pass
|
||||
|
||||
def log_recv(self, handler, data):
|
||||
pass
|
||||
|
||||
def log_reply(self, handler, reply):
|
||||
pass
|
||||
|
||||
def log_request(self, handler, request):
|
||||
if QTYPE[request.q.qtype] == 'AAAA':
|
||||
return
|
||||
domain = request.q.qname.__str__().lower()
|
||||
if not domain.endswith(settings.DNS_DOMAIN + '.'):
|
||||
return
|
||||
matches = re.search(r'\.?([^\.]+)\.%s\.' % settings.DNS_DOMAIN, domain)
|
||||
if not matches:
|
||||
return
|
||||
user = User.objects.filter(user_domain__exact=matches.group(1))
|
||||
if not user and domain.strip(".") != settings.ADMIN_DOMAIN:
|
||||
user = User.objects.filter(user_domain__exact='@')
|
||||
if not user:
|
||||
return
|
||||
ip = handler.client_address[0]
|
||||
try:
|
||||
doc = requests.get('http://ip.ws.126.net/ipquery?ip=%s' % ip).text
|
||||
doc = doc[doc.find('var localAddress=')+17:].strip()
|
||||
doc = doc.replace('city', '"city"').replace('province', '"province"')
|
||||
_doc = json.loads(doc)
|
||||
city = _doc['province'] + ' ' + _doc['city']
|
||||
except Exception as e:
|
||||
city = ''
|
||||
log = DNSLog(user=user[0], host=domain.strip('.'), type=QTYPE[request.q.qtype], ip=ip, city=city)
|
||||
log.save()
|
||||
|
||||
def log_send(self, handler, data):
|
||||
pass
|
||||
|
||||
def log_truncated(self, handler, reply):
|
||||
pass
|
||||
|
||||
|
||||
class ZoneResolver(BaseResolver):
|
||||
"""
|
||||
Simple fixed zone file resolver.
|
||||
"""
|
||||
|
||||
def __init__(self, zone, glob=False):
|
||||
"""
|
||||
Initialise resolver from zone file.
|
||||
Stores RRs as a list of (label,type,rr) tuples
|
||||
If 'glob' is True use glob match against zone file
|
||||
"""
|
||||
self.zone = [(rr.rname, QTYPE[rr.rtype], rr) for rr in RR.fromZone(zone)]
|
||||
self.glob = glob
|
||||
self.eq = 'matchGlob' if glob else '__eq__'
|
||||
|
||||
def resolve(self, request, handler):
|
||||
"""
|
||||
Respond to DNS request - parameters are request packet & handler.
|
||||
Method is expected to return DNS response
|
||||
"""
|
||||
reply = request.reply()
|
||||
qname = request.q.qname
|
||||
qtype = QTYPE[request.q.qtype]
|
||||
if qtype == 'TXT':
|
||||
reply.add_answer(RR(qname, QTYPE.TXT, rdata=TXT('Nothing to response')))
|
||||
# rebind
|
||||
try:
|
||||
if qtype == 'A' and qname.__str__().endswith(settings.DNS_DOMAIN + '.'):
|
||||
subs = qname.__str__().replace('.' + settings.DNS_DOMAIN + '.', '')
|
||||
ret = subs.split('.')
|
||||
if len(ret) == 4 and ret[-2].lower() == 'r':
|
||||
valid_ip = int(ret[0], 16)
|
||||
invalid_ip = int(ret[1], 16)
|
||||
valid_ip = socket.inet_ntoa(struct.pack("!I", valid_ip))
|
||||
invalid_ip = socket.inet_ntoa(struct.pack("!I", invalid_ip))
|
||||
ip = random.choice((valid_ip, invalid_ip))
|
||||
rr = RR(qname, request.q.qtype, rdata=A(ip), ttl=0)
|
||||
reply.add_answer(rr)
|
||||
return reply
|
||||
except Exception as e:
|
||||
pass
|
||||
#
|
||||
for name, rtype, rr in self.zone:
|
||||
# Check if label & type match
|
||||
if getattr(qname, self.eq)(name) and (qtype == rtype or qtype == 'ANY' or rtype == 'CNAME'):
|
||||
# If we have a glob match fix reply label
|
||||
if self.glob:
|
||||
a = copy.copy(rr)
|
||||
a.rname = qname
|
||||
if qname.__str__().rstrip('.') in settings.ADMIN_DOMAIN:
|
||||
a.ttl = 1200
|
||||
reply.add_answer(a)
|
||||
else:
|
||||
reply.add_answer(rr)
|
||||
# Check for A/AAAA records associated with reply and
|
||||
# add in additional section
|
||||
if rtype in ['CNAME', 'NS', 'MX', 'PTR']:
|
||||
for a_name, a_rtype, a_rr in self.zone:
|
||||
if a_name == rr.rdata.label and a_rtype in ['A', 'AAAA']:
|
||||
reply.add_ar(a_rr)
|
||||
if not reply.rr:
|
||||
reply.header.rcode = RCODE.NXDOMAIN
|
||||
return reply
|
||||
|
||||
|
||||
def main():
|
||||
zone = '''
|
||||
{dns_domain}. IN NS {ns1_domain}.
|
||||
{dns_domain}. IN NS {ns2_domain}.
|
||||
*.{dns_domain}. IN NS {ns1_domain}.
|
||||
*.{dns_domain}. IN NS {ns2_domain}.
|
||||
*.{dns_domain}. IN A {server_ip}
|
||||
{dns_domain}. IN A {server_ip}
|
||||
'''.format(
|
||||
dns_domain=settings.DNS_DOMAIN,
|
||||
ns1_domain=settings.NS1_DOMAIN,
|
||||
ns2_domain=settings.NS2_DOMAIN,
|
||||
server_ip=settings.SERVER_IP
|
||||
)
|
||||
resolver = ZoneResolver(zone, True)
|
||||
logger = MySQLLogger()
|
||||
udp_server = DNSServer(resolver, port=53, address='', logger=logger)
|
||||
udp_server.start()
|
||||
print("Zone Resolver started (%s:%d) [%s]" % ("*", 53, "UDP"))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||