Skip to content

Instantly share code, notes, and snippets.

@sauravexodus
Created October 11, 2025 10:26
Show Gist options
  • Select an option

  • Save sauravexodus/e326e5eb673f79e6cd3414472322cff4 to your computer and use it in GitHub Desktop.

Select an option

Save sauravexodus/e326e5eb673f79e6cd3414472322cff4 to your computer and use it in GitHub Desktop.
Abracadabra Exploit: Detecting Cauldron Solvency Bypass
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
)
type Finding struct {
Contract string
Function string
Severity string
Description string
Line string
}
func checkSolvencyRequire(node map[string]interface{}) bool {
nodeType, ok := node["nodeType"].(string)
if !ok || nodeType != "FunctionCall" {
goto recurse
}
exprRaw, ok := node["expression"]
if !ok {
goto recurse
}
exprMap, isMap := exprRaw.(map[string]interface{})
if !isMap {
goto recurse
}
name, ok := exprMap["name"].(string)
if !ok || name != "require" {
goto recurse
}
argsRaw, ok := node["arguments"]
if !ok {
goto recurse
}
argsList, isList := argsRaw.([]interface{})
if !isList || len(argsList) == 0 {
goto recurse
}
argMap, isMap := argsList[0].(map[string]interface{})
if !isMap {
goto recurse
}
argType, ok := argMap["nodeType"].(string)
if !ok || argType != "FunctionCall" {
goto recurse
}
innerExprRaw, ok := argMap["expression"]
if !ok {
goto recurse
}
innerExprMap, isMap := innerExprRaw.(map[string]interface{})
if !isMap {
goto recurse
}
innerName, ok := innerExprMap["name"].(string)
if !ok || innerName != "_isSolvent" {
goto recurse
}
return true
recurse:
for _, val := range node {
if dictVal, ok := val.(map[string]interface{}); ok {
if checkSolvencyRequire(dictVal) {
return true
}
} else if listVal, ok := val.([]interface{}); ok {
for _, item := range listVal {
if itemDict, ok := item.(map[string]interface{}); ok {
if checkSolvencyRequire(itemDict) {
return true
}
}
}
}
}
return false
}
func detectCookSolvencyBypass(body map[string]interface{}, contractName, funcName, srcInfo string) []Finding {
findings := []Finding{}
nodeType, ok := body["nodeType"].(string)
if ok && nodeType == "IfStatement" {
if _, conditionExists := body["condition"]; conditionExists {
if trueBodyRaw, ok := body["trueBody"]; ok {
if trueBodyMap, isMap := trueBodyRaw.(map[string]interface{}); isMap {
if checkSolvencyRequire(trueBodyMap) {
line := "?"
if srcInfo != "" {
parts := strings.Split(srcInfo, ":")
if len(parts) > 0 {
line = parts[0]
}
}
findings = append(findings, Finding{
Contract: contractName,
Function: funcName,
Severity: "Critical",
Description: "The final solvency check (`require(_isSolvent)`) is guarded by a conditional statement, which can be bypassed in a multi-call context.",
Line: line,
})
return findings
}
}
}
}
}
for _, val := range body {
if dictVal, ok := val.(map[string]interface{}); ok {
findings = append(findings, detectCookSolvencyBypass(dictVal, contractName, funcName, srcInfo)...)
} else if listVal, ok := val.([]interface{}); ok {
for _, item := range listVal {
if itemDict, ok := item.(map[string]interface{}); ok {
findings = append(findings, detectCookSolvencyBypass(itemDict, contractName, funcName, srcInfo)...)
}
}
}
}
return findings
}
func analyzeNodeForVulnerabilities(astNode map[string]interface{}, contractName string) []Finding {
findings := []Finding{}
nodeType, ok := astNode["nodeType"].(string)
if !ok {
return findings
}
if nodeType == "FunctionDefinition" {
funcName, _ := astNode["name"].(string)
if funcName == "cook" {
srcInfo, _ := astNode["src"].(string)
if bodyRaw, bodyExists := astNode["body"]; bodyExists {
if bodyMap, isMap := bodyRaw.(map[string]interface{}); isMap {
findings = append(findings, detectCookSolvencyBypass(bodyMap, contractName, funcName, srcInfo)...)
}
}
}
}
for _, val := range astNode {
if dictVal, ok := val.(map[string]interface{}); ok {
findings = append(findings, analyzeNodeForVulnerabilities(dictVal, contractName)...)
} else if listVal, ok := val.([]interface{}); ok {
for _, item := range listVal {
if itemDict, ok := item.(map[string]interface{}); ok {
findings = append(findings, analyzeNodeForVulnerabilities(itemDict, contractName)...)
}
}
}
}
return findings
}
func analyzeSolidityAST(astData string) []Finding {
var ast map[string]interface{}
if err := json.Unmarshal([]byte(astData), &ast); err != nil {
fmt.Printf("Error: Invalid JSON AST data: %v\n", err)
return nil
}
var allFindings []Finding
nodesRaw, ok := ast["nodes"]
if !ok {
fmt.Println("Error: AST is missing 'nodes' field.")
return nil
}
nodes, isList := nodesRaw.([]interface{})
if !isList {
fmt.Println("Error: 'nodes' field is not a list.")
return nil
}
for _, nodeRaw := range nodes {
node, isMap := nodeRaw.(map[string]interface{})
if !isMap {
continue
}
if node["nodeType"] == "ContractDefinition" {
contractName, _ := node["name"].(string)
if contractName == "" {
contractName = "UnknownContract"
}
fmt.Printf("Analyzing Contract: %s\n", contractName)
findings := analyzeNodeForVulnerabilities(node, contractName)
allFindings = append(allFindings, findings...)
}
}
return allFindings
}
const EXAMPLE_AST_JSON = `
{
"id": 1,
"nodeType": "SourceUnit",
"src": "0:400:0",
"nodes": [
{
"id": 3,
"nodeType": "ContractDefinition",
"src": "27:370:0",
"name": "CauldronV4",
"contractKind": "contract",
"nodes": [
{
"id": 100,
"nodeType": "FunctionDefinition",
"src": "100:250:0",
"name": "cook",
"parameters": {"nodeType": "ParameterList", "parameters": []},
"returnParameters": {"nodeType": "ParameterList", "parameters": []},
"visibility": "public",
"body": {
"nodeType": "Block",
"statements": [
{
"id": 150,
"nodeType": "IfStatement",
"src": "300:50:0",
"condition": {
"nodeType": "MemberAccess",
"memberName": "needsSolvencyCheck"
},
"trueBody": {
"nodeType": "Block",
"statements": [
{
"nodeType": "ExpressionStatement",
"expression": {
"nodeType": "FunctionCall",
"expression": {
"nodeType": "Identifier",
"name": "require"
},
"arguments": [
{
"nodeType": "FunctionCall",
"expression": {
"nodeType": "Identifier",
"name": "_isSolvent"
}
}
]
}
}
]
}
}
]
}
}
]
}
]
}
`
func main() {
fmt.Println("SAST Tool: Specialized Solvency Detector")
fmt.Println("Analyzing simulated AST data for conditional solvency check...\n")
results := analyzeSolidityAST(EXAMPLE_AST_JSON)
if len(results) > 0 {
fmt.Println("--- CRITICAL FINDINGS REPORT ---")
for _, finding := range results {
fmt.Printf("🔥 CRITICAL VULNERABILITY FOUND (%s):\n", finding.Severity)
fmt.Printf(" Contract: %s\n", finding.Contract)
fmt.Printf(" Function: %s\n", finding.Function)
fmt.Printf(" Line: %s\n", finding.Line)
fmt.Printf(" Details: %s\n", finding.Description)
fmt.Println(strings.Repeat("-", 70))
}
} else {
fmt.Println("✅ No conditional solvency check pattern found.")
}
os.Exit(0)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment