2020년 3월 25일 보안정보 스크래핑

2020년 3월 25일 보안정보 스크래핑 3월 25일 보안정보 스크래핑 ==================================================================== + 주요 취약점 - 메일전송 프로토콜을 이용한 원격 명령어 실행 주의 권고 외 1건 1. 메일전송 프로토콜을 이용한 원격 명령어 실행 주의 권고 최근 OpenSMTPD* 취약점이 발견되는 등 메일전송 프로토콜에서 원격 명령어 실행이 가능하여 주의를 권고함 공격자는 취약점을 악용하여 피해를 발생시킬 수 있으므로, 해결방안을 참고하여 조치 필요 - https://www.krcert.or.kr/data/secNoticeView.do?bulletin_writing_sequence=35302 2. Django 제품 SQL Injection 취약점 보안 업데이트 권고 최근 Django*에서 SQL Injection취약점(CVE-2020-9402)을 악용할 수 있는 개념증명코드(Proof of concept, PoC)가 인터넷상에 공개되어 사용자의 보안 업데이트 필요 - https://www.krcert.or.kr/data/secNoticeView.do?bulletin_writing_sequence=35301 ==================================================================== + 취약점 - Apple Safari 취약점 1. Apple Safari 취약점 Apple Safari security bypass CVE-2020-3885 - https://exchange.xforce.ibmcloud.com/vulnerabilities/178339 Apple Safari security bypass CVE-2020-3887 - https://exchange.xforce.ibmcloud.com/vulnerabilities/178338 Apple Safari inform

[Django] Authentication 과 Permissions

[Django] Authentication 과 Permissions

DRF 에서의 접근제한을 알아보도록 하겠습니다.

우선 기본적으로 접근제한을 제외한 기본적인 코드들에 대해서는 설명을 생략하겠습니다.

앞의 포스팅들을 참고해주세요.

현재 까지의 진행 상황은 다음과 같습니다.

Post 모델에 author 를 추가하여 글 작성자를 연결하였습니다.

# models.py from django.db import models from django.conf import settings class Post(models.Model): author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) title = models.CharField(max_length=100) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True)

# serializers.py from rest_framework.serializers import ModelSerializer from .models import Post class PostSerializer(ModelSerializer): class Meta: model = Post fields = ['title']

# urls.py from django.urls import path, include from rest_framework.routers import DefaultRouter from . import views router = DefaultRouter() router.register(r'post', views.PostViewSet) urlpatterns = [ path('', include(router.urls)), ]

또한 임시로 admin page 에서 user 를 만들어주었습니다.

Authentication

인증의 종류

지원하는 인증의 종류는 총 4가지가 있습니다.

SessionAuthentication 세션을 통한 인증 여부 체크 APIView를 통해 디폴트 지정 (우선순위 1)

BasicAuthentication Basic 인증헤더를 통한 인증 수행 ex) Authorization: Basic YWxsaWV1czE6MTAyOXNoYWtl APIView를 통해 디폴트 지정 (우선순위 2)

TokenAuthentication Token 헤더를 통한 인증 수행 ex) Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a

RemoteUserAuthentication User 정보가 다른 서비스에서 관리될 때, Remote 인증 (장고 공식문서) Remote-User 헤더를 통한 인증 수행

HTTPie 를 통한 TEST

API 요청 시에 인증정보를 제공하지 않았기 때문에 self.request.user 에 AnonymouseUser 인스턴스가 할당되어 모델저장에 실패하게 됩니다.

http --form POST localhost:8000/post/ title="제목" # 500 Internal Server Error

이럴 경우 HTTP Basic 인증헤더 를 통해 유저 정보를 넘겨줘야 합니다.

위 username 과 password 에는 각각 맞는 정보를 입력하면 됩니다.

http --auth username:password --form POST localhost:8000/post/ title="제목" # 201 Created

이 요청에서 username:password 문자열은 base64로 인코딩되어 Authorization 헤더 로 전달되고, 이 헤더를 BasicAuthorization 에서 인지하여 인증을 처리합니다.

httpbin.org 를 통해 헤더를 확인할 수 있습니다.

12번째 줄에 인코딩된 값이 있습니다.

http -a username:password --form POST httpbin.org/post """ { "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=", "Connection": "close", "Content-Length": "0", "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", "Host": "httpbin.org", "User-Agent": "HTTPie/0.9.9" }, "json": null, "origin": "221.148.61.230", "url": "http://httpbin.org/post" } """

웹 브라우저에서의 로그인

DRF 는 웹 브라우저에서의 로그인도 지원합니다.

8번째 줄과 같이 추가해주시면 아래 이미지에서 오른쪽 상단에 로그인, 로그아웃 기능이 추가됩니다.

# config/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), # 생략 path('api_auth/', include('rest_framework.urls', namespace='rest_framework')), ]

Permission

django 에서의 권한

django 는 기본적인 권한들을 제공해주고 있습니다.

is_superuser createsuper 로 생성한 user 에 대해 True

True일 경우 별도 permission 없이 모든 권한 허용

is_staff True 일 경우 admin 페이지 접속가능 나머지는 일반 유저와 동일

is_active False 일 경우 모든 권한 불허 로그인도 불가능

DRF 에서 기본제공하는 Permission 은 다음과 같습니다.

AllowAny : 인증여부에 상관없이 뷰 호출 허용 (default)

IsAuthenticated : 인증된 요청에 한해서 뷰호출 허용

IsAdminUser : Staff 인증 요청에 한해서 뷰호출 허용

IsAuthenticatedOrReadOnly : 비인증 요청에게는 읽기 권한만 허용

DjangoModelPermissions : 인증된 요청에 한해서만 뷰 호출 허용, 추가로 유저별 인증 권한체크를 수행

DjangoModelPermissionsOrAnonReadOnly : DjangoModelPermissions 와 유사하나 비인증 요청에 대해서는 읽기 권한만 허용

DjangoObjectPermissions 비인증된 요청 거부 인증된 레코드 접근에 대한 권한체크를 추가로 수행

권한 지정하기

기본적으로 제공되는 권한을 적용해보도록 하겠습니다.

APIView 에서는 permission_classes 을 통해 권한을 지정할 수 있습니다.

ViewSet 역시 APIView 를 상속받았으므로 동일하게 가능이 가능합니다.

# views.py from rest_framework.viewsets import ModelViewSet from rest_framework.permissions import IsAuthenticated from .models import Post from .serializers import PostSerializer class PostViewSet(ModelViewSet): queryset = Post.objects.all() serializer_class = PostSerializer permission_classes = [ IsAuthenticated, ] def perform_create(self, serializer): print(self.request.user) serializer.save(author=self.request.user)

IsAuthenticated 의 경우, 위에서 봤듯이 인증된 요청에 한해서만 뷰호출을 허용합니다.

로그인을 안한 상태에서는 권한이 없으므로 아래와 같이 권한이 없다고 나옵니다.

커스텀 Permission

DRF 에서 제공해주긴 하지만 이들만으로는 부족합니다.

따라서 커스텀하는 방법에 대해서 알아보도록 하겠습니다.

모든 Permission 클래스는 다음 2가지 함수를 선택적으로 구현합니다.

has_permission(request, view) 뷰 호출 접근 권한 APIView 접근 시 체크

has_object_permission(request, view, obj) 개별 레코드 접근 권한 APIView 의 get_object 함수를 통해 object 획득 시 체크 브라우저를 통한 API 접근시에 CREATE/UPDATE Form 노출 여부 확인 시에

permission 들의 코드를 살펴보고 어떤 식으로 커스텀 해야할 지 방향을 잡아보겠습니다.

https://github.com/encode/django-rest-framework/blob/master/rest_framework/permissions.py

우선 상당부에 안전한 method 들을 따로 정의해두었습니다.

이들은 수정 삭제 삽입을 하지 않아서 안전하다고 부릅니다.

SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')

간단히 몇 개의 permission 에 대해서 살펴보겠습니다.

AllowAny 는 모든 요청에 대해 허가합니다.

class AllowAny(BasePermission): def has_permission(self, request, view): return True

IsAuthenticated 는 유저가 존재하고 로그인 되어 있을 경우에 허가합니다.

class IsAuthenticated(BasePermission): def has_permission(self, request, view): return bool(request.user and request.user.is_authenticated)

IsAdminUser 는 유저가 존재하고 스태프일 경우에 허가합니다.

class IsAdminUser(BasePermission): def has_permission(self, request, view): return bool(request.user and request.user.is_staff)

IsAuthenticatedOrReadOnly 는 안전한 request method 이거나 유저가 존재하고 로그인 되어 있을 경우에 허가합니다.

class IsAuthenticatedOrReadOnly(BasePermission): def has_permission(self, request, view): return bool( request.method in SAFE_METHODS or request.user and request.user.is_authenticated )

이하의 다른 permission 에 대해서는 위의 링크를 통해 참고하시면 되겠습니다.

커스텀 permission 을 만들기 위해서 permissions.py 파일을 만들어 이 안에서 새롭게 정의해보도록 하겠습니다.

첫 번째 예시에서는 포스트 작성자에 한해 수정/삭제 권한 을 부여해보겠습니다.

# permissions.py from rest_framework import permissions class IsAuthorOrReadonly(permissions.BasePermission): # 인증된 유저에 대해 목록 조회 / 포스팅 등록 허용 def has_permission(self, request, view): return request.user.is_authenticated # 작성자에 한해 Record에 대한 수정 / 삭제 허용 def has object_permission(slef, request. views, obj): # 조회 요청은 항상 True if request.method in permissions.SAFE_METHODS: return True # PUT, DELETE 요청에 한해, 작성자에게만 허용 return obj.author == request.user

두 번째로 볼 예시는 포스트 작성자에 한해 수정 권한은 부여하되 삭제권한은 superuser 에게만 부여 해보도록하겠습니다.

# permissions.py from rest_framework import permissions class IsAuthorUpdateOrReadOnly(permissions.BasePermission): def has_permission(slef, request, view): return request.user.is_authenticated def has_object_permission(self, request, view, obj): if request.method in permissions.SAFE_METHODS: return True if (request.method == 'DELETE'): return request.user.is_superuser return obj.author == request.user

두 예시 중 IsAuthorUpdateOrReadOnly 을 직접 적용시켜보면 views 12번째 줄에 추가해주었습니다.

# views.py from rest_framework.viewsets import ModelViewSet from rest_framework.permissions import AllowAny, IsAuthenticated from .models import Post from .serializers import PostSerializer from .permissions import IsAuthorUpdateOrReadOnly class PostViewSet(ModelViewSet): queryset = Post.objects.all() serializer_class = PostSerializer permission_classes = [ IsAuthorUpdateOrReadOnly, ] def perform_create(self, serializer): serializer.save(author=self.request.user)

이를 HTTPie 와 브라우저를 통해 확인해보도록 하죠.

user1 이 사용한 2번 글에 대한 요청들입니다.

root 로 접속하는 경우

http --auth root:root --form PUT http://localhost:8000/post/2/ title="제목 수정" """ 403 Forbidden { "detail": "You do not have permission to perform this action." } """

user1 으로 접속하는 경우

http --auth user1:mskang0710! --form PUT http://localhost:8000/post/2/ title="제목 수정" """ 200 OK { "title": "user1 제목 수정" } """

POST 조회 응답에 작성자 추가

누가 어떤 글을 썼는지 알기 위해서는 조회 요청이 들어왔을 때 작성자에 대한 정보도 제공해주어야 합니다.

아래와 같이 fields 에 추가하게 되면 request 를 받을 때도 author 를 받아야 하는 문제가 발생합니다. 글을 작성하는 경우 작성자에 대해서는 직접 작성하는 경우는 많이 없으니 이는 잘못된 방법이죠.

# serializers.py from rest_framework.serializers import ModelSerializer from .models import Post class PostSerializer(ModelSerializer): class Meta: model = Post fields = ['author','title']

따라서 서버에서 자동으로 넣어주어야 합니다.

이를 위해 ReadOnlyField 를 사용하였습니다.

# serializers.py from rest_framework.serializers import ModelSerialzer, ReadOnlyField from .models import Post class PostSerializer(ModelSerializer): author_username = ReadOnlyField(source='author.username') class Meta: model = Post fields = ['author_username', 'title']

입력받을 때는 title 만 받게 되고, response 대해서는 author_username 도 표시를 해주게 됩니다.

from http://ssungkang.tistory.com/255 by ccl(A)

댓글

이 블로그의 인기 게시물

Django Rest Api 참고

Elasticsearch-dsl 삽질 복기(1)

스프링 프레임워크(Spring Framework)란?