strict_spec.rb 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe HtmlSanitizer::Strict, :aggregate_failures do
  4. def sanitize(input, external: false)
  5. described_class.new.sanitize(input, external: external, timeout: false)
  6. end
  7. describe('#sanitize') do
  8. it 'performs various XSS checks' do # rubocop:disable RSpec/ExampleLength
  9. expect(sanitize('<div class="to-be-removed">test</div><script>alert();</script>')).to eq('<div>test</div>')
  10. expect(sanitize('<b>123</b>')).to eq('<b>123</b>')
  11. expect(sanitize('<script><b>123</b></script>')).to eq('')
  12. expect(sanitize('<script><style><b>123</b></style></script>')).to eq('')
  13. expect(sanitize('<abc><i><b>123</b><bbb>123</bbb></i></abc>')).to eq('<i><b>123</b>123</i>')
  14. expect(sanitize('<abc><i><b>123</b><bbb>123<i><ccc>abc</ccc></i></bbb></i></abc>')).to eq('<i><b>123</b>123<i>abc</i></i>')
  15. expect(sanitize('<not_existing>123</not_existing>')).to eq('123')
  16. expect(sanitize('<script type="text/javascript">alert("XSS!");</script>')).to eq('')
  17. expect(sanitize('<SCRIPT SRC=http://xss.rocks/xss.js></SCRIPT>')).to eq('')
  18. expect(sanitize('<IMG SRC="javascript:alert(\'XSS\');">')).to eq('')
  19. expect(sanitize('<IMG SRC=javascript:alert(\'XSS\')>')).to eq('')
  20. expect(sanitize('<IMG SRC=JaVaScRiPt:alert(\'XSS\')>')).to eq('')
  21. expect(sanitize('<IMG SRC=`javascript:alert("RSnake says, \'XSS\'")`>')).to eq('')
  22. expect(sanitize('<IMG """><SCRIPT>alert("XSS")</SCRIPT>">')).to eq('<img>"&gt;')
  23. expect(sanitize('<IMG SRC=# onmouseover="alert(\'xxs\')">')).to eq('<img src="#">')
  24. expect(sanitize('<IMG SRC="jav ascript:alert(\'XSS\');">')).to eq('')
  25. expect(sanitize('<IMG SRC="jav&#x09;ascript:alert(\'XSS\');">')).to eq('')
  26. expect(sanitize('<IMG SRC="jav&#x0A;ascript:alert(\'XSS\');">')).to eq('')
  27. expect(sanitize('<IMG SRC="jav&#x0D;ascript:alert(\'XSS\');">')).to eq('')
  28. expect(sanitize('<IMG SRC=" &#14; javascript:alert(\'XSS\');">')).to eq('<img src="">')
  29. expect(sanitize('<SCRIPT/XSS SRC="http://xss.rocks/xss.js"></SCRIPT>')).to eq('')
  30. expect(sanitize('<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>')).to eq('')
  31. expect(sanitize('<SCRIPT/SRC="http://xss.rocks/xss.js"></SCRIPT>')).to eq('')
  32. expect(sanitize('<<SCRIPT>alert("XSS");//<</SCRIPT>')).to eq('&lt;')
  33. expect(sanitize('<SCRIPT SRC=http://xss.rocks/xss.js?< B >')).to eq('')
  34. expect(sanitize('<SCRIPT SRC=//xss.rocks/.j>')).to eq('')
  35. expect(sanitize('<IMG SRC="javascript:alert(\'XSS\')"')).to eq('')
  36. expect(sanitize('<IMG SRC="javascript:alert(\'XSS\')" abc<b>123</b>')).to eq('123')
  37. expect(sanitize('<iframe src=http://xss.rocks/scriptlet.html <')).to eq('')
  38. expect(sanitize('</script><script>alert(\'XSS\');</script>')).to eq('')
  39. expect(sanitize('<STYLE>li {list-style-image: url("javascript:alert(\'XSS\')");}</STYLE><UL><LI>XSS</br>')).to eq('<ul><li>XSS</li></ul>')
  40. expect(sanitize('<IMG SRC=\'vbscript:msgbox("XSS")\'>')).to eq('')
  41. expect(sanitize('<IMG SRC="livescript:[code]">')).to eq('')
  42. expect(sanitize('<svg/onload=alert(\'XSS\')>')).to eq('')
  43. expect(sanitize('<BODY ONLOAD=alert(\'XSS\')>')).to eq('')
  44. expect(sanitize('<LINK REL="stylesheet" HREF="javascript:alert(\'XSS\');">')).to eq('')
  45. expect(sanitize('<STYLE>@import\'http://xss.rocks/xss.css\';</STYLE>')).to eq('')
  46. expect(sanitize('<META HTTP-EQUIV="Link" Content="<http://xss.rocks/xss.css>; REL=stylesheet">')).to eq('')
  47. expect(sanitize('<IMG STYLE="java/*XSS*/script:(alert(\'XSS\'), \'\')">')).to eq('<img>')
  48. expect(sanitize('<IMG src="java/*XSS*/script:(alert(\'XSS\'), \'\')">')).to eq('')
  49. expect(sanitize('<IFRAME SRC="javascript:alert(\'XSS\');"></IFRAME>')).to eq('')
  50. expect(sanitize('<TABLE><TD BACKGROUND="javascript:alert(\'XSS\')">')).to eq('<table><td></td></table>')
  51. expect(sanitize('<DIV STYLE="background-image: url(javascript:alert(\'XSS\'), \'\')">')).to eq('<div></div>')
  52. expect(sanitize('<a href="/some/path">test</a>')).to eq('<a href="/some/path">test</a>')
  53. expect(sanitize('<a href="https://some/path">test</a>')).to eq('<a href="https://some/path" rel="nofollow noreferrer noopener" target="_blank" title="https://some/path">test</a>')
  54. expect(sanitize('<a href="https://some/path">test</a>', external: true)).to eq('<a href="https://some/path" rel="nofollow noreferrer noopener" target="_blank" title="https://some/path">test</a>')
  55. expect(sanitize('<XML ID="xss"><I><B><IMG SRC="javas<!-- -->cript:alert(\'XSS\')"></B></I></XML>')).to eq('<i><b></b></i>')
  56. expect(sanitize('<IMG SRC="javas<!-- -->cript:alert(\'XSS\')">')).to eq('')
  57. expect(sanitize(' <HEAD><META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=UTF-7"> </HEAD>+ADw-SCRIPT+AD4-alert(\'XSS\');+ADw-/SCRIPT+AD4-')).to eq(' +ADw-SCRIPT+AD4-alert(\'XSS\');+ADw-/SCRIPT+AD4-')
  58. expect(sanitize('<SCRIPT a=">" SRC="httx://xss.rocks/xss.js"></SCRIPT>')).to eq('')
  59. expect(sanitize("<A HREF=\"h\ntt p://6 6.000146.0x7.147/\">XSS</A>")).to eq('<a href="h%0Att%20%20p://6%206.000146.0x7.147/" rel="nofollow noreferrer noopener" target="_blank" title="h%0Att%20%20p://6%206.000146.0x7.147/">XSS</a>')
  60. expect(sanitize("<A HREF=\"h\ntt p://6 6.000146.0x7.147/\">XSS</A>", external: true)).to eq('<a href="http://h%0Att%20%20p://6%206.000146.0x7.147/" rel="nofollow noreferrer noopener" target="_blank" title="http://h%0Att%20%20p://6%206.000146.0x7.147/">XSS</a>')
  61. expect(sanitize('<A HREF="//www.google.com/">XSS</A>')).to eq('<a href="//www.google.com/" rel="nofollow noreferrer noopener" target="_blank" title="//www.google.com/">XSS</a>')
  62. expect(sanitize('<A HREF="//www.google.com/">XSS</A>', external: true)).to eq('<a href="//www.google.com/" rel="nofollow noreferrer noopener" target="_blank" title="//www.google.com/">XSS</a>')
  63. expect(sanitize('<form id="test"></form><button form="test" formaction="javascript:alert(1)">X</button>')).to eq('X')
  64. expect(sanitize('<maction actiontype="statusline#http://google.com" xlink:href="javascript:alert(2)">CLICKME</maction>')).to eq('CLICKME')
  65. expect(sanitize('<a xlink:href="javascript:alert(2)">CLICKME</a>')).to eq('CLICKME')
  66. expect(sanitize('<a xlink:href="javascript:alert(2)">CLICKME</a>', external: true)).to eq('CLICKME')
  67. expect(sanitize('<!--<img src="--><img src=x onerror=alert(1)//">')).to eq('<img src="x">')
  68. expect(sanitize('<![><img src="]><img src=x onerror=alert(1)//">')).to eq('<img src="]&gt;&lt;img%20src=x%20onerror=alert(1)//">')
  69. expect(sanitize('<svg><![CDATA[><image xlink:href="]]><img src=xx:x onerror=alert(2)//"></svg>')).to eq('')
  70. expect(sanitize('<abc><img src="</abc><img src=x onerror=alert(1)//">')).to eq('<img src="&lt;/abc&gt;&lt;img%20src=x%20onerror=alert(1)//">')
  71. expect(sanitize('<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></object>')).to eq('')
  72. expect(sanitize('<embed src="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></embed>')).to eq('')
  73. expect(sanitize('<img[a][b]src=x[d]onerror[c]=[e]"alert(1)">')).to eq('<img>')
  74. expect(sanitize('<a href="[a]java[b]script[c]:alert(1)">XXX</a>')).to eq('<a href="[a]java[b]script[c]:alert(1)">XXX</a>')
  75. expect(sanitize('<a href="[a]java[b]script[c]:alert(1)">XXX</a>', external: true)).to eq('<a href="http://[a]java[b]script[c]:alert(1)" rel="nofollow noreferrer noopener" target="_blank" title="http://[a]java[b]script[c]:alert(1)">XXX</a>')
  76. expect(sanitize('<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script></svg>')).to eq('')
  77. end
  78. it 'performs style cleanups' do
  79. expect(sanitize('<a style="position:fixed;top:0;left:0;width: 260px;height:100vh;background-color:red;display: block;" href="http://example.com"></a>')).to eq('<a href="http://example.com" rel="nofollow noreferrer noopener" target="_blank" title="http://example.com"></a>')
  80. expect(sanitize('<a style="position:fixed;top:0;left:0;width: 260px;height:100vh;background-color:red;display: block;" href="http://example.com"></a>', external: true)).to eq('<a href="http://example.com" rel="nofollow noreferrer noopener" target="_blank" title="http://example.com"></a>')
  81. expect(sanitize('<table><tr style="font-size: 0"><td>123</td></tr></table>')).to eq('<table><tr><td>123</td></tr></table>')
  82. expect(sanitize('<table><tr style="font-size: 0px"><td>123</td></tr></table>')).to eq('<table><tr><td>123</td></tr></table>')
  83. expect(sanitize('<table><tr style="font-size: 0pt"><td>123</td></tr></table>')).to eq('<table><tr><td>123</td></tr></table>')
  84. expect(sanitize('<table><tr style="font-size:0"><td>123</td></tr></table>')).to eq('<table><tr><td>123</td></tr></table>')
  85. expect(sanitize('<table><tr style="font-Size:0px"><td>123</td></tr></table>')).to eq('<table><tr><td>123</td></tr></table>')
  86. expect(sanitize('<table><tr style="font-size:0em"><td>123</td></tr></table>')).to eq('<table><tr><td>123</td></tr></table>')
  87. expect(sanitize('<table><tr style=" Font-size:0%"><td>123</td></tr></table>')).to eq('<table><tr><td>123</td></tr></table>')
  88. expect(sanitize('<table><tr style="font-size:0%;display: none;"><td>123</td></tr></table>')).to eq('<table><tr><td>123</td></tr></table>')
  89. expect(sanitize('<table><tr style="font-size:0%;visibility:hidden;"><td>123</td></tr></table>')).to eq('<table><tr><td>123</td></tr></table>')
  90. expect(sanitize('<table><tr style="font-size:0%;visibility:hidden;"><td>123</td></tr></table>')).to eq('<table><tr><td>123</td></tr></table>')
  91. expect(sanitize('<html><body><div style="font-family: Meiryo, メイリオ, &quot;Hiragino Sans&quot;, sans-serif; font-size: 12pt; color: rgb(0, 0, 0);">このアドレスへのメルマガを解除してください。</div></body></html>')).to eq('<div>このアドレスへのメルマガを解除してください。</div>')
  92. end
  93. context 'when performing multiline style cleanup' do
  94. let(:input) { <<~INPUT }
  95. <div>
  96. <style type="text/css">#outlook A {
  97. .content { WIDTH: 100%; MAX-WIDTH: 740px }
  98. A { COLOR: #666666; TEXT-DECORATION: none }
  99. A:link { COLOR: #666666; TEXT-DECORATION: none }
  100. A:hover { COLOR: #666666; TEXT-DECORATION: none }
  101. A:active { COLOR: #666666; TEXT-DECORATION: none }
  102. A:focus { COLOR: #666666; TEXT-DECORATION: none }
  103. BODY { FONT-FAMILY: Calibri, Arial, Verdana, sans-serif }
  104. </style>
  105. <!--[if (gte mso 9)|(IE)]>
  106. <META name=GENERATOR content="MSHTML 9.00.8112.16800"></HEAD>
  107. <BODY bgColor=#ffffff>
  108. <DIV><FONT size=2 face=Arial></FONT>&nbsp;</DIV>
  109. <BLOCKQUOTE
  110. style="BORDER-LEFT: #000000 2px solid; PADDING-LEFT: 5px; PADDING-RIGHT: 0px; MARGIN-LEFT: 5px; MARGIN-RIGHT: 0px">
  111. <DIV style="FONT: 10pt arial">----- Original Message ----- </DIV>
  112. <DIV style="FONT: 10pt arial"><B>To:</B> <A title=smith.test@example.dk
  113. href="mailto:smith.test@example.dk">smith.test@example.dk</A> </DIV>
  114. <DIV style="FONT: 10pt arial"><B>Sent:</B> Friday, November 10, 2017 9:11
  115. PM</DIV>
  116. <DIV style="FONT: 10pt arial"><B>Subject:</B> Din bestilling hos
  117. example.dk - M123 - KD1234</DIV>
  118. <div>&nbsp;</div>
  119. <![endif]-->test 123
  120. <blockquote></div>
  121. INPUT
  122. let(:output) { <<~OUTPUT }
  123. <div>
  124. test 123
  125. <blockquote></blockquote>
  126. </div>
  127. OUTPUT
  128. it 'filters correctly' do
  129. expect(sanitize(input)).to eq(output)
  130. end
  131. end
  132. context 'when performing more multiline style cleanup' do
  133. let(:input) { <<~INPUT }
  134. <style><!--
  135. /* Font Definitions */
  136. @font-face
  137. {font-family:"Cambria Math";
  138. panose-1:2 4 5 3 5 4 6 3 2 4;}
  139. {page:WordSection1;}</style><!--[if gte mso 9]><xml>
  140. <o:shapedefaults v:ext="edit" spidmax="1026" />
  141. </xml><![endif]--><!--[if gte mso 9]><xml>
  142. <o:shapelayout v:ext="edit">
  143. <o:idmap v:ext="edit" data="1" />
  144. </o:shapelayout></xml><![endif]-->
  145. <div>123</div>
  146. <a href="#DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2" width="1" height="1">abc</a></div>
  147. INPUT
  148. let(:output) { <<~OUTPUT }
  149. <div>123</div>
  150. <a href="#DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2">abc</a>
  151. OUTPUT
  152. it 'filters correctly' do
  153. expect(sanitize(input)).to eq(output)
  154. end
  155. end
  156. it 'handles mailto: links' do
  157. expect(sanitize('<a href="mailto:testäöü@example.com" id="123">test</a>')).to eq('<a href="mailto:test%C3%A4%C3%B6%C3%BC@example.com">test</a>')
  158. end
  159. context 'when handling code blocks' do
  160. let(:input) { <<~INPUT }
  161. <pre><code>apt-get update
  162. Get:1 http://security.ubuntu.com/ubuntu focal-security InRelease [114 kB]
  163. Hit:2 http://de.archive.ubuntu.com/ubuntu focal InRelease
  164. Hit:3 http://de.archive.ubuntu.com/ubuntu focal-updates InRelease
  165. Get:4 http://10.10.21.205:3207/dprepo/ubuntu experimental/20.04_x86_64/ InR=
  166. elease [3820 B]
  167. Hit:5 http://de.archive.ubuntu.com/ubuntu focal-backports InRelease
  168. Get:6 http://10.10.21.205:3207/dprepo/ubuntu 20.04_x86_64/ InRelease [3781 =
  169. B]
  170. Get:7 http://10.10.21.205:3207/dprepo/ubuntu experimental/20.04_x86_64/ Sou=
  171. rces [2710 B]
  172. Get:8 http://10.10.21.205:3207/dprepo/ubuntu experimental/20.04_x86_64/ Pac=
  173. kages [6507 B]
  174. Get:9 http://10.10.21.205:3207/dprepo/ubuntu 20.04_x86_64/ Sources [9066 B]
  175. Get:10 http://10.10.21.205:3207/dprepo/ubuntu 20.04_x86_64/ Packages [23.8 =
  176. kB]
  177. Get:11 http://security.ubuntu.com/ubuntu focal-security/main amd64 DEP-11 M=
  178. etadata [40.6 kB]
  179. Get:12 http://security.ubuntu.com/ubuntu focal-security/universe amd64 DEP-=
  180. 11 Metadata [66.3 kB]
  181. Get:13 http://security.ubuntu.com/ubuntu focal-security/multiverse amd64 DE=
  182. P-11 Metadata [2464 B]
  183. Fetched 273 kB in 1s (288 kB/s)
  184. Reading package lists...
  185. Batterie-Status pr&uuml;fen
  186. Reading package lists...
  187. Building dependency tree...</code></pre>
  188. INPUT
  189. let(:output) { <<~OUTPUT }
  190. <pre><code>apt-get update
  191. Get:1 http://security.ubuntu.com/ubuntu focal-security InRelease [114 kB]
  192. Hit:2 http://de.archive.ubuntu.com/ubuntu focal InRelease
  193. Hit:3 http://de.archive.ubuntu.com/ubuntu focal-updates InRelease
  194. Get:4 http://10.10.21.205:3207/dprepo/ubuntu experimental/20.04_x86_64/ InR=
  195. elease [3820 B]
  196. Hit:5 http://de.archive.ubuntu.com/ubuntu focal-backports InRelease
  197. Get:6 http://10.10.21.205:3207/dprepo/ubuntu 20.04_x86_64/ InRelease [3781 =
  198. B]
  199. Get:7 http://10.10.21.205:3207/dprepo/ubuntu experimental/20.04_x86_64/ Sou=
  200. rces [2710 B]
  201. Get:8 http://10.10.21.205:3207/dprepo/ubuntu experimental/20.04_x86_64/ Pac=
  202. kages [6507 B]
  203. Get:9 http://10.10.21.205:3207/dprepo/ubuntu 20.04_x86_64/ Sources [9066 B]
  204. Get:10 http://10.10.21.205:3207/dprepo/ubuntu 20.04_x86_64/ Packages [23.8 =
  205. kB]
  206. Get:11 http://security.ubuntu.com/ubuntu focal-security/main amd64 DEP-11 M=
  207. etadata [40.6 kB]
  208. Get:12 http://security.ubuntu.com/ubuntu focal-security/universe amd64 DEP-=
  209. 11 Metadata [66.3 kB]
  210. Get:13 http://security.ubuntu.com/ubuntu focal-security/multiverse amd64 DE=
  211. P-11 Metadata [2464 B]
  212. Fetched 273 kB in 1s (288 kB/s)
  213. Reading package lists...
  214. Batterie-Status prüfen
  215. Reading package lists...
  216. Building dependency tree...</code></pre>
  217. OUTPUT
  218. it 'handles code blocks correctly' do
  219. expect(sanitize(input)).to eq(output)
  220. end
  221. end
  222. context 'when checking attachment URLs' do
  223. let(:api_path) { Rails.configuration.api_path }
  224. let(:http_type) { Setting.get('http_type') }
  225. let(:fqdn) { Setting.get('fqdn') }
  226. let(:attachment_url) { "#{http_type}://#{fqdn}#{api_path}/ticket_attachment/239/986/1653" }
  227. let(:attachment_url_good) { "#{attachment_url}?disposition=attachment" }
  228. let(:attachment_url_evil) { "#{attachment_url}?disposition=inline" }
  229. let(:different_fqdn_url) { attachment_url_evil.gsub(fqdn, 'some.other.tld') }
  230. let(:attachment_url_evil_other) { "#{attachment_url}?disposition=some_other" }
  231. it 'handles attachment URLs correctly' do
  232. expect(sanitize('<a href="/some/path%20test.pdf">test</a>')).to eq('<a href="/some/path%20test.pdf">test</a>')
  233. expect(sanitize('<a href="https://somehost.domain/path%20test.pdf">test</a>')).to eq('<a href="https://somehost.domain/path%20test.pdf" rel="nofollow noreferrer noopener" target="_blank" title="https://somehost.domain/path%20test.pdf">test</a>')
  234. expect(sanitize('<a href="https://somehost.domain/zaihan%20test">test</a>')).to eq('<a href="https://somehost.domain/zaihan%20test" rel="nofollow noreferrer noopener" target="_blank" title="https://somehost.domain/zaihan%20test">test</a>')
  235. expect(sanitize("<a href=\"#{attachment_url_evil}\">Evil link</a>")).to eq("<a href=\"#{attachment_url_good}\" rel=\"nofollow noreferrer noopener\" target=\"_blank\" title=\"#{attachment_url_good}\">Evil link</a>")
  236. expect(sanitize("<a href=\"#{attachment_url_good}\">Good link</a>")).to eq("<a href=\"#{attachment_url_good}\" rel=\"nofollow noreferrer noopener\" target=\"_blank\" title=\"#{attachment_url_good}\">Good link</a>")
  237. expect(sanitize("<a href=\"#{attachment_url}\">No disposition</a>")).to eq("<a href=\"#{attachment_url}\" rel=\"nofollow noreferrer noopener\" target=\"_blank\" title=\"#{attachment_url}\">No disposition</a>")
  238. expect(sanitize("<a href=\"#{different_fqdn_url}\">Different FQDN</a>")).to eq("<a href=\"#{different_fqdn_url}\" rel=\"nofollow noreferrer noopener\" target=\"_blank\" title=\"#{different_fqdn_url}\">Different FQDN</a>")
  239. expect(sanitize("<a href=\"#{attachment_url_evil_other}\">Evil link</a>")).to eq("<a href=\"#{attachment_url_good}\" rel=\"nofollow noreferrer noopener\" target=\"_blank\" title=\"#{attachment_url_good}\">Evil link</a>")
  240. end
  241. end
  242. end
  243. end