システムプログラム(第10週): スクリプト言語、クロスサイトスクリプティング攻撃

                                       筑波大学 システム情報系 情報工学域
                                       新城 靖
                                       <yas@cs.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2012/2012-06-20 /index.html
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2012/
http://www.coins.tsukuba.ac.jp/~yas/

JavaScript

JavaScript は、WWW ブラウザ上で動作するスクリプト言語である。 Ecma International により標準化されたものは、ECMAScript

JavaScript は、文法が少し Java 言語に似ているが、Java とはまったく別の 言語である。WWW ページの中の Javaアプレットは、 「実行可能なインライン・イメージ」に似ている。これに対して JavaScript の記述は、「実行可能なインライン・テキスト」 に似ている。

JavaScrip のプログラムの例:

<SCRIPT LANGUAGE="JavaScript">
<!--
for( i=0 ; i<10; i++ )
  document.writeln("<P>hello,world</P>");
//-->
</SCRIPT>
これは、<P>hello,world</P> を 10 回書いた のと同じ効果がある。<!--//->は、JavaScriptを知らないブラウザにはコメント として扱われる。関数定義などは、ヘッダ部分 <HEAD></HEAD>に書くという方法もよく使われる。

[実行例]

JavaScript の言語としての特徴

JavaScript によるブラウザの制御

JavaScript の記述は、CGI と似ているところもあるが、JavaScript でないと できないものに、ブラウザの制御がある。たとえば、次の例では、ブラウザの (戻る)ボタンと同じ動きをさせることができる。

<A HREF="javascript:history.back();">戻る</A>
次の例は、<FORM></FORM>からパラメタを受け取るも のである。

<SCRIPT LANGUAGE="JavaScript">
<!--
function go(s,h,p)
{
	location.href = s.value + "://" + h.value + p.value ;
}
//-->
</SCRIPT>

<FORM NAME="form1">
Scheme: <INPUT NAME="scheme" TYPE="text" VALUE="http"><BR>
Host: <INPUT NAME="host"   TYPE="text"><BR>
Path: <INPUT NAME="path"   TYPE="text" VALUE="/"><BR>
<INPUT TYPE="button" VALUE="go" onClick="go(form1.scheme,form1.host,form1.path)">
</FORM>
onClick 属性の値は、クリックした時に評 価される式であり、関数 go() が呼び出されている。引 数は、<FORM> の値である。関数 go() の中では、 .value フィールドから値が読み出されている。 location.href に代入することで そのページを表示させることができる。

[実行例]

この例では、<INPUT type="text"><INPUT type="button">が使われている。その他に、 <FORM>では、<INPUT TYPE="radio"><INPUT TYPE="checkbox"><SELECT>が使える。イベントとしては、 onClick が主に使われる。その他に、 onFocus が使われることもある。

JavaScript では、document.open() で新しく HTML の ドキュメントを生成して、それをブラウザに表示させることもできる。

クロスサイトスクリプティング攻撃

WWWブラウザでは、信頼しているサイトから送られてくるJavaScriptのプログ ラムだけを実行するようにし、攻撃サイトから送られてくるJavaScriptのプロ グラムを実行しないようにしたい。

WWW ブラウザ、攻撃サイト、信頼しているサイト、JavaScriptのプログラム

図? JavaScripの送信元サイトの区別

脆弱性があるサイトでは、攻撃サイトから送られてきた JavaScript のプログ ラムを中継してしまう。

WWW ブラウザ、攻撃サイト、脆弱性のあるサイト、JavaScriptのプログラム

図? JavaScripの送信元サイトの区別

これがクロスサイトスクリプティング攻撃(cross-site scripting atack, XSS atack)である。

CGI のプログラムをつくる時には、クロスサイトスクリプティング攻撃に気を つける。これは、クライアントから送られてる文字列の中に <SCRIPT>のようなタグが含まれていた場合、それをそのままクラ イアントに送り返すと問題がある。 (さらに、 %hh にも気をつける必要がある。)

クライアントから送られてきた文字列は、必ず検査し、安全な状態にして (sanitize)から使う。「<>&"」のようなタグが含まれている場合 には注意する。このような文字列を受け取った場合、不用意に送り返してはい けない。送り返す時には、html_escape() のよう な方法で必ずエスケープする。

html_escape()

以下は cgi-printarg.c に含まれていたhtml_escape() の内容である。引数と結果は、ともに文字列で、 次のような入出力が期待される。
 169:	char *html_escape( char *str )
 170:	{
 171:	    int len ;
 172:	    char c, *tmp, *p, *res ;
 173:	
 174:	        len = strlen( str );
 175:	        tmp = malloc( len * 6 + 1 );
 176:	        if( tmp == 0 )
 177:	            return( 0 );
 178:	        p = tmp ;
 179:	        while( c = *str++ )
 180:	        {
 181:	            switch( c )
 182:	            {
 183:	            case '&': memcpy(p,"&amp;", 5); p += 5 ; break;
 184:	            case '<': memcpy(p,"&lt;",  4); p += 4 ; break;
 185:	            case '>': memcpy(p,"&gt;",  4); p += 4 ; break;
 186:	            case '"': memcpy(p,"&quot;",6); p += 6 ; break;
 187:	            default:  *p = c ;              p++ ;    break;
 188:	            }
 189:	        }
 190:	        *p = 0 ;
 191:	        res = strdup( tmp );
 192:	        free( tmp );
 193:	        return( res );
 194:	}
html_escape() は、次の文字を置換えている。それ以外の文字はそのままコピー している。
文字	変換後の文字列
----------------------
&	&amp;
<	&lt;
>	&gt;
"	&quot;
もし、<SCRIPT> のような文字列がクライアントから送られてきた としても、html_escape() で起き得得ると、クライアントには 「&lt;SCRIPT&gt;」と返すだけで、スクリプトは実行されない。

len * 6 は、最大で元の文字列の6倍の長さになることに備えている。

他のプログラムの実行

CGI では、意図していないプログラムを実行しないようにする。

他のプログラムの実行(C言語)

他のプログラムを実行する時には、execve() のようなシステムコールを使い、 かつ、限られたプログラムしか実行しないようにすると安全性が高くなる。ク ライアントから送られてきた文字列をsystem() や popen() に渡してプログラ ムを実行する時には、必ず検査する。特にシェルが解釈する特殊な文字 「| & ; && || `」などが含 まれていた場合、意図しないプログラムが実行されることがある。

char *user ;
...
snprintf(cmd,BUFSIZE,"finger %s",user );
f = poepn(cmd,"r");
もし、user に ";""|" が含まれていたら、、、 
f = poepn("finger yas; /bin/sh","r");

他のプログラムの実行(Ruby言語)

Ruby には、バッファ・オーバーフローの問題はない。

Ruby の open() には、危険性がある。 C 言語のライブラリ関数 popen() と同じ動きをすることがある。

open("|cmd")
Ruby の注意すべき関数、式、クラス

他のプログラムの実行(Perl言語)

Perl には、バッファ・オーバーフローの問題はない。

perl の open() には、危険性がある。 C 言語のライブラリ関数 popen() と同じ動きをすることがある。

open(FILE, "|cmd")
Perl の注意すべき関数や式

参考

IPA セキュア・プログラミング講座
http://www.ipa.go.jp/security/awareness/vendor/programming/
)

スクリプト言語

コンピュータ言語の種類 スクリプト言語とは、アプリケーション本体ではなく、アプリケーションの細 かな動作を変更したり、アプリケーション本体を変更することなく機能を追加 したりするために使われる言語。

インタプリタとスクリプト

スクリプト言語は、多くの場合、インタプリタで実行される。

プロセスはインタプリタの実行形式(機械語)から作られ、インタプリタのソー ス・プログラムは、そのプロセスが読み込む単なるデータとなる。

Unix には、スクリプトを簡単に実行する仕組みとして #!がある。

インタプリタ/bin/cat

標準入力を標準出力に出力する cat コマンド 「#!」の働きを調べる。 cat は「プログラムを表示する」インタプリタである。 まず、「catインタプリタ」用のプログラムを作成する。
$ cat > run-cat [←]
#!/bin/cat[←]
hello[←]
^D
$ chmod +x run-cat [←]
$ ls -l run-cat [←]
-rwxr-xr-x  1 yas            20 Sep 13 04:11 run-cat
$ []
「catインタプリタ」用のプログラムを実行する。
$ ./run-cat [←]
#!/bin/cat
hello
$ []
これは次のように実行したものと同じになる。
$ /bin/cat ./run-cat [←]
#!/bin/cat
hello
$ []

一般のインタプリタ

「#!」の形式は次のようになる。

インタプリタの実行形式の絶対パス名: /dir/interpreter

そのソース・プログラムのファイル名: filename

filename の内容:

#!/dir/interpreter opt-1 opt-2 opt-3
<以下、プログラム>
このファイルに実行可能属性を付ける(chmod +x)と、 ファイル名を入力して実行することができる。
$ chmod +x ./filename [←]
$ ./filename arg-a arg-b arg-c [←]
<実行結果>
これは、次のようにインタプリタを起動したものと同じ結果になる。
$ /dir/interpreter opt-1 opt-2 opt-3 ./filename arg-a arg-b arg-c [←]
./filename ファイルに書いた引数は、シェルから実行する時場合には、 インタプリタのプロセスへの引数として渡される。

#!の解釈

#!」行は、Unix のカーネルが解釈する。 シェルが、ファイルの先頭を読み、指定されたインタプリタを起動するのでは ない。「#!」行では、シェル変数、環境変数、エイリアスはつ かえない。

スクリプト言語のプログラムでは 「#」から始まる行がコメントであると都合がよい。

awksed のように、プログラムが含まれたファイルを指定 する時に -f (program file) オプションが必要なものは、次の ように、この行に -f を付ける。

#!/usr/bin/awk -f
{ print }

シェル

シェルはインタプリタであり、シェル・スクリプトはシェル・インタプリタの プログラムである。

シェル・スクリプトの先頭の #!/bin/sh#!/bin/csh は、 そのインタプリタを起動するという意味である。

1年生の時に行ったシェルスクリプトの作成を復習しなさい。3年次編入生で、 シェルスクリプトが初めてという人にも勧める。 http://www.coins.tsukuba.ac.jp/~yas/coins/literacy-2011/2011-06-17/index.html コンピュータリテラシ/シェル・スクリプト

CGIプログラミングでのスクリプト言語の利用

CGI のプログラムを作成する場合、トップレベル(クライアントにデータを送 る部分)は、C言語で記述するよりも、スクリプト言語で記述することが多い。 スクリプト言語は、インタプリタで動作するので、プログラミングが容易である。

普通のプログラミング言語は、アプリケーション・プログラム本体を記述する 時に使われる。 これに対して、 スクリプト言語 は、アプリケーション本体で はなく、アプリケーションの細かな動作を変更したり、アプリケーション本体 を変更することなく機能を追加したりするために使われる言語である。 普通のプログラミング言語は、アプリケーション・プログラマにより使われ、 コンパイラで機械語に変換されるので、実行時には機械語しか残っていない。 これに対して、スクリプト言語は、アプリケーションのユーザや、システム管 理者などによって使われ、プログラムは、アプリケーションに組み込まれた インタプリタで解釈実行される。 スクリプト言語を使うと、単に変数を設定することに比べて、 高度な機能拡張が機能になる。

スクリプト言語の例:

スクリプト言語 csh で記述した CGI のプログラムの例:
$ cat cgi-hello-sh.cgi [←]
#!/bin/sh

cat <<EOF
Content-Type: text/html

<HTML><HEAD></HEAD><BODY>
hello.
</BODY></HTML>
EOF
$ ./cgi-hello-sh.cgi [←]
Content-Type: text/html

<HTML><HEAD></HEAD><BODY>
hello.
</BODY></HTML>
$ []
http://www.coins.tsukuba.ac.jp/~syspro/2012/2012-06-20/cgi-hello-sh.cgi

RubyによるCGI

cgi-printarg.c を Ruby で書直す。
   1:	#!/usr/bin/ruby
   2:	# cgi-printarg-ruby.cgi -- CGI プログラムに対する引数を表示するプログラム
   3:	# ~yas/syspro/www/cgi-printarg-ruby.cgi
   4:	# Created on 2005/06/27 01:52:36
   5:	
   6:	require "cgi"
   7:	
   8:	def main()
   9:	        $SAFE = 1
  10:	        @cgi = CGI.new()
  11:	        print_header()
  12:	        print_content()
  13:	        exit( 0 )
  14:	end
  15:	
  16:	def print_header()
  17:	        printf("Content-Type: text/html\n")
  18:	        printf("\n")
  19:	end
  20:	
  21:	def print_content()
  22:	        printf("<HTML><HEAD></HEAD><BODY><PRE>\n")
  23:	        printf("request_method: %s\n",e(@cgi.request_method))
  24:	        printf("script_name: %s\n",e(@cgi.script_name))
  25:	        printf("query_string: %s\n",e(@cgi.query_string))
  26:	        printf("content_length: %d\n",@cgi.content_length)
  27:	        qh = @cgi.keys
  28:	        i = 0
  29:	        qh.each { |name|
  30:	            val = @cgi[name]
  31:	            printf("qv[%d]: %s=%s \n",i,e(name),e(val) )
  32:	            i = i + 1
  33:	        }
  34:	        printf("</PRE></BODY></HTML>\n")
  35:	end
  36:	
  37:	def e( str )
  38:	        return( str == nil ? "" : CGI::escapeHTML(str) )
  39:	end
  40:	
  41:	main()

Ruby では、require で、必要なライブラリを読み込む。 def から end までがメソッドの定義(関数の定義)である。

main() では、CGI.new() により、CGI クラスのインスタンスを生成している。 その結果を @cgi という変数(インスタンス変数、mainの外でも使える)に保存 している。

このプログラムでは、main() という名前のメソッドを定義しているが、、 main() というメソッドから実行が開始させるわけではない。メソッド定義で はないものは、即座に実行される。このプログラムは最後に main() を呼び出 す文がある。これを忘れると何も実行されない。

$SAFE は、グローバル変数である。Ruby では、ファイルからの入力や環境変 数は汚染されたものとして扱われる。$SAFE を 1 にすると、汚染された文字 列でファイルを開くとエラーになる。標準では、0 。安全を確認したら、 obj.untaint() メソッドで汚染を解除する。明示的に obj.taint() で汚染さ せることもできる。

print_header()では、HTTP のヘッダのうち、Content-Type: 行だけを 出力している。

print_content() では、本文を出力している。

関数(メソッド) e() では、CGI ライブラリ (CGIクラス)の CGI::escapeHTML() を呼び出して、安全なものにして表示する。 たとえば、「<」は、「&lt;」と変換している。こ れで、<SCRIPT> のような危険なスクリプトが送り込まれたとし ても「&lt;SCRIPT&gt;」と表示と表示されるだけで、スクリプト は実行されない。

@cgi に保存されたCGI クラスのインスタンスの request_method() メソッド を呼び出すと、"GET" か "POST" が返される。環境変数は、ハッシュ表 ENV に対して ENV['REQUEST_METHOD'] のようにしてもアクセスできるが、環境変 数を CGI クラスで変更してしまうこともあるようである。

@cgi.keys により、パラメタの一覧が配列の形で得られる。配列の各要素につ いて(qh.each)、パラメタ名を得て表示している。この例では、どんなパラメ タでも表示しているので、このようなループになっているが、通常の CGI プ ログラムでは、@cgi['パラメタ名'] のようにして、パラメタの値を文字列と して取り出すだけでよい。

	arg1_s = @cgi['arg1']
	arg2_s = @cgi['arg2']

表示例:

CGI の GET メソッドを使う例

姓: 名:


その他
電子メール:

CGI の POST メソッドを使う例

姓: 名:


その他
電子メール:

実行例:

<HTML><HEAD></HEAD><BODY><PRE>
request_method: GET
script_name: /~syspro/2012/2012-06-20/cgi-printarg-ruby.cgi
query_string: lastname=name1&amp;firstname=name2&amp;sex=Male&amp;email=who%40dom
content_length: 0
qv[0]: lastname=name1 
qv[1]: sex=Male 
qv[2]: firstname=name2 
qv[3]: email=who@dom 
</PRE></BODY></HTML>
<HTML><HEAD></HEAD><BODY><PRE>
request_method: POST
script_name: /~syspro/2012/2012-06-20/cgi-printarg-ruby.cgi
query_string: 
content_length: 55
qv[0]: lastname=name1 
qv[1]: sex=Male 
qv[2]: firstname=name2 
qv[3]: email=who@dom 
</PRE></BODY></HTML>

コマンドラインからの実行

Ruby 言語の CGI クラスを使ったプログラムは、 環境変数を設定したデバッグ もできる。
$ export REQUEST_METHOD=GET [←]
$ export QUERY_STRING='lastname=name1&firstname=name2&sex=Male&email=who@dom' [←]
$ ./cgi-printarg-ruby.cgi  [←]
Content-Type: text/html

<HTML><HEAD></HEAD><BODY><PRE>
request_method: GET
script_name: 
query_string: lastname=name1&amp;firstname=name2&amp;sex=Male&amp;email=who@dom
content_length: 0
qv[0]: lastname=name1 
qv[1]: sex=Male 
qv[2]: firstname=name2 
qv[3]: email=who@dom 
</PRE></BODY></HTML>
$ []
その他に標準入力からパラメタを与えてでデバッグすることもできる。
$ unset REQUEST_METHOD  [←]
$ unset QUERY_STRING  [←]
$ echo 'lastname=name1&firstname=name2&sex=Male&email=who@dom' | ./cgi-printarg-ruby.cgi  [←]
Content-Type: text/html

<HTML><HEAD></HEAD><BODY><PRE>
request_method: 
script_name: 
query_string: 
content_length: 0
qv[0]: lastname=name1 
qv[1]: sex=Male 
qv[2]: firstname=name2 
qv[3]: email=who@dom 
</PRE></BODY></HTML>
$ []

WWWプログラミング、その他の話題

以下の内容は、読み飛ばしてよい。

SSI (Server Side Include)

内容は、読み飛ばしてよい。

Javaアプレット

内容は、読み飛ばしてよい。

練習問題と課題

練習問題(1001) クロスサイトスクリプティング攻撃に対する脆弱性の除去

今まで「自分で」作成した CGI のプログラムの中で、 クロスサイトスクリプティング攻撃に対する脆弱性 あるものを探しなさい。そして、それを修正しなさい。

レポートには、最初に脆弱生がある CGI プログラムを作成した年月日、 今回修正した場所、および、その修正箇所の説明をつけなさい。

ただし、脆弱性があるプログラムとしては、この科目で作成したプログラムは 使えないものとする。

練習問題(1002) 他のプログラムの実行を許してしまうような脆弱性の除去

今まで「自分で」作成した CGI のプログラムの中で、 他のプログラムの実行を許してしまうような脆弱性が あるものを探しなさい。そして、それを修正しなさい。

レポートには、最初に脆弱生がある CGI プログラムを作成した年月日、 今回修正した場所、および、その修正箇所の説明をつけなさい。

ただし、脆弱性があるプログラムとしては、この科目で作成したプログラムは 使えないものとする。

練習問題(1003) CGIによる足算(Ruby)

練習問題(904) CGIによる足算を Ruby で書直しなさい。

ヒント:strtol() のように、文字列から整数値を得るには、Ruby では、 Integer() を使って次のように行う方法がある。

     s = "100"
     i = Integer(s)
なお、文字列として不正なものを与えると Integer() は例外を発生する。 この課題では、正しい文字列が与えられると仮定して良いものとする。 例外が発生した時にはそれを捕捉することなく終了して良いことにする。

この課題では、 練習問題(904) CGIによる足算 と同様に、作成したプログラムをシェルから実行して動作を確認しなさい。

練習問題(1004) CGIによる加減乗除(Ruby)

練習問題(905) CGIによる加減乗除を Ruby で書直しなさい。

練習問題(1005) CGIによるカレンダの表示(Ruby)

練習問題(906) CGIによるカレンダの表示を Ruby で書直しなさい。

練習問題(1006) CGIによるカレンダの表示(引数付き,Ruby)

練習問題(907) CGIによるカレンダの表示(引数付き)を Ruby で書直しなさい。

練習問題(1007) ハッシュ表(Ruby)

メモリ中のハッシュ表を使うクラス Hash について調べなさい。

練習問題(1008) 永続的ハッシュ表(Ruby)

永続的なハッシュ表を扱うクラス DBM について調べなさい。

練習問題(1009) 文字列を表示するアクセス・カウンタ(永続的ハッシュ表の利用,Ruby)

練習問題(913) 文字列を表示するアクセス・カウンタ(永続的ハッシュ表の利用)を Ruby で書直しなさい。

練習問題(1010) CGIによる例外処理(Ruby)

CGIによる足算(Ruby) で、クラ イアントから整数以外の値が送られて来たとしても、対応しなさい。この時、Ruby が持つ例外処理の機能(begin ... rescue ... ensure ... end) を利用しなさい。

練習問題(1011) 配列の足算(Ruby)

CGI では、同じ変数名で複数のパラメタを渡すことができる。たとえば、 「arg=10&arg=20&arg=30」のようにパラメタを送信することが できる。この機能を用いて、配列の加算を行いなさい。

ヒント: CGI オブジェクトの params() メソッドを使い、配列の形で得る。

練習問題(1012) ファイルの受信(Ruby)

Ruby ファイルを受信して、その内容を処理(例えば加算、保存)しなさい。 ファイルを受信するには、HTML の <form> では、 <input type="file" name="file1"> のようにする。

ヒント: CGI オブジェクトでは、ファイルの場合、StringIO クラスのオブジェ クトになる。IOクラスのオブジェクトのように、read(), each(), gets(), readline() といったメソッドが使える。


Last updated: 2012/06/18 12:26:23
Yasushi Shinjo / <yas@cs.tsukuba.ac.jp>