다른 서비스
NEW
기획
디자인
개발
프로덕트
아웃소싱
프리랜싱
비즈니스
최근 검색어
전체 삭제
최근 검색어가 없습니다.

개발

시큐어 코딩의 의미와 실천 방안: ②시큐어 코딩 점검 유형

 

지난 1편에서는 시큐어 코딩의 의미와 구체적인 가이드라인에 대해 알아봤습니다. 또한 시큐어 코딩을 실천하기 위한 대표적인 유형 중 첫 번째 입력값 검증 및 표현의 내용과 샘플 코드를 통한 예제를 소개했습니다. 이번 글에서는 시큐어 코딩을 실천하기 위한 점검 유형에 대해 좀 더 살펴보겠습니다.

 

시큐어코딩
<출처: freepik>

 

시큐어 코딩, 어떻게 수행할 것인가?

2) 보안 기능

소프트웨어 개발 구현 단계에서 코딩하는 기능의 인증, 접근제어, 기밀성, 암호화 등이 올바르게 구현되는지 확인합니다. 암호처럼 중요한 정보를 암호화 없이 저장하거나, 프로그램 내부에 하드코딩 되어 노출의 위험성이 있는 경우 이를 제거하는 활동입니다. 또한 비인가 접근을 방어하고 저장된 정보를 암호화하여, 취약한 기능이 존재하지 않도록 하는 것도 보안 기능의 중요한 활동입니다.

 

대표적으로 악성 파일 업로드 공격에 대비하는 것이 있습니다. 업로드할 파일의 개수, 파일 크기, 파일 이름의 확장자 등의 유효성 검사를 하지 않고, 파일시스템에 저장할 경우 공격자에 의해 악성코드, 쉘 코드 등 위험한 형식의 파일을 시스템에 업로드할 수 있습니다. 이 문제의 해결은 기본적으로 업로드 이전에 파일 검증을 수행해야 하고, 업로드 이후에는 사용자가 파일 업로드 시, 해당 파일을 실행하면서 파일명과 확장자를 추측할 수 없도록 문자열 값을 변형해 저장합니다.

 

아래 예제는 정해진 규칙에 의해서만 파일 업로드가 가능하도록, 파일의 검증 로직을 구현한 파이썬 예제입니다.

import os
from django.shortcuts import render
from django.core.files.storage import FileSystemStorage

# 업로드 하는 파일에 대한 개수, 크기, 확장자 제한
FILE_COUNT_LIMIT = 5
# 업로드 하는 파일의 최대 사이즈 제한 예 ) 5MB - 5*1024*1024
FILE_SIZE_LIMIT = 5242880
# 허용하는 확장자는 화이트리스트로 관리한다.
WHITE_LIST_EXT = [
'.jpg',
'.jpeg'
]

def file_upload(request):
    # 파일 개수 제한
    if len(request.FILES) == 0 or len(request.FILES) > FILE_COUNT_LIMIT:
        return render(request, '/error.html', {'error': '파일 개수 초과'})

    for filename, upload_file in request.FILES.items():
        # 파일 타입 체크
        if upload_file.content_type != 'image/jpeg':
            return render(request, '/error.html', {'error': '파일 타입 오류'})
        # 파일 크기 제한
        if upload_file.size > FILE_SIZE_LIMIT:
            return render(request, '/error.html', {'error': '파일사이즈 오류'})
        # 파일 확장자 검사
        file_name, file_ext = os.path.splitext(upload_file.name)
        if file_ext.lower() not in WHITE_LIST_EXT:
            return render(request, '/error.html', {'error': '파일 타입 오류'})

    fs = FileSystemStorage(location='media/screenshot', base_url = 'media/screenshot')
    for upload_file in request.FILES.values():
        fs.save(upload_file.name, upload_file)
    return render(request, '/success.html', {'filename': filename})

 

사용자와의 상호작용이 잦은 웹 사이트의 개발 경우, 입력값 검증을 위한 코드를 서버 측 프로그래밍 언어에 포함할지, 자바스크립트 같은 클라이언트 측 프로그래밍 언어에 포함할지에 따라, 보안 수준이 달라집니다. 일반적으로는 서버 측과 클라이언트 측 필터링이 모두 필요합니다. 클라이언트 측 필터링의 경우, 공격자가 웹 인터셉트 프로그램으로 이를 무력화할 수 있고 브라우저상으로 클라이언트 측 스크립트가 노출되어, 이에 대한 해독과 공격이 가능하기 때문입니다.

 

화이트리스트 방식으로 필터링을 수행하면, 필요한 특정 입력값만 받아들이고 그 외의 모든 값은 차단하는 방식을 사용합니다. 예를 들어, JPG, JPEG 외의 파일 확장자는 모두 차단하는 것입니다. 블랙리스트 방식 필터링은 악성 패턴만 필터링하며, 대부분의 입력값은 허용하고 일부 특정 입력값만 차단할 때 사용합니다.

 

3) 시간 및 상태

시간 및 상태 유형은 동시 또는 거의 동시 수행을 지원하는 병렬 시스템이나, 하나 이상의 프로세스가 동작하는 환경에서 이루어집니다. 시간 및 상태를 부적절하게 관리하여 발생할 수 있는 보안 취약점을 예방하기 위한 단계입니다. 하나의 자원을 다수의 프로세스가 사용해야 하는 경우, 자원 공유가 적절히 진행되지 않아 프로그램 로직에 이상이 생기는 것을 예방해야 합니다.

 

먼저 레이스 컨디션(Race Condition)은 두 개 이상의 프로세스가 공용 자원을 동시에 Read/Write 할 때, 공용 자원에 대한 접근이 어떤 순서에 따라 이루어졌는지에 따라 그 실행 결과가 달라집니다. 그러니 소프트웨어의 특성을 이해하고 이를 준비해야 합니다. 예를 들어, 파일의 존재를 확인하는 부분과 실제 파일을 사용하는 부분에서 시간차가 발생할 수 있습니다. 즉 파일을 읽는 작업이 수행되는 것과 동시에, 파일 삭제가 실행되면 프로그램이 예상치 못한 흐름으로 진행될 수 있습니다.

 

공통 파일 같은 공유 자원을 여러 스레드가 접근하여 사용하는 경우, 동기화 구문을 이용하여 한 번에 하나의 스레드만 접근할 수 있게 프로그램을 작성해야 합니다. 또한 성능에 최소한의 영향을 주기 위해 임계 코드만을 동기화 구문으로 만드는 것이 중요합니다.

 

...
  Public synchronized void run() {
    Try {
       If (iFileMode == MODE_READ) {
          File f = new file("test1.txt");
          If (f.exists()) {
              BufferedReader br = new BufferedReader (new FIleReader(f));
              Br.close();
          }
      }
      Else if (iFileMode == MODE_DELETE) {
          File f = new file("test1.txt")
               If (f.exists()) {
                   f.delete();
              }
      }
    }
}

 

위 코드는 synchronized method를 사용하여, 클래스의 인스턴스에 lock을 걸고 사용하는 패턴을 보여줍니다. 이에 따라 동일한 프로세스가 동일한 파일을 작업하는 것을 방지할 수 있습니다.

 

4) 에러 처리

에러를 처리하는 방식이 부적절하거나, 누락되어 발생하는 보안 항목을 관리하는 단계입니다. 공격자는 소프트웨어에서 처리하지 못한 에러 발생 시, 이때 생성되는 에러 메시지를 통해 오히려 시스템과 관련된 중요 정보를 획득할 수도 있습니다.

 

취약한 코드

...
} catch (IllegalAccessException e) {
    e.printStackTrace();
...

 

대응 방법

...
} catch (IllegalAccessException e) {
    Log.error(“Illegal Access”);
...

 

오류가 발생하는 상황을 적절히 예방하지 않았거나, 잘못 처리한 경우도 이 항목에 해당합니다. 개발 단계에서 빠르고 쉬운 디버깅을 위해 많은 메시지를 담은 에러 처리 메시지를 구현했다면, 운영 환경에서는 이 부분이 드러나지 않도록 코드를 리팩토링 하거나, 에러 코드 대신 사용자에게 어떤 에러가 발생하였는지 알려주는 문구를 출력하는 등의 처리가 필요합니다.

 

5) 코드 오류

개발자의 실수나 개발 지식의 부족으로 인한 오류를 예방하기 위한 점검 단계입니다. 프로그래밍 언어 특성상 주로 자료형(data type)을 변환할 때 발생하는 오류 혹은 자원 반환, NullPointer 참조가 대표적인 예제입니다.

 

Null 값을 체크하지 않고 선언된 변수를 무턱대고 사용하거나, 스레드와 같은 자원을 무한하게 할당하여 시스템에 부하가 발생하는 것도 코드 오류의 일종입니다. 개발자가 잘못된 코딩 습관을 개선하지 않는다면, 코드 오류 항목에서 반복적으로 보안 취약점이 발생하게 됩니다. 따라서 개발팀 내 정해진 보안 코딩 규칙을 만들어, 이에 따라 개발 및 테스트를 수행하는 것이 중요합니다.

 

//안전하지않은 코드
public void checknull()
{
    String cmd = System.getProperty("cmd");
    //cmd의 Null값 여부를 확인하지 않는다.
    cmd = cmd.trim();
    System.out.println(cmd);
} 
.....

//안전한 코드
public void checknull()
{
    String cmd = System.getProperty("cmd");
    //cmd의 Null값 여부를 확인한다.
    if (cmd != null)
    {
        cmd = cmd.trim();
        System.out.println(cmd);
    }
    else System.out.println("null command");
.....

 

위 예제는 Null을 제대로 확인하지 않아 코드 오류 취약점을 가진 코드와 이를 안전하게 변경한 코드 예제입니다. 코드에서 cmd 프로퍼티가 항상 정의되어 있다고 가정하지만, 만약 공격자가 프로그램 환경을 제어해 cmd 프로퍼티가 정의되지 않게 하면, cmd는 Null이 되어 trim() 메서드를 호출 시 Null 포인터 예외 현상이 발생하게 됩니다. 이를 방지하기 위해서는 특정 프로퍼티의 로직 수행 시, 반드시 Null 값 여부를 습관적으로 확인하는 코드가 필요합니다.

 

6) 캡슐화

캡슐화(Encapsulation)는 객체 지향 방법론에 중요한 개념으로 객체와 필드 변수의 은닉을 통해, 외부로부터의 오용과 남용을 막는 단계입니다. 데이터나 기능을 불충분하게 캡슐화한다면 보안 취약점으로 작용하는 경우가 발생할 수 있는데, 이때 시스템의 중요 정보가 노출될 수 있습니다. 부적절한 캡슐화는 정보 은닉의 기능을 상실할 뿐만 아니라, 변수 혹은 함수가 외부에 노출되면 이를 통해 외부에서 직접적인 수정이 가능하기 때문에 조심해야 합니다.

 

//안전하지않은 코드
public class printName extends HttpServlet
{
    private String name;
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        name = request.getParameter("name");
        ...
        out.println(name + ", thanks for visiting!");
    }
}

//안전한 코드
public class printName extends HttpServlet
{
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        //지역변수로 설정한다.
        String name = request.getParameter("name");
        if (name == null || "".equals(name)) return;
        out.println(name + ", thanks for visiting!");
    }
}

 

위 예제 코드에서는 두 사용자가 거의 동시에 접속할 때, 첫 번째 사용자를 위한 스레드가 out.println()을 수행하기 전에 두 번째 사용자의 스레드가 name = ... 을 수행한다면, 이전 사용자는 그다음 사용자의 정보 - name을 볼 수 있게 됩니다. 이런 경우에 지역 변수를 선언하여 사용하면 해당 보안 취약점을 코드에서 제거할 수 있습니다.

 

7) API 오용

DNS 시스템의 보안 취약점
DNS 시스템의 보안 취약점 <출처: 작가>

 

API(Application Programming Interface)는 많은 애플리케이션에서 사용할 수 있도록 운영체제나 프로그래밍 언어가 자주 사용하는 기능을 제공하는 인터페이스입니다. API 덕분에 개발자는 편리하고 빠르게 개발할 수 있고, 반복적인 작업을 줄일 수 있습니다. 그러나 의도된 내용과 다르게 API를 이용하거나 보안에 취약한 API를 사용한다면, 이는 전체적인 소프트웨어의 심각한 보안 취약점을 유발할 수 있습니다.

 

공격자는 API 도메인을 관리하는 DNS 캐시 오염을 유발하거나, 올바르지 않은 값을 응답하도록 조작하는 방식의 공격도 가능합니다. DNS가 오염된 상황에서 사용되는 API는 공격자의 서버로 트래픽이 경유하거나, 공격자의 서버를 도착지로 인식할 수 있습니다. 이를 방지하기 위해 보안에 취약한 API 사용은 피해야 하며, DNS 조회가 완료된 API 서비스의 IP 소유주를 확인하는 것도 중요합니다.

 

또한 API 자체의 보안을 위한 웹 방화벽(WAF) 규칙을 추가하거나, API Rate Control을 통한 API 사용의 남용을 소프트웨어 외부에서 막는 방법도 고민해 볼 수 있습니다.

 

시큐어 코딩 실천하기

소프트웨어 보안 목표는 문제없는 소프트웨어 서비스를 제공해, 사업을 성공적으로 운영하고 정보 자원의 기밀성, 무결성, 가용성을 유지하는 것입니다. 이 목표를 달성하기 위해 시큐어 코딩의 적용이 요구됩니다.

 

지금까지 두 편에 걸쳐 시큐어 코딩의 7가지 점검 유형과 예제를 살펴봤습니다. 이 글을 통해 시큐어 코딩의 기본적인 내용을 이해하고, 정부의 가이드 문서를 참고하여 소프트웨어 보안 취약점을 완화할 수 있는 기술적 점검 항목이 여러분의 소프트웨어에 올바르게 적용되었는지 진단해 보시기 바랍니다.

 

요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.

댓글 0

테크유람

테크유람(https://blog.naver.com/techtrip) 블로그를 운영하고 있으며 클라우드, 오픈 소스, 웹 성능과 선진적인 DevOps 적용에 관심이 많은 블로거입니다.

삼성SDS에서 웹 개발자로 IT 경력을 시작하였고 이후 마이크로소프트에서는 Xbox 게임 타이틀 개발을, 현재는 클라우드 서비스를 제공하는 IT 기업에서 아시아 지역의 기술 컨설팅을 담당하고 있습니다.

12권의 IT 도서의 저자이기도 하며 글과 콘퍼런스, 교육을 통해 다양한 IT 기술을 많은 이에게 전달하고 공유하며, 상호 작용하는 IT 엔지니어의 삶을 즐기고 있습니다.

같은 분야를 다룬 글들을 권해드려요.

요즘 인기있는 이야기들을 권해드려요.

일주일에 한 번!
전문가들의 IT 이야기를 전달해드려요.

[구독하기] 버튼을 누르면 개인정보 처리방침에 동의됩니다.

일주일에 한 번! 전문가들의 요즘IT 이야기를 전달해드려요.

[구독하기] 버튼을 누르면 개인정보 처리방침에 동의됩니다.