我的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 } }
查詢結果如下:

查詢出來的數據即UserModel.query.all()
的執行結果。但如果我要在瀏覽器實現只查詢id=2的用戶的資訊是做不到,因為後端python程式碼里沒有寫,也就是說,只有程式碼里寫了介面,定義了相應的schema,才能通過GraphQL查詢出對應結果,所以並不是通過GraphQL就能查詢獲取資料庫所有數據。
GraphQL的安全問題
如果看過p牛在先知大會上的分享——《攻擊GraphQL》,會對GraphQL的安全問題有一個全面的認識。這裡,在GraphQL安全問題研究上,我並沒有新的發現,可以算是個人的學習筆記以及自己的一些理解。
讓我們先回顧一下p牛總結的問題。

這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 {   editProfile(name:"hac ker", age: 5) {     n ame     age   } }" /> <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 } } }

得到User,再查詢User的欄位。
{ __type(name: "User") { name fields { name type { name } } } }

在實際挖洞過程中,就可判斷是否存在敏感欄位,如用戶的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}}}}
測試效果如下,只需一次請求就可列出對象和欄位。

工具的效果如下:

在實際使用過程中,常常需要修改腳本,修改post的參數名稱以及返回結果的參數名,使之與實際請求結果相對應,若有登陸態校驗,則還需要添加cookie,這一塊腳本有待改進,有時間就造一個順手的輪子。
至於嵌套查詢導致ddos,在實際挖掘中暫時沒有碰到,具體介紹可以參考freebuf的《GraphQL安全指北》這篇文章。
參考
- https://xzfile.aliyuncs.com/upload/zcon/2018/7_%E6%94%BB%E5%87%BBGraphQL_phithon.pdf
- https://www.freebuf.com/articles/web/184040.html