Skip to content

Instantly share code, notes, and snippets.

@surfsoft
Last active May 20, 2020 09:35
Show Gist options
  • Save surfsoft/c7ce6a7545cc11c410eba68bb18e5e58 to your computer and use it in GitHub Desktop.
Save surfsoft/c7ce6a7545cc11c410eba68bb18e5e58 to your computer and use it in GitHub Desktop.
Selectively sanitising JSON string values in Kotlin

Selectively sanitising JSON string values in Kotlin

You've a JSON document you are consuming over REST, and you want to sanitise one or more string values to make sure nobody injects any pesky JavaScript that might end up as content on a web page somewhere? Look no further than a custom JSON deserialiser coupled with the OWSAP endcoder library:

The code

import com.fasterxml.jackson.core.*
import com.fasterxml.jackson.databind.*
import org.owasp.encoder.Encode
import java.text.ParseException

class SanitisingStringDeserializer: JsonDeserializer<String>() {

    override fun deserialize(parser: JsonParser, context: DeserializationContext): String {
        try {
            return when (parser.currentToken()) {
                JsonToken.VALUE_STRING -> Encode.forHtml(parser.text)
                else -> throw ParseException("current token must be a string value", 0)
            }
        } catch (e: ParseException) {
            throw RuntimeException(e)
        }
    }

}

The significant bit is Encode.forHtml(parser.text). A call to Encode.forHtml will take a string value and encode all your special characters. No more injection!

The string

<script>alert('Hello world');</script>

is turned into

&lt;script&gt;alert(&#39;Hello world&#39;);&lt;/script&gt;

by this deserialiser - all special characters, including quotes, are encoded. Testing this code is also pretty simple. Create a data class and you can run a simple deserialisation, verifying the contents of the data class:

data class TestTarget(@JsonDeserialize(using = SanitisingStringDeserializer::class) val myValue: String)

@Test
fun `Deserialise malicious content`() {
    val instance = Jackson.asA("""{ "myValue": "<script>" }""", TestTarget::class)
    assertThat(instance).isEqualTo(TestTarget("&lt;script&gt"))
}

In use

To use it on a data class just annotate the field(s):

data class MyRequestDetails(val id: UUID,
                            @JsonDeserialize(using = SanitisingStringDeserializer::class) val freeTextNotes: String?)

The deserialiser works for both nullable and non-nullable data types. Of course you might prefer to compare the raw string to the output of Encode.forHtml and raise a parse exception if they aren't identical, instead of storing a sanitised version of the value you received.

More information on the OWASP java encoder library is available at the github repo. If you want a more generic JSON sanitiser then take a look at the OWASP JSON sanitiser which sanitises JSON documents before they are deserialised.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment