iT邦幫忙

2024 iThome 鐵人賽

DAY 25
0

什麼是 GraphQL

GraphQL 是一種用於 API 的查詢語言和執行引擎,由 Facebook 於 2015 年開源。它提供了一種更有效、更靈活的方式來設計、建立和消費 API。

不同於傳統的 REST API,GraphQL 允許客戶端精確指定所需的資料,無需多餘或不足,從而實現更高效的資料傳輸。

GraphQL 的核心特點包括:

  1. 聲明式資料取得:客戶端可以精確描述所需的資料結構。
  2. 單一端點:所有請求都發送到同一個端點,簡化了 API 的設計。
  3. 強類型系統:GraphQL 使用強類型模式定義,提供了更好的開發時錯誤檢查和自動完成功能。
  4. 階層式:GraphQL 查詢與回傳的 JSON 資料結構相匹配,使得資料關係更直觀。
  5. GraphQL API 可以查詢自身的模式,便於工具和 IDE 集成。

什麼時候使用 GraphQL

  1. 複雜的資料關係:應用程式涉及多個相互關聯的資料實體時,GraphQL 可以輕鬆處理複雜的資料關係。
  2. 手機應用程式:在網路條件不穩定或頻寬受限的情況下,GraphQL 可以精確取得所需資料,減少資料傳輸量。
  3. 快速迭代的產品:當產品需求經常變化時,GraphQL 的靈活性允許前端輕鬆調整資料需求,而無需後端進行大幅修改。
  4. 整合多個資料源:GraphQL 可以作為中間層,整合來自多個後端服務或資料庫的資料。
  5. 微服務架構:在微服務架構中,GraphQL 可以作為統一的 API 網關,簡化客戶端與多個微服務的交互。

有什麼資安弱點

  1. 過度取得(Over-fetching):由於客戶端可以自由指定查詢內容,惡意使用者可能會嘗試取得過多敏感資料。
  2. 複雜查詢攻擊:攻擊者可能會構造極其複雜的查詢,消耗大量伺服器資源,導致拒絕服務(DoS)。
  3. 批量操作風險:GraphQL 允許在單一請求中執行多個操作,這可能被用來進行大規模的資料抓取或修改。
  4. 認證和授權挑戰:在文字級別實施細粒度的訪問控制可能變得複雜。
  5. 資訊洩漏:錯誤訊息和查詢自身的模式功能可能會洩漏 API 結構和實現細節。
  6. 注入攻擊:如果不小心處理使用者輸入,可能導致類似 SQL 注入的攻擊。
  7. 速率限制難題:傳統的請求計數方法可能不適用於 GraphQL,需要更複雜的限制機制。

實作程式碼

程式碼

https://github.com/fei3363/ithelp_web_security_2024/commit/bdc0fa658bcb0942a7bb2983f93022dc56d81e06

  1. web/graphql/resolvers.js:

這個檔案定義了 GraphQL 查詢和變更的解析器。它包含了模擬的資料儲存和不安全的操作。

// 模擬資料儲存
let users = [
  { id: '1', username: 'user1', email: 'user1@example.com', password: 'hashedpassword1' },
  { id: '2', username: 'user2', email: 'user2@example.com', password: 'hashedpassword2' }
];

let posts = [
  { id: '1', title: 'First Post', content: 'This is the first post', authorId: '1' },
  { id: '2', title: 'Second Post', content: 'This is the second post', authorId: '2' }
];

let comments = [
  { id: '1', content: 'Great post!', authorId: '2', postId: '1' },
  { id: '2', content: 'Thanks for sharing', authorId: '1', postId: '2' }
];

const resolvers = {
  Query: {
    hello: () => 'Hello world!',
    users: () => users, // 直接回傳所有使用者資料,包括密碼
    user: (_, { id }) => {
      // 不安全的資料過濾,可能導致原型污染攻擊
      return users.find(user => user.id == id);
    },
    posts: () => posts,
    search: (_, { term }) => {
      // 不安全的搜尋,可能導致資訊洩漏
      const results = [...users, ...posts, ...comments].filter(
        item => JSON.stringify(item).toLowerCase().includes(term.toLowerCase())
      );
      return results;
    },
    dangerousQuery: (_, { query }) => {
      // 非常不安全:允許客戶端執行任意 JavaScript 程式碼
      try {
        return eval(query);
      } catch (error) {
        return { error: error.toString() };
      }
    },
  },
  // ... 其他解析器
};

module.exports = resolvers;
  1. web/graphql/schema.js:

這個檔案定義了 GraphQL 的類型和操作結構。

const { gql } = require('apollo-server-express');

const typeDefs = gql`
  type Query {
    hello: String
    users: [User!]!
    user(id: ID!): User
    posts: [Post!]!
    search(term: String!): [SearchResult!]!
    dangerousQuery(query: String!): JSON
  }

  type User {
    id: ID!
    username: String!
    email: String!
    password: String!
    posts: [Post!]!
  }

  type Post {
    id: ID!
    title: String!
    content: String!
    author: User!
    comments: [Comment!]!
  }

  type Comment {
    id: ID!
    content: String!
    author: User!
  }

  union SearchResult = User | Post | Comment

  scalar JSON

  type Mutation {
    createUser(username: String!, email: String!, password: String!): User!
    createPost(userId: ID!, title: String!, content: String!): Post!
    createComment(userId: ID!, postId: ID!, content: String!): Comment!
    deleteUser(id: ID!): Boolean!
    updateUser(id: ID!, username: String, email: String, password: String): User
    dangerousUpdate(collection: String!, id: ID!, updates: JSON!): JSON
  }
`;

module.exports = typeDefs;
  1. web/graphql/server.js:

這個檔案設定並啟動 Apollo Server。

const { ApolloServer } = require('apollo-server-express');
const typeDefs = require('./schema');
const resolvers = require('./resolvers');

async function startApolloServer(app) {
  const server = new ApolloServer({
    typeDefs,
    resolvers,
  });

  await server.start();
  server.applyMiddleware({ app });
}

module.exports = { startApolloServer };

實作指令與實驗

  • 基本查詢測試

    1. 實驗名稱:基本使用者查詢
    2. 目的:測試基本的 GraphQL 查詢功能
    3. 參數:
    query { hello users { id username email } }
    
    1. 預期結果:回傳 "Hello world!" 和所有使用者的基本資訊
    2. 問題點:可能暴露過多使用者資訊
    3. curl 命令:
      curl -X POST \
           -H "Content-Type: application/json" \
           -d '{"query": "query { hello users { id username email } }"}' \
           http://nodelab.feifei.tw/graphql
      
  • 密碼洩漏測試

    1. 實驗名稱:使用者密碼查詢
    2. 目的:測試是否能取得使用者密碼
    3. 參數:
    query { users { id username email password } }
    
    
    1. 預期結果:回傳包含密碼在內的使用者資訊
    2. 問題點:嚴重的安全漏洞,暴露使用者密碼
    3. curl 命令:
      curl -X POST \
           -H "Content-Type: application/json" \
           -d '{"query": "query { users { id username email password } }"}' \
           http://nodelab.feifei.tw/graphql
      

image

  • 不安全的使用者建立

    1. 實驗名稱:建立新使用者
    2. 目的:測試使用者建立功能和密碼處理
    3. 參數:
    mutation { createUser(username: "newuser", email: "new@example.com", password: "password123") { id username email password } }
    
    
    1. 預期結果:建立新使用者並回傳所有資訊,包括密碼
    2. 問題點:回傳明文密碼,不安全的密碼儲存
    3. curl 命令:
      curl -X POST \
           -H "Content-Type: application/json" \
           -d '{"query": "mutation { createUser(username: \"newuser\", email: \"new@example.com\", password: \"password123\") { id username email password } }"}' \
           http://nodelab.feifei.tw/graphql
      

image

  • 未授權的使用者刪除

    1. 實驗名稱:刪除使用者
    2. 目的:測試是否能未經授權刪除使用者
    3. 參數:
    mutation { deleteUser(id: "1") }
    
    1. 預期結果:成功刪除使用者
    2. 問題點:缺乏授權檢查,任何人都可以刪除使用者
    3. curl 命令:
      curl -X POST \
           -H "Content-Type: application/json" \
           -d '{"query": "mutation { deleteUser(id: \"1\") }"}' \
           http://nodelab.feifei.tw/graphql
      

image

  • 不安全的資料更新
    1. 危險更新操作
    2. 目的:測試是否能更新任意資料
    3. 參數:
    mutation { dangerousUpdate(collection: "users", id: "2", updates: { password: "hacked" }) }
    
    1. 預期結果:成功更新使用者密碼
    2. 問題點:允許未經授權的敏感資料修改
    3. curl 命令:
      curl -X POST \
           -H "Content-Type: application/json" \
           -d '{"query": "mutation { dangerousUpdate(collection: \"users\", id: \"2\", updates: { password: \"hacked\" }) }"}' \
           http://nodelab.feifei.tw/graphql
      

image

這些測試展示了多個嚴重的安全問題,包括:

  • 敏感資料洩漏(如密碼)
  • 不安全的密碼處理
  • 缺乏適當的授權檢查
  • 允許未經授權的資料修改

在實際的 GraphQL API 開發中,應該採取適當的安全措施來防止這些問題,如:

  • 永不暴露或傳輸明文密碼
  • 實施良好的授權和身份驗證機制
  • 限制查詢複雜度和深度
  • 實施輸入驗證和清理
  • 使用安全的密碼雜湊算法
  • 限制可更新的字段,特別是敏感資訊

總結

GraphQL 作為一種現代化的 API 查詢語言和執行引擎,為開發者提供了更靈活、高效的資料取得方式。

它的特點如聲明式資料取得、單一端點、強類型系統等,使得 API 開發和使用變得更加直觀和高效。

然而,GraphQL 的靈活性也帶來了一系列的安全挑戰。

我們透過實作範例展示了一些常見的 GraphQL 安全漏洞,包括敏感資訊洩漏、不安全的資料操作、缺乏授權檢查等。

這些問題凸顯了在開發 GraphQL API 時需要特別注意的安全考量:

  1. 資料保護:避免直接暴露敏感資訊,如密碼。
  2. 授權機制:確保每個操作都經過適當的權限檢查。
  3. 輸入驗證:防止注入攻擊和惡意輸入。
  4. 查詢複雜度控制:避免資源耗盡攻擊。
  5. 安全的更新操作:限制可更新的欄位,特別是敏感資訊。

小試身手

  1. GraphQL 的哪個特性可能導致過度取得(over-fetching)問題?
    A. 強類型系統
    B. 單一端點
    C. 客戶端指定查詢內容
    D. 內省功能

    答案:C
    解析:GraphQL 允許客戶端精確指定所需的資料,這種靈活性可能被濫用,導致客戶端請求過多不必要的資料,造成過度取得問題。

  2. 在 GraphQL 中,下列哪種做法最容易導致安全漏洞?
    A. 使用強類型定義
    B. 實施查詢深度限制
    C. 允許執行任意 JavaScript 程式碼
    D. 使用單一端點

    答案:C
    解析:允許執行任意 JavaScript 程式碼(如範例中的 dangerousQuery)是極其危險的做法,可能導致遠程程式碼執行等嚴重的安全漏洞。

  3. 為防止 GraphQL API 中的 DoS 攻擊,最有效的方法是?
    A. 使用強密碼策略
    B. 實施查詢複雜度和深度限制
    C. 加密所有資料傳輸
    D. 使用 JSON Web Tokens (JWT)

    答案:B
    解析:透過限制查詢的複雜度和深度,可以有效防止攻擊者構造極其複雜的查詢,從而避免伺服器資源耗盡,防止 DoS 攻擊。

  4. 在 GraphQL schema 中,包含密碼欄位的主要問題是?
    A. 增加了 schema 的複雜性
    B. 可能導致效能問題
    C. 違反了 GraphQL 最佳實踐
    D. 可能導致敏感資訊洩漏

    答案:D
    解析:在 schema 中包含密碼欄位可能導致敏感資訊洩漏,因為它允許查詢直接取得密碼資訊。最佳實踐是永遠不在 API 回應中包含密碼。

  5. 以下哪種 GraphQL 操作最不可能造成安全問題?
    A. 批量查詢
    B. 遞迴查詢
    C. 變更(Mutation)操作
    D. 內省查詢

    答案:D
    解析:雖然內省查詢可能洩漏 API 結構,但相比其他選項,它通常不會直接導致資料洩漏或未授權的資料修改。批量查詢可能導致效能問題,遞迴查詢可能造成深度問題,而未經適當保護的變更操作可能導致未授權的資料修改。


上一篇
資安這條路:Day 24 WebSocket 與商業邏輯漏洞
下一篇
資安這條路:Day 26 SSDLC 安全開發軟體生命週期─概述、需求階段
系列文
資安這條路:系統化學習網站安全與網站滲透測試30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言