システムプログラム(第10回): Web CGI プログラミング(2)

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

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

捕捉

Coins での Web ページの作成と公開

Coins で Web ページを作り、全世界に公開する方法の説明は、次のページにある。 情報科学類の学生は、自分で勉強して実力をつけることが望まれる。 「教わったことだけ(一時的に)理解して、試験・レポートを受けて、単位を取る(後は忘れる)」 では不十分。

ユーザ apache

Web サーバは、「apache」という名前のユーザの UID で動作している。
$ id yas [←]
uid=1013(yas) gid=510(prof) groups=510(prof),1180(c-gakusei),1150(tebiki),
1065(c-spec),1020(c-admin),20(games)
$ finger yas [←]
Login: yas				Name: Yasushi SHINJO
Directory: /home/prof/yas           	Shell: /bin/bash
On since 日  6月 20 10:07 (JST) on pts/2 from xxx
No mail.
No Plan.
$ id apache [←]
uid=48(apache) gid=48(apache) groups=48(apache)
$ finger apache [←]
Login: apache				Name: Apache
Directory: /usr/share/httpd         	Shell: /sbin/nologin
Last login 金  5月 21 00:00 (JST) on pts/3
No mail.
No Plan.
$ []
HTML ファイルや CGI のファイルは、この UID のプロセスからアクセスできる 状態でなければならない。アクセスできない時には、Web サーバは、HTTP のヘッ ダの status line では 403 Forbidden 、本文では HTML で次のような ものを返す。
Forbidden You don't have permission to access /~ユーザ名/・・・ on this server.
この状態を修復するには、Web サーバが HTML や CGI のファイルがアクセスよ うにする。すなわち、自分でもグループでもなく、その他のユーザが HTML ファ イルを read できるか、CGI のプログラムを exec できるかを確認する。
$ ls -l cgi-hello.cgi [←]
-rwxr-xr-x 1 yas prof 8488  6月 14 12:04 cgi-hello.cgi
$ []
システムコールを使ったファイルのコピー(filecopy-syscall.c) で出てきた次の部分に注目。
    18          fpd = open("dst", O_WRONLY | O_CREAT | O_TRUNC, 0644);
こうして作成されたファイルのモードは、最大 644 (rw-r--r--) になる。 umask が 0022 なら、これも引かれる。 umask が 0022 なら、次のようにしても 644 のファイルが作成される。 シェルの標準出力のリダイレクションでは、0666 が使われている。
                fpd = open("dst", O_WRONLY | O_CREAT | O_TRUNC, 0666);

アクセスできるかどうかは、末端のファイルだけでなく、ルートディレクから このファイルに至るまでのディレクトリのモード(xビット)も関係する。

/home/prof/yas/public_html/htdocs/syspro-cgi-examples/cgi-hello.cgi
ホーム・ディレクトリのモードが次のように 700 (rwx------) になっていると、Web ページを公開することはできない。Web サーバのプロセ スは、このホームディレクトリ以下のファイルを一切アクセスできない。
$ ls -ld ~ [←]
drwx------ 36 s1954321 ugrad 4096  2月 12 23:23 /home/ugrad/19/s1954321
$ []
少なくとも、次のように 711 (rwx-----x)になっていなければ ならない。
$ ls -ld ~ [←]
drwx-----x 36 s1954321 ugrad 4096  2月 12 23:23 /home/ugrad/19/s1954321
$ []
ホームディレクトリのモードを変更すると、Web ページは公開できるようにな るが、その副作用として、他人がレポートのファイル等をアクセス可能になる ことがある。変更する前に、この点をきちんと理解すること。

連絡

JavaScript

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

JavaScript は、文法が少し Java 言語に似ているが、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 の ドキュメントを生成して、それをブラウザに表示させることもできる。

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

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

Web ブラウザは、 同一オリジンポリシー(Same Origin Policy) を実装している。Web サイト(origin) ごとに cookie や localStorage が隔離 される。ある Web サイトから届いた JavaScript のプログラムは、他のWeb サ イトのデータをアクセスする時に規制を受ける。

Web ブラウザ、攻撃サイト、信頼しているサイト、JavaScriptのプログラム
図? JavaScripの送信元サイトの区別

脆弱性があるサイトでは、Web ブラウザを経由して 攻撃サイトから送られてき た JavaScript のプログラムを中継してしまう。 悪意のある JavaScript のプログラムが、 攻撃サイト−>Webブラウザ−脆弱性のあるサイト−>Webブラウザと 中継され、実行される。 悪意のある JavaScript のプログラムが、脆弱性のある Web サイトに関連した データを自由にアクセスできる。

Web ブラウザ、攻撃サイト、脆弱性のあるサイト、JavaScriptのプログラム
図? JavaScripの送信元サイトの区別

これがクロスサイトスクリプティング攻撃(cross-site scripting atack, XSS atack)である。 CGI のプログラムをつくる時には、クロスサイトスクリプティング攻撃に気を つけなければならない。

何が問題だったのか

クライアントから送られてる文字列の中に <SCRIPT>のようなタグが含まれていた場合、それをそのままクラ イアントに送り返すと問題がある。 (さらに、 %hh にも気をつける必要がある。)

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

むやみにエスケープすれば安全になるというものでもない。 エスケープするのは、クライアントから送られてきたデータに由来するものだ けで良い。サーバ側で生成したもの、たとえば、10+20 を計算して得られた整 数 30 を"%d" で表示したようなものは、エスケープする必要はない。

html_escape()

html_escape() は cgi-printarg.c に含まれていた関数である。引数と結果は、ともに文字列で、 次のような入出力が期待される。 以下はそのコードである。
 184:	char *
 185:	html_escape( char *str )
 186:	{
 187:	        int len ;
 188:	        char c, *tmp, *p, *res ;
 189:	
 190:	        len = strlen( str );
 191:	        tmp = malloc( len * 6 + 1 );
 192:	        if( tmp == 0 )
 193:	                return( 0 );
 194:	        p = tmp ;
 195:	        while( (c = *str++) )
 196:	        {
 197:	                switch( c )
 198:	                {
 199:	                case '&': memcpy(p,"&amp;", 5); p += 5 ; break;
 200:	                case '<': memcpy(p,"&lt;",  4); p += 4 ; break;
 201:	                case '>': memcpy(p,"&gt;",  4); p += 4 ; break;
 202:	                case '"': memcpy(p,"&quot;",6); p += 6 ; break;
 203:	                default:  *p = c ;              p++ ;    break;
 204:	                }
 205:	        }
 206:	        *p = 0 ;
 207:	        res = strdup( tmp );
 208:	        free( tmp );
 209:	        return( res );
 210:	}
 211:	
html_escape() は、次の文字を置換えている。それ以外の文字はそのままコピー している。
文字	変換後の文字列
----------------------
&	&amp;
<	&lt;
>	&gt;
"	&quot;
もし、<SCRIPT> のような文字列がクライアントから送られてきた としても、html_escape() で置換ると、クライアントには 「&lt;SCRIPT&gt;」と返すだけで、スクリプトは実行されない。

len * 6 は、最大で元の文字列の6倍の長さになることに備えている。 メモリの断片化が起きやすいので、良いコードとは言えない。

表示例

CGI の GET メソッドを使う例

姓: 名:
C言語
Java言語
その他
電子メール:

CGI の POST メソッドを使う例

姓: 名:
C言語
Java言語
その他
電子メール:

スクリプト言語

コンピュータ言語の種類

スクリプト言語の位置づけ

普通のプログラミング言語は、アプリケーション・プログラム本体を記述する 時に使われる。 これに対して、 スクリプト言語 は、アプリケーション本体で はなく、アプリケーションの細かな動作を変更したり、アプリケーション本体 を変更することなく機能を追加したりするために使われる言語である。

普通のプログラミング言語は、アプリケーション・プログラマにより使われ、 コンパイラで機械語に変換されるので、実行時には機械語しか残っていない。 これに対して、スクリプト言語は、アプリケーションのユーザや、システム管 理者などによって使われ、プログラムは、アプリケーションに組み込まれた インタプリタで解釈実行される。

スクリプト言語を使うと、単に変数を設定することに比べて、 高度な機能拡張が機能になる。

シェル・スクリプト を使うと、OS本体(C言語で記述)の機能を拡張できる。

インタプリタのプロセスとスクリプト

スクリプトの実行とは

インタプリタ/bin/cat

Unix 系の OS には、インタプリタによるスクリプトを簡単に実行する仕組みとして #!がある。 cat コマンドを使って「#!」の働きを調べる。

cat コマンドは、テキストファイルを画面に表示する時に使うコマンドである (補講)。 引数で与えられた名前のファイルを open し、read し、それを標準出力に write する。 -n オプションをつけると、行番号を表示する。 ファイル名の引数が与えられないと、標準入力から read したものを標準出力に write する。

$ cat /etc/shells [←]
/bin/sh
/bin/bash
/sbin/nologin
/usr/bin/sh
/usr/bin/bash
/usr/sbin/nologin
/bin/tcsh
/bin/csh
/bin/zsh
/usr/bin/fish
/bin/ksh
/bin/rksh
/usr/bin/tmux
$ cat -n /etc/shells [←]
     1	/bin/sh
     2	/bin/bash
     3	/sbin/nologin
     4	/usr/bin/sh
     5	/usr/bin/bash
     6	/usr/sbin/nologin
     7	/bin/tcsh
     8	/bin/csh
     9	/bin/zsh
    10	/usr/bin/fish
    11	/bin/ksh
    12	/bin/rksh
    13	/usr/bin/tmux
$ cat  [←]
abc[←]
abc
012[←]
012
^D
$ cat -n [←]
abc[←]
     1	abc
012[←]
     2	012
^D
$ []

cat コマンドは「ファイル(プログラム)を表示する」インタプリタである。 まず、「catインタプリタ」用の2行のプログラムを作成する。

$ cat > run-cat [←]
#!/bin/cat[←]
hello[←]
^D
$ cat run-cat [←]
#!/bin/cat
hello
$ ls -l run-cat [←]
-rw-r--r-- 1 yas prof 17  7月  7 17:33 run-cat
$ ./run-cat [←]
bash: ./run-cat: 許可がありません
$ chmod +x run-cat [←]
$ ls -l run-cat [←]
-rwxr-xr-x 1 yas prof 17  7月  7 17:33 run-cat
$ []
「catインタプリタ」用のプログラムを実行する。
$ ./run-cat [←]
#!/bin/cat
hello
$ []
これは次のように実行したものと同じになる。
$ /bin/cat ./run-cat [←]
#!/bin/cat
hello
$ []
cat コマンドに -n オプション(行番号をつける)を与えてみる。
$ emacs run-cat [←]
$ cat run-cat [←]
#!/bin/cat -n
hello
$ [←]
実行してみる。
$ ./run-cat [←]
     1	#!/bin/cat -n
     2	hello
$ []
これは次のように実行したものと同じになる。
$ /bin/cat -n ./run-cat [←]
     1	   #!/bin/cat -n
     2	   hello
$ []

一般のインタプリタ

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 [←]

macOS の場合:

$ /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 のカーネルが解釈する ( 補講/シェルとカーネルの関係 ) 。 シェルが、ファイルの先頭を読み、指定されたインタプリタを起動するのでは ない。「#!」行では、シェル変数、環境変数、エイリアスはつ かえない。

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

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

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

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

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

シェル

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

オペレーティング・システムのカーネルは、現在の所、C言語で記述されていることが多い。 シェルは、オペレーティング・システムの機能を拡張する。

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

情報科学類の学生は、シェルスクリプトくらい作れるようになりたい。

スクリプト言語 sh で記述した CGI のプログラムの例

[no-cgi/cgi-hello-sh.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/2022/2022-07-27/cgi-hello-sh.cgi

RubyによるCGI

cgi-printarg.c を Ruby で書直す。

[no-cgi/cgi-printarg-ruby.cgi]

   1:	#!/usr/local3/coins/linux/bin/ruby
   2:	# -*- coding: utf-8 -*-
   3:	# cgi-printarg-ruby.cgi -- CGI プログラムに対する引数を表示するプログラム
   4:	# ~yas/syspro/www/cgi-printarg-ruby.cgi
   5:	
   6:	require "cgi"
   7:	
   8:	def main()
  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 ? @cgi.content_length : 0 )
  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 ? "(null)" : CGI::escapeHTML(str) )
  39:	end
  40:	
  41:	main()

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

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

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

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['パラメタ名']は、もし 'パラメタ名'のパラメタが設定されていなければ、 空文字列 "" を返す。

表示例:

CGI の GET メソッドを使う例

姓: 名:
C言語
Java言語
その他
電子メール:

CGI の POST メソッドを使う例

姓: 名:
C言語
Java言語
その他
電子メール:

実行例:

request_method: GET
script_name: /~yas/coins/syspro-2022/2022-07-27/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-2022/2022-07-27/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

シェルからの実行

Ruby 言語の CGI クラスを使ったプログラムは、 環境変数を設定したデバッグ もできる。
$ 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&amp;firstname=name2&amp;lang=C&amp;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>
$ []

外部のプログラムの実行

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

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

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

File *f;
char cmd[BUFSIZE];

year_s  = getparam(qc,qv,"year");
month_s = getparam(qc,qv,"month");
snprintf(cmd,BUFSIZE,"/usr/bin/cal %s %s", month_s, year_s );
f = poepn(cmd,"r");
fgets(line,sizeof(line),f);
もし、year_s に ";""|" が含まれていたら、侵入される。
f = poepn("/usr/bin/cal 8 2022; cat /etc/passwd","r");

このような問題を防ぐには、次のようにすると良い。

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

C 言語とは異なり、Ruby 言語で CGI 等のプログラムを書けば、バッファ・オー バーフローやメモリ・リークの問題をプログラマは忘れることができる。

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

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

Ruby でも fork() は使える。

Ruby の exec(), spawn() は、 コマンドの引数にシェルのメタキャラクタが含まれていれば、 シェルを経由して実行されることがある。 次の形式では、execve() で実行される(argsは配列)。

exec("/full/path/name", *args)

インタプリタなので、eval() にも注意する。 eval() は、Ruby の任意の式を評価できる。 system() 等の実行だけでなく、Ruby の変数もアクセスできる。

eval("system(\"ls\")")
x = 10
eval("x=100")
printf("%d\n",x)

外部のプログラムの実行(Python言語)

Python の注意すべき関数、式、クラス

subprocess モジュールの run 等はデフォルトではシェルを実行しない(shell=False)。 Python subprocess モジュール/セキュリティで考慮すべき点 参照。

インタプリタなので、eval() や exec() にも注意する。 引数の文字列を評価・実行できる。

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

Python言語によるCGIプログラミング

1年生の「プログラミング入門」で利用した Python 言語でCGI のプログラム を書く事ができる。

SSI (Server Side Include)

Javaアプレット

授業評価アンケート

情報科学類では、教育の改善のために、学生の皆さんに授業評価アンケートを 実施していますので、ご協力をお願いします。

アンケートはTWINSから回答してください。

複数の教員が講義を担当している場合は、授業全体について回答して下さい。 各教員への意見・質問は最後の自由記載欄にお願いします。

なお、皆さんの評価が成績に影響することは一切ありません。 また、評価結果を教育の改善以外の目的に利用することはありませんし、 評価結果を公開する場合には個人を特定できるような情報は含めません。

期間は、7月27から 8月中旬(予定) 8月31日まで。 レポートを提出したら忘れないうちにすぐに提出すると良いでしょう。

練習問題と課題

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

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

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

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

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

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

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

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

練習問題(1003) cgi-arg1arg2-ruby.cgi

次の CGI プログラムを シェルから実行 しなさい。

$ cp ~yas/syspro/www/cgi-arg1arg2-ruby.cgi . [←]
$ 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>
$ []

自宅の PC で実行する時には、 一般のインタプリタ で説明したように、1行目のパス名を変更しなさい。 ruby コマンドのパス名は、次のコマンドで表示される。

$ which ruby [←]
./cgi-arg1arg2-ruby.cgi」で実行する代りに、 ruby コマンドにスクリプトのファイル名を与えて実行する方法もある。
$ ruby cgi-arg1arg2-ruby.cgi [←]

次のようにパラメタを設定して実行してみなさい。

余裕があれば、REQUEST_METHOD=POST についても、同様に実行してみなさい。

余裕があれば、フォームを定義し、何かデータを送ってみなさい。 coins で CGI を実行する場合、CGI の実行形式を Web サーバ (http://www.coins.tsukuba.ac.jp) に ssh でログインした上で作成すること。

http://www.coins.tsukuba.ac.jp/~syspro/2022/2022-07-27/cgi-arg1arg2-ruby.cgi

たとえば、次のような記述を含む HTML ファイルを作成し、 ~/public_html/htdocs/ 以下に置く。

<FORM ACTION="http://www.coins.tsukuba.ac.jp/~syspro/2022/2022-07-27/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>
arg1: arg2:
また、余裕があれば、method="post" についても同様に行ってみなさい。

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

この課題を解く前に 練習問題(1003) を解きなさい。その結果をレポートに含めなくなくて良い。

CGIによる足算を Ruby で書き直しなさい。ただし、次のことを実装しなくてもよい。

ヒント: C言語の関数getparam()の 代わりに Ruby 言語の @cgi["arg1"] が使える。 @cgi["arg1"] は、もし "arg1" のパラメタが設定されていな ければ、空文字列 "" を返す。

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

     s = "100"
     i = s.to_i()
to_i() は、文字列として "100xx" のように、数字以外が 現れると、それより前の数字だけから計算した値を返す。 たとえば、"100xx".to_i() は、整数 100 を返す。 文字列に数字が一切表れない時には、0 を返す。 本日の課題では、to_i() の動作に従って回答しても良い。

この課題では、 CGIによる足算 と同様に、作成したプログラムを シェルから実行 して動作を確認しなさい。 この時、単に足算の結果を表示するだけでなく、CGI として必須の行(Content-Type: 行や空行) を含んでいること。 結果を HTML で返す場合、ブラウザで表示できるような HTML であること。

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

この課題を解く前に 練習問題(1003) を解くことを強く奨める。

CGIによる加減乗除を Ruby で書き直しなさい。

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

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

練習問題(1007) cgi-arg1arg2-python.cgi

練習問題(1003) と同じことを、Python 言語を用いて 次の CGI プログラムに対して行いなさい。 Python言語によるCGIプログラミング参照。

練習問題(1008) CGIによる足算(Python)

この課題を解く前に 練習問題 cgi-arg1arg2-python.cgi を解きなさい。その結果をレポートに含めなくなくて良い。

練習問題(1004) と同じことを、Python 言語を用いて行いなさい。

Python言語によるCGIプログラミング参照。

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

fork() と execve() を使って、/usr/bin/cal を実行し、Web ブラウザに今月 のカレンダを実行する CGI プログラムを作りなさい。cal コマンドは、引数を 付けずに実行すると、今月のカレンダーを表示する。
$ cal [←]
      July 2022     
Su Mo Tu We Th Fr Sa
                1  2
 3  4  5  6  7  8  9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
$ []

CGI プログラムを実行した月のカレンダーを表示しなさい。固定した表示する だけなら、CGI の意味はない。余計な表示は省略すること。

  1. ヘッダや HTML の一部を表示する。fflush() で、それらをWeb サーバに 送る。
  2. fork(), execve(), wait() 等を用いて、cal コマンドを実行する。 標準出力をそのままにしておけば cal コマンドの結果がそのまま CGI の結果として返される。 その他に、pipe() を用いて結果を受け取る方法も考えられる。 この課題では、system() や popen() を用いてはならない。
  3. wait() 等で子プロセスの終了を確認したら、残りの HTML を出力する。
  4. 作成したプログラムを、 シェルから実行 して動作を確認する。
    $ ./a.out [←]
    Content-Type: text/html
    
    <HTML><HEAD></HEAD><BODY><PRE>
          July 2022     
    Su Mo Tu We Th Fr Sa
                    1  2
     3  4  5  6  7  8  9
    10 11 12 13 14 15 16
    17 18 19 20 21 22 23
    24 25 26 27 28 29 30
    31
    </PRE></BODY></HTML>
    $ []
    
そのプログラムを CGI として動作させなさい。
  1. ~/public_html/htdocs の下にコピーする。
    $ mkdir ~/public_html/htdocs/syspro-cgi-examples [←]
    $ cp a.out ~/public_html/htdocs/syspro-cgi-examples/show-cal-one.cgi [←]
    
  2. Web ブラウザから次のようなURLを開き、CGI を実行する。
    $ open http://www.coins.tsukuba.ac.jp/~ログイン名/syspro-cgi-examples/calc-one.cgi [←]
    
  3. CGI のプログラムを実行するようなフォームを含むHTML ファイルを作成する。
    <FORM ACTION="http://www.coins.tsukuba.ac.jp/~ログイン名/syspro-cgi-examples/calc-one.cgi" method="get">
    <INPUT type="submit">
    </FORM>
    
  4. Web ブラウザからフォームを含む HTML ファイルを開き、CGI を実行する。

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

練習問題(1009) で、今月のカレンダーではなく、任意の年・月のカレンダーを表示しなさい。cal コマン ドは、次のように引数を与えて実行すると、任意の年月のカレンダーを表示する機能があ る。
$ cal 8 2022 [←]
     August 2022    
Su Mo Tu We Th Fr Sa
    1  2  3  4  5  6
 7  8  9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
$ []
この機能を利用して、Web ブラウザから送られた年月のカレンダーを表示する CGI プログラムを作成しなさい。
  1. "year=2022&month=8"のように送るようなフォームを想定してプログラムを作成する。
  2. cgi-arg1arg2.cgiで、余計な表示をなくす。
  3. string_split() で分解した後、"year=" や "month=" があるパラメタを検索 する。
         char *year_s,*month_s;
         year_s = getparam(qc,qv,"year");
         month_s = getparam(qc,qv,"month");
    
    getparam() は、 cgi-arg1arg2.cgiのソースコード に含まれている。
  4. 引数が足りかったり、不正な場合にはエラーメッセージを表示して終了する。
  5. 得られた文字列を、strtol() 等で整数に変換する。 引数の範囲をチェックする。たとえば、13月や-1年, 10000年等が与えられた場合には、 エラーメッセージを表示して終了する。
  6. snprintf() 等で、cal コマンドにあたえる引数を文字列で作る。一度、 整数としてチェックした結果から cal コマンドに与える引数を作成すると安全 である。
  7. ヘッダや HTML の一部を表示する。fflush() で、それらをWeb サーバに 送る。
  8. fork(), execve(), wait() 等を用いて、cal コマンドを実行する。 この時、慎重に 外部のプログラムを実行 しなさい。 /usr/bin/calだけを実行しなさい。 それ以外のプログラムを実行してはならない。 標準出力をそのままにしておけば cal コマンドの結果がそのまま CGI の結果として返される。 その他に、pipe() を用いて結果を受け取る方法も考えられる。 この課題では、system() や popen() を用いてはならない。
  9. wait() 等で子プロセスの終了を確認したら、残りの HTML を出力する。
  10. 作成したプログラムを、 シェルから実行 して動作を確認する。
    $ export REQUEST_METHOD=GET [←]
    $ export QUERY_STRING='year=2022&month=8' [←]
    $ ./a.out [←]
    Content-Type: text/html
    
    <HTML><HEAD></HEAD><BODY><PRE>
         August 2022    
    Su Mo Tu We Th Fr Sa
        1  2  3  4  5  6
     7  8  9 10 11 12 13
    14 15 16 17 18 19 20
    21 22 23 24 25 26 27
    28 29 30 31
    </PRE></BODY></HTML>
    $ []
    
    なお、一度設定した環境変数の値は、シェルを終了するまで残っている。 a.out を実行するたびに設定する必要はない。

    レポートには、次の結果を含めなさい。

そのプログラムを CGI として動作させなさい。
  1. ~/public_html/htdocs の下にコピーする。
    $ mkdir ~/public_html/htdocs/syspro-cgi-examples [←]
    $ cp a.out ~/public_html/htdocs/syspro-cgi-examples/show-cal.cgi [←]
    
  2. Web ブラウザから次のようなURLを開き、CGI を実行する。
    http://www.coins.tsukuba.ac.jp/~ログイン名/syspro-cgi-examples/cal.cgi?year=2022&month=8
    
    "year=2022&month=8"のように送るようなフォームを含むHTML ファイルを作成する。
  3. Web ブラウザからフォームを含む HTML ファイルを開き、CGI を実行する。

この課題では、フォームを含む HTML ファイルを作成し、 CGIとして実行し、その結果をレポート含めなさい。 その結果としては、 アクセスログ を含めなさい。

注意: CGI のパラメタは、最初は、文字列で受け取り、最終的に cal コマンドには文 字列で渡す。この時に、年月日としては不適切なものがクライアントから渡さ れる可能性がある。不適切なものが渡された場合には、エラー・メッセージを 表示しなさい。また、不正なパラメタで cal コマンドは実行しないようにしなさい。

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

練習問題(1009) と同じ動作をするプログラムを Ruby で書きなさい。 この課題では、慎重に 外部のプログラムを実行 しなさい。 /usr/bin/calだけを実行しなさい。 それ以外のプログラムを実行してはならない。

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

この課題を解く前に 練習問題(1003) を解きなさい。 レポートには、その結果を含めなくて良い。

練習問題(1010) と同じ動作をするプログラムを Ruby で書きなさい。

クライアントから不正なパラメタから与えられた場合には、 エラー・メッセージをクライアントに返しなさい。 レポートには、不正なパラメタが与えられた場合の実行結果を含めなさい。

この課題では、慎重に 外部のプログラムを実行 しなさい。 シェルを経由しないで /usr/bin/cal を実行しなさい。 それ以外のプログラムを実行してはならない。

この課題では、フォームを含む HTML ファイルを作成し、 CGIとして実行し、その結果をレポート含めなさい。 その結果としては、 アクセスログ を含めなさい。

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

この課題を解く前に 練習問題 cgi-arg1arg2-python.cgi を解きなさい。 レポートには、その結果を含めなくて良い。

Python言語によるCGIプログラミング参照。

練習問題(1010) と同じ動作をするプログラムを Python で書きなさい。

クライアントから不正なパラメタから与えられた場合には、 エラー・メッセージをクライアントに返しなさい。 レポートには、不正なパラメタが与えられた場合の実行結果を含めなさい。

この課題では、慎重に 外部のプログラムを実行 しなさい。 シェルを経由しないで /usr/bin/cal を実行しなさい。 それ以外のプログラムを実行してはならない。

この課題では、フォームを含む HTML ファイルを作成し、 CGIとして実行し、その結果をレポート含めなさい。 その結果としては、 アクセスログ を含めなさい。

練習問題(1014) 配列の足算

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

ヒント: Ruby では CGI オブジェクトの params() メソッドを使い、配列の形で得る。 Python では、getfirst() ではなく getlist() を使う。

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

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

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


Last updated: 2022/07/25 10:31:49
Yasushi Shinjo / <yas@cs.tsukuba.ac.jp>