筑波大学 システム情報系 情報工学域
新城 靖
<yas@cs.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2017/2017-06-21
/index.html
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2017/
http://www.coins.tsukuba.ac.jp/~yas/
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>に書くという方法もよく使われる。
for、while 、
if, else, continue,
break, switch case がある。
try,
catch,
throwによる例外を扱う機能がある。
function で、関数定義ができる。
var で宣言すれば、ローカル変数になる。
配列は、new Array(長さ) で確保する。
class というキーワードは、もともとはない(EcmaScrypt 6 2015で導入)。
function に new を付けて呼べば、オブジェ
クトになる。オブジェクトのメソッドでは、this を使っ
て要素を参照できる。
自分で持っていないフィールドは、プロトタイプを探しにいく。
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のプロ グラムを実行しないようにしたい。
図? JavaScripの送信元サイトの区別
脆弱性があるサイトでは、攻撃サイトから送られてきた JavaScript のプログ ラムを中継してしまう。
図? JavaScripの送信元サイトの区別
CGI のプログラムをつくる時には、クロスサイトスクリプティング攻撃に気を
つける。これは、クライアントから送られてる文字列の中に
<SCRIPT>のようなタグが含まれていた場合、それをそのままクラ
イアントに送り返すと問題がある。
(さらに、
%hh
にも気をつける必要がある。)
クライアントから送られてきた文字列は、必ず検査し、安全な状態にして (sanitize)から使う。「<>&"」のようなタグが含まれている場合 には注意する。このような文字列を受け取った場合、不用意に送り返してはい けない。送り返す時には、html_escape() のよう な方法で必ずエスケープする。
abc<>&012
abc<>&012
182: char *
183: html_escape( char *str )
184: {
185: int len ;
186: char c, *tmp, *p, *res ;
187:
188: len = strlen( str );
189: tmp = malloc( len * 6 + 1 );
190: if( tmp == 0 )
191: return( 0 );
192: p = tmp ;
193: while( (c = *str++) )
194: {
195: switch( c )
196: {
197: case '&': memcpy(p,"&", 5); p += 5 ; break;
198: case '<': memcpy(p,"<", 4); p += 4 ; break;
199: case '>': memcpy(p,">", 4); p += 4 ; break;
200: case '"': memcpy(p,""",6); p += 6 ; break;
201: default: *p = c ; p++ ; break;
202: }
203: }
204: *p = 0 ;
205: res = strdup( tmp );
206: free( tmp );
207: return( res );
208: }
html_escape() は、次の文字を置換えている。それ以外の文字はそのままコピー
している。
文字 変換後の文字列 ---------------------- & & < < > > " "もし、<SCRIPT> のような文字列がクライアントから送られてきた としても、html_escape() で起き得得ると、クライアントには 「<SCRIPT>」と返すだけで、スクリプトは実行されない。
len * 6 は、最大で元の文字列の6倍の長さになることに備えている。 メモリの断片化が起きやすいので、良いコードとは言えない。
プロセスはインタプリタの実行形式(機械語)から作られ、インタプリタのソー ス・プログラムは、そのプロセスが読み込む単なるデータとなる。
Unix には、スクリプトを簡単に実行する仕組みとして #!がある。
$ 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
<実行結果>
これは、次のようにインタプリタを起動したものと同じ結果になる。
Linux, FreeBSD の場合:
$ /dir/interpreter 'opt-1 opt-2 opt-3' ./filename arg-a arg-b arg-c
MacOSX の場合:
$ /dir/interpreter opt-1 opt-2 opt-3 ./filename arg-a arg-b arg-c
Solaris の場合:
$ /dir/interpreter opt-1 ./filename arg-a arg-b arg-c
./filename ファイルに書いた引数は、シェルから実行する時場合には、
インタプリタのプロセスへの引数として渡される。
#!」行は、Unix のカーネルが解釈する。
シェルが、ファイルの先頭を読み、指定されたインタプリタを起動するのでは
ない。「#!」行では、シェル変数、環境変数、エイリアスはつ
かえない。
スクリプト言語のプログラムでは
「#」から始まる行がコメントであると都合がよい。
awkや sed のように、プログラムが含まれたファイルを指定
する時に -f (program file) オプションが必要なものは、次の
ように、この行に -f を付ける。
#!/usr/bin/awk -f
{ print }
シェル・スクリプトの先頭の
#!/bin/sh や
#!/bin/csh は、
そのインタプリタを起動するという意味である。
1年生の時に行ったシェルスクリプトの作成を復習しなさい。3年次編入生で、 シェルスクリプトが初めてという人にも勧める。
http://www.coins.tsukuba.ac.jp/~yas/coins/literacy-2017/2017-06-16/index.html,コンピュータリテラシ/シェル・スクリプト
普通のプログラミング言語は、アプリケーション・プログラム本体を記述する 時に使われる。 これに対して、 スクリプト言語 は、アプリケーション本体で はなく、アプリケーションの細かな動作を変更したり、アプリケーション本体 を変更することなく機能を追加したりするために使われる言語である。
普通のプログラミング言語は、アプリケーション・プログラマにより使われ、 コンパイラで機械語に変換されるので、実行時には機械語しか残っていない。 これに対して、スクリプト言語は、アプリケーションのユーザや、システム管 理者などによって使われ、プログラムは、アプリケーションに組み込まれた インタプリタで解釈実行される。
スクリプト言語を使うと、単に変数を設定することに比べて、 高度な機能拡張が機能になる。
スクリプト言語の例:
$ 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/2017/2017-06-21/cgi-hello-sh.cgi
1: #!/usr/bin/ruby
2: # -*- coding: utf-8 -*-
3: # cgi-printarg-ruby.cgi -- CGI プログラムに対する引数を表示するプログラム
4: # ~yas/syspro/www/cgi-printarg-ruby.cgi
5: # Created on 2005/06/27 01:52:36
6:
7: require "cgi"
8:
9: def main()
10: $SAFE = 1
11: @cgi = CGI.new()
12: print_header()
13: print_content()
14: exit( 0 )
15: end
16:
17: def print_header()
18: printf("Content-Type: text/html\n")
19: printf("\n")
20: end
21:
22: def print_content()
23: printf("<HTML><HEAD></HEAD><BODY><PRE>\n")
24: printf("request_method: %s\n",e(@cgi.request_method))
25: printf("script_name: %s\n",e(@cgi.script_name))
26: printf("query_string: %s\n",e(@cgi.query_string))
27: printf("content_length: %d\n",@cgi.content_length ? @cgi.content_length : 0 )
28: qh = @cgi.keys
29: i = 0
30: qh.each { |name|
31: val = @cgi[name]
32: printf("qv[%d]: %s=%s \n",i,e(name),e(val) )
33: i = i + 1
34: }
35: printf("</PRE></BODY></HTML>\n")
36: end
37:
38: def e( str )
39: return( str == nil ? "(null)" : CGI::escapeHTML(str) )
40: end
41:
42: 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() を呼び出して、安全なものにして表示する。 たとえば、「<」は、「<」と変換している。こ れで、<SCRIPT> のような危険なスクリプトが送り込まれたとし ても「<SCRIPT>」と表示と表示されるだけで、スクリプト は実行されない。
@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['パラメタ名']は、もし 'パラメタ名'のパラメタが設定されていなければ、 空文字列
"" を返す。
表示例:
実行例:
request_method: GET script_name: /~yas/coins/syspro-2017/2017-06-21/cgi-printarg-ruby.cgi query_string: lastname=name1&firstname=name2&lang=C&email=who%40dom content_length: 0 qv[0]: lastname=name1 qv[1]: firstname=name2 qv[2]: lang=C qv[3]: email=who@dom
request_method: POST script_name: /~yas/coins/syspro-2017/2017-06-21/cgi-printarg-ruby.cgi query_string: content_length: 53 qv[0]: lastname=name1 qv[1]: firstname=name2 qv[2]: lang=C qv[3]: email=who@dom
$ export REQUEST_METHOD=GET
$ export QUERY_STRING='lastname=name1&firstname=name2&lang=C&email=who@dom'
$ ./cgi-printarg-ruby.cgi
Content-Type: text/html
<HTML><HEAD></HEAD><BODY><PRE>
request_method: GET
script_name: (null)
query_string: lastname=name1&firstname=name2&lang=C&email=who@dom
content_length: 0
qv[0]: lastname=name1
qv[1]: firstname=name2
qv[2]: lang=C
qv[3]: email=who@dom
</PRE></BODY></HTML>
$
その他に標準入力からパラメタを与えてでデバッグすることもできる。
$ unset REQUEST_METHOD
$ unset QUERY_STRING
$ echo 'lastname=name1&firstname=name2&lang=C&email=who@dom' | ./cgi-printarg-ruby.cgi
Content-Type: text/html
<HTML><HEAD></HEAD><BODY><PRE>
request_method: (null)
script_name: (null)
query_string: (null)
content_length: 0
qv[0]: lastname=name1
qv[1]: firstname=name2
qv[2]: lang=C
qv[3]: email=who@dom
</PRE></BODY></HTML>
$
他のプログラムを実行する時には、execve() のようなシステムコールを使い、
かつ、限られたプログラムしか実行しないようにすると安全性が高くなる。ク
ライアントから送られてきた文字列をsystem() や popen() に渡してプログラ
ムを実行する時には、必ず検査する。特にシェルが解釈する特殊な文字
「| & ; && || `」などが含
まれていた場合、意図しないプログラムが実行されることがある。
char *user ;
File *f;
char line[BUFSIZ];
...
snprintf(cmd,BUFSIZE,"finger %s",user );
f = poepn(cmd,"r");
fgets(line,sizeof(line),f);
もし、user に ";" や "|" が含まれていたら、、、
f = poepn("finger yas; cat /etc/passwd","r");
Ruby の open() には、危険性がある。 C 言語のライブラリ関数 popen() と同じ動きをすることがある。
open("|cmd")
Ruby の注意すべき関数、式、クラス
perl の open() には、危険性がある。 C 言語のライブラリ関数 popen() と同じ動きをすることがある。
open(FILE, "|cmd")
Perl の注意すべき関数や式
IPA セキュア・プログラミング講座
http://www.ipa.go.jp/security/awareness/vendor/programming/
)
レポートには、最初に脆弱生がある CGI プログラムを作成した年月日、 今回修正した場所、および、その修正箇所の説明をつけなさい。
ただし、脆弱性があるプログラムとしては、この科目で作成したプログラムは 使えないものとする。
レポートには、最初に脆弱生がある CGI プログラムを作成した年月日、 今回修正した場所、および、その修正箇所の説明をつけなさい。
ただし、脆弱性があるプログラムとしては、この科目で作成したプログラムは 使えないものとする。
次の CGI プログラムを コマンドラインから実行 しなさい。
$ cp ~yas/syspro/www/cgi-arg1arg2-ruby.cgi .
$ ./cgi-arg1arg2-ruby.cgi
(offline mode: enter name=value pairs on standard input)
^D
Content-Type: text/html
<HTML><HEAD></HEAD><BODY><PRE>
arg1: []
arg2: []
</PRE></BODY></HTML>
$ export REQUEST_METHOD=GET
$ export QUERY_STRING='arg1=10&arg2=20'
$ ./cgi-arg1arg2-ruby.cgi
Content-Type: text/html
<HTML><HEAD></HEAD><BODY><PRE>
arg1: [10]
arg2: [20]
</PRE></BODY></HTML>
$
レポートには、次の結果を含めなさい。
余裕があれば、REQUEST_METHOD=POST についても、同様に実行してみなさい。
余裕があれば、フォームを定義し、何かデータを送ってみなさい。 coins で CGI を実行する場合、CGI の実行形式を Web サーバ (http://www.coins.tsukuba.ac.jp) に ssh でログインした上で作成すること。
http://www.coins.tsukuba.ac.jp/~syspro/2017/2017-06-21/cgi-arg1arg2-ruby.cgi
たとえば、次のような記述を含む HTML ファイルを作成し、 ~/public_html/htdocs/ 以下に置く。
<FORM ACTION="http://www.coins.tsukuba.ac.jp/~syspro/2017/2017-06-21/cgi-arg1arg2-ruby.cgi" method="get">
arg1: <INPUT type="text" name="arg1">
arg2: <INPUT type="text" name="arg2">
<INPUT type="submit">
<INPUT type="reset">
</FORM>
CGIによる足算を Ruby で書き直しなさい。ただし、次のことを実装しなくてもよい。
ヒント:
C言語の関数getparam()の
代わりに Ruby 言語の @cgi["arg1"] が使える。
@cgi["arg1"] は、もし "arg1" のパラメタが設定されていな
ければ、空文字列 "" を返す。
ヒント:C 言語の strtol() のように、文字列から整数値を得るには、Ruby で は、Integer() を使って次のように行う方法がある。
s = "100"
i = Integer(s)
なお、文字列として不正なものを与えると Integer() は例外を発生する。
この課題では、正しい文字列が与えられると仮定して良いものとする。
例外が発生した時にはそれを捕捉することなく終了して良いことにする。
この課題では、 CGIによる足算 と同様に、作成したプログラムをシェルから実行して動作を確認しなさい。
CGIによる加減乗除を Ruby で書き直しなさい。
CGIによるカレンダの表示(引数付き)を Ruby で書き直しなさい。
クライアントから不正なパラメタから与えられた場合には、 練習問題(1006) で示したように、例外処理で捕捉し、 エラー・メッセージをクライアントに返しなさい。 レポートには、不正なパラメタが与えられた場合の実行結果を含めなさい。
この課題では、フォームを含む HTML ファイルを作成し、CGIとして実行し、その結果をレポート含めなさい。
ヒント: CGI オブジェクトの params() メソッドを使い、配列の形で得る。
ヒント: CGI オブジェクトでは、ファイルの場合、StringIO クラスのオブジェ クトになる。IOクラスのオブジェクトのように、read(), each(), gets(), readline() といったメソッドが使える。
クライアントから不正なパラメタから与えられた場合には、 練習問題(1006) で示したように、例外処理で捕捉し、 エラー・メッセージをクライアントに返しなさい。
この課題では、フォームを含む HTML ファイルを作成し、CGIとして実行し、その結果をレポート含めなさい。
この課題では、フォームを含む HTML ファイルを作成し、CGIとして実行し、その結果をレポート含めなさい。