원문 보기 
1편 튜토리얼 보기  2편 튜토리얼 보기



앞서 첫번째와 두번째 강좌에서 만들었던 간단한 페이스북 게임은 재미는 없어도, 그럭저럭 잘 작동은 한다. 페이스북 프로필 사진을 보여주고, 점수를 저장하고 불러올 수 있다. 그런데, 두번째 강좌 마지막 부분에, 마크 주커버그가 6이 최고인 게임에서 7이라는 이상한 점수를 가지고 있는 것을 보았을 것이다. 이 튜토리얼에서는, 어떻게 자신의 점수를 수정할 수 있는지와 이로부터 예방하는 방법을 배울 것이다.

100% 안전한 어플리케이션을 만드는 것은 불가능하다.  최근 몇년간 출시된 메이저 게임들을 보면, 이들 모두 복사 방지법을 가지고 있지만, 이들이 해킹을 당하는 것은 시간문제일 뿐이다. 해커는 매우 창의적이고, 항상 당신의 프로그램을 부당하게 이용하는 방법을 찾아내며, 당신의 삶을 괴롭힐 것이다. 그래서, 당신이 할 수 있는 것은 해킹을 좀 더 어렵게 하는 방법 밖에는 없다. 슬프게도, 완전하게 이를 예방하는 것은 지금이나 미래나 불가능하다.

자신의 게임 해킹하기

보안을 이해하기 위해서는, 어쩔 수 없이 어두운 면도 알아야 하기 때문에, 어떻게 당신의 어플리케이션을 해킹하는지 배우게 될 것이다. 우리는 마크 주커버그가 어떻게 자신의 점수를 수정할 수 있었는지에 대해서 집중적으로 다뤄볼 것이다. 하지만 여기에서 언급되지 않은 많은 복사 예방법이 존재한다는 것을 명심하자.

웹개발자들은 자신의 어플리케이션을 테스트하고 디버그하기 위해서 Firebug나 Chrome Developer Tools같은 툴들을 사용한다. 이 툴들은 웹개발자들에게 아주 중요한데, 툴을 이용해서 일반 사용자들이 보지 못하는 것들을 볼 수 있기 때문이다. 그러나 악의적인 사용자 역시도, 이 툴을 사용하여 어플리케이션이 어떻게 작동하는지 보고, 점수를 수정하는 것처럼 내부 작동을 변경할 수 있다. 다음의 샘플에서는 Firebug를 사용하지만, 다른 웹디버거 툴을 사용해도 무방하다.



파이어버그를 실행시킨다음 net 탭으로 가보자. 여기서는 모든 external call과 불러온 리소스를 볼 수 있다. 만약 net 탭을 열어놓은 상태에서 페이지를 불러오면, Unity3d파일, 요청된 facebook 이미지 파일, 그리고 호출된 다른 웹서비스들을 볼 수 있을 것이다. 누구나 이 내부를 조사할 수 있고, 웹서비스가 어디에 있는지, 어떻게 사용되고 있는지를 알아낼 수 있다. net 탭을 열어둔 상태에서 게임을 실행해 보자. 그러면 insert_highscore 요청을 볼 수 있을 것이다. 악의적인 사용자는 직접 URL을 입력해서 페이지에 접근할 수 있는데, 이런 방법으로 그들의 페이스북 ID와 자신이 원하는 점수를 넣을 수 있는 것이다.

(역자 주: 크롬을 사용하시는 분은 웹 페이지에서 F12를 누르면 개발자 툴을 사용할 수 있고, 'NetWork'탭에서 위에서 설명한 부분을 확인하실 수 있습니다.)

우리는 유니티에서 보낸 데이터와 마지막에 받은 데이터가 훼손되지 않고, 똑같은지 확인하는 방법이 필요하다. 다시 말하지만, 100% 예방은 불가능하지만, 적어도 해킹을 더 어렵게 하는 것은 가능하다. 요청을 확인하는 방법은 우리가 보내는 파라미터를 단방향 해쉬(one-way-hash)한 다음, 서버쪽의 해쉬를 검사하는 것이다. 단방향 해쉬는 하나의 문자열을 다른 문자열로 변경하기위해 MD5나 SHA1같은 알고리즘을 사용하는 것을 의미한다. 하지만 이 프로세스는 반대 방향으로는 불가능하다. 

예를 들어 MD5는 'Monkey'를 4126964b770eb1e2f3ac01ff7f4fd942“로 해쉬하지만 'Donkey'는 046d93852f6419ac27a74b4ebcaa32e4“로 해쉬된다. 원본 글자는 매우 유사할지는 몰라도, 그들의 해쉬는 완전히 다르다. 우리는 이와같은 특성을 우리가 보낸 데이터의 해쉬를 계산하는데 사용할 것이고, 서버에 원본 데이터와 함께 보낸 다음, 보낸 해쉬와 서버에서 만든 해쉬를 비교할 것이다. 예를 들어:

  1. 구분문자(여기서는 '_')를 사용하여 Facebook ID와 Score를 붙이고, 보안을 위한 비밀 키(paladinrules)를 추가하자 예를 들어:

  2. 첨부된 데이터를 해쉬하고, 원본 데이터와 함께 전달하자. 우리의 샘플 해쉬는 3b707fdc8d8f54c27a543b16a68efcf7가 될 것이다.

  3. 서버에서도 똑같은 프로세스가 진행되는데, 받은 데이터와 비밀키를 붙인다. 그리고 해쉬와 같은지 확인한다.
만약 사악한 마크가 자신의 점수를 변경하려고 한다면, 해쉬는 일치하지 않을 것이고, 우리는 요청을 거부할 것이다. 충분히 설명을 한 것 같다. 이제 이 방법을 구현해 보자!

insert_highscore.php에 해쉬하는 부분을 추가해 보자:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
 mysql_connect('localhost','root',''); //Setup database connection
 mysql_select_db('highscores');
 $score = @$_GET['score'];
 $fbid = @$_GET['fbid'];
 $request_hash = @$_GET['hash'];
 
 if(!$score || !$fbid || !$request_hash ) //Make sure we get all input
    die("No fbid, score or hash");
 
 $data = $fbid . "_" . $score . "_paladinrules"//Hash it up using our format fbid_score_paladinrules
 $hash = md5($data); //Hash it up!
 if(strtolower($hash) != strtolower($request_hash)) //The hashes are different, the request is invalid.
 {
  echo "Invalid request!";
 } else { //it seems legit, lets insert it!
  mysql_query("INSERT INTO scores (fbid, score) VALUES ('$fbid', $score)");
?>

보이는 것처럼, 'Hash'라는 또 다른 GET 매개변수가 전달되고, 서버에서 계산된 Hash와 비교할 것이다. 두개의 Hash가 매칭되는 순간, 안심하고 입력값을 넣을 수 있을 것이고, 그렇지 않으면 해커에게 겁을 주는 메시지를 보여줄 수 있을 것이다.
Hash는 반드시 대소문자를 하나로 통일 해야하는데, 왜냐하면 몇몇 MD5 알고리즘의 구현은 출력을 대문자로 하지만, 아닌 것들도 있기 때문이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
 mysql_connect('localhost','root',''); //Setup database connection
 mysql_select_db('highscores');
 $score = @$_GET['score'];
 $fbid = @$_GET['fbid'];
 $request_hash = @$_GET['hash'];
 
 if(!$score || !$fbid || !$request_hash ) //Make sure we get all input
    die("No fbid, score or hash");
 
 $data = $fbid . "_" . $score . "_paladinrules"//Hash it up using our format fbid_score_paladinrules
 $hash = md5($data); //Hash it up!
 if(strtolower($hash) != strtolower($request_hash)) //The hashes are different, the request is invalid.
 {
  echo "Invalid request!";
 } else { //it seems legit, lets insert it!
  mysql_query("INSERT INTO scores (fbid, score) VALUES ('$fbid', $score)");
?>

DiceGame 스크립트에 있는 submitHighscore에 Hash부분을 추가해보자:
insert_highscore.php에서 삽입한 것과 매우 유사한데, MD5가 좀 더 복잡하긴 하지만, 다행히 C#은 문서화가 잘 되어 있다. 우리가 만든 포맷(id_score_secretkey)을 사용한 매개변수를  해쉬하고 이를 서버로 보내를 것이다.

게임을 빌드하고, 실행을 해보자. 그리고 나서 점수를 전 방법으로 수정해보자. 해쉬가 같지 않기 때문에, 전달된 점수가 거절될 것이다. 이 방법은 아마추어 해커에게는 장벽이 되지만, 악의적인 사용자가 당신의 비밀 형식을 알아내는 것은 시간 문제이며, 스스로 해쉬를 만들어 낼 것이다. 좀 더 복잡하게 만들기 위해서 사용자에 특화된 비밀 키를 추가하거나, 다른 해쉬 알고리즘을 사용하는 것도 가능하다.

우리가 기본적인 공격으로부터 안전하게 되었지만, 아직 크게 기뻐하기에는 이르다.
여전히 해킹을 하는 많은 방법들이 존재하며, 항상 새로운 방법은 있다고 생각해야 한다. 특히 웹사이트에 가장 많이 사용되는 SQL Injection에 대해서 조심해야 한다.
이론적으로 서버에 전달되는 값이 수정할 수 없다고 할지라도, 조심하는게 후회하는 것 보다는 낫다.

이것으로 이번 튜토리얼을 마무리하려고 한다. 이것으로 여러분들이 많은 도움이 되었으면 한다. 이 글을 읽고 여러분의 생각을 댓글로 남겨주거나 Unity forum topic에 글을 남겨줬으면 좋겠다.

Tijmen van den Heuvel
Paladin Studios


Posted by Life is like a box of chocolates Asgump

댓글을 달아 주세요