SHA2017 CTF round Teaser Website Attack writeup

SHA2017 CTF round Teaser Website Attack (Network) のwriteup。ひさしぶりにCTFをやった。

問題文

Our website received an attack in 2013, we managed to capture the attack in this pcap. Can you find out if we leaked some sensitive information?

website-attack.pcapが与えられる。

decode as

IPAという見慣れないプロトコルのパケットがあるが、中身を見ると明らかにHTTPなのでwiresharkDecode As...でHTTPとして認識させる。

f:id:TAKEmaru:20170621031102p:plain

通信内容

WiresharkExport Objects → HTTPでHTTPのリクエストをざっと見てみる。

f:id:TAKEmaru:20170621045208p:plain

action=search&words=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&sort=stockの後のリクエストはwordsパラメータが少しずつ変化しているのでBlind SQL Injectionっぽい通信だなと分かる。

通信の流れ

  1. GET /
  2. GET bootstrap.min.css
  3. GET /?action=search&words=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&sort=stock
  4. redirect GET /?action=display&what=e4146d4252bafb3b38212df186497a7479d5e95af4796e7573a65e6849952032e4146d4252bafb3b38212df186497a7479d5e95af4796e7573a65e6849952032e4146d4252bafb3b38212df186497a7479d5e95af4796e7573a65e6849952032e4146d4252bafb3b38212df186497a7479d5e95af4796e7573a65e6849952032e4146d4252bafb3b38212df186497a74799edb6fda5b44
  5. GET /?action=display&what=af7d6f4240be9a2d31252290ef5b7e797dd7fc3be66d6d6766b5375a79b84d428964052355a9f53759403fe18b416f7067d9e948e17d7d147eae52605cf4505f947c0c3e33dc8a5d593424f58928484157f7c33bf0747c7112976d406bb14136eb1105
  6. 5のwhatパラメータを少しずつ変えながらGETリクエストを投げ続けている。

Our website received an attack in 2013?

2013年にRC4に対する脆弱性(JVNDB-2013-001910 - TLS プロトコルおよび SSL プロトコルで使用される RC4 アルゴリズムにおける平文回復攻撃を実行される脆弱性)が発表されていた。ので、RC4が関係していると考える。

RC4 , Is it possible to find the key if we know the plaintext and ciphertext? - Cryptography Stack Exchangeによると、RC4のciphertext Cはplaintext PとkeyであるRC4(key)のXORで求められる。

f:id:TAKEmaru:20170621163826p:plain

よって、RC4(key)が同一であった場合、Pは2つ目のplaintext P'、2つ目のciphertext C'を用いて以下のように表せる。

f:id:TAKEmaru:20170621031035p:plain

これを用いてリクエスト内容を復号化する。このような式になるのはRC4がXORを用いて暗号化しているからだ。

これ以外の2013年のメジャーな攻撃にはTLSに対するCBCモードPadding oracle attackであるLucky Thirteen Attackがあったが今回はTLSに関係ない。

解く

RC4のものと思われるchipertextを、Wiresharkでexportしたファイル名から取り出し、sql文にdecryptする。

コード

# coding: UTF-8

import os

files = os.listdir('./exports')


def decrypt(encrypted):
    p2 = ""
    for i in range(len(encrypted) / 64 + 1):
        c = encrypted[i * 64: (i + 1) * 64]
        c2 = "e4146d4252bafb3b38212df186497a7479d5e95af4796e7573a65e6849952032"
        p = "4141414141414141414141414141414141414141414141414141414141414141"
        for j in range(len(c) / 2):
            begin = j * 2
            end = (j + 1) * 2
            c_i = ord(c[begin: end].decode('hex'))
            c2_i = ord(c2[begin: end].decode('hex'))
            p_i = ord(p[begin: end].decode('hex'))
            p2 = p2 + chr(c_i ^ c2_i ^ p_i)

    return p2

for filename in files:
    with open('./exports/' + filename, 'r') as f:
        data = f.read()
        # レスポンスが HTTP/1.1 200 OKのときだけ
        if data.find('hyper') < data.find('Traditional'):
            print decrypt(filename[23:])

実行結果

flagテーブルからSELECTしているSQL文とSQLテーブルからSELECTしているSQL文が混在している。

$ python solve.py
...
(CASE WHEN (SELECT SUBSTR(flag,28,1)  FROM secret_flag LIMIT 0,1) = '1' THEN stock ELSE price END)

(CASE WHEN (SELECT SUBSTR(flag,2,1)  FROM secret_flag LIMIT 0,1) = 'l' THEN stock ELSE price END)

(CASE WHEN (SELECT SUBSTR(flag,9,1)  FROM secret_flag LIMIT 0,1) = '7' THEN stock ELSE price END)

(CASE WHEN (SELECT SUBSTR(flag,8,1)  FROM secret_flag LIMIT 0,1) = '0' THEN stock ELSE price END)

(CASE WHEN (SELECT SUBSTR(sql,3,1) FROM  SQLITE_MASTER LIMIT 1,1) = 'E' THEN stock ELSE price END)

(CASE WHEN (SELECT SUBSTR(sql,35,1) FROM  SQLITE_MASTER LIMIT 1,1) = 't' THEN stock ELSE price END

(CASE WHEN (SELECT SUBSTR(sql,34,1) FROM  SQLITE_MASTER LIMIT 1,1) = 'x' THEN stock ELSE price END
...

この結果を踏まえて、flagテーブルを操作しているSQL文から一文字ずつflagを抜くことを考える。

コード

# coding: UTF-8

import os

files = os.listdir('./exports')


def decrypt(encrypted):
    p2 = ""
    for i in range(len(encrypted) / 64 + 1):
        c = encrypted[i * 64: (i + 1) * 64]
        c2 = "e4146d4252bafb3b38212df186497a7479d5e95af4796e7573a65e6849952032"
        p = "4141414141414141414141414141414141414141414141414141414141414141"
        for j in range(len(c) / 2):
            begin = j * 2
            end = (j + 1) * 2
            c_i = ord(c[begin: end].decode('hex'))
            c2_i = ord(c2[begin: end].decode('hex'))
            p_i = ord(p[begin: end].decode('hex'))
            p2 = p2 + chr(c_i ^ c2_i ^ p_i)

    return p2

flag = [' ' for _ in range(40)]

for filename in files:
    with open('./exports/' + filename, 'r') as f:
        data = f.read()
        # レスポンスが HTTP/1.1 200 OKのときだけ
        if data.find('hyper') < data.find('Traditional'):

            for filename in files:
                with open('./exports/' + filename, 'r') as f:
                    fc = f.read()
                    if fc.find('hyper') < fc.find('Traditional'):
                        decrypted = decrypt(filename[23:])

                        if decrypted.find('flag') != -1:
                            flag_i = decrypted.find("'") + 1
                            if flag_i != 0:
                                flag_c = decrypted[flag_i]

                            substr_position_i = decrypted.find(',') + 1
                            if substr_position_i != 0:
                                substr_position = decrypted[substr_position_i:substr_position_i + 2]
                                if substr_position[1] == ',':
                                    substr_position = substr_position[0]

                            if flag_i != 0:
                                flag[int(substr_position)] = flag_c


print ''.join(flag)

実行結果

$ python solve.py
 flag{7307e3ee8da198ca4a7f9b1f8b018d8e} 

参考資料

GSoC 2017にmetasploitで採択された。

GSoCとは

GSoCというのはGoogle Summer of Codeの略で、アメリカでは学校が休みである夏の3ヶ月(5/30〜8/22)の間、Googleからお金💰を貰ってOSSに貢献しようというイベントである。日本では一部の期間、学校がある期間とかぶり、一見むずかしいように見えるが、今のところ問題なく行えている。作業開始前にメンターに夏休みかどうか聞かれたので考慮してくれているかんじはあるが、この辺はOSSによると思う。参加しているOSSの組織は多く、今年は201個の組織が参加している。

Google Developers Japan: Google Summer of Code のおしらせ

Google Summer of Code 2017 statistics part 1 によると、今年は日本から13人採択された模様。インド人569人マジか…

GSoCの流れとしては、まずGoogleが学生を受け入れることができるOSSの団体を募集し、承認された団体は学生に取り組んでほしいタスクのリストを用意しておく。学生はそのリストからタスクを選ぶ、もしくは自分でやりたいテーマを提案することでテーマを決める。 学生はその内容をProposalに書いて応募する。そして採択されるとメンターの指導のもと実装に入る。

このProposalを書くのが結構大変でもちろん英語だし、OSSによっては1週間毎のスケジュールを書く必要があったりする。書いたら終わりじゃなくて、メンターとなりそうな人と連絡を取りレビューしてもらいながら完成させる。Proposalに何を書くかというと、Abstract, Vitals, Skill, Projectの詳細等である。ProposalのテンプレートがOSSによってはあるので気になる人は見てみるといい。このProposalの内容によって採択の可否が決まる。2つのOSSに応募したのでほんとに大変だった。

採択テーマ

採択されたテーマはImproving stager payloads especially on non-Windows platformsでmetasploitというOSSでやっていくこととなった。 ProposalはGistに上げたので興味のある各位はみてくれ。 最初、適当にテーマを選んでProposal書き、レビューしてもらうために送りつけてみたところ、今までの開発経験を評価してもらえたらしく、メンターにこっちのほうがいいよーというかんじで別のテーマを提案してもらえて幸運だった。たしかに面白そうだったので提案してもらったテーマで応募した。

何をやるのか

テーマを直訳すると非windows環境でのstager payloadが、貧弱なので強化していくというかんじになる。 stager payloadというのはstage payloadと連携して動作し、 攻撃者とリモートホストとの通信を確立し、リモートホスト上でstage payloadを読み込み実行するというものである。stageというのは、stagersモジュールによってダウンロードされるpayload componentである。

具体的には、以下のようなことをやる。

  • windowsと違って、linux環境には汎用的なHTTP libraryがないので、reverse_http/https stagerを作成する。

  • Windows環境でしか動かない reverse_tcp_rc4 stagerをlinux環境で動くようにする。payloadのfingerprintingを回避するためにrc4で暗号化するというstagerである。

  • posix stagerにwindowsのstagerにはあってない機能、リトライやUUID等を実装する。

完走できるようやっていくぞ!!!!

RSpecで画像をアップロードするAPIのテストを書く

Rack::Test::Methodsに含まれるRack::Test::UploadedFile.newを使う。Gemfile.lockを見れば分かるようにrack-testはデフォルトでRailsアプリケーションにインストールされている。

以下、grapeで作成した画像をアップロードするAPIに対する正常系のテストの例である。

describe Hoge::Image::CreateAPI, type: :request do
  describe 'POST /Image' do
    let(:path) { '/api/v1/image/' }

    let!(:user) { create(:user) }
    let!(:access_token) { user.activate.access_token }
    let(:valid_headers) { { 'Access-Token' => access_token } }

    context 'when param is valid' do
      let(:valid_params) do
        {
          image: {
            image: Rack::Test::UploadedFile.new('path/forTest.png', 'image/png')
          }
        }
      end

      it_behaves_like 'require_login', :post

      describe 'response' do
        before do
          post path, params: valid_params, headers: valid_headers
          @json = JSON.parse(response.body)
        end

        it 'returns 201' do
          expect(response.status).to eq(201)
        end

        it 'contains success message' do
          expect(@json['message']).to eq('Success')
        end
      end
    end
  end
end

GOT、PLTとIAT

動的リンクされたライブラリのアドレス解決の際、ELFではGOT、PLTが、PEではIATがそれぞれ用いられる。たまにどっちがどっちでどうだったのか分からなくなるのでメモしておく。

GOTとPLT

GOT(Global Offset Table)はシンボルへのポインタの配列で、 プログラムが使用するすべてのライブラリ関数はGOTにエントリを持ち、そこで実際の関数のアドレスを管理する。 これにより、ライブラリをプロセスメモリ空間の中で容易に再配置できるようになる。

遅延リンク

ELFではPLT(Procedure Linkage Table)を用いて遅延リンクできるようにしている。 遅延リンクとは、共有ライブラリのロード時にシンボル解決を行うのではなく、関数が実際に呼び出されるときにシンボル解決を行うことである。 関数が最初に呼ばれるとき、飛び先は関数の本体ではなく、PLT上の対応するコードとなり、 そのPLTには、GOTの対応するスロットから飛び先アドレスを取得し、そこに飛ぶようなコードが格納されている。 呼びだされたときには、PLT経由でRTL(RunTime Linker: 実行時リンカ)のシンボル解決のコードが呼ばれ、 GOTに設定してある自分自身の飛び先を。本来の関数に設定しなおす。 関数が次に呼び出されたときには、今度はRTLは関与せずにGOTのエントリを通じて直接実行される。

確認する

以下のコードを実行し、puts()を呼ぶ前と呼んだ後でGOTのエントリを確認する。

コード

#include <stdio.h>

int main()
{
  puts("test");
}

gdbにより確認

$ gdb ./a.out
Reading symbols from ./a.out...(no debugging symbols found)...done.
gdb-peda$ start
gdb-peda$ disas main
Dump of assembler code for function main:
   0x0000000000400526 <+0>:  push   rbp
   0x0000000000400527 <+1>:  mov    rbp,rsp
=> 0x000000000040052a <+4>:  mov    edi,0x4005c4
   0x000000000040052f <+9>:  call   0x400400 <puts@plt>
   0x0000000000400534 <+14>: mov    eax,0x0
   0x0000000000400539 <+19>: pop    rbp
   0x000000000040053a <+20>: ret    
End of assembler dump.
gdb-peda$ disas 0x400400
Dump of assembler code for function puts@plt:
   0x0000000000400400 <+0>:  jmp    QWORD PTR [rip+0x200c12]        # 0x601018
   0x0000000000400406 <+6>:  push   0x0
   0x000000000040040b <+11>: jmp    0x4003f0
End of assembler dump.
gdb-peda$ x/x 0x601018 // GOTを確認
0x601018:  0x0000000000400406
// 書き換え前
gdb-peda$ break 0x400534
Function "0x400534" not defined.
gdb-peda$ break *0x400534
Breakpoint 2 at 0x400534
gdb-peda$ c
Continuing.
test
Breakpoint 2, 0x0000000000400534 in main ()
gdb-peda$ x/x 0x601018  // GOTを確認
0x601018:  0x00007ffff7a7d690
// 書き換え後

こうして遅延リンクを用いたアドレス解決は位置独立性と共有性を損なわずに行われる。

遅延リンクを用いるときGOTを動的に書き換えるため、GOTは書き込み可能である必要がある。 そのため、format string bugやROPを用いてGOT overwriteするといった形で攻撃に用いられることがある。

この攻撃に対するセキュリティ機構としてFull RELRO(RELocation ReadOnly)というものがある。 これは、遅延リンクを行わずに、ローダがプログラム実行開始時に実際の関数アドレスをGOTのエントリに挿入することで、 GOTを書き込み禁止するというものである。

IAT

Windows PEにもGOTと似たIAT(Import Address Table)というのがある。IATはプログラム中で使用するAPIのコードの開始位置のアドレスを格納したテーブルである。

IATのエントリにはローダがメモリ空間上に各DLLが読み込まれたアドレスをベースにして、実際の関数開始アドレスを書き込む。ローダが書き込むので実行前にIATのエントリは書き換えられる。これによって、プログラムはIATを参照するだけでAPIを呼び出せるようになる。IATのエントリは実行時には書き換える必要性がないので、デフォルトで書き込み禁止にすることができ、GOTより実行中に書き換えられるリスクは軽減されている。

IATが書き込み禁止になっているからといって、書き換えられない訳ではなく、Windows APIのVirtualProtect()を用いて書き込めるようにできる。 これはAPIのIATエントリを書き換え、任意の関数を指すようにするAPIフックに利用される。

参考資料

メソッドを関数に内包して関数のように扱うと便利

メソッドを関数に内包すると関数のように扱える。初期化済みのインスタンスや構造体を使うことで、構造体の初期化をしなくていいようにしている。これは特にライブラリを作るときに便利で、lib.func() という風にメソッドを呼ぶことができる。ライブラリが呼び出される時に、インスタンスや構造体を初期化しておくとよい。

golangだと以下のように書く。tkmru/pbas: Config file finder for golangより引用。

package pbas

import (
    "io/ioutil"
    "path"
)

type Pbas struct {
    configName  string
    configPaths []string
}

var p *Pbas

func init() {
  p = new(Pbas)
  p.configName = ""
  p.configPaths = []string{}
}

func SetConfigName(fileName string) { p.SetConfigName(fileName) }
func (p *Pbas) SetConfigName(fileName string) {
    if fileName != "" {
        p.configName = fileName
    }
}

Pythonスクリプトに対してユニットテストするときのディレクトリ構成

フレームワークを使って開発しているとユニットテスト用にディレクトリが用意されていることもあるが、単体のPythonスクリプトに対してユニットテストを書くときはディレクトリ構成を考える必要がある。やはりユニットテストとそれ以外は違うディレクトリに配置したい。この記事ではディレクトリ構成を紹介する。

ディレクトリ構成

new_project
├── project_name
│   ├── __init__.py
│   └── hoge.py
└── tests
    ├── __init__.py
    └── test_hoge.py

空の __init__.pyディレクトリに置きパッケージ化することで、ディレクトリの下のファイルを読めるようにしている。 これによって、new_project以下からtests以下のテストを実行すると、project_name以下のファイルをimport文で読み込むことができる。 tkmru/nao: No-meaning Assembly Omitter for IDA proでもこのようなディレクトリ構成にしている。

なお、unittestモジュールを使ったテストの実行は以下のコマンドでできる。

テストすべてを実行

python -m unittest discover

テストケースを指定

python -m unittest tests.ファイル名.クラス名

メソッドを指定

python -m unittest tests.ファイル名.クラス名.メソッド名

参考資料

近況

nao: No-meaning Assembly Omitter for IDA proを公開したところ、リポジトリめっちゃふぁぼられたり、unicornの作者にコメントされたり、unicornのshowcaseに載せてもらえたりして高まった。

ツイートされた!

リポジトリのissueでほめられた!!

cool project! · Issue #23 · tkmru/nao

showcaseに載せてもらった!!! f:id:TAKEmaru:20170128030311p:plain

Showcases – Unicorn – The ultimate CPU emulator

やっていきましょう。