メインコンテンツ

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

CERT C: Rec.STR02-C

Sanitize data passed to complex subsystems

説明

ルール定義

複雑なサブシステムに渡すデータをサニタイズします。1

Polyspace 実装

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

  • 外部制御されるコマンドの実行

  • 外部制御されるパスから実行されたコマンド

  • 外部制御されるパスから読み込まれたライブラリ

チェッカーの拡張

既定の Bug Finder 解析では、現在の解析境界の外部からの特定の入力に対しては、このチェッカーにフラグを設定しない場合があります。Polyspace 解析での汚染のソースを参照してください。Polyspace 解析の現在のスコープ以外から発生したすべてのデータを汚染されたものと見なすには、コマンド ライン オプション [-consider-analysis-perimeter-as-trust-boundary] を使用します。

すべて展開する

問題

外部制御されるコマンドの実行では、外部制御される入力により完全にまたは部分的に構築されるコマンドをチェックします。

リスク

攻撃者は外部制御される入力を、オペレーティング システムのコマンドとして、あるいはアプリケーションの引数として利用できます。攻撃者は、読み取りや変更が可能な機密データの読み取りや変更を行い、意図しないコードを実行し、あるいはプログラムの他の側面にアクセスする可能性があります。

修正方法

入力を検証し、想定どおりの入力値のみが許可されるようにします。たとえば、許容される入力のリストを作成し、入力をそのリストと比較します。

例 - 外部コマンドの呼び出し
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include "unistd.h"
#include "dlfcn.h"
#include "limits.h"
#define MAX 128


void taintedexternalcmd(void)
{
	char* usercmd;
	fgets(usercmd,MAX,stdin);
	char cmd[MAX] = "/usr/bin/cat ";
	strcat(cmd, usercmd);
	system(cmd);  //Noncompliant
}

この関数例では、コマンド変数をチェックせずにユーザー入力からコマンドを呼び出しています。

修正 — 事前定義されたコマンドを使用

1 つの修正方法として、switch ステートメントを使用して、ユーザー入力を switch 変数として使う事前定義されたコマンドを実行します。

#define _XOPEN_SOURCE
#define _GNU_SOURCE

#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include "unistd.h"
#include "dlfcn.h"
#include "limits.h"

enum {
    SIZE10  =  10,
    SIZE100 = 100,
    SIZE128 = 128
};
enum { CMD0 = 1, CMD1, CMD2 };

void taintedexternalcmd(void)
{
    int usercmd = strtol(getenv("cmd"),NULL,10);
    char cmd[SIZE128] = "/usr/bin/cat ";

    switch(usercmd) {
        case CMD0:
            strcat(cmd, "*.c");
            break;
        case CMD1:
            strcat(cmd, "*.h");
            break;
        case CMD2:
            strcat(cmd, "*.cpp");
            break;
        default:
            strcat(cmd, "*.c");
    }
    system(cmd); 
}
問題

外部制御されるパスから実行されたコマンドでは、アプリケーションが制御するコマンドのパスをチェックします。コマンドのパスが外部ソース由来であるか外部ソースで構築されている場合、Bug Finder ではコマンドの関数にフラグを立てます。

リスク

攻撃者は次のことができます。

  • プログラムが実行するコマンドを、場合によっては攻撃者のみが制御できるコマンドに変更する。

  • コマンドが実行される環境を変更し、それによって攻撃者がコマンドの意味と実行内容を制御する。

修正方法

コマンドを呼び出す前にパスを検証し、想定どおりの場所であることを確認します。

例 - 環境変数から得た実行パス
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

enum {
    SIZE10  =  10,
    SIZE100 = 100,
    SIZE128 = 128
};

void bug_taintedpathcmd() {
    char cmd[SIZE128] = "";
    char* userpath = getenv("MYAPP_PATH");

    strncpy(cmd, userpath, SIZE100);
    strcat(cmd, "/ls *");
    /* Launching command */
    system(cmd);  //Noncompliant
}

この例では、パスを環境変数 MYAPP_PATH から取得しています。system は、パスの値をチェックせずにそのパスからコマンドを実行しています。そのパスが想定されたパスではない場合、プログラムは誤った場所で実行されます。

修正 — 信頼できるパスを使用

1 つの修正方法として、許可されているパスのリストを使用して、環境変数パスと照合します。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

enum {
    SIZE10  =  10,
    SIZE100 = 100,
    SIZE128 = 128
};

/* Function to sanitize a string */
int sanitize_str(char* s, size_t n) {
    int res = 0;
    /* String is ok if */
    if (s && n>0 && n<SIZE128) {
        /* - string is not null                     */
        /* - string has a positive and limited size */
        s[n-1] = '\0';  /* Add a security \0 char at end of string */
        /* Tainted pointer detected above, used as "firewall" */
        res = 1;
    }
    return res;
}

/* Authorized path ids */
enum { PATH0=1, PATH1, PATH2 };

void taintedpathcmd() {
    char cmd[SIZE128] = "";

    char* userpathid = getenv("MYAPP_PATH_ID");
    if (sanitize_str(userpathid, SIZE100)) {
        int pathid = atoi(userpathid);


        char path[SIZE128] = "";
        switch(pathid) {
            case PATH0:
                strcpy(path, "/usr/local/my_app0");
                break;
            case PATH1:
                strcpy(path, "/usr/local/my_app1");
                break;
            case PATH2:
                strcpy(path, "/usr/local/my_app2");
                break;
            default:
                /* do nothing */
		break;
        }
        if (strlen(path)>0) {
            strncpy(cmd, path, SIZE100);
            strcat(cmd, "/ls *");
            system(cmd);    
        }
    }
}
問題

外部制御されるパスから読み込まれたライブラリは、固定のパスまたは制御されているパスから読み込まれたライブラリを調べます。この固定のパス上にある 1 つ以上の場所を想定外のアクターが制御できる場合、Bug Finder により欠陥が報告されます。

リスク

ライブラリの読み込みに使用されているパスを攻撃者が知っているか制御している場合、攻撃者は以下を変更できます。

  • プログラムで読み込まれるライブラリ。想定されたライブラリとコマンドが置き換えられる。

  • ライブラリが実行される環境。想定外の権限と機能が攻撃者に付与される。

修正方法

可能な場合は、ハードコードされたパス名か完全修飾パス名を使用してライブラリを読み込みます。ハードコードされたパスは、他のシステムでは機能しない可能性があります。ハードコードされたパスには一元的な場所を使用して、ソース コード内でパスを簡単に変更できるようにします。

別の解決策として、明示的なパスが要求される関数を使用します。たとえば、system() では環境変数 PATH を使用できるため、絶対パスは要求されません。しかし、execl()execv() では絶対パスが要求されます。

例 - カスタム ライブラリの呼び出し
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>
#include <limits.h>

enum {
    SIZE10  =  10,
    SIZE100 = 100,
    SIZE128 = 128
};

void* taintedpathlib() {
    void* libhandle = NULL;
    char lib[SIZE128] = "";
    char* userpath = getenv("LD_LIBRARY_PATH");
    strncpy(lib, userpath, SIZE128);
    strcat(lib, "/libX.so");
    libhandle = dlopen(lib, 0x00001); //Noncompliant
    return libhandle;
}

この例では、ライブラリ libX.so が環境変数 LD_LIBRARY_PATH から読み込まれています。攻撃者は、この環境変数のライブラリ パスを変更できます。実際に読み込まれるライブラリは、想定とは異なるライブラリとなる可能性があります。

修正 — パスを変更してチェック

1 つの修正方法として、ライブラリ パスの取得方法を変更し、ライブラリを開く前にライブラリのパスをチェックします。この例では、パスを入力引数として受け取っています。そのうえでパスがチェックされ、ライブラリが /usr/ の下にないことが確認されます。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>
#include <limits.h>

enum {
    SIZE10  =  10,
    SIZE100 = 100,
    SIZE128 = 128
};

/* Function to sanitize a string */
int sanitize_str(char* s, size_t n) {
    /* strlen is used here as a kind of firewall for tainted string errors */
    int res = (strlen(s) > 0 && strlen(s) < n);
    return res;
}
void* taintedpathlib(char* userpath) {
    void* libhandle = NULL;
    if (sanitize_str(userpath, SIZE128)) {
        char lib[SIZE128] = "";

        if (strncmp(userpath, "/usr", 4)!=0) {
            strncpy(lib, userpath, SIZE128);
            strcat(lib, "/libX.so");
            libhandle = dlopen(lib, RTLD_LAZY);    
        }
    }
    return libhandle;
}

チェック情報

グループ: Rec.07.文字と文字列 (STR)

バージョン履歴

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.