サイバーセキュリティ演習 ― Webセキュリティ基礎&実践― 6. データベースの不正操作対策 講義内容 1. 2*. Webサイトの仕組みとWebプログラミング基礎 3.4.5*. 不正スクリプトの実行対策 6.7*. データベースの不正操作対策 8 *. システムの不正操作対策とHTTPレスポンスの改竄対策 9 *. 偽リクエストによるサービスの不正利用対策 10*. セッションIDの 不正取得対策 11. 総合演習(1) 12*. 公開ディレクトリの不正横断対策と認証認可制御の欠落 による不正アクセス対策とエラーメッセージからの情報 漏えい対策 13. 総合演習(2) 14. 総合演習(3) 15. 学期末試験 ※*はレポートがある回になります。 本日の内容 SQLインジェクションの脆弱性攻撃体験と 脆弱性の修正 不正なログイン(文字列リテラル) 情報漏えい(数値リテラル) SQLとは SQLは、リレーショナルデータベース管理システム (RDBMS) において、データの操作や定義を行うた めのデータベース言語(問い合わせ言語)である。 RDBMSは、リレーショナルデータベースを管理す るためのソフトウェア。 Oracle、Access、 MySQL、PostgreSQL、 SQLite (Wikipediaより) 等 リレーショナルデータベース管 理システム(RDBMS) データを表に似た構造で管理するデータベース。複 数のデータ群が関係と呼ばれる構造で相互連結可能 userテーブル ログインID ユーザ名 パスワード (id:文字列) (name:文字列) (password:文字列) yamada Yamada Taro P@ssword suzuki Suzuki Jiro 3f858c accountテーブル 関係を持っ た複数の テーブルに 分けて管理 ログインID 口座番号 残高 (id:文字列) (account_id:数値) (balance:数値) yamada 1000001 100,000 yamada 1000002 200,000 suzuki 1000003 300,000 RDBMSのイメージとデータ ベースの用語 RDBMS 検索機能 追加削除機能 抽出機能 データベース テーブル2 accountテーブル テーブル1 userテーブル レコード (行) カラム(列) SQLの種類 データ定義言語 カラムやレコードなどリレーショナルデータ ベースの構造を定義する言語 データ操作言語 データの検索やデータの挿入などリレーショナ ルデータベースに対する操作を定義する言語 データ制御言語 データの操作に対する権限の付与やはく奪など アクセス制御を行うために定義する言語 データ定義言語 CREATE データベース、テーブルやカラム、レコードな どを作成する DROP データベース、テーブルやカラム、レコードな どを削除する ALTER 既に存在するデータベース、テーブルやカラム、 レコードなどを変更する TRUNCATE テーブルからレコードを削除する データ操作言語 SELECT テーブルからデータを検索する INSERT レコードを追加する UPDATE レコードを更新する DELETE レコードを削除する データ制御言語 GRANT 利用者に特定の作業の権限を与える REVOKE 利用者に与えた特定の作業の権限をはく奪する SQLの基礎1 データを検索する場合、 SELECT カラム1、カラム2 FROM テーブル名 [WHERE <条件>]; SELECT * FROM user WHERE id=‘yamada’; SELECT *:すべてのカラムを検索する FROM user:userテーブルから WHERE id=‘yamada’:id=yamadaに一致する レコードの 例えば、以下のテーブルがあっ た場合の検索結果は? SELECT * FROM user WHERE id=‘yamada’; userテーブル ログインID (id:文字列) ユーザ名 (name:文字列) パスワード (password:文字列) yamada Yamada Taro P@ssword suzuki Suzuki Jiro 3f858c accountテーブル ログインID 口座番号 残高 (id:文字列) (account_id:数値) (balance:数値) yamada 1000001 100,000 yamada 1000002 200,000 suzuki 1000003 300,000 SQLの基礎2 リテラル(直定数) プログラミングなどで、データ型に直接定めた値のこと。 文字列リテラルは、データ型に直接定めた文字列のこと 以下の例では、yamadaが文字列リテラルとなる SELECT * FROM user WHERE id = 'yamada'; 数値リテラルは、データ型に直接定めた数値のこと 以下の例では、200000が数値リテラルとなる SELECT * FROM account WHERE balance >= 200000; SQLの基礎3 シングルクォート(’) SQLでは、データの区切り文字として使う。 シングルクォートで囲むことで文字列リテラルを定義 する 数値リテラルの定義の場合は不要 コメント(--) SQLでは、2連続のハイフン(--)以降は、コメン トとして扱う SQLインジェクションとは 悪意のあるSQLを注入(インジェクショ ン)し、Webアプリケーションが意図しな いSQL文を実行してしまうことで、データ ベースを不正に操作されてしまう脆弱性 SQLインジェクションの種類 文字列リテラルに対するSQLインジェクション SQL文の文字列要素に、SQL文の一部やSQLコメント を入力することで、データベースへ不正な操作が行わ れる。 数値リテラルに対するSQLインジェクション SQL文の数値要素に、SQL文の一部や悪意のあるSQL 文を入力することで、データベースへ不正な操作が行 われる。 演習テーマ SQLインジェクション 「不正なログイン(文字列リテラル)」のリンクをク リックしましょう。 文字列リテラルに対するSQLイ ンジェクションの原理 ユーザからの入力値をそのまま文字列リテラルに連結して SQL文を組み立てることにより、意図しないSQL文を実行さ れ、データベースを不正に操作されてしまう 一般的なSQL文の組み立て処理 のやり方 指定したidの個人情報をuserテーブルから取得するSQL文を ユーザからの入力値(yamada)を利用して文字列連結によ り組み立てる 入力値に「' OR 'A'='A' --」と いう文字列を与えると Where句の条件がTRUE(真)となります。 その結果、userテーブルの全てのレコードが取得されます。 SQLを使った認証判定 ログインページの認証判定でSQLはよく使われる。 WHERE句の条件にあてはまるならば認証OK WHERE句の条件にあてはまらないならば認証NG ○:SELECT * FROM user WHERE id=‘yamada’ AND password=‘P@ssword’ ×:SELECT * FROM user WHERE id=‘suzuki’ AND password=‘12345678’ userテーブル ログインID (id:文字列) ユーザ名 (name:文字列) パスワード (password:文字列) yamada Yamada Taro P@ssword suzuki Suzuki Jiro 3f858c 文字列リテラルを用いたSQLイ ンジェクションの攻撃手口 WHERE句の条件が必ずTRUE(真)になるように内容 を変更する シングルクォート(’)とコメント(- -)が使われること が多い SELECT * FROM user WHERE id = ‘yamada’ AND password = ‘ ‘ OR ‘A’=’A’ -- '; 最初のシングルクォート(‘)でpasswordの入力を終端する OR演算子を使って別の条件を提示する ‘A’=’A’によって条件を必ずTRUE(真)とする コメント (--)で最後のシングルクォートを無効にする ID入力部分のSQLインジェク ションの攻撃手口 IDの入力部分にSQLインジェクションの脆弱性があった 場合、攻撃者はパスワードを知らなくてもログインが可 能になることがあります。 SELECT * FROM user WHERE id = ‘ yamada ‘ - AND password = ‘P@ssword'; ' 攻撃者が入力した文字列リテラル:yamada ‘ - コメントになる部分: ’ AND password = ‘P@ssword'; 適当なパスワードでyamadaのページにログインできる 演習内容(疑似的な攻撃) オンラインバンキングの文字列リテラルに不正な文字列 を入力し、不正にログインする 脆弱性があるウェブサイト オンラインバンキング 演習の進め方 1. Webサイトの挙動を把握する 2. 脆弱性となる箇所を特定する 3. 不正ログイン用の文字列リテラルを考える 4. 不正にログインする 1.Webサイトの挙動を把握 する オンラインバンクの入力 フィールドに様々な値を 入力して動作を確認する。 攻撃者として、利用でき ることを考える。 SQL文のエラーを起こす 値を入力して、脆弱な個 所を発見しましょう。 2.脆弱性となる箇所を特定す る 文字列リテラルを終端を狙い、Webサイトの入力項目 にシングルクォート(’)を入力してみる 正常(ログインエラー) 脆弱性あり(データベースエ ラー) 構文エラーが出た ということは、入 力値を含んだSQL 文が作成され、 データベースに問 い合わせされたと 推測できる なぜデータベースエラーが起き るのか 正常なSQL文 SELECT * FROM user WHERE id = ‘yamada ’ AND password = ‘P@ssword’; エラーとなるSQL文 SELECT * FROM user WHERE id = ‘’ ‘ AND password = ‘P@ssword’; SELECT * FROM user WHERE id = ‘yamada ’ AND password = ‘’ ’; シングルクォートの余りがあるため構文エラーとなる。 3.不正ログイン用の文字列リ テラルを考える ユーザ認証処理では、次のSQL文が使用されていま す。 SELECT * FROM user WHERE id = ‘yamada' AND password = 'パスワード'; 論理演算子 OR を使用してWHERE句が常に正しく なる条件にしましょう パスワードの条件が常に「真」となる文字リテラル を考えましょう。 SELECT * FROM user WHERE id = ‘yamada' AND password = ‘OR ‘A’=’A’ --'; 4.不正にログインする 考えたパスワードを使ってログインができるか確認 しましょう。 ログインIDは「yamada」を使いましょう パスワードは「' OR 'A'='A' --」 以下のログインIDを使った場合でもログインができ るか確認しましょう。 ログインIDは「yamada ‘ --」 パスワードは、適当。 4.不正にログインする 「Congratulations!!演習の目標を達成しまし た。」と表示されたら、OKです。 SQLインジェクションの一般的 な影響 データベースに蓄積された非公開情報を閲覧される 個人情報や機密情報などが漏えいする可能性がある データベースに蓄積された情報を改ざん、消去される ウェブページが改ざんされたり、パスワードが変更されてし まったり、システムが停止するといった被害が発生する可能性 がある。 認証回避により不正にログインされる ログインしたユーザに許可されている全ての操作が可能となり、 不正な行為が行われる可能性がある。 ストアドプロシージャなどを利用してOS コマンドを実行さ れる システムの乗っ取りや、他への攻撃の踏み台として悪用される 可能性がある。 対処方法 原因 外部から入力できるパラメータをそのまま文字列連結 してSQL文を組み立てるため、WHERE句の条件が常に 真となるリテラルの注入によりSQL文が変更されてし まい、そのままSQLの処理が実行される 対処方法 文字列リテラルの入力値を文字そのものとして解釈さ せるために、SQL文の組み立ては全てプレースホルダ を使いプリペアステートメントで実装する。 プレースホルダ SQL文のリテラル部分に後から正式な値を挿入するために一 時的に場所として確保しておく値 SQL文の事前に確定させておく方法をプリペアドステートメン トと言う。 実際の値を挿入することをバインド処理という 静的プレースホルダと動的プレースホルダがある プレースホルダなし SELECT * FROM user WHERE id = ‘yamada' AND password = ‘P@ssword'; プレースホルダあり SELECT * FROM user WHERE id = ? AND password = ?; バインド処理後は普通の 文字列として扱われる yamada P@ssword 静的&動的プレースホルダ 静的プレースホルダ バインド処理をデータベース側で実行する方式 PHPからプレースホルダのついたSQL文をデータベース側 にあらかじめ送信してSQL文を確定させておく SQL実行段階で、PHPから実際の値をデータベースに送り、 データベースがバインド処理をしてからSQL文を実行する 動的プレースホルダ バインド処理をWebアプリケーション側で実行する方式 PHPがバインド処理したSQL文をデータベースに送り、 データベースがそのSQL文を実行する 動的プレースホルダの不適切な実装は脆弱性となるため、 可能な限り静的プレースホルダを利用しましょう プリペアドステートメントの実装 PDO(PHP Data Object) 1つの関数で複数のデータベースアクセスを可能にす る抽象化クラス 2005年11月にリリースされたPHP5.1.0から実装された PDOが実装されるまでは、データベースの種類ごとに 関数を分けていた プリペアドステートメントの実装手順 PDOの利用を宣言:PDO() 例外処理を施す: try{}catch{} SQL文を準備する:prepare() SQLを実行する:execute() プレースホルダを使わないプリ ペアドステートメントの例 <?php データベース接続 try { 例外処理 $db = new PDO($dbsrv, $dbusr, $dbpwd); $stmt = $db->prepare("SELECT * FROM user WHERE id = $id AND password = $pwd”); $stmt->execute(); } catch (PDOException $e) { } ?> SQLを準備 実行 以下は既に宣言済みとする。 print(“Error:”.$e->getMessage()); $dbsrv=‘mysql:host=example.jp; dbname=exampleDB’; $dbusr=‘root’; 例外発生時の $dbpwd=‘csr2015’; エラー表示 $id=‘yamada’; $pwd=‘P@ssword’ プレースホルダを使ったプリペ アドステートメントの例 <?php try { $db = new PDO($dbsrv, $dbusr, $dbpwd); 疑問符プレースホルダ を記述 $stmt = $db->prepare("SELECT * FROM user WHERE id = ? AND password = ?”); $stmt->execute(array($id,$pwd)); } catch (PDOException $e) { print(“Error:”.$e->getMessage()); } ?> プレースホルダに代 入する値をexecute 関数に配列で渡す 演習内容(脆弱性の修正) オンラインバンキングの文字列リテラルを用いたSQLイ ンジェクションの脆弱性となるコードを特定し、プレー スホルダの実装を行う 演習では、脆弱性のあるWebアプリケーションとデータ ベースサーバが同一ホスト上にあるため、結果として動 的プレースホルダの実装で演習を進めることになります。 修正プログラム オンラインバンキング bank.php bank108.class.phpの処理内容をゲット メッセージ割り当て テンプレート表示 bank108.class.php データベースのデータの初期設定 テーブルからの情報取得の成功判定 ログイン処理 脆弱性の修正の手順 bank108.class.php 1. ログイン処理の内容の確認。 2. プレースホルダを使用する箇所の特定。 3. プレースホルダを使ったプリペアドステートメントの 実装。 1.ログイン処理の内容の確認 public function login() データベースのアクセス情報を取得 { $db = $this->get_db(); リテラル(入力値)の情報を取得 $param = $this->get_param(); try { $stmt = $db->prepare("SELECT * FROM user WHERE id = '" . $param["id"] . "' AND password = '" . $param["password"] . "';"); if ($stmt == null) { throw new Exception(); } $stmt->execute(); プリペアドステートメントを実行 (データベース検索の実行) } catch (Exception $e) { throw new Exception((__LINE__ . $db->errorInfo()), LogUtil::ERROR_DB_EXECUTE); } (次ページに続く) 1.ログイン処理の内容の確認 データベース検索結果からユーザ情報を取得 (つづき) $user = $stmt->fetch(); if ($user) { 検索結果が返ってきたら 攻撃成功判定 $this->is_success($user, $param["id"], $param["password"]); $this->set_session($this->get_login(), $user); クライアントとの $this->set_name(); 利用者のページを セッションを確立 return true; 設定 認証成功を 返す } else { ログアウト処理を $this->proc_logout(); $this->set_content(parent::WARNING3, true); $this->set_content(parent::WARNING4, false); return false; } } 認証失敗を 返す 実行 警告メッセージを 設定 2.プレースホルダを使用する 箇所の特定 try { $stmt = $db->prepare("SELECT * FROM user WHERE id = ‘" . $param["id"] . "' AND password = ‘" . $param["password"] . "';"); if ($stmt == null) { throw new Exception(); } $stmt->execute(); } catch (Exception $e) { throw new Exception((__LINE__ . $db->errorInfo()), LogUtil::ERROR_DB_EXECUTE); } 3.プレースホルダを使ったプ リペアドステートメントの実装 try { $stmt = $db->prepare("SELECT * FROM user WHERE id = ? AND password = ?"); if ($stmt == null) { throw new Exception(); } $stmt->execute(array($param["id"], $param["password"])); } catch (Exception $e) { throw new Exception((__LINE__ . $db->errorInfo()), LogUtil::ERROR_DB_EXECUTE); } 動作確認 ログインIDに「yamada」、パスワードに「' OR 'A'='A' --」を入力してログインしてみましょう。 ログインができなければOKです。 文字列リテラルに対するSQLイ ンジェクションの要点 文字列リテラルに対するSQLインジェクションの脆弱性 の原因は、ユーザから受け取った入力値をそのまま文字 連結してSQL文を組み立てていることです。 意図しないSQL文の実行を防ぐために、プレースホルダ を用いたプリペアドステートメントによるSQL文の組み 立てを行い、ユーザからの入力値をエスケープ処理する ようにしましょう。 本日の内容 SQLインジェクションの脆弱性攻撃体験と 脆弱性の修正 不正なログイン(文字列リテラル) 情報漏えい(数値リテラル) SQLインジェクションの種類 文字列リテラルに対するSQLインジェクション SQL文の文字列要素に、SQL文の一部やSQLコメント を入力することで、データベースへ不正な操作が行わ れる。 数値リテラルに対するSQLインジェクション SQL文の数値要素に、SQL文の一部や悪意のあるSQL 文を入力することで、データベースへ不正な操作が行 われる。 演習テーマ SQLインジェクション 「情報漏えい(数値リテラル)」のリンクをクリック しましょう。 数値リテラルに対するSQLイン ジェクションの原理 ユーザからの入力値をそのまま数値リテラルに連結してSQL 文を組み立てることにより、意図しないSQL文を実行され、 データベースを不正に操作されてしまう 一般的なSQL文の組み立て処理 のやり方 指定したaccount_idの口座情報をaccountテーブルから取 得するSQL文をユーザからの入力値(10)を利用して文字列 連結により組み立てる 入力値に「 0 OR 1 = 1 」とい う文字列を与えると Where句の条件がTRUE(真)となります。 その結果、accountテーブルの全てのレコードが取得されます。 SQLを使った情報取得 ユーザの情報取得にSQLはよく使われる。 WHERE句の条件にあてはまるならば結果を出力 WHERE句の条件にあてはまらないならば出力なし ○:SELECT * FROM user WHERE id=‘yamada’ AND account_id=1000001 ×:SELECT * FROM user WHERE id=‘suzuki’ AND account_id=9999999 accountテーブル ログインID 口座番号 残高 (id:文字列) (account_id:数値) (balance:数値) yamada 1000001 100,000 yamada 1000002 200,000 suzuki 1000003 300,000 数値リテラルを用いたSQLイン ジェクションの攻撃手口 WHERE句の条件が必ずTRUE(真)になるように内容 を変更する OR演算子が使われることが多い SELECT * FROM user WHERE id = ‘yamada’ AND account_id= 0 OR 1=1 ; account_idには適当な値を指定する OR演算子を使って別の条件を提示する 1=1によって条件を必ずTRUE(真)とする 演習内容(疑似的な攻撃) オンラインバンキングの数値リテラルに不正な文字列を 入力し、口座残高照会ページで全てのユーザの口座残高 を表示させる 脆弱性があるウェブサイト オンラインバンキング 演習の進め方 1. Webサイトの挙動を把握する 2. 脆弱性となる箇所を特定する 3. 全てのユーザの口座残高を表示させる数値リテラ ルを考える 4. 不正アクセスのURLを作成し実行する 1.Webサイトの挙動を把握 する オンラインバンクの口座 残高照会ページの動作を 確認する。 攻撃者として、利用でき ることを考える。 SQL文のエラーを起こす 値を入力して、脆弱な個 所を発見しましょう。 URLも確認しましょう。 2.脆弱性となる箇所を特定す る 数値リテラルの終端を狙い、account_idにシングルクォー ト(’)を入力してみる http://localhost/Web/Scenario109/VulSoft/bank.php?pa ge=3&account_id=‘; 構文エラーが出た ということは、入 力値を含んだSQL 文が作成され、 データベースに問 い合わせされたと 推測できる 正常(番号が無ければ表示されない) 脆弱性あり(データベースエラー) 3.全てのユーザの口座残高を表 示させる数値リテラルを考える 口座残高照会処理では、次のSQL文が使用されてい ます。 SELECT * FROM account WHERE id = 'ログインID' AND account_id = 口座番号; 論理演算子 OR を使用してWHERE句を常に正しく なる条件にしましょう account_idの条件が常に「真」となる数値リテラル を考えましょう。 SELECT * FROM account WHERE id = ‘yamada' AND account_id = 1 OR 1=1; 4.不正アクセスのURLを作成 し実行する 口座残高照会のURLは以下の通りです。 http://localhost/Web/Scenario109/VulSoft/bank. php?page=3&account_id=1000001 考えた数値リテラルを追記したURLを作成しましょ う。 http://localhost/Web/Scenario109/VulSoft/bank. php?page=3&account_id=1 OR 1=1; 4.不正アクセスのURLを作成 し実行する ログインユーザ以外の口座残高情報が取得できるか 確認しましょう まずは、yamadaのIDでログインしましょう。 ログインIDは「yamada」 パスワードは「P@ssword」 ログイン後、作成したURLをURL欄に入力しましょう。 4.不正アクセスのURLを作成 し実行する 「Congratulations!!演習の目標を達成しまし た。」と表示されたら、OKです。 対処方法 原因 外部から入力できるパラメータをそのまま文字列連結 してSQL文を組み立てるため、WHERE句の条件が常に 真となるリテラルの注入によりSQL文が変更されてし まい、そのままSQLの処理が実行される 対処方法 数値リテラルの入力値を値そのものとして解釈させる ために、SQL文の組み立ては全てプレースホルダを使 いプリペアステートメントで実装する。 演習内容(脆弱性の修正) オンラインバンキングの数値リテラルを用いたSQLイン ジェクションの脆弱性となるコードを特定し、動的プ レースホルダの実装を行う 演習では、脆弱性のあるWebアプリケーションとデータ ベースサーバが同一ホスト上にあるため、結果として動 的プレースホルダの実装で演習を進めることになります。 修正プログラム オンラインバンキング bank.php bank109.class.phpの処理内容をゲット メッセージ割り当て テンプレート表示 bank109.class.php データベースのデータの初期設定 テーブルからの情報取得の成功判定 口座番号の有効性の検証 口座データの取得処理 脆弱性の修正の手順 bank109.class.php 1. 口座データの取得の内容の確認。 2. プレースホルダを使用する箇所の特定。 3. プレースホルダを使ったプリペアドステートメントの 実装。 1.口座データの取得の内容の 確認 public function proc_inquiry() { $param = $this->get_param(); if (isset($param[parent::ACCOUNT_ID])) { $stmt = null; $db = $this->get_db(); データベース、セッ ション、アカウントID の情報を取得 $session = $this->get_session(); $id = $session[$this->get_login()][parent::USER_ID]; $account_id = $param[parent::ACCOUNT_ID]; (次ページに続く) (つづき) 1.口座データの取得の内容の 確認 プリペアドステートメ ントを実行 try { $stmt = $db->prepare("SELECT * FROM account WHERE id = '" . $id . "' AND account_id = " . $account_id); if ($stmt == null) { throw new Exception(); } $result1 = $stmt->execute(); } catch (Exception $e) { throw new Exception((__LINE__ . $db->errorInfo()), LogUtil::ERROR_DB_EXECUTE); } (次ページに続く) 1.口座データの取得の内容の 確認 (つづき) $balances = array(); while ($row = $stmt->fetch()) { 残高を金額形式(カン マ区切り)に変換 $row[parent::BALANCE] = number_format($row[parent::BALANCE]); $balances[] = $row; } 金額形式の残高をセッ トし、攻撃成功判定 $this->set_content(parent::BALANCE, $balances); $this->is_success($balances); } $result2 = $this->set_accounts(); } 2.プレースホルダを使用する 箇所の特定 try { $stmt = $db->prepare("SELECT * FROM account WHERE id = '" . $id . "' AND account_id = " . $account_id); if ($stmt == null) { throw new Exception(); } $result1 = $stmt->execute(); } catch (Exception $e) { throw new Exception((__LINE__ . $db->errorInfo()), LogUtil::ERROR_DB_EXECUTE); } 3.プレースホルダを使ったプ リペアドステートメントの実装 try { $stmt = $db->prepare("SELECT * FROM account WHERE id = ? AND account_id = ?”); if ($stmt == null) { throw new Exception(); } $result1 = $stmt->execute(array($id, $account_id)); } catch (Exception $e) { throw new Exception((__LINE__ . $db->errorInfo()), LogUtil::ERROR_DB_EXECUTE); } 動作確認 ログインIDに「yamada」、パスワードに 「 P@ssword 」を入力してログインしてみましょう。 URL欄に次のURLを入力してみましょう http://localhost/Web/Scenario109/EditSoft/bank.php ?page=3&account_id=99 OR 1=1 口座残高照会ページに全てのユーザの口座残高が表示さ れなければOKです。 数値リテラルに対するSQLイン ジェクションの要点 数値リテラルに対するSQLインジェクションの脆弱性の 原因も、ユーザから受け取った入力値をそのまま文字連 結してSQL文を組み立てていることです。 文字列リテラルに対するSQLインジェクションと同様に、 プレースホルダを用いたプリペアドステートメントによ るSQL文の組み立てを行い、ユーザからの入力値をエス ケープ処理するようにしましょう。
© Copyright 2025 ExpyDoc