依賴項安全檢測新利器:Scorecard API

  • 2022 年 9 月 21 日
  • 筆記

Scorecard 是 OpenSSF 旗下的開源項目,用於評估開源軟件風險,本文由該項目的主要貢獻者 Naveen 撰寫。

現代軟件是建立在數百個甚至數千個第三方開源組件之上的,這些通常被稱為依賴項。它們可以幫助開發團隊快速迭代並保持生產力。

由於生產力的提升,大部分企業正在快速採用開源軟件(OSS),導致承載關鍵任務的應用程序依賴於成千上萬的直接和傳遞依賴項。隨着開源軟件包正在成為惡意用戶的攻擊目標,這些依賴項的健康狀況已經成為整個供應鏈安全至關重要的部分。

一個依賴項的健康程度牽涉到諸多影響因素:它是否被良好維護?它是否經常更新?該項目是否有多個維護者?或者當其中一個維護者離開之後,這個項目是否會停止運轉?在代碼中是否有嚴重的漏洞?

無論何時,當你考慮要引入一個新的依賴項時,如果能獲得該項目健康和安全程度的評分將幫助你評估該項目是否安全。那麼 OpenSSF Scorecard 將是你的不二之選,在之前的文章中我們也對 OpenSSF Scorecard 進行了拆解。目前,Seal 軟件供應鏈防火牆已經集成 Scorecard,對用戶掃描出來的漏洞進行評分,進而對其進行修復優先級的排序。

OpenSSF Scorecard 是一款自動化工具,用於評估與軟件安全相關的幾個重要啟發式(heuristics)檢查,並給每項檢查從0-10分打分。這些分數有助於了解需要改善的具體環節,以加強依賴項的安全。

其中一些檢查是:

  • 決定項目在PR合併之前是否需要代碼審查

  • 檢查項目的默認分支和發佈分支是否受到 GitHub 的分支保護

  • 檢查項目是否在源倉庫生成可執行的二進制構件

諸如 envoy.proxytensorflowflutter 等知名項目都在使用 Scorecard,以彰顯他們的安全意識。

當前,Scorecards 每周掃描超過100萬個代碼倉庫,掃描結果會被儲存在 BigQuery(//github.com/ossf/scorecard#public-data )中。

Scorecard API 發佈

Scorecard API 已於本月初正式發佈,它可以用於訪問可用的數據集,這讓 Scorecard 的能力更上一層樓。

地址://api.securityscorecards.dev

為什麼 Scorecard API 如此有意義?

由於軟件供應鏈對於整個項目的安全來說極其重要,因此最好能有一個明確的安全策略。在理想狀況下,機器檢查/強制執行可以確保新的依賴項的高質量標準,減緩不必要的依賴項增加,並解決結構性問題(如項目中所包含重複的依賴項、已知有問題的依賴項)。

我們的策略可以解決如下問題:

隨着時間的推移,依賴項是如何變化的?

  • 那些依賴項做了什麼?

  • 它們如何影響項目安全?

  • 它們如何更新?

  • 你如何限制你的暴露?

依賴項數量的快速增長會帶來一些問題,比如難以跟蹤:

  • 依賴項相對於其上游的陳舊程度

  • 是否有適用於項目依賴項的任何已知的常見漏洞和暴露(CVE)

  • 外部依賴項在多大程度上遵循最佳實踐,例如,代碼審查、更新依賴項、雙因子認證(2FA)、漏洞披露過程、定期發佈實踐等

以下例子可以說明Envoy如何使用明確的外部依賴性策略:

//github.com/envoyproxy/envoy/blob/main/DEPENDENCY_POLICY.md

使用 API 查看依賴項健康狀況

如「木桶效應」所闡述的那樣,一隻水桶能裝多少水取決於其最短的那塊木板。軟件供應鏈的健壯程度也取決於其最薄弱的環節。但是,在持續的時間範圍內確定最薄弱的環節並非易事。接下來,我們將演示如何使用 Scorecard API 來評估 Golang 項目中的一組依賴項是否在其項目內部使用模糊測試,這是驗證項目中是否存在零日漏洞的方法之一。

要解決這一問題會涉及到以下步驟:

1、 獲取指定項目中的依賴項和傳遞依賴列表:


// FetchDependencies parses the dependencies in the go.mod using the `go list command`
// This functions expects the directory to contain the go.mod file.
func FetchDependencies(directory string) ([]string, error) {
  modquery := `
  go list -m -f '{{if not (or  .Main)}}{{.Path}}{{end}}' all \
  | grep "^github" \
  | sort -u \
  | cut -d/ -f1-3 \
  | awk '{print $1}' \
  | tr '\n' ',' 
  `
  // Runs the modquery to generate the dependencies
  c := exec.Command("bash", "-c", fmt.Sprintf("cd %s;", directory)+modquery)
  data, err := c.Output()
  if err != nil {
    return nil, fmt.Errorf("failed to run go list: %w %s", err, string(data))
  }
  m := make(map[string]bool)
  parameters := []string{}
  result := append(parameters, strings.Split(string(data), ",")...)
  //filter the result to remove empty strings and duplicates
  for _, dep := range result {
    if dep != "" {
      m[dep] = true
    }
  }
  result = []string{}
  for dep := range m {
    result = append(result, dep)
  }
  return result, nil
}

以上代碼只是為演示準備的,不能在生產環境中使用。最後會返回項目中所有傳遞依賴項。

2、 接下來,該函數從 API 中獲取 Scorecard 的結果,並檢查某個項目是否經過模糊測試:

func fuzzed(repo string) (bool, int, error) {
  //repo = "github.com/sigstore/sigstore"
  req, err := http.NewRequest("GET", fmt.Sprintf("//api.securityscorecards.dev/projects/%s", repo), nil)
  if err != nil {
    panic(err)
  }
  req.Header.Set("Accept", "application/json")

  resp, err := http.DefaultClient.Do(req)
  if err != nil {
    return false, 0, err
  }
  defer resp.Body.Close()
  result, err := ioutil.ReadAll(resp.Body)
  if err != nil {
    return false, 0, err
  }
  var scorecard Scorecard
  err = json.Unmarshal(result, &scorecard)
  if err != nil {
    return true, 0, err
  }
  for _, check := range scorecard.Checks {
    if check.Name == "Fuzzing" {
      if check.Score >= 7 || check.Score < 0 {
        return true, check.Score, nil
      }
      return false, 0, nil
    }
  }
  return false, 0, nil
}

3、 最後一步是將這兩個函數結合起來,獲得最終結果:

func main() {
  repoLocation := os.Args[1]
  if repoLocation == "" {
    panic("repoLocation is empty")
  }
  dependencies, err := FetchDependencies(repoLocation)
  if err != nil {
    panic(err)
  }
  fmt.Println("Projects that are being fuzzed:")
  var ops uint64
  var wg sync.WaitGroup

  for _, dep := range dependencies {
    dependency := dep
    wg.Add(1)
    go func(dep string) {
      defer wg.Done()
      maintained, score, err := fuzzed(dependency)
      if err != nil {
        return
      }
      if maintained && score >= 7 {
        atomic.AddUint64(&ops, 1)
        fmt.Println(dependency, score)
      }
    }(dep)
  }
  wg.Wait()
  fmt.Println("-----------------")
  fmt.Println("Total number of dependencies :", len(dependencies))
  fmt.Println("The number of dependencies that are fuzzed :", ops)
}

訪問以下鏈接可以查看完整的工作實例:

//github.com/naveensrinivasan/scorecard-api-demo

Scorecard 項目可以讓用戶更容易測量依賴項的健康程度,並根據其創建策略。而Scorecard API 讓依賴項策略的自動化和執行都更輕鬆了。在未來的版本中,Seal 軟件供應鏈防火牆計劃集成 Scorecard API ,更好地保障用戶依賴項安全。