Notice
Recent Posts
Recent Comments
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Today
Total
관리 메뉴

만재송

[JavaScript, TypeScript] 코드 보안 본문

프로그래밍/JavaScript, TypeScript

[JavaScript, TypeScript] 코드 보안

만재송 2018. 1. 14. 19:51

코드 보안


자바스크립트와 타입스크립트를 공부하면서 가장큰 불안요소가 내가 만든 코드에 접근하여 코드를 조작하는 부분이었다. 크롬이나 익스플로러 등, 웹으로 동작하는 모든것들은 개발자 창을 통해 코드를 볼 수 있고, 콘솔창에서 어떤 함수나 변수에 접근도 가능하다. 물론 해킹을 방지하기 위해서 자바스크립트 난독화 라던지 암호화 등 많은 보안 기술이 나왔지만, 여전히 허점은 존재하는 것 같다. 이번 내용은 필자가 자바스크립트와 타입스크립트를 공부하면서 이 기능을 잘 사용하면 코드 보안에 도움이 될 것 같은 기능들을 설명하겠다.


클로저 (closure)


클로저는 이미 생명 주기가 끝난 외부 함수의 변수를 참조하는 방법이다. 외부 함수가 종료 되더라도 내부함수가 실행되는 상태면 내부함수에서 참조하는 외부함수는 닫히지 못하고 내부함수에 의해서 닫히게 된다. 즉, 클로저 안에 정의된 함수는 외부에서 만들어진 환경을 기억하고 있다 라고 생각하면 된다.

흔히 함수 내에서 함수를 정의하고 사용하면 클로저라고 한다. 하지만 보통 정의한 함수를 리턴하고 사용은 바깥에서 하게된다. 우선 아래 코드를 보자


function getClosure() {
var text = '접근가능??';
return function() {
return text;
};
}
var closure = getClosure();
console.log(closure()); // '접근가능??'


위에서 정의한 getClosure를 호출해서 closure 변수에 함수를 반환하고, 반환된 함수는 getClosure() 내부에서 선언된 변수를 참조하고 있다. 또한 이렇게 참조된 변수는 함수 실행이 끝났다고 해서 사라지지 않았고, 제대로 된 값을 반환하고 있는 걸 알 수 있다.

그렇다면 이 클로저를 이용하여 어떻게 코드를 보안할 수 있을까?? 바로 함수내에 있는 변수 접근 제한을 줄 수 있다. 위의 예시코드도 보면 closure 변수를 호출해서 text 값을 참조한것이지, 직접적으로 console.log(text)를 호출해보면 오류가 발생할 것이다. 이 방법을 사용해서 모든 접근제한자가 public인 변수를 private 하게 꾸며줄 수 있다는 것이다.


예시로 아래의 gameManager 객체를 보자 (필자가 게임 개발자라서... ㅎㅎ). 


// 클로저 미적용
var gameManager = {
num: 1,
getNum: function () {
return this.num;
}
}
console.log(gameManager.getNum()); // 1
gameManager.num = 2;
console.log(gameManager.getNum()); // 2


gameManager 객체에는 num 프로퍼티와 num 프로퍼티값을 반환하는 getNum 메서드가 있다. 클로저가 미적용된 객체는 직접적으로 num 프로퍼티에 접근할 수 있어서 마지막 console.log 에 프린트된 값을 보면 2가 나오게된다. 웹에서 콘솔로 코드를 수정할 수 있는 스크립트의 특성상, 이런식으로 코드를 작성하면 변수 조작은 식은죽 먹기다.


// 클로저 적용
var gameManager = (function () {
var _num = 1;
return {
getNum: function () {
return _num;
}
}
}());
console.log(gameManager.getNum()); // 1
gameManager._num = 2;
console.log(gameManager.getNum()); // 1


위의 코드는 클로저가 적용된 gameManager 객체이다. 아니 function을 대입했는데 왜 이게 객제라고 하냐고 물을수도 있지만, 그 이유는 함수끝에 () 를 넣어서 즉시실행했기 때문이다. 즉 gameManager 변수에는 익명함수를 즉시실행한 리턴값이 담겨있다. 리턴값에는 getNum 메서드가 담겨있는 객체이고 getNum은 _num의 값을 출력하게 되어있다. 우리는 클로저에 대해 배웠으니 gameManager.getNum() 을 실행하면 1이 나오는것을 알 수 있다. 그럼 아까와 똑같이 gameManager의 _num프로퍼티를 변경하고 getNum을 호출하면 뭐가 나올까? 당황스럽게도 1이나온다. 그 이유는 우리가 변경한 _num은 리턴한 객체의 this._num이지, 외부에 선언한 var _num이 아니라는 것이다. 이처럼 클로저로 외부의 접근을 제한하면서도 개발자는 필요할 때 사용할 수 있게 코드를 구현할 수 있다.


get / set


get / set 는 ES5 부터 사용할 수 있는 기능이다. 어떤 프로퍼티에 값을 저장하거나 읽을 때 설정을 하고싶다면 get과 set 기능을 사용할 수 있다. 아래 TypeScript 예제를 보자.


class Employee {
fullName: string;
}

let e = new Employee();
e.fullName = "Bob Smith";
if (e.fullName) {
console.log(e.fullName);
}


위의 코드는 간단한 Employee 클래스안에 fullName 프로퍼티가 있는 코드이다. new Employee() 를하면 Employee 객체가 변수 e에 담기게 되고 e.fullName 으로 접근하여 값을 변경할 수 있다. 이렇게 코드를 작성하면 외부에서 쉽게 fullName 프로퍼티에 접근이 가능하여 코드를 조작하기가 쉬워진다. 이제 이를 해결하기 위해서 get / set 을 사용할 차례다. 아래코드를 보자.


class Employee {
private _fullName: string;
private passcode = "secret passcode";

get fullName(): string {
return this._fullName;
}

set fullName(newName: string) {
if (this.passcode && this.passcode == "secret passcode") {
this._fullName = newName;
}
else {
console.log("Error: Unauthorized update of employee!");
}
}
}

let e = new Employee();
e.fullName = "Bob Smith";
if (e.fullName) {
console.log(e.fullName);
}


먼저 쉽게 접근 가능했던 _fullName을 private 으로 변경해서 Employee 클래스를 제외하고는 접근할 수 없게 만든다. 그리고 fullName 이름을 사용하는 get / set 메서드를 생성한다. 생성은 (get / set) 메서드명 {} 으로 생성한다. get에는 return으로 현재 _fullName 값을 전달해주고 set 에는 코드 예외처리를 통해 _fullName의 값을 변경한다. 위 의 예제는 Employee 객체를 e 변수에 담아서 e.fullName 에 "Bob Smith" 값을 넣는다. 그럼 Employee 객체에서는 값을 넣었으니 set 함수가 호출이 되고 예외 처리를 통해 _fullName에 값을 넣어주게 된다. 만약 passcode 가 잘못된 형식으로 되어있으면 값을 넣을 수가 없게 된다. 마지막으로 e.fullName 을 console.log 로 프린트하면 get이 호출되면서 _fullName의 값을 호출할 수 있게 된다. 

이처럼 get 과 set 을 이용하여 클로저와 비슷한 방식으로 변수로 직접접근 대신, 간접적으로 거쳐서 데이터를 변경할 수 있게 된다.


데코레이터 (decorator)


데코레이터는 ES7 부터 사용할 수 있는 기능이고 주로 TypeScript에서 사용한다. 데코레이터는 함수를 선언한 뒤 어노테이션(@) 키워드를 이용하여 선언된 함수를 데코레이터로 사용할 수 있다. 어노테이션은 모든 클래스나 메서드, 프로퍼티, 파라미터에 사용할 수 있다. 많은 기능들이 있지만 그중 보안에 관련한 기능을 살펴보고 더많은 기능이 궁금하면 여기 에서 확인하기 바란다.

데코레이터 에서 가장 흥미로운 부분은 메서드나 프로퍼티를 수정 불가능하게 만드는 기능이 있다는 것이다. 아래 코드를 보자.


// 데코레이터 함수는 컴퓨터가 코드를 읽는 순간부터 실행된다.
function editable(canBeEditable: boolean) {
return function (target: any, propName: string, description: PropertyDescriptor) {
console.log(target); // Person 객체
console.log(propName); // hello 메서드 이름
description.writable = canBeEditable; //description.writable 이 false 면 수정이 안된다.
}
}
class Person {
@editable(false)
hello() {
console.log('hello~');
}
}
const p = new Person();
p.hello(); // hello~
p.hello = function () {
console.log('world');
}
p.hello(); // hello~


데코레이터로 정의된 함수는 컴퓨터가 코드를 읽는 순간에 실행된다. 위의 코드를 보면 컴퓨터가 Person 클래스를 읽으면서 @editable(false) 를 읽는 순간 editable함수가 실행된다는 것이다. 데코레이터는 방금 말했듯이 클래스나 메서드, 프로퍼티 파라미터에 사용할 수 있는데 첫번째 예시는 메서드에 데코레이터를 사용하는 방법이다.

위의 코드는 Person 클래스에서 hello 메서드에 데코레이션을 달았다. 실행된 데코레이션 함수는 3개의 인자를 가진 함수를 반환하는데 첫번쨰 인자 target은 Person 객체를, propName은 데코레이션을 단 메서드의 이름, description는 ProertyDescriptor 객체를 가지고 있다. 여기서 3번째 인자인 description.writable을 false로 지정하면 해당 메서드는 오버로딩이 불가능 해진다. 메서드를 변경할 수 없다는 뜻이다. 아래 결과를 보면 중간에 p.hello를 재정의 했는데도 p.hello 를 호출하면 처음과 같은 값 hello~ 가 호출이 된다.


hello~
hello~


아래는 프로퍼티 변경을 막는 예제이다. 


function writable(canBeWritable: boolean) {
return function (target: any, propName: string): any {
console.log(target);
console.log(propName);
return {
writable: canBeWritable
};
}
}
class Person {
@writable(false)
name: string = "manJae";
}
const p = new Person();
console.log(p.name); // manJae
p.name = 'minJae'; // error!


아까 위의 메서드 데코레이터 와 비슷하게 writable 데코레이터 함수를 선언해서 name 프로퍼티에 데코레이터를 부착한 모습이다. 이번엔 인자가 2개인데 3번째 인자인 description이 return 객체로 대체하게 된다. 즉, 반환하는 객체에서 writable 프로퍼티를 false 로 지정해주면 name 프로퍼티를 수정불가능하게 할 수 있다. 위의 예시를 보면 p.name 을 변경하는 순간 에러가 발생하게 된다.


이처럼 데코레이터를 이용하여 클래스안에 프로퍼티나 메서드를 변경불가능하게 만들 수 있다. 이제 위와같은 기능을 배웠으니 실전으로 넘어가서 사용해보자.


텔레그램 게임 해킹


텔레그램이라는 인터넷 메신저가 있는데 거기서 지원하는 웹게임, 흔히 말하는 HTML5 게임을 제공하고 있다. 아래영상은 게임을 개발자모드에서 해킹하여 미사일의 속도를 1로 줄이는 영상이다.



아이구야;;;; 미사일을 전역변수로 지정한것이 큰 오점이다. 전역변수는 코드에서 가장 접근하기 쉽고 변경하기 쉬운 것이다. 아마 코드 난독화도 안된다는걸로 알고 있다. 그래서 저렇게 쉽게 접근하여 미사일의 속도를 제어할 수 있게되는 것이다.

그럼 어떻게하면 미사일을 접근하지 못하게 할 수 있을까? 일단 미사일을 전역이아닌 클래스나 객체에 담아야하고 클로저나 get / set으로 한번 감싸야 된다. 아래 코드를 보자.


// 클로저, get / set 이용
var bulletManager = (function () {
var _bulletSpeed = 120;

return {
get bulletSpeed () {
return _bulletSpeed;
},
set bulletSpeed (bulletSpeed) {
// 외부에서 데이터를 접근하지 못하는 예외 처리
},
}
}());


일단 첫번째는 bulletSpeed를 bulletManager 클로저를 이용하여 객체에 담아 외부에서 접근하지 못하게 막았다. 이렇게 되면 코드압축을 통해서 변수를 쉽게 접근하지 못하게 할 수 있다. 두번째로는 get / set 을 이용하여 한번 감싸서 bulletSpeed에 접근 할 수 있다. set 을 통하여 예외처리를 통과하면 bulletSpeed의 값을 변경할 수 있게도 할 수 있다. 그보다도 가장 중요한 것은 중요한 데이터를 전역변수로 설정하지 않는것이 가장 중요하다.


코드 보안을 마치며


솔직히 100프로 보안은 힘들다고 생각한다. 하지만 해커들을 조금이나마 귀찮게 굴어서 해킹을 포기하게 만든다면 필자는 이와 같은 기능들을 적극 사용 할 것이다. ES5 이전만 해도 사용하고 싶은 패턴을 폴리필 해야만 했는데 버전이 업그레이드 되고나서 클래스, 프록시, 데코레이터 등등 많은 기능들이 추가되었다. 이렇게 좋은 기능들이 계속 추가하다보면 언젠가는 보안도 완벽하게 할 수 있는 코드를 작성할 수 있지 않을까 싶다.

'프로그래밍 > JavaScript, TypeScript' 카테고리의 다른 글

[JavaScript] Chrome V8 엔진  (0) 2018.03.11
[JavaScript] 제너레이터 (Generator)  (2) 2018.01.06
Comments