{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">
<div class="container my-3">
<h2 class="border-bottom py-2">{{ question.subject }}</h2>
<div class="card my-3">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;">
{{ question.content }}
</div>
<div class="d-flex justify-content-end">
<div class="badge badge-light p-2">
{{ question.create_date }}
</div>
</div>
</div>
</div>
<h5 class="border-bottom my-3 py-2">
{{ question.answer_set.count }}개의 답변이 있습니다.
</h5>
{% for answer in question.answer_set.all %}
<div class="card my-3">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;">
{{ answer.content }}
</div>
<div class="d-flex justify-content-end">
<div class="badge badge-light p-2">
{{ answer.create_date }}
</div>
</div>
</div>
</div>
{% endfor %}
<form action="{% url 'pybo:answer_create' question.id %}" method="post" class="my-3">
{% csrf_token %}
<div clas="form-group">
<textarea name="content" id="content" rows="15" class="form-control"></textarea>
</div>
<input type="submit" value="답변 등록" class="btn btn-primary"/>
</form>
</div>
→ 화면 확인
{% load static %}
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shring-to-fit=no">
<link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'style.css' %}">
<title>Hello, pybo!</title>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
{% extends 'base.html' %}
{% block content %}
<div class="container my-3">
<table class="table">
<thead>
<tr class="thead-dark">
<th>번호</th>
<th>제목</th>
<th>작성일시</th>
</tr>
</thead>
<tbody>
{% if question_list %}
{% for question in question_list %}
<tr>
<td>{{ forloop.counter }}</td>
<td>
<a href="{% url 'pybo:detail' question.id %}">
{{ question.subject }}
</a>
</td>
<td>{{ question.create_date }}</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="3">질문이 없습니다.</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
{% endblock %}
{% extends 'base.html' %}
{% block content %}
<div class="container my-3">
<h2 class="border-bottom py-2">{{ question.subject }}</h2>
<div class="card my-3">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;">
{{ question.content }}
</div>
<div class="d-flex justify-content-end">
<div class="badge badge-light p-2">
{{ question.create_date }}
</div>
</div>
</div>
</div>
<h5 class="border-bottom my-3 py-2">
{{ question.answer_set.count }}개의 답변이 있습니다.
</h5>
{% for answer in question.answer_set.all %}
<div class="card my-3">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;">
{{ answer.content }}
</div>
<div class="d-flex justify-content-end">
<div class="badge badge-light p-2">
{{ answer.create_date }}
</div>
</div>
</div>
</div>
{% endfor %}
<form action="{% url 'pybo:answer_create' question.id %}" method="post" class="my-3">
{% csrf_token %}
<div clas="form-group">
<textarea name="content" id="content" rows="15" class="form-control"></textarea>
</div>
<input type="submit" value="답변 등록" class="btn btn-primary"/>
</form>
</div>
{% endblock %}
{% extends 'base.html' %}
{% block content %}
<div class="container my-3">
<table class="table">
<thead>
<tr class="thead-dark">
<th>번호</th>
<th>제목</th>
<th>작성일시</th>
</tr>
</thead>
<tbody>
{% if question_list %}
{% for question in question_list %}
<tr>
<td>{{ forloop.counter }}</td>
<td>
<a href="{% url 'pybo:detail' question.id %}">
{{ question.subject }}
</a>
</td>
<td>{{ question.create_date }}</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="3">질문이 없습니다.</td>
</tr>
{% endif %}
</tbody>
</table>
<!-- 질문 등록 버튼 추가 -->
<a href="{% url 'pybo:question_create' %}" class="btn btn-primary">
질문 등록하기
</a>
</div>
{% endblock %}
from django.urls import path
from . import views
app_name = 'pybo'
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name="detail"),
path('answer/create/<int:question_id>', views.answer_create, name='answer_create'),
path('question/create/', views.question_create, name='question_create'),
]
from django.shortcuts import render, get_object_or_404, redirect
from .models import Question
from .forms import QuestionForm
from django.utils import timezone
def index(request):
question_list = Question.objects.order_by('-create_date')
context = { 'question_list': question_list }
return render(request, 'pybo/question_list.html', context)
def detail(request, question_id):
# question = Question.objects.get(id=question_id)
question = get_object_or_404(Question, pk=question_id)
context = { 'question': question }
return render(request, 'pybo/question_detail.html', context)
def answer_create(request, question_id):
question = get_object_or_404(Question, pk=question_id)
question.answer_set.create(content=request.POST.get('content'), create_date=timezone.now())
return redirect('pybo:detail', question_id=question.id)
def question_create(request):
form = QuestionForm()
return render(request, 'pybo/question_form.html', { 'form': form })
from django import forms
from pybo.models import Question
class QuestionForm(forms.ModelForm): # ModelForm을 상속받아 모델 폼을 정의
class Meta: # 모델 폼은 Meta라는 내부 클래스를 정의해야 함
model = Question # 모델 폼에서 사용할 모델과 모델의 필드 지정
fields = ['subject', 'content']
{% extends 'base.html' %}
{% block content %}
<div class="container">
<h5 class="my-3 border-bottom pb-2">질문 등록</h5>
<form method="post" class="post-form my-3">
{% csrf_token %}
# 모델 폼과 연결된 입력 항목(Meta 클래스의 field로 지정된 항목)에
값을 입력할 수 있는 코드 자동 생성
{{ form.as_p }}
<button type="submit" class="btn btn-primary">저장하기</button>
</form>
</div>
{% endblock %}
→ 질문 등록 화면 확인
from django.shortcuts import render, get_object_or_404, redirect
from .models import Question
from .forms import QuestionForm
from django.utils import timezone
def index(request):
question_list = Question.objects.order_by('-create_date')
context = { 'question_list': question_list }
return render(request, 'pybo/question_list.html', context)
def detail(request, question_id):
# question = Question.objects.get(id=question_id)
question = get_object_or_404(Question, pk=question_id)
context = { 'question': question }
return render(request, 'pybo/question_detail.html', context)
def answer_create(request, question_id):
question = get_object_or_404(Question, pk=question_id)
question.answer_set.create(content=request.POST.get('content'), create_date=timezone.now())
return redirect('pybo:detail', question_id=question.id)
def question_create(request):
if request.method == 'GET':
form = QuestionForm()
return render(request, 'pybo/question_form.html', { 'form': form })
elif request.method == 'POST':
# POST 방식으로 전달된 요청인 경우, 요청 본문을 통해 전달된 입력값을 저장
form = QuestionForm(request.POST)
if form.is_valid():
question = form.save(commit=False)
question.create_date = timezone.now()
question.save()
return redirect('pybo:index')
→ 질문 등록 동작 확인
from django import forms
from pybo.models import Question
class QuestionForm(forms.ModelForm):
class Meta:
model = Question
fields = ['subject', 'content']
widgets = {
# <input type="text" name="subject" maxlength="200" required id="id_subject" class="form-control">
'subject': forms.TextInput(attrs={'class': 'form-control'}),
# <textarea name="content" cols="40" rows="10" required id="id_content" class="form-control">
'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 10}),
}
labels = {
'subject': '제목',
'content': '내용',
}
SW 개발보안 가이드
→ https://www.kisa.or.kr/2060204/form?postSeq=5&lang_type=KO&page=1
SW 보안약점 진단 가이드
→ https://www.kisa.or.kr/2060204/form?postSeq=9&page=2
Python 시큐어코딩 가이드
→ https://www.kisa.or.kr/2060204/form?postSeq=13&page=1
JavaScript 시큐어코딩 가이드
→ https://www.kisa.or.kr/2060204/form?postSeq=14&page=1
모바일 전자정부서비스 앱 소스코드 검증 가이드라인
→ https://www.kisa.or.kr/2060204/form?postSeq=3&page=2
주요정보통신기반시설 기술적 취약점 분석 평가 상세 가이드
→ https://www.kisa.or.kr/2060204/form?postSeq=12&page=2
공개SW를 활용한 소프트웨어 개발보안 검증가이드
→ https://www.kisa.or.kr/2060204/form?postSeq=10&page=3
https://drive.google.com/file/d/1qjAzKSw9sEw6yOaZn1WTD5VR467cl33R/view?usp=sharing
→ WebGoat
https://cdimage.kali.org/kali-2023.4/kali-linux-2023.4-vmware-amd64.7z
→ Kali Linux
https://sourceforge.net/projects/bwapp/files/bee-box/bee-box_v1.6.7z/download
→ bWAPP 가상머신 이미지
https://www.vmware.com/go/getworkstation-win
→ Vmware workstation
가상머신 로그인 계정
: Kali Linux → kali / kali
bee-box 가상머신에서 브라우저를 실행하고 http://localhost/bWAPP로 접속
: bee-box → bee / bug
┌──(kali㉿kali)-[~]
└─$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.37.128 netmask 255.255.255.0 broadcast 192.168.40.255
inet6 fe80::403d:144a:a460:bd5b prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:ca:54:d8 txqueuelen 1000 (Ethernet)
RX packets 103 bytes 16804 (16.4 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 40 bytes 11528 (11.2 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 4 bytes 240 (240.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 4 bytes 240 (240.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
bee@bee-box:~$ ifconfig
eth0 Link encap:Ethernet HWaddr 00:0c:29:3e:ba:70
inet addr:192.168.45.3 Bcast:192.168.40.255 Mask:255.255.255.0
inet6 addr: fe80::20c:29ff:fe3e:ba70/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:5682 errors:0 dropped:0 overruns:0 frame:0
TX packets:2263 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:582181 (568.5 KB) TX bytes:2167508 (2.0 MB)
Interrupt:16 Base address:0x2024
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:1028 errors:0 dropped:0 overruns:0 frame:0
TX packets:1028 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:726401 (709.3 KB) TX bytes:726401 (709.3 KB)
C:\Users\r2com> ipconfig
Windows IP 구성
이더넷 어댑터 이더넷: # 유선 네트워크를 사용하고 있는 경우
미디어 상태 . . . . . . . . : 미디어 연결 끊김
연결별 DNS 접미사. . . . :
무선 LAN 어댑터 로컬 영역 연결* 9:
미디어 상태 . . . . . . . . : 미디어 연결 끊김
연결별 DNS 접미사. . . . :
무선 LAN 어댑터 로컬 영역 연결* 10:
미디어 상태 . . . . . . . . : 미디어 연결 끊김
연결별 DNS 접미사. . . . :
무선 LAN 어댑터 Wi-Fi:
연결별 DNS 접미사. . . . :
링크-로컬 IPv6 주소 . . . . : fe80::622c:5a77:1e68:babd%13
# IP가 할당된 무선 LAN 어댑터 확인
IPv4 주소 . . . . . . . . . : 192.168.45.129
서브넷 마스크 . . . . . . . : 255.255.255.0
기본 게이트웨이 . . . . . . : 192.168.0.1
이더넷 어댑터 VMware Network Adapter VMnet1:
연결별 DNS 접미사. . . . :
링크-로컬 IPv6 주소 . . . . : fe80::123a:7830:2bb5:3e6e%43
IPv4 주소 . . . . . . . . . : 192.168.65.1
서브넷 마스크 . . . . . . . : 255.255.255.0
기본 게이트웨이 . . . . . . :
이더넷 어댑터 VMware Network Adapter VMnet8:
연결별 DNS 접미사. . . . :
링크-로컬 IPv6 주소 . . . . : fe80::a201:eee3:eca8:5fd7%44
IPv4 주소 . . . . . . . . . : 192.168.40.1
서브넷 마스크 . . . . . . . : 255.255.255.0
기본 게이트웨이 . . . . . . :
이더넷 어댑터 Bluetooth 네트워크 연결:
미디어 상태 . . . . . . . . : 미디어 연결 끊김
연결별 DNS 접미사. . . . :
C:\Users\r2com>
hosts 파일에 등록할 내용을 작성
→ 앞에서 확인한 본인 환경의 IP 주소를 사용
kali.linux
: 192.168.37.128
bee.box
: 192.168.45.3
host.pc
: 192.168.45.129
내 PC의 hosts 파일에 4번 내용을 등록 후 저장
→ 관리자 권한으로 명령 프롬프트 실행
C:\Windows\system32> notepad c:\Windows\System32\drivers\etc\hosts
bee@bee-box:~$ sudo gedit /etc/hosts
[sudo] password for bee: bug
┌──(kali㉿kali)-[~]
└─$ sudo apt update
┌──(kali㉿kali)-[~]
└─$ sudo apt install gedit -y
┌──(kali㉿kali)-[~]
└─$ sudo gedit /etc/hosts
- web 서버는 이미지나 css, js, html 등의 정적(static) 컨텐츠 제공
- was는 사용자로의 요청을 받아 서버에 프로그램을 실행하여 동적인 컨텐츠를 생성하고 제공해주는 역할
→ web 서버와 was 서버는 같이 동작하는 경우가 많음
→ 처리할 파일이 많아질 경우 웹 서버 하나로는 감당이 안되기 때문에 WAS를 이용해 복잡하거나 많은 로직을 처리
방화벽(F/W)
: 특정 IP나 port가 들어오거나 나가는 것을 제어
웹 어플리케이션 방화벽(WAF)
: 들어온 매개변수들의 패턴을 추측하여 보안 관련 공격 방어
: 요청 헤더나 요청 본문에 있는 문자열을 검사해 해로운 것이 있다면 그것을 막는 역할
→ F/W는 데이터까지는 보지 못하지만 WAF는 볼 수 있음
※ 방화벽과 WAF는 하드웨어를 기반으로 네트워크를 방어하므로 들어오는 공격들을 100% 막기엔 어려움
※ 침해사고의 대부분은 잘못 만들어진 소스코드에 의해 발생하는 정보 유출 등의 경우가 대다수
시큐어 코딩
: 소스 코드에 잠재된 보안 약점을 제거
: 현재는 문제가 없더라도 나중에 시간이 지나서 문제가 생길 가능성 다분
→ 코드를 안전하게 하기 위한 기법을 '시큐어 코딩'이라고 함
개발 보안 방법론
: SW 개발 과정에서 실행되는 일련의 보안 사이클을 수행해야 함
요구사항 분석
: 사용자가 원하는 기능을 도출
: 개발 과정에선 기능 요구사항을 많이 따졌었지만 보안 개발 과정에선 보안 요구사항이 어떻게 만들어져야 하는가에 대해 기술
→ ex. 패스워드는 암호화해서 저장, 패스워드는 일방향 해시 함수를 통해 저장 등
설계
: 위험사항이 뭐가 있는지?
: 그에 관련된 외부 위협 사항이 뭐가 있는지?
: 각종 위험 요소들과 관련하여 보안에 대해 고려해야하는 것들을 작성
구현
: 표준에 맞춰 코딩
: 교육받은 대로 코딩을 하는지 모니터링도 함
: 도구를 활용해 보안 약점을 진단하는 방법도 있음
→ 정적분석도구
테스트
: 해당 단계에서 문제점이 발견되면 구현 단계, 잘못하면 설계 단계까지 돌아가 다시 해야될 수도 있음
: 코드의 보안 취약점에 대해 진단하고 그를 테스트
- 애플리케이션의 문제점을 도출
→ 정적 분석과 동적 분석은 상호 보완적 관계로 항상 함께 적용해야 함
- 정적 분석
: 애플리케이션을 실행하지 않고 소스 코드의 내용, 형태, 구문 등을 분석해서 문제가 있는지 확인
↓
코드 리뷰
↓
SQL Injection 과 같이 소스 코드 구문을 통해서 확인할 수 있는 문제점을 쉽고, 명확하게 찾을 수 있음
: ex) cursor.execute(query, (param1, param2, ...))
→ SQL Injection에 안전한 코드
: ex) cursor.execute(f"select ... where id = {name}")
→ SQL Injection에 취약한 코드
- 동적 분석
: 애플리케이션을 실행해서 원하는(계획한) 결과가 제공되는지 확인
↓
디버깅, 부하테스트, 모의해킹/침투테스트
↓
인증 및 인가와 같이 정적 분석으로 파악이 힘든 경우 수행
: ex) 게시판 상세 조회 페이지는 로그인한 사용자만 볼 수 있는지 로그인 전후에 게시판 상세 조회를 해 보면 쉽게 알 수 있음
입력 데이터 검증 및 표현
: 가장 많음
: 가장 기본
: 문법, 데이터에서 잘못된 것을 찾아낼 수 있는 경우가 많음
보안 기능
: 보안과 관련해 잘못 구현했을 때 발생할 수 있음
: 암호화, 인가, 인증 등
시간 및 상태
: 동시성과 관련
: 멀티 프로세스와 관련해 발생할 수 있는 문제들
에러 처리
: 오류가 발생할 수 있음에도 불구하고 예외 처리를 하지 않았을 경우
: 예외를 두리뭉실하게 처리했을 경우
: 메시지를 통해 외부로 유출되는 경우
코드 오류
: 코딩 오류로 인해 발생할 수 있는 보안 약점
캡슐화
: 데이터를 숨기는 것, 포괄적으론 보호해야하는 데이터를 외부에서 알지 못하도록 하는 것
: 캡슐화가 잘못되어 외부에 내용이 노출되었을 때 이를 캡슐화 오류라고 함
API 오용
: 사용하면 안되는 API를 사용했거나 잘못 사용한 경우 발생할 수 있는 에러
: HTTP is simple
: HTTP is extensible
: HTTP is stateless(무상태), but not sessionless
→ 성공적으로 완료된 두 개의 요청 사이에는 연결고리 X
→ 헤더 확장성을 사용하여 동일한 컨텍스트, 동일한 상태를 공유하기 위해 각각의 요청들에 세션을 만들도록 쿠키 추가
: HTTP and connections
→ HTTP/1.0
: 요청/응답 교환에 대한 각각의 TCP 연결 생성
→ HTTP/1.1
: 파이프 라이닝 개념과 지속적인 연결의 개념 도입(기본적인 TCP 연결을 Connection 헤더를 사용해 부분적으로 제어)
→ HTTP/2
: 단일 연결을 통해 메시지 다중화, 연결을 보다 효율적으로 유지
연결 → 요청 → 응답 → 연결해제