セキュリティ・キャンプ 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インジェクション」の対策を行うことを選択しました。