Skip to content

Commit 67a02aa

Browse files
authored
Libclamav: tighten HTML filetype signatures (#1734)
Require a tag boundary after built-in <table and <TABLE HTML filetype signatures so OOXML elements such as <tableStyles> do not retype XML content as HTML. Match the ASCII whitespace bytes accepted by htmlnorm's isspace() handling, and add clamscan coverage for the false positive and accepted table tag boundaries. The built-in HTML FTM signatures for iframe, img, object, and script only matched the start of the tag name. That allowed longer XML or custom element names, such as <scriptlet> or <objectId>, to retype otherwise textual content as HTML. This is the same class of false positive fixed for <table>. Require an HTML tag boundary after those tag names using the same byte set used for the table signature: '>', '/', and the ASCII whitespace bytes accepted by htmlnorm. Broaden the clamscan regression to cover the adjacent false-positive prefixes and the accepted real tag boundary forms. Validation: - cmake --build clamav/build-asan-clamscan --target clamscan -j12 - VERSION=test SOURCE=/Users/val/dev/clamav-dev-workspace/clamav BUILD=/Users/val/dev/clamav-dev-workspace/clamav/build-asan-clamscan TMP=/private/tmp CLAMSCAN=/Users/val/dev/clamav-dev-workspace/clamav/build-asan-clamscan/clamscan/clamscan python3 -m pytest -q clamscan/hash_and_file_type_test.py::TC::test_html_file_type_tag_signatures_require_tag_boundary - git -C clamav diff --check CLAM-3007
1 parent 0a296a0 commit 67a02aa

2 files changed

Lines changed: 107 additions & 13 deletions

File tree

libclamav/filetypes_int.h

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -90,23 +90,23 @@ static const char *ftypes_int[] = {
9090
"1:*:3c48544d4c3e:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
9191
"1:*:3c486561643e:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
9292
"1:*:3c48746d6c3e:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
93-
"1:*:3c494652414d45:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
94-
"1:*:3c494d47:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
95-
"1:*:3c496d67:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
96-
"1:*:3c4f424a454354:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
97-
"1:*:3c4f626a656374:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
98-
"1:*:3c534352495054:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
99-
"1:*:3c536372697074:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
100-
"1:*:3c5441424c45:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
93+
"1:*:3c494652414d45(3e|20|09|0a|0b|0c|0d|2f):HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
94+
"1:*:3c494d47(3e|20|09|0a|0b|0c|0d|2f):HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
95+
"1:*:3c496d67(3e|20|09|0a|0b|0c|0d|2f):HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
96+
"1:*:3c4f424a454354(3e|20|09|0a|0b|0c|0d|2f):HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
97+
"1:*:3c4f626a656374(3e|20|09|0a|0b|0c|0d|2f):HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
98+
"1:*:3c534352495054(3e|20|09|0a|0b|0c|0d|2f):HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
99+
"1:*:3c536372697074(3e|20|09|0a|0b|0c|0d|2f):HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
100+
"1:*:3c5441424c45(3e|20|09|0a|0b|0c|0d|2f):HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
101101
"1:*:3c6120*(68|48)(72|52)4546:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
102102
"1:*:3c6120*(68|48)(72|52)6566:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
103103
"1:*:3c686561643e:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
104104
"1:*:3c68746d6c3e:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
105-
"1:*:3c696672616d65:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
106-
"1:*:3c696d67:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
107-
"1:*:3c6f626a656374:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
108-
"1:*:3c736372697074:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
109-
"1:*:3c7461626c65:HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
105+
"1:*:3c696672616d65(3e|20|09|0a|0b|0c|0d|2f):HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
106+
"1:*:3c696d67(3e|20|09|0a|0b|0c|0d|2f):HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
107+
"1:*:3c6f626a656374(3e|20|09|0a|0b|0c|0d|2f):HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
108+
"1:*:3c736372697074(3e|20|09|0a|0b|0c|0d|2f):HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
109+
"1:*:3c7461626c65(3e|20|09|0a|0b|0c|0d|2f):HTML data:CL_TYPE_ANY:CL_TYPE_HTML",
110110
"1:*:4d5a{60-300}50450000:PE:CL_TYPE_ANY:CL_TYPE_MSEXE",
111111
"1:*:504b0304:ZIP-SFX:CL_TYPE_ANY:CL_TYPE_ZIPSFX",
112112
"1:*:526172211a0700:RAR-SFX:CL_TYPE_ANY:CL_TYPE_RARSFX",

unit_tests/clamscan/hash_and_file_type_test.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,97 @@ def test_file_type_hint(self):
269269
'logo.png FileType: faketype',
270270
]
271271
self.verify_output(output.out, expected=expected_stdout, unexpected=unexpected_stdout)
272+
273+
274+
def test_html_file_type_tag_signatures_require_tag_boundary(self):
275+
self.step_name('Test that HTML file type tag signatures require a tag boundary.')
276+
277+
(TC.path_tmp / 'good.ldb').write_text(
278+
"logo.png.good;Engine:150-255,Target:0;0;fuzzy_img#af2ad01ed42993c7#0\n"
279+
)
280+
281+
def check_file_type(filename, contents, expected_type, unexpected_types=None):
282+
if unexpected_types is None:
283+
unexpected_types = []
284+
285+
testfile = TC.path_tmp / filename
286+
testfile.write_bytes(contents)
287+
288+
command = '{valgrind} {valgrind_args} {clamscan} -d {path_db} {testfile} --log-file-type'.format(
289+
valgrind=TC.valgrind, valgrind_args=TC.valgrind_args, clamscan=TC.clamscan,
290+
path_db=TC.path_tmp / 'good.ldb',
291+
testfile=testfile,
292+
)
293+
output = self.execute_command(command)
294+
295+
assert output.ec == 0 # clean
296+
297+
self.verify_output(
298+
output.out,
299+
expected=[
300+
'{}: OK'.format(filename),
301+
'{} FileType: {}'.format(filename, expected_type),
302+
],
303+
unexpected=[
304+
'{} FileType: {}'.format(filename, file_type)
305+
for file_type in unexpected_types
306+
]
307+
)
308+
309+
non_html_xml_files = {
310+
'iframe_prefix.xml': b'<?xml version="1.0"?><root><iframeView/></root>',
311+
'iframe_upper_prefix.xml': b'<?xml version="1.0"?><root><IFRAMEVIEW/></root>',
312+
'img_prefix.xml': b'<?xml version="1.0"?><root><imgData/></root>',
313+
'img_mixed_prefix.xml': b'<?xml version="1.0"?><root><ImgData/></root>',
314+
'img_upper_prefix.xml': b'<?xml version="1.0"?><root><IMGDATA/></root>',
315+
'object_prefix.xml': b'<?xml version="1.0"?><root><objectId/></root>',
316+
'object_mixed_prefix.xml': b'<?xml version="1.0"?><root><ObjectId/></root>',
317+
'object_upper_prefix.xml': b'<?xml version="1.0"?><root><OBJECTID/></root>',
318+
'script_prefix.xml': b'<?xml version="1.0"?><root><scriptlet/></root>',
319+
'script_mixed_prefix.xml': b'<?xml version="1.0"?><root><Scriptlet/></root>',
320+
'script_upper_prefix.xml': b'<?xml version="1.0"?><root><SCRIPTLET/></root>',
321+
'table_styles.xml': (
322+
b'<?xml version="1.0" encoding="UTF-8"?>'
323+
b'<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
324+
b'<dxfs count="0"/><tableStyles count="0" defaultTableStyle="TableStyleMedium2"/>'
325+
b'</styleSheet>'
326+
),
327+
'table_upper_prefix.xml': b'<?xml version="1.0"?><root><TABLESTYLES/></root>',
328+
}
329+
330+
for filename, contents in non_html_xml_files.items():
331+
check_file_type(filename, contents, 'CL_TYPE_TEXT_ASCII', ['CL_TYPE_HTML'])
332+
333+
html_tag_files = {
334+
'iframe_close.html': b'<iframe></iframe>',
335+
'iframe_space.html': b'<iframe src="sample"></iframe>',
336+
'iframe_slash.html': b'<iframe/>',
337+
'iframe_uppercase.html': b'<IFRAME></IFRAME>',
338+
'img_close.html': b'<img>sample',
339+
'img_space.html': b'<img src="sample">',
340+
'img_slash.html': b'<img/>',
341+
'img_mixedcase.html': b'<Img>sample',
342+
'img_uppercase.html': b'<IMG>sample',
343+
'object_close.html': b'<object></object>',
344+
'object_space.html': b'<object data="sample"></object>',
345+
'object_slash.html': b'<object/>',
346+
'object_mixedcase.html': b'<Object></Object>',
347+
'object_uppercase.html': b'<OBJECT></OBJECT>',
348+
'script_close.html': b'<script></script>',
349+
'script_space.html': b'<script type="text/javascript"></script>',
350+
'script_slash.html': b'<script/>',
351+
'script_mixedcase.html': b'<Script></Script>',
352+
'script_uppercase.html': b'<SCRIPT></SCRIPT>',
353+
'table_close.html': b'<table></table>',
354+
'table_space.html': b'<table class="sample"></table>',
355+
'table_tab.html': b'<table\tclass="sample"></table>',
356+
'table_lf.html': b'<table\nclass="sample"></table>',
357+
'table_vtab.html': b'<table\vclass="sample"></table>',
358+
'table_ff.html': b'<table\fclass="sample"></table>',
359+
'table_cr.html': b'<table\rclass="sample"></table>',
360+
'table_slash.html': b'<table/>',
361+
'table_uppercase.html': b'<TABLE></TABLE>',
362+
}
363+
364+
for filename, contents in html_tag_files.items():
365+
check_file_type(filename, contents, 'CL_TYPE_HTML')

0 commit comments

Comments
 (0)