hongcheol / CS-study

cs지식을 정리하는 공간
MIT License
248 stars 30 forks source link

시큐어 코딩 #142

Open jslee7420 opened 3 years ago

jslee7420 commented 3 years ago

1. 시큐어(secure coding) 코딩이란?

개발자의 실수나 논리적 오류로 인해 SW에 내포될 수 있는 보안취약점을 배제하여 안전한 소프트웨어를 개발하는 SW개발 기법

2. 시큐어 코딩 가이드

취약점을 모두 고려하는 프로그래밍이란 어려운 일.

어떠한 규칙에 따라 코딩을 하면 되는지에 대한 기준이 있으면 좋을 것.

국내에서는 2012년 12월부터 행정안전부에 의해 시큐어 코딩에 대한 법규가 제정, 시행되어 그 기준을 제시 중(대한민국의 모든 행정기관 및 공공기관 정보시스템은 해당 가이드 준수 의무가 있음)

50개의 소프트웨어 보안 약점 항목으로 구성

JAVA 시큐어코딩 가이드 문서 다운로드 링크: https://www.kisa.or.kr/public/laws/laws3_View

1. 입력데이터 검증 및 표현

2. 보안기능

적절한 인증 없는 중요기능 허용

// 안전하지 않은 코드 예
// 재인증을 거치지 않는 계좌 이체
public void sendBankAccount(String accountNumber,double balance){
  ...
  BankAccount account = new BankAccount();
  account.setAccountNumber(accountNumber);
  account.setToPerson(toPerson);
  account.setBalance(balance);
  AccountManager.send(account);
  ...
}
// 안전한 코드 예
// 인증 받은 사용자만이 다시 재인증을 거쳐 계좌 이체
public void sendBankAccount(HttpServletRequest request, HttpSession session, String
accountNumber,double balance){
  ...
  // 재인증을 위한 팝업화면을 통해 사용자의 credential을 받는다.
  String newUserName = request.getParameter("username");
  String newPassword = request.getParameter("password");
  if ( newUserName == null || newPassword == null ){
    throw new MyEception("데이터오류");
  }

  // 세션으로부터 로그인한 사용자의 credentail을 읽는다.
  String password = session.getValue("password");
  String userName = session.getValue("username");

  // 재인증을 통해서 이체여부를 판단한다.
  if ( isAuthenticatedUser() && newUserName.equal(userName) && newPassword.equal(password) ) {
    BankAccount account = new BankAccount();
    account.setAccountNumber(accountNumber);
    account.setToPerson(toPerson);
    account.setBalance(balance);
    AccountManager.send(account);
  }
  ...
 }

3. 시간 및 상태

4. 에러 처리

오류 메세지를 통한 정보 누출

종종 개발자가 디버깅의 편의성을 위해 에러 메시지를 화면에 출력하는 경우가 있다. 에러 메시지는 시스템과 관련된 중요 정보를 포함하는 경우가 많아 공격자의 악성 행위를 도울 수 있다. 또한, 오류가 발생할 상황을 적절하게 검사하지 않았거나 잘못된 처리를 한 경우도 에러 처리 항목에 포함된다. 에러 처리는 가능한 최소한의 정보만을 담고 있어야 하며, 광범위한 예외 처리보다는 구체적인 예외 처리를 통해 보안 공격을 사전에 방어하는 것이 중요하다.

// 안전하지 않은 코드
// 예외 이름이나 스택 트레이스를 출력하면 프로그램 내부 정보가 유출된다.
public static void main(String[] args){
  String urlString = args[0];
  try{
    URL url = new URL(urlString);
    URLConnection cmx =
    url.openConnection();
    cmx.connect();
  }catch (Exception e){
    e.printStackTrace();
  }
}
// 안전한 코드
// 예외 이름이나 스택 트레이스를 출력하지 않는다.
public static void main(String[] args){
  String urlString = args[0];
  try{
    URL url = new URL(urlString);
    URLConnection cmx = url.openConnection();
    cmx.connect();
  }catch (Exception e){
    System.out.println("연결 예외 발생");
  }
}

5. 코드오류

널(NULL)포인터 역참조

일반적으로 특정 객체가 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");
....

6. 캡슐화

Public 메소드로부터 반환된 private 배열

// 안전하지 않은 코드
// private배열을 public 메소드가 return
// private 배열의 레퍼런스를 얻을 수 있게됨
private String[] colors;
public String[] getColors() { return colors; }
...
// 안전한 코드
....
private String[] colors;
// 메소드를 private으로 하거나, 복제본을 반환하거나, 수정을 제어하는 public 메소드를 별도로 만든다.
public String[] getColors(){
String[] ret = null;
  if ( this.colors != null ){
    ret = new String[colors.length];
    for (int i = 0; i < colors.length; i++) {
      ret[i] = this.colors[i];
    }
  }
  return ret;
}
....

7. API 오용

DNS lookup에 의존한 보안 결정

// 안전하지 않은 코드
// DNS 이름을 통해 해당 요청이 신뢰할 수 있는지를 검사하는 경우
// 공격자가 DNS 캐쉬를 조작하면 잘못된 신뢰 상태 정보를 얻게 됨
public class checkDNS extends HttpServlet
{
  public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException){
    boolean trusted = false;
    String ip = req.getRemoteAddr();
    // 호스트의 IP 주소를 얻어온다.
    InetAddress addr = InetAddress.getByName(ip);

    // 호스트의 IP정보와 지정된 문자열(trustme.com)이 일치하는지 검사한다.
    if (addr.getCanonicalHostName().endsWith("trustme.com")){
      trusted = true;
    }
    if (trusted){
      ....
    }else{
      ....
    }
  }
}
// 안전한 코드
// DNS lookup에 의한 호스트 이름을 비교하지 않고 IP 주소를 직접 비교
public class checkDNS extends HttpServlet{
  public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException){
    String ip = req.getRemoteAddr();
    if (ip == null || "".equals(ip))
      return ;

    String trustedAddr = "127.0.0.1";

    if (ip.equals(trustedAddr)){
      ....
    }else{
      ....
    }
  }
}