GraphQL, Uygulama Programlama Arayüzleri (API’ler) için modern bir sorgu dilidir. Facebook ve GraphQL Vakfı tarafından desteklenen GraphQL hızla büyüdü ve Shopify, GitHub ve Amazon gibi büyük sektör oyuncularının da katılımıyla teknoloji benimseme döngüsünün erken çoğunluk aşamasına girdi.

Her yeni teknolojinin yükselişinde olduğu gibi, GraphQL’i kullanmak da özellikle GraphQL’i ilk kez uygulayan geliştiriciler için artan zorluklarla birlikte geldi. GraphQL, geleneksel REST API’lerine göre daha fazla esneklik ve güç vaat etse de GraphQL, erişim kontrolü açıklarına yönelik saldırı yüzeyini potansiyel olarak artırabilir. Geliştiriciler, GraphQL API’lerini uygularken bu sorunlara dikkat etmeli ve üretimde güvenli varsayılanlara güvenmelidir. Aynı zamanda güvenlik araştırmacıları GraphQL API’lerini güvenlik açıklarına karşı test ederken bu zayıf noktalara dikkat etmelidir.
REST API ile istemciler bireysel uç noktalara HTTP isteklerinde bulunur.
Örneğin:
GET /api/user/1: Kullanıcıyı al1POST /api/user: Bir kullanıcı oluşturunPUT /api/user/1: Kullanıcıyı düzenle1DELETE /api/user/1: Kullanıcıyı sil1
GraphQL, standart REST API paradigmasının yerini alır. Bunun yerine GraphQL, istemcilerin ikisini de göndereceği yalnızca bir uç noktayı belirtir. sorgu veya mutasyon istek türleri. Bunlar sırasıyla okuma ve yazma işlemlerini gerçekleştirir. Üçüncü bir istek türü, aboneliklerdaha sonra tanıtıldı ancak çok daha az kullanıldı.
Arka uçta geliştiriciler, farklı kaynakları temsil edecek nesne türlerini ve alanları içeren bir GraphQL şeması tanımlar.
Örneğin bir kullanıcı şu şekilde tanımlanabilir:
type User {
id: ID!
name: String!
email: String!
height(unit: LengthUnit = METER): Float
friends: \[User!\]!
status: Status!
}
enum LengthUnit {
METER
FOOT
}
enum Status {
FREE
PREMIUM
}
Bu basit örnek, GraphQL’in birçok güçlü özelliğini göstermektedir. Diğer nesne türlerinin bir listesini destekler (friends), değişkenler (unit) ve numaralandırmalar (status). Ayrıca geliştiriciler yazıyor çözümleyicilerbir GraphQL isteği için arka ucun veritabanından sonuçları nasıl getireceğini tanımlar.
Bunu göstermek için, bir geliştiricinin şemada aşağıdaki sorguyu tanımladığını varsayalım:
{
"name": "getUser",
"description": null,
"args": [
{
"name": "id",
"description": null,
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "User",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
İstemci tarafında, bir kullanıcı şunları yapar: getUser sorgula ve geri al name Ve email Aşağıdaki POST isteği aracılığıyla alanlar:
POST /graphql
Host: example.com
Content-Type: application/json
{
"query":"query getUser($id:ID!) { getUser(id:$id) { name email }}",
"variables":{"id":1},
"operationName":"getUser"
}
Arka uçta GraphQL katmanı isteği ayrıştırır ve eşleşen çözümleyiciye iletir:
Query: {
user(obj, args, context, info) {
return context.db.loadUserByID(args.id).then(
userData => new User(userData)
)
}
}
Burada, args GraphQL sorgusunda alana sağlanan argümanları ifade eder. Bu durumda, args.id öyle 1.
Son olarak istenen veriler müşteriye iade edilecektir:
fark etmiş olabilirsiniz User nesne türü ayrıca şunları içerir: friends diğerlerine referans veren alan User nesneler. İstemciler bunu ilgili alandaki diğer alanları sorgulamak için kullanabilirler. User nesneler.
POST /graphql
Host: example.com
Content-Type: application/json
{
"query":"query getUser($id:ID!) { getUser(id:$id) { name email friends { email }}}",
"variables":{"id":1},
"operationName":"getUser"
}
Böylece geliştiriciler, tek tek API uç noktalarını ve denetleyici işlevlerini manuel olarak tanımlamak yerine, arka uçta değişiklik yapmak zorunda kalmadan istemci tarafında karmaşık sorgular oluşturmak için GraphQL’in esnekliğinden yararlanabilir. Bu, GraphQL’i AWS Lambda’lı Apollo Server gibi sunucusuz uygulamalarda popüler hale getiriyor.
Bilinen şu sözü hatırlayın: Büyük güç, büyük sorumluluğu da beraberinde getirir mi? GraphQL’in esnekliği güçlü bir avantaj olsa da, erişim kontrolü ve bilgilerin ifşa edilmesi güvenlik açıklarından yararlanmak için kötüye kullanılabilir.
Basit olanı düşünün User nesne türü ve sorgu. Makul olarak şunu bekleyebilirsiniz: user sorgulayabilir email onların friends. Peki ya email arkadaşlarının friends? Saldırganın izin istemesine gerek kalmadan kolaylıkla bu bilgileri elde etmesi mümkündür. emails Aşağıdakileri kullanarak ikinci derece ve üçüncü derece bağlantıların belirlenmesi:
query Users($id: ID!) {
user(id: $id) {
name
friends {
friends {
email
friends {
email
}
}
}
}
}
Klasik REST paradigmasında geliştiriciler, her bir denetleyici veya model kancası için erişim denetimlerini uygular. Potansiyel olarak Kendinizi Tekrarlama (DRY) ilkesini ihlal ederken, bu, geliştiricilere her çağrının erişim kontrolleri üzerinde daha fazla kontrol sağlar.
GraphQL, geliştiricilere yetkilendirmeyi GraphQL katmanı yerine iş mantığı katmanına devretmelerini önerir.

GraphQL’den İş Mantığı Katmanı
Bu nedenle yetkilendirme mantığı GraphQL çözümleyicisinin altında bulunur. Örneğin, GraphQL’den alınan bu örnekte:
//Authorization logic lives inside postRepository
var postRepository = require('postRepository');
var postType = new GraphQLObjectType({
name: 'Post',
fields: {
body: {
type: GraphQLString,
resolve: (post, args, context, { rootValue }) => {
return postRepository.getBody(context.user, post);
}
}
}
});
postRepository.getBody iş mantığı katmanındaki erişim kontrollerini doğrular.
Ancak bu, GraphQL spesifikasyonu tarafından zorunlu kılınmaz. GraphQL, geliştiricilerin yetkilendirme mantığını GraphQL katmanına yanlış yerleştirmesinin “cazip” olabileceğinin farkındadır. Ne yazık ki geliştiriciler bu tuzağa çok sık düşüyor ve erişim kontrol katmanında delikler yaratıyor.
Peki güvenlik araştırmacıları GraphQL API’sine nasıl yaklaşmalı? GraphQL, geliştiricilerin verilerini modellerken “grafiklerle düşünmelerini” ve araştırmacıların da aynısını yapmasını önerir. Klasik REST paradigmasında benim “ikinci dereceden Güvenli Olmayan Doğrudan Nesne Referansları (IDOR’lar)” olarak adlandırdığım şeye paralellikler kurabiliriz.
Örneğin, bir REST API’sinde aşağıdaki API çağrısı uygun şekilde korunuyor olabilir:
GET /api/user/1
“İkinci dereceden” bir API çağrısı yeterince korunmayabilir:
GET /api/user/1/photo/6
Arka uç mantığı, kullanıcı numarası isteyen kullanıcının 1 bu kullanıcıya okuma izinleri var. Ancak fotoğraf numarasına da erişebilmeleri gerekip gerekmediğini kontrol edemedi 6.
Aynı durum GraphQL çağrıları için de geçerlidir; ancak bir grafik şemasında olası yolların sayısı katlanarak artar. Örneğin bir sosyal medya fotoğrafı çekin: Bir saldırgan, bir fotoğrafı beğenen kullanıcıları sorgular ve ardından bu fotoğrafa erişirse ne olur? onların fotoğraflar?
query Users($id: ID!) {
user(id: $id) {
name
photos {
image
likes {
user {
photos {
image
}
}
}
}
}
}
Peki beğeniler ne durumda? onlar fotoğraflar? Zincir devam ediyor. Kısacası bir güvenlik araştırmacısı grafikteki “döngüyü kapatmaya” çalışmalı ve hedef nesneye giden yolları bulmalıdır. GitLab’dan Dominic Couture, kendisi hakkındaki gönderisinde bunu kapsamlı bir şekilde açıklıyor graphql-path-enum alet.
GraphQL API’lerinin çoğu uygulamasında, GraphQL uç noktasını hızlı bir şekilde tanımlayabilmeniz gerekir çünkü bunlar basit olma eğilimindedirler. /graphql veya /graph. Bunları, bu uç noktalara yapılan isteklere göre de tanımlayabilirsiniz.
POST /graphql
Host: example.com
Content-Type: application/json
{"query": "query AllUsers { allUsers{ id } }"}
Sorgu ve mutasyon gibi anahtar kelimelere dikkat etmelisiniz. Ek olarak, bazı GraphQL uygulamaları şunları kullanır: GET şuna benzeyen istekler: GET /graphql?query=….
Uç noktayı belirledikten sonra GraphQL şemasını çıkarmalısınız. Neyse ki GraphQL spesifikasyonu, şemanın tamamını döndüren bu tür “iç gözlem” sorgularını destekler. Bu, geliştiricilerin GraphQL sorgularını hızlı bir şekilde oluşturmasına ve hatalarını ayıklamasına olanak tanır. Bu iç gözlem sorguları, REST API’lerindeki Swagger gibi API çağrı belgeleme araçlarına benzer bir işlevi gerçekleştirir.
İç gözlem sorgusunu şu ana fikirden uyarlayabiliriz:
query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
…FullType
}
directives {
name
description
args {
…InputValue
}
locations
}
}
}
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
}
}
}
}
Elbette bunu çağrının yapıldığı yönteme göre kodlamanız gerekecektir. Standardı eşleştirmek için POST /graphql JSON biçimini kullanın:
POST /graphql
Host: example.com
Content-Type: application/json
{"query": "query IntrospectionQuery {__schema {queryType { name },mutationType { name },subscriptionType { name },types {…FullType},directives {name,description,args {…InputValue},locations}}}\\nfragment 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}}\\nfragment InputValue on __InputValue {name,description,type { …TypeRef },defaultValue}\\nfragment TypeRef on __Type {kind,name,ofType {kind,name,ofType {kind,name,ofType {kind,name}}}}"}
Bunun tüm şemayı döndüreceğini umuyoruz, böylece istediğiniz nesne türüne giden farklı yollar aramaya başlayabilirsiniz. Apollo gibi çeşitli GraphQL çerçeveleri, iç gözlem sorgularını açığa çıkarmanın tehlikelerini kabul eder ve bunları üretimde varsayılan olarak devre dışı bırakır. Bu gibi durumlarda, sabırla kaba kuvvet uygulayarak ve olası nesne türlerini ve alanlarını sıralayarak ileriye doğru yolunuzu bulmanız gerekecektir. Apollo için sunucu yararlı bir şekilde geri dönüyor Error: Unknown type "X". Did you mean "Y"? gerçek değere yakın bir tür veya alan için.
Güvenlik araştırmacıları orijinal şemanın mümkün olduğu kadar çoğunu ortaya çıkarmalıdır. Şemanın tamamına sahipseniz, bir sorgudan hedef nesne türüne giden farklı yolları numaralandırmak için bunu graphql-path-enum gibi araçlarla çalıştırmaktan çekinmeyin. Tarafından verilen örnekte graphql-path-enumeğer bir şemadaki hedef nesne türü Skillaraştırmacı şunları çalıştırmalıdır:
$ graphql-path-enum -i ./schema.json -t Skill
Found 27 ways to reach the "Skill" node from the "Query" node:
— Query (assignable_teams) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
— Query (checklist_check) -> ChecklistCheck (checklist) -> Checklist (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
— Query (checklist_check_response) -> ChecklistCheckResponse (checklist_check) -> ChecklistCheck (checklist) -> Checklist (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
— Query (checklist_checks) -> ChecklistCheck (checklist) -> Checklist (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
— Query (clusters) -> Cluster (weaknesses) -> Weakness (critical_reports) -> TeamMemberGroupConnection (edges) -> TeamMemberGroupEdge (node) -> TeamMemberGroup (team_members) -> TeamMember (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
…
Sonuçlar şemada ulaşılacak farklı yolları döndürür Skill iç içe geçmiş sorgular ve bağlantılı nesne türleri aracılığıyla nesneler.
Güvenlik araştırmacıları aynı zamanda şemayı manüel olarak inceleyerek bu yolları keşfetmelidir. graphql-path-enum kaçırmış olabilir. Aracın çalışması aynı zamanda bir GraphQL şeması gerektirdiğinden, şemanın tamamını çıkaramayan araştırmacıların da manuel incelemeye güvenmesi gerekecek. Bunu yapmak için, saldırganın erişebildiği çeşitli nesne türlerini göz önünde bulundurun, bağlantılı nesne türlerini bulun ve korunan kaynağa giden bu bağlantıları izleyin. Daha sonra bu sorguları erişim kontrolü sorunları açısından test edin.
Mutasyonlar için de yaklaşım benzerdir. Doğrudan erişim kontrolü sorunlarına (erişim sahibi olmamanız gereken nesnelerdeki mutasyonlar) yönelik testlerin ötesinde, bağlantılı nesne türleri için mutasyonların dönüş değerlerini kontrol etmeniz gerekecektir.
GraphQL, nesneleri grafik paradigması aracılığıyla sorgulayarak API’lere daha fazla esneklik ve derinlik katar. Ancak erişim kontrolü güvenlik açıkları için her derde deva değildir. GraphQL API’leri, REST API’lerini etkileyen aynı yetkilendirme ve kimlik doğrulama sorunlarına eğilimlidir. Ek olarak, erişim kontrolleri hâlâ geliştiricilerin uygun iş mantığını veya model kancalarını tanımlamasına bağlı olduğundan insan hatası potansiyeli artıyor.
Geliştiriciler erişim kontrollerini mümkün olduğunca kalıcılık (model) katmanına yakın bir yere taşımalı ve şüpheye düştüklerinde Apollo gibi makul varsayılanlara sahip çerçevelere güvenmelidir. Apollo özellikle veri modellerinde yetkilendirme kontrollerinin yapılmasını önerir:
En başından beri, gerçek veri alma ve dönüştürme mantığını çözümleyicilerden, her biri uygulamanızdaki bir kavramı temsil eden merkezi Model nesnelerine taşımanızı öneriyoruz: Kullanıcı, Gönderi vb. Bu, çözümleyicilerinizi ince bir yönlendirme katmanı yapmanıza ve tüm iş mantığınızı tek bir yere koymanıza olanak tanır.
Örneğin, Kullanıcı modeli şuna benzer:
export const generateUserModel = ({ user }) => ({
getAll: () => {
if(!user || !user.roles.includes('admin')) return null;
return fetch('http://myurl.com/users');
},
…
});
Geliştiriciler, yetkilendirme mantığını farklı denetleyicilere yaymak yerine model katmanına taşıyarak tek bir “doğruluk kaynağı” tanımlayabilir.
Uzun vadede, GraphQL daha da fazla benimsenmeye başladıkça ve teknoloji benimseme döngüsünün geç çoğunluk aşamasına ulaştıkça, daha fazla geliştirici GraphQL’i ilk kez uygulayacak. Geliştiriciler, GraphQL şemalarının saldırı yüzeyini dikkatlice düşünmeli ve kullanıcı verilerini korumak için güvenli erişim kontrolleri uygulamalıdır.
Katkılarından dolayı Dominic Couture, Kenneth Tan, Medha Lim, Serene Chan ve Teck Chung Khor’a özellikle teşekkür ederiz.