Solution de tadhjao pour Shuffle Shop

web php crypto symmetric

4 janvier 2024

Description

Shuffle Shop est un chall proposé à FCSC 2020 en catégories Web/crypto. Cette épreuve va nous permettre de voir les bases du chiffrement AES ECB.

Recon

Tout d’abord analysons le site qui nous est proposé.

La première page du site semble juste être le sas d’entrée. Rien à signaler pour le code source non plus. Il nous est simplement dit que peu importe les actions que nous réaliserons plus tard, revenir à cette page les annulera.

Entrons.

Cette page semble être un magasin de potions. Avec :

  1. Un tableau de potions avec les différents prix de chacune ;
  2. Notre portefeuille -> “5000 FCSC-coins” ;
  3. Notre panier actuel.

Il y a deux boutons :

  1. Un pour melanger le magasin et changer les produits ;
  2. Un pour confirmer l’achat du panier.

Quand nous appuyons sur le bouton “Confirm cart” avec le Panier vide nous avons une box alerte qui nous affiche une erreur.

Le but du challenge semble donc de reussir à acheter la “Flag potion”. Le problème est que cette potion coûte 10 000 ‘FCSC-coins’.

Source code

Il y a plusieurs fonctions Javascript intéressantes dans le code source de cette page.

  • initialisation des variables. Chaque option a donc un identifiant “idx” spécifique qui la défini.
var potions = JSON.parse(
  '[{"idx":0,"name":"Health potion","price":10,"color":"#ff0000"},{"idx":1,"name":"Stamina potion","price":10,"color":"#33cc33"},{"idx":2,"name":"Strengh potion","price":15,"color":"#996633"},{"idx":3,"name":"Clerverness potion","price":15,"color":"#99ffcc"},{"idx":9,"name":"Flag potion","price":100000,"color":"#ffff00"},{"idx":4,"name":"Breath potion","price":25,"color":"#00ffcc"},{"idx":5,"name":"Invisible potion","price":30,"color":"#ffffcc"},{"idx":6,"name":"Fly potion","price":59,"color":"#ff99ff"},{"idx":7,"name":"Love potion","price":99,"color":"#ff0066"},{"idx":8,"name":"Fire-protection potion","price":67,"color":"#ff6600"}]',
);
var cart_enc = '';
var p1 = 0,
  p2 = 1,
  p3 = 2;
  • Fonction displayPotions. Cette fonction affiche trois potions dans le magasin.
function displayPotions() {
  document.getElementById('potion_name_1').innerHTML = potions[p1]['name'];
  document.getElementById('potion_name_1').style =
    'background-color:' + potions[p1]['color'];
  document.getElementById('potion_price_1').innerHTML = potions[p1]['price'];
  document.getElementById('potion_buy_1').onclick = function () {
    addItem(potions[p1]['idx']);
  };
  document.getElementById('potion_name_2').innerHTML = potions[p2]['name'];
  document.getElementById('potion_name_2').style =
    'background-color:' + potions[p2]['color'];
  document.getElementById('potion_price_2').innerHTML = potions[p2]['price'];
  document.getElementById('potion_buy_2').onclick = function () {
    addItem(potions[p2]['idx']);
  };
  document.getElementById('potion_name_3').innerHTML = potions[p3]['name'];
  document.getElementById('potion_name_3').style =
    'background-color:' + potions[p3]['color'];
  document.getElementById('potion_price_3').innerHTML = potions[p3]['price'];
  document.getElementById('potion_buy_3').onclick = function () {
    addItem(potions[p3]['idx']);
  };
}
  • Fonction addItem. qui permet d’ajouter une potion au panier. Cette fonction nous bloque pour ajouter notre “Flag potion” au panier.
function addItem(idx) {
  var xhr_addItem = new XMLHttpRequest();
  xhr_addItem.open('GET', 'cart.php?add&idx=' + idx, true);
  xhr_addItem.onload = function () {
    if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
      if (this.responseText === 'not enough FCSC-coins')
        alert(
          'Le montant de votre panier ne peut pas dépasser la quantité de FCSC-coins que vous possédez !',
        );
      else if (this.responseText === 'potion added') refreshCart();
    }
  };
  xhr_addItem.send();
}
  • Fonction refreshCart. qui va mettre à jour les potions du panier. la requête cart.php?refresh retourne le JSON en clair.
{
  "total": 0,
  "Health potion": 0,
  "Stamina potion": 0,
  "Strengh potion": 0,
  "Clerverness potion": 0,
  "Flag potion": 0,
  "Breath potion": 0,
  "Invisible potion": 0,
  "Fly potion": 0,
  "Love potion": 0,
  "Fire-protection potion": 0
}

la requête cart.php?refreshEnc retourne le JSON chiffré et le stocke dans la variale cart_enc.

d6753c1c1c0fea6dc5acb87141896dc2d04f5ef584029a718b779a05967bae0b58a831353216845122eb26a951ac29eee049440c06e50aec529fb14bdbbe81d4e0af636931ea0d18f030c8a9453679a17fca8a0ff4f5cf1c34812e251a99f5ad3c7e45712b14444d470cfe46c6f9b27a53d2da2e395063a67047ecc49dec5aba568c3df55964d7d3bab704e701831c3429e83a4493fd102cd9b49d2ada8b68d1a04cb37d89749b3254e1933360286b8fa0ca8a3aa1e227b75d2a0e3baa91aac35cc148da97c5cdd3cde4a35494765949
function refreshCart() {
  var xhr_refreshCart = new XMLHttpRequest();
  xhr_refreshCart.open('GET', 'cart.php?refresh', true);
  xhr_refreshCart.onload = function () {
    if (this.readyState === XMLHttpRequest.DONE && this.status === 200)
      document.getElementById('cart').innerHTML = this.responseText;
  };
  var xhr_refreshCartEnc = new XMLHttpRequest();
  xhr_refreshCartEnc.open('GET', 'cart.php?refreshEnc', true);
  xhr_refreshCartEnc.onload = function () {
    if (this.readyState === XMLHttpRequest.DONE && this.status === 200)
      cart_enc = this.responseText;
  };
  xhr_refreshCart.send();
  xhr_refreshCartEnc.send();
}
  • Fonction ValidateCart. qui va valider le panier actuel avec la variable cart_enc.
function validateCart() {
  refreshCart();
  var xhr_validateCart = new XMLHttpRequest();
  xhr_validateCart.open('GET', 'cart.php?validate&cart=' + cart_enc, true);
  xhr_validateCart.onload = function () {
    if (this.readyState == XMLHttpRequest.DONE && this.status === 200)
      alert(this.responseText);
  };
  xhr_validateCart.send();
}

Le but maintenant semble clair modifier la valeur de cart_enc par du json_encrypted avec le paramètre de “Flag potion” à 1.

Chiffrement AES ECB

  • Le chiffrement d’un panier vide est toujours le même. On peut donc supposer que la clef est une constante et ne change pas.
  • De plus quand nous modifions la quantité d’une potion seul un bloc change, on peut donc supposer à de AES ECB.

| AES always uses a block size of 128 bits (16 bytes) source

Sachant que c’est de l’hexa on peut découper le json_encrypted par bloc de 32 caractères, soit 16 bytes en hexa.

>>>len('ed01c983b07a4706eb938131297f4a56d04f5ef584029a718b779a05967bae0b58a831353216845122eb26a951ac29eee049440c06e50aec529fb14bdbbe81d4cd1df04eb05e53626848b88121bb35747fca8a0ff4f5cf1c34812e251a99f5ad3c7e45712b14444d470cfe46c6f9b27a53d2da2e395063a67047ecc49dec5aba568c3df55964d7d3bab704e701831c3429e83a4493fd102cd9b49d2ada8b68d1a04cb37d89749b3254e1933360286b8fa0ca8a3aa1e227b75d2a0e3baa91aac35cc148da97c5cdd3cde4a35494765949')
416 # en hexa donc

>>> 416/32
13.0

Il y a donc 13 blocs de 16 bytes. Dans le json ça donne des blocs de 16 caractères car l’ascii est encodé sur 1 bytes. Voici les Blocs :

>>>for i in range (13) :
...     print('{"total":15,"Health potion":0,"Stamina potion":0,"Strengh potion":0,"Clerverness potion":1,"Flag potion":0,"Breath potion":0,"Invisible potion":0,"Fly potion":0,"Love potion":0,"Fire-protection potion":0}'[16*i:][:16])
...
{"total":15,"Hea
lth potion":0,"S
tamina potion":0
,"Strengh potion
":0,"Clerverness
 potion":0,"Flag <---  | # les deux blocs se ressemblent on pourrait les switch
 potion":0,"Brea <---
th potion":0,"In
visible potion":
0,"Fly potion":0
,"Love potion":0
,"Fire-protectio
n potion":0}

Ligne 9 et 10 les blocs se ressemble il suffirait donc de remplacer la ligne 10 (potion":0,"Brea) par la ligne 9 (potion":1,"Flag). Je vais donc acheter une potion Clerverness et coller le bloc a 1 dans Flag potion

#!/bin/python3

## --- DEFINITION ---

def poc():
    # cart JSon
    json = '{"total":15,"Health potion":0,"Stamina potion":0,"Strengh potion":0,"Clerverness potion":1,"Flag potion":0,"Breath potion":0,"Invisible potion":0,"Fly potion":0,"Love potion":0,"Fire-protection potion":0}'
    # JSon encrypted
    jsonEnc = "d6753c1c1c0fea6dc5acb87141896dc2d04f5ef584029a718b779a05967bae0b58a831353216845122eb26a951ac29eee049440c06e50aec529fb14bdbbe81d4e0af636931ea0d18f030c8a9453679a17fca8a0ff4f5cf1c34812e251a99f5ad3c7e45712b14444d470cfe46c6f9b27a53d2da2e395063a67047ecc49dec5aba568c3df55964d7d3bab704e701831c3429e83a4493fd102cd9b49d2ada8b68d1a04cb37d89749b3254e1933360286b8fa0ca8a3aa1e227b75d2a0e3baa91aac35cc148da97c5cdd3cde4a35494765949"

    # Knowing that the json is encrypted using 32 bits Block
    # print the json decoded blocks
    print("\njson blocks =")
    for i in range(int(len(jsonEnc)/32)):
        print(json[16*i:16*(i+1)])

    print("\n#------RESULT--------#")
    print()
    json_result = json[16*0:16*6]+json[16*5:16*6]+json[16*7:]
    print(f"json = {json_result}")
    print()
    json_enc_result = jsonEnc[32*0:32*6] + \
        jsonEnc[32*5:32*6]+jsonEnc[32*7:]
    print(f"encrypted json = {json_enc_result}")


## --- UTILISATION ---
if __name__ == "__main__":
    poc()

ce code retourne


json blocks =
{"total":15,"Hea
lth potion":0,"S
tamina potion":0
,"Strengh potion
":0,"Clerverness
 potion":1,"Flag
 potion":0,"Brea
th potion":0,"In
visible potion":
0,"Fly potion":0
,"Love potion":0
,"Fire-protectio
n potion":0}

#------RESULT--------#

json = {"total":15,"Health potion":0,"Stamina potion":0,"Strengh potion":0,"Clerverness potion":1,"Flag potion":1,"Flagth potion":0,"Invisible potion":0,"Fly potion":0,"Love potion":0,"Fire-protection potion":0}

encrypted json = d6753c1c1c0fea6dc5acb87141896dc2d04f5ef584029a718b779a05967bae0b58a831353216845122eb26a951ac29eee049440c06e50aec529fb14bdbbe81d4e0af636931ea0d18f030c8a9453679a17fca8a0ff4f5cf1c34812e251a99f5ad7fca8a0ff4f5cf1c34812e251a99f5ad53d2da2e395063a67047ecc49dec5aba568c3df55964d7d3bab704e701831c3429e83a4493fd102cd9b49d2ada8b68d1a04cb37d89749b3254e1933360286b8fa0ca8a3aa1e227b75d2a0e3baa91aac35cc148da97c5cdd3cde4a35494765949

Nous avons plus qu’à écraser la valeur de cart_enc avec notre nouveau encrypted json

Flag

FCSC{c9582322dfa2997384b7d38b73b5a80a69374d5f3f616c431e98823172f5c7df}