mirror of
https://github.com/jumpserver/jumpserver.git
synced 2026-03-18 11:02:09 +00:00
Merge branch 'pr@dev@fix_sec' of github.com:jumpserver/jumpserver into pr@dev@fix_sec
This commit is contained in:
@@ -18,6 +18,7 @@ from rest_framework.response import Response
|
||||
from common.api import CommonApiMixin
|
||||
from common.const.http import GET, POST
|
||||
from common.drf.filters import DatetimeRangeFilterBackend
|
||||
from common.drf.throttling import FileTransferThrottle
|
||||
from common.permissions import IsServiceAccount
|
||||
from common.plugins.es import QuerySet as ESQuerySet
|
||||
from common.sessions.cache import user_session_manager
|
||||
@@ -111,6 +112,7 @@ class FTPLogViewSet(OrgModelViewSet):
|
||||
|
||||
@action(
|
||||
methods=[GET], detail=True, permission_classes=[RBACPermission, ],
|
||||
throttle_classes=[FileTransferThrottle],
|
||||
url_path='file/download'
|
||||
)
|
||||
def download(self, request, *args, **kwargs):
|
||||
@@ -133,7 +135,9 @@ class FTPLogViewSet(OrgModelViewSet):
|
||||
)
|
||||
return response
|
||||
|
||||
@action(methods=[POST], detail=True, permission_classes=[IsServiceAccount, ], serializer_class=FileSerializer)
|
||||
@action(methods=[POST], detail=True, permission_classes=[IsServiceAccount, ],
|
||||
throttle_classes=[FileTransferThrottle],
|
||||
serializer_class=FileSerializer)
|
||||
def upload(self, request, *args, **kwargs):
|
||||
ftp_log = self.get_object()
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
|
||||
@@ -352,7 +352,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="contact-form col-md-10 col-md-offset-1" style='float: none; overflow: hidden'>
|
||||
<form id="login-form" action="" method="post" role="form" novalidate="novalidate">
|
||||
<form id="login-form" autocomplete="off" action="" method="post" role="form" novalidate="novalidate">
|
||||
{% csrf_token %}
|
||||
<div style="line-height: 17px;margin-bottom: 20px;color: #999999;">
|
||||
{% if form.non_field_errors %}
|
||||
@@ -362,9 +362,9 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% bootstrap_field form.username show_label=False %}
|
||||
{% bootstrap_field form.username autocomplete="off" show_label=False %}
|
||||
<div class="form-group {% if form.password.errors %} has-error {% endif %}">
|
||||
<input type="password" class="form-control" id="password" placeholder="{% trans 'Password' %}"
|
||||
<input type="password" autocomplete="off" class="form-control" id="password" placeholder="{% trans 'Password' %}"
|
||||
required>
|
||||
<input id="password-hidden" type="text" style="display:none"
|
||||
name="{{ form.password.html_name }}">
|
||||
|
||||
@@ -12,6 +12,7 @@ from rest_framework.renderers import BaseRenderer
|
||||
from rest_framework.utils import encoders, json
|
||||
|
||||
from common.serializers import fields as common_fields
|
||||
from common.serializers.fields import EncryptedField
|
||||
from common.utils import get_logger
|
||||
from .mixins import LogMixin
|
||||
|
||||
@@ -23,6 +24,8 @@ class BaseFileRenderer(LogMixin, BaseRenderer):
|
||||
# 渲染模板标识, 导入、导出、更新模板: ['import', 'update', 'export']
|
||||
template = 'export'
|
||||
serializer = None
|
||||
# 敏感字段名称,这些字段不允许导出
|
||||
secret_field_names = ("password", "token", "secret", "key", "private_key", "passphrase")
|
||||
|
||||
@staticmethod
|
||||
def _check_validation_data(data):
|
||||
@@ -48,6 +51,18 @@ class BaseFileRenderer(LogMixin, BaseRenderer):
|
||||
disposition = 'attachment; filename="{}"'.format(filename)
|
||||
response['Content-Disposition'] = disposition
|
||||
|
||||
def is_secret_field(self, field):
|
||||
"""检查字段是否为敏感字段,敏感字段不允许导出"""
|
||||
# 检查字段类型是否为 EncryptedField
|
||||
if isinstance(field, EncryptedField):
|
||||
return True
|
||||
# 检查字段名是否包含敏感关键词
|
||||
field_name = field.field_name.lower()
|
||||
for secret_name in self.secret_field_names:
|
||||
if secret_name in field_name:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_rendered_fields(self):
|
||||
fields = self.serializer.fields
|
||||
meta = getattr(self.serializer, 'Meta', None)
|
||||
@@ -62,6 +77,8 @@ class BaseFileRenderer(LogMixin, BaseRenderer):
|
||||
|
||||
fields_unexport = getattr(meta, 'fields_unexport', [])
|
||||
fields = [v for v in fields if v.field_name not in fields_unexport]
|
||||
# 过滤敏感字段,禁止口令、密钥等敏感信息导出
|
||||
fields = [v for v in fields if not self.is_secret_field(v)]
|
||||
return fields
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from rest_framework.throttling import SimpleRateThrottle
|
||||
|
||||
__all__ = ['RateThrottle', 'FileTransferThrottle']
|
||||
|
||||
|
||||
class RateThrottle(SimpleRateThrottle):
|
||||
|
||||
@@ -32,3 +34,17 @@ class RateThrottle(SimpleRateThrottle):
|
||||
'scope': self.scope,
|
||||
'ident': ident
|
||||
}
|
||||
|
||||
|
||||
class FileTransferThrottle(SimpleRateThrottle):
|
||||
"""
|
||||
文件上传下载限流,防止DOS攻击
|
||||
"""
|
||||
scope = 'file_transfer'
|
||||
|
||||
def get_cache_key(self, request, view):
|
||||
if request.user and request.user.is_authenticated:
|
||||
ident = request.user.pk
|
||||
else:
|
||||
ident = self.get_ident(request)
|
||||
return self.cache_format % {'scope': self.scope, 'ident': ident}
|
||||
|
||||
@@ -226,6 +226,9 @@ class Config(dict):
|
||||
'THROTTLE_RATES_USER': '180/min',
|
||||
'THROTTLE_RATES_SERVICE_ACCOUNT': '300/min',
|
||||
|
||||
# 文件上传下载限流 (防止DOS攻击)
|
||||
'THROTTLE_FILE_TRANSFER': '50/hour',
|
||||
|
||||
# Security
|
||||
'X_FRAME_OPTIONS': 'SAMEORIGIN',
|
||||
'VERIFY_EXTERNAL_SSL': True,
|
||||
|
||||
@@ -45,6 +45,7 @@ REST_FRAMEWORK = {
|
||||
'anon': CONFIG.THROTTLE_RATES_ANON,
|
||||
'user': CONFIG.THROTTLE_RATES_USER,
|
||||
'service_account': CONFIG.THROTTLE_RATES_SERVICE_ACCOUNT,
|
||||
'file_transfer': CONFIG.THROTTLE_FILE_TRANSFER,
|
||||
},
|
||||
'DEFAULT_FILTER_BACKENDS': (
|
||||
'django_filters.rest_framework.DjangoFilterBackend',
|
||||
|
||||
@@ -18,6 +18,7 @@ from rest_framework.views import APIView
|
||||
from acls.models import LoginAssetACL
|
||||
from assets.models import Asset
|
||||
from common.const.http import POST
|
||||
from common.drf.throttling import FileTransferThrottle
|
||||
from common.permissions import IsValidUser
|
||||
from common.utils import get_request_ip_or_data
|
||||
from ops.celery import app
|
||||
@@ -171,7 +172,9 @@ class JobViewSet(LoginAssetACLCheckMixin, OrgBulkModelViewSet):
|
||||
return exceeds_limit_files
|
||||
|
||||
@action(methods=[POST], detail=False, serializer_class=FileSerializer,
|
||||
permission_classes=[IsValidUser, ], url_path='upload')
|
||||
permission_classes=[IsValidUser, ],
|
||||
throttle_classes=[FileTransferThrottle],
|
||||
url_path='upload')
|
||||
def upload(self, request, *args, **kwargs):
|
||||
uploaded_files = request.FILES.getlist('files')
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
|
||||
@@ -25,6 +25,7 @@ from common.const.http import GET, POST
|
||||
from common.drf.filters import BaseFilterSet
|
||||
from common.drf.filters import DatetimeRangeFilterBackend
|
||||
from common.drf.renders import PassthroughRenderer
|
||||
from common.drf.throttling import FileTransferThrottle
|
||||
from common.permissions import IsServiceAccount
|
||||
from common.storage.replay import ReplayStorageHandler, SessionPartReplayStorageHandler
|
||||
from common.utils import data_to_json, is_uuid, i18n_fmt
|
||||
@@ -127,6 +128,7 @@ class SessionViewSet(OrgBulkModelViewSet):
|
||||
return file
|
||||
|
||||
@action(methods=[GET], detail=True, renderer_classes=(PassthroughRenderer,), url_path='replay/download',
|
||||
throttle_classes=[FileTransferThrottle],
|
||||
url_name='replay-download')
|
||||
def download(self, request, *args, **kwargs):
|
||||
session = self.get_object()
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
<div class="form-input form-group">
|
||||
<input type="password" id="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
|
||||
<input type="password" id="password" autocomplete="off" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">{% trans 'Confirm' %}</button>
|
||||
</form>
|
||||
@@ -32,6 +32,3 @@
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user