読者です 読者をやめる 読者になる 読者になる

セキュリティ・キャンプ 2016  選択問題解答

 ブログでセキュキャンの解答を晒している方が何人かいらっしゃったので、自分も晒してみようと思います。 ここ違くね?とかここは同じだ!とか思っていただければ幸いです。

選択問題1

 実行結果のアドレスのサイズを見る限りですと、上記のプログラムは64bit の OS で実行されているのではないかと考えました。

 理由は、32bit の OS で上記のプログラムを実行した際は上記の実行結果よりも小さなサイズであり、 64bit の OS で実行した際は上記の実行結果に近いアドレスとなったからです。

 32bit の OS で実行した結果は下記の通りです。

hoge address = 0019F824
fuga address = 002D3208

 他に感じたことは、スタックとヒープのおおよその先頭アドレスがわかるので、上記のアドレスの差から これら2つの領域に割り当てられているアドレス領域のサイズがわかるのでは無いかということです。 「おおよその」というのは、ヒープに OS の管理領域がある為、正確な位置がわからないという意味です。

選択問題3

 パソコンの電源が入るとまず、システムBIOSがハードウェアを初期化して記憶装置からブートローダを読み出します。 そして読み出されたブートローダMBRに記録されているプログラムを呼び出し、呼び出されたプログラムはOSを読み込んで起動します。

 OS起動後に何かプログラムを実行させる時は、補助記憶装置に書き込まれたプログラムを実行することになります。 しかし問題文にあるようにCPUは主記憶上のプログラムしか実行できないので、 プログラムローダと呼ばれるものが必要に応じて、補助記憶装置に書き込まれているプログラムを主記憶装置に読み出しています。

 その後、プログラムローダは読み出したプログラムの先頭アドレスに飛ぶことで、目的のプログラムが実行されています。 以上の処理が行われているため、私たちは補助記憶装置に書き込んだプログラムを実行することが出来ています。

選択問題4

 プログラムは Windows Vista 32bit版 で Microsoft Visual C++ 2010 Express で作成・実行しました。 最初はこの問題を解こうとは思っていなかったのですが、チノちゃんが目に入ってきて問題を解くことを決意しました。 ソースコードは下記の通りです。

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

#define TRUE  1
#define FALSE 0

#define MAX_MAGIC       2
#define MAX_SOURCE      20
#define MAX_DESTINATION 20

/* バイトオーダーの違いを吸収する */
typedef union{
    unsigned int  byte_4;
    char          byte_1[4];
}byte_union;

/*------------ 
 | その他関数 |
  ------------*/

/* 文字列を小文字に変換 */
void str_to_lower(char *str,int size){
    int i;
    for( i=0 ; i<MAX_SOURCE+1 ; i++ ) str[i] = tolower(str[i]);
}

/* バイトオーダー変換 */
void change_byte_order(byte_union *data){
    const int INT_SIZE = sizeof(int) - 1;
    byte_union tmp_data;
    
    // バイトオーダー変換
    int i;
    for( i=0 ; i<4 ; i++ ) tmp_data.byte_1[i] = data->byte_1[INT_SIZE-i];
    
    // 代入
    data->byte_4 = tmp_data.byte_4;
}


/*-------
 | 条件 |
  ------- */

/* Magicの検証 */
int Condition1(char *data){
    if( strcmp(data,"RH") == 0 ) return TRUE;
    else                         return FALSE;
}

/* Sourceの検証 */
int Condition2(char *data){
    str_to_lower(data, MAX_SOURCE);
    if( strcmp(data,"rise-san") == 0 || strcmp(data,"cocoa-san") == 0 ) return TRUE;
    else                                                                return FALSE;
}

/* Destinationの検証 */
int Condition3(char *data){
    str_to_lower(data, MAX_DESTINATION);
    if( strcmp(data,"chino-chan") == 0 || strcmp(data, "chino") == 0 ) return TRUE;
    else                                                               return FALSE;
}

/* data に valid_order_brand が含まれているか検証 */
int Condition4(char *data){
    const char *valid_order_brand[3] = {
        "BlueMountain",
        "Columbia",
        "OriginalBlend"
    };

    int i=0;
    for( i=0 ; i<3 ; i++ ) if( strstr(data,valid_order_brand[i]) != NULL ) return TRUE;
    return TRUE;
}

/* data に invalid_order_brand が含まれていないか検証 */
int Condition5(char *data){
    const char *invalid_order_brand[2] = {
        "DandySoda",
        "FrozenEvergreen"
    };
    
    int i=0;
    for( i=0 ; i<2 ; i++ ) if( strstr(data,invalid_order_brand[i]) != NULL )  return FALSE;
    return TRUE;
}

/*-----------  
 |メイン関数 |
  -----------*/
int main(void)
{
    // 変数宣言(ファイルからデータを読み込む変数は、読み込んだデータを文字列として扱うために \0 の領域分を確保している)
    char Magic[MAX_MAGIC+1];
    char Source[MAX_SOURCE+1];
    char Destination[MAX_DESTINATION+1];
    byte_union DataLength;
    char *Data;
    int  byte_count = 0;
    int  pass_flag  = 0;
    int  loop_count = 0; // ループ回数を出力するためのもので、ループ処理には使用されない
    
    // 条件が全て通ったときの pass_flag の値
    const int PASS_FLAG_COUNT = 4;

    //ファイル読み出し
    FILE *fp;
    fp = fopen("pyonpyon.rh","rb");
    if(fp == NULL){
        printf("ファイル読み込み失敗\n");
        exit(0);
    }
    
    // ファイルサイズ取得
    fpos_t fsize = 0;
    fseek(fp,0,SEEK_END);
    fgetpos(fp,&fsize);
    fseek(fp,0,SEEK_SET);
    
    // ファイル終端まで読み出す
    while( byte_count < fsize ){
        // データ読み込み変数初期化
        memset(Magic,'\0',sizeof(Magic));
        memset(Source,'\0',sizeof(Source));
        memset(Destination,'\0',sizeof(Destination));
        
        // 一連のファイルを読み出す
        fread(Magic,sizeof(char),MAX_MAGIC,fp);
        fread(Source,sizeof(char),MAX_SOURCE,fp);
        fread(Destination,sizeof(char),MAX_DESTINATION,fp);
        fread(&DataLength.byte_4,sizeof(int),1,fp);
        // pyonpyon.rh はビッグエンディアンだが、windowsはリトルエンディアンなので変換する
        change_byte_order(&DataLength); 
        Data = (char *)calloc(DataLength.byte_4+1,sizeof(char));
        fread(Data,DataLength.byte_4,1,fp);
        
        // バイトカウントに読み込んだバイト数加算
        byte_count += MAX_MAGIC + MAX_SOURCE + MAX_DESTINATION + sizeof(int) + DataLength.byte_4;
        
        // 判定
        if( Condition1(Magic) )                       pass_flag++;
        if( Condition2(Source) )                      pass_flag++;
        if( Condition3(Destination) )                 pass_flag++;
        if( Condition4(Data) && Condition5(Data) )    pass_flag++;
        
        // ループカウント更新
        loop_count++;

        // 結果出力
        if( pass_flag == PASS_FLAG_COUNT ) printf("%2d回目:PASS\n",loop_count);
        else                               printf("%2d回目:REJECTED\n",loop_count);

        // メモリ開放 & paa_flag初期化
        free(Data);
        pass_flag = 0;
    }
    
    fclose(fp);
    return 0;
}

実行結果

 1回目:PASS
 2回目:PASS
 3回目:PASS
 4回目:PASS
 5回目:PASS
 6回目:PASS
 7回目:PASS
 8回目:PASS
 9回目:PASS
10回目:REJECTED
11回目:PASS
12回目:PASS
13回目:PASS
14回目:REJECTED
15回目:REJECTED
16回目:PASS
17回目:PASS
18回目:PASS
19回目:REJECTED
20回目:REJECTED
21回目:REJECTED
22回目:PASS
23回目:PASS
24回目:PASS
25回目:REJECTED
26回目:REJECTED
27回目:REJECTED
28回目:REJECTED
29回目:PASS
30回目:PASS
31回目:PASS
32回目:REJECTED
33回目:REJECTED
34回目:REJECTED
35回目:REJECTED
36回目:REJECTED
37回目:PASS
38回目:PASS
39回目:PASS
40回目:REJECTED
41回目:REJECTED
42回目:REJECTED
43回目:REJECTED
44回目:REJECTED
45回目:REJECTED

選択問題6

 まず簡単なシステム構成の前提ですが、ユーザ認証等の処理に使用する言語は PHP で、ユーザ情報を保存するデータベースは MySQL です。

 上記の構成でユーザ認証のWebアプリを構築した場合、自分が一番最初に行うセキュリティテストは「SQLインジェクション」の対策が行われているかどうかのテストです。 SQLインジェクションを選択した理由は、攻撃者が手軽に実行できる攻撃でありながら、 開発者はフレームワーク等を使用していない場合は自ら対策を行う必要があるからです。

 また開発者側がSQLインジェクション攻撃の存在を認識していても、サニタイズを忘れてしまう可能性もあります。 以上のような理由から、私は最初のセキュリティテストに「SQLインジェクション」の対策を行うことを選択しました。