If you only filter for script tags, yes.
For example:
<img src="x" onerror="alert(1)" />
Or
<div style="position: absolute; top: 0; left: 0; width: 9999px; height: 9999px; z-index: 9999" onmouseenter="alert(1)"></div>
Or an infinite number of other vectors.
You need to properly filter the entire contents when you write them out to the page, using whatever encoding function is appropriate for the output encoding (e.g. HTML, XML, JSON, etc.) and available in the language you're using.
Examples for various languages:
I also recommend reading the XSS Prevention Cheat Sheet.
EDIT: After your update, yes, this is still possible.
Imagine you filter <
and >
, but then put content in an attribute:
<span class="$class">
I can then use this payload:
" style="display: block; position: absolute; top: 0; left: 0; width: 9999px; height: 9999px; z-index: 9999" onmouseenter="alert(1)" foo="
This turns your span into a block element which fills the entire page, executing the onmouseenter
event as soon as the user's mouse enters the page.
Ok, so you're still not convinced.
People have spent a LOT of time making XSS work in all sorts of simple filter-based environments. The number of tricks is astounding, getting into all sorts of madness like tricking the browser to misinterpret the page as having a different character encoding so that UTF-8 or other Unicode codepoints sent as input later get treated as distinct ASCII codepoints containing HTML-affecting characters like >
or "
.
As an example, let's say you're using UTF-8 as your character set on your server. I put the 㱁 character into your page, but then through various tricks (this is browser dependent and complicated, but can be achieved trivially if you've got a header-splitting bug on your server) trick your browser into assuming the encoding is simple ASCII. The UTF-8 codepoint for 㱁 is U+3C41, which when interpreted as ASCII is simply <A
. Your browser sees this as the opening of a tag.
Example payload:
㱁 href="x" style="display: block; ..." onmouseenter="alert(1)"㸠foo
The huge number of pitfalls makes it impossible to handle yourself. You need a proper XSS filtering library which is encoding-aware to take these issues into account.
<
and>
isn't the only thing happening. You need to figure out what is actually happening to bypass it.