Reflected XSS in Tokopedia Train Ticket

Bypassing XSS filter via two reflected parameters.

Reflected XSS in Tokopedia Train Ticket


There was an XSS filter that would encode GET parameter if it contains < character followed by > character. The filter could be bypassed by splitting </script/> closing tag into </script/ and > in two reflected parameters.

Old Bug?

Back in May 2018, I found a reflected XSS in Tokopedia train ticket. It was a simple reflected XSS in JavaScript context. I reported to Tokopedia security team and they told me my report was duplicate. I didn't bother to check if it was already fixed or not.

In March this year, I was scrolling through my old emails and found the report. I re-tested, fuzzed here and there. Eventually, I found the vulnerability on the same page.

Tag Filtering

If you search a train ticket in Tokopedia, you will be redirected to a URL something like this https://tiket.tokopedia.com/kereta-api/search/Jakarta-Gambir-GMR/Bandung-Bandung-BD?adult=1&infant=0&trip=departure&dep_date=16-09-2019&ori=GMR&dest=BD. It stores all GET parameters to a JavaScript variable, dataJs.query.

Content of dataJs.query
All GET parameters are stored in dataJs.query.

It lives in the JavaScript context. So if you want to trigger an XSS you have to either:

  1. Break out of JavaScript context.
    Insert </script><script>alert(1)</script> in one of the parameters. This will malform the HTML parser to close the context, causing previous JavaScript execution to error (we don't care!) and start a new attacker-controlled script context.
  2. Break out of JavaScript variable, dataJs.query.
    Insert "}; alert(1); // in one of the parameters. This will cause JavaScript parser to close the variable, execute our controlled script directly, and ignore the rest.

My previous report was using the first method. The server didn't encode dangerous characters, such as < into &lt;. However, it encoded " into \", and \ into \\, so the second method couldn't be used.

Then I noticed a strange behavior. The encoding used in ori and dest parameters was different than the rest. Notice how in other parameters, " character got encoded into %22.

Different encoding was used.
Different encoding was used.

I tried with another dangerous character, >.

> was not encoded
> was not encoded in both parameters.

Interesting, it was not encoded in ori and dest parameters. What if I insert both < and > characters?

>< was not encoded
>< was not encoded.
<> was encoded.
<> was encoded.

Apparently the server did sanitize the parameters, but only if <.*> appears in the parameter!

Bypassing the Filter

First I thought yeah that's it, it can't be exploited. But then I was googling some XSS payload and found this awesome repository. It says:

You can use // to close a tag instead of >.

Let's try it.

An error occurred.
An error occurred.

Chrome threw an error: Uncaught SyntaxError: Invalid or unexpected token. Okay I knew I was making a progress. Then I tried to insert XSS payload.

XSS payload inserted.
XSS payload inserted.

It didn't work, JavaScript parser doesn't consider it as a closing tag. I re-read the XSS payload repository again and found this on "Bypass tag blacklisting" section, </script/>. We knew the fact that we couldn't insert < and > characters on a same parameter because it would be encoded. But what if we separate </script/ and > on different parameters (i.e. ori and dest)? There would be other characters between them, in this case </script/","dest":">. Is that still a valid closing tag?

XSS auditor highlighting the XSS payload.
XSS auditor highlighting the XSS payload.

Turned out it was a valid closing tag! Chrome XSS auditor blocked the page, indicating there was a reflected XSS. Then I tried it on Firefox, and it worked! Here is the full payload I used: https://tiket.tokopedia.com/kereta-api/search/Jakarta-Gambir-GMR/Bandung-Bandung-BD?dep_date=26-06-2019&adult=1&infant=0&trip=departure&ori=</script//&dest=><svg/onload=alert(document.location.href))//.

Proof of concept.
Proof of concept.

Going Deeper

Session cookie in Tokopedia (named _SID_Tokopedia) is HTTP-only so we can't steal the session via XSS. But it turned out that the session cookie was accidentally stored in a JavaScript variable named dataSession.session.cookies. This defeats the purpose of HTTP-only attribute on the cookie. By exploiting the XSS, an attacker can steal victim's session as demonstrated on proof of concept video below.


  • 28/03/2019 - Reported the vulnerability to Tokopedia security team.
  • 08/04/2019 - Sent a follow-up email. The vulnerability was fixed and the report was valid with high severity.
  • 11/06/2019 - Tokopedia rewarded IDR 3.000.000 and a certificate.

Related posts