소프트웨어(SW) 보안약점 진단원/구현단계 보안약점 제거 기준
구현단계 보안약점 기준 - 부적절한 자원 해제
브루노W
2025. 6. 4. 22:53
유형 | 코드오류 |
보안약점 | 부적절한 자원 해제 |
개요 |
프로그램의 자원, 예를 들면 열린 파일 디스크립터(Open File Descriptor), 힙 메모리(Heap Memory), 소켓(Socket) 등은 유한한 자원이다.
이러한 자원을 할당받아 사용한 후, 더 이상 사용하지 않는 경우에는 적절히 반환하여야 하는데, 프로그램 오류 또는 에러로 사용이 끝난 자원을 반환하지 못하는 경우이다.
|
보안대책 | 자원을 획득하여 사용한 다음에는 반드시 자원을 해제하여 반환한다. |
진단방법 |
자원(파일기술자, 힙메모리, 소켓)이 선언되고, 선언된 자원이 할당된 경우 해제되는지 확인한다.
진단자는 제어문, 예외처리문 등의 분기 등에 따라 모든 흐름(control flow)을 판단하여 자원해제 여부를 체크해야 한다.
할당된 자원이 해제될 경우 안전하다고 판단하고 자원이 해제되지 않는 분기가 존재할 경우 취약하다.
|
연관된 설계단계 기준 | - |
코드예제
● 안전하지 않은 코드 예 (Java)
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(inputFile);
out = new FileOutputStream(outputFile);
...
FileCopyUtils.copy(fis, os);
// 자원반환 실행 전에 오류가 발생할 경우 자원이 반환되지 않으며, 할당된 모든 자원을 반환해야 한다.
in.close();
out.close();
} catch (IOException e) {
logger.error(e);
}
● 안전한 코드 예 (Java)
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(inputFile);
out = new FileOutputStream(outputFile);
...
FileCopyUtils.copy(fis, os);
} catch (IOException e) {
logger.error(e);
// 항상 수행되는 finally 블록에서 할당받은 모든 자원에 대해 각각 null검사를 수행 후
// 예외처리를 하여 자원을 해제하여야 한다.
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
logger.error(e);
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
logger.error(e);
}
}
}
● 안전하지 않은 코드 예 (C#)
public void FileStreamTest() {
// fsSource에 자원이 할당되었으나 해제되지 않습니다.
FileStream fsSource = new FileStream(pathSource, FileMode.Open, FileAccess.Read);
byte[] bytes = new byte[fsSource.Length];
int numBytesToRead = (int)fsSource.Length;
int numBytesRead = 0;
while(numBytesToRead > 0) {
int n = fsSource.Read(bytes, numBytesRead, numBytesToRead);
if(n==0) break;
numBytesToRead += n;
numBytesToRead -= n;
}
using(FileStream fsNew = new FileStream(pathNew, FileMode.Create, FileAccess.Write)) { /* OK */
fsNew.Write(bytes, 0, numBytesToRead);
}
}
● 안전한 코드 예 (C#)
public void FileStreamTest() {
// using 구문으로 자원을 할당하면 구문이 끝나는 지점에서 자동으로 자원이 해제됩니다.
using(FileStream fsSource = new FileStream(pathSource, FileMode.Open, FileAccess.Read)){
byte[] bytes = new byte[fsSource.Length];
int numBytesToRead = (int)fsSource.Length;
int numBytesRead = 0;
while(numBytesToRead > 0) {
int n = fsSource.Read(bytes, numBytesRead, numBytesToRead);
if(n==0)
break;
numBytesToRead += n;
numBytesToRead -= n;
}
using(FileStream fsNew = new FileStream(pathNew, FileMode.Create, FileAccess.Write)) { /* OK */
fsNew.Write(bytes, 0, numBytesToRead);
}
}
● 안전하지 않은 코드 예 (C)
void ImproperResourceRelease(char* filename) {
char buf[BUF_SIZE];
FILE *f = fopen(filename, "r");
if(!checkSomething()) {
printf("Something is wrong");
return;
}
// checkSomething에서 false를 반환하는 경우, 파일 핸들러를 종료할 수 없습니다.
fclose(f);
}
● 안전한 코드 예 (C)
void ImproperResourceRelease(char* filename) {
char buf[BUF_SIZE];
FILE *f = fopen(filename, "r");
if(!checkSomething()) {
printf("Something is wrong");
// checkSomthing에서 false를 반환해도 파일 핸들러를 종료하도록 수정
fclose(f);
return;
}
fclose(f);
}
진단방법

자원(파일기술자, 힙메모리, 소켓)이 선언되고, 선언된 자원이 할당된 경우 해제되는지 확인한다.
진단자는 제어문, 예외처리문 등의 분기 등에 따라 모든 흐름(control flow)을 판단하여 자원해제 여부를 체크해야 한다.
할당된 자원이 해제될 경우 안전하다고 판단하고 자원이 해제되지 않는 분기가 존재할 경우 취약하다.
● 일반적인 진단의 예
…
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(url);
…
} catch (ClassNotFoundException e) {
conn.rollback();
} finally {
● 정탐코드
try {
String sqlSelect = "SELECT USER_ID, PWD FROM USER"
String sqlUpdate = "UPDATE USER SET PASSWD = ? WHERE USER_ID = ?"
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@192.168.1.1:1521:db", "aaa", "AAA");
pstmt = conn.prepareStatement(sqlSelect);
pstmt1 = conn.prepareStatement(sqlUpdate);
rs = pstmt.executeQuery();
while( rs.next()) {
System.out.println("#################### user_id : " + rs.getString("US- ER_ID"));
pstmt1.setString(1, CryptUtils.encrypt(rs.getString("PWD")));
pstmt1.setString(2, rs.getString("USER_ID"));
pstmt1.executeUpdate();
pstmt1.clearParameters();
System.out.println("#################### 01 : ");
}
conn.commit();
} catch(Exception e) {
e.printStackTrace();
try {
conn.rollback();
} catch(Exception ex) {}
} finally {
try {
rs.close();
pstmt.close();
pstmt1.close();
conn.close();
} catch(Exception e) { }
}
rs.close(), pstmt.close() 등 자원 해제가 try catch 문안에서 함께 이루어지고 있다. 이 경우 rs.close()문에서 예외가 발생하면 pstmt.close() 이하의 자원 해제는 이루어지지 않으므로 자원유출이 일어난다.
● 오탐코드
try {
String sqlSelect = "SELECT USER_ID, PWD FROM USER"
String sqlUpdate = "UPDATE USER SET PASSWD = ? WHERE USER_ID = ?"
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@192.168.1.1:1521:db", "aaa", "AAA");
pstmt = conn.prepareStatement(sqlSelect);
pstmt1 = conn.prepareStatement(sqlUpdate);
rs = pstmt.executeQuery();
while( rs.next()) {
System.out.println("#################### user_id : " + rs.getString("USER_ID"));
pstmt1.setString(1, CryptUtils.encrypt(rs.getString("PWD")));
pstmt1.setString(2, rs.getString("USER_ID"));
pstmt1.executeUpdate();
pstmt1.clearParameters();
System.out.println("#################### 01 : ");
}
conn.commit();
} catch(Exception e) {
e.printStackTrace();
try {
conn.rollback();
} catch (Exception ex) { }
} finally {
try {
rs.close();
} catch (Exception e) { }
try {
pstmt.close();
} catch(Exception e) { }
try {
pstmt1.close();
} catch (Exception e) { }
try {
conn.close();
} catch (Exception e) { }
}
finally 절에서 connection을 close 해주고 있으므로 자원의 부적절한 반환이 이루어지지 않는다.
● 오탐코드
public class DBUtil {
public static Connection getConnection() {
Connection conn = null;
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection(DbInfo.url,DbInfo.id, DbInfo.password);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return conn;
}
}
함수의 역할이 자원을 리턴하는 경우에는 자원의 해제는 함수를 호출한 쪽에서 담당하므로 취약하지 않다.