這篇文章給大家介紹如何進(jìn)行GraphQL的分析,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。
成都創(chuàng)新互聯(lián)公司主要從事成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作、外貿(mào)網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)鞏義,10年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專(zhuān)業(yè),歡迎來(lái)電咨詢(xún)建站服務(wù):13518219792
下面以GraphQL中一些容易讓初學(xué)者與典型Web API(為了便于理解,下文以目前流行的RESTful API為例代指)混淆或錯(cuò)誤理解的概念特性進(jìn)行內(nèi)容劃分,由我從安全的角度拋出GraphQL應(yīng)該注意的幾點(diǎn)安全問(wèn)題,而@圖南則會(huì)更多的從開(kāi)發(fā)的角度給出他在實(shí)際使用過(guò)程中總結(jié)的最佳實(shí)踐。
另外,需要提前聲明的是,文中我使用的后端開(kāi)發(fā)語(yǔ)言是Go,@圖南使用的是Node.js,前端統(tǒng)一為React(GraphQL客戶(hù)端為Apollo),請(qǐng)大家自行消化。
Let’s Go!
有些同學(xué)是不是根本沒(méi)聽(tīng)過(guò)這個(gè)玩意?我們先來(lái)看看正在使用它的大客戶(hù)們:
是不是值得我們花幾分鐘對(duì)它做個(gè)簡(jiǎn)單的了解了?XD
簡(jiǎn)單的說(shuō),GraphQL是由Facebook創(chuàng)造并開(kāi)源的一種用于API的查詢(xún)語(yǔ)言。
再引用官方文案來(lái)幫助大家理解一下GraphQL的特點(diǎn):
1.請(qǐng)求你所要的數(shù)據(jù),不多不少
向你的API發(fā)出一個(gè)GraphQL請(qǐng)求就能準(zhǔn)確獲得你想要的數(shù)據(jù),不多不少。GraphQL查詢(xún)總是返回可預(yù)測(cè)的結(jié)果。使用GraphQL的應(yīng)用可以工作得又快又穩(wěn),因?yàn)榭刂茢?shù)據(jù)的是應(yīng)用,而不是服務(wù)器。
2.獲取多個(gè)資源,只用一個(gè)請(qǐng)求
GraphQL查詢(xún)不僅能夠獲得資源的屬性,還能沿著資源間引用進(jìn)一步查詢(xún)。典型的RESTful API請(qǐng)求多個(gè)資源時(shí)得載入多個(gè)URL,而GraphQL可以通過(guò)一次請(qǐng)求就獲取你應(yīng)用所需的所有數(shù)據(jù)。
3.描述所有的可能,類(lèi)型系統(tǒng)
GraphQL基于類(lèi)型和字段的方式進(jìn)行組織,而非入口端點(diǎn)。你可以通過(guò)一個(gè)單一入口端點(diǎn)得到你所有的數(shù)據(jù)能力。GraphQL使用類(lèi)型來(lái)保證應(yīng)用只請(qǐng)求可能的數(shù)據(jù),還提供了清晰的輔助性錯(cuò)誤信息。
用于描述接口的抽象數(shù)據(jù)模型,有Scalar(標(biāo)量)和Object(對(duì)象)兩種,Object由Field組成,同時(shí)Field也有自己的Type。
用于描述接口獲取數(shù)據(jù)的邏輯,類(lèi)比RESTful中的每個(gè)獨(dú)立資源URI。
用于描述接口的查詢(xún)類(lèi)型,有Query(查詢(xún))、Mutation(更改)和Subscription(訂閱)三種。
用于描述接口中每個(gè)Query的解析邏輯,部分GraphQL引擎還提供Field細(xì)粒度的Resolver(想要詳細(xì)了解的同學(xué)請(qǐng)閱讀GraphQL官方文檔)。
GraphQL沒(méi)有過(guò)多依賴(lài)HTTP協(xié)議,它有一套自己的解析引擎來(lái)幫助前后端使用GraphQL查詢(xún)語(yǔ)法。同時(shí)它是單路由形態(tài),查詢(xún)內(nèi)容完全根據(jù)前端請(qǐng)求對(duì)象和字段而定,前后端分離較明顯。
用一張圖來(lái)對(duì)比一下:
@gyyyy:
前面說(shuō)到,GraphQL多了一個(gè)中間層對(duì)它定義的查詢(xún)語(yǔ)言進(jìn)行語(yǔ)法解析執(zhí)行等操作,與RESTful這種充分利用HTTP協(xié)議本身特性完成聲明使用的API設(shè)計(jì)不同,Schema、Resolver等種種定義會(huì)讓開(kāi)發(fā)者對(duì)它的存在感知較大,間接的增加了對(duì)它理解的復(fù)雜度,加上它本身的單路由形態(tài),很容易導(dǎo)致開(kāi)發(fā)者在不完全了解其特性和內(nèi)部運(yùn)行機(jī)制的情況下,錯(cuò)誤實(shí)現(xiàn)甚至忽略API調(diào)用時(shí)的授權(quán)鑒權(quán)行為。
在官方的描述中,GraphQL和RESTful API一樣,建議開(kāi)發(fā)者將授權(quán)邏輯委托給業(yè)務(wù)邏輯層:
在沒(méi)有對(duì)GraphQL中各個(gè)Query和Mutation做好授權(quán)鑒權(quán)時(shí),同樣可能會(huì)被攻擊者非法請(qǐng)求到一些非預(yù)期接口,執(zhí)行高危操作,如查詢(xún)所有用戶(hù)的詳細(xì)信息:
query GetAllUsers {
users {
_id
username
password
idCard
mobilePhone
email
}
}
這幾乎是使用任何API技術(shù)都無(wú)法避免的一個(gè)安全問(wèn)題,因?yàn)樗cAPI本身的職能并沒(méi)有太大的關(guān)系,API不需要背這個(gè)鍋,但由此問(wèn)題帶來(lái)的并發(fā)癥卻不容小覷。
對(duì)于這種未授權(quán)或越權(quán)訪(fǎng)問(wèn)漏洞的挖掘利用方式,大家一定都很清楚了,一般情況下我們都會(huì)期望盡可能獲取到比較全量的API來(lái)進(jìn)行進(jìn)一步的分析。在RESTful API中,我們可能需要通過(guò)代理、爬蟲(chóng)等技術(shù)來(lái)抓取API。而隨著Web 2.0時(shí)代的到來(lái),各種強(qiáng)大的前端框架、運(yùn)行時(shí)DOM事件更新等技術(shù)使用頻率的增加,更使得我們不得不動(dòng)用到如Headless等技術(shù)來(lái)提高對(duì)API的獲取覆蓋率。
但與RESTful API不同的是,GraphQL自帶強(qiáng)大的內(nèi)省自檢機(jī)制,可以直接獲取后端定義的所有接口信息。比如通過(guò)__schema查詢(xún)所有可用對(duì)象:
{ __schema { types { name } } }
通過(guò)__type查詢(xún)指定對(duì)象的所有字段:
{ __type(name: "User") { name fields { name type { name } } } }
這里我通過(guò)graphql-go/graphql的源碼簡(jiǎn)單分析一下GraphQL的解析執(zhí)行流程和內(nèi)省機(jī)制,幫助大家加深理解:
1.GraphQL路由節(jié)點(diǎn)在拿到HTTP的請(qǐng)求參數(shù)后,創(chuàng)建Params對(duì)象,并調(diào)用Do()完成解析執(zhí)行操作返回結(jié)果:
params := graphql.Params{ Schema: *h.Schema, RequestString: opts.Query, VariableValues: opts.Variables, OperationName: opts.OperationName, Context: ctx, } result := graphql.Do(params)
2.調(diào)用Parser()把params.RequestString轉(zhuǎn)換為GraphQL的AST文檔后,將AST和Schema一起交給ValidateDocument()進(jìn)行校驗(yàn)(主要校驗(yàn)是否符合Schema定義的參數(shù)、字段、類(lèi)型等)。
3.代入AST重新封裝ExecuteParams對(duì)象,傳入Execute()中開(kāi)始執(zhí)行當(dāng)前GraphQL語(yǔ)句。
具體的執(zhí)行細(xì)節(jié)就不展開(kāi)了,但是我們關(guān)心的內(nèi)省去哪了?原來(lái)在GraphQL引擎初始化時(shí),會(huì)定義三個(gè)帶缺省Resolver的元字段:
SchemaMetaFieldDef = &FieldDefinition{ // __schema:查詢(xún)當(dāng)前類(lèi)型定義的模式,無(wú)參數(shù) Name: "__schema", Type: NewNonNull(SchemaType), Description: "Access the current type schema of this server.", Args: []*Argument{}, Resolve: func(p ResolveParams) (interface{}, error) { return p.Info.Schema, nil }, } TypeMetaFieldDef = &FieldDefinition{ // __type:查詢(xún)指定類(lèi)型的詳細(xì)信息,字符串類(lèi)型參數(shù)name Name: "__type", Type: TypeType, Description: "Request the type information of a single type.", Args: []*Argument{ { PrivateName: "name", Type: NewNonNull(String), }, }, Resolve: func(p ResolveParams) (interface{}, error) { name, ok := p.Args["name"].(string) if !ok { return nil, nil } return p.Info.Schema.Type(name), nil }, } TypeNameMetaFieldDef = &FieldDefinition{ // __typename:查詢(xún)當(dāng)前對(duì)象類(lèi)型名稱(chēng),無(wú)參數(shù) Name: "__typename", Type: NewNonNull(String), Description: "The name of the current Object type at runtime.", Args: []*Argument{}, Resolve: func(p ResolveParams) (interface{}, error) { return p.Info.ParentType.Name(), nil }, }
當(dāng)resolveField()解析到元字段時(shí),會(huì)調(diào)用其缺省Resolver,觸發(fā)GraphQL的內(nèi)省邏輯。
GraphQL為了考慮接口在版本演進(jìn)時(shí)能夠向下兼容,還有一個(gè)對(duì)于應(yīng)用開(kāi)發(fā)而言比較友善的特性:『API演進(jìn)無(wú)需劃分版本』。
由于GraphQL是根據(jù)前端請(qǐng)求的字段進(jìn)行數(shù)據(jù)回傳,后端Resolver的響應(yīng)包含對(duì)應(yīng)字段即可,因此后端字段擴(kuò)展對(duì)前端無(wú)感知無(wú)影響,前端增加查詢(xún)字段也只要在后端定義的字段范圍內(nèi)即可。同時(shí)GraphQL也為字段刪除提供了『廢棄』方案,如Go的graphql包在字段中增加DeprecationReason屬性,Apollo的@deprecated標(biāo)識(shí)等。
這種特性非常方便的將前后端進(jìn)行了分離,但如果開(kāi)發(fā)者本身安全意識(shí)不夠強(qiáng),設(shè)計(jì)的API不夠合理,就會(huì)埋下了很多安全隱患。我們用開(kāi)發(fā)項(xiàng)目中可能會(huì)經(jīng)常遇到的需求場(chǎng)景來(lái)重現(xiàn)一下。
假設(shè)小明在應(yīng)用中已經(jīng)定義好了查詢(xún)用戶(hù)基本信息的API:
graphql.Field{ Type: graphql.NewObject(graphql.ObjectConfig{ Name: "User", Description: "用戶(hù)信息", Fields: graphql.Fields{ "_id": &graphql.Field{Type: graphql.Int}, "username": &graphql.Field{Type: graphql.String}, "email": &graphql.Field{Type: graphql.String}, }, }), Args: graphql.FieldConfigArgument{ "username": &graphql.ArgumentConfig{Type: graphql.String}, }, Resolve: func(params graphql.ResolveParams) (result interface{}, err error) { // ... }, }
小明獲得新的需求描述,『管理員可以查詢(xún)指定用戶(hù)的詳細(xì)信息』,為了方便(也經(jīng)常會(huì)為了方便),于是在原有接口上新增了幾個(gè)字段:
graphql.Field{ Type: graphql.NewObject(graphql.ObjectConfig{ Name: "User", Description: "用戶(hù)信息", Fields: graphql.Fields{ "_id": &graphql.Field{Type: graphql.Int}, "username": &graphql.Field{Type: graphql.String}, "password": &graphql.Field{Type: graphql.String}, // 新增 用戶(hù)密碼 字段 "idCard": &graphql.Field{Type: graphql.String}, // 新增 用戶(hù)身份證號(hào) 字段 "mobilePhone": &graphql.Field{Type: graphql.String}, // 新增 用戶(hù)手機(jī)號(hào) 字段 "email": &graphql.Field{Type: graphql.String}, }, }), Args: graphql.FieldConfigArgument{ "username": &graphql.ArgumentConfig{Type: graphql.String}, }, Resolve: func(params graphql.ResolveParams) (result interface{}, err error) { // ... }, }
如果此時(shí)小明沒(méi)有在字段細(xì)粒度上進(jìn)行權(quán)限控制(也暫時(shí)忽略其他權(quán)限問(wèn)題),攻擊者可以輕易的通過(guò)內(nèi)省發(fā)現(xiàn)這幾個(gè)本不該被普通用戶(hù)查看到的字段,并構(gòu)造請(qǐng)求進(jìn)行查詢(xún)(實(shí)際開(kāi)發(fā)中也經(jīng)常容易遺留一些測(cè)試字段,在GraphQL強(qiáng)大的內(nèi)省機(jī)制面前這無(wú)疑是非常危險(xiǎn)的。如果熟悉Spring自動(dòng)綁定漏洞的同學(xué),也會(huì)發(fā)現(xiàn)它們之間有一部分相似的地方)。
故事繼續(xù),當(dāng)小明發(fā)現(xiàn)這種做法欠妥時(shí),他決定廢棄這幾個(gè)字段:
// ... "password": &graphql.Field{Type: graphql.String, DeprecationReason: "安全性問(wèn)題"}, "idCard": &graphql.Field{Type: graphql.String, DeprecationReason: "安全性問(wèn)題"}, "mobilePhone": &graphql.Field{Type: graphql.String, DeprecationReason: "安全性問(wèn)題"}, // ...
接著,他又用上面的__type做了一次內(nèi)省,很好,廢棄字段查不到了,通知前端回滾查詢(xún)語(yǔ)句,問(wèn)題解決,下班回家(GraphQL的優(yōu)勢(shì)立刻凸顯出來(lái))。
熟悉安全攻防套路的同學(xué)都知道,很多的攻擊方式(尤其在Web安全中)都是利用了開(kāi)發(fā)、測(cè)試、運(yùn)維的知識(shí)盲點(diǎn)(如果你想問(wèn)這些盲點(diǎn)的產(chǎn)生原因,我只能說(shuō)是因?yàn)檎G闆r下根本用不到,所以不深入研究基本不會(huì)去刻意關(guān)注)。如果開(kāi)發(fā)者沒(méi)有很仔細(xì)的閱讀GraphQL官方文檔,特別是內(nèi)省這一章節(jié)的內(nèi)容,就可能不知道,通過(guò)指定includeDeprecated參數(shù)為true,__type仍然可以將廢棄字段暴露出來(lái):
{ __type(name: "User") { name fields(includeDeprecated: true) { name isDeprecated type { name } } } }
而且由于小明沒(méi)有對(duì)Resolver做修改,廢棄字段仍然可以正常參與查詢(xún)(兼容性惹的禍),故事結(jié)束。
正如p牛所言,『GraphQL是一門(mén)自帶文檔的技術(shù)』??蛇@也使得授權(quán)鑒權(quán)環(huán)節(jié)一旦出現(xiàn)紕漏,GraphQL背后的應(yīng)用所面臨的安全風(fēng)險(xiǎn)會(huì)比典型Web API大得多。
@圖南:
GraphQL并沒(méi)有規(guī)定任何身份認(rèn)證和權(quán)限控制的相關(guān)內(nèi)容,這是個(gè)好事情,因?yàn)槲覀兛梢愿`活的在應(yīng)用中實(shí)現(xiàn)各種粒度的認(rèn)證和權(quán)限。但是,在我的開(kāi)發(fā)過(guò)程中發(fā)現(xiàn),初學(xué)者經(jīng)常會(huì)忽略GraphQL的認(rèn)證,會(huì)寫(xiě)出一些裸奔的接口或者無(wú)效認(rèn)證的接口。那么我就在這里詳細(xì)說(shuō)一下GraphQL的認(rèn)證方式。
如果后端本身支持RESTful或者有專(zhuān)門(mén)的認(rèn)證服務(wù)器,可以修改少量代碼就能實(shí)現(xiàn)GraphQL接口的認(rèn)證。這種認(rèn)證方式是最通用同時(shí)也是官方比較推薦的。
以JWT認(rèn)證為例,將整個(gè)GraphQL路由加入JWT認(rèn)證,開(kāi)放兩個(gè)RESTful接口做登錄和注冊(cè)用,登錄和注冊(cè)的具體邏輯不再贅述,登錄后返回JWT Token:
設(shè)置完成后,請(qǐng)求GraphQL接口需要先進(jìn)行登錄操作,然后在前端配置好認(rèn)證請(qǐng)求頭來(lái)訪(fǎng)問(wèn)GraphQL接口,以curl代替前端請(qǐng)求登錄RESTful接口:
curl -X POST http://localhost:4000/login -H 'cache-control: no-cache' -H 'content-type: application/x-www-form-urlencoded' -d 'username=user1&password=123456' {"message":"登錄成功","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7Il9pZCI6IjViNWU1NDcwN2YyZGIzMDI0YWJmOTY1NiIsInVzZXJuYW1lIjoidXNlcjEiLCJwYXNzd29yZCI6IiQyYSQwNSRqekROOGFQbEloRzJlT1A1ZW9JcVFPRzg1MWdBbWY0NG5iaXJaM0Y4NUdLZ3pVL3lVNmNFYSJ9LCJleHAiOjE1MzI5MTIyOTEsImlhdCI6MTUzMjkwODY5MX0.Uhd_EkKUEDkI9cdnYlOC7wSYZdYLQLFCb01WhSBeTpY"}
以GraphiQL(GraphQL開(kāi)發(fā)者調(diào)試工具,大部分GraphQL引擎自帶,默認(rèn)開(kāi)啟)代替前端請(qǐng)求GraphQL接口,要先設(shè)置認(rèn)證請(qǐng)求頭:
如果GraphQL后端只能支持GraphQL不能支持RESTful,或者全部請(qǐng)求都需要使用GraphQL,也可以用GraphQL構(gòu)造login接口提供Token。
如下面例子,構(gòu)造login的Query Schema, 由返回值中攜帶Token:
type Query { login( username: String! password: String! ): LoginMsg } type LoginMsg { message: String token: String }
在Resolver中提供登錄邏輯:
import bcrypt from 'bcryptjs'; import jsonwebtoken from 'jsonwebtoken'; export const login = async (_, args, context) => { const db = await context.getDb(); const { username, password } = args; const user = await db.collection('User').findOne({ username: username }); if (await bcrypt.compare(password, user.password)) { return { message: 'Login success', token: jsonwebtoken.sign({ user: user, exp: Math.floor(Date.now() / 1000) + (60 * 60), // 60 seconds * 60 minutes = 1 hour }, 'your secret'), }; } }
登錄成功后,我們繼續(xù)把Token設(shè)置在請(qǐng)求頭中,請(qǐng)求GraphQL的其他接口。這時(shí)我們要對(duì)ApolloServer進(jìn)行如下配置:
const server = new ApolloServer({ typeDefs: schemaText, resolvers: resolverMap, context: ({ ctx }) => { const token = ctx.req.headers.authorization || ''; const user = getUser(token); return { ...user, ...ctx, ...app.context }; }, });
實(shí)現(xiàn)getUser函數(shù):
const getUser = (token) => { let user = null; const parts = token.split(' '); if (parts.length === 2) { const scheme = parts[0]; const credentials = parts[1]; if (/^Bearer$/i.test(scheme)) { token = credentials; try { user = jwt.verify(token, JWT_SECRET); console.log(user); } catch (e) { console.log(e); } } } return user }
配置好ApolloServer后,在Resolver中校驗(yàn)user:
import { ApolloError, ForbiddenError, AuthenticationError } from 'apollo-server'; export const blogs = async (_, args, context) => { const db = await context.getDb(); const user = context.user; if(!user) { throw new AuthenticationError("You must be logged in to see blogs"); } const { blogId } = args; const cursor = {}; if (blogId) { cursor['_id'] = blogId; } const blogs = await db .collection('blogs') .find(cursor) .sort({ publishedAt: -1 }) .toArray(); return blogs; }
這樣我們即完成了通過(guò)GraphQL認(rèn)證的主要代碼。繼續(xù)使用GraphiQL代替前端請(qǐng)求GraphQL登錄接口:
得到Token后,設(shè)置Token到請(qǐng)求頭 完成后續(xù)操作。如果請(qǐng)求頭失效,則得不到數(shù)據(jù):
在認(rèn)證過(guò)程中,我們只是識(shí)別請(qǐng)求是不是由合法用戶(hù)發(fā)起。權(quán)限控制可以讓我們?yōu)橛脩?hù)分配不同的查看權(quán)限和操作權(quán)限。如上,我們已經(jīng)將user放入GraphQL Sever的context中。而context的內(nèi)容又是我們可控的,因此context中的user既可以是{ loggedIn: true },又可以是{ user: { _id: 12345, roles: ['user', 'admin'] } }。大家應(yīng)該知道如何在Resolver中實(shí)現(xiàn)權(quán)限控制了吧,簡(jiǎn)單的舉個(gè)例子:
users: (root, args, context) => { if (!context.user || !context.user.roles.includes('admin')) throw ForbiddenError("You must be an administrator to see all Users"); return User.getAll(); }
@gyyyy:
有語(yǔ)法就會(huì)有解析,有解析就會(huì)有結(jié)構(gòu)和順序,有結(jié)構(gòu)和順序就會(huì)有注入。
前端使用變量構(gòu)建帶參查詢(xún)語(yǔ)句:
const id = props.match.params.id; const queryUser = gql`{ user(_id: ${id}) { _id username email } }`
name的值會(huì)在發(fā)出GraphQL查詢(xún)請(qǐng)求前就被拼接進(jìn)完整的GraphQL語(yǔ)句中。攻擊者對(duì)name注入惡意語(yǔ)句:
-1)%7B_id%7Dhack%3Auser(username%3A"admin")%7Bpassword%23
可能GraphQL語(yǔ)句的結(jié)構(gòu)就被改變了:
{ user(_id: -1) { _id } hack: user(username: "admin") { password #) { _id username email } }
因此,帶參查詢(xún)一定要保證在后端GraphQL引擎解析時(shí),原語(yǔ)句結(jié)構(gòu)不變,參數(shù)值以變量的形式被傳入,由解析器實(shí)時(shí)賦值解析。
@圖南:
幸運(yùn)的是,GraphQL同時(shí)提供了『參數(shù)』和『變量』給我們使用。我們可以將參數(shù)值的拼接過(guò)程轉(zhuǎn)交給后端GraphQL引擎,前端就像進(jìn)行參數(shù)化查詢(xún)一樣。
例如,我們定義一個(gè)帶變量的Query:
type Query { user( username: String! ): User }
請(qǐng)求時(shí)傳入變量:
query GetUser($name: String!) { user(username: $name) { _id username email } } // 變量 {"name": "some username"}
@gyyyy:
做過(guò)代碼調(diào)試的同學(xué)可能會(huì)注意過(guò),在觀察的變量中存在相互關(guān)聯(lián)的對(duì)象時(shí),可以對(duì)它們進(jìn)行無(wú)限展開(kāi)(比如一些Web框架的Request-Response對(duì))。如果這個(gè)關(guān)聯(lián)關(guān)系不是引用而是值,就有可能出現(xiàn)OOM等問(wèn)題導(dǎo)致運(yùn)算性能下降甚至應(yīng)用運(yùn)行中斷。同理,在一些動(dòng)態(tài)求值的邏輯中也會(huì)存在這類(lèi)問(wèn)題,比如XXE的拒絕服務(wù)。
GraphQL中也允許對(duì)象間包含組合的嵌套關(guān)系存在,如果不對(duì)嵌套深度進(jìn)行限制,就會(huì)被攻擊者利用進(jìn)行拒絕服務(wù)攻擊。
@圖南:
在開(kāi)發(fā)中,我們可能經(jīng)常會(huì)遇到這樣的需求:
1. 查詢(xún)所有文章,返回內(nèi)容中包含作者信息
2. 查詢(xún)作者信息,返回內(nèi)容中包含此作者寫(xiě)的所有文章
當(dāng)然,在我們開(kāi)發(fā)的前端中這兩個(gè)接口一定是單獨(dú)使用的,但攻擊者可以利用這它們的包含關(guān)系進(jìn)行嵌套查詢(xún)。
如下面例子,我們定義了Blog和Author:
type Blog { _id: String! type: BlogType avatar: String title: String content: [String] author: Author # ... } type Author { _id: String! name: String blog: [Blog] }
構(gòu)建各自的Query:
extend type Query { blogs( blogId: ID systemType: String! ): [Blog] } extend type Query { author( _id: String! ): Author }
我們可以構(gòu)造如下的查詢(xún),此查詢(xún)可無(wú)限循環(huán)下去,就有可能造成拒絕服務(wù)攻擊:
query GetBlogs($blogId: ID, $systemType: String!) { blogs(blogId: $blogId, systemType: $systemType) { _id title type content author { name blog { author { name blog { author { name blog { author { name blog { author { name blog { author { name blog { author { name blog { author { name # and so on... } } } } } } } } } } } } } title createdAt publishedAt } } publishedAt } }
避免此問(wèn)題我們需要在GraphQL服務(wù)器上限制查詢(xún)深度,同時(shí)在設(shè)計(jì)GraphQL接口時(shí)應(yīng)盡量避免出現(xiàn)此類(lèi)問(wèn)題。仍然以Node.js為例,graphql-depth-limit就可以解決這樣的問(wèn)題。
// ... import depthLimit from 'graphql-depth-limit'; // ... const server = new ApolloServer({ typeDefs: schemaText, resolvers: resolverMap, context: ({ ctx }) => { const token = ctx.req.headers.authorization || ''; const user = getUser(token); console.log('user',user) return { ...user, ...ctx, ...app.context }; }, validationRules: [ depthLimit(10) ] });// ...
添加限制后,請(qǐng)求深度過(guò)大時(shí)會(huì)看到如下報(bào)錯(cuò)信息:
@gyyyy:
作為Web API的一員,GraphQL和RESTful API一樣,有可能被攻擊者通過(guò)對(duì)參數(shù)注入惡意數(shù)據(jù)影響到后端應(yīng)用,產(chǎn)生XSS、SQL注入、RCE等安全問(wèn)題。此外,上文也提到了很多GraphQL的特性,一些特殊場(chǎng)景下,這些特性會(huì)被攻擊者利用來(lái)優(yōu)化攻擊流程甚至增強(qiáng)攻擊效果。比如之前說(shuō)的內(nèi)省機(jī)制和默認(rèn)開(kāi)啟的GraphiQL調(diào)試工具等,還有它同時(shí)支持GET和POST兩種請(qǐng)求方法,對(duì)于CSRF這些漏洞的利用會(huì)提供更多的便利。
當(dāng)然,有些特性也提供了部分保護(hù)能力,不過(guò)只是『部分』而已。
@圖南:
GraphQL的類(lèi)型系統(tǒng)對(duì)注入是一層天然屏障,但是如果開(kāi)發(fā)者的處理方式不正確,仍然會(huì)有例外。
比如下面的例子,參數(shù)類(lèi)型是字符串:
query GetAllUsers($filter: String!) { users(filter: $filter) { _id username email } }
假如后端沒(méi)有對(duì)filter的值進(jìn)行任何安全性校驗(yàn),直接查詢(xún)數(shù)據(jù)庫(kù),傳入一段SQL語(yǔ)句字符串,可能構(gòu)成SQL注入:
{"filter": "' or ''='"}
或者JSON字符串構(gòu)成NOSQL注入:
{"filter": "{\"$ne\": null}"}
GraphQL真的只是一個(gè)API技術(shù),它為API連接的前后端提供了一種新的便捷處理方案。無(wú)論如何,該做鑒權(quán)的就鑒權(quán),該校驗(yàn)數(shù)據(jù)的還是一定得校驗(yàn)。
關(guān)于如何進(jìn)行GraphQL的分析就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。
分享標(biāo)題:如何進(jìn)行GraphQL的分析
新聞來(lái)源:http://www.rwnh.cn/article4/ghchoe.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供營(yíng)銷(xiāo)型網(wǎng)站建設(shè)、服務(wù)器托管、Google、搜索引擎優(yōu)化、品牌網(wǎng)站建設(shè)、外貿(mào)建站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)