합병 정렬(Merge sort)이란?

  • 합병정렬(merge sort)은 정렬 알고리즘의 한 종류로, 폰 노이만이 개발한 알고리즘이다.
  • 이 정렬은 안정 정렬에 속하며, 분할 정복(divide and conquer) 알고리즘의 하나이다.

 

합병 정렬(Merge sort) 아이디어

https://en.wikipedia.org/wiki/Merge_sort

합병(Merge)라는 이름에서 알 수 있듯, 합병 정렬의 기본 아이디어는 정렬할 배열을 분할하여, 작은 조각으로 나눈 후 더 이상 나눌 수 없을 때(하나의 원소만 있을때) 다른 조각과 비교하며 합쳐 정렬하는 알고리즘이다.

과정

  1.  정렬되지 않은 리스트를 2개의 리스트로 나눈다.
    • 각 리스트의 길이가 1이 될때까지 반복한다(길이가 1인 리스트는 정렬된 것으로 간주한다.)
  2. 2개의 리스트의 값들을 처음부터 하나씩 비교하여 두 개의 리스트의 값 중에서 더 작은 값을 새로운 리스트로 옮긴다.
    • 둘중 한 리스트가 끝날 때까지 이를 반복한다.
    • 한 리스트가 끝나면, 나머지 값들은 새 리스트에 복사해서 넣어준다(이미 정렬되어 있으므로)
  3. 2번 과정을 하나의 리스트가 될때까지 반복한다

 

합병 정렬(Merge sort) 증명

Merge 과정은 문제없이 기능한다 가정

귀납적 증명(Proof by Induction)

1. Base Case (기본 단계):

배열의 크기가 1 또는 0인 경우

  • 배열의 크기가 0 또는 1이면, 이미 정렬된 상태이다.

2. Inductive Hypothesis (귀납 가정):

배열의 크기가 n 이하인 모든 배열에 대해 Merge sort가 올바르게 작동한다고 가정.


3. Inductive Step (귀납적 단계):

배열의 크기가 n+1인 배열에 대해서 Merge sort가 올바르게 작동함을 증명.

  • 크기가 n+1인 배열 A를 반으로 나누어 두 개의 하위 배열 L과 R로 나눈다고 가정하자. 각 하위 배열의 크기는 대략 n/2 또는 그와 비슷하다.
  • 귀납 가정에 따라, 배열 L과 배열 R에 대해 각각 Merge sort를 호출하면, L과 R은 각각 오름차순으로 정렬된다.
  • Merge 과정이 끝나면, L과 R의 모든 요소가 오름차순으로 정렬된 하나의 배열로 합쳐지고. 이 배열은 오름차순으로 정렬된 상태이다.

따라서 배열의 크기가 n+1일 때도 Merge sort는 올바르게 작동한다.

 

합병 정렬(Merge sort) 시간복잡도

$T(n)=2T(\frac{n}{2})+n$ (이때 n은 merge시 걸리는 시간)

$\ \ \ \ \ \ \ \ =2(2T(\frac{n}{4})+\frac{n}{2})+n=4T(\frac{n}{4})+2n$

$\ \ \ \ \ \ \ \ =4(2T(\frac{n}{8})+\frac{n}{4})+2n=8T(\frac{n}{8})+3n$

$\ \ \ \ \ \ \ \ ...$

$\ \ \ \ \ \ \ \ =nT(1)+log(n)*n$

$\ \ \ \ \ \ \ \ =n*log(n)$

 

합병 정렬(Merge sort) 구현

#include <iostream>
using namespace std;

// 두 개의 하위 배열을 병합하는 함수
void merge(int arr[], int left, int mid, int right) {
    int n1 = mid - left + 1;  // 왼쪽 하위 배열의 크기
    int n2 = right - mid;     // 오른쪽 하위 배열의 크기

    // 임시 배열 생성
    int L[n1], R[n2];

    // 원본 배열의 값을 각각 왼쪽과 오른쪽 임시 배열로 복사
    for (int i = 0; i < n1; i++)
        L[i] = arr[left + i];
    for (int j = 0; j < n2; j++)
        R[j] = arr[mid + 1 + j];

    // 두 배열을 병합하여 정렬된 상태로 원래 배열에 다시 삽입
    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            arr[k] = L[i];
            i++;
        } else {
            arr[k] = R[j];
            j++;
        }
        k++;
    }

    // 왼쪽 배열의 남은 요소 복사
    while (i < n1) {
        arr[k] = L[i];
        i++;
        k++;
    }

    // 오른쪽 배열의 남은 요소 복사
    while (j < n2) {
        arr[k] = R[j];
        j++;
        k++;
    }
}

// Merge Sort 알고리즘
void mergeSort(int arr[], int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2;

        // 왼쪽과 오른쪽 배열에 대해 재귀적으로 정렬
        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);

        // 정렬된 두 배열을 병합
        merge(arr, left, mid, right);
    }
}

// 배열을 출력하는 함수
void printArray(int arr[], int size) {
    for (int i = 0; i < size; i++)
        cout << arr[i] << " ";
    cout << endl;
}

int main() {
    int arr[] = {12, 11, 13, 5, 6, 7};
    int arr_size = sizeof(arr) / sizeof(arr[0]);

    cout << "Given array is \n";
    printArray(arr, arr_size);

    mergeSort(arr, 0, arr_size - 1);

    cout << "\nSorted array is \n";
    printArray(arr, arr_size);
    return 0;
}

프림 알고리즘(Prim Algorithm)이란?

어떠한 그래프에서 최소신장트리(Minimum Spanning Tree)를 찾는 알고리즘이다.

그리디(Greedy) 알고리즘 중 하나이다.

 

프림 알고리즘(Prim Algorithm)의 아이디어

  • 트리중 하나의 노드에서 시작(아무 노드나 상관없음)
  • 우리가 만들어가고 있는 트리에 인접한 edge들 중 가장 작은 weight를 가진 edge를 추가
  • (단, 사이클을 만들지 않아야함)
  • spanning tree가 될때까지 반복 
    출처 : https://en.wikipedia.org/wiki/File:Prim-animation.gif

프림 알고리즘(Prim Algorithm)의 증명

정확성

G : 주어진 그래프(방향성X, 가중치O)

V : 전체 node의 집합

E : 전체 edge의 집합

Y : 방문한 node

F : 방문한 edge

 

G = (V,E)로 나타낼 수 있고, F  E, Y V이다.

G'(만들어가는중인 그래프) = (Y,F)

MST = (V,F') 이라고 정의하자.

 

가장 처음 F는 empty set이므로 F ⊆ F' 이 성립한다. 

 

1. G'에 인접한 edge들 중 가장 가중치가 낮은 edge를 e(v1 Y와 v2 V-Y를 잇는 edge)라고 하자

2. e가 F'의 원소일 경우, F U {e} 도 MST가 될 수 있는 상태이다.

3. e가 F'의 원소가 아닐 경우, F'은 spanning tree이므로 F' U {e}에는 하나의 사이클이 생길것이다.

    이때, F' U {e}에는 사이클에 속하지만 F에 속하지 않는 edge e'가 존재할 것이다.

3-1. F' U {e} - {e'}이 spanning tree가 된다면 e<=e'이므로, F' U {e} - {e'} 이 MST일 것이다.

3-2. F U {e} F' U {e} - {e'} 이므로 F U {e}는 항상 MST가 될 수 있는 상태이다.

 

이를 Y=V가 될 때까지 반복하여 MST를 만들 수 있다.

 

시간복잡도

E = edge, V = vertex

Minimum edge weight data structure Time complexity (total)
adjacency matrix, searching $O(|V|^2)$
binary heap and adjacency list $O((|V|+|E|)log|V|) = O(|E|log|V|)$
Fibonacci heap and adjacency list $O(|E|+|V|log|V|)$

출처 : https://en.wikipedia.org/wiki/Prim%27s_algorithm

구현(백준 1647번)

문제

동물원에서 막 탈출한 원숭이 한 마리가 세상구경을 하고 있다. 그러다가 평화로운 마을에 가게 되었는데, 그곳에서는 알 수 없는 일이 벌어지고 있었다.

마을은 N개의 집과 그 집들을 연결하는 M개의 길로 이루어져 있다. 길은 어느 방향으로든지 다닐 수 있는 편리한 길이다. 그리고 각 길마다 길을 유지하는데 드는 유지비가 있다. 임의의 두 집 사이에 경로가 항상 존재한다.

마을의 이장은 마을을 두 개의 분리된 마을로 분할할 계획을 가지고 있다. 마을이 너무 커서 혼자서는 관리할 수 없기 때문이다. 마을을 분할할 때는 각 분리된 마을 안에 집들이 서로 연결되도록 분할해야 한다. 각 분리된 마을 안에 있는 임의의 두 집 사이에 경로가 항상 존재해야 한다는 뜻이다. 마을에는 집이 하나 이상 있어야 한다.

그렇게 마을의 이장은 계획을 세우다가 마을 안에 길이 너무 많다는 생각을 하게 되었다. 일단 분리된 두 마을 사이에 있는 길들은 필요가 없으므로 없앨 수 있다. 그리고 각 분리된 마을 안에서도 임의의 두 집 사이에 경로가 항상 존재하게 하면서 길을 더 없앨 수 있다. 마을의 이장은 위 조건을 만족하도록 길들을 모두 없애고 나머지 길의 유지비의 합을 최소로 하고 싶다. 이것을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 집의 개수 N, 길의 개수 M이 주어진다. N은 2이상 100,000이하인 정수이고, M은 1이상 1,000,000이하인 정수이다. 그 다음 줄부터 M줄에 걸쳐 길의 정보가 A B C 세 개의 정수로 주어지는데 A번 집과 B번 집을 연결하는 길의 유지비가 C (1 ≤ C ≤ 1,000)라는 뜻이다.

임의의 두 집 사이에 경로가 항상 존재하는 입력만 주어진다.

출력

첫째 줄에 없애고 남은 길 유지비의 합의 최솟값을 출력한다.

예시

 

풀이

MST를 구하는 문제로, kruskal알고리즘 또는 Prim 알고리즘을 사용할 수 있다. 이 글에선 Prim알고리즘을 사용하여 문제를 풀어보았다.

입력받은 값들로 MST를 구한 후 가장 긴 edge를 제거하면 풀리는 문제이다.

#include <iostream>
#include <queue>
#include <vector>

using namespace std;

typedef struct edge {
    int vertex1, vertex2;
    int weight;

    edge() {
        vertex1 = 0;
        vertex2 = 0;
        weight = 0;
    }

    edge(int v1, int v2, int w) {
        vertex1 = v1;
        vertex2 = v2;
        weight = w;
    }

    bool operator < (const edge& e) const {
        return weight < e.weight;
    }
}edge;

int Prim(int* vertex, vector<edge> edges[], int v) {
    int total_weight = 0;
    priority_queue<edge> adj_edges;
    int cur_vertex = 0, max_edge=0;
    vertex[0] = 1;

    for (int j = 0; j < v - 1; j++) {
        for (int i = 0; i < edges[cur_vertex].size(); i++) {
            adj_edges.push(edges[cur_vertex][i]);
        }
        while (!adj_edges.empty() && (vertex[adj_edges.top().vertex1] == 1 && vertex[adj_edges.top().vertex2] == 1)) {
            adj_edges.pop();
        }
        edge e = adj_edges.top();
        adj_edges.pop();
        vertex[e.vertex2] = 1;
        cur_vertex = e.vertex2;
        total_weight += e.weight;
        if(max_edge>e.weight){
            max_edge = e.weight;
        }
    }
    // return MST; 이렇게 하고 싶지만 최대한 빠르게 하기 위해서..total_weight를 리턴하는 걸로 하자
    return  -(total_weight-max_edge);

}

int main()
{
    int v, e;
    int v1, v2, w;
    int total_weight = 0;

    scanf("%d %d", &v, &e);

    int* vertex = new int[v];
    fill(vertex, vertex + v, 0);
    vector<edge> *edges = new vector<edge> [v];

    for (int i = 0; i < e; i++) {
        scanf("%d %d %d", &v1, &v2, &w);
        edges[v1-1].push_back(edge(v1-1, v2-1, -w));
        edges[v2-1].push_back(edge(v2-1, v1-1, -w));
    }

    total_weight = Prim(vertex, edges, v);
    printf("%d\n", total_weight);
}

 

 

 

오류수정은 언제나 환영입니다^^

'CS > Algorithm' 카테고리의 다른 글

합병 정렬(Merge sort)  (0) 2024.10.12
그리디 알고리즘(Greedy Algorithm, 탐욕법)  (0) 2024.07.13

운영체제란?

출처 : https://ko.wikipedia.org/wiki/운영체제

운영체제란 하드웨어와 응용프로그램의 사이에서 작동하며 PC의 하드웨어, 시스템 리소스를 제어하여, 하드웨어가 문제없이 작동하고, 리소스를 효율적으로 사용할 수 있도록 하는 시스템 소프트웨어입니다.

 

우리가 흔히 아는 윈도우,MacOS,리눅스,유닉스 등등이 운영체제입니다.

 

운영체제의 역할

운영체제는 하드웨어와 응용 프로그램의 중간에서 인터페이스를 제공하고 응용 프로그램들이 효율적으로 관리되도록하는 관리자와 같은 역할을 합니다.

응용 프로그램이 직접 하드웨어에 접근해서 작동하게 되면, 프로그램을 만드는 것도 어려워질 뿐만 아니라 하드웨어의 정상적인 작동, 시스템 자원의 효율적인 활용, 보안 등에 심각한 문제가 생기기 때문에, 운영체제는 컴퓨터의 필수적인 요소 입니다.

프로그램 하나 실행했는데 CPU와 메모리를 전부 점유하고 안놔준다거나, 악성 프로그램이 PC를 마음대로 조종하게 되면 큰 문제가 되겠죠.

 

운영체제는 PC에서 아래와 같은 역할을 합니다.

 

  1. 프로세서, 메모리, 디스크, 입출력장치 간의 통신
  2. 시스템 자원 관리(스케줄링 등)
  3. 사용자와 시스템 간의 편리한 인터페이스 제공
  4. 시스템의 오류를 검사 및 복구

 

오류수정은 언제나 환영입니다^^

1. 그리디 알고리즘이란?

  • 최적의 값을 구해야 하는 상황에서 전체적인 상황을 상정하여 구하는 것이 아닌, 각 단계에서 최선의 선택을 하는 근시안적 방법론입니다.
  • 전체적인 상황을 상정하지 않기 때문에 항상 최적의 해를 구하는 것이 보장되어있지 않지만, 근사해를 구하는것이 가능합니다.

아래와 같은 트리에서 각 노드의 합의 최대값을 구하는 상황일때, 탐욕법을 이용하면 각 단계에서 가장 큰 값을 선택하게 되어 5->10->4와 같이 선택하게 됩니다.

최적의 해는 5->7->9이므로 탐욕법을 이용하는것이 항상 최적의 해를 구하는 것은 아님을 보여줍니다.

출처 https://velog.io/@kyunghwan1207/%EA%B7%B8%EB%A6%AC%EB%94%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98Greedy-Algorithm-%ED%83%90%EC%9A%95%EB%B2%95

2. 그리디 알고리즘의 특징

2.1 그리디 알고리즘의 조건

그리디 알고리즘이 근사값이 아닌 최적값을 보장하는 조건 2가지가 있습니다.

 

  • 탐욕 선택 속성 (Greedy Choice Property)
현재의 선택이 미래의 선택에 영향을 끼치지 않는다.
  • 최적 부분 구조 (Optimal Substructure)
부분의 최적해가 모여 전체의 최적해가 된다.

 

문제가 이 두가지의 조건을 만족한다면 그리디 알고리즘을 사용해 최적값을 구할 수 있습니다.

2.2 그리디 알고리즘의 단계

1. 선택절차(Selection Procedure)

현재 상태에서 최적인 선택을 한다. 이 선택은 바뀌지 않는다.

 

2. 적절성 검사(Feasibility Check)

선택한 항목이 문제의 조건을 만족시키는지 확인한다.

 

3. 해답 검사(Solution Check)

모든 선택이 완료되면, 최종 선택이 문제의 조건을 만족시키는지 확인한다.

 

Reference

 

'CS > Algorithm' 카테고리의 다른 글

합병 정렬(Merge sort)  (0) 2024.10.12
프림 알고리즘(Prim Algorithm) 구현과 증명  (0) 2024.09.17

1. 디자인 패턴이란?

객체 지향 프로그래밍 설계를 할 때 자주 발생하는 문제들을 피하기 위해 사용되는 패턴. 개발을 하며 생길 수 있는 반복적인 문제들을 패턴을 통해 설계를 함으로써 미리 예방을 할 수 있게 됩니다..

디자인 패턴을 이용하면 유지보수성이 좋고 이해가 쉬운 코드를 짤 수 있게 됩니다.

 

2. MVC패턴이란?

출처 https://ko.wikipedia.org/wiki/%EB%AA%A8%EB%8D%B8-%EB%B7%B0-%EC%BB%A8%ED%8A%B8%EB%A1%A4%EB%9F%AC

MVC패턴은 데이터를 가지고 있는 Model, 사용자 인터페이스 요소를 가지고 있는 View, View와 Model을 이어주는  Controller의 3가지 요소로 나누어집니다.

이와 같이 3가지 요소로 나누어 사용자 인터페이스와 비즈니스 로직을 분리하여 코드의 의존성을 낮추고 유지보수성을 높이는 효과를 얻게 됩니다.

 

MVC의 작동은 다음과 같이 진행됩니다.

  1. 사용자의 Request(요청)를 Controller가 받는다.
  2. Controller는 비즈니스 로직을 처리한 후 결과를 Model에 담는다.
  3. Model에 저장된 결과를 바탕으로 시각적 요소 출력을 담당하는 View를 제어하여 사용자에게 전달한다.

2.1 Model 모델

Model(모델)은 필요한 데이터에 대해서 Controller의 제어에 따라, DB에게 query를 하여 데이터를 저장, 삭제, 업데이트등을 진행하게 됩니다. View는 이 Model의 데이터를 기반으로 사용자 화면을 구성하게 됩니다.

 

Model의 상태에 변화가 있을 때 컨트롤러와 뷰에 이를 통보합니다. 이와 같은 통보를 통해서 뷰는 최신의 결과를 보여줄 수 있고, 컨트롤러는 모델의 변화에 따른 적용 가능한 명령을 추가·제거·수정할 수 있습니다. 어떤 MVC 구현에서는 통보 대신 뷰나 컨트롤러가 직접 모델의 상태를 읽어오기도 합니다.

 

모델의 규칙

  • 사용자가 편집하길 원하는 모든 데이터를 가지고 있어야 한다.
  • View나 Controller에 대해서 어떤 정보도 알지 말아야 한다.
  • 변경이 일어나면, 변경 통지에 대한 처리방법을 구현해야만 한다.

 

2.2 View 뷰

View(뷰)는 사용자가 직접 보게되는 화면, 사용자 인터페이스를 구성하는 역할을 합니다.

뷰는 컨트롤러를 통해 받은 모델의 값을 통해 화면을 구성하게 됩니다.

 

뷰의 규칙

  • Model이 가지고 있는 정보를 따로 저장해서는 안된다.
  • Model이나 Controller를 알고 있을 필요가 없다.
  • 변경이 일어나면 변경통지에 대한 처리방법을 구현해야만 한다.

 

2.3 Controller 컨트롤러

Controller(컨트롤러)는 뷰와 모델의 사이를 잇는 인터페이스의 역할을 합니다.

모델이 데이터를 어떻게 처리할 것인지를 알려주고, 처리된 모델을 뷰에게 넘겨주게 됩니다.

모델이나 뷰로부터 변경 내용을 통지 받으면 이를 각 구성 요소에게 통지해야 합니다. 사용자가 어플리케이션을 조작하여 발생하는 변경 이벤트들을 처리하는 역할을 수행합니다.

 

컨트롤러의 규칙

  • 모델이나 뷰에 대해서 알고 있어야 한다.
  • 모델이나 뷰의 변경을 모니터링 해야 한다.

 

 

3. MVC 패턴의 장점

  • MVC패턴의 각 컴포넌트는 자신의 역할을 한 후 값을 넘겨주기만 하면 되기 때문에, 시스템 결합도가 낮아집니다.
  • 각 코드가 여러파일에 나누어져 있기 때문에 코드의 가독성이 높아집니다.
  • 유지보수 시 특정 컴포넌트만 수정하면 되기 때문에 유지보수가 쉬워집니다.

 

4. MVC 패턴의 한계

Massive View Controller

대규모 시스템의 경우 컨트롤러가 다수의 모델과 뷰를 컨트롤하여, 컨트롤러의 부담이 커지는 문제가 생기게 됩니다.

또한 다수의 뷰에 다수의 모델이 연결되는 경우가 생겨 서로간의 의존성이 커지는 경우가 발생할 수 있습니다.

 

이를 해결하기 위해 아래와 같은 패턴들이 있습니다.

  • MVVM
  • MVP
  • MVW
  • Flux
  • Redux
  • RxMVVM

 

Reference

더보기

'Dev > Web' 카테고리의 다른 글

주식 전망 서비스 RAG-1  (0) 2025.11.10
REST와 RESTful API  (0) 2025.02.06

1. SQL injection이란

2. SQL injection의 종류

3. 대응방법

1. SQL injection이란

출처:안랩

SQL injection은 Database의 취약점을 이용하여 정상적이지 않은 동작을 하도록 하는 SQL문을 입력하여 공격자가 원하는 작업을 수행하거나 정보를 유출하게되는 공격입니다.

 

SQL injection은 최근까지도 많이 일어나고 있는 공격기법 중 하나입니다.

 

2. SQL injection의 종류

2.1 Normal SQL injection

출처:안랩

Normal SQL injection은 논리적 에러를 이용한 SQL injection으로 위의 로그인 예시에서 ' OR 1=1 --와 같이 값을 입력해주게 되면 OR 1=1에 의해 항상 참, --에 의해 뒷 부분은 주석처리가 되며 로그인에 성공하게 됩니다.

 

2.2 Error based SQL injection

https://www.bugbountyclub.com/pentestgym/view/53

Error based SQL injection은 주로 데이터베이스에 대한 정보를 획득하기 위해 사용됩니다. SQL의 잘못된 문법이나 자료형 불일치 등에 의해 데이터베이스가 알려주는 데이터베이스 오류 메시지에 의존하여 수행되는 공격 기법입니다.

 

2.3 Union based SQL injection

출처 : 안랩

SQL의 Union이라는 구문을 사용하는 공격인 Union based SQL injection은 Union을 이용하여 원래의 요청에 하나의 추가 쿼리를 삽입하는 공격입니다.

이 때 필요한 전제조건으로 Union하는 두 쿼리의 컬럼의 개수와 데이터 형식이 같아야합니다.

 

위의 사진의 경우 게시글을 찾는 쿼리문에 Union을 넣어 Users 테이블의 id, 패스워드를 읽어오는 공격입니다.

 

2.4 Blind SQL injection - Boolean based SQL

출처 : 안랩

 

위의 사진은 MySQL 에서 테이블 명을 조회하는 구문으로 limit 키워드를 통해 하나의 테이블만 조회하고, SUBSTR 함수로 첫 글자만, 그리고 마지막으로 ASCII 를 통해서 ascii 값으로 변환해줍니다

만약에 조회되는 테이블 명이 Users 라면 ‘U’ 자가 ascii 값으로 조회가 될 것이고, 뒤의 100 이라는 숫자 값과 비교를 하게 됩니다.

U의 ascii 값은 125이므로 뒤의 100의 값을 변경해가면 125일때 거짓이 나오므로 첫 글자가 U라는것을 알 수 있습니다.

이를 자동화하여 테이블명을 알아내는 방법입니다.

 

2.5 Blind SQL injection - Time based SQL

출처 : 안랩

위의 그림은 Time based SQL Injection을 사용하여 현재 사용하고 있는 데이터베이스의 길이를 알아내는 방법입니다. 로그인 폼에 주입이 되었으며 임의로 abc123 이라는 계정을 생성해 두었습니다.

악의적인 사용자가 abc123’ OR (LENGTH(DATABASE())=1 AND SLEEP(2)) -- 이라는 구문을 주입하였습니다. 여기서 LENGTH 함수는 문자열의 길이를 반환하고, DATABASE 함수는 데이터베이스의 이름을 반환합니다.

주입된 구문에서, LENGTH(DATABASE()) = 1 가 참이면 SLEEP(2) 가 동작하고, 거짓이면 동작하지 않습니다.

 

이를 통해서 숫자 1 부분을 조작하여 데이터베이스의 길이를 알아 낼 수 있습니다. 만약에 SLEEP 이라는 단어가 치환처리 되어있다면, 또 다른 방법으로 BENCHMARK  WAIT 함수를 사용 할 수 있습니다.

 

3. 대응방법

입력 값에 대한 검증

사용자의 입력이 올바른 값인지 검증합니다. 사용자가 공격할 가능성이 있는 키워드들을 검증하고 차단합니다.

 

 

Prepared Statement 구문사용

 Prepared Statement 구문을 사용하게 되면, 사용자의 입력 값이 데이터베이스의 파라미터로 들어가기 전에DBMS가 미리 컴파일 하여 실행하지 않고 대기합니다. 그 후 사용자의 입력 값을 문자열로 인식하게 하여 공격쿼리가 들어간다고 하더라도, 사용자의 입력은 이미 의미 없는 단순 문자열 이기 때문에 전체 쿼리문도 공격자의 의도대로 작동하지 않습니다.

 

 

Error Message 노출 금지

공격자가 SQL Injection을 수행하기 위해서는 데이터베이스의 정보(테이블명, 컬럼명 등)가 필요합니다. 데이터베이스 에러 발생 시 따로 처리를 해주지 않았다면, 에러가 발생한 쿼리문과 함께 에러에 관한 내용을 반환해 줍니다. 여기서 테이블명 및 컬럼명 그리고 쿼리문이 노출이 될 수 있기 때문에, 데이터 베이스에 대한 오류발생 시 사용자에게 보여줄 수 있는 페이지를 제작 혹은 메시지박스를 띄우도록 하여야 합니다.

 

웹 방화벽 사용

웹 공격 방어에 특화되어있는 웹 방화벽을 사용하는 것도 하나의 방법입니다. 웹 방화벽은 소프트웨어 형, 하드웨어 형, 프록시 형 이렇게 세가지 종류로 나눌 수 있는데 소프트웨어 형은 서버 내에 직접 설치하는 방법이고, 하드웨어 형은 네트워크 상에서 서버 앞 단에 직접 하드웨어 장비로 구성하는 것이며 마지막으로 프록시 형은 DNS 서버 주소를 웹 방화벽으로 바꾸고 서버로 가는 트래픽이 웹 방화벽을 먼저 거치도록 하는 방법입니다.

'Security > Webhacking' 카테고리의 다른 글

CSRF 공격  (0) 2024.05.12
XSS문제 실습  (0) 2024.05.10
Cookie & Session  (0) 2024.05.08
  1. CSRF란
  2. CSRF의 원리
  3. CSRF 방어 방법

1. CSRF란

CSRF는 공격자가 만든 악성 스크립트가 포함된 URL을 사용자가 실행하게 되면, 사용자의 세션을 이용하여 사용자가 의도하지 않은 작업을 수행하는 공격입니다.

대표적으로 패스워드 변경, 송금 등이 있습니다.

 

2. CSRF의 원리

CSRF 공격이 성립하기 위해 충족되야하는 조건이 있습니다.

1. 사용자는 보안이 취약한 서버로부터 이미 로그인되어 있는 상태여야 합니다.
2. 쿠키 기반의 서버 세션 정보를 획득할 수 있어야 합니다.
3. 공격자는 서버를 공격하기 위한 요청 방법에 대해 미리 파악하고 있어야 합니다.

 

위 조건이 만족되었을 때  CSRF공격이 가능하게 됩니다.

 

CSRF공격의 시나리오는 다음과 같습니다. 

1. 사용자가 보안이 취약한 서버에 로그인되어있다.

2. 공격자의 악성 스크립트가 포함된 URL을 사용자가 접속하게된다.

3. 악성 스크립트에 담겨있는 내용이 사용자 세션 ID를 이용해 실행되게된다.

4. 서버는 해당 내용을 사용자가 요청한것으로 인식하고 실행하게 된다.

3. CSRF 방어방법

1. Referrer 검증

Request header에 포함된 Referer는 현재 요청된 페이지의 링크 이전의 웹페이지 주소를 포함합니다.

Referrer를 검증해서 해당 요청이 요청될 수 있는 위치인지 검사를 하면 CSRF를 막을 수 있습니다.

 

2. CSRF 토큰 검증

로그인을 할 때 CSRF 토큰을 생성하여 세션에 저장해 둡니다.

CSRF 토큰을 페이지 요청태그의 hidden값에 넣어줍니다.

Ex) <input type="hidden" name="_csrf" value="${CSRF_TOKEN}" />

이후 요청을 할 때마다 CSRF 토큰을 같이 보내어, 서버에서 토큰을 검증합니다.

'Security > Webhacking' 카테고리의 다른 글

SQL injection 공격  (0) 2024.05.12
XSS문제 실습  (0) 2024.05.10
Cookie & Session  (0) 2024.05.08

XSS(Cross-Site-Scripting)이란


사이트 간 스크립팅, 크로스 사이트 스크립팅(영어: Cross-site scripting XSS)은 웹 애플리케이션에서 많이 나타나는 취약점의 하나로 웹사이트 관리자가 아닌 이가 웹 페이지에 악성 스크립트를 삽입할 수 있는 취약점이다. 이름이 CSS가 아닌 이유는 웹 기술인 CSS와 헷갈릴 수 있어서다. -Wikipedia-

 

 

XSS공격은 위에서 말한것과 같이 관리자가 아닌 사용자가 웹 페이지에 스크립트를 삽입할 수 있는 취약점을 말한다.

 

스크립트를 삽입함으로써 웹 페이지가 공격자가 원하는 임의의 행동을 수행시킬 수 있다. 공격자가 XSS를 이용하여 쿠키를 탈취하게 된다면 세션하이재킹(Session Hijacking) 공격등으로도 이어질 수 있다.

 

XSS는 이름에서 알 수 있듯 자바스크립트를 이용한다.

XSS의 종류와 기법


Stored XSS

Stored XSS, 출처:안랩

 

Stored XSS는 웹 서버에 악성 스크립트를 심어놓고 사용자가 해당 페이지에 접근 할 때마다 스크립트가 실행되는 방식의 공격기법이다.

Stored XSS공격은 보통 악성 스크립트를 게시글 등을 이용하여 사용자가 해당 게시글에 접근하면 스크립트가 작동하는 방식으로 진행이 된다.

 

악성 스크립트가 저장된 게시글 등을 열람한 사용자들은 악성 스크립트가 작동함에 따라 쿠키나 세션 등의 민감 정보를 탈취당하거나 다른 웹사이트로 리다이렉션될 수도 있다.

 

Stored XSS 공격 시나리오

  1. 공격자가 XSS 취약점이 있는 사이트를 발견
  2. 보안이 취약한 사이트에서 제공하는 게시판에 사용자 정보를 빼돌릴 수 있는 스크립트를 작성하여 업로드
  3. 일반 사용자가 공격자가 작성한 게시글을 읽으면, 서버로부터 악성 스크립트가 담긴 게시글 응답을 받음
  4. 일반 사용자의 브라우저에서 응답 메시지를 실행하면서 악성 스크립트 실행
  5. 악성 스크립트를 통해 사용자 정보가 공격자에게 전달됨

 

 

Reflected XSS

Reflected XSS 출처:안랩

Reflected XSS는 스크립트가 포함된 공격용 악성 URL을 만든 뒤, 사용자가 해당 URL을 클릭했을 때 정보를 획득하는 공격이다. 

 

XSS 공격에 취약한 웹 사이트에 악성 스크립트를 실행하는 URL을 직접 사용자가 누르게 만든다. 

URL 링크를 누르게 되면 자동적으로 악성 스크립트가 실행되게 된다.

 

Reflected XSS 공격 시나리오

  1. 공격자가 XSS 취약점이 있는 사이트를 발견
  2. 보안이 취약한 사이트에서 사용자 정보를 빼돌릴 수 있는 스크립트가 담긴 URL을 만들어 일반 사용자에게 스팸 메일등으로 전달
  3. 일반 사용자가 메일을 통해 전달받은 URL 링크를 클릭하게 되면, 일반 사용자 브라우저에서 보안이 취약한 사이트로 요청을 전달
  4. 일반 사용자의 브라우저에서 응답 메시지를 실행하면서 악성 스크립트가 실행
  5. 악성 스크립트를 통해 사용자 정보가 공격자에게 전달됨.

 

DOM based XSS

DOM based XSS 출처:안랩

DOM based XSS공격은 악성 스크립트가 포함된 URL을 일반 사용자가 클릭하였을 때, 웹 서버에서 받아온 페이지를 클라이언트 브라우저에서 DOM을 구성하는 과정에서 일어난다.

 

DOM based XSS 공격 시나리오

  1. 공격자가 XSS 취약점이 있는 사이트를 발견
  2. 보안이 취약한 웹 페이지에서 악성 스크립트가 실행되도록 URL 주소를 만들어 일반 사용자에게 전달.
  3. 일반 사용자가 메일 등을 통해 전달받은 URL 링크를 클릭하여, 서버로부터 HTML 문서를 전달받음.
  4. 사용자의 브라우저가 응답 받은 HTML 문서를 읽으면서 필요한 스크립트를 실행하는 중에 악성 스크립트가 동작합니다.
  5. 악성 스크립트를 통해 사용자 정보가 악의적으로 전달됩니다.

 

위의 Reflected XSS와 차이점이 무엇인지 헷갈리기 쉽다.

Reflected XSS : URL의 값을 서버로 전달해 서버에서 페이지를 처리하여 응답해주면서 일어나는 취약점

DOM based XSS : URL의 값이 서버로 전달되는것이 아닌 클라이언트 측에서 페이지를 처리하며 생기는 취약점

 

예를들어, page.php?value="악성스크립트"와 같은 요청을 할 때,

Reflected XSS의 경우 서버에서 value의 값을 처리하여 응답 페이지에 악성스크립트가 담겨 오게되지만

DOM based XSS의 경우 서버의 응답에는 악성스크립트가 없고 클라이언트 측에서 페이지를 처리하며 악성스크립트가 실행되게 된다

 

XSS 대응방법


  • 웹 서버에서 입력 값에 정의된 문자 길이를 제한 또는 검증하여 클라이언트 측에서 실행될 수 있는 스크립트 명령이 삽입되지 않도록 설정
  • HTML 태그의 사용이 불필요한 경우 사용자의 입력 값에서 HTML 특수 문자들을 HTML Entity로 변환하여 스크립트를 일반 문자열로 인식되도록 하기
  • HTML태그를 사용해야할 경우에는 블랙리스트 또는 화이트리스트를 이용해 특정 태그만 사용할 수 이ㅆ도록 설정
  • 웹 방화벽을 사용하여 비정상적인 데이터가 전송될 경우 차단

+ Recent posts