개발자가 알아야 할 10가지 보안 팁으로 코드 보호하기

C++, C# 및 SQL을 아신다면 이해에 큰 도움이 됩니다.

보안은 다차원적인 문제입니다. 어디에서나 보안상 위험이 발생할 수 있습니다. 사용자가 오류 처리 코드를 작성하거나 너무 많은 권한을 제공하는 경우, 서버에서 실행하고 있는 서비스가 무엇인지 잊어 버리는 경우, 모든 사용자 입력을 허용하는 경우 등이 있습니다. 그 밖에도 여러 가지 경우가 있습니다. 다음과 같은 안전한 네트워크 전략에 대한 10가지 주의 사항에 따라 시스템, 네트워크, 코드를 순조롭게 보호할 수 있습니다.

1. 사용자 입력 신뢰의 위험성

이 기사의 나머지 부분을 읽지 않더라도 한 가지 주의해야 할 사항은 "사용자 입력을 믿지 말아야 한다"는 것입니다. 데이터가 항상 올바르게 구성되고 제대로 되어 있다고 생각한다면 문제에 직면하게 됩니다. 대부분의 보안상 취약점은 서버 시스템에 잘못된 데이터가 제공하는 공격자로 인해 발생합니다.

입력이 올바른 형식이라고 무조건 신뢰하는 것은 버퍼 오버런, 사이트 간 스크립트 공격, SQL 주입 공격 등과 같은 문제를 일으킬 수 있습니다.

이러한 잠재적인 공격 문제에 대해 자세히 살펴보겠습니다.



2. 버퍼 오버런 방지

버퍼 오버런은 공격자가 필요 이상으로 규모가 큰 데이터를 응용 프로그램에 제공할 때 발생하는 것으로서 내부 메모리 공간으로 오버플로됩니다. 버퍼 오버런은 C/C++에서 주로 발생하며 일반적으로 쉽게 해결할 수 있습니다. 버퍼 오버런은 명확하지 않은 버퍼 오버런과 수정하기 어려운 버퍼 오버런 두 가지 유형이 있습니다. 개발자는 내부 버퍼보다 큰 데이터가 외부에서 제공되리라고는 생각하지 못했습니다. 오버플로는 메모리의 다른 데이터 구조에 손상을 일으키기 때문에 공격자가 유해한 코드를 실행할 수 있는 경우가 있습니다. 또한 버퍼 언더플로 및 버퍼 오버런은 배열 인덱싱 문제를 유발시키지만 일반적인 것은 아닙니다.

아래의 C++ 코드를 살펴봅시다.



void DoSomething(char *cBuffSrc, DWORD cbBuffSrc) {
char cBuffDest[32];
memcpy(cBuffDest,cBuffSrc,cbBuffSrc);
}

이 코드에서 잘못된 점은 무엇일까요? 사실, cBuffSrc 및 cbBuffSrc가 데이터를 신뢰하지 않은 코드 등 신뢰하는 원본에서 나온 것이고 데이터의 형식과 크기가 올바른지 확인한 것이라면 이 코드에는 잘못된 점이 없습니다. 하지만 해당 데이터가 신뢰되지 않은 원본에서 나온 것이고 유효성 검사를 하지 않은 경우에는 공격자(신뢰되지 않은 원본)가 cBuffDest보다 큰 cBuffSrc를 작성할 수 있으며 cbBuffSrc를 cBuffDest보다 크게 설정할 수도 있습니다. memcpy가 데이터를 cBuffDest에 복사할 때, cBuffDest가 함수의 스택 프레임의 반환 주소 옆에 있고 공격자로 인해 코드가 유해한 작업을 실행하므로 DoSomething에서 반환된 주소에 나쁜 영향을 미칩니다.
이 문제를 해결하는 방법은 사용자 입력과 cBuffSrc 및 cbBuffSrc의 데이터를 신뢰하지 않는 것입니다.

void DoSomething(char *cBuffSrc, DWORD cbBuffSrc) {
const DWORD cbBuffDest = 32;
char cBuffDest[cbBuffDest];
#ifdef _DEBUG
memset(cBuffDest, 0x33, cbBuffSrc);
#endif
memcpy(cBuffDest, cBuffSrc, min(cbBuffDest, cbBuffSrc));
}

이 함수는 제대로 작성된 함수의 세 가지 속성을 보여줍니다. 이 함수는 버퍼 오버런을 완화시킵니다. 첫째, 버퍼 길이를 제공하는 호출자가 필요합니다. 물론 이 값을 무조건 신뢰하지는 말아야 합니다. 둘째, 디버그 빌드에서 소스 버퍼를 포함할 수 있는 충분한 크기인지 확인하기 위해 해당 코드가 버퍼를 검색합니다. 그렇지 않을 경우, 액세스 위반이 발생하며 코드를 디버거에 throw합니다. 이 작업을 실행하는 중에 많은 버그를 볼 수 있습니다. 마지막으로, 가장 중요한 사항으로서 memcpy에 대한 호출은 방어적이기 때문에 버퍼가 소유할 수 있는 데이터만큼만 복사합니다.
Microsoft의 Windows Security Push에서는 C 프로그래머를 위해 안전한 문자열 처리 함수 목록을 만들었습니다. Strsafe.h: Safer String Handling in C 에서 확인하십시오.



3. 사이트 간 스크립팅 방지

사이트 간 스크립팅 취약점은 웹 관련 문제점으로서 단일 웹 페이지의 결함으로 인해 클라이언트의 데이터에 손상을 입힐 수 있습니다. 다음과 같은 ASP.NET 코드 단편이 있다고 가정해 봅시다.

<script language=c#>
Response.Write("안녕하세요, " + Request.QueryString("name"));
</script>

이러한 코드를 본 적이 있습니까? 이 코드는 잘못된 코드입니다. 일반적으로 다음과 같은 URL을 사용하여 이 코드에 액세스합니다.
http://explorationair.com/welcome.aspx?name=Michael

C# 코드에서는 해당 데이터가 항상 올바른 형식이며 이름만 포함한다고 가정합니다. 하지만 공격자는 이 코드를 악용하여 스크립트 및 HTML을 이름으로 제공합니다. 다음 URL을 입력하면
http://northwindtraders.com/welcome.aspx?name=<script>alert('안녕!');
</script>

"안녕!"이라고 표시된 대화 상자가 있는 웹 페이지에 연결됩니다. 사용자가 이와 같은 링크를 클릭하면 어떻게 될까요? 쿼리 문자열에 좋지 않은 스크립트 및 HTML이 있어 사용자의 쿠키를 공격자가 가져가 공격자의 사이트에 게시하게 되면 사용자의 개인 쿠키 정보를 공격자가 갖게 되거나 더 나쁜 상황이 발생하게 됩니다.
이것을 방지할 수 있는 두 가지 방법이 있습니다. 첫째, 입력 내용을 신뢰하지 않고 사용자의 이름이 무엇으로 되어 있는지 신중히 살펴야 합니다. 예를 들어, 일반 식을 사용하여 이름에 일반적인 문자 하위 집합만 포함되어 있으며 크기가 너무 크지 않은지 확인합니다. 다음 C# 코드 조각을 사용하여 이러한 작업을 할 수 있습니다.

Regex r = new Regex(@"^[\w]{1,40}$");

if (r.Match(strName).Success) {
// 좋습니다! 올바른 문자열입니다.
} else {
// 좋지 않습니다! 잘못된 문자열입니다.
}

이 코드에서는 일반 식을 사용하여 문자열에 1과 40 사이의 영숫자만 포함되는지 확인합니다. 이 방법만이 값이 올바른지 확인하는 안전한 방법입니다.
이 일반 식을 통해 HTML 또는 스크립트를 확인할 수 있습니다. 일반 식을 사용하여 잘못된 문자를 찾고, 잘못된 문자가 검색된 경우에는 요청을 거부하는 작업을 하지 말아야 합니다. 왜냐하면 이러한 작업은 실수로 빠뜨리는 경우가 있기 때문입니다.

두 번째 방법은 입력이 출력으로 사용될 때 모든 입력을 HTML 인코딩하는 것입니다. 이렇게 하면 위험한 HTML 태그를 줄일 수 있어 이스케이프 문자를 좀 더 안전하게 할 수 있습니다. Server.HTMLEncode가 있는 ASP 또는 HttpServerUtility.HtmlEncode가 있는 ASP.NET에서 문제가 될 수 있는 모든 스트링을 이스케이프할 수 있습니다.



4. sa 사용 권한 불필요

마지막 입력 트러스트 공격으로 SQL 주입을 설명하겠습니다. 많은 개발자들이 Microsoft SQL Server 또는 Oracle과 같은 백엔드 데이터 저장소와 통신하기 위해 입력이 필요한 코드를 작성하고 해당 입력을 사용하여 SQL 쿼리를 작성합니다.

아래의 코드를 살펴봅시다.

void DoQuery(string Id) {
SqlConnection sql=new SqlConnection(@"data source=localhost;" +
"user id=sa;password=password;");
sql.Open();
sqlstring= "SELECT hasshipped" +
" FROM shipping WHERE id='" + Id + "'";
SqlCommand cmd = new SqlCommand(sqlstring,sql);
•••
이 코드는 세 가지 심각한 결함이 있습니다. 첫째, 시스템 관리자 계정인 sa로 Web Service에서 SQL Server로 연결이 되었습니다. 이 부분이 왜 결함이 있는지 곧 설명하겠습니다. 둘째, sa 계정에 대한 암호로 "password"를 사용했습니다.
그러나 이 코드에 결함이 있는 가장 중요한 원인은 SQL 문을 작성하는 스트링 연결입니다. 사용자가 1001이라는 ID를 입력하면 다음과 같은 올바른 형식의 유효한 SQL 문이 표시됩니다.



SELECT hasshipped FROM shipping WHERE id = '1001'

하지만 공격자는 이보다 훨씬 앞서 생각합니다. 즉, 공격자는 다음과 같은 질의를 실행하는 "'1001' DROP table shipping --" ID를 입력합니다.
SELECT hasshipped FROM
shipping WHERE id = '1001'
DROP table shipping --';

이렇게 하면 질의가 작동하는 방법이 변경됩니다. 이 코드는 전달된 사항이 있는지 확인할 뿐만 아니라 전달 테이블도 삭제합니다. -- 연산자는 SQL의 설명 연산자로서 공격자로 하여금 유효하지만 위험한 일련의 SQL 문을 작성하도록 합니다.
이제 SQL Server 데이터베이스에서 테이블을 삭제하는 방법을 알아보겠습니다. 관리자만이 테이블 삭제 작업을 할 수 있습니다. 하지만 여기에서는 sa로 데이터베이스에 연결하여 sa가 SQL Server 데이터베이스에서 어떤 작업도 할 수 있습니다. sa로 응용 프로그램에서 SQL Server로 연결하지 않아야 합니다. 대신 적합한 Windows 통합 인증을 사용하거나 적절히 제한된 권리가 있는 미리 정의된 계정으로 연결해야 합니다.

SQL 주입 문제를 쉽게 해결할 수 있습니다. 아래의 코드에서는 SQL 저장 프로시저 및 매개 변수를 사용하여 이러한 쿼리를 작성하는 방법을 보여줍니다. 또한 이 코드에서는 4자리와 10자리 사이의 숫자로 된 전달 ID만 검색하므로 일반 식을 사용하여 입력이 유효한지 확인하는 방법을 보여줍니다.



Regex r = new Regex(@"^\d{4,10}$");
if (!r.Match(Id).Success)
throw new Exception("잘못된 ID");

SqlConnection sqlConn= new SqlConnection(strConn);
string str="sp_HasShipped";
SqlCommand cmd = new SqlCommand(str,sqlConn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("@ID",Id);

버퍼 오버런, 사이트 간 스크립트, SQL 주입 공격은 입력 트러스트의 예입니다. 이러한 공격에서 모든 입력 사항이 올바른 것이라고 증명될 때까지는 잘못된 것이라고 믿어야 합니다.


5. Crypto 코드 주의

이제 우리가 잘 알고 있는 사항에 대해 살펴봅니다. 우리가 검토하는 보안 코드의 30% 이상에 보안상의 결함이 있습니다. 가장 일반적인 결함은 현지 암호화 코드로서 이 코드는 대체로 매우 취약하여 공격당하기 쉽습니다. 사용자 자신의 암호화 코드를 올바르게 사용할 수 없으므로 작성하지 말아야 합니다. 사람들이 이해하지 못하는 암호화 알고리즘을 작성했기 때문은 아닙니다. 공격자들은 디버거에 액세스하고 시간 및 지식을 들여 시스템이 정확히 어떻게 작동하는지 결정하며 몇 시간만에 시스템을 공격하는 경우가 있습니다. 따라서 Win32 응용 프로그램용 CryptoAPI를 사용하여 System.Security.Cryptography 네임스페이스에 제대로 작성되고 올바르게 테스트된 암호화 알고리즘이 있도록 해야 합니다.



6. 사용자의 공격 프로필 감소

클라이언트 중 90%가 필요로 하지 않는 기능은 기본적으로 설치되어서는 안됩니다. 인터넷 정보 서비스(IIS) 6.0은 이러한 설치 계획을 따릅니다. 이에 대한 자세한 내용은 이달에 발행된 Wayne Berry의 기사 "Innovations in Internet Information Services Let You Tightly Guard Secure Data and Server Processes"를 참조하십시오. 이 설치 방법 이면에 있는 생각은 사용하지 않는 서비스를 실행하여 사용자가 관심을 두지 않는 서비스를 활용할 수 있다는 것입니다. 이 기능이 기본적으로 설치된 경우, 최소한의 권한 원칙 아래 작동되어야 합니다. 다시 말해, 관리 권한이 필요하지 않으면 관리 권한으로 응용 프로그램을 실행할 필요는 없습니다. 이 주의 사항을 유념해야 합니다.



7. 최소한의 권한 원칙 사용

운영 체제 및 공용 언어 런타임(CLR)에는 몇 가지 이유로 인한 보안 정책이 있습니다. 많은 사람들이 보안 정책이 존재하는 이유는 사용자가 의도적으로 유해한 일 즉, 액세스가 허용되지 않는 파일에 액세스하고 필요에 따라 임의로 네트워크를 재구성하거나 기타 유해한 행위를 하지 못하도록 방지하는 것이라고 생각합니다. 일반적인 내부 공격을 방지해야 한다는 것도 한 가지 이유가 될 수 있지만 보안 정책을 강력히 유지해야 하는 또 다른 이유가 있습니다. 보안 정책은 코드 주변에 방어벽을 쌓아 사용자가 고의 또는 흔히 실수로 네트워크에 해를 주지 않도록 하는 것입니다. 예를 들어, Alice라는 사람의 시스템에서 전자 메일을 통해 다운로드하여 실행한 첨부 파일은 Alice가 액세스할 수 있는 리소스에만 액세스할 수 있도록 제한됩니다. 첨부 파일에 트로이 목마가 있는 경우 좋은 보안 정책이 시스템의 손상을 제한하게 됩니다.

서버 응용 프로그램을 설계, 작성 및 배포할 때, 적합한 사용자만 요청을 보낼 수 있는 것은 아닙니다. 공격자가 악의를 가지고 잘못된 요청을 보내면 사용자의 코드가 오동작을 일으키는 경우, 사용자는 손상을 제한하기 위해 응용 프로그램에 가능한 모든 방어벽을 쌓아야 합니다. 회사가 보안 정책을 실행하는 이유가 그저 사용자 또는 사용자의 코드를 신뢰하지 않기 때문만이 아니라는 것입니다. 외부인에 의해 이용되는 코드에 대해 보호하려는 목적도 있습니다.

최소한의 권한 원칙이란 주어진 권한은 최소한 필요한 만큼의 시간동안 최소한 필요한 만큼의 코드에만 허용되어야 한다는 것입니다. 다시 말해, 언제든지 지정된 시간에 가능한 코드에 방어벽을 많이 쌓아야 합니다. 문제가 발생했을 때 미리 쌓아놓은 이러한 방어벽이 도움이 될 것입니다. 따라서 최소한의 권한으로 코드를 실행하는 데 대한 몇 가지 구체적인 생각을 설명하겠습니다.

작업을 완료하는 데 필요한 리소스에만 액세스를 허용하는 서버 코드에 대한 보안 컨텍스트를 선택합니다. 일부 코드에 더 많은 권한이 필요하면 해당 코드를 계수화하여 더 높은 수준의 권한으로 실행합니다. 다른 운영 체제 자격 증명으로 실행하는 코드를 안전하게 분리하려면 더 많은 권한이 부여된 보안 컨텍스트에서 실행되는 별도의 프로세스로 이 코드를 실행하면 됩니다. 즉, COM 또는 Microsoft .NET 원격 등과 같은 프로세스 간 통신이 필요하며 최소한의 왕복을 유지하기 위해 해당 코드에 대한 인터페이스를 디자인해야 합니다.

.NET Framework를 사용하여 코드를 어셈블리에 계수화하는 경우, 각 코드 조각에 필요한 수준의 권한을 고려해야 합니다. 높은 권한이 필요한 코드를 더 많은 사용 권한을 허용할 수 있는 별도의 어셈블리에 쉽게 분리할 수 있습니다. 이렇게 하면 적은 권한으로도 대부분의 어셈블리를 실행할 수 있어 코드에 더 많은 방어벽을 추가할 수 있습니다. 그림 1과 같이 어셈블리 수준 사용 권한 요청을 통해 특정 어셈블리의 권한을 쉽게 제한할 수 있습니다. 그림 2는 이러한 사용 권한 요청에 사용하는 XML 파일을 작성하는 방법을 보여줍니다. 이 작업을 하는 경우, 코드 액세스 보안(CAS) 스택 워크로 인해 사용자 자신의 어셈블리의 사용 권한만 제한하는 것이 아니라 호출한 모든 어셈블리의 사용 권한도 제한하는 것입니다.

많은 사람들이 제품이 테스트되고 배달된 후에 새로운 구성 요소가 플러그 인될 수 있도록 응용 프로그램을 작성합니다. 버그 및 보안 허점을 찾기 위해 모든 코드 경로를 테스트할 수 있는 방법은 없기 때문에 이러한 응용 프로그램을 보호하는 것은 매우 어려운 일입니다. 하지만 응용 프로그램이 관리되는 경우에는 CLR이 제공하는 기능을 사용하여 이러한 확장성을 잠급니다. 사용 권한 개체 또는 사용 권한 집합을 선언하고 PermitOnly 또는 Deny를 호출하여, 호출하는 모든 코드에 허용된 사용 권한을 제한하는 스택에 표식을 추가합니다. 일부 플러그 인을 호출하기 전에 이 작업을 실행하여 플러그 인이 할 수 있는 작업을 제한할 수 있습니다. 예를 들어, 지불 계산을 해야 하는 플러그 인은 파일 시스템에 액세스할 필요가 없습니다. 이것은 최소한의 권한의 다른 예로서 사용자 자신을 미리 보호할 수 있습니다. 이러한 제한 사항을 문서화하고 많은 권한이 주어진 플러그 인은 어셜션 문이 있는 이러한 제한을 극복할 수 있다는 것을 명심해야 합니다.



8. 실패 모드에 주의

오류 처리 코드를 작성하기 싫어하는 것을 인정해야 합니다. 코드가 실패하는 경우는 많습니다. 코드가 실패하면 실망하는 것은 당연한 것입니다. 우리 자신을 포함해서 대부분의 프로그래머들은 일반적인 실행 경로에 초점을 맞추려 합니다. 실제 작업이 그 곳에서 이루어지기 때문입니다. 가능한 신속하고 손쉽게 오류를 처리한 다음 실제 코드의 다음 줄로 이동하는 것은

아쉽게도 안전한 사고 방식이 아닙니다. 우리는 코드의 실패 모드에 많은 주의를 기울여야 합니다. 이러한 코드 조각들은 세부적인 사항에 주의를 기울이지 않고 작성한 것으로서 완전히 테스트되지 않습니다. 모든 사소한 오류 처리기를 포함하여 함수의 모든 코드 줄에서 디버거를 단계별로 실행한 때가 마지막으로 언제인지 생각해 보십시오.

테스트되지 않은 코드는 보안 취약성을 유발시킬 수 있습니다. 이 문제를 완화할 수 있는 세 가지 방법이 있습니다. 첫째, 일반 코드에 대해 많은 신경을 쓰는 만큼 사소한 오류 처리기에도 많은 관심을 두어야 합니다. 오류 처리 코드가 실행되었을 때의 시스템 상태를 생각해 보십시오. 시스템을 보안 상태로 두시겠습니까? 둘째, 함수를 작성하면 함수에서 단계별로 디버거를 실행하여 모든 오류 처리기를 확실히 찾아야 합니다. 이 기술로도 미묘한 시간 오류는 해결하지 못할 수도 있습니다. 유해한 인수를 함수에 전달하거나 오류 처리기를 실행하도록 하는 방법으로 시스템 상태를 조정해야 하는 경우도 있습니다. 충분한 시간적 여유를 가지고 코드를 단계별로 실행하여, 시스템이 실행될 때 시스템 상태 및 코드를 천천히 살펴보십시오. 디버거의 코드를 단계별로 실행하여 프로그래밍 논리에서 많은 결함을 찾아 왔습니다. 따라서 이 기술은 입증된 기술입니다. 이 기술을 사용하십시오. 셋째, 테스트로 인해 함수가 실패하도록 하십시오. 테스트가 함수에 있는 모든 코드 줄마다 실행하도록 하십시오. 이러한 작업을 통해, 특히 테스트를 자동화하여 모든 빌드 후에 실행하면 회귀를 찾는 데 도움이 됩니다.

실패 모드에 대한 매우 중요한 사실이 하나 더 있습니다. 코드가 실패할 경우, 시스템은 최상의 보안 상태에 있게 됩니다. 다음은 유해 코드의 예입니다.



bool accessGranted = true; // 낙관적
try {
// c:\test.txt에 액세스했는지 확인합니다.
new FileStream(@"c:\test.txt",
FileMode.Open,
FileAccess.Read).Close();
}
catch (SecurityException x) {
// 액세스가 거부되었습니다.
accessGranted = false;
}
catch (...) {
// 다른 일이 발생했습니다.
}

CLR의 경우 해당 파일에 대한 액세스를 허용했다고 가정해 봅시다. 이 경우, SecurityException이 throw되지 않습니다. 하지만 파일의 임의 액세스 컨트롤 목록(DACL)에 액세스할 수 없는 경우를 예로 들어 봅시다. 이러한 경우 다른 유형의 예외가 throw됩니다. 하지만 첫 번째 코드 줄에서 낙관적 가정을 했기 때문에 이 사실을 알 수가 없습니다.
더 좋은 방법은 이 코드를 비관적으로 작성하는 것입니다.

bool accessGranted = false; // 비관적
try {
// c:\test.txt에 액세스했는지 확인합니다.
new FileStream(@"c:\test.txt",
FileMode.Open,
FileAccess.Read).Close();
// 이 모드에 계속 있는 경우 성공한 것입니다.
accessGranted = true;
}
catch (...) {}

이 방법은 실패를 하더라도 가장 안전한 모드로 다시 이동하기 때문에 훨씬 더 확실한 방법입니다.


9. 취약한 가장

서버 응용 프로그램을 작성할 때 가장이라고 하는 Windows의 편리한 기능을 직접적으로나 간접적으로 사용하는 경우가 있습니다. 가장이라는 기능을 사용하면 프로세스의 각 스레드를 별도의 보안 컨텍스트(일반적으로 클라이언트의 보안 컨텍스트)에서 실행할 수 있습니다. 예를 들어, 파일 시스템 리디렉터가 네트워크를 통해 파일 요청을 받으면, 파일 시스템 리디렉터가 원격 클라이언트를 인증하고 클라이언트의 요청이 공유 DACL에 위배되지 않는지 확인한 다음, 요청을 처리하는 스레드에 클라이언트 토큰을 첨부하여 클라이언트를 가장합니다. 이 스레드는 클라이언트의 보안 컨텍스트를 사용하여 서버의 로컬 파일 시스템에 액세스할 수 있습니다. 이 방법은 로컬 파일 시스템이 안전하게 되므로 편리합니다. 또한 요청할 액세스 유형, 파일의 DACL, 스레드의 가장 토큰을 고려하여 액세스 확인을 합니다. 액세스 확인이 실패하면 로컬 파일 시스템은 이 사항을 파일 시스템 리디렉터에게 보고합니다. 파일 시스템 리디렉터는 이 결함을 다시 원격 클라이언트에게 보냅니다. 이 기능은 로컬 파일 시스템에 책임을 전가하여 마치 클라이언트가 로컬인 것처럼 로컬 파일 시스템이 액세스 확인을 하도록 하기 때문에 파일 시스템 리디렉터에게는 매우 편리한 기능입니다.

이 기능은 파일 시스템 리디렉터와 같은 간단한 게이트웨이에 유용합니다. 하지만, 가장은 좀 더 복잡한 응용 프로그램에서 다르게 사용되는 경우가 있습니다. 웹 응용 프로그램을 예로 들어 봅시다. Web.config 파일에 아래와 같이 지정하는 관리되지 않는 기본 ASP 응용 프로그램, ISAPI 확장명 또는 ADP.NET 응용 프로그램을 작성하는 경우,

<identity impersonate='true'>

프로세스 토큰과 스레드 토큰 두 개의 다른 보안 컨텍스트가 있는 환경에서 실행하게 됩니다. 일반적으로 스레드 토큰은 액세스 확인을 위해 사용됩니다(그림 3 참조). 웹 서버 프로세스에서 실행되는 ISAPI 응용 프로그램을 작성한다고 가정해 봅시다. 스레드 토큰은 IUSR_MACHINE과 같이 되며 대부분의 요청은 인증되지 않습니다. 그러나 프로세스 토큰은 SYSTEM입니다! 버퍼 오버플로 활용을 통해 코드가 공격당했다고 합시다. 공격자는 IUSR_MACHINE으로 실행하는 것으로는 만족하지 않을 것입니다. 공격자의 권한 수준을 높이기 위해 공격 코드가 RevertToSelf를 호출하여 가장 토큰을 제거할 가능성이 많습니다. 이 경우, 공격자의 의도대로 될 수 있습니다. 또한 공격자는 CreateProcess를 호출할 수도 있습니다. 이렇게 되면 새 프로세스에 대한 토큰이 가장 토큰에서가 아닌 프로세스 토큰에서 복사되어 새 프로세스가 시스템으로 실행됩니다.



그림 3 확인



이 문제를 해결할 수 있는 방법은 무엇일까요? 우선 버퍼 오버플로가 발생하지 않도록 해야 하며 최소한의 권한 원칙을 명심해야 합니다. SYSTEM에 제공했던 강력한 권한이 코드에는 필요하지 않은 경우, 웹 응용 프로그램이 웹 서버 프로세스 내에서 실행되도록 구성하지 마십시오. 웹 응용 프로그램이 중간 또는 높은 격리 수준으로 실행되도록 간단히 구성하는 경우, 프로세스 토큰은 IWAM_MACHINE이 됩니다. 이렇게 하면 사실상 사용자는 권한을 전혀 가질 수 없게 되며 이러한 종류의 공격은 거의 영향을 미치지 않습니다. Windows .NET Server의 구성 요소가 될 IIS 6.0에서 사용자 작성 코드는 기본적으로 SYSTEM으로 실행되지 않습니다. 이 작업은 개발자가 범하는 실수를 바탕으로 한 것이며, 웹 서버가 코드에 주어진 권한을 줄이기 위해 제공할 수 있는 지원은 코드에 보안 버그가 있을 때 유용합니다.

COM 프로그래머가 실행할 수 있는 또 다른 작업이 있습니다. COM은 스레드에 나쁜 영향을 주는 경향이 있습니다. 스레딩 모델이 호출 스레드와 일치하지 않는 in-process COM 서버를 호출하는 경우, COM이 다른 스레드에 대한 호출을 실행합니다. COM은 호출자 스레드의 가장 토큰을 전파하지 않기 때문에 해당 호출이 호출 스레드가 아닌 프로세스의 보안 컨텍스트로 실행됩니다.

가장 기능으로 인해 곤란한 경우가 또 있습니다. 명명된 파이프, DCOM 또는 RPC를 통해 요청을 허용하는 서버가 있다고 합시다. 클라이언트를 인증하고 가장하며 가장하는 동안 클라이언트 대신 커널 개체를 엽니다. 클라이언트가 연결을 끊을 때 이러한 개체(예: 파일) 중 하나를 닫는 것을 잊었다고 가정합시다. 다음 클라이언트가 연결되면, 해당 클라이언트를 인증하고 가장합니다. 그러면 어떻게 될까요? 이전 클라이언트에서 "누설된" 파일을 여전히 액세스할 수 있으며, 새 클라이언트가 해당 파일에 액세스가 허용되지 않은 경우에도 마찬가지입니다. 성능상의 이유로 개체를 처음 열 때 개체의 액세스 확인은 커널만 실행합니다. 나중에 다른 사람을 가장하기 때문에 보안 컨텍스트가 변경되더라도 이 파일에는 액세스할 수 있습니다.

지금까지 설명한 내용은 가장 기능이 서버 개발자들에게는 편리한 기능이며 취약점이 있다는 것입니다. 가장 토큰으로 실행하는 경우 코드에 특별히 신경써야 합니다.



10. 관리자가 아닌 사용자가 실제로 사용할 수 있는 응용 프로그램 작성

이 응용 프로그램 작성은 최소한의 권한 원칙을 사용한 실제 결과입니다. 프로그래머들이 Windows에서 제대로 작동하지 않는 코드를 계속 작성한다면 관리자가 아닌 사용자들은 "불안전한" 시스템이 표적이 되었다는 사실을 알아차리지 못할 것입니다. Windows에는 매우 견고한 보안 기능 집합이 있지만 사용자들이 관리자의 작업을 해야 한다면 사용자에게 이러한 기능은 소용이 없습니다.

이 문제를 어떻게 해결할 수 있을까요? 우선, 스스로 해결해야 합니다. 사용자 자신이 관리자로 실행하지 말아야 합니다. 보안 설계가 되지 않은 프로그램을 사용하는 것이 매우 어려운 일이라는 것을 곧 알게 될 것입니다. 얼마 전에, 저(Keith)는 데스크톱과 장치 간에 데이터를 동기화하도록 디자인된 핸드헬드 장치의 제조업체가 제공하는 몇몇 소프트웨어를 설치했습니다. 항상 그랬듯이 일반 사용자 계정으로 로그오프하고 기본으로 제공되는 관리자 계정으로 다시 로그인하여 소프트웨어를 설치한 다음, 제 일반 계정으로 다시 로그인하여 소프트웨어를 실행했습니다. 필요한 일부 데이터 파일에 액세스할 수 없다는 내용의 대화 상자가 나타난 다음 액세스 위반 처리가 진행되었습니다. 놀라운 것은 이 소프트웨어가 핸드헬드 장치의 주요 공급업체의 것이었습니다. 여기에 대해서는 변명의 여지가 없습니다.

http://sysinternals.com 에서 FILEMON을 실행해 본 결과, 해당 응용 프로그램이 실행 파일과 동일한 디렉터리에 설치된 쓰기 액세스에 대한 데이터 파일을 열려고 했던 것을 알았습니다. 응용 프로그램이 Program Files 디렉터리에 설치되면 해당 디렉터리에 데이터를 작성하지 말아야 합니다. Program Files에 제한된 액세스 컨트롤 정책이 있기 때문입니다. 사용자가 그러한 디렉터리에 작성하는 것은 바람직하지 않습니다. 왜냐하면 이렇게 하면 트로이 목마를 다른 사용자가 실행하게 되기 때문입니다. 사실 이 규정은 Windows XP에 대한 기본 로고 요구 사항의 일부입니다(http://www.microsoft.com/winlogo 참조).

많은 프로그래머들이 코드를 개발할 때 관리자로 실행하는 이유가 있습니다. 하지만 이 문제를 계속해서 무시한다면 상황은 더욱 나빠질 것입니다. 텍스트 파일을 편집하는 데에는 관리자 권한이 필요하지 않습니다. 또한 시작한 프로그램을 컴파일 또는 디버그하는 데에는 관리자 권한이 필요하지 않습니다. 관리자 권한이 필요하면, 운영 체제의 RunAs 기능을 사용하여 향상된 권한으로 개별 프로그램을 실행하십시오(2001년 11월자 Security Briefs 칼럼 참조). 개발자가 사용할 도구를 작성하는 경우 커뮤니티에 대한 책임도 있습니다. 이렇게 관리자만 실행할 수 있는 코드를 계속해서 작성하는 악순환을 중단해야 하며, 그렇게 하려면 일반 사용자들의 수준에서 코드를 작성하는 것입니다.

관리자가 아닌 사용자로 쉽게 실행할 수 있는 방법에 대해서는 Keith의 웹 사이트인 http://www.develop.com/kbrown 을 참조하십시오. 또한 비관리 환경에서도 제대로 실행되는 응용 프로그램을 작성하는 방법에 대해 설명한 Michael의 저서 보안 코드 작성(Microsoft Press, 2001년)의 사본을 살펴보십시오.



발췌 : Michael Howard와 Keith Brown의 칼럼 중 / 마이크로소프트

+ Recent posts