펜테스트짐


훈련을 통해 당신의 실력을 향상시켜보세요.



인코딩(Encoding)

모두 74명의 회원님이 완료했어요.


이번 훈련에서는 웹 애플리케이션에서 사용되는 여러가지 인코딩 기법을 다룹니다.





인코딩(Encoding)이란?


인코딩이란 어떤 한 형식의 데이터를 다른 형식으로 변환하는 기술이나 과정을 말합니다. 이 훈련에서 다룰 인코딩은 정확히 말해 문자 인코딩(Character encoding)이며, 위키백과의 정의를 인용하자면 아래와 같습니다.

"문자 인코딩(character encoding), 줄여서 인코딩은 사용자가 입력한 문자나 기호들을 컴퓨터가 이용할 수 있는 신호로 만드는 것을 말한다."
디코딩(Decoding)이란?
인코딩된 형식을 다시 원래의 형식으로 변환하는 기술이나 과정을 말합니다.


웹 애플리케이션은 왜 인코딩을 사용하는가?


초기 웹 환경에서는 ASCII 기반의 알파벳이나 일부 특수문자가 아닌 문자를 표기해야 하는 요구사항이 거의 없었으나, 인터넷이라는 글로벌 네트워크를 통해 전 세계가 웹을 사용하기 시작하면서 다국어 문자나 기타 다른 생소한 문자들을 일관되게 표기해야 하는 요구사항이 생기기 시작했습니다. 이에 따라 다양한 인코딩 기법이 개발되고, 사용되다 버려지기도 했습니다. 지금부터 웹에서 주로 사용되는 인코딩 기법을 살펴보겠습니다. 웹 애플리케이션을 테스트할 때 인코딩을 이용해 데이터를 변경해야 하는 경우가 발생하므로 이 훈련에서 설명하는 인코딩 기법들은 반드시 이해하시기 바랍니다.


인코딩 기법의 종류


URL 인코딩

URL 인코딩은 URL상에서 문자를 표현하기 위해 사용되는 기법입니다. URL 인코딩의 형식은 % 문자 뒤에 16진수 ASCII 문자를 붙힌 형태를 사용합니다. 따라서 퍼센트 인코딩이라고 불리기도 합니다.

아시다시피 URL에는 출력 가능한 ASCII 문자만 이용할 수 있습니다. 즉, URL에 ASCII 문자 이외에 특수 문자나 한국어, 중국어 등의 글자가 포함될 경우에는 인코딩을 해야만 합니다. 또한 URL에서 사용되는 예약문자는 URL 구조를 식별하는 등 중요한 문법적 의미를 갖는 구분자로 사용되므로 이런 문자들을 예약 문자와 충돌없이 문자 그대로의 의미로 사용하기 위해서는 반드시 인코딩을 해야 합니다.


예약 문자

!#$&'()*+,/:;=?@[]
%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D


이 중 웹 애플리케이션에서 주로 사용되는 다음의 예약 문자는 숙지하시기 바랍니다.

  • 공백문자(Space): %20 이나 +, 띄워쓰기에 사용됩니다. 
  • 샾(#): %23, Fragment의 시작을 알립니다.
  • 퍼센트(%): %25, 
  • 앤드(&): %26, 매개변수와 값의 쌍을 구분하는 문자입니다. 
  • 슬래시(/): %2F
  • 콜른(:): %3A, 도메인명 뒤에 붙어 포트 번호를 지정할 때 사용됩니다.
  • 등호(=): %3D, 매개변수와 값을 구분하는 문자입니다. 
  • 물음표(?): %3F, 쿼리스트링의 시작을 표시합니다.
  • @: %40
  • NULL문자: %OO (숫자 '영영'입니다. 에디터에서 표기되지 않아 알파벳 O를 이용했습니다.^^)


이중 인코딩

이중(Double) 인코딩은 웹 서버와 웹 애플리케이션에서 각각 URL 디코딩을 수행하는 경우 입력값을 두 번 URL 인코딩하는 기법입니다. 말 그대로 URL 인코딩된 값을 한 번 더 인코딩합니다.

공백문자의 경우 URL 인코딩된 값이 %20이니, 한 번 더 인코딩하게 되면 %2520이 됩니다.



HTML 인코딩

HTML 문서에는 <, >와 같이 특수한 기능을 하는 문자들이 포함됩니다. 공격자가 보낸 입력값에 이런 특수문자들이 포함되고, HTML 문서 안에서 이 문자들을 그대로 사용할 경우 XSS 등의 공격에 취약하게 됩니다. 이런 악의적인 공격을 방어하기 위해 스크립트 실행에 이용될 수 있는 특수문자들을 인코딩하고 HTML 문서 안에 안전하게 표시하기 위해 사용됩니다. 


  • 쌍따옴표("): &quot;
  • 홑따옴표('): &apos;
  • 앤드기호(&): &amp;
  • 여는 꺽쇠괄호(<): &lt;
  • 닫는 꺽쇠괄호(>): &gt;
  • 공백문자(Space): &nbsp;


Base64 인코딩

위키백과에 서술된 Base64의 정의는 다음과 같습니다.

"Base64란 바이너리 데이터(예를 들어, 실행 파일이나 ZIP파일 등)를 문자 코드에 영향을 받지 않는 공통 ASCII 영역의 문자들로만 이루어진 일련의 문자열로 바꾸는 인코딩 방식을 말한다. (이하 생략)"

전자메일을 통한 이미지 등의 바이너리 데이터를 전송하는 용도로 고안되었으며, 웹 사이트에서는 일반적으로 요청 매개변수값이나 세션 등을 난독화하는 용도로 사용됩니다. 이는 Base64 인코딩이 아주 쉽게 디코딩될 수 있다는 사실을 인지하지 못한 개발자에 의한 실수이며, 사실상 난독화를 하지 않은 것과 마찬가지입니다. HTTP 인증에서도 사용자의 자격 증명 데이터를 인코딩할 때 사용되기도 합니다.

Base64로 인코딩된 문자열은 64개의 알파벳 대소문자(A-Z,a-z)와 숫자(0-9), 그리고 "+", "/" 기호로 구성되며, 끝을 알리기 위한 코드로 "=" 또는 "=="를 사용합니다. Base64 인코딩을 하게 되면 원본보다 33% 정도 크기가 늘어나게 됩니다.


Base64 인코딩은 아래의 프로세스에 의해 진행됩니다.

  1. 각 글자를 8bit의 블럭으로 나눕니다.
  2. 8bit 단위의 블럭 3개를 결합해 24bit의 그룹을 만듭니다.
  3. 24bit의 그룹은 다시 6bit 단위의 개별 그룹으로 나눕니다.
  4. 각 6bit 단위의 개별 그룹을 색인표에 따라 변환합니다.
  5. 24bit 그룹 구성시 8bit 단위로 채워지지 않고 누락되는 경우 글자수만큼 "="로 채웁니다. 


"bugbountyclub"이라는 문자열을 Base64 인코딩을 할 경우 아래와 같이 변환됩니다.


다음은 위의 결과에 대해 인코딩 프로세스에 따라 변환되는 과정을 나타낸 그림입니다.

그림을 보시면 각 글자가 8bit 블럭 3개씩 24bit의 그룹으로 묶여 총 5개의 그룹이 있으며 각 글자를 6bit씩 끊어 Base64 색인표에 대응하는 문자로 변환했습니다. 또한, 마지막 그룹에서는 24bit를 채우지 못한 부분은 "0"으로 채워지고 "="를 사용했다는 것을 알 수 있습니다.



다른 문자열을 직접 테스트해보시려면 Base64 visualizer를 방문하여 테스트해보세요. 


유니코드 인코딩

유니코드(Unicode)란 전 세계의 모든 문자(심지어 이모티콘이나 공상과학에 나올 법한 해괴한 문자들까지도 포함)를 컴퓨터에서 일관되게 표현하고 처리하기 위한, 쉽게 말해 이기종 시스템 환경이나 다국어 언어 환경에서도 글자가 깨지지 않게 하기 위한 국제 표준입니다. 유니코드 컨소시엄이란 단체에서 관리하고 있으며, 유니코드 컨소시엄에서는 각 글자마다 고유한 코드를 매핑해 놓았습니다. 이 고유한 코드를 코드 포인트(Code Point)라고 부르며, 코드 포인트에는 보통 U+ 라는 접두사가 붙습니다.  

"bugbounty"라는 문자열을 유니코드로 표현하게 되면 다음 그림과 같이 나타낼 수 있습니다. 

bugbounty
U+0062U+0075U+0067U+0062U+006fU+0075U+006eU+0074U+0079

위의 예시는 문자를 코드에 단순 매핑한 방식으로 이를 유니코드라고 이해하시면 됩니다. 유니코드 인코딩은 어떤 문자에 대해 이 문자가 이진수로 표현된 숫자를 8 bit의 길이에 저장하느냐, 16 bit의 길이에 저장하느냐 혹은 빅 엔디언 방식으로 저장하느냐, 리틀 엔디언 방식으로 저장하느냐 (엔디언에 대해서는 위키백과를 참고하세요.)등의 유니코드 숫자를 저장하거나 표현하는 방식을 말합니다. 이러한 유니코드 인코딩에는 다양한 기법이 있으며, 코드 포인트의 길이가 고정이냐, 가변이냐에 따라 나눌 수 있습니다.

코드 포인트의 길이가 고정인 경우에는 UCS-2, UCS-4 방식이 있습니다.

  • UCS-2: 2 byte(16 bit)에 1개의 문자를 저장한다.
  • UCS-4: 4 byte(32 bit)에 1개의 문자를 저장한다.


하지만 위와 같은 코드포인트 길이가 고정적인 인코딩 방식은 1byte면 충분한 알파벳에는 비효율적이었으며, 이를 개선하기 위해 코드포인트를 가변 길이로 하는 인코딩 방식이 고안되었습니다. 대표적으로 UTF-8, UTF-16이 있으며, 가장 널리 사용되는 방식은 UTF-8입니다.


UTF-16 인코딩은 이름에서 알 수 있듯이 기본 다국어 평면(유니코드의 U+0000~U+FFFF까지)에 속하는 문자를 저장하기 위해 2 byte(16 bit)를 사용합니다. 고정 길이 인코딩인 UCS-2와 유사하지만 이를 확장한 가변 길이 인코딩 방식입니다. 대부분의 문자들이 2 byte의 길이로 표기가 가능하여 고정 길이 인코딩으로 오해하는 경향이 있을 뿐 기본 다국어 평면에 속하지 않는 문자를 표기하기 위해서는 4 byte를 사용합니다.

기본 다국어 평면에 속하는 문자에 대한 UTF-16 인코딩은 매우 심플합니다. 원본 문자에 매핑되는 유니코드 값에 접두사 u를 써서 표기하면 됩니다.

알파벳 소문자 "b"는 u0062로 표현할 수 있으며, 한글 문자 "버"는 ubc84로 표현됩니다. HTTP를 통해 전송될 때는 앞에 %가 추가되어 %u0062, %ubc84의 형태로 표기됩니다.



그럼 UTF-8은 어떻게 인코딩될까요?

UTF-8은 원본 문자의 유니코드 값의 길이에 따라 가변적으로 최소 1byte에서 최대 6 byte로 저장됩니다.

다음과 같이 용량이 각각 1 byte, 2 byte, 3 byte인 3개의 바구니가 있다고 가정해봅시다. 각 바구니에는 "1"과 "0"이 미리 채워져있고 "x"는 가용 공간입니다. 



각 바구니에 미리 채워진 "0"과 "1"은 데이터를 정의하기 위한 일종의 선언값으로 각각에 대한 의미는 다음과 같습니다.

 

여기서 다루지 않는 4~6 byte도 위의 형식에 따라 정의됩니다.

그럼 실제 문자들이 어떻게 인코딩이 되는지 살펴 봅시다.

먼저 "b"라는 알파벳 소문자의 유니코드 값은 0x0062로 이를 이진수로 나타내면 0000 0000 0110 0010 입니다. 이 값(110 0010)은 7비트에 저장할 수 있습니다. 즉, 1 byte 용량의 바구니에 담는 것이 가장 효율적이며, 아래 그림과 같이 최종적으로 바구니에 담긴 값은 0110 0010이 되어 0x62로 변환됩니다. 



"Я"이라는 특수문자는 유니코드 값이 0x042f이고, 이진 표기는 0000 0100 0010 1111입니다. 이 값(100 0010 1111)을 저장하기 위해서는 11 bit의 공간이 필요하므로 2 byte 용량의 바구니를 준비해야 합니다. 바구니의 가용 공간에 각 비트의 값을 순서대로 채워 넣으면 바구니에 담긴 최종값은 1101 0000 1010 1111이 되고, 16진수로 변환할 경우 0xD0 0xAF가 됩니다.


반면 "버"라는 한글 문자는 유니코드 값이 0xBC84로 이진수로 나타내면 1011 1100 1000 0100 입니다. 이 값은 16 bit이므로 가용 공간이 16 bit인 3 byte 용량의 바구니가 필요합니다. 각 비트를 바구니의 가용 공간인 x에 순서대로 채워 넣으면, 바구니에 담긴 값은 1110 1011 1011 0010 1000 0100이 되고, 이 값을 다시 16진수로 변환하면 0xEB 0xB2 0x84가 되는 것이죠. 이 값을 HTTP 통신을 통해 전송하게 되면 앞서 배운 URL 인코딩이 적용되어 %EB%B2%84가 됩니다. 바로 URL에 포함된 한글 문자들이 이와 같은 형태로 보이는 것은 UTF-8로 변환되었기 때문입니다.



Hex 인코딩

Hex는 16진수를 의미하며, 문자열을 각각의 바이트에 해당하는 두 자리의 16진수 문자로 변환하는 인코딩 기법입니다. 바이너리 데이터를 일반 텍스트로 변환하는 가장 간단하고 쉬운 방법입니다.

예를 들어, "bugbountyclub" 문자열은 각각의 문자들이 대응하는 16진수 문자로 변환되게 됩니다.

"b"에 대응하는 16진수 문자는 "62"이고,

"u"에 대응하는 16진수 문자는 "75",

"g"에 대응하는 16진수 문자는 "67"... 이와 같이  모든 문자가 Hex 인코딩을 통해 변환되게 되면 결과물은 아래와 같습니다.




참고 문헌