Home Reply - EmoGigi
Post
Cancel

Reply - EmoGigi

An emoji search webpage is vulnerable to sql injection due to incorrect sanitizing of unicode characters.

I solved this challenge together whit @Teuler27.

Event Link: Reply CyberSecurity Challenge 2022/WEB200

Challenge Description

The web page of the challenge consists in a container showing a lot of emoji, divided in custom categories; they can be filtered using an Emoji Search Box. We are also provided a leak of the source code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def search():
    # [... SNIP ...] Init the variables here
    
    # Custom SQL filter
    query = sqlfilter(request.form['query'])
    
    # [... SNIP ...]

    if query is None:
        # [... SNIP ...] Return an error here
    else:
        # Normalize weird chars here
        norm = unicodedata.normalize("NFKD", query).encode('ascii', 'ignore').decode('ascii')
        
        # Custom HTML filter
        query = htmlfilter(norm)
        
        conn = sqlite.connect('./emoji.db')
        cur = conn.cursor()

        # Prefix:   f09f90
        # Range:    80;c0
        # Category: animals
        result = cur.execute("SELECT prefix,range,category,id FROM emoji WHERE category like '%" + query + "%'").fetchall()
        conn.close()

        # No Results
        if len(result) == 0:
            return index()

        # [... SNIP ...] Build the results table here
        for r in result:
            rng = r[1].split(';')
            emoji += '<div class="category"><div id="lh"></div>' + r[2].upper() + '<div id="rh"></div></div>'
            emoji += emoji_gen(bytes.fromhex(r[0]), int(rng[0], 16), int(rng[1], 16))
            
        # [... SNIP ...]

    return render_template('index.html', error=error, emoji=emoji, pages=pages, query=query), 200

Solution

The structure of the webpage immediately suggest a SQL Injection. However, if we search for standard SQL payloads like ', SELECT or WHERE into the box, instead of emoji categories we get a banner with the famous italian questioning hand emoji saying

This is probably due to the custom sqlfilter() functions which detects our payloads and returns the alert. However, from the source code we see that the imput is unicode-normalized only after sqlfilter is applied. This suggest a technique called Unicode Injection. Unicode is an international character encoding standard that provides a unique number for every character across languages and scripts; in contrast to ascii, which only supports 128 (or 256) characters from the standard english alphabet, unicode includes about 150000 of them. To be handled in some programs unicode can be normalized to ascii: every unicode character is mapped to an ascii one in some (obvious or less obvious) way.

Apparently, unicode payloads are often build using emojis (for example here), because the beahve weirdly when converted into ascii. This was probably the hint behind the theme of the challenge.

Since this code normalizes the input only after checking for SQL commands, we can try to bypass the filter using unicode characters. For example, if we put "SELèCT" in the form, the custom sqlfilter function will likely not recognize it as a valid SQL command. After passing the filter, however, it will be normalized into "SELeCT", which is a valid SQL command that will execute the query. In this way we can inject arbitrary commands in the form by finding suitable unicode alternative for every character. Luckily, there are many online tools to do that.

Now that we know how to perform the injection, we want to be able to retreive the results. This is not so hard in this case. If we look at the query, we ask for prefix, range, category and id, and these values are displayed in the table. From the source code we see that prefix, range and id have to satisfy some requirements, but we also have some standard values from them in the comments. On the other hand, category (which is displayed as the title of the table) can be any text. Moreover, multiple results are shown in different pages. This is a very handy surface for our attack.
From here, we can easily dump the whole database using standard SQL injection techinques. We used three unicode queries:

  1. xxx\uff07 uniòn sèlect \uff07f09f90\uff07,\uff0780;c0\uff07,name,1 FRòM sqlite_schema WHèRè name likè \uff07%, from which we found an interesting table named R3PLYCH4LL3NG3FL4G;
  2. looking closer at this table, xxx\uff07 uniòn sèlect \uff07f09f90\uff07,\uff0780;c0\uff07,name,1 FRòM PRAGMA_TABLE_INFO\uff08\uff07R3PLYCH4LL3NG3FL4G\uff07\uff09 WHèRè name likè \uff07% reveals that it has only one column, value;
  3. finally, xxx\uff07 uniòn sèlect \uff07f09f90\uff07,\uff0780;c0\uff07,value,1 FRòM R3PLYCH4LL3NG3FL4G WHèRè value likè \uff07% gives us the flag: {FLG:O0O0OP5_1_H4V3_B33N_PWN3D_(54DF4C3)!}.
This post is licensed under CC BY 4.0 by the author.