이 글은 2021년 11월, Windows 10 Chrome Disk Cache ver 2.0을 기준으로 한 분석 내용임을 밝히고 시작한다.
Chromium Project disk cache 문서의 내용을 바탕으로 분석을 시작하였는데, 실제 로컬에 저장된 내용과 차이가 있는 부분이 있어서 그러한 내용을 중심으로 글을 작성하였다.

 


 

Cache Directory 경로

C:\Users\<username>\AppData\Local\Google\Chrome\User Data\Default\Cache

 

Disk Cache 구조

Chrome에서는 각 캐시 데이터를 entry 단위로 저장한다.
disk cache에 저장되는 모든 데이터는 그것이 저장되어 있는 위치를 가리키는 cache address를 가지고 있다.
앞서 캐시 데이터 저장에 사용된다고 했던 entry 역시 그러한 cache address를 가지고 있다.
이 정도의 정보를 가지고 아래의 구조도를 보도록 하자.

Chromium Disk Cache Big Picture (https://www.chromium.org/developers/design-documents/network-stack/disk-cache/files4.PNG?attredirects=0)

Chromium design document disk_cache 문서에 의하면 disk_cache는 다음의 세 가지 유형에 해당되는 파일로 이루어져 있다.

- index : 캐시 데이터 추적의 시작.
  캐시 데이터가 다운로드된 url의 해시값을 키로 하고 그에 대응되는 값으로 entry의 cache address를 가지는 해시 테이블이 저장된 파일. header가 존재한다(그림 1).


- data_x (block file) : 리소스 데이터가 저장된 파일.
  HTTP header부터 시작해서 실제 캐시의 raw 데이터까지 저장되어 있다. header가 존재한다(그림 2).
  각 파일별로 주로 저장되는 데이터의 유형이 다르다.. (ex. data_1 에는 entry가 저장된다.)


- f_0000xx (separate file) : 크기가 큰 캐시 데이터를 저장하는 파일.
  캐시 데이터가 kMaxBlockSize 보다 클 경우 block file 내에 저장되지 못하고 별도의 파일에 저장된다. (kMaxBlockSize = 16KB)
  separate file은 index, block file과 달리 파일 자체의 헤더가 존재하지 않고 오로지 캐시의 raw 데이터만 저장된다.

(그림1) index file header analyze

 

(그림2) block file header analyze

- 참고: block file의 body내용은 offset 0x2000부터 있다.

 

이 내용을 바탕으로 실제 내 컴퓨터 환경의 disk cache를 분석하였는데, index file에서 해결되어야 할 entry의 cache address가 data_0 file에 담겨있었다. 아래 (그림 3)에서도 볼 수 있다시피 해시 테이블이 시작되어야 할 위치인 0x160 이후로 데이터가 매우 드물게 존재하고 있다. 반면에 data_0 파일에서는 0x24 bit 단위로 캐시 데이터가 저장되어 있으며 이 중 빨간색으로 표시된 부분이 entry address이다.

이밖에도 11월 14일 기준으로 확인해봤을 때 data_0 파일과 달리 index 파일은 여전히 마지막 수정일이 10월 29일에 멈춰있다.

이러한 정황들을 통해 index 파일보다는 data_0 파일의 entry address가 더 신뢰성 있다고 판단하여 이를 가지고 분석을 이어나갔다.

 


 

Cache Address 읽는 법

Chromium disk cache 문서에 의하면 cache address는 크게 3가지 유형으로 나뉜다.

- 0x00000000 : 정의되지 않음

- 0x8000002A : separate file (file name: f_00002A)

- 0xA0010003 : data_1 block file0x0003번째 block

정의되지 않은 경우와 separate file 의 경우는 이 정보만을 가지고 데이터를 찾아갈 수 있다.
하지만 block file에 저장된 데이터의 경우 이 정보만으로는 실제 위치를 알아내기가 어렵다..
그래서 추가적으로 조사한 결과 아래와 같은 규칙을 찾았다.

data_1에 저장된 경우 : 0xA0010003
-> 0x0003 * 0x100 + 0x2000 = 0x2300 (data_1 file)

data_2에 저장된 경우 : 0xA0020004
-> 0x0004 * 0x400 + 0x2000 = 0x3000 (data_2 file)

data_3에 저장된 경우 : 0xA0030005
-> 0x0005 * 0x1000 + 0x2000 = 0x7000 (data_3 file)

cache address로부터 해당 주소가 어느 파일에 저장된 경우인지를 얻은 후,
block number와 각 파일별로 가지는 block size를 곱해주고 파일 body의 시작 offset인 0x2000을 더하면 해당 데이터의 진짜 offset을 얻을 수 있다.

여기서 block size는 각 block file header의 entry_size 필드의 값으로부터 얻을 수 있다.
(data_1 : 0x100, data_2 : 0x400, data_3 : 0x1000)

 


 

이러한 정보를 이용하여 cache address를 따라가 보면 다음과 같이 entry를 만날 수 있다.

entry analyze (아래)

Chromium 문서에 의하면 하나의 entry에 data stream이 4개까지 존재할 수 있다고 하는데, 이렇게만 들으면 잘 와닿지 않는다.
그래서 직접 각각의 data stream cache address를 따라가보면 또 하나의 규칙을 발견할 수 있다.

1. data stream[0] 에는 주로 meta data 가 저장된다. 그 캐시가 저장된 http header가 저장되어 있고 이곳에서 content-type, filename 등의 정보를 얻을 수 있다. 이 meta data를 가리키는 cache address는 C로 시작된다는 것 외에는 이전에 봤던 유형과 크게 다르지 않다.
0xC103406E : data_3 block file의 0x406E번째 block
-> 0x406E * 0x1000 + 0x2000 = 0x4070000 (data_3 file)

2. data stream [1] 이후에는 주로 캐시 데이터가 저장된다. separate file을 가리키는 주소일 수도 있고, block file 내에 저장된 데이터를 가리키는 주소일 수도 있다.

이렇게 하나하나 따라가다 보면 아래와 같은 흐름으로 캐시 데이터를 얻을 수 있게 된다.

Stored cache data analysis flow

 


참고 자료

- Chromium disk cache docs
https://www.chromium.org/developers/design-documents/network-stack/disk-cache
- Chromium disk cache v3 docs
https://www.chromium.org/developers/design-documents/network-stack/disk-cache/disk-cache-v3
- Chromium disk cache, disk_format.h
https://chromium.googlesource.com/chromium/src/net/+/15905ac8d688a9910055170314839dc7dc7b2f75/disk_cache/disk_format.h

 

오늘은 힙에 대해 써보려고 한다.

힙은 

이진트리의 일종으로, 다음의 두 가지 속성을 만족하는 것을 말한다.

1. 트리 내 모든 노드의 부모-자식 관계에서 부모 노드의 키값과 자식 노드의 키값이 일정한 대소관계를 가지고 있어야 한다.
2. 완전이진트리로 구성되어야 한다.

각각이 어떤 것을 의미하는지 살펴보자.

먼저 첫번째 속성의 경우, 힙의 두 가지 유형인 최대힙과 최소힙에 대한 설명으로 이해할 수 있다.
최대힙이란 트리 내에서 부모 노드를 제외한 나머지 모든 노드들의 키가 그것의 부모 모드의 키보다 큰 힙을 말한다.
이에 대한 예시는 아래와 같다.

최대힙(maxHeap)

 

노드처럼 표시된 동그라미 안에 써있는 숫자를 키라고 할 때, 모든 부모 노드의 키가 자식 노드의 키보다 큰 경우이다.
여기서 모든 부모 노드의 키가 자식 노드의 키보다 작을 경우로 바뀌면 최소힙이 되는 것이다.
아래는 위의 최대힙을 최소힙으로 바꿔 본 것이다.

최소힙(minHeap)

 

힙의 첫번째 속성은 이렇게 모든 노드의 부모-자식 관계가 최대힙이면 부모가 더 크도록, 최소힙이면 부모가 더 작도록 일정한 대소관계를 이루고 있어야 한다는 의미였다.

 

힙의 두 번째 속성은 완전이진트리이어야 한다는 것이다.
완전이진트리가 되려면 우선 모든 내부 노드가 자식을 두 개씩 가지고 있어야 한다.
또한 가장 깊은 레벨의 노드들이 왼쪽부터 채워져있어야 한다. 이를 만족하는 트리의 예시는 다음과 같다.

완전이진트리인 경우_ 잘 보면 이것도 최소힙이다!

 

아래는 완전이진트리가 아닌 경우에 대한 예시이다. 첫번째 트리는 모든 내부 노드가 두 개의 자식을 가지지 않은 경우이며, 두번째 트리는 가장 깊은 레벨의 노드들이 왼쪽부터 채워지지 않은 경우이다.

완전이진트리가 아닌 경우_ 첫번째

 

완전이진트리가 아닌 경우_ 두번째

 

이렇게 힙은 최대힙이나 최소힙 속성을 만족하면서 완전이진트리인 자료구조를 말한다.

또한 힙에는 루트(root) 노드와 마지막 노드를 가리키는 두 개의 접근점이 있는데, 이를 이용하여 힙에 새로운 값을 추가하거나 삭제할 수 있다. 접근점이 어떤 식으로 사용되는지는 구현 과정에서 함께 보기로 하자.

쉬운 이해를 위해 구현에는 정수형 데이터가 들어있는 선형 배열을 사용하였으며, C언어를 사용했다.


1. 초기화

일반적으로 배열의 인덱스는 0부터 시작한다.
그러나 배열로 힙을 만들 때에는 0번 인덱스는 비우고 시작하는 것으로 생각하고 배열의 크기를 잡는 것이 정신건강에 좋다.

이유는, 배열의 시작 인덱스가 1일 경우 현재 노드의 인덱스를 이용하여 다음의 관계를 표현해낼 수 있게 되기 때문이다.

i : 현재 노드
i * 2 : 왼쪽 자식 노드
i * 2 + 1 : 오른쪽 자식 노드
floor(i / 2) : 부모 노드

따라서 n 개의 데이터를 입력받고자 한다면 배열의 크기는 n+1개가 되도록 하고 데이터는 1번 인덱스부터 넣도록 하자.

 

2. 힙 생성(삽입)

힙을 생성하는 방식은 두 가지가 있다. 삽입식과 상향식이 여기에 해당되는데, 이들은 데이터를 입력받는 방식에 따른 분류이다.

입력 데이터가 하나씩 들어오는 상황이라면 삽입식 힙 생성을 사용할 수 있고,
입력 데이터가 한 번에 들어오는 상황이라면 삽입식 힙 생성 또는 상향식 힙 생성을 사용할 수 있다.

먼저 삽입식 힙 생성은 입력되는 데이터에 대해 하나씩 삽입 작업을 해주는 것과 같다.

데이터 삽입 절차는 다음과 같다.

1. last가 가리키고 있는 곳을 새로운 노드가 추가될 곳으로 옮긴다.
2. last에 새로 입력받은 데이터를 넣는다.
3. 힙의 순서를 복구한다.

(last : 마지막 노드를 가리키고 있는 접근점, 마지막 노드: 레벨 순회로 데이터를 접근했을 때 제일 마지막에 접근하게 되는 노드)

배열로 구현할 경우 1번과 2번에 대해서는 어려울게 없다.
1번은 last가 가리키고 있는 인덱스값을 하나 증가시켜주면 되고, 2번은  증가된 위치에 데이터를 넣어주면 된다.

문제는 3번이 되는데, 앞에서 힙의 노드 간에는 순서가 존재한다고 언급했다.
그런데 임의의 데이터를 마지막 노드로 추가하게 될 경우 이것에 의해 힙 순서 속성에 위배되는 트리가 만들어질 수 있기 때문에 힙 순서 속성을 유지하기 위한 작업을 해야만 한다. 그림을 보면서 따라가보자.

우선 힙에 임의의 새로운 노드가 추가된 상황을 생각해보자.


이는 기존의 최소힙에 새로운 데이터 1이 추가된 상황이다.
1이 추가되기 이전까지 last는 6을 가리키고 있었는데 데이터 추가를 위해 레벨 순위 상 다음 위치인 4의 왼쪽 자식 위치를 가리키도록 변경된 것이다. (단계1) 그리고 변경된 last 위치에 1을 넣었다. (단계2)

이 힙은 최소힙이어야하는데 1이 추가됨으로 인해 최소힙의 속성에 위배되어버렸다. 4보다 큰 데이터가 와야할 위치에 1이 저장되어있기 때문이다.

원래의 순서를 가지도록 복구하기 위해 새로 추가된 노드에서 시작하여 1보다 작은 노드가 나올 때까지 부모 노드를 타고 올라가면서 값을 맞바꾼다. 이 때 두 노드가 저장된 메모리 자체를 맞바꾸는 것이 아닌 노드 안의 데이터만을 맞바꾸는 식으로 구현하면 불필요한 수고를 덜 수 있다.

부모 노드의 값인 4보다 1이 더 작으므로 둘을 맞바꿨다.

 

부모 노드의 값인 2보다 1이 더 작으므로 둘을 맞바꿨다. => 힙의 순서가 복구되었다!


이 과정을 upHeap이라고 한다. upHeap 과정을 코드로 구현하면 다음과 같다.

// 재귀적 방식
void upHeap(int idx){
	if(idx <= 1) return;
    
    if(heap[idx] >= heap[idx/2]) return;
    int tmp = heap[idx];
    heap[idx] = heap[idx/2];
    heap[idx/2] = tmp;
  
    upHeap(idx/2);
}

// 비재귀적 방식
void upHeap(int idx){
	int i = idx, tmp;
	while(i > 1 && heap[i] < heap[i/2]){
        tmp = heap[i];
        heap[i] = heap[i/2];
        heap[i/2] = tmp;
        
        i = i/2;
    }
}

 

위 3단계의 삽입 절차를 코드로 구현하면 다음과 같다.

// heap : 힙, N : 힙(배열)의 크기

void buildHeap(int key){
	N += 1; // step 1
    heap[N] = key; // step 2
    upHeap(N); // step 3
}

삽입식 힙 생성 방식을 사용할 경우 데이터를 하나씩 입력받을 때마다 이 함수를 호출하면 된다.

 

다음으로 상향식 힙생성은 입력 데이터가 전부 미리 주어져 있을 때만 사용할 수 있는 방식이다.

설명을 위해 방금 봤던 힙을 다시 가져와보자.

최소힙이다.

삽입식 힙 생성에서는 매번 새로운 노드가 추가될 때마다 일단 제일 마지막 last 위치에 넣고, 그 자리부터 최대 root 노드까지 계속 따라 올라가면서 값을 맞바꾸는 작업(이하 swap)을 했었다. 이러한 방식을 사용하여 한 번 데이터를 추가할 경우 발생하는 시간복잡도는 다음과 같다. (n: 힙에 존재하는 노드의 갯수)

1. last가 가리키고 있는 곳을 새로운 노드가 추가될 곳으로 옮긴다. => 배열의 경우 O(1)
2. last에 새로 입력받은 데이터를 넣는다. => O(1)
3. 힙의 순서를 복구한다. => O(log n) : 힙의 높이 log n , 최악의 경우 last부터 root까지의 모든 경로에 대해 swap을 해주어야 한다.

Total O(log n)

이 작업을 n 개의 데이터가 추가될 때마다 반복한다고 할 경우 전체 힙 생성에 걸리는 시간은 O(n log n)이 된다.

만약 여기서 미리 모든 입력 데이터가 주어져 있어 상향식 힙 생성을 사용할 수 있다고 할 경우 이에 걸리는 시간은 O(n)이 된다. 이것이 상향식 힙 생성을 사용하는 이유이다. 어떻게 O(n)의 시간안에 힙을 만들 수 있는지 보도록 하겠다.

상향식 힙 생성은 이름 그대로 아래에서부터 윗 방향으로 힙을 만들어나간다. 그림을 보자.

이러한 힙을 만들려고 한다. 이건 이미 힙이다. 그런데 지금은 힙을 "만들어나가는" 과정을 보기 위한 것이니까 이것을 힙이 아닌 일반 트리가 되도록 순서를 뒤바꾸겠다.

얼핏 보면 최대힙 같지만..아닙니다.

 

상향식 힙 생성 방식은 두 개의 부트리를 엮어 하나의 더 큰 트리를 만들어내는 개념으로부터 시작한다.

그리하여 트리 각각의 내부 노드를 root로 갖는 부트리로 생각하여 높이(h) - 1 깊이에 있는 부트리부터 차례대로 힙으로 만든다.

위의 트리를 부트리로 쪼갠다고 하면 아래와 같을 것이다.

가장 작은 크기의 부트리부터 순차적으로 힙으로 만들고 -> 합치는 과정을 반복하여 최종적인 힙을 완성하게 될 것이며,

힙을 만들기 위해 힙 순서를 복구하는 함수를 downHeap이라고 한다.

downHeap에서는 특정 인덱스의 노드를 그것의 자식 노드들과 비교해나가며 힙 순서를 복구한다.

내부노드를 루트로 갖는 부트리의 힙 순서를 downHeap 과정을 통해 힙으로 만든 상황이다.

합칠 두 부트리가 준비되었으므로 8을 기준으로 하여 다시 한 번 downHeap을 수행한다.
(그림 상으로는 노드 8 과 부트리 3, 부트리 2가 각각 떨어져있는 것처럼 보이는데, 실제로는 그렇지 않다. 배열 내에서 일어나는 작업이며 각 노드의 데이터만 바뀌는 것이다.)

8이 3과 2 중에 더 작은 크기를 가진 2와 swap된 상황
8이 4와 5 중에 더 작은 크기를 가진 4와 swap된 상황

 

이 작업을 코드로 구현하면 다음과 같다.

void downHeap(int idx){
    if(idx*2 > N) return;
    
    int small = idx*2;
    if(idx*2+1 > N){
    	if(heap[small] > heap[idx*2+1]) small = idx*2+1;
    }
    
    if(heap[small] >= heap[idx]) return;
    int tmp = heap[small];
    heap[small] = heap[idx];
    heap[idx] = tmp;
    
    downHeap(small);
}

최소힙을 만드는 과정이기 때문에 조금이라도 더 작은 키를 가지고 있는 자식을 위로 올리려고 하고 있다. 이를 위해 small이라는 변수를 만들어 더 작은 값을 가지고 있는 자식의 인덱스를 구하도록 했다.

만약 최대힙을 만드는 과정이라면 두 자식 중 조금이라도 더 큰 키를 가지고 있는 자식의 인덱스를 big 변수에 넣어 사용하는 식으로 응용하면 될 것이다.

downHeap을 사용하여 상향식 힙 생성을 하는 전체 코드는 다음과 같다.

// heap : 힙, N : 힙(배열)의 크기

// 재귀적 방식
void buildHeap(int idx){
	// 외부노드일 경우 비교를 끝낸다.
	if(idx*2 > N) return;
    
    buildHeap(idx*2);
    buildHeap(idx*2+1);
    downHeap(idx);
}

// 비재귀적 방식
void buildHeap(){
	for(int i = N/2; i > 1; i--){
    	downHeap(i);
    }
}

이미 데이터가 주어진 경우에 대해 내부노드부터 알아서 다 스캔해나가는 방식이기 때문에 여러번 호출할 필요없이 한 번에 끝난다.


 

시간 복잡도

힙 생성을 하는 두 가지 방안에 대해 살펴봤다. 각각의 시간복잡도는 아래와 같다.

1. 삽입식 힙 생성 : O(n log n)
2. 상향식 힙 생성 : O(n)

삽입식은 위에서 다루었기 때문에 상향식에 대해서만 설명한다.

상향식 힙 생성의 동작 방식의 경우, downHeap 코드를 잘 살펴봤다면 캐치했겠지만 현재 노드의 키가 자식 노드의 키보다 이미 작아서(최소힙) 힙의 순서를 만족하는 경우 더 이상 진행하지 않고 return 한다.

이것이 자식쪽에서부터 시작해서 root로 올라가는 방식이다. 그러므로 부모 노드의 downHeap 과정이 진행될 때 자식 부트리의 경우 이미 힙으로 순서가 맞춰져있는 상태이므로, 비교되고 있는 노드들의 값의 순서에만 이상이 없다면 더이상 복구를 진행할 것이 없다.

이 과정이 이해가 되지 않는다면 아래와 같이 배열을 그린 뒤에 역방향(최소힙이라면 내림차순, 최대힙이라면 오름차순)으로 정렬된 데이터를 넣고 swap이 이루어질 때마다 하나씩 색칠해가면서 과정을 이해해보자. 이미 색칠이 되어있는 자리는 더이상 색칠할 일이 없거나 혹은 한 번 더 색칠하게 된다고 하더라도 그것이 겹치는 횟수만큼 다른 곳에 접근이 이루어지지 않은 원소가 존재할 것이다.

이번 글에서 다루었던 것은 힙에 대한 기본적인 개념과 구현 뿐이었는데, 힙을 이용하여 구현할 수 있는 다른 자료구조나 알고리즘도 있다.

대표적인 것으로 우선순위큐 ADT와 힙정렬이 있다.

이들에 대해서는 별도의 글에서 다루도록 하겠다.

'DataStructure' 카테고리의 다른 글

큐(Queue)의 정의와 구현  (0) 2021.05.17
스택(Stack)의 정의와 구현  (0) 2021.05.17

suninatas의 비밀번호를 알아내는 문제이다.

문제파일을 다운로드 받으면 tar 확장자를 가진 압축 파일이 나오는데, 이를 풀면 passwd와 shadow 두 개의 파일을 얻을 수 있다.

그리고 역시나 두 파일 내에 suninatas 라는 이름의 user가 있었다.
이를 이용하여 비밀번호를 구하면 된다.

구글링을 통해 passwd 파일과 shadow 파일을 이용하여 비밀번호 cracking을 할 때 john(john the ripper) 을 주로 쓴다는 것을 알았다.
곧바로 설치해준다.

++
shadow 파일을 보면 각 사용자별로
username:$number$hash
형태로 내용이 적혀있는데, 여기서 number값이 그 number에 해당하는 암호화 알고리즘으로 hash를 만들었다는 것을 의미한다고 한다.
number는 1~6 범위에 있는 숫자인데 여기서는 6번에 해당하는 암호화 방법과 salt로 hash를 만든 것이고 이 정보를 이용하여 john이 복호화를 해주는 것.
리눅스에서 사용자의 비밀번호 암호화 시 6가지 암호화알고리즘 중 하나와 특정 salt값을 이용한다는 것을 알 수 있는 대목이다.
++

# install | configuration
~/src$ git clone git://github.com/magnumripper/JohnTheRipper -b bleeding-jumbo john
~/src/john/src$ ./configure && make -s clean && make -sj4

# testing
~/src/john/run$ ./john --test

(애초에 apt로 john을 설치하면 john 파일이 아닌 다른 위치에서도 실행가능.! 그런데 이렇게만 해도 john이 활성화까지 되는지는 모르겠다..)
설치를 마쳤으면 문제에서 주어진 두 파일을 이용하여 unshadow 한 결과를 password.txt에 저장한다.

$ unshadow passwd shadow > password.txt

그리고 그 파일을 john 명령어를 통해서 실행시켜준다. 그리고 성공한다면 john이 --show 옵션으로 확인해보라고 알려준다.

$ john ~/Downloads/password.txt
$ john --show ~/Downloads/password.txt

 

알려준대로 --show를 하면 suninatas의 비밀번호를 알 수 있게 된다 :)

 

몇 주 전 시작한 삽질이 이제까지 계속될 줄은 몰랐다.
처음에는 macOS에 올리려고 거의 몇 주 내내 삽질을 했는데 도저히 안돼서 ubuntu로 돌렸다.
그래도 macOS에서 삽질하면서 많이 배워서인지 ubuntu에서는 금방 해냈다.!
(mac에서 성공하신 분들 정말 대단... b)

감격스러움에 눈물이 앞을 가리지만.. 잊어버리기 전에 얼른 적어둬야지


개발 환경

Ubuntu 18.04 LTS
Nginx 1.14.0
Django 2.2.4

 

  • 이 포스트는 이미 어느 정도 만들어져 개발 서버로는 동작이 되는 Django 프로젝트가 존재한다는 상황을 가정한다.
  • virtualenv를 이용한 가상 환경에서의 작업을 전제로 한다.

 

여기서 사용할 Django + uWSGI + Nginx 간의 관계는 다음과 같다.

Client <-> Nginx <-> uWSGI <-> Django

: Nginx가 Client들로부터 비동기식으로 요청을 받아 이를 uWSGI한테 넘겨주면, uWSGI는 이를 Django가 이해할 수 있는 형태로 바꿔 Django에게 요청을 넘겨주고 그에 대한 응답이 다시 위 흐름대로 Client에게 돌아가는 방식

이러한 구조를 통해 내가 생각한 구현 흐름은 이렇다.
Django로 서비스를 만들고 이를 uWSGI와 연결하고 실행하면 특정 포트로 이 웹서버에 접근할 수 있을 것이다. (socket 설정 전)
그 후 Nginx와는 소켓을 이용하여 통신하도록 할 것이기 때문에 uwsgi와 nginx 사이에서는 소켓 파일을 공유하도록 하면 된다.

이를 바탕으로 작업한 내용은 다음과 같다.

 

현재 포트 포워드 상태

1. 외부 :8880 -> 내부 :8080
2. 외부 :8888 -> 내부: 8000

최종 연결은 1번 포트에 할 건데,
중간에 테스트용으로 1번과 2번을 암거나 가져다 쓸 수 있으니 양해 부탁..

 

Django

1) Directory 구조

repo
    - db.sqlite3
    - manage.py
    - app
        - migrations
        - static
        - templates
        - __init__.py
        - admin.py
        - apps.py
        - models.py
        - urls.py
        - views.py
    - conf
        - __init__.py
        - asgi.py
        - settings.py
        - urls.py
        - wsgi.py

2) settings.py

ALLOWED_HOSTS = ['*']
STATIC_URL = '/static/'

 

여기까지 한 것을 아래 명령어를 이용하여 실행 후 접속하면 다음과 같이 원하던 대로 잘 접속되는 것을 볼 수 있다.

$ python3 manage.py runserver 0:8080

 

이 템플릿 어디서 가져왔는데 기억이 안난다... Thank you so much for making good design template

 

uWSGI

1) /etc/uwsgi/django.ini

[uwsgi]
home = /path/to/venvFolder/venv
chdir = /path/to/project/repo
module = conf.wsgi:application

http = :8000

master = True
processes = 5

logto = /var/log/uwsgi/uwsgi.log

** Nginx와 연결하기 전에 uwsgi와 django가 연결이 되고 있는지를 확인해보기 위해 우선 http 방식으로 설정하였다.
Nginx와 연결할 때에는 socket으로 바꿀 예정

아래의 명령어로 uwsgi를 실행시킨 후 8000번 내부 포트에 대응하는 8888번 외부 포트로 접속하여 진행상황을 확인한다.

$ uwsgi --ini /etc/uwsgi/django.ini
[uWSGI] getting INI configuration from /etc/uwsgi/django.ini
*** Starting uWSGI 2.0.19.1 (64bit) on [Sat Sep 11 05:16:26 2021] ***
compiled with version: 7.5.0 on 11 September 2021 00:27:30
os: Linux-4.4.0-1128-aws #142-Ubuntu SMP Fri Apr 16 12:42:33 UTC 2021
nodename: goorm
machine: x86_64
clock source: unix
pcre jit disabled
detected number of CPU cores: 2
current working directory: /home
detected binary path: /usr/local/bin/uwsgi
chdir() to /home/ssg
your memory page size is 4096 bytes
detected max file descriptor number: 1024
lock engine: pthread robust mutexes
thunder lock: disabled (you can enable it with --thunder-lock)
uwsgi socket 0 bound to UNIX address /path/to/socketFile/uwsgi.sock fd 3
setuid() to 33
Python version: 3.7.4 (default, Nov  4 2020, 10:17:35)  [GCC 7.4.0]
PEP 405 virtualenv detected: /path/to/venvFolder
Set PythonHome to /path/to/venvFolder
*** Python threads support is disabled. You can enable it with --enable-threads ***
Python main interpreter initialized at 0x55eb1a8d8020
your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
mapped 437520 bytes (427 KB) for 5 cores
*** Operational MODE: preforking ***
WSGI app 0 (mountpoint='') ready in 2 seconds on interpreter 0x55eb1a8d8020 pid: 528 (default app)
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 528)
spawned uWSGI worker 1 (pid: 531, cores: 1)
spawned uWSGI worker 2 (pid: 532, cores: 1)
spawned uWSGI worker 3 (pid: 533, cores: 1)
spawned uWSGI worker 4 (pid: 534, cores: 1)
spawned uWSGI worker 5 (pid: 535, cores: 1)

 

django 프로젝트는 잘 찾는 것 같은데, 왜인지 static 파일을 하나도 찾지 못하고 있는 것 같다. 일단 django 프로젝트만이라도 Nginx까지 연결해보자 싶어서 계속 진행했다.

Nginx와 연결할 때에는 socket을 이용하고자 아래와 같이 django.ini를 수정하였다.

[uwsgi]
base = /.../WEB
home = %(base)/venv
chdir = %(base)/repo
module = conf.wsgi:application

socket = %(base)/run/uwsgi.sock
chown-socket = www-data:www-data;
chmod-socket = 660
vacuum = True

master = True
processes = 5

logto = /var/log/uwsgi/uwsgi.log

여기서 chown과 chmod 설정을 해주지 않으면 추후에 client의 요청이 nginx를 통해 처리될 때 권한 문제로 .sock파일을 사용하지 못하는 에러가 발생한다. www-data는 nginx 설정에 적힌 user 이름이다. (뭘로 하든 nginx user와 같은 사용자로 파일 권한 설정해야 함)

(nginx와 uwsgi가 .sock파일을 이용하여 통신할 것이고 이 때 .sock파일이 매개가 되는 상황.)

 

Nginx

1) /etc/nginx/nginx.conf

user www-data;
http {
	include	/etc/nginx/sites-enabled/*;
    include	mime.types;
    default_type application/octet-stream;    
    ...
}

앞서 나왔던 대로 user를 www-data로 한다. (근데 이건 사실 default로 적혀있을 거라서 그냥 내버려 두면 된다.)
include로 sites-enabled 폴더 내용 가져오는 것도 기본적으로 적혀있을 내용이긴 하다. 별 차이 없으면 패스
(+여기서 삽질했던 거: nginx.conf 파일 안에는 server 블록이 필요 없다. server 블록은 sites-available/project.conf에 적으면 됨...)

2) /etc/nginx/sites-available/django.conf

upstream django {
	server unix:/path/to/socketFolder/uwsgi.sock;
}

server {
	listen 8080;
    server_name [local ip addr];
    
    charset utf-8;
    root /path/to/project/repo;
    
    location / {
    	include /etc/nginx/uwsgi_params;
        uwsgi_pass django;
    }
    
    location /static/ {
    	alias /path/to/project/app/static/;
    }
}

(+ static 경로 마지막에 / 를 꼭 붙여주자)

sites-available에 django.conf 파일을 작성했으면 이를 sites-enabled 폴더에 링크 걸어주는 것까지 해야 한다.

$ sudo ln -sf /etc/nginx/sites-available/django.conf  /etc/nginx/sites-enabled/

여기까지 한 뒤 uwsgi와 nginx를 실행시킨다.

$ service nginx start
$ uwsgi --ini /etc/uwsgi/django.ini

/etc/nginx/sites-available/django.conf 에서 port를 8080번으로 설정했기 때문에 이에 대응하는 8880번 포트로 접속하면 static 파일까지 다시 성공적으로 나온다!


참고

 

System Preferences로 user/group를 추가할 수도 있긴 하지만,
필자는 terminal로 추가하는 법이 필요했기 때문에 정리해봤다.

 

user 추가

< ... > 괄호가 있는 부분만 본인 상황에 맞게 바꿔서 사용하면 된다.

$ sudo dscl . -create /Users/<username>
$ sudo dscl . -create /Users/<username> UserShell /bin/bash
$ sudo dscl . -create /Users/<username> RealName "<full name>"
$ sudo dscl . -create /Users/<username> UniqueID <uid>  ## set user id
$ sudo dscl . -create /Users/<username> PrimaryGroupID <gid>  ## set group id
$ sudo dscl . -create /Users/<username> NFSHomeDirectory /Local/Users/<username>  ## create user's directory
$ sudo dscl . -create /Users/<username> password  ## set user's password

 

새로 만든 user에 관리자 권한을 주고싶을 때에는 아래의 명령을 추가로 사용하면 된다.

$ sudo dscl . -append /Groups/admin GroupMembership <username>

 

user 삭제

$ sudo /usr/bin/dscl . -delete /Users/<username>

 

user list 출력

$ dscl . list /Users | grep -v “^_”

 

user 정보 출력

$ sudo /usr/bin/dscl . -read /Users/<username>

 

group 추가

$ sudo dscl . -create /groups/<groupname>
$ sudo dscl . -append /groups/<groupname> gid <gid>
$ sudo dscl . -append /groups/<groupname> passwd <"*">

 

group list 출력

$ sudo dscacheutil -q group

 

user의 group 변경

$ sudo dscl . -create /Users/<username> PrimaryGroupID <gid>

 

 


참고

https://blog.travismclarke.com/post/osx-cli-user-management/
https://www.alphr.com/create-admin-mac-terminal/
https://support.4it.com.au/article/list-user-accounts-mac-osx-using-terminal-command-line/
https://community.jamf.com/t5/jamf-pro/unhide-delete-a-user-account/td-p/195116

 

 

XCZ.KR의 패스워드를 잃어버렸다고 한다.

주어진 파일은 .pcapng 파일 하나이고, 곧바로 Wireshark로 열어보겠다.

간단히 살펴봤는데 line-based text data 전송량이 좀 있었다. 뭐를 그렇게 보냈나 보기 위해 HTTP object list를 살펴보았다.

password를 잃어버렸다고 하더니,, 로그인에 시도할 법하게 생긴 login.php와 회원가입 완료 시 쓸 법한 join_ok.php가 여럿 보인다.

특히 join_ok.php나 login_ok.php 의 Content-Type이 form-urlencoded 인 것으로 봐서 이곳에 쓸 만한 정보가 있는지 보는 것도 괜찮아 보인다.

no.313 packet

첫 번째로 찾은 정보는 id=menboong 이라는 사람이 pw=123123으로 하여 회원가입에 성공하였다는 것이다.

no.378 packet

두 번째로 찾은 정보는 id의 길이가 3~20 이어야 한다는 것이다.

no.571 packet

세 번째로 찾은 정보는 id=IMZZANGHACKER 라는 사람이 pw=IDISLIE 로 하여 회원가입에 성공하였다는 것이다.

IMZZANGHACKER...? 문제의 주인공이다.

벌써 답이 나온 것 같다.^^...

+ Recent posts