Opened 2 years ago
Closed 22 months ago
#14174 closed defect (fixed)
Possibilities of SQL injection and cross-site scripting
Reported by: | Jun Omae | Owned by: | Ralph Ewig |
---|---|---|---|
Priority: | high | Component: | TracChecklistMacro |
Severity: | critical | Keywords: | |
Cc: | Trac Release: | 1.4 |
Description
DB-API with passing parameters and tag
builder should be used. See trac:TracDev/DatabaseApi#Parameterpassing and trac:TracDev/PortingFromGenshiToJinja#tag.
Locations:
Attachments (1)
Change History (16)
comment:1 Changed 2 years ago by
Status: | new → accepted |
---|
comment:2 Changed 23 months ago by
Priority: | normal → high |
---|---|
Resolution: | → fixed |
Status: | accepted → closed |
Trac Release: | → 1.4 |
Fixed in version 0.1.6: tracchecklistmacro?rev=18503
FIXED updated all sql to follow TracDev Guidelines
DONE used tag constructors (in some places)
The query changes might help with cross-db compatibility, but I've only tested using MariaDB/MySQL as the backend.
The tag constructors require generating html from the "inside-out", rather than "top-down" which was awkward in places. Since their use is not security related (as I understand it), I only used them were they made the code more convenient.
comment:3 follow-up: 7 Changed 23 months ago by
It seem that XSS attack is able via name
parameter at source:/tracchecklistmacro/0.1/checklist/macros.py@18503#L131
- head = "<form class='checklist' method='post' action='"+dest+"'> \n" - head += " <div class='source'> \n" - head += (" TracChecklist: <a href='" + formatter.req.href("/wiki/" + name) + - "'>" + name + "</a>\n") - head += " </div>" + head = tag.form(class_='checklist', method='post', action=dest) + head.append(tag.div('TracChecklist: ', + tag.a(name, href=formatter.req.href('wiki', name)), + class_='source')) + head = unicode(head) # template footer / form backpath = formatter.req.href(formatter.req.path_info) form_token = formatter.req.form_token realm = str(formatter.resource.realm) resource = str(formatter.resource.id ) checklist = str(content ) - foot = Markup(Element('input', type='hidden', name='__backpath__', value=backpath )) - foot += Markup(Element('input', type='hidden', name='__FORM_TOKEN', value=form_token )) - foot += Markup(Element('input', type='hidden', name='realm' , value=realm )) - foot += Markup(Element('input', type='hidden', name='resource' , value=resource )) - foot += Markup(Element('input', type='hidden', name='checklist' , value=checklist )) - foot += Markup(Element('input', type='submit', class_='button' , value='Save Checklist' )) - foot += "</form>" + foot = unicode(tag( + tag.input(type='hidden', name='__backpath__', value=backpath ), + tag.input(type='hidden', name='__FORM_TOKEN', value=form_token ), + tag.input(type='hidden', name='realm' , value=realm ), + tag.input(type='hidden', name='resource' , value=resource ), + tag.input(type='hidden', name='checklist' , value=checklist ), + tag.input(type='submit', class_='button' , value='Save Checklist' ), + ))
comment:4 Changed 23 months ago by
How would you exploit that though? Not that I don't believe you, just trying to understand. From what I can tell, both methods (using the tag constructor vs. string coding) result in the same source for the generated page, so I'm not sure how one is vulnerable but not the other?
comment:5 Changed 23 months ago by
- Create wiki page
TracChecklist/<a href="javascript:alert(42)">XSS</a>
with[[Checklist(<a href="javascript:alert(42)">XSS</a>)]]
.- Visit
SandBox
page, add[wiki:'TracChecklist/<a href="javascript:alert(42)">XSS</a>']
to the page, and save it. - Click the added link to
TracChecklist/<a href="javascript:alert(42)">XSS</a>
page. - Click "Create this page" button
- Store
[[Checklist(<a href="javascript:alert(42)">XSS</a>)]]
in the page.
- Visit
- Click
XSS
(Edit: The page content is revised)
Changed 23 months ago by
Attachment: | checklistmacro-xss.png added |
---|
comment:6 Changed 23 months ago by
Another thing, Option
usage in the plugin is wrong.
-
checklist/macros.py
38 38 _description = "Inserts a checklist sourced from a wiki page template." 39 39 implements(ITemplateProvider) 40 40 41 template_root = Option( 42 'checklist', 'template_root', default='TracChecklist', 43 doc="wiki page root for checklist templates") 44 41 45 def get_htdocs_dirs(self): 42 46 return [('checklist', resource_filename(__name__, 'htdocs'))] 43 47 … … 84 88 check_dict = json.loads(row[0]) 85 89 86 90 # get the template wiki page (create if needed) 87 root = Option('checklist', 'template_root', default='TracChecklist', 88 doc="wiki page root for checklist templates") 89 90 root = self.config.get('checklist', 'template_root') 91 root = self.template_root 91 92 name = str(root) + '/' + content 92 93 name = name.replace('//','/') 93 94 page = WikiPage(self.env, name) … … 262 263 263 264 # list of available checklist templates (pages) under root wiki page 264 265 templates = [] 265 root = Option('checklist', 'template_root', default='TracChecklist', 266 doc="wiki page root for checklist templates") 267 root = self.config.get('checklist', 'template_root') 266 root = self.template_root 268 267 query = "SELECT name FROM wiki WHERE version = 1 AND name LIKE %s" 269 268 param = ['%'+root+'%'] 270 269
comment:7 follow-up: 10 Changed 23 months ago by
Super helpful - thank you. The tag.append() method is great, but I'm unsure how to append the raw html from the converted wiki page? Right now I have:
html = format_to_html(self.env, formatter.context, wiki) html = ... (replace '[x]' with '<input type=checkbox> ...' head = ... (some html) foot = ... (some html) out = unescape(head + html + foot)
So, I tried to construct the form instead using:
html = format_to_html(self.env, formatter.context, wiki) html = ... (replace '[x]' with '<input type=checkbox> ...' form = tag.form (... head = tag.div(... foot = tag(... form.append(head) form.append(html) form.append(foot) out = unicode(form)
But this strips the checkbox functionality from the converted wiki page?
comment:8 Changed 23 months ago by
Resolution: | fixed |
---|---|
Status: | closed → reopened |
comment:9 Changed 23 months ago by
Status: | reopened → accepted |
---|
comment:10 Changed 23 months ago by
But this strips the checkbox functionality from the converted wiki page?
I'm afraid what do "this strips the checkbox functionality" mean? It seems that work like the following. Please explain it.
>>> from trac.test import EnvironmentStub >>> from trac.wiki.formatter import format_to_html >>> from trac.web.chrome import web_context >>> from trac.test import MockRequest >>> from trac.util.html import tag >>> env = EnvironmentStub() >>> req = MockRequest(env) >>> context = web_context(req) >>> rv = format_to_html(env, context, '[x] text') >>> rv Markup(u'<p>\n[x] text\n</p>\n') >>> rv = rv.replace('[x]', tag.input(type='checkbox')) >>> rv Markup(u'<p>\n<input type="checkbox" /> text\n</p>\n') >>> form = tag.form() >>> form.append(rv) >>> unicode(form) u'<form><p>\n<input type="checkbox" /> text\n</p>\n</form>'
comment:11 Changed 23 months ago by
The way the plugin works, a user first creates a checklist template as a wiki page ("edit mode") and that template is then reused to create the clickable checklist in many tickets ("live mode"). In the wiki page (template), a "step" to be checked off later is identified with a pattern of [x] some description \\
.
To generate the page for "live mode", I do the following steps:
- load the wiki page (template) from the database
- convert page to html using
format_to_html(self.env, formatter.context, wiki)
- replace string pattern
[x] some description \\
with checkbox code<input ...>
- wrap the result in a form, and insert it in the ticket display
When I use tag constructors for step 4, the part I'm struggling with is how to keep the html formatting (step 2) and check boxes (step 3), but I think your example above can do that (I'll try it).
comment:12 Changed 23 months ago by
The procedure which replaces [x]
markers in html with checkboxes is buggy. If forgetting \\
at end of line, it is busy loop and eats huge memory (as the results, oom-killer is called).
It could be simplified using re.sub()
.
-
checklist/macros.py
3 3 # Macro that pulls a checklist template from a wiki page, and renders it as 4 4 # a checklist in a ticket description. 5 5 # 6 import json, time6 import json, re, time 7 7 import pkg_resources 8 8 from pkg_resources import resource_filename 9 9 … … 96 96 return("CHECKLIST ERROR: \n template '" + name + "' does not exist.") 97 97 98 98 # strip wiki page header & convert to html 99 wiki = page.text[page.text.find('----')+4:] 99 wiki = page.text 100 sep = wiki.find('----') 101 if sep != -1: 102 wiki = wiki[sep + 4:] 100 103 html = format_to_html(self.env, formatter.context, wiki) 101 104 102 105 # parse template for steps 103 # steps start with [x] and end with \\, which converts to <br /> 104 check_index = 0 105 check_start = html.find('[x]') 106 cbox_re = re.compile(r'\s*\[x\]([^<]+)(<br\s*/?>)?', re.DOTALL) 107 cbox_idx = [0] 106 108 107 while check_start > -1: 108 check_end = html.find('<br />',check_start)+6 109 check_step = 'step_' + str(check_index) 110 check_label = html[check_start+3:check_end] 109 def cbox_repl(match): 110 label = match.group(1) 111 cbox_name = 'step_%d' % cbox_idx[0] 112 cbox_idx[0] += 1 113 br = match.group(2) 114 return (u'<label><input type="checkbox" name="{}" value="done" />' 115 u'{}</label>{}').format(cbox_name, label, br) 111 116 112 check_box = ("<label class='container'>" + check_label + 113 "<input type='checkbox' " + 114 "name='step_" + str(check_index) + "' " + 115 "value='done' ") 117 html = cbox_re.sub(cbox_repl, html) 116 118 117 if check_step in check_dict:118 check_box += "checked"119 120 check_box += "><span class='checkmark'></span></label>"121 html = html[0:check_start] + check_box + html[check_end:]122 123 check_index +=1124 check_start = html.find('[x]')125 126 119 # template header info + form 127 120 dest = formatter.req.href('/checklist/update') 128 121 head = "<form class='checklist' method='post' action='"+dest+"'> \n"
comment:13 follow-up: 14 Changed 22 months ago by
I think I fixed it in v0.1.7. Also improved the conversion of the wiki checklist template into the html checklist instance to make it more robust. It uploaded without error to PyPi and svn, but doesn't show yet in trac-hacks for some reason.
comment:14 Changed 22 months ago by
Replying to Ralph Ewig:
It uploaded without error to PyPi and svn, but doesn't show yet in trac-hacks for some reason.
The issue has been fixed by Ryan.
comment:15 Changed 22 months ago by
Resolution: | → fixed |
---|---|
Status: | accepted → closed |
Great! I just uploaded v0.1.8 which should have everything linted/formatted/spellchecked and a couple more small tweaks to make the code more robust. I'll close this as fixed, but please let me know if anything else needs attention.
Good catch, thank you for reporting this (and the documentation link).