GraphQl Injection
in 各种姿势WriteUP with 0 comment

GraphQl Injection

in 各种姿势WriteUP with 0 comment

前段时间试着打了几场ctftime上的比赛,遇到了很多以前没有看到过的东西,其中刚拿到最蒙逼的就是graphql。比赛出现的频次还挺高,所以研究研究记录一下。

涉及到的题目有:HITB CTF 2017-Blog SEC-T 2017-Dark Market

GraphQl是什么?

GraphQL 可以看做一条以通用基础业务数据模型为基础、将后端服务和前端页面联系在一起的东西。其实就是放在页面数据和服务端中间做数据处理的东西。

GraphQl能实现的最重要的功能,就是在一个数据接口,执行不同的查询,返回不同的数据,比如以下例子:

a 查询对应的响应:

{
  "user" : {
    "id": 3500401,
    "name": "Jing Chen",
    "isViewerFriend": true
  }
}

b 查询对应的响应:

{
  "user" : {
    "name": "Jing Chen",
    "profilePicture": {
      "uri": "http: //someurl.cdn/pic.jpg",、
      "width": 50,
      "height": 50
    }
  }
}

这两个查询是传入同一个数据接口的,只需要改变查询内容,前端就能定制服务器返回的响应内容,这就是 GraphQL 的客户端指定查询(Client Specified Queries)。

如何使用

在这里可以学习GraphQl的具体操作:

learn-GraphQl

题目中的操作

HITB CTF 2017-Blog

这道题在查询时可以看到这个传参query={ itemsForAuthor(id:"aYT0x") { id title } }

其实就是

query=
{
    itemsForAuthor(id:"aYT0x") {
        id
        title
    } 
}

这里的"aYT0x"其中的"YT0x"base64一下就是a=1,因此构建一个原来一个不存在的id,比如query={ itemsForAuthor(id:"aYT0xaaaa") { id title } }就会产生报错

{"errors":[{"message":"(sqlite3.ProgrammingError) You must not use 8-bit bytestrings unless you use a text_factory that can interpret 8-bit bytestrings (like text_factory = str). It is highly recommended that you instead just switch your application to Unicode strings. [SQL: u'SELECT author.id AS author_id, author.name AS author_name \\nFROM author \\nWHERE author.id = ?'] [parameters: ('1i\\xa6\\x9a',)]","locations":[{"column":4,"line":1}]}],"data":{"itemsForAuthor":null}}

报错发现是sqLite数据库,在base64编码一下,就可以愉快的进行注入了。

SEC-T 2017-Dark Market

个人感觉这道题对GraqhQl的考察要透彻一点,毕竟上道题没有表现出客户端指定查询(Client Specified Queries),其实还是一个用了GraphQl的普通注入题,后面这道感觉思路要更厉害一点。(导致即使查询可控变多了,虽然对GraphQl有一点了解,但是仍然没有做出来)

这道题是一道商店的题,开头有100块,但是要买110块的东西。一般拿到这种买东西的题,一般就是条件竞争、注入得高级账号、进制转换等方法。

但是做这道题的时候蒙逼了,只有一个没有注入漏洞的登录页面、一个买东西的页面、一个可以根据订货码来查询所定货物的页面,其中最后一个页面用了GraphyQl。这道题里面根本没有可以加钱的功能,也没有拿到高级账号的办法。

唯一突破口是在查询订货码的地方,可以自定义查询,可以查询订货码、id、商品描述等商品信息,各种猜解发现查询不出什么敏感信息,后来只有放弃。

后来看到一个老外的writeup,简直666.

大佬的writeup

这里对里面的一些东西讲解一下。

首先这道题是可以客户端指定查询的,但是我不知道有什么查询姿势,大佬用了一个很厉害的payload:

payload

这个payload不是专门对于题目环境弄的,而是可以在各个GraphQl环境中使用,(比如上面那个GraphQl教程的环境),把各个query、mutation、subscription查询出来。

writeup中查询出的database_schema.json

在这个database_schema.json中,可以看到根据订单号和userid删除订单,退回花的钱到现有账户,这样再注册一个账户进行之前账户的退款操作,这样总钱数就可以大于110,买到flag了。

那么如何从database_schema.json中找到自己想要的query和mutation呢,请看下面。

database_schema.json剖析

首先拿到1540行的东西肯定是蒙蔽的,我们先从头看起。

因为具体的代码较长,所以后门没有贴完,说的东西很多是在省略号里面的,建议结合上面连接给的database_schema.json一起看。

首先我们知道,在GraphQl中定义操作,有query、mutation等,这些在文件最前面有定义:

{
  "data": {
    "__schema": {
      "queryType": {
        "name": "Query"
      },
      "mutationType": {
        "name": "MyMutations"
      },

这里规定了queryType和mutationType的name,全局搜索这个name,既可看到对Query的定义。

下面这一段就是对Query的定义:

"types": [
        {
          "kind": "OBJECT",
          "name": "Query",
          "description": null,
          "fields": [
            .....
            ]
          }
         ]

在fields中就是查询方法,findProduct和findOrder。

这里拿findProduct来讲一下:

            {
              "name": "findProduct",
              "description": null,
              "args": [
                {
                  "name": "productId",
                  "description": null,
                  "type": {
                    "kind": "SCALAR",
                    "name": "Int",
                    "ofType": null
                  },
                  "defaultValue": null
                }
              ],
              "type": {
                "kind": "OBJECT",
                "name": "Products",
                "ofType": null
              },
              "isDeprecated": false,
              "deprecationReason": null
            },

这里除了可以看出是通过参数productId查询之外,主要是在type中:

            "type": {
                "kind": "OBJECT",
                "name": "Products",
                "ofType": null
              },

这里的"name": "Products"规定了可以查询出来的东西,全局搜索Products可以找到:

        {
          "kind": "OBJECT",
          "name": "Products",
          "description": null,
          "fields": [
          .....
          ]
        }

这部分在69行到149行, "fields":[.....] 里面就是可以查询的参数信息,有id、name、desc、image、price。用同样的方法就可以看到在findOrder中可以查询id、productId、userId、orderId、user (223行到303行)。

这里的user比较有用:

        {
              "name": "user",
              "description": null,
              "args": [],
              "type": {
                "kind": "OBJECT",
                "name": "Users",
                "ofType": null
              },
              "isDeprecated": false,
              "deprecationReason": null
            }

可以看到type里又有一个 "name": "Users",说明这里查询user之后里面可以嵌套一个子查询,子查询的定义可以通过寻找"name": "Users"的查询方法找到。

        {
          "kind": "OBJECT",
          "name": "Users",
          "description": null,
          "fields": [
          ....
          ]
        }

这里fields里面定义了可以查询id、name等。所以可以这样定义payload:

query= {
    findOrder (orderId: "9fcdcedb-f15f-4759-b6d4-dfbe2419a0fc")
    {
        user
        {
            id
            name
        }
    }
}

然后我们就可以查询出userid了,这个username可以带入deleteOrder进行删除订单获得钱的操作。关于deleteOrder的定义,在database_schema.json中查找的方式和上面差不多。就不再多做赘述了。

本文仅一点拙见,错误情大佬们指出。

参考/引用除文中已给出链接外:
云栖社区

Responses