자격/SW보안약점진단원

구현단계 보안약점 제거 기준-하드코드된 중요정보

Ignatius Heo 2023. 7. 4. 20:37

작성일: 230704

 

※ 본 게시글은 학습 목적으로 행정안전부·KISA의 소프트웨어 보안약점 진단 가이드, 소프트웨어 개발보안 가이드를 참고하여 작성하였습니다.

 

정리 내용: 소프트웨어 보안약점 진단 가이드(350~359p)

구분 - 보안기능
설계단계 - 비밀번호 관리
https://cryptocurrencyclub.tistory.com/102
개요 프로그램 코드 내부에 하드코드된 비밀번호 또는 암호화키를 포함하여 내부 인증에 사용하거나 암호화를 수행하면 중요정보(관리자 정보, 암호화된 정보 등)가 유출될 수 있는 보안약점이다.
진단 세부사항
(설계단계)
 ① 비밀번호 설정 시 KISA의 비밀번호 선택 및 이용 안내서의 비밀번호 생성규칙을 적용한다. 조합규칙, 길이 등 비밀번호가 안전하게 설정되도록 설계되어 있는지 확인한다.
  ㅇ  비밀번호 설정 규칙 정의 시 안전한 비밀번호 설정규칙이 설계에 적용되었는지 확인
      - 길이: 조합규칙의 복잡도에 따라 최소 8자리 이상
      - 조합규칙: 숫자, 영 대소문자, 특수문자 포함
      - 제한문자열: 전화번호, 동일 문자열 반복 제한, 생년월일 등
  ㅇ  비밀번호 설정규칙을 위배하는 입력값과 예상 결과를 포함하는 테스트 계획의 수립 여부 확인
 
 ② 네트워크로 비밀번호를 전송하는 경우 반드시 비밀번호를 암호화하거나 암호화된 통신 채널을 이용해야 한다. 네트워크로 비밀번호 전송 시 암호화 또는 암호화 채널을 사용하도록 설계되어 있는지 확인한다.
  ㅇ  네트워크로 비밀번호가 전송되는 기능이 분류되어 있는지 확인
  ㅇ  비밀번호를 암호화하여 전송하는 경우, 안전한 암호 알고리즘을 사용한 비밀번호 암호화 여부 확인
  ㅇ  암호화된 통신채널을 사용하여 전송하는 경우 TLS/VPN 등 안전한 통신채널을 사용하도록 설계되었는지 확인
  ㅇ  패킷스니핑 등으로 암호화 전송을 점검하는 테스트 계획의 수립 여부 확인
패킷까지 떠????
 
 ③ 비밀번호 저장 시, 솔트가 적용된 안전한 해시함수를 사용해야 하며, 해시함수 실행은 서버에서 해야한다. 비밀번호 저장 시 해시함수를 사용하도록 설계하고, 사용할 해시함수와 솔트 값을 포함한 해시함수 사용 방법이 명시되어 있는지 확인한다.
  ㅇ  비밀번호를 암호화하기위해 SHA-2 계열(SHA224/256/384/512)의 해시함수 사용여부 확인
  ㅇ  해시함수 사용 시 솔트값을 사용하도록 설계되어 있는지 확인
  ㅇ  솔트값은 데이터 암·복호화에 사용되는 암호키가 저장되는 장소에 저장되도록 설계되어 있는지 확인
  ㅇ  해시함수를 이용한 일방향 암호를 서버에서 수행하는 프로그램이 설계되어 있는지 확인
  ㅇ  비밀번호 크랙도구를 이용하여 DB에 저장된 비밀번호의 안전을 점검하는 테스트 계획의 수립 여부 확인
 
  비밀번호 재설정/변경 시 안전하게 변경할 수 있는 규칙을 정의해서 적용해야 한다. 비밀번호를 주기적으로 변경하고, 비밀번호 변경 및 비밀번호 분실로 인한 재설정 시 안전하게 변경절차가 이루어지도록 설계되어 있는지 확인한다.
  ㅇ  비밀번호 변경 시 기존 비밀번호를 확인하는 기능의 설계 여부 확인(예전 비번과 동일여부 확인 목적인듯?)
  ㅇ  안전한 비밀번호 설정규칙이 적용되도록 설계되어 있는지 확인
  ㅇ  비밀번호 재설정 요청 시, 안전한 본인인증 절차를 거치도록 설계되어 있는지 확인(휴대전화인증, I-PIN, 공인인증서, 신용카드 등)
  ㅇ  본인인증 후 비밀번호 재설정 페이지 연결방식의 안전한 설계 여부 확인
  ㅇ  권한이 있는 사용자만 비밀번호 변경이 가능한지 점검하는 테스트계획 수립 여부 확인
  ㅇ  본인인증 절차를 우회하거나 비밀번호 재설정 경로로 직접 접근하여 비밀번호 재설정 가능 여부를 점검하는 테스트 계획 수립 여부 확인
 
 ⑤ 비밀번호 관리 규칙을 정의해서 적용해야 한다. 비밀번호 변경주기, 만료기간 설정, 성공한 로그인 시간 관리를 포함하여 비밀번호 관리규칙이 설계되어 있는지 확인한다.
  ㅇ  비밀번호 만료기간이나 변경 주기에 대한 정책의 정의 여부 확인
  ㅇ  비밀번호 만료기간이나 변경주기 정책을 관리할 수 있도록 DB 및 프로그램 기능의 설계 여부 확인(최종 변경일, 만료기간, 변경주기 등)
보안대책
(구현단계)
비밀번호는 암호화 하여 별도의 파일에 저장하여 사용한다.

또한 중요정보를 암호화하면, 상수가 아닌 암호화 키를 사용하도록 하며 소스코드 내부에 상수형태의 암호화 키를 저장해서 사용하지 않도록 한다.
진단방법
(구현단계)
ㆍ하드코드된 비밀번호
사용자․관리자 로그인페이지(식별․인증)를 요청하는 모듈․함수를 확인하여 사용자․관리자 비밀번호가 소스코드 안에 직접 코딩되어 있는지 확인하고 내․외부 서버(업무서버, DB 등)에 접속을 관리하는 모듈, 함수를 확인하여 서버 접속 비밀번호가 소스코드 안에 직접 코딩되어 있는지 확인한다.

비밀번호를 별도의 파일에 저장하여 사용하지 않고 소스코드 안에 하드코딩 되어 있는 경우엔 취약하다고 판정한다.

ㆍ하드코드된 암호화 키
① DB접속 등 비밀번호 사용이 필요한 로직을 확인하고,

② 사용된 비밀번호의 암호화 여부 확인한다

③ 암호화된 비밀번호가 소스내에 존재하는지 여부 확인한다. 소스코드내에 암호화된 비밀번호를 저장하는 경우 취약하다.

 

다. 코드예제

 

ㅇ 분석

1: public class MemberDAO {
2: private static final String DRIVER = "oracle.jdbc.driver.OracleDriver";
3: private static final String URL = "jdbc:oracle:thin:@192.168.0.3:1521:ORCL";
4: private static final String USER = "SCOTT"; // DB ID;
//DB 비밀번호가 소스코드에 평문으로 저장되어 있다.
5: private static final String PASS = "SCOTT"; // DB PW;
6: ……
7: public Connection getConn() {
8: Connection con = null;
9: try {
10: Class.forName(DRIVER);
11: con = DriverManager.getConnection(URL, USER, PASS);
12: ……

ㅇ 내용

1. 패스워드 좋네. 근데 IP도 노출해도 되나? 내부망이라 상관없나

 

ㅇ 수정

1: public class MemberDAO {
2: private static final String DRIVER = "oracle.jdbc.driver.OracleDriver";
3: private static final String URL = "jdbc:oracle:thin:@192.168.0.3:1521:ORCL";
4: private static final String USER = "SCOTT"; // DB ID
5: ……
6: public Connection getConn() {
7: Connection con = null;
8: try {
9: Class.forName(DRIVER);
//암호화된 비밀번호를 프로퍼티에서 읽어들여 복호화해서 사용해야한다.
10: String PASS = props.getProperty("EncryptedPswd");
11: byte[] decryptedPswd = cipher.doFinal(PASS.getBytes());
12: PASS = new String(decryptedPswd);
13: con = DriverManager.getConnection(URL, USER, PASS);
14: ……

ㅇ 내용

1. 패스워드 읽고, 복호화해서 사용.


ㅇ 분석

1: string UserName = "username";
2: string Password = "password";
// 평문으로 저장된 비밀번호를 이용하여 NetworkCredential 생성
3: NetworkCredential myCred = new NetworkCredential(UserName, Password);

ㅇ 내용

1. 비번 간단하고 좋네

 

ㅇ 수정

1: string UserName = "username";
2: string Password = "password";
3: SecureString SecurelyStoredPassword = new SecureString();
4: foreach (char c in Password)
5: {
6: SecurelyStoredPassword.AppendChar(c);
7: }
// 암호화된 비밀번호를 사용하여 NetworkCredential 생성
8: NetworkCredential secure_myCred = new NetworkCredential(UserName,
SecurelyStoredPassword);

ㅇ 내용

1. 근데 이것도 하드코드된 패스워드 아닌가?


ㅇ 분석

1: int dbaccess(char *server, char *user){
2: SQLHENV henv;
3: SQLHDBC hdbc;
4: char *password = “password”;
5: SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
6: SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
// 하드코드된 비밀번호를 사용
7: SQLConnect(hdbc,(SQLCHAR*)server,strlen(server),user,strlen(user),
password, strlen(password));
8: return 0;

ㅇ 내용

1. 똑같이 그대로 사용

 

ㅇ 수정

1: int dbaccess(char *server, char *user, char *passwd){
2: SQLHENV henv;
3: SQLHDBC hdbc;
4: // 비밀번호를 외부에서 불러와서 사용
5: char *password = getenv(“password”);
6: SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
7: SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
8: SQLConnect(hdbc, (SQLCHAR*) server, strlen(server), user, strlen(user),
password, strlen(password));
9: SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
10: SQLFreeHandle(SQL_HANDLE_ENV, henv);
11: return 0;
12:}

ㅇ 내용

1. passwd를 받는데 password는 어디서 생겼어요...?


ㅇ 분석

1: import javax.crypto.KeyGenerator;
2: import javax.crypto.spec.SecretKeySpec;
3: import javax.crypto.Cipher;
4: ……
5: public String encriptString(String usr) {
//암호화 키를 소스코드 내부에 사용하는 것은 안전하지 않다.
6: String key = "22df3023sf~2;asn!@#/>as";
7: if (key != null) {
8: byte[] bToEncrypt = usr.getBytes("UTF-8");
9: SecretKeySpec sKeySpec = new SecretKeySpec(key.getBytes(), "AES");

ㅇ 내용

1. 하드코딩

 

ㅇ 수정

1: import javax.crypto.KeyGenerator;
2: import javax.crypto.spec.SecretKeySpec;
3: import javax.crypto.Cipher;
4: ……
5: public String encriptString(String usr) {
//암호화 키는 외부 파일에서 암호화 된 형태로 저장하고, 사용시 복호화 한다.
6: String key = getPassword("./password.ini");
7: key = decrypt(key);
8: if (key != null) {
9: byte[] bToEncrypt = usr.getBytes("UTF-8");
10: SecretKeySpec sKeySpec = new SecretKeySpec(key.getBytes(), "AES");

ㅇ 내용

1. 암호화해서 파일로 저장 -> 불러와서 복호화해서 사용 괜춘한듯함


ㅇ 분석

//암호화 키를 소스코드 내부에 사용하는 것은 안전하지 않다.
1: byte[] key = new byte[] { 0x43, 0x87, 0x23, 0x72 };
2: byte[] iv = new byte[] { 0x43, 0x87, 0x23, 0x72 };
3: FileStream fStream = File.Open(fileName, FileMode.OpenOrCreate);
4: CryptoStream cStream = new CryptoStream(fStream,
new TripleDESCryptoServiceProvider().CreateEncryptor(key, iv),
CryptoStreamMode.Write);

ㅇ 내용

1. 암호키를 평문으로 저장해??

 

ㅇ 수정

//암호화 키는 외부 파일에서 암호화 된 형태로 저장하고, 사용시 복호화 한다.
1: byte[] key = GetKey(./password.ini);
2: byte[] iv = GetIV(./password.ini);
3: FileStream fStream = File.Open(fileName, FileMode.OpenOrCreate);
4: CryptoStream cStream = new CryptoStream(fStream, new
TripleDESCryptoServiceProvider().CreateEncryptor(Decrypt(key),
Decrypt(iv)),
CryptoStreamMode.Write);

ㅇ 내용

1. 암호키 암호화


ㅇ 분석

1: typedef int SQLSMALLINT;
2: int dbaccess(char *user, char *passwd){
3: char *server = "DBserver";
4: char *cpasswd;
5: SQLHENV henv;
6: SQLHDBC hdbc;
7: SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
8: SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
//암호화된 비밀번호와 솔트를 사용합니다. 코드에 접근 권한이 있는 사용자는 해당 비밀번호와 솔트를
획득할 수 있습니다.
9: cpasswd = crypt(passwd, “salt”);
10: if (strcmp(cpasswd, "68af404b513073582b6c63e6b") != 0) {
11: // USE_OF_HARDCODED_CRYPTOGRAPHIC_KEY
12: printf("Incorrect password\n");
13: return -1;

ㅇ 내용

1. 여기서 솔트는 "salt"이고 암호화된 패스워드는 68~~로 보임.

 

ㅇ 수정

1: extern char *salt;
2: typedef int SQLSMALLINT;
3: int dbaccess(char *user, char *passwd){
4: char *server = "DBserver";
5: char *cpasswd;
// 외부에 있는 암호화된 비밀번호와 솔트를 불러옵니다.
6: char* storedpasswd = getenv(“password”);
7: char* salt = getenv(“salt”);
8: SQLHENV henv;
9: SQLHDBC hdbc;
10: SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
11: SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
// 외부에서 불러온 솔트 값을 사용해 비빌번호를 암호화합니다.
12: cpasswd = crypt(passwd, salt);
// 암호화된 비밀번호와 외부에서 불러온 값을 비교합니다.
13: if (strcmp(cpasswd, storedpasswd) != 0){
14: printf("Incorrect password\n");
15: SQLFreeHandle(SQL_HANDLE_DBC, &hdbc);
16: SQLFreeHandle(SQL_HANDLE_ENV, &henv);
17: return -1;
18: }
19:}

ㅇ 내용

1. 솔트와 패스워드 모두 외부에서 참조해서 사용함. 패스워드는 암호화되어있음

 

 

 

 

끝.