Compare commits

..

6 Commits

Author SHA1 Message Date
rubenwardy
5e60cb83de Add XSS strings to test data 2020-01-22 23:45:40 +00:00
rubenwardy
595d6ea3b6 Use server-side markdown renderer in WYSIWYG preview
Fixes #117
2020-01-22 23:10:06 +00:00
rubenwardy
71fa62fd6a Update EasyMDE 2020-01-22 22:47:24 +00:00
rubenwardy
be5bb11fe3 Add data* to docker ignore 2020-01-22 22:11:01 +00:00
rubenwardy
981ae74e5c Improve markdown escaping
Fixes #118
2020-01-22 22:10:02 +00:00
rubenwardy
2b66193969 Fix version being styled differently 2020-01-21 23:42:41 +00:00
13 changed files with 166 additions and 21 deletions

View File

@@ -1,5 +1,5 @@
.git .git
data data*
uploads uploads
*.pyc *.pyc
__pycache__ __pycache__

View File

@@ -20,7 +20,6 @@ from flask_user import *
from flask_gravatar import Gravatar from flask_gravatar import Gravatar
import flask_menu as menu import flask_menu as menu
from flask_mail import Mail from flask_mail import Mail
from flaskext.markdown import Markdown
from flask_github import GitHub from flask_github import GitHub
from flask_wtf.csrf import CsrfProtect from flask_wtf.csrf import CsrfProtect
from flask_flatpages import FlatPages from flask_flatpages import FlatPages
@@ -35,7 +34,6 @@ app.config.from_pyfile(os.environ["FLASK_CONFIG"])
r = redis.Redis.from_url(app.config["REDIS_URL"]) r = redis.Redis.from_url(app.config["REDIS_URL"])
menu.Menu(app=app) menu.Menu(app=app)
markdown = Markdown(app, extensions=["fenced_code"], safe_mode=True, output_format="html5")
github = GitHub(app) github = GitHub(app)
csrf = CsrfProtect(app) csrf = CsrfProtect(app)
mail = Mail(app) mail = Mail(app)
@@ -59,9 +57,13 @@ if not app.debug and app.config["MAIL_UTILS_ERROR_SEND_TO"]:
register_mail_error_handler(app, mail) register_mail_error_handler(app, mail)
@babel.localeselector from .markdown import init_app
def get_locale(): init_app(app)
return request.accept_languages.best_match(app.config['LANGUAGES'].keys())
# @babel.localeselector
# def get_locale():
# return request.accept_languages.best_match(app.config['LANGUAGES'].keys())
from . import models, tasks, template_filters from . import models, tasks, template_filters

View File

@@ -19,8 +19,10 @@ from flask import *
from flask_user import * from flask_user import *
from . import bp from . import bp
from .auth import is_api_authd from .auth import is_api_authd
from app import csrf
from app.models import * from app.models import *
from app.utils import is_package_page from app.utils import is_package_page
from app.markdown import render_markdown
from app.querybuilder import QueryBuilder from app.querybuilder import QueryBuilder
@bp.route("/api/packages/") @bp.route("/api/packages/")
@@ -107,3 +109,9 @@ def whoami(token):
return jsonify({ "is_authenticated": False, "username": None }) return jsonify({ "is_authenticated": False, "username": None })
else: else:
return jsonify({ "is_authenticated": True, "username": token.owner.username }) return jsonify({ "is_authenticated": True, "username": token.owner.username })
@bp.route("/api/markdown/", methods=["POST"])
@csrf.exempt
def clean_markdown():
return render_markdown(request.data.decode("utf-8"))

View File

@@ -18,7 +18,7 @@
from flask import * from flask import *
from flask_user import * from flask_user import *
from flask_login import login_user, logout_user from flask_login import login_user, logout_user
from app import markdown from app.markdown import render_markdown
from . import bp from . import bp
from app.models import * from app.models import *
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
@@ -153,7 +153,7 @@ def send_email(username):
form = SendEmailForm(request.form) form = SendEmailForm(request.form)
if form.validate_on_submit(): if form.validate_on_submit():
text = form.text.data text = form.text.data
html = markdown(text) html = render_markdown(text)
task = sendEmailRaw.delay([user.email], form.subject.data, text, html) task = sendEmailRaw.delay([user.email], form.subject.data, text, html)
return redirect(url_for("tasks.check", id=task.id, r=next_url)) return redirect(url_for("tasks.check", id=task.id, r=next_url))

View File

@@ -304,6 +304,38 @@ No warranty is provided, express or implied, for any part of the project.
game1.desc = """ game1.desc = """
As seen on the Capture the Flag server (minetest.rubenwardy.com:30000) As seen on the Capture the Flag server (minetest.rubenwardy.com:30000)
` `[`javascript:/*--></title></style></textarea></script></xmp><svg/onload='+/"/+/onmouseover=1/+/`](javascript:/*--%3E%3C/title%3E%3C/style%3E%3C/textarea%3E%3C/script%3E%3C/xmp%3E%3Csvg/onload='+/%22/+/onmouseover=1/+/)`[*/[]/+alert(1)//'>`
<IMG SRC="javascript:alert('XSS');">
<IMG SRC=javascript:alert(&amp;quot;XSS&amp;quot;)>
``<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>``
\<a onmouseover="alert(document.cookie)"\>xxs link\</a\>
\<a onmouseover=alert(document.cookie)\>xxs link\</a\>
<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>
<script>alert("hello");</script>
<SCRIPT SRC=`[`http://xss.rocks/xss.js></SCRIPT>`](http://xss.rocks/xss.js%3E%3C/SCRIPT%3E)`;`
`<IMG \"\"\">`
<SCRIPT>
alert("XSS")
</SCRIPT>
<IMG SRC= onmouseover="alert('xxs')">
<img src=x onerror="&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041">
"\>
Uses the CTF PvP Engine. Uses the CTF PvP Engine.
""" """

63
app/markdown.py Normal file
View File

@@ -0,0 +1,63 @@
import bleach
from markdown import Markdown
from flask import Markup
# Whitelist source: MIT
#
# https://github.com/Wenzil/mdx_bleach/blob/master/mdx_bleach/whitelist.py
"""
Default whitelist of allowed HTML tags. Any other HTML tags will be escaped or
stripped from the text. This applies to the html output that Markdown produces.
"""
ALLOWED_TAGS = [
'ul',
'ol',
'li',
'p',
'pre',
'code',
'blockquote',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'hr',
'br',
'strong',
'em',
'a',
'img'
]
"""
Default whitelist of attributes. It allows the href and title attributes for <a>
tags and the src, title and alt attributes for <img> tags. Any other attribute
will be stripped from its tag.
"""
ALLOWED_ATTRIBUTES = {
'a': ['href', 'title'],
'img': ['src', 'title', 'alt']
}
"""
If you allow tags that have attributes containing a URI value
(like the href attribute of an anchor tag,) you may want to adapt
the accepted protocols. The default list only allows http, https and mailto.
"""
ALLOWED_PROTOCOLS = ['http', 'https', 'mailto']
md = Markdown(extensions=["fenced_code"], output_format="html5")
def render_markdown(source):
return bleach.clean(md.convert(source), \
tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES, \
styles=[], protocols=ALLOWED_PROTOCOLS)
def init_app(app):
@app.template_filter()
def markdown(source):
return Markup(render_markdown(source))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,34 @@
$("textarea.markdown").each(function() {
async function render(plainText, preview) {
const response = await fetch(new Request("/api/markdown/", {
method: "POST",
credentials: "same-origin",
body: plainText,
headers: {
"Accept": "text/html; charset=UTF-8",
},
}));
preview.innerHTML = await response.text();
}
let timeout_id = null;
new EasyMDE({
element: this,
hideIcons: ["image"],
forceSync: true,
previewRender: (plainText, preview) => {
if (timeout_id) {
clearTimeout(timeout_id);
}
timeout_id = setTimeout(() => {
render(plainText, preview);
timeout_id = null;
}, 500);
return preview.innerHTML;
}
});
})

View File

@@ -148,12 +148,9 @@
<script src="/static/bootstrap.min.js"></script> <script src="/static/bootstrap.min.js"></script>
<script src="/static/easymde.min.js"></script> <script src="/static/easymde.min.js"></script>
<link rel="stylesheet" type="text/css" href="/static/easymde.min.css"> <link rel="stylesheet" type="text/css" href="/static/easymde.min.css">
<script>
$("textarea.markdown").each(function() {
new EasyMDE({ element: this, hideIcons: ["image"], forceSync: true });
})
</script>
<link href="/static/fa/css/all.css" rel="stylesheet"> <link href="/static/fa/css/all.css" rel="stylesheet">
<script src="/static/markdowntextarea.js"></script>
{% block scriptextra %}{% endblock %} {% block scriptextra %}{% endblock %}
</body> </body>
</html> </html>

View File

@@ -84,7 +84,7 @@
{% endif %} {% endif %}
</div> </div>
{% if release and (release.min_rel or release.max_rel) %} {% if release and (release.min_rel or release.max_rel) %}
<div class="text-secondary col-md-auto"> <div class="btn col-md-auto">
<img src="https://www.minetest.net/media/icon.svg" style="max-height: 1.2em;"> <img src="https://www.minetest.net/media/icon.svg" style="max-height: 1.2em;">
<span class="count"> <span class="count">
{% if release.min_rel and release.max_rel %} {% if release.min_rel and release.max_rel %}
@@ -364,7 +364,7 @@
</ul> </ul>
</div> </div>
<div class="card my-4""> <div class="card my-4">
<div class="card-header"> <div class="card-header">
{% if package.approved and package.checkPerm(current_user, "CREATE_THREAD") %} {% if package.approved and package.checkPerm(current_user, "CREATE_THREAD") %}
<div class="btn-group float-right"> <div class="btn-group float-right">

View File

@@ -60,7 +60,7 @@ Topics to be Added
{% set perc = 100 * (total - topic_count) / total %} {% set perc = 100 * (total - topic_count) / total %}
<div class="progress-bar bg-success" role="progressbar" <div class="progress-bar bg-success" role="progressbar"
style="width: {{ perc }}%" aria-valuenow="{{ perc }}" aria-valuemin="0" aria-valuemax="100"></div> style="width: {{ perc }}%" aria-valuenow="{{ perc }}" aria-valuemin="0" aria-valuemax="100"></div>
</div> </div>
{% else %} {% else %}
<p> <p>
The forum topic crawler needs to run at least once for this section to work. The forum topic crawler needs to run at least once for this section to work.

View File

@@ -2,7 +2,6 @@ Flask~=1.1
Flask-FlatPages~=0.7 Flask-FlatPages~=0.7
Flask-Gravatar~=0.5 Flask-Gravatar~=0.5
Flask-Login~=0.4.1 Flask-Login~=0.4.1
Flask-Markdown~=0.3
Flask-Menu~=0.7 Flask-Menu~=0.7
Flask-Migrate~=2.3 Flask-Migrate~=2.3
Flask-SQLAlchemy~=2.3 Flask-SQLAlchemy~=2.3
@@ -11,6 +10,9 @@ Flask-Babel
GitHub-Flask~=3.2 GitHub-Flask~=3.2
SQLAlchemy-Searchable~=1.1 SQLAlchemy-Searchable~=1.1
markdown ~= 3.1
bleach ~= 3.1
beautifulsoup4~=4.6 beautifulsoup4~=4.6
celery~=4.4 celery~=4.4
kombu~=4.6 kombu~=4.6