.NET General2006. 7. 25. 23:40

FxCop은 MS에서 만든 코드 분석 툴이다. 닷넷 코드를 분석해서 표준적인 규칙에 맞는지, 성능에 문제가 없는지 등을 검사해주는 도구로, 많은 프로젝트에서 코드 리뷰를 위한 툴로 많이 사용되고 있다.


이번 Visual Studio Team System에서는, 이 FxCop이 아예 Visual Studio 내부에 내장되어서, 아주 간단하게 코드 분석을 해볼 수 있게 되었다. 솔루션 탐색기에서 프로젝트를 오른쪽 클릭해서 나오는 컨텍스트 메뉴에서 "코드 분석 실행"이라는 것을 클릭하면 바로 FxCop을 이용한 코드 리뷰를 실행하고 그 결과를 얻을 수 있다.


솔루션 탐색기에서 코드 리뷰 실행




코드 리뷰 결과 화면




이 코드 분석 툴에는 200개 정도의 기본 규칙들이 있다. 디자인, 명명, 보안, 사용, 상호 운용성, 성능, 안정성, 유지 관리, 이동성, 이식성, 전역화 등으로 분류된 이런 규칙들은 MS에서 제시하는 닷넷의 매우 표준적인 규칙에 의거해서 제작되었기 때문에 이 규칙들을 적용해서 코드를 분석해보고 규칙에 따르도록 코드를 고치는 것은 전체적인 코드의 질을 높이고 결과물의 퀄리티와 성능, 유지 관리성 등을 높이는데 매우 도움이 된다.


그런데, 실제 SI 프로젝트에서는 아무래도 Custom Rule이 필요하다. 많은 사람들이 참여하는 프로젝트에서 이런 자동화된 툴을 사용해서 개발 표준 준수여부를 분석하는 것은 시간과 리소스의 절약을 가져오기 때문에, 그 프로젝트에만 있는 규칙에 대한 준수 여부를 체크할 수 있는 규칙을 만들어서 사용한다면 아주 유용하다.


그리고, 이 FxCop은 Custom Rule을 작성할 수 있는 구조를 지원한다. FxCop의 규칙은 닷넷 어셈블리(DLL) 형태로 만들어져 있는데, 이 DLL을 만들 수 있도록 FxCopSdk.dll, Microsoft.Cci.dll 등의 참조할 수 있는 어셈블리를 제공하고 있다. 이 FxCop은 코드를 분석하는데 reflection을 쓰지 않고 introspection이라는 것을 사용한다. 이 introspection은 reflection과 같이 MSIL 코드를 분석해서 어셈블리(내부의 클래스 등의 모든 정보 또한)의 정보를 추출하는데, reflection보다 더 빠르다.


Custom Rule을 만드는 절차는 다음과 같다.


1. Visual Studio 2005에서 클래스 라이브러리 프로젝트를 만든다.


2. FxCopSdk.dll 과 Microsoft.Cci.dll을 참조해야 한다. 이 DLL들은 비주얼 스튜디오가 설치된 프로젝트 아래에 \Team Tools\Static Analysis Tools\FxCop 폴더에 있다.


3. 먼저 BaseRule 클래스를 만들어야 한다. 이 Base 클래스는 현재 만드는 프로젝트 내부의 모든 Rule 클래스들이 상속하게 될 클래스로, 리소스 XML파일을 로딩하는 역할을 한다. 이 Base 클래스는 Microsoft.FxCop.Sdk.Introspection.BaseIntrospectionRule 클래스를 상속해서 만들어야 하고, 아래의 샘플 코드와 똑같이 만들면 된다. (물론 두번째 파라미터의 XML 리소스 이름, 세번째 파라미터 내부의 클래스 자신의 이름은 바꿔주어야 한다)


using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.FxCop.Sdk.Introspection;
using Microsoft.FxCop.Sdk;
using Microsoft.Cci;


namespace FxCopSampleRule
{
// 반드시 BaseIntrospectionRule 클래스를 상속해야 한다.
public abstract class BaseFxCopSampleRule : Microsoft.FxCop.Sdk.Introspection.BaseIntrospectionRule
{
protected BaseFxCopSampleRule(string ruleIdentifier)
  : base(ruleIdentifier, "FxCopSampleRule.FxCopSampleRule", typeof(BaseFxCopSampleRule).Assembly)
{
}
}
}


4. 리소스 XML 파일을 만든다. 이 리소스 파일에는 이 어셈블리의 규칙 클래스들이 사용하게 될 메시지(규칙을 어긴 문제에 대한 이름, 설명, 해결책 등)들이 들어가게 된다. 만들 때는 리소스 파일이 아니라 XML 파일로 만들어야 하며, 중요한 것은 이 XML 파일의 속성에서 반드시 빌드 작업을 "포함 리소스"로 해야 한다는 것.




<Rules FriendlyName="AARule">
<Rule TypeName="ShouldHaveAANameSpace" Category="AA.Naming" CheckId="AA0001">
<Name>Should Have AA NameSpace</Name>
<Description>Should Have SK NameSpace</Description>
<Url></Url>
<Resolution Name="Namespace">네임스페이스는 반드시 AA.BB로 시작해야 합니다. {0} 네임스페이스를 고치세요</Resolution>
<Email>kkongchi@interdev.co.kr</Email>
<MessageLevel Certainty="95">Error</MessageLevel>
<FixCategories>Breaking</FixCategories>
<Owner>kkongchi</Owner>
<GroupOwner>AA</GroupOwner>
<DevOwner>kkongchi</DevOwner>
</Rule>
</Rules>


5. 이제 규칙을 만들 차례이다. 규칙은 하나의 클래스로 만드는데, 3번에서 만든 BaseRule 클래스를 상속해서 만든다. 이름은 규칙을 잘 나타내도록 만드시기 바란다. 생성자에서 Base 생성자를 상속할 때에 현재 만드는 규칙이 사용할 메시지의 RuleName을 파라미터로 넣어서 그 메시지를 로드할 수 있도록 해야 한다.


6. 그리고 그 규칙 클래스에서는 Check 메소드를 오버라이드해서 체크할 규칙을 구현하면 된다. Check 메서드에는 많은 오버로드된 버전이 있기 때문에 만들려고 하는 규칙에 맞는 것을 선택해야 한다. 아래의 샘플은 어셈블리의 네임스페이스를 체크하는 것이기 때문에 Module이 파라미터인 것을 선택했다. 체크해야 할 규칙에 어긋나는 케이스가 발생했을 때, Problems 프로퍼티에 새로운 Problem 객체를 Add시키면 된다. 그리고 그 Problem 객체에는 XML에서 로드하는 Resolution 객체가 필요하다. (코드를 보면 알겠지만, {0} 등의 형태로 메시지 내부의 문자열에 현재 검사한 모듈의 이름 등을 넣을 수가 있도록 되어 있다.


using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.FxCop.Sdk.Introspection;
using Microsoft.FxCop.Sdk;
using Microsoft.Cci;

namespace FxCopSampleRule
{
// 먼저 만든 Base 클래스를 상속한다.
public class ShouldHaveAANameSpace : BaseFxCopSampleRule
{
//리소스 XML파일에 있는 규칙들 중에서 ShouldHaveAANameSpace (이름)이 반드시 있어야 한다.
public ShouldHaveAANameSpace() : base("ShouldHaveAANameSpace")
{
}

/// <summary>
/// 네임스페이스가 AA.BB로 시작하는지를 체크하는 규칙이다.
/// </summary>
/// <param name="module"></param>
/// <returns></returns>
public override ProblemCollection Check(Module module)
{
for (int i = 0; i < module.GetNamespaceList().Length; i++)
{
  if (!module.GetNamespaceList()[i].FullName.StartsWith("AA.BB"))
  {
   //Problem 객체를 만들어서 base.Problems에 더해야 한다.
   //GetResolution 함수의 파라미터는 XML에 정의된 Resolution에서 {0}, {1} 로 표시된 문자열을 채우는 값이다.
   base.Problems.Add(new Problem(GetResolution(new string[1] { module.GetNamespaceList()[i].FullName })));
  }
}

return base.Problems;
}
}
}


7. 코드가 완성되면 컴파일을 하면 된다. 컴파일된 DLL을 비주얼 스튜디오가 설치된 디렉토리 아래의 Team Tools\Static Analysis Tools\FxCop\Rules 폴더에 복사하면, 바로 그 후부터 코드 분석 시에 새로 만든 규칙을 사용할 수가 있다.


Posted by kkongchi
.NET General2006. 7. 22. 02:29

1. SQL Injection 이란?


a. SQL Injection은 SQL 쿼리에 사용되는 사용자의 입력값에 대해서 제대로 유효성 검사를 하지 않았거나 타입 체크를 하지 않아서, 쿼리가 제대로 동작하지 않거나 시스템에 문제를 일으킬 수 있는 다른 코드가 실행될 수 있게 만드는 애플리케이션 상의 보안 취약점이다.



2. SQL Injection의 예


a. 사용자 정보가 DB에 있으며, 로그인 페이지에서 다음과 같은 쿼리를 호출해서 인증을 처리한다고 생각해보자

SELECT * FROM USERS WHERE USERID = '" + UserIDTextBoxValue + "' AND PASSWORD = '" + PasswordTextBoxValue + "'"


b. 여기서 UserIDTextBoxValue에 a' or 1=1 -- 이라는 값을 넣는다면, or 1=1 부분에 의해서 참이 되고, -- 에 의해서 그 다음부터는 주석처리가 되기 때문에 전혀 쿼리에 영향을 주지 않는다. 그러므로, 실제로 실행되는 쿼리는 다음과 같다.

SELECT * FROM USERS WHERE USERID = 'a' or 1=1 -- AND PASSWORD = ''


3. 해결책


a. ADO.NET에서 제공하는 SqlCommand, SqlParameter 객체를 사용하면, 이 SQL Injection 공격으로부터 안전한 코드를 작성할 수 있다. SqlParameter 객체를 통해서 전달된 값은 쿼리 자체에 영향을 끼치지 못하고 문자열로만 취급되기 때문이다. 즉, 위의 쿼리에 Parameter로 a or 1=1 -- 이란 값을 넣어도 'a or 1=1 --" 이라는 문자열과 USERID를 비교하게 되기 때문에 일치하는 값이 없는 것으로 결과가 나오게 될 것이다. 이런 방식을 Parameterized Query라고 한다.

//SqlCommand 객체 생성
System.Data.SqlClient.SqlCommand oCom = new System.Data.SqlClient.SqlCommand();
oCom.CommandText = "SELECT * FROM USERS WHERE USERID = @param1";
oCom.CommandType = CommandType.Text;

//SqlParameter 객체 생성
System.Data.SqlClient.SqlParameter oParam = new System.Data.SqlClient.SqlParameter();
oParam.DbType = DbType.String;
oParam.Value = "value";
oParam.ParameterName = "param1";

//SqlCommand객체에 SqlParameter를 적용한다.
oCom.Parameters.Add(oParam);


b. Stored Procedure를 사용하게 되면 반드시 SqlParameter객체를 사용해야 하기 때문에 Sql Injection으로부터 안전할 수 있다.


c. 하지만 Stored Procedure 내부에서 문자열과 파라미터를 더해서 쿼리를 구성한다면, SQL Injection에 취약한 코드가 된다. 그래서, 이런 Dynamic Query로 이루어진 Stored procedure를 사용할 때는, 아주 엄격하게 Validation을 해야 할 것이다.

Posted by kkongchi
C# & VB.NET2006. 7. 15. 01:19

LogonUser 함수는 Windows API의 일부이지만, .NET에서 Impersonation을 코드로 구현할 때 쓰이기도 한다.
How to implement impersonation in an ASP.NET application(http://support.microsoft.com/default.aspx?scid=kb;en-us;Q306158) 문서를 보면 그 자세한 방법을 알 수 있다.


그런데 이 함수의 스펙을 보면, LogonType이라는 파라미터가 있는 것을 알 수 있다. 이 파라미터는 로그온 유형을 정의하는데, 일반적으로 .NET에서 Impersonation 용도로 사용할 때에는 Interactive 모드를 사용하게 된다. 하지만 실제로는 이 것 말고도 몇 가지 유형이 더 있다. 다음은 이 파라미터에서 선택할 수 있는 모든 유형에 대한 설명이다. (MSDN 영문 문서를 번역한 것으로, 오역이 있다면 알려주시길 부탁드린다)


LOGON32_LOGON_BATCH - 이 유형은 사용자를 대신해서 프로세스를 처리하는 배치 서버를 위해서 만들어졌다. 또 이 유형은 많은 plaintext 인증시도를 하는 고성능 서버가 사용하기 위해서 만들어 진 것으로, 메일 서버나 웹 서버가 바로 그 예이다. LogonUser 함수는 이 경우에 사용자의 Credential을 캐싱하지 않는다.


LOGON32_LOGON_INTERACTIVE - 이 유형은 컴퓨터를 이용해서 상호작용하는 - 예를 들면 터미널 서버, 원격 셸, 혹은 유사한 프로세스 - 사용자를 위해서 만들어진 것이다. 이 유형은 접속이 끊어졌을 때의 오퍼레이션을 위해서 로그온 정보를 캐싱하기 때문에 부가적인 비용이 소요된다. 그렇기 때문에, 메일 서버와 같은 클라이언트/서버 애플리케이션에는 부적당하다.


LOGON32_LOGON_NETWORK - 이 유형은 plaintext 패스워드로 인증하는 고성능 서버를 위해서 만들어졌다. LogonUser 함수는 이 경우에 Credential을 캐시하지 않는다.


LOGON32_LOGON_NETWORK_CLEARTEXT - 이 유형은 Impersonating하는 동안 다른 서버로 또 연결할 수 있도록, 인증 패키지에 이름과 패스워드를 저장한다. 서버는 클라이언트로부터 plaintext credential을 받을 수 있고, LogonUser 함수를 호출해서, 네트워크에 있는 다른 서버로 접속할 수 있는지 확인하고, 다른 서버와 계속 통신할 수 있다. (NT에서는 지원하지 않는다)


LOGON32_LOGON_NEW_CREDENTIALS - 이 유형은 호출자가 현재의 토큰을 복제해서 외부로 나가는 연결에 대해서 새로운 credential을 지정하게 해준다. 새로운 로그온 세션은 같은 로컬 식별자를 가지지만, 외부 연결에서는 다른 Credential을 가지게 된다. 이 유형은 LOGON32_PROVIDER_WINNT50 프로바이더를 지정했을 때만 지원된다. (NT에서는 지원하지 않는다)


LOGON32_LOGON_SERVICE - 서비스 타입 로그온을 가리킨다. 계정은 반드시 서비스 권한이 활성화되어 있어야 한다.


LOGON32_LOGON_UNLOCK - 이 유형은 GINA(Graphical Identification and Authentication) DLL - Window인증창을 생각하면 된다 - 이 사용한다. 이 유형의 로그인은 워크스테이션이 잠금해제되었다는 유니크한 감사 레코드를 생성한다.

Posted by kkongchi