Security Issues Caused by Delimiters

Summary0x01 Preface. I recently read P's discussion of delimiter security and a Django security advisory, so I studied delimiter-related vulnerabilities. 0x02 Delimiters. A delimiter marks a boundary. If // is the delimiter, //panda// tells the computer to start at the first //, read panda, and stop at the next…

cveDelimiterDjango

0x01 Preface

I recently read P's discussion of delimiter security and a Django security advisory, so I took the opportunity to study delimiter-related vulnerabilities.

0x02 Delimiters

A delimiter marks boundaries. Suppose the delimiter is//, then//panda//means the computer starts at the first//begins, andpandastring, and ends at the next//end. Delimiters take many forms across languages: separators, comment markers, and more. In PHP, for example,<<<as a delimiter; MySQL's default statement delimiter is; . Python delimiter examples appear in the table below:

( ) [ ] { }
, : . ` = ;
+= -= *= /= //= %=
&= |= ^= >>= <<= **=

Delimiters are used widely, which is exactly why they can create security vulnerabilities.

0x03 Security Vulnerabilities Caused by Delimiters

1. Authentication Bypass

Suppose the PHP code is:

PHP
SELECT * FROM Users WHERE ((Username ='$username') AND (Password=MD5('$password')))

This is clearly login-validation code. Without checks for special characters—especially delimiters—authentication may be bypassed. In this example, set:

PHP
username = 1' or '1' = '1')) /*
password = test

The value passed into the SQL statement should therefore be:

PHP
SELECT * FROM Users WHERE ((Username='1' or '1' = '1'))/*') AND (Password=MD5('$password')))

This keeps the condition true and bypasses authentication.

2. Privilege Escalation

See this vulnerability:CVE-2003-1350 . If registration fields are insufficiently filtered and the application's database delimiter can be supplied, privilege escalation may result. List Site Pro uses |to delimit database fields without validating delimiters in user input, allowing an attacker to change any account's password.

A similar flaw also appeared in Poster V2, which contains a file namedmem.php, which stores user fields such as username, password, email, and privileges. It manages application users; an example follows:

PHP
<? panda|12345678|panda@cnpanda.net|admin| test|5211314|test@test.com|normal| ?>

panda is an administrator and test a normal user. When editing a profile, use index.phpOpen Edit Account and enter the user's login information.

The file example shows that its delimiter is|. If edited profile content is not filtered, a user can elevate their privileges to administrator through the edited field:

PHP
Username: test Password: 5211314 Email: test@test.com |admin|

When this information is stored inmem.phpfile, it becomes:

PHP
test|5211314|test@test.com|admin|normal|

This achieves privilege escalation. Applications rarely store user records in files this way anymore, but the technique remains instructive.

3. SQL Injection

A representative case is Django SQL injection, CVE-2020-7471. On February 3, 2020, Django published an advisory stating thatdjango.contrib.postgres.aggregates.StringAggThe aggregate function is vulnerable. A crafted delimiter enables SQL injection. Locate the function:

PYTHON
class StringAgg(OrderableAggMixin, Aggregate):
    function = 'STRING_AGG'
    template = "%(function)s(%(distinct)s%(expressions)s, '%(delimiter)s'%(ordering)s)"
    allow_distinct = True

    def __init__(self, expression, delimiter, **extra):
        super().__init__(expression, delimiter=delimiter, **extra)

    def convert_value(self, value, expression, connection):
        if not value:
            return ''
        return value

The documentation explains the function as follows:

TEXT
class StringAgg(expression, delimiter)
Returns the input values concatenated into a string, separated by the delimiter string.
delimiter
Required argument. Needs to be a string.

Put simply, the user supplies a delimiter and the function joins queried or supplied values with that custom delimiter.

For example, set the delimiter to-, with this table:

uid username private
1 panda admin
2 test normal
3 hello normal
4 world normal
TEXT
SELECT "vul_app_info"."gender", STRING_AGG("vul_app_info"."name", '-') AS "mydefinedname" FROM "djsqltest_info" GROUP BY "djsqltest_info"."gender" LIMIT 1 OFFSET 1 --

Querying by private and aggregating username displays this result in Django:

0.png
JSON
{'private':'admin','username':'panda'}
{'private':'normal','username':'test-hello-world'}

The advisory states that a particular delimiter causes injection. Fuzzing identifies the single quote; setting it as the delimiter produces an error:

1.png

The query is:

SQL
SELECT "test_sql_userinfo"."private", STRING_AGG("test_sql_userinfo"."username", \'\'\') AS "username" FROM "test_sql_userinfo" GROUP BY "test_sql_userinfo"."private"

The supplied delimiter is escaped as\', which reaches PostgreSQL as:

SQL
SELECT "test_sql_userinfo"."private", STRING_AGG("test_sql_userinfo"."username", ''') AS "username" FROM "test_sql_userinfo" GROUP BY "test_sql_userinfo"."private"

Three single quotes make the SQL invalid. Because the supplied delimiter reaches the SQL statement, a carefully chosen delimiter may enable injection.

The vulnerability can now be demonstrated:

Define a database named django_sql

There is a table namedtest_sql_userinfo, containing:

2.png

There is a table namedsql_admin, containing:

3.png

Under normal conditions, set the delimiter to:-, producing:

4.png

But if the delimiter is:') AS "uname" FROM "test_sql_admin" group by "test_sql_admin"."id"--

The result is:

5.png

Other data is extracted successfully.

Although the chance of controlling this injection is small, it remains an SQL injection vulnerability and a classic delimiter-related case.

4. Denial of Service

In CVE-2008-5185, a delimiter fails to close a tag, causing an infinite loop and denial of service. The central code is:

PHP
 function parse_code () {

       ...
        $code = str_replace("\r\n", "\n", $this->source);
        $code = str_replace("\r", "\n", $code);
        // Add spaces for regular expression matching and line numbers
        $code = "\n" . $code . "\n";
       ...

        if ($this->strict_mode) {
            // Break the source into bits. Each bit will be a portion of the code
            // within script delimiters - for example, HTML between < and >
            $parts = array(0 => array(0 => '', 1 => ''));
            $k = 0;
            for ($i = 0; $i < $length; ++$i) {
                foreach ($this->language_data['SCRIPT_DELIMITERS'] as $delimiters) {
                    foreach ($delimiters as $open => $close) {
                        // Get the next little bit for this opening string
                        $open_strlen = strlen($open);
                        $check = substr($code, $i, $open_strlen);
                        // If it matches...
                        if ($check == $open) {
                            // We start a new block with the highlightable
                            // code in it
                            ++$k;
                            $parts[$k][0] = $open;
                            $close_i = strpos($code, $close, $i + $open_strlen)  + strlen($close);
                            if ($close_i === false) {
                                $close_i = $length - 1;
                            }
                            $parts[$k][7] = substr($code, $i, $close_i - $i);
                            $i = $close_i - 1;
                            ++$k;
                            $parts[$k][0] = '';
                            $parts[$k][8] = '';

                            // No point going around again...
                            continue 3;
                        }
                    }
                }
                // only non-highlightable text reaches this point
                $parts[$k][9] .= $code[$i];
            }
        }
			...

Focus on the loop:$this->language_data['SCRIPT_DELIMITERS']defines an opening marker such as<) and an ending marker such as>) and assigns those characters to$openand$close, then reads the code block's first character and searches for an opening marker. If found, it evaluates:

PHP
   $close_i = strpos($code, $close, $i + $open_strlen)  + strlen($close);

$closeis the ending marker. If the block contains no ending marker, then$close_ibecomes 1 and is passed to: $i = $close_i - 1;, then$ibecomes 0, so the loop restarts indefinitely and causes denial of service.

0x04 Conclusion

Beyond these examples, delimiters can also cause code execution, as in CVE-2007-5178, but the source is too old to locate for analysis. In a broader sense, DOM XSS and CRLF injection can also be viewed as delimiter flaws. This is my interpretation; other techniques are welcome for discussion.

0x05 References

https://www.codeleading.com/article/8787703343/

https://owasp.org/www-community/attacks/Parameter_Delimiter

https://www.djangoproject.com/weblog/2020/feb/03/security-releases/

https://xz.aliyun.com/t/7218

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2008-5185

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2003-1350