차이
문서의 선택한 두 판 사이의 차이를 보여줍니다.
양쪽 이전 판이전 판다음 판 | 이전 판다음 판양쪽 다음 판 | ||
guide:java_개발_보안_가이드 [2013/12/24 04:17] – 121.140.124.172 | guide:java_개발_보안_가이드 [2013/12/26 07:10] – 121.140.124.172 | ||
---|---|---|---|
줄 3368: | 줄 3368: | ||
=== 다. 예제 === | === 다. 예제 === | ||
<code | 안전하지 않는 코드 예제> | <code | 안전하지 않는 코드 예제> | ||
+ | 1: class FileMgmtThread extends Thread { | ||
+ | 2: | ||
+ | 3: private String manageType = ""; | ||
+ | 4: | ||
+ | 5: public FileMgmtThread (String type) { | ||
+ | 6: manageType = type; | ||
+ | 7: } | ||
+ | 8: | ||
+ | 9: public void run() { | ||
+ | 10: try { | ||
+ | 11: if ( manageType.equals(" | ||
+ | 12: File f = new File(" | ||
+ | 13: if (f.exists()) { // 만약 파일이 존재하면 파일내용을 읽음 | ||
+ | 14: BufferedReader br = new BufferedReader(new FileReader(f)); | ||
+ | 15: br.close(); | ||
+ | 16: } | ||
+ | 17: } else if ( manageType.equals(" | ||
+ | 18: File f = new File(" | ||
+ | 19: if (f.exists()) { // 만약 파일이 존재하면 파일을 삭제함 | ||
+ | 20: f.delete(); | ||
+ | 21: } else { | ||
+ | 22: ; | ||
+ | 23: } | ||
+ | 24: } | ||
+ | 25: } catch (IOException e) { | ||
+ | 26: } | ||
+ | 27: } | ||
+ | 28: } | ||
+ | 29: | ||
+ | 30: public class CWE367 { | ||
+ | 31: public static void main(String[] args) { | ||
+ | 32: // 파일의 읽기와 파일을 삭제하는 것을 동시에 수행한다. | ||
+ | 33: FileMgmtThread fileAccessThread = new FileMgmtThread(" | ||
+ | 34: FileMgmtThread fileDeleteThread = new FileMgmtThread(" | ||
+ | 35: fileAccessThread.start(); | ||
+ | 36: fileDeleteThread.start(); | ||
+ | 37: } | ||
+ | 38: } | ||
+ | </ | ||
+ | 위 예제는 파일의 존재를 확인하는 부분과 실제로 파일을 사용하는 부분을 실행하는 과정에서 시간차가 발생하는 경우, 파일에 대한 삭제가 발생하여 프로그램이 예상하지 못하는 태로 수행될 수 있다. 또한 위 예제는 시간차를 이용하여 파일을 변경하는 등의 공격에 취약할 수 있다. | ||
+ | <code | 안전한 코드 예제> | ||
+ | 1: class FileMgmtThread extends Thread { | ||
+ | 2: | ||
+ | 3: private static final String SYNC = " | ||
+ | 4: | ||
+ | 5: private String manageType = ""; | ||
+ | 6: | ||
+ | 7: public FileMgmtThread (String type) { | ||
+ | 8: manageType = type; | ||
+ | 9: } | ||
+ | 10: | ||
+ | 11: public void run() { | ||
+ | 12: // synchronized 를 사용함으로써 지정된 객체에 lock이 걸려서 | ||
+ | 13: // 블록을 수행하는 동안 다른 Thread가 접근할 수 없다. | ||
+ | 14: synchronized(SYNC) { | ||
+ | 15: try { | ||
+ | 16: if ( manageType.equals(" | ||
+ | 17: File f = new File(" | ||
+ | 18: if (f.exists()) { // 만약 파일이 존재하면 파일내용을 읽음 | ||
+ | 19: BufferedReader br = new BufferedReader(new FileReader(f)); | ||
+ | 20: br.close(); | ||
+ | 21: } | ||
+ | 22: } else if ( manageType.equals(" | ||
+ | 23: File f = new File(" | ||
+ | 24: if (f.exists()) { // 만약 파일이 존재하면 파일을 삭제함 | ||
+ | 25: f.delete(); | ||
+ | 26: } else { | ||
+ | 27: ; | ||
+ | 28: } | ||
+ | 29: } | ||
+ | 30: } catch (IOException e) { | ||
+ | 31: } | ||
+ | 32: } | ||
+ | 33: } | ||
+ | 34: } | ||
+ | 35: | ||
+ | 36: public class CWE367 { | ||
+ | 37: public static void main(String[] args) { | ||
+ | 38: // 파일의 읽기와 파일을 삭제하는 것을 동시에 수행한다. | ||
+ | 39: FileMgmtThread fileAccessThread = new FileMgmtThread(" | ||
+ | 40: FileMgmtThread fileDeleteThread = new FileMgmtThread(" | ||
+ | 41: fileAccessThread.start(); | ||
+ | 42: fileDeleteThread.start(); | ||
+ | 43: } | ||
+ | 44: } | ||
+ | </ | ||
+ | 공유자원(예를 들어, 파일)을 여러 스레드가 접근하여 사용할 경우, 동기화 구문을 이용하여 한 번에 하나의 스레드만 접근 가능하도록 변경한다. | ||
+ | <code | 안전하지 않는 코드 예제> | ||
+ | 1: public class MyServlet extends HttpServlet { | ||
+ | 2: String name; | ||
+ | 3: public void doPost ( HttpRequestRequest hreq, HttpResponceServlet hres ) { | ||
+ | 4: name = hreq.getParameter(" | ||
+ | 5: ……. | ||
+ | 6: } | ||
+ | </ | ||
+ | HttpServet을 상속받은 MyServlet이 멤버필드로 name을 사용하고 있다. name은 MyServlet을 동시에 사용하는 모든 사용자에게 정보가 노출된다. | ||
+ | <code | 안전한 코드 예제> | ||
+ | 1: public class MyServlet extends HttpServlet { | ||
+ | 2: public void doPost ( HttpRequestRequest hreq, HttpResponceServlet hres ) { | ||
+ | 3: // 서블릿 프로그램의 멤버 변수는 공용으로 사용하기 때문에 로컬로 정의해서 사용한다. | ||
+ | 4: String name = hreq.getParameter(" | ||
+ | 5: ... | ||
+ | 6: } | ||
+ | </ | ||
+ | name을 doPost 메소드에만 사용할 수 있도록 로컬로 정의하여 경쟁상태를 제거한다. | ||
+ | <code | 안전한 코드 예제> | ||
+ | 1: public class MyClass { | ||
+ | 2: String name; | ||
+ | 3: public void doProcess (HttpRequestRequest hreq ) { | ||
+ | 4: // 멤버변수 공유 시 동기화시킨다. | ||
+ | 5: synchronized { | ||
+ | 6: name = hreq.getParameter(" | ||
+ | 7: ... | ||
+ | 8: } | ||
+ | 9: ... | ||
+ | 10: } | ||
+ | </ | ||
+ | 업무상 name을 여러 쓰레드 사이에서 공유해야 한다면, synchronized 문장을 사용하여, | ||
+ | === 라. 참고 문헌 === | ||
+ | [1] CWE-367 경쟁 조건: 검사시점과 사용시점 - http:// | ||
+ | \\ | ||
+ | [2] SANS Top 25 Most Dangerous Software Errors | ||
+ | \\ | ||
+ | [3] Michael Howard, David LeBlanc and John Viega. "24 Deadly Sins of Software Security" | ||
+ | \\ | ||
+ | [4] Andrei Alexandrescu. " | ||
+ | \\ | ||
+ | [5] Steven Devijver. " | ||
+ | \\ | ||
+ | [6] Matt Bishop. " | ||
+ | \\ | ||
+ | [7] Johannes Ullrich. "Top 25 Series - Rank 25 - Race Conditions" | ||
+ | ==== 4. J2EE 잘못된 습관: 스레드의 직접 사용(J2EE Bad Practices: Direct Use of Threads) ==== | ||
+ | |||
+ | === 가. 정의 === | ||
+ | J2EE 표준은 웹 응용프로그램에서 스레드 사용을 금지하고 있다. 따라서 스레드를 직접 사용하는 것 대신에 해당 플랫폼에서 제공하는 병렬 실행을 위한 프레임워크를 사용해야 한다. 그렇지 않을 경우, 교착 상태, 경쟁 조건, 및 기타 동기화 오류 등이 발생한다. | ||
+ | === 나. 안전한 코딩기법 === | ||
+ | J2EE에서는 스레드를 사용하는 것 대신에 병렬 실행을 위한 프레임워크를 사용해야 한다. | ||
+ | === 다. 예제 === | ||
+ | <code | 안전하지 않는 코드 예제> | ||
+ | 1: public class U383 extends HttpServlet { | ||
+ | 2: protected void doGet(HttpServletRequest request, HttpServletResponse response) | ||
+ | throws ServletException, | ||
+ | 3: // Thread를 만들고 background에서 작업을 수행한다. | ||
+ | 4: Runnable r = new Runnable() { | ||
+ | 5: public void run() { | ||
+ | 6: System.err.println(" | ||
+ | 7: } | ||
+ | 8: }; | ||
+ | 9: new Thread(r).start(); | ||
+ | 10: } | ||
+ | 11: } | ||
+ | </ | ||
+ | J2EE 프로그램에서 스레드를 직접 생성하여 사용하면, | ||
+ | <code | 안전한 코드 예제> | ||
+ | 1: public class S383 extends HttpServlet { | ||
+ | 2: protected void doGet(HttpServletRequest request, HttpServletResponse response) | ||
+ | throws ServletException, | ||
+ | 3: // 수행할 Thread에 대해서 일반 자바 클래스를 만든다. | ||
+ | 4: // New MyClass().main(); | ||
+ | 5: | ||
+ | 6: // 만약 async로 병렬작업을 하기 위해서는 JAVA Runtime을 | ||
+ | 7: // 사용하여 async로 통신하는 게 좋다. | ||
+ | 8: Runtime.getRuntime().exec(" | ||
+ | 9: } | ||
+ | 10: } | ||
+ | 11: | ||
+ | 12: class AsyncClass { | ||
+ | 13: public static void main(String args[]) { | ||
+ | 14: // Process and store request statistics. | ||
+ | 15: // …… | ||
+ | 16: System.err.println(" | ||
+ | 17: } | ||
+ | 18: } | ||
+ | </ | ||
+ | 스레드를 직접 사용하는 것 대신에 해당 플랫폼에서 제공하는 병렬 실행을 위한 프레임워크를 사용한다. | ||
+ | === 라. 참고 문헌 === | ||
+ | [1] CWE-383 J2EE 잘못된 습관: 스레드의 직접 사용 - http:// | ||
+ | \\ | ||
+ | [2] Java 2 Platform Enterprise Edition Specification, | ||
+ | ==== 5. 심볼릭명이 정확한 대상에 매핑되어 있지 않음(Symbolic Name not Mapping to Correct Object) ==== | ||
+ | |||
+ | === 가. 정의 === | ||
+ | 심볼릭명을 사용하여 특정 대상을 지정하는 경우 공격자는 심볼릭명이 가리키는 대상을 작하여 프로그램이 원래 의도했던 동작을 못하게 할 수 있다. | ||
+ | === 나. 안전한 코딩기법 === | ||
+ | 클래스 객체가 필요할 때에는 클래스 생성자를 표준적인 방법으로만 호출해야 한다. | ||
+ | === 다. 예제 === | ||
+ | <code | 안전하지 않는 코드 예제> | ||
+ | 1: …… | ||
+ | 2: public void f() throws ClassNotFoundException, | ||
+ | IllegalAccessException { | ||
+ | 3: // Class.forName으로 클래스를 생성하고 있다. | ||
+ | 4: Class c = Class.forName(" | ||
+ | 5: Object obj = (Add)c.newInstance(); | ||
+ | 6: Add add = (Add) obj; | ||
+ | 7: System.out.println(add.add(3, | ||
+ | 8: | ||
+ | 9: Object obj2 = (Add)Class.forName(" | ||
+ | 10: Add add2 = (Add) obj2; | ||
+ | 11: System.out.println(add2.add(3, | ||
+ | 12: } | ||
+ | 13: | ||
+ | 14: class Add { | ||
+ | 15: int add(int x, int y) { | ||
+ | 16: return x + y; | ||
+ | 17: } | ||
+ | 18: } | ||
+ | 19: } | ||
+ | 20: | ||
+ | 21: class Add { | ||
+ | 22: int add(int x, int y) { return (x*x + y*y); } | ||
+ | 23: } | ||
+ | </ | ||
+ | java.lang.Class.forName()은 인수 문자열을 기반으로 해당 클래스를 반환(return)하지만, | ||
+ | <code | 안전한 코드 예제> | ||
+ | 1: …… | ||
+ | 2: public void f() throws ClassNotFoundException, | ||
+ | 3: InstantiationException, | ||
+ | 4: // 객체의 생성은 직접 생성자를 호출하여 생성한다. | ||
+ | 5: testbed.safe.S386.Add add = new testbed.safe.S386.Add(); | ||
+ | 6: System.out.println(add.add(3, | ||
+ | 7: testbed.safe.Add add2 = new testbed.safe.Add(); | ||
+ | 8: System.out.println(add2.add(3, | ||
+ | 9: } | ||
+ | 10: | ||
+ | 11: class Add { | ||
+ | 12: private int add(int x, int y) { | ||
+ | 13: return x + y; | ||
+ | 14: } | ||
+ | 15: } | ||
+ | 16: } | ||
+ | 17: | ||
+ | 18: class Add { | ||
+ | 19: int add(int x, int y) { return (x*x + y*y); } | ||
+ | 20: } | ||
+ | </ | ||
+ | java.lang.Class.forName 대신, 각 클래스의 생성자를 제대로 호출하여 객체를 생성한다. | ||
+ | === 라. 참고 문헌 === | ||
+ | [1] CWE-386 심볼릭명이 정확한 대상에 매핑되어 있지 않음 - http:// | ||
+ | ==== 6. 중복 검사된 잠금(Double-Checked Locking) ==== | ||
+ | |||
+ | === 가. 정의 === | ||
+ | 중복 검사된 잠금(double-checked locking)은 프로그램의 효율성을 높이기 위해 사용하지만, | ||
+ | \\ | ||
+ | 동기화 비용을 줄이기 위해, 프로그래머는 하나의 객체만 할당될 수 있도록 코드를 작성하지만, | ||
+ | === 나. 안전한 코딩기법 === | ||
+ | 특정 리소스가 (비)할당되었을 경우만 작업을 수행하도록 두 번에 걸쳐 검사를 시도해도 원하는 자원이 (비)할당되었는지 보장이 불가능하다. | ||
+ | \\ | ||
+ | 중복 검사된 잠금에 대한 완벽한 보장을 원할 경우 메소드를 동기화해야 한다. | ||
+ | === 다. 예제 === | ||
+ | <code | 안전하지 않는 코드 예제> | ||
+ | 1: …… | ||
+ | 2: Helper helper; | ||
+ | 3: | ||
+ | 4: public Helper MakeHelper() { | ||
+ | 5: // helper 객체의 null 체크에 대해서는 동기화가 되지 않는다. | ||
+ | 6: if (helper == null) { | ||
+ | 7: synchronized (this) { | ||
+ | 8: if (helper == null) { | ||
+ | 9: helper = new Helper(); | ||
+ | 10: } | ||
+ | 11: } | ||
+ | 12: } | ||
+ | 13: return helper; | ||
+ | 14: } | ||
+ | 15: | ||
+ | 16: class Helper { | ||
+ | 17: …… | ||
+ | 18: } | ||
+ | 19: } | ||
+ | </ | ||
+ | 위 예제는 하나의 helper 객체만 할당될 수 있도록 코드가 작성되었다. 이는 불필요한 동기화를 피하면서 스레드 안전성을 보장하는 것처럼 보인다. 그러나 자바에서는 객체 참조주소를 할당하고 생성자를 호출하므로, | ||
+ | <code | 안전한 코드 예제> | ||
+ | 1: …… | ||
+ | 2: Helper helper; | ||
+ | 3: | ||
+ | 4: // 메소드 전체에 대해 동기화를 하도록 설정함. | ||
+ | 5: public synchronized Helper MakeHelper() { | ||
+ | 6: if (helper == null) { | ||
+ | 7: helper = new Helper(); | ||
+ | 8: } | ||
+ | 9: return helper; | ||
+ | 10: } | ||
+ | 11: } | ||
+ | 12: | ||
+ | 13: class Helper { | ||
+ | 14: …… | ||
+ | 15: } | ||
+ | </ | ||
+ | 중복 검사된 잠금에 대한 완벽한 보장을 원할 경우, 메소드 전체에 대해 동기화를 하도록 설정한다. | ||
+ | === 라. 참고 문헌 === | ||
+ | [1] CWE-609 중복 검사된 잠금 - http:// | ||
+ | \\ | ||
+ | [2] David Bacon et al.. "The " | ||
+ | \\ | ||
+ | http:// | ||
+ | ==== 7. 제대로 제어되지 않은 재귀(Uncontrolled Recursion) ==== | ||
+ | |||
+ | === 가. 정의 === | ||
+ | 재귀의 순환횟수를 제어하지 못하여 할당된 메모리나 프로그램 스택 등의 자원을 과다하게 사용하면 위험하다. 대부분의 경우, 귀납 조건(base case)이 없는 재귀는 무한 재귀에 빠진다. | ||
+ | === 나. 안전한 코딩기법 === | ||
+ | 무한 재귀를 방지하기 위하여 모든 재귀 호출을 조건문 블럭이나 반복문 블럭 안에서만 수행해야 한다. | ||
+ | === 다. 예제 === | ||
+ | <code | 안전하지 않는 코드 예제> | ||
+ | 1: …… | ||
+ | 2: public int factorial(int n) { | ||
+ | 3: // 재귀 호출이 조건문/ | ||
+ | 4: return n * factorial(n - 1); | ||
+ | 5: } | ||
+ | </ | ||
+ | 재귀적으로 정의되는 함수의 경우, 재귀 호출이 조건문/ | ||
+ | <code | 안전한 코드 예제> | ||
+ | 1: …… | ||
+ | 2: public int factorial(int n) { | ||
+ | 3: int i; | ||
+ | 4: // 모든 재귀 호출은 조건문이나 반복문 블럭 안에서 이루어져야한다. | ||
+ | 5: if (n == 1) { | ||
+ | 6: i = 1; | ||
+ | 7: } else { | ||
+ | 8: i = n * factorial(n - 1); | ||
+ | 9: } | ||
+ | 10: return i; | ||
+ | 11: } | ||
+ | </ | ||
+ | 모든 재귀 호출은 조건문이나 반복문 블럭 안에서 이루어져야한다. | ||
+ | === 라. 참고 문헌 === | ||
+ | [1] CWE-674 제대로 제어되지 않은 재귀 - http:// | ||