CERT C: Rec. MSC18-C

Будьте осторожны при обработке конфиденциальных данных, таких как пароли, в коде программы

Описание

Определение правила

Будьте осторожны при обработке конфиденциальных данных, таких как пароли, в коде программы.[1]

Реализация Polyspace

Эта проверка проверяет на наличие следующих проблем:

  • Вектор инициализации постоянного блочного шифра.

  • Постоянный ключ шифра.

  • Предсказуемый вектор инициализации блочного шифра.

  • Предсказуемый ключ шифра.

  • Чувствительная куча памяти не очищена перед релизом.

  • Неубранные конфиденциальные данные в стеке.

  • Небезопасная стандартная функция шифрования.

Примеры

расширить все

Проблема

Вектор инициализации постоянного блочного шифра возникает, когда вы используете константу для вектора инициализации (IV) во время шифрования.

Риск

Использование константы IV эквивалентно отказу от использования IV. Ваши зашифрованные данные уязвимы для атак словаря.

Блочные шифры разбивают ваши данные на блоки фиксированного размера. Режимы блочного шифрования, такие как CBC (Cipher Block Chaining), защищают от атак словаря XOR-ing каждый блок с зашифрованным выходом из предыдущего блока. Чтобы защитить первый блок, эти режимы используют вектор случайной инициализации (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); 
}

В этом примере вектор инициализации iv имеет только постоянные. Вектор постоянной инициализации делает ваш шифр уязвимым для атак словаря.

Коррекция - Используйте вектор случайной инициализации

Одна возможная коррекция состоит в том, чтобы использовать сильный генератор случайных чисел, чтобы создать вектор инициализации. Исправленный код здесь использует функцию RAND_bytes объявлен в openssl/rand.h.


#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); 
}

В этом примере ключ шифра, key, имеет только константы. Атакующий может легко получить постоянный ключ.

Коррекция - Используйте случайный ключ

Используйте сильный генератор случайных чисел, чтобы создать ключ шифра. Исправленный код здесь использует функцию RAND_bytes объявлен в openssl/rand.h.


#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-ing каждый блок с зашифрованным выходом из предыдущего блока. Чтобы защитить первый блок, эти режимы используют вектор случайной инициализации (IV). Если вы используете слабый генератор случайных чисел для вашего IV, ваши данные становятся уязвимыми для атак словаря.

Зафиксировать

Используйте сильный генератор псевдослучайных чисел (PRNG) для вектора инициализации. Для образца используйте:

  • PRNG уровня ОС, такой как /dev/random в UNIX® или CryptGenRandom() в Windows®

  • PRNG прикладного уровня, такой как Advanced Encryption Standard (AES) в режиме счетчика (CTR), HMAC-SHA1 и т.д.

Список генераторов случайных чисел, которые являются криптографически слабыми, см. 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); 
}

В этом примере функция RAND_pseudo_bytes объявлен в openssl/rand.h создает вектор инициализации. Последовательности байтов, которые RAND_pseudo_bytes генерация не обязательно непредсказуема.

Коррекция - Используйте сильный генератор случайных чисел

Используйте сильный генератор случайных чисел, чтобы создать вектор инициализации. Исправленный код здесь использует функцию RAND_bytes объявлен в openssl/rand.h.


#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) для ключа. Для образца:

  • Используйте PRNG уровня ОС, такой как /dev/random в UNIX или CryptGenRandom() в Windows

  • Используйте PRNG прикладного уровня, такой как Advanced Encryption Standard (AES) в режиме счетчика (CTR), HMAC-SHA1 и т.д.

Список генераторов случайных чисел, которые являются криптографически слабыми, см. 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); 
}

В этом примере функция RAND_pseudo_bytes объявлен в openssl/rand.h задает ключ шифра. Однако последовательности байтов, которые RAND_pseudo_bytes генерация не обязательно непредсказуема.

Коррекция - Используйте сильный генератор случайных чисел

Одна возможная коррекция состоит в том, чтобы использовать сильный генератор случайных чисел, чтобы получить ключ шифра. Исправленный код здесь использует функцию RAND_bytes объявлен в openssl/rand.h.


#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, очистить конфиденциальные данные с помощью memset или SecureZeroMemory.

Пример - Чувствительный буфер освобожден, не очищен
#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);
}

В этом примере функция использует буфер паролей и освобождает память перед окончанием функции. Однако данные в памяти не очищаются при помощи free команда.

Коррекция - аннулирование данных

Одной из возможных коррекций является запись данных для очистки конфиденциальной информации. Этот пример использует 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 поднимает дефект на последней фигурной скобке.

Риск

Оставление конфиденциальной информации в вашем стеке, таком как пароли или информация о пользователе, позволяет злоумышленнику получить дополнительный доступ к информации после окончания работы вашей программы.

Зафиксировать

Перед выходом из функции или программы очистите зоны памяти, которые содержат конфиденциальные данные, используя memset или SecureZeroMemory.

Пример - Статический буфер информации о пароле
#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);
} 

В этом примере статический буфер заполняется парольной информацией. Программа освобождает память стека в конце программы. Однако данные все еще доступны из памяти.

Коррекция - Очистить память

Одной из возможных коррекций является запись по памяти перед выходом из функции. Этот пример использует 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); 
        break;
    }
    
    r = (strcmp(cipher_pwd, decrypted_pwd) == 0); 
    
    return r;
}

В этом примере crypt_r и crypt расшифровать пароль. Однако crypt не является входным и использует небезопасный алгоритм Data Encryption Standard.

Коррекция - Использование crypt_r

Одной из возможных коррекций является замена crypt с crypt_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] Это программное обеспечение было создано MathWorks, включающее фрагменты: «Сайт SEI CERT-C», © 2017 Университет Карнеги Меллон, Веб-сайт SEI CERT-C + + © 2017 Университет Карнеги Меллон, "Стандарт кодирования SEI CERT C - Правила разработки безопасных, Надежные и безопасные системы - 2016 Edition ", © 2016 Университет Карнеги Меллон, и "Стандарт кодирования SEI CERT C++ - Правила разработки безопасных, Надежные и безопасные системы в C++ - 2016 Edition "© 2016 Университет Карнеги Меллон, с специального разрешения от его Института программной инженерии.

ЛЮБОЙ МАТЕРИАЛ УНИВЕРСИТЕТА КАРНЕГИ МЕЛЛОН И/ИЛИ ЕГО ИНЖЕНЕРНОГО ИНСТИТУТА ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ, СОДЕРЖАЩИЙСЯ В НАСТОЯЩЕМ ДОКУМЕНТЕ, ПОСТАВЛЯЕТСЯ НА БАЗИСЕ «КАК ЕСТЬ». УНИВЕРСИТЕТ КАРНЕГИ МЕЛЛОН НЕ ДАЕТ НИКАКИХ ГАРАНТИЙ, ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, В ОТНОШЕНИИ ЛЮБОГО ВОПРОСА, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ, ГАРАНТИЮ ПРИГОДНОСТИ ДЛЯ ЦЕЛЕЙ ИЛИ КОММЕРЧЕСКОЙ ВЫГОДЫ, ИСКЛЮЧИТЕЛЬНОСТИ, ИЛИ УНИВЕРСИТЕТ КАРНЕГИ МЕЛЛОН НЕ ДАЕТ НИКАКИХ ГАРАНТИЙ В ОТНОШЕНИИ СВОБОДЫ ОТ ПАТЕНТА, ТОВАРНОГО ЗНАКА ИЛИ НАРУШЕНИЯ АВТОРСКИХ ПРАВ.

Это программное обеспечение и связанная с ним документация не были рассмотрены и не одобрены Университетом Карнеги-Меллон или его Институтом программной инженерии.