メインコンテンツ

このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。

CERT C: Rec.MSC18-C

Be careful while handling sensitive data, such as passwords, in program code

説明

ルール定義

プログラム コード内ではパスワードなどの機密データを慎重に取り扱います。1

Polyspace 実装

ルール チェッカーは以下の問題をチェックします。

  • 定数のブロック暗号初期化ベクトル

  • 定数の暗号キー

  • 予測可能なブロック暗号初期化ベクトル

  • 予測可能な暗号キー

  • 要注意のヒープ メモリが解放前にクリアされていません

  • スタック内にクリアされていない機密データがあります

  • 安全でない標準暗号化関数

すべて展開する

問題

定数のブロック暗号初期化ベクトルは、暗号化中の初期化ベクトル (IV) に定数を使用する場合に発生します。

リスク

定数 IV の使用は、IV を使用しないのと同じです。このような暗号化データは、辞書攻撃に対して脆弱です。

ブロック暗号は、データを固定サイズのブロックに分割します。CBC (Cipher Block Chaining) などのブロック暗号モードは、辞書攻撃から保護するために、各ブロックと前のブロックからの暗号化出力とを XOR 演算します。最初のブロックを保護するため、このようなモードではランダムな初期化ベクトル (IV) を使用します。複数のデータ ストリームを暗号化するために定数の IV を使用すると、最初のブロックが共通することになるため、データは辞書攻撃に対して脆弱になります。

修正方法

強力な乱数発生器を使用してランダムな IV を作成します。

暗号法的に脆弱な疑似乱数発生器の一覧については、Vulnerable pseudo-random number generatorを参照してください。

例 - 初期化ベクトルでの定数の使用


#include <openssl/evp.h>
#include <stdlib.h>
#define SIZE16 16

/* Using the cryptographic routines */

int func(EVP_CIPHER_CTX *ctx, unsigned char *key){
    unsigned char iv[SIZE16] = {'1', '2', '3', '4','5','6','b','8','9',
                                 '1','2','3','4','5','6','7'};
    return EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv, 1);  //Noncompliant
}

この例では、初期化ベクトル iv は定数のみを保持します。この定数初期化ベクトルにより、暗号は辞書攻撃に対して脆弱になります。

修正 — ランダムな初期化ベクトルを使用

1 つの修正方法として、強力な乱数発生器を使用して初期化ベクトルを作成します。修正した次のコードでは、openssl/rand.h で宣言されている関数 RAND_bytes を使用します。



#include <openssl/evp.h>
#include <openssl/rand.h>
#include <stdlib.h>
#define SIZE16 16

/* Using the cryptographic routines */

int func(EVP_CIPHER_CTX *ctx, unsigned char *key){
    unsigned char iv[SIZE16];
    RAND_bytes(iv, 16);
    return EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv, 1); 
}
問題

定数の暗号キーは、暗号化キーまたは復号化キーに定数を使用する場合に発生します。

リスク

暗号化キーまたは復号化キーに定数を使用すると、攻撃者はそのキーを簡単に手に入れることができます。

データの暗号化とその後の復号化にはキーを使用します。キーが容易に取得される場合、そのキーを使用して暗号化したデータは安全ではありません。

修正方法

強力な乱数発生器を使用してランダムなキーを作成します。

暗号法的に脆弱な疑似乱数発生器の一覧については、Vulnerable pseudo-random number generatorを参照してください。

例 - キーでの定数の使用


#include <openssl/evp.h>
#include <stdlib.h>
#define SIZE16 16

int func(EVP_CIPHER_CTX *ctx, unsigned char *iv){
    unsigned char key[SIZE16] = {'1', '2', '3', '4','5','6','b','8','9',
                                 '1','2','3','4','5','6','7'};
    return EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv, 1);  //Noncompliant
}

この例では、暗号キー key は定数のみを保持します。攻撃者は定数のキーを簡単に手に入れることができます。

修正 — ランダムなキーを使用

強力な乱数発生器を使用して暗号キーを作成します。修正した次のコードでは、openssl/rand.h で宣言されている関数 RAND_bytes を使用します。



#include <openssl/evp.h>
#include <openssl/rand.h>
#include <stdlib.h>
#define SIZE16 16

int func(EVP_CIPHER_CTX *ctx, unsigned char *iv){
    unsigned char key[SIZE16];
    RAND_bytes(key, 16);
    return EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv, 1); 
}
問題

予測可能なブロック暗号初期化ベクトルは、脆弱な乱数発生器を使用してブロック暗号初期化ベクトルを生成する場合に発生します。

リスク

脆弱な乱数発生器を使用して初期化ベクトルを生成すると、データは辞書攻撃に対して脆弱になります。

ブロック暗号は、データを固定サイズのブロックに分割します。CBC (Cipher Block Chaining) などのブロック暗号モードは、辞書攻撃から保護するために、各ブロックと前のブロックからの暗号化出力とを XOR 演算します。最初のブロックを保護するため、このようなモードではランダムな初期化ベクトル (IV) を使用します。IV に脆弱な乱数発生器を使用すると、データは辞書攻撃に対して脆弱になります。

修正方法

初期化ベクトルに強力な疑似乱数発生器 (PRNG) を使用します。たとえば、次を使用します。

  • UNIX®/dev/random や Windows®CryptGenRandom() など、OS レベルの PRNG

  • カウンター (CTR) モードでの高度暗号化標準 (AES)、HMAC-SHA1 など、アプリケーション レベルの PRNG

暗号法的に脆弱な疑似乱数発生器の一覧については、Vulnerable pseudo-random number generatorを参照してください。

例 - 予測可能な初期化ベクトル


#include <openssl/evp.h>
#include <openssl/rand.h>
#include <stdlib.h>
#define SIZE16 16

int func(EVP_CIPHER_CTX *ctx, unsigned char *key){
    unsigned char iv[SIZE16];
    RAND_pseudo_bytes(iv, 16);
    return EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv, 1);  //Noncompliant
}

この例では、openssl/rand.h で宣言されている関数 RAND_pseudo_bytes によって初期化ベクトルを生成しています。RAND_pseudo_bytes が生成するバイト シーケンスは、必ずしも予測不可能ではありません。

修正 — 強力な乱数発生器を使用

強力な乱数発生器を使用して初期化ベクトルを作成します。修正した次のコードでは、openssl/rand.h で宣言されている関数 RAND_bytes を使用します。



#include <openssl/evp.h>
#include <openssl/rand.h>
#include <stdlib.h>
#define SIZE16 16

int func(EVP_CIPHER_CTX *ctx, unsigned char *key){
    unsigned char iv[SIZE16];
    RAND_bytes(iv, 16);
    return EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv, 1); 
}
問題

予測可能な暗号キーは、暗号化または復号化のキーに脆弱な乱数発生器を使用する場合に発生します。

リスク

暗号化または復号化のキーに脆弱な乱数発生器を使用すると、攻撃者はそのキーを簡単に手に入れることができます。

データの暗号化とその後の復号化にはキーを使用します。キーが容易に取得される場合、そのキーを使用して暗号化したデータは安全ではありません。

修正方法

キーに強力な疑似乱数発生器 (PRNG) を使用します。次に例を示します。

  • UNIX の /dev/random や Windows の CryptGenRandom() など、OS レベルの PRNG を使用

  • カウンター (CTR) モードの高度暗号化標準 (AES)、HMAC-SHA1 など、アプリケーション レベルの PRNG を使用

暗号法的に脆弱な疑似乱数発生器の一覧については、Vulnerable pseudo-random number generatorを参照してください。

例 - 予測可能な暗号キー


#include <openssl/evp.h>
#include <openssl/rand.h>
#include <stdlib.h>
#define SIZE16 16

int func(EVP_CIPHER_CTX *ctx, unsigned char *iv){
    unsigned char key[SIZE16];
    RAND_pseudo_bytes(key, 16);
    return EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv, 1);  //Noncompliant
}

この例では、openssl/rand.h で宣言されている関数 RAND_pseudo_bytes によって暗号キーを生成しています。ただし、RAND_pseudo_bytes が生成するバイト シーケンスは、必ずしも予測不可能ではありません。

修正 — 強力な乱数発生器を使用

1 つの修正方法として、強力な乱数発生器を使用して暗号キーを生成します。修正した次のコードでは、openssl/rand.h で宣言されている関数 RAND_bytes を使用します。



#include <openssl/evp.h>
#include <openssl/rand.h>
#include <stdlib.h>
#define SIZE16 16

int func(EVP_CIPHER_CTX *ctx, unsigned char *iv){
    unsigned char key[SIZE16];
    RAND_bytes(key, 16);
    return EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv, 1); 
}
問題

要注意のヒープ メモリが解放前にクリアされていませんは、機密データを含んでいる、動的に割り当てられたメモリを検出します。メモリを解放する際に機密データがクリアされていないと、Bug Finder により関数 free での欠陥が報告されます。

リスク

メモリ ゾーンが再割り当てされる場合でも、攻撃者は古いメモリ ゾーンにある機密データを検査できます。

修正方法

free を呼び出す前に、memsetSecureZeroMemory を使用して、機密データをクリアしておきます。

例 - 機密のバッファーが解放されてもクリアされていない
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <pwd.h>

void sensitiveheapnotcleared(const char * my_user) {
    struct passwd* result, pwd;
    long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
    char* buf = (char*) malloc(1024);
    getpwnam_r(my_user, &pwd, buf, bufsize, &result);
    free(buf); //Noncompliant
}

この例では、関数でパスワードのバッファーが使用され、関数の終了前にそのメモリが解放されています。しかし、メモリ内のデータは、free コマンドを使用してもクリアされません。

修正 — データを無効化

1 つの修正方法として、データを上書きして機密情報をクリアします。この例では memset を使用して、データをゼロで上書きしています。

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <pwd.h>
#include <assert.h>

#define isNull(arr) for(int i=0;i<(sizeof(arr)/sizeof(arr[0]));i++) assert(arr[i]==0)

void sensitiveheapnotcleared(const char * my_user) {
    struct passwd* result, pwd;
    long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
    char* buf = (char*) malloc(1024);

    if (buf) {
        getpwnam_r(my_user, &pwd, buf, bufsize, &result);
        memset(buf, 0, (size_t)1024);
        isNull(buf);
        free(buf); 
    }
}
問題

スタック内にクリアされていない機密データがありますは、機密データを含んでいる静的メモリを検出します。関数またはプログラムの終了前に機密データがスタックからクリアされていない場合、Bug Finder により最後の中かっこで欠陥が報告されます。

リスク

パスワードやユーザー情報などの機密情報がスタック内に残っていると、プログラム終了後、攻撃者がその情報にアクセスできるようになります。

修正方法

関数またはプログラムの終了前に、memsetSecureZeroMemory を使用して、機密データを含むメモリ ゾーンをクリアしておきます。

例 - パスワード情報の静的バッファー
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>

void bug_sensitivestacknotcleared(const char * my_user) {
    struct passwd* result, pwd;
    long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
    char buf[1024] = "";
    getpwnam_r(my_user, &pwd, buf, bufsize, &result);
}  //Noncompliant

この例では、静的バッファーがパスワード情報で埋められています。プログラムは、その終了時にスタック メモリを解放します。しかし、データは依然としてメモリからアクセス可能です。

修正 — メモリをクリア

1 つの修正方法として、関数の終了前にメモリを上書きします。この例では、memset を使用してバッファー メモリからデータをクリアしています。

#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <pwd.h>
#include <assert.h>

#define isNull(arr) for(int i=0; i<(sizeof(arr)/sizeof(arr[0])); i++) assert(arr[i]==0)

void corrected_sensitivestacknotcleared(const char * my_user) {
    struct passwd* result, pwd;
    long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
    char buf[1024] = "";
    getpwnam_r(my_user, &pwd, buf, bufsize, &result);
    memset(buf, 0, (size_t)1024);
    isNull(buf);
}
問題

安全でない標準暗号化関数では、壊れているか脆弱な暗号アルゴリズムを用いる関数の使用を検出します。たとえば、crypt は再呼び出し可能でなく、リスクの大きいデータ暗号化標準 (DES) に基づいています。

リスク

壊れているか脆弱、または非標準などのアルゴリズムを使用する場合、機密情報が攻撃者の脅威にさらされる可能性があります。確信犯的なハッカーはさまざまな技術を利用して、保護されたデータにアクセスする可能性があります。

脆弱な関数が再呼び出し不可能であり、同時実行される複数のプログラムでその関数が使用されると、競合状態のリスクがさらに高まります。

修正方法

上述のような暗号化アルゴリズムを使用する関数の使用を回避します。代わりに、より強固な暗号化アルゴリズムを使用する、再呼び出し可能な関数を使用します。

メモ

crypt の実装によっては、追加の、よりセキュリティで保護された暗号化アルゴリズムをサポートしています。

例 - crypt を使用したパスワードの復号化
#define _GNU_SOURCE
#include <pwd.h>
#include <string.h>
#include <crypt.h>

volatile int rd = 1;

const char *salt = NULL;
struct crypt_data input, output;

int verif_pwd(const char *pwd, const char *cipher_pwd, int safe)
{
    int r = 0;
    char *decrypted_pwd = NULL;
    
    switch(safe)
    {
      case 1: 
        decrypted_pwd = crypt_r(pwd, cipher_pwd, &output);
        break;
        
      case 2: 
        decrypted_pwd = crypt_r(pwd, cipher_pwd, &output);
        break;
        
      default:
        decrypted_pwd = crypt(pwd, cipher_pwd);  //Noncompliant
        break;
    }
    
    r = (strcmp(cipher_pwd, decrypted_pwd) == 0); 
    
    return r;
}

この例では、crypt_rcrypt によってパスワードを復号化しています。しかし、crypt は再呼び出し不可能であり、安全でないデータ暗号化標準アルゴリズムを使用しています。

修正 — crypt_r を使用

1 つの修正方法として、cryptcrypt_r に置き換えます。

#define _GNU_SOURCE
#include <pwd.h>
#include <string.h>
#include <crypt.h>

volatile int rd = 1;

const char *salt = NULL;
struct crypt_data input, output;

int verif_pwd(const char *pwd, const char *cipher_pwd, int safe)
{
    int r = 0;
    char *decrypted_pwd = NULL;
    
    switch(safe)
    {
      case 1: 
        decrypted_pwd = crypt_r(pwd, cipher_pwd, &output);
        break;
        
      case 2: 
        decrypted_pwd = crypt_r(pwd, cipher_pwd, &output);
        break;
        
      default:
        decrypted_pwd = crypt_r(pwd, cipher_pwd, &output);  
        break;
    }
    
    r = (strcmp(cipher_pwd, decrypted_pwd) == 0);
    
    return r;
}

チェック情報

グループ: Rec.48.その他 (MSC)

バージョン履歴

R2019a で導入


1 This software has been created by MathWorks incorporating portions of: the “SEI CERT-C Website,” © 2017 Carnegie Mellon University, the SEI CERT-C++ Web site © 2017 Carnegie Mellon University, ”SEI CERT C Coding Standard – Rules for Developing safe, Reliable and Secure systems – 2016 Edition,” © 2016 Carnegie Mellon University, and “SEI CERT C++ Coding Standard – Rules for Developing safe, Reliable and Secure systems in C++ – 2016 Edition” © 2016 Carnegie Mellon University, with special permission from its Software Engineering Institute.

ANY MATERIAL OF CARNEGIE MELLON UNIVERSITY AND/OR ITS SOFTWARE ENGINEERING INSTITUTE CONTAINED HEREIN IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.

This software and associated documentation has not been reviewed nor is it endorsed by Carnegie Mellon University or its Software Engineering Institute.