Skip to content

Feat/security markdown audit#2

Open
louis14448 wants to merge 7 commits into
gnoverse:mainfrom
louis14448:feat/security-markdown-audit
Open

Feat/security markdown audit#2
louis14448 wants to merge 7 commits into
gnoverse:mainfrom
louis14448:feat/security-markdown-audit

Conversation

@louis14448
Copy link
Copy Markdown
Contributor

PR — Security audit: markdown injection in Render()

Context

Following the discussion around gnolang/gno#5714:
the moul/md helpers do not sanitize user input. An attacker can inject arbitrary markdown
into any user-controlled field stored by a realm, and that content is returned verbatim by
Render() — which gnoweb renders as HTML in the browser.

These scripts document 5 attack vectors, all KNOWN VULNERABLE on current master.
They are intended as regression tests: once the upstream fix from #5714 is merged, all scripts
should exit 0 without modification.

Branch

feat/security-markdown-audit

Directory

tests/samourai-crew/security-markdown/
├── common.sh                     ← shared config (RPC, CHAINID, KEY, PASSWORD)
├── audit_md_title_leak.sh        ← vector 1: title injected unsanitized into markdown body
├── audit_md_html_inject.sh       ← vector 2: raw HTML tags returned verbatim by Render()
├── audit_md_link_hijack.sh       ← vector 3: phishing link with official-looking display text
├── audit_md_blockquote.sh        ← vector 4: blockquote impersonating a core-team statement
└── audit_md_image_tracking.sh    ← vector 5: external image URL deanonymizing visitors

Not a standalone contributor — no Makefile/Dockerfile. Scripts are called from run_tests.sh
inside samourai-crew, reusing existing wallets and funding.


Vectors

Vector 1 — Title leak into body (audit_md_title_leak.sh)

Scenario: A DAO proposal realm stores a user-controlled title and prepends # in Render().
An attacker injects newlines and a new heading into the title — voters see a fake page structure.

Detection: Render() contains # INJECTED as a separate heading → VULNERABLE

Fix note: Uses printf to pass real newlines to gnokey -args — shell double-quotes keep
\n literal, which would produce a false positive without this.


Vector 2 — Raw HTML injection (audit_md_html_inject.sh)

Scenario: A forum realm returns user post content directly from Render(). An attacker
inserts raw HTML tags that may be rendered by the browser.

Detection: Render() contains <b>ADMIN unescaped → VULNERABLE

Note: gnoweb currently escapes raw HTML tags (renders &lt;b&gt;), but the vulnerability
exists at the VM level — any client that does not escape will be affected.


Vector 3 — Link URL hijacking (audit_md_link_hijack.sh)

Scenario: A realm stores a user message containing a markdown link. The display text
mimics an official gno.land URL while the href points to a phishing site.

Detection: Render() contains phishing.example.comVULNERABLE


Vector 4 — Blockquote context confusion (audit_md_blockquote.sh)

Scenario: A comment realm renders user posts inline in Render(). An attacker formats
a comment as a blockquote to visually impersonate an official core-team endorsement.

Detection: Render() contains @core-team inside a blockquote → VULNERABLE


Vector 5 — External image tracking pixel (audit_md_image_tracking.sh)

Scenario: An attacker embeds an external image URL in a realm. Every gnoweb visitor's
browser loads the external resource, deanonymizing viewers via IP logging. If gnoweb ever
fetches images server-side, this also becomes an SSRF vector.

Detection: Render() contains attacker.example.comVULNERABLE


Detection pattern

All scripts follow the same structure:

RESULT=$(gnokey query "vm/qeval" -data "${PKGPATH}.Render(\"\")" -remote "$RPC")

if echo "$RESULT" | grep -q "$MARKER"; then
    echo "⚠️  VULNERABLE — ... (expected on master)"
    echo "   Reference: https://github.com/gnolang/gno/pull/5714"
    exit 1
elif echo "$RESULT" | grep -q "$PATCHED_MARKER"; then
    echo "✅ PATCHED"
else
    echo "⚠️  UNKNOWN OUTPUT"
    exit 1
fi

Detection is VM-only (vm/qeval). Gnoweb rendering is not asserted — the vulnerability
lives at the realm level regardless of the consumer.


…LE — gnolang/gno#5714)

Adds 4 shell scripts under tests/samourai-crew/security-markdown/ that each
deploy a minimal Gno realm, inject a malicious markdown payload, and verify
that Render() returns the content unsanitised (exit 1 = KNOWN VULNERABLE on
current master, regression tests for when gnolang/gno#5714 lands).

Vectors: title leak into body, raw HTML injection, link URL hijacking,
blockquote context confusion.
…fix title injection

- Add audit_md_anchor_hijack.sh: fragment ID hijacking via duplicate heading
- Add audit_md_image_tracking.sh: external tracking pixel injection
- Translate all scripts and realm code from French to English
- Fix audit_md_title_leak.sh: use printf to pass real newlines to gnokey args
  (shell double-quotes kept \n literal, causing false positive detection)
- All scripts target gnolang/gno#5714
…acking requires gnoweb, not testable at VM level
@aeddi aeddi force-pushed the feat/security-markdown-audit branch from a56ff8b to 2672023 Compare May 27, 2026 15:58
Copy link
Copy Markdown
Contributor

@aeddi aeddi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants