Skip to content

Commit 1c361de

Browse files
authored
Merge pull request #933 from nobu/h1-1187156-xss
Escape links
2 parents 369e4fa + 5dedb57 commit 1c361de

File tree

4 files changed

+87
-11
lines changed

4 files changed

+87
-11
lines changed

lib/rdoc/generator/template/darkfish/index.rhtml

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@
1717
main_page = @files.find { |f| f.full_name == @options.main_page } then %>
1818
<%= main_page.description %>
1919
<%- else -%>
20-
<p>This is the API documentation for <%= @title %>.
20+
<p>This is the API documentation for <%= h @title %>.
2121
<%- end -%>
2222
</main>

lib/rdoc/markup/to_html.rb

+12-10
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def init_link_notation_regexp_handlings
8484
def handle_RDOCLINK url # :nodoc:
8585
case url
8686
when /^rdoc-ref:/
87-
$'
87+
CGI.escapeHTML($')
8888
when /^rdoc-label:/
8989
text = $'
9090

@@ -95,13 +95,11 @@ def handle_RDOCLINK url # :nodoc:
9595
else text
9696
end
9797

98-
gen_url url, text
98+
gen_url CGI.escapeHTML(url), CGI.escapeHTML(text)
9999
when /^rdoc-image:/
100-
"<img src=\"#{$'}\">"
101-
else
102-
url =~ /\Ardoc-[a-z]+:/
103-
104-
$'
100+
%[<img src=\"#{CGI.escapeHTML($')}\">]
101+
when /\Ardoc-[a-z]+:/
102+
CGI.escapeHTML($')
105103
end
106104
end
107105

@@ -125,7 +123,7 @@ def handle_regexp_HARD_BREAK target
125123
# Reference to a local file relative to the output directory.
126124

127125
def handle_regexp_HYPERLINK(target)
128-
url = target.text
126+
url = CGI.escapeHTML(target.text)
129127

130128
gen_url url, url
131129
end
@@ -154,9 +152,13 @@ def handle_regexp_TIDYLINK(target)
154152
text =~ /^\{(.*)\}\[(.*?)\]$/ or text =~ /^(\S+)\[(.*?)\]$/
155153

156154
label = $1
157-
url = $2
155+
url = CGI.escapeHTML($2)
158156

159-
label = handle_RDOCLINK label if /^rdoc-image:/ =~ label
157+
if /^rdoc-image:/ =~ label
158+
label = handle_RDOCLINK(label)
159+
else
160+
label = CGI.escapeHTML(label)
161+
end
160162

161163
gen_url url, label
162164
end

test/rdoc/test_rdoc_generator_darkfish.rb

+21
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,22 @@ def test_template_stylesheets
248248
assert_include File.read('index.html'), %Q[href="./#{base}"]
249249
end
250250

251+
def test_title
252+
title = "RDoc Test".freeze
253+
@options.title = title
254+
@g.generate
255+
256+
assert_main_title(File.read('index.html'), title)
257+
end
258+
259+
def test_title_escape
260+
title = %[<script>alert("RDoc")</script>].freeze
261+
@options.title = title
262+
@g.generate
263+
264+
assert_main_title(File.read('index.html'), title)
265+
end
266+
251267
##
252268
# Asserts that +filename+ has a link count greater than 1 if hard links to
253269
# @tmpdir are supported.
@@ -271,4 +287,9 @@ def assert_hard_link filename
271287
"#{filename} is not hard-linked"
272288
end
273289

290+
def assert_main_title(content, title)
291+
title = CGI.escapeHTML(title)
292+
assert_equal(title, content[%r[<title>(.*?)<\/title>]im, 1])
293+
assert_include(content[%r[<main\s[^<>]*+>\s*(.*?)</main>]im, 1], title)
294+
end
274295
end

test/rdoc/test_rdoc_markup_to_html.rb

+53
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,26 @@ def test_convert_RDOCLINK_ref
665665
assert_equal "\n<p>C</p>\n", result
666666
end
667667

668+
def test_convert_RDOCLINK_escape_image
669+
assert_escaped '<script>', 'rdoc-image:"><script>alert(`rdoc-image`)</script>"'
670+
end
671+
672+
def test_convert_RDOCLINK_escape_label_id
673+
assert_escaped '<script>', 'rdoc-label::path::"><script>alert(`rdoc-label_id`)</script>"'
674+
end
675+
676+
def test_convert_RDOCLINK_escape_label_path
677+
assert_escaped '<script>', 'rdoc-label::"><script>alert(`rdoc-label_path`)</script>"'
678+
end
679+
680+
def test_convert_RDOCLINK_escape_ref
681+
assert_escaped '<script>', 'rdoc-ref:"><script>alert(`rdoc-ref`)</script>"'
682+
end
683+
684+
def test_convert_RDOCLINK_escape_xxx
685+
assert_escaped '<script>', 'rdoc-xxx:"><script>alert(`rdoc-xxx`)</script>"'
686+
end
687+
668688
def test_convert_TIDYLINK_footnote
669689
result = @to.convert 'text{*1}[rdoc-label:foottext-1:footmark-1]'
670690

@@ -690,6 +710,11 @@ def test_convert_TIDYLINK_image
690710
"\n<p><a href=\"http://example.com\"><img src=\"path/to/image.jpg\"></a></p>\n"
691711

692712
assert_equal expected, result
713+
714+
result =
715+
@to.convert '{rdoc-image:<script>alert`link text`</script>}[http://example.com]'
716+
717+
assert_not_include result, "<script>"
693718
end
694719

695720
def test_convert_TIDYLINK_rdoc_label
@@ -704,6 +729,23 @@ def test_convert_TIDYLINK_irc
704729
assert_equal "\n<p><a href=\"irc://irc.freenode.net/#ruby-lang\">ruby-lang</a></p>\n", result
705730
end
706731

732+
def test_convert_TIDYLINK_escape_text
733+
assert_escaped '<script>', '{<script>alert`link text`</script>}[a]'
734+
assert_escaped '<script>', 'x:/<script>alert(1);</script>[[]'
735+
end
736+
737+
def test_convert_TIDYLINK_escape_javascript
738+
assert_not_include '{click}[javascript:alert`javascript_scheme`]', '<a href="javascript:'
739+
end
740+
741+
def test_convert_TIDYLINK_escape_onmouseover
742+
assert_escaped '"/onmouseover="', '{onmouseover}[http://"/onmouseover="alert`on_mouse_link`"]'
743+
end
744+
745+
def test_convert_TIDYLINK_escape_onerror
746+
assert_escaped '"onerror="', '{link_image}[http://"onerror="alert`link_image`".png]'
747+
end
748+
707749
def test_convert_with_exclude_tag
708750
assert_equal "\n<p><code>aaa</code>[:symbol]</p>\n", @to.convert('+aaa+[:symbol]')
709751
assert_equal "\n<p><code>aaa[:symbol]</code></p>\n", @to.convert('+aaa[:symbol]+')
@@ -794,6 +836,11 @@ def test_handle_regexp_HYPERLINK_irc
794836
assert_equal '<a href="irc://irc.freenode.net/#ruby-lang">irc.freenode.net/#ruby-lang</a>', link
795837
end
796838

839+
def test_handle_regexp_HYPERLINK_escape
840+
code = 'irc://irc.freenode.net/"><script>alert(`irc`)</script><a"'
841+
assert_escaped '<script>', code
842+
end
843+
797844
def test_list_verbatim_2
798845
str = "* one\n verb1\n verb2\n* two\n"
799846

@@ -903,5 +950,11 @@ def test_accept_table
903950
assert_include(res[%r<<td[^<>]*>.*em.*</td>>], '<em>em</em>')
904951
assert_include(res[%r<<td[^<>]*>.*strong.*</td>>], '<strong>strong</strong>')
905952
end
953+
954+
def assert_escaped(unexpected, code)
955+
result = @to.convert(code)
956+
assert_not_include result, unexpected
957+
assert_include result, CGI.escapeHTML(unexpected)
958+
end
906959
end
907960

0 commit comments

Comments
 (0)