Uway.com 은 대부분의 대학에서 이용하는 원서 접수 대리 사이트이다. 또한 물리 올림피아드 , 수학 올림피아드 등 각종 올림피아드의 수상자 발표 서비스 및 성적 컨설팅 서비스도 제공한다.

사용자 삽입 이미지

때문에 굉장히 많은 학생들의 신상 정보를 포함하기 마련이다. 사실 대한민국의 모든 수험생의 신상 정보를 담고 있다고 해도 과언이 아니다.

지난 8월 13일 한국 물리올림피아드의 수상자가 온라인으로 발표되었다. 발표 페이지는 Uway에서 제작한 대행 서비스를 사용하고 있었다. 주소는 http://ipsi9.uway.com/ksi/kps/pass/1/index.htm 이며, 이 포스트를 작성할 때는 서비스를 중단한 상태였다.

페이지는 단순했다. 성명과 주민등록번호를 입력하고 확인을 누르면 수상 정보를 알려 주는 것이었다. HTML 코드를 분석해 본 결과 form1이라는 폼에 namekor, juminno라는 이름의 Text field와 mode라는 이름의 hidden field가 있었다. method는 POST 방식을 사용하고 있었으며 onsubmit에는 값 검사 함수가 할당되어 있어 각 필드의 유효성을 검사했다.

여기서 두 가지 흥미로운 점을 발견할 수 있었다. 즉, form1의 action 페이지가 설정되어있지 않았다는 점mode라는 hidden field의 값은 check로 설정된 채 바뀌지 않는다는 점이었다. action에 주소가 할당되지 않으면 submit시 현재 페이지가 리로딩되며 데이터가 전달된다. 즉, action의 기본값은 현재 페이지이다. 따라서 이 페이지는 한 페이지에서 값의 입력과 출력을 모두 담당하며, 입력 페이지를 표시할 것인지 출력 페이지를 표시할 것인지는 mode라는 변수가 check라는 값을 가지고 있는지를 이용해 판단할 것임을 추측할 수 있다.

기본적으로 POST 방식으로 전송되는 데이터는 변조가 쉽지 않다. 따라서 입력 필드를 통해서만 공격을 해야 되기 때문에 공격이 많이 제한될 수밖에 없다. 특히 필드 검사 함수가 있는 상황에서는 더욱 힘들다. 그러나 만약 GET 방식의 파라미터를 받아들일 수 있다면, 입력 검사 함수를 회피하여 더욱 다양한 형태의 공격이 가능하다. 혹시나 하는 기대를 품고 주소 표시줄에 다음을 입력했다.

(생략)/index.htm?mode=check&namekor=홍길동

mode=check은 입력 페이지가 아닌 출력 페이지를 호출하기 위함이며, namekor 필드를 GET 형식으로 넘기고 있다. 결과는 놀라웠다. 데이터 에러가 아닌 '수상내역 없음' 페이지가 떴다. 알고 있는 수상자의 이름을 입력하여 보았더니 수험 번호와 이름, 주민등록번호, 상급, 여름학교 참가 여부가 정상적으로 표시되었다!

이 공격에서 POST로는 어떠한 값도 넘기지 않았다. 심지어 GET으로도 주민등록번호는 넘기지 않았다. 그러나 이름만 가지고도 정상적인 출력을 했으며, 이는 매우 위험하다.

결과를 통해 이 페이지가 범하고 있는 실수들을 추측해보면 다음과 같다.


  1. POST value와 GET value를 구분하지 않고 사용한다.

    서버사이드 언어를 PHP로 사용했을 경우를 예로 들어 보자. PHP에서 POST value와 GET value에 접근하기 위해서는 각각 $_POST['varname'], $_GET['varname']과 같이 사용해야 한다. 그러나 PHP 배포판의 기본 설정상, POST value와 GET value 모두 단순히 $varname으로도 접근할 수 있다. 만약 POST value에 접근하기 위해 $varname을 사용하였는데 공격자가 POST value 대신 GET value를 전달한다면, 프로그램은 GET value를 POST value로 인식하고 사용할 것이다. 물리 올림피아드 수상자 발표 페이지에 대한 공격이 성공한 결정적인 이유는, POST value 대신 GET value를 넘길 수 있었기 때문이다. 만약 두 값이 구분되었다면, mode를 인위적으로 check로 바꾸기는 힘들었을 것이다.


  2. 폼 값을 검사하지 않는다.

    웹 페이지를 작성하며 가장 하기 쉬운 잘못은 바로 값을 전달할 때 송신자와 수신자 중 한 쪽에서만 검사를 한다는 것이다. 로컬로 실행되는 프로그램은 상대적으로 값이 조작될 가능성이 낮기 때문에 값 전달시 한 쪽에서만 검사하는 것도 큰 문제가 되지 않는다. 그러나 웹 프로그래밍에서 값은 언제 어디서 어떻게 조작될 지 알 수 없으므로 언제나 값을 사용하기 전에는 검사해야 한다. 즉, 폼 값을 전송하기 전에 Javascript를 이용하여 유효성 검사를 했다고 하더라도, 서버사이드에서 이 값을 사용할 때에는 다시 유효성 검사를 해야 한다는 것이다. 물리 올림피아드 수상자 발표 페이지가 서버사이드에서 다시 유효성 검사를 했더라면, juminno 필드가 비어 있는 것을 발견하고 에러를 냈을 것이다.


  3. 적절한 Query문을 작성하지 않는다.

    주민등록번호가 전달되지 않으므로 juminno=""라고 예상할 수 있다. 만약 Query가 적절히 작성되었다면, 주민등록번호가 ""인 데이터는 검색되지 않아야 한다. 예상컨대 Query를 작성할 때 해당 필드가 빈 문자열이 아닐 때에만 조건을 추가하도록 한 것으로 보이며, 이는 이 코드가 재활용되기 때문일 것이다. 어떤 수상자 발표 페이지에서는 이름과 수험번호를, 어떤 페이지에서는 수험 번호와 주민등록번호를 요구하는 등 서로 다른 필드를 요구하기 때문에 빈 문자열이 아닌 경우에만 조건을 추가하도록 한 것이 문제의 원인으로 보인다.


만약 모든 가능한 이름에 대하여 Brute-force 공격(마구잡이 대입 공격)을 시도한다면 1초에 10개의 이름을 처리할 수 있다는 가정 하에 약 900시간만에 모든 이름에 대해 공격을 시도할 수 있으며 한국인의 이름은 주요 성씨에 대부분이 모여 있다는 점을 이용하면 훨씬 짧은 시간 안에도 대다수의 이름에 대해 공격을 시도할 수 있다. 이름만으로 주민등록번호를 포함한 수상 정보를 얻을 수 있다는 것은 매우 위험한 일이다.

9월 초에는 한국 수학올림피아드의 수상자가 또 Uway의 페이지를 통해 발표되었다. 코드를 재활용할 것이라는 예상과 같이, 수학 올림피아드와 물리 올림피아드의 수상자 발표 페이지는 출력 형식을 제외하고는 거의 100% 일치했다.

사용자 삽입 이미지

단일 페이지에서 mode 변수를 이용해 출력을 컨트롤하는 점, POST value와 GET value를 필터링하지 않는 점 등 모든 보안 문제가 그대로였기 때문에 정확히 같은 방법으로 공격을 할 수 있다. 다른 점이라면 출력 페이지에 주민등록번호가 표시되지 않는다는 것이다. 따라서 이름과 주민등록번호를 연결시켜 개인 정보를 얻기 위해서는 이름이 아닌 주민등록번호에 대해 Brute-force 공격을 시도해야 한다.

가장 큰 문제는 보안 의식이다. Uway에서 발생한 문제는 절대 기술적인 문제가 아니다. 공격법이 새로운 것도 아니고, 그렇다고 복잡한 것도 아니다. 심지어 기본적인 PHP 입문서에나 나올 법한 보안 예제이다. 아주 단순하고 초보적인 공격에 개인 주민등록번호를 노출하고 만 것이다. 조금만 보안에 관심을 가졌다면 쉽게 발견하고 고칠 수 있는 문제이다.

문제는 이러한 보안 문제가 이 페이지에만 국한된 것이 아니라는 것이다. 얼마 전 전자신문 에 '학교에서 주민번호가 줄~줄 샌다 '라는 기사가 떴다. Uway와 같이 온라인 서비스가 주된 사업 영역이고, 그 규모도 동종 기업 중 1위라고 자부하는 회사에서조차 너무나 허술한 보안의식으로 심각한 보안 문제가 나타났는데, 학교야 어련하랴 싶은 마음이 들었다.

이제 프로그램을 개발할 때 보안에 신경을 쓰는 것은 필수이다. 프로그래머가 보안을 담당할 수 없다면, 보안 기술자를 고용하는 것이 현명할 것이다. 이 공격법은 주민등록번호 노출에서 그쳤지만, 이와 같은 보안 의식으로 설계한 프로그램이라면 다른 허점도 존재할 것이고, 이는 더 심각한 문제를 야기할 수 있다. 이러한 사태를 맞는 것보다는 보안 분야에 예산을 더 투입하는 것이 훨씬 경제적인 판단이다.

복잡하고 교묘한 공격을 막기는 힘들더라도, 기초적 보안 의식 결여에 의한 보안 허점은 이제 사라지길 바라며 마친다.


2008년 1월 19일 이후 작성된 모든 글에 대해서 퍼가는 것을 금지합니다.
퍼가고자 하시는 분은 링크를 달아 주시기 바랍니다.
Posted by Harry
AJAX를 이용해 서버와 통신하는 방법을 알아보자.
통신할 데이터는 XML의 형태로 주고 받는다.

소스코드 살펴보기

일단 다음 소스를 보고 스스로 분석해보자. 자바스크립트에 대한 어느 정도의 지식만 있다면 쉽게 분석할 수 있을 것이다.

GetServerTime.html (Language : javascript)
  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
  2. <HTML>
  3.  
  4.     <HEAD>
  5.         <TITLE> AJAX </TITLE>
  6.  
  7.         <script language="javascript">
  8.             var xhr; //XML Http Request Object
  9.             var url="time.php";
  10.  
  11.             function createXHR()
  12.             {
  13.                 if(window.ActiveXObject)
  14.                     xhr=new ActiveXObject("Microsoft.XMLHTTP");
  15.                 else if(window.XMLHttpRequest)
  16.                     xhr=new XMLHttpRequest();
  17.                 else
  18.                     return null;
  19.             }
  20.  
  21.             function startAjax()
  22.             {
  23.                 createXHR();
  24.                 if(xhr==null) return;
  25.  
  26.                 xhr.open("GET", url, true);
  27.                 xhr.onreadystatechange=CallBack;
  28.                 xhr.send(null);
  29.             }
  30.  
  31.             function CallBack()
  32.             {
  33.                 if(xhr.readystate==4)
  34.                 {
  35.                     if(xhr.status==200)
  36.                     {
  37.                         var result=xhr.responseXML;
  38.                         document.title=result.getElementsByTagName("time")[0].firstChild.data;
  39.  
  40.                         setTimeout(Again, 1000);
  41.                     }
  42.                 }
  43.             }
  44.  
  45.             function Again()
  46.             {
  47.                 xhr.open("GET", url, true);
  48.                 xhr.onreadystatechange=CallBack;
  49.                 xhr.send(null);
  50.             }
  51.         </script>
  52.     </HEAD>
  53.  
  54.     <BODY onload="javascript:startAjax();">
  55.     </BODY>
  56.  
  57. </HTML>
time.php (Language : php)
  1. <?php
  2.     header ("Content-Type: text/xml; charset=euc-kr");
  3.     echo '<?xml version="1.0" encoding="euc-kr" ?>';
  4.     echo '<time>';
  5.     echo date ("G:i:s");
  6.     echo '</time>';
  7. ?>

GetServerTime.html에서 XMLHttpRequest 객체를 이용하여 time.php에 비동기적으로 접속하여 계속적으로 서버 시간을 알아오는 소스이다.

가장 중요한 것은 XMLHttpRequest 객체이다. 이 객체는 서버와의 비동기통신을 할 수 있도록 제공되는 객체이다.

XMLHttpRequest 객체 생성하기

객체의 생성은 11~19라인이다.

createXHR (Language : javascript)
  1. function createXHR()
  2. {
  3.     if(window.ActiveXObject)
  4.         xhr=new ActiveXObject("Microsoft.XMLHTTP");
  5.     else if(window.XMLHttpRequest)
  6.         xhr=new XMLHttpRequest();
  7.     else
  8.         return null;
  9. }

IE의 지원이 불완전한 관계로 브라우저의 종류에 따라 생성 방법을 다르게 하야 함에 유의하라. 특별한 이유가 없는 한 위의 소스코드는 변경될 일이 없으므로 아예 외워 버리는 것도 좋을 것이다.

서버와 본격적으로 통신하기

서버와의 통신을 본격적으로 시작하는 StartAjax 함수를 보자.

startAjax (Language : javascript)
  1. function startAjax()
  2. {
  3.     createXHR();
  4.     if(xhr==null) return;
  5.  
  6.     xhr.open("GET", url, true);
  7.     xhr.onreadystatechange=CallBack;
  8.     xhr.send(null);
  9. }


23라인에서 createXHR() 함수를 호출하여 XHR 객체를 생성한다. 생성된 객체는 전역 변수인 xhr에 저장된다. 이 때 xhr이 null이라면 생성에 실패한 것이므로 통신을 종료한다(24라인).

26라인에서는 xhr의 연결을 설정하는 open() 함수를 볼 수 있다. open() 함수에는 세 개의 인자가 전달되는데, 각각은 다음과 같다.

XMLHttpRequest::open(string method, string url, boolean asynch, string username, string password);
method
통신의 방법을 설정한다. "POST"와 "GET", "PUT" 중 하나를 넘겨줄 수 있는데, "POST"의 경우 실제 http 자체에 내용을 넣어 전달하는 것이고, "GET"의 경우 '?' 기호와 '&' 기호를 이용하여 주소에 여러 변수의 값을 담아 넘겨주는 방법이다. "PUT"의 경우 거의 쓰이지는 않는 방법이다.

url
통신할 파일의 주소를 적어준다. 절대 경로와 상대 경로 둘 다 상관 없지만 주의할 점은 같은 서버 내에 있어야 접근이 허가된다는 것이다.

asynch
통신의 동기/비동기 여부를 결정한다. true일 경우 비동기 통신, false일 경우 동기 통신이 된다. 기본값은 true, 즉 비동기 통신이다. AJAX의 큰 장점 중 하나가 비동기 통신이므로 주로 true로 사용한다.

username, password
해당 페이지에서 username과 password를 요구한다면 이 인자를 통해 username과 password를 전달한다.
27라인에서는 콜백 함수를 설정하고 있다. 콜백 함수란, 직접 호출하지 않아도 특정 이벤트에 자동으로 호출되는 함수를 말한다. onreadystatechange라는 이벤트에 CallBack()이라는 함수를 할당함으로써 onreadystatechange 이벤트가 발생할 경우 자동으로 CallBack() 함수가 호출된다.

28라인의 send() 함수는 실질적으로 통신을 하는 함수이다. 인자로는 전달할 데이터를 넣어주는데, "GET" 방식에서는 null을, "POST" 방식에서는 전달할 데이터를 넣어주면 된다.

서버에서 응답하기

이렇게 하여 서버로 요청이 넘어가면 서버에서는 time.php 파일을 열어 응답을 한다. 응답은 일반 웹 페이지를 뿌리는 것과 똑같이 하면 된다. 즉, php로 응답을 할 경우 echo로 뿌려 준 모든 소스코드가 응답이 된다.

time.php에서는 GetServerTime.html에서 요청을 전달받고 바로 응답을 한다. header 함수의 경우 응답 http의 헤더를 설정하는 부분인데, 꼭 응답의 헤더에서 Content-Type, 즉 mime을 text/xml로 설정해야 클라이언트에서 XML 데이터로 받아 처리할 수 있다. 그렇지 않을 경우 XML 데이터임에도 불구하고 XML로 인식을 하지 못하게 된다.

ReadyState

XMLHttpRequest 객체를 사용하는 모든 과정에서 이 객체의 속성 중 하나인 readystate가 계속 변하게 되며, 변할 때마다 onreadystatechange라는 이벤트를 발생시킨다. readystate는 다음과 같이 5가지의 값을 가질 수 있다.

0 객체는 생성되었으나 open 함수는 호출되지 않음
1 open 함수는 호출되었으나 send 함수는 호출되지 않음
2 send 함수는 호출되었으나 아직 서버로부터 응답이 오지 않음
3 서버로부터 응답이 일부만 도착함
4 서버로부터 모든 응답이 도착함
즉 실제로 응답을 처리해야 하는 시점은 readystate가 4인 때이다.

onreadystatechange 이벤트의 콜백 함수로 CallBack() 함수를 설정했으니 readystate가 0~4로 바뀔 때마다 CallBack() 함수가 호출된다. CallBack() 함수에서는 매번 readystate의 값을 검사하여 4일 경우에만 응답을 처리하면 된다.

하지만 readystate가 4인 경우에도 올바르지 않은 응답이 올 수 있다. 가령, 접근 권한이 없어 접근하지 못했을 때에도 readystate는 4가 될 수 있다. 이 때에는 XMLHttpRequest의 status 속성을 검사하면 된다. status는 성공적인 응답이 왔을 때에 200으로 설정되므로 이를 검사한다.

응답 처리하기

이렇게 readystate==4 && status==200의 조건을 만족했을 경우 서버로부터 성공적인 응답이 도착한 것이므로 xhr에는 응답이 저장된다. 이 응답은 두 가지 방법으로 접근할 수 있다.

첫 번째 방법은 xhr.responseText로 접근하는 것이다. 서버에서 XML 데이터가 아닌 Text 데이터를 리턴했을 경우 사용한다. XML을 리턴했더라도 이를 Text의 형태로 받고 싶다면 이를 이용한다.

두 번째 방법은 xhr.responseXML로 접근하는 것이다. 이 방법은 응답의 Content-type이 text/xml인 경우에만 성공적으로 처리된다. 이 방법은 DOM Object의 형태로 XML 문서에 접근할 수 있어 매우 편하다. 또한 AJAX 자체가 본래 XML을 사용하도록 만들어 진 것이므로 주로 이 방법을 사용하게 된다.

DOMDocument

DOMDocument란, XML 데이터를 효과적으로 제어/조작하기 위하여 고안된 객체이다. XML 데이터를 통째로 읽어 메모리에 트리의 형태로 저장해 놓고 다음의 명령어들을 통해 검색/추가/제거/수정 등의 작업을 할 수 있다. element라는 DOM Object가 존재한다고 가정하고 설명한다.

접근
element.childNodes : element의 모든 자식 노드를 배열로 반환한다.
element.firstChild : element의 첫 번째 자식 노드를 반환한다.
element.lastChild : element의 마지막 자식 노드를 반환한다.
element.parentNode : element의 부모 노드를 반환한다.
element.nextSibling : element의 다음 형제 노드를 반환한다.
element.previousSibling : element의 이전 형제 노드를 반환한다.
element.nodeValue(=data) : element의 값을 반환한다.
element.hasChildNodes(); : element가 자식노드를 가지면 true, 가지지 않으면 false를 반환한다.
element.getAttribute(name); : element의 attribute 중 이름이 name인 attribute의 값을 반환한다.

검색
element.getElementsByTagName(tagname); : 이름이 tagname인 모든 노드를 배열의 형태로 반환한다.
element.getElementById(tagID); : ID가 tagID인 노드를 반환한다.

추가
document.createElement(tagname); : tagname의 이름으로 새로운 노드를 생성한다.
document.createTextNode(text); : text의 값으로 새로운 텍스트 노드를 생성한다.
element.appendChild(child); : child를 맨 마지막 자식 노드 뒤에 추가한다.
element.insertBefore(new, target); : new를 target 바로 앞에 추가한다.

제거
element.removeChild(child); : element의 자식 노드 중 child를 삭제한다.

변경
element.replaceChild(new, old); : element의 자식 노드 중 old를 new로 대체한다.
element.setAttribute(name, value); : element의 속성 중 이름이 name인 속성의 값을 value로 설정한다.
element.removeAttribute(name); : element의 속성 중 이름이 name인 속성을 삭제한다.

마무리

기본적으로 서버와 통신하는 방법을 알아보았다. 사실 위의 소스코드를 그대로 실행하면 시간이 바뀌지 않는다는 것을 알 수 있다. 이는 인터넷 임시 파일이 생성되어 분명 페이지가 바뀌었음에도 불구하고 컴퓨터에 저장된 임시 파일을 읽어들여서 바뀌지 않은 내용을 보여주기 때문이다. 이 때에는 요청하는 주소를 계속 변화시키기 위하여 주소에 랜덤값을 같이 넘겨 주는 것이 좋다.
예를 들어 다음과 같다.

url="time.php?key=" + Math.random();

위와 같이 하면 매번 다른 주소로 요청이 넘어가기 때문에 저장된 임시 페이지를 읽어오지 않아서 매번 다른 값을 볼 수 있다.




2008년 1월 19일 이후 작성된 모든 글에 대해서 퍼가는 것을 금지합니다.
퍼가고자 하시는 분은 링크를 달아 주시기 바랍니다.
Posted by Harry
PHP로 사이트 개발을 끝낸 후에 배포를 했는데, 가끔 있는 오류 때문에 페이지에 오류 메시지가 뜬다면?
이러한 상황을 방지하기 위해 php.ini 의 세팅을 변경함으로써 오류메시지를 보이지 않게 할 수 있다.

ini_set('display_errors', 'off');

위의 코드가 들어가 있는 페이지에서는 오류 메시지가 표시되지 않는다. 다만, 그 페이지에서만 한정되므로, 모든 페이지에 위 코드를 써야 한다. 한 페이지에 설정하고 이를 include하는 것이 현명할 것이다.

만약 어떤 특정 함수에서만 오류메시지를 출력하고 싶지 않다면 다음과 같이 함수 이름 앞에 @(at)을 붙여주면 된다.

@mysql_connect(...);

문제가 발생할 소지가 있는 함수에는 @을 붙여 깔끔한 페이지 작성을 하도록 하자.


2008년 1월 19일 이후 작성된 모든 글에 대해서 퍼가는 것을 금지합니다.
퍼가고자 하시는 분은 링크를 달아 주시기 바랍니다.
Posted by Harry

휴,, 정말 오랜만에 포스팅하는군요.

인천대학교 실습 때문에 MSSQL 서버를 쓰게 되었습니다. 분명 PHP와 연동할 방법이 있을 것 같아서 조사해 본 자료입니다.

먼저, MSSQL의 연동 방법은 MySQL과 같습니다. 함수 이름만 mysql_[...] 에서 mssql_[...]로 바뀐 것 뿐입니다. 하지만 사용 전에 세팅이 필요합니다.

먼저, PHP.ini 파일을 수정해야 합니다. APM Setup 사용자들은 APM 관리자에서 [환경 설정] -> [PHP 환경성절] -> [PHP 확장모듈 설정] 으로 들어가서 'php_mssql.dll'에 체크를 해 주시면 됩니다. APM Setup을 사용하시지 않으시는 분들은 직접 PHP.ini 파일을 열어서 주석으로 처리되어 있는 'extension=php_mssql.dll' 부분의 주석을 해제해주시거나 없는 경우 직접 입력해 주셔야 합니다. 그러면 일단 MSSQL을 사용하실 수 있습니다.

위 함수들이 주로 사용하게 되는 함수들입니다. MySql과 사용법은 같습니다. 자세한 사항은 링크된 Documentation을 참고하시기 바랍니다.

MSSQL에서 가장 까다로운 점은, MySQL과는 달리 여러 사용자를 설정할 수 있다는 것입니다. (MySQL에 다중 사용자 기능이 있는지는 모르겠지만, 저는 사용해 본 적이 없으므로...) 뭐 익숙해진 이 시점에서는 장점이라고 보아야겠지만, 처음 할 때는 매우 헷갈리더군요.

MSSQL은 로그인사용자의 개념을 구분합니다. 전체 DB에 로그온하는 것'로그인'으로 설정된 유저이며, '사용자'각각의 데이터베이스에 접근하는 자격입니다. 또한 사용자는 로그인에 종속적입니다.

무슨 말인고 하니, board라는 데이터베이스가 있다고 합시다. 이 데이터베이스에 접근하기 위해서는 먼저 DB 전체에 로그인을 해야 합니다. 하지만 로그인을 했다 해도 그것만 가지고는 board에 접근할 수 없습니다. 이 로그인이 board에 접근할 수 있는 권한을 주어야 하는데 이것이 '사용자'입니다. '로그인' 자격으로 로그인을 한 후 '사용자'의 자격으로 DB에 접근하는 것입니다. 하나의 로그인은 여러 개의 사용자를 가질 수 있으며, 하나의 사용자는 한 개의 로그인에서만 유효합니다.

최대한 쉽게 풀어보려고 했는데 점점 꼬이네요..;; 어쨌든 그렇습니다.

MSSQL은 기본적으로 sa라는 로그인을 제공합니다. 또한 각 데이터베이스에는 sa의 로그인을 사용하는 dbo라는 사용자가 설정됩니다. 때문에 모든 DB에 접근하기 위해서는 sa로만 로그인하면 됩니다. 하지만 이는 보안상 불리할 수 있기 때문에 용도에 따라 로그인을 몇 개 더 만든 후 사용하는 것이 좋습니다.

기본적으로 MySQL과 MSSQL의 쿼리문은 같습니다. 다만, MySQL의 LIMIT 문을 MSSQL에서는 TOP으로 써야 합니다. 다음은 비교 소스입니다.

MySQL (Language : sql)
  1. SELECT * FROM tbl_name LIMIT 10;
  2. SELECT * FROM tbl_name LIMIT 0,30;
  3. SELECT * FROM tbl_name LIMIT 30,10;
 
MSSQL (Language : sql)
  1. SELECT TOP 10 * FROM tbl_name;
  2. SELECT TOP 30 * FROM tbl_name;
  3. SELECT TOP 10 * FROM tbl_name WHERE INDEX NOT IN ( SELECT TOP 30 INDEX FROM tbl_name );

보시는 바와 같이 중간에 있는 데이터를 뽑아올 때에는 NOT IN 문을 써야 합니다. 좀 귀찮은 건 사실입니다.

아직 많이 써 보진 않았지만 관리하기에는 MSSQL이 더 편할 것 같아 추후 개발시에는 MSSQL을 이용하려고 합니다.



2008년 1월 19일 이후 작성된 모든 글에 대해서 퍼가는 것을 금지합니다.
퍼가고자 하시는 분은 링크를 달아 주시기 바랍니다.
Posted by Harry
우리 반 H모씨 曰,
 "미궁게임 문제 줄테니까 웹페이지좀 만들어줘."

뭐 그냥 일반적인 미궁 게임처럼 페이지 이름을 답으로 해서 넘어가는 형식으로 하면 되지만, PHP 공부도 할 겸 해서 DB에 문제와 답을 저장하고 하나의 페이지에서 다 할 수 있는 일명 '미궁게임 PHP Version'을 만들어보았습니다.

사용자 삽입 이미지

문제 페이지입니다.


사용자 삽입 이미지

사용자 삽입 이미지

만세~


모든 작업은 index.php 하나에서 이루어집니다. 현재 풀고 있는 문제는 세션을 이용하여 처리할 수 있도록하였습니다. 세션을 이용하여 자료를 저장하는 방법을 아키군에게 물어보니, 세션 변수에 스테이지 자체를 저장하라고 하더군요. 하지만 그래도 변수인데 솜씨 좋은 놈이 조작할까 왠지 모를 걱정이 되어 세션과 DB를 연동하여 자료를 저장했습니다. 일단 제가 저장할 것은 stage이기 때문에 데이터베이스에 session용 테이블을 하나 만들고, 여기에 session_id, expire, stage와 같은 3개의 필드를 만들어주었습니다.

session_id는 그야말로 해당 세션의 고유 아이디를 저장하고 있고, expire는 해당 세션의 만료 시간, stage는 실제로 저장될 풀고 있는 문제 번호를 저장하는 필드입니다. id는 30자리의 1부터 9까지로 이루어진 문자열을 임의로 생성하여 할당했으며, 만료 시간은 기본 1시간으로 설정하였습니다.

일단 사용자가 최초로 사이트에 접속하면 세션이 있는지를 검사하고 없다면 새로운 id를 할당한 후 이를 세션 변수에 저장하고, 생성된 id를 DB에 저장합니다. 물론 DB의 stage필드의 값으로는 초기값인 1을 넣어주어야겠지요. 만약 사용자가 문제를 맞혔다면 세션 변수에 저장되어 있는 id를 이용하여 DB에서 id와 일치하는 레코드를 찾고, 해당하는 stage값을 읽어와 1 증가시킨 후에 업데이트합니다.

또한 페이지가 열릴 때마다 세션 테이블에서 만료 시간이 지난 레코드를 모두 찾아 삭제해줍니다. 이렇게 하면 최근 1시간 이내에 생성된 세션만 남아있게 됩니다.

사실 이외에도 몇 가지 부수적인 것들이 많이 있습니다. 아직 함수화를 하는 데 익숙치가 않아서 소스코드가 매우 난잡한 게 최대 단점이랄까요. 뭐 그래도 보안상 신경을 쓴다고 써서 만든 것이니(솔직히 보안 할 문제도 아니지만) 쉽게 뚫리지는 않겠지요.

버전 업좀 하고 문제좀 만들어서 올린 후에 공개하겠습니다.


2008년 1월 19일 이후 작성된 모든 글에 대해서 퍼가는 것을 금지합니다.
퍼가고자 하시는 분은 링크를 달아 주시기 바랍니다.
Posted by Harry
PHP에서 랜덤함수를 사용하는 방법은 C와 별반 차이가 없습니다.

제일 먼저, C에서와 같이 seed를 선택해줍니다. 즉, 여러 개의 난수표에서 하나를 고른다는 것이죠. C와 마찬가지로 srand()라는 함수를 사용하며, 인자값으로는 사용중인 시스템의 시간을 얻는 함수인 microtime()을 씁니다.

그 다음에는 rand함수를 이용하여 난수를 발생시키면 됩니다. rand 함수는 매개변수 두 개를 받아 하나의 정수를 리턴하는 함수입니다. rand 함수의 매개변수 두 개는 각각 반환될 값의 최소값과 최대값입니다. 즉, rand(10, 50)과 같이 함수를 호출하면 이 함수는 10이상 50이하의 정수 중 하나를 리턴합니다.


더 자세한 것은 다음 링크를 참조하시기 바랍니다.

srand() : http://kr2.php.net/manual/kr/function.srand.php
microtime() : http://kr2.php.net/manual/kr/function.microtime.php
rand() : http://kr2.php.net/manual/kr/function.rand.php

documentation 활용을 생활화합시다!


2008년 1월 19일 이후 작성된 모든 글에 대해서 퍼가는 것을 금지합니다.
퍼가고자 하시는 분은 링크를 달아 주시기 바랍니다.
Posted by Harry
지난 2006 겨울캠프에서 시간이 남는 나머지 친구 녀석과 S-game이라는 웹 게임을 만들어보고자 했습니다. 그런데 그 게임을 만드는 데 필수 요소인 PHP를 저는 할 줄 몰라 친구에게 위임하고 저는 게임 시스템과 디자인을 담당하게 되었죠. 그런데 요 친구녀석이 만날 바쁘다며 개발을 미루며, 개발이 늦어지는 이유가 제가 안해서라더군요. 저는 이미 시스템은 완성시켰고 프로그래밍이 되면 거기에 옷을 입히는 게 역할인데, 프로그래밍이 안되니 옷을 입힐 수가 있나요.

그런 고로 이 참에 제가 PHP를 배워버리려고 합니다. 유비에듀라는 사이트에서 무료 강좌가 있어서 보는 중입니다. 나쁘지 않더군요. 문법도 거의 C랑 비슷한 것이 배우기 어렵지는 않습니다. 다만, 변수의 형식이 좀 애매하더군요. 선언도 필요 없는데다가, 형식도 정해져 있지 않아서 하나의 변수에 여러 값이 들어갈 수 있도록 되어있더군요. 어떤 면에서는 편하겠지만, 제 생각에는 왠지 별로 바람직해보이지는 않습니다.

배열 만드는 것도 조금 맘에 안 드는 부분이었습니다. '맵' 자료구조를 지원하는 것은 반가운 일이지만, 숫자 인덱스를 사용하는 배열은 만드는 것부터가 번거롭고, 다차원 배열은 사용할 엄두도 못 내겠더군요.

아직 PHP를 이용해서 페이지는 안 만들어봤습니다. 빨리 배워서 게시판이라도 하나 만들고 싶네요. 나중 가면 이 블로그도 제 손길이 닿은 PHP 코드로 이루어질지도 모르죠. 열심히 해야겠습니다.


2008년 1월 19일 이후 작성된 모든 글에 대해서 퍼가는 것을 금지합니다.
퍼가고자 하시는 분은 링크를 달아 주시기 바랍니다.
Posted by Harry